mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-26 12:14:56 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86da250e2c | ||
|
|
347a8c2f70 | ||
|
|
9624200663 | ||
|
|
18b1e95350 | ||
|
|
407877a4d5 | ||
|
|
7541c6339a | ||
|
|
31f1efd4f5 | ||
|
|
2477f80a43 | ||
|
|
cf94fb3642 | ||
|
|
2210043b87 | ||
|
|
10872b3227 | ||
|
|
cb324cfb9a | ||
|
|
4ee5849a04 | ||
|
|
fa216849e5 | ||
|
|
df6e35e330 | ||
|
|
96bb9aac36 | ||
|
|
7cec2fe280 | ||
|
|
02937f8852 | ||
|
|
51f0cc2b67 | ||
|
|
253927008c | ||
|
|
9778a4ccfc | ||
|
|
d0e2741832 | ||
|
|
3a5e2fba88 | ||
|
|
85940c8045 | ||
|
|
a0b599b308 | ||
|
|
d43ccda9a7 | ||
|
|
675b83a3ef | ||
|
|
dfdfcc060d | ||
|
|
143fb4f423 | ||
|
|
b7eb12017c | ||
|
|
bd1c6c1506 | ||
|
|
efb7f4f05d | ||
|
|
5475fe661d | ||
|
|
8f62822a33 | ||
|
|
0aa1a9e324 | ||
|
|
7a77bb1c4a | ||
|
|
4576b69e30 | ||
|
|
4fd6eb597d | ||
|
|
257dacd7bb | ||
|
|
c88b7b19b3 | ||
|
|
4fbfb5d0d0 | ||
|
|
67c678270e | ||
|
|
049db9015b | ||
|
|
9dde225b5e | ||
|
|
3a0a22f4cc | ||
|
|
b4a4b15835 | ||
|
|
dca2eab152 | ||
|
|
f9f212442c | ||
|
|
83793aadde | ||
|
|
f7a705c40a | ||
|
|
f5f6edcf5a | ||
|
|
9cb1048c66 | ||
|
|
522a7eb554 | ||
|
|
072833acd5 | ||
|
|
a5aa08e408 | ||
|
|
12d98a1732 | ||
|
|
a6c2c2a10b | ||
|
|
fbe1914226 | ||
|
|
64a744055f | ||
|
|
0007e47c07 | ||
|
|
4708cce8bd | ||
|
|
3eb9156e60 | ||
|
|
2990f9328a | ||
|
|
a0befd65a0 | ||
|
|
872ad8badf | ||
|
|
0970e080d9 | ||
|
|
52fe1461d5 | ||
|
|
d5b33adb88 | ||
|
|
ec84a148f0 | ||
|
|
77d5d4c921 | ||
|
|
58dab48156 | ||
|
|
7abb406d0f | ||
|
|
b941dcd5c5 | ||
|
|
b7adf9b52a | ||
|
|
28f7d83a74 | ||
|
|
34ee1ef3d6 | ||
|
|
e518517456 | ||
|
|
7d0662f1bc | ||
|
|
e3751d29d5 | ||
|
|
04b9ccca22 | ||
|
|
1a17165227 | ||
|
|
b6d968f00c | ||
|
|
a69987a711 | ||
|
|
492328474d | ||
|
|
673a2c581f | ||
|
|
9ef42621e0 | ||
|
|
f5865886b2 | ||
|
|
473e2a1cb2 | ||
|
|
146cf1d1c0 | ||
|
|
ab73055ef3 | ||
|
|
3b83543592 | ||
|
|
d4ac96c24d | ||
|
|
0612129a88 | ||
|
|
9bd5903f1d | ||
|
|
d4eae0e251 | ||
|
|
4d7ea04dd8 | ||
|
|
653fab6b41 | ||
|
|
f4c30b746e | ||
|
|
923e5004e8 | ||
|
|
b14a1e25f0 | ||
|
|
5a3b1123f6 | ||
|
|
6a9494b683 | ||
|
|
63c5658a2b | ||
|
|
fe087cdce9 | ||
|
|
af7efd980c | ||
|
|
2cb325c981 | ||
|
|
66435a7158 | ||
|
|
48417ba911 | ||
|
|
f39c4c8909 | ||
|
|
2a7e720f12 | ||
|
|
ec22250d9b | ||
|
|
a7889756a9 | ||
|
|
9ac67770eb | ||
|
|
3330bca4be | ||
|
|
31f0409bf9 | ||
|
|
ebf9ee089b | ||
|
|
88cf329490 | ||
|
|
bba29d32ce | ||
|
|
2f1f408882 | ||
|
|
46a9e42b8f | ||
|
|
58d3abe165 | ||
|
|
03e737829a | ||
|
|
95d8881454 | ||
|
|
a50a52ca91 | ||
|
|
50371ef309 | ||
|
|
6192d1b53a | ||
|
|
daa9175139 | ||
|
|
8a65611a8c | ||
|
|
6c8a0aa768 | ||
|
|
787b15551d | ||
|
|
2c896e7e5c | ||
|
|
0e49214c7e | ||
|
|
914606a046 | ||
|
|
b30417e908 | ||
|
|
5b8d4cdf68 | ||
|
|
ba06d135c3 | ||
|
|
f0d3d7e81d | ||
|
|
d1bd15291e | ||
|
|
d00b7f4300 | ||
|
|
ebc909b61b | ||
|
|
537fb39b21 | ||
|
|
e106151072 | ||
|
|
257de0709f | ||
|
|
774a819e76 | ||
|
|
ff1412f29d | ||
|
|
c6606c714f | ||
|
|
de3d4e3019 | ||
|
|
2f49e63d5b | ||
|
|
988b40f70d | ||
|
|
b5fdd48d76 | ||
|
|
f4f73a8dfa | ||
|
|
74711c686f | ||
|
|
78c994f7cc | ||
|
|
2d5d668863 | ||
|
|
87279f3518 | ||
|
|
bd10a03da0 | ||
|
|
d138119d24 | ||
|
|
73019665fa | ||
|
|
66824e7757 | ||
|
|
7fe6b29218 | ||
|
|
ebdf973af9 | ||
|
|
71dd7a96c7 | ||
|
|
886dcb3f5d | ||
|
|
37751a2875 | ||
|
|
45347bd44c | ||
|
|
8c4521af03 | ||
|
|
0d2a8db626 | ||
|
|
e791a795b4 | ||
|
|
03d846529d | ||
|
|
607651a3ff | ||
|
|
0714e0dd8f | ||
|
|
224d18589f | ||
|
|
2fd7191836 | ||
|
|
853d162b55 | ||
|
|
762a68a0cf | ||
|
|
1da098671e | ||
|
|
f852a4db0f | ||
|
|
ddea940690 | ||
|
|
292e43b41b | ||
|
|
915e923e35 | ||
|
|
e6ac3af454 | ||
|
|
3adaf8dba9 | ||
|
|
1777cc6e86 | ||
|
|
f9f623bd60 | ||
|
|
fe95707b4e | ||
|
|
a631ee1e1e | ||
|
|
493666dac6 | ||
|
|
46ef4e33c8 | ||
|
|
cafaecfc56 | ||
|
|
47606a71ee | ||
|
|
eedfedd13b | ||
|
|
87d841d81b | ||
|
|
5153eebc9b | ||
|
|
70154f6c23 | ||
|
|
d3577b7877 | ||
|
|
199e0a330f | ||
|
|
a8da1b3334 | ||
|
|
63c19b4631 | ||
|
|
1f471e1cf3 | ||
|
|
ef28c37529 | ||
|
|
cd7e1b9eb7 | ||
|
|
226d476856 | ||
|
|
21965e0e03 | ||
|
|
40ab4a8d3d | ||
|
|
e84d0574fa | ||
|
|
1a64d37fc7 | ||
|
|
9cca843a9b | ||
|
|
ecbf79135f | ||
|
|
96eb48902b | ||
|
|
079c99c47f | ||
|
|
6d2a76b609 | ||
|
|
24e0950ca2 | ||
|
|
98214ce1a4 | ||
|
|
7eed7eab36 | ||
|
|
f0fb5c01a2 | ||
|
|
a92aa3bf49 | ||
|
|
b12cc9fd49 | ||
|
|
a61c3a2636 | ||
|
|
9fd435059f | ||
|
|
3156ac9791 | ||
|
|
b946f33189 | ||
|
|
1e0b7054e6 | ||
|
|
46e04e857c | ||
|
|
4abdeaddbf | ||
|
|
029af1c376 | ||
|
|
c02f2ff4bb | ||
|
|
f74fec8a7e | ||
|
|
50f8e8891b | ||
|
|
d67b1dbd53 | ||
|
|
b0eeb7d67b | ||
|
|
2c6c75cbad | ||
|
|
241388b8a0 | ||
|
|
820b497f42 | ||
|
|
88d8435a02 | ||
|
|
f3f177f3ed | ||
|
|
bfa171cd97 | ||
|
|
19c94ce218 | ||
|
|
5e5033a0f3 | ||
|
|
411a269991 | ||
|
|
bca6aab5a5 | ||
|
|
330e2ff74e | ||
|
|
eb5848478c | ||
|
|
5bb361b1d2 | ||
|
|
0d40364089 | ||
|
|
16c4fe1f37 | ||
|
|
3070e8572d | ||
|
|
baf4d8af27 | ||
|
|
d5acf2e2a7 | ||
|
|
22f8cb14d5 | ||
|
|
e28b3ca91d | ||
|
|
73d2b395d1 | ||
|
|
e556a487e0 | ||
|
|
5728fa1cea | ||
|
|
b461edabd9 | ||
|
|
c9e666babc | ||
|
|
18e64397fc | ||
|
|
3c8418d324 | ||
|
|
784a1580ce | ||
|
|
6a0ac69761 | ||
|
|
7c06279a8c | ||
|
|
aec6f85f55 | ||
|
|
52e3be5c58 | ||
|
|
01a664ad06 | ||
|
|
94933047a7 | ||
|
|
cd6005c86f | ||
|
|
6d14ed3a18 | ||
|
|
0fb5db59d3 | ||
|
|
20b274bb2b | ||
|
|
00b289a0f4 | ||
|
|
9818f9849b | ||
|
|
df1c23222e | ||
|
|
f18ac3e094 | ||
|
|
d8596fae27 | ||
|
|
85209ac4fa | ||
|
|
a58e82b301 | ||
|
|
35f485558b | ||
|
|
93365fcbd2 | ||
|
|
e40a44ba70 | ||
|
|
1eed770238 | ||
|
|
511ef4137f | ||
|
|
479164129b | ||
|
|
08746115e2 | ||
|
|
587aad75c1 | ||
|
|
218428662e | ||
|
|
04fc209644 | ||
|
|
53bedd1eb5 | ||
|
|
a0b43ae8e0 | ||
|
|
866a7e2823 | ||
|
|
2055d41a9f | ||
|
|
1ef29c1b3b | ||
|
|
e95dbb90a7 | ||
|
|
cf18c932a4 | ||
|
|
a730afc44d | ||
|
|
94296c9697 | ||
|
|
667707df50 | ||
|
|
90b6fab3bb | ||
|
|
e47dbcbdc5 | ||
|
|
2a15f8d567 | ||
|
|
b5d397c74f | ||
|
|
3d6b373c8f | ||
|
|
ed236dddeb | ||
|
|
596fb1e88a | ||
|
|
ca0cd5040d | ||
|
|
5b84acbc14 | ||
|
|
c12838e2c3 | ||
|
|
fa3e6ce271 | ||
|
|
743f48b8e0 | ||
|
|
421c16b2bb | ||
|
|
3054a7f09e | ||
|
|
2de96bd54e | ||
|
|
f815c315e7 | ||
|
|
e59ee00154 | ||
|
|
b07745ab50 | ||
|
|
2468a8fb38 | ||
|
|
ed38a4102e | ||
|
|
b1c66f6544 | ||
|
|
8f79a0564f | ||
|
|
2e93cde929 | ||
|
|
e53d05637a | ||
|
|
9d75d35365 | ||
|
|
2ea8d85cff | ||
|
|
ea81a22529 | ||
|
|
526f3aa31c | ||
|
|
62d66201c9 | ||
|
|
f32e14478f | ||
|
|
ca019799b9 | ||
|
|
c6fb9ac3d0 | ||
|
|
c80a88a407 | ||
|
|
f2e19ca3ff | ||
|
|
16c463d2e9 | ||
|
|
6a99518048 | ||
|
|
3b242ec68b | ||
|
|
7409166409 | ||
|
|
ca66b84f7d | ||
|
|
dc1c732f11 | ||
|
|
61148f1323 | ||
|
|
136e3e353d | ||
|
|
c70b80e4e1 | ||
|
|
cba4c9d85d | ||
|
|
3e588065dc | ||
|
|
35cb27dbe6 | ||
|
|
d6b9f76e9c | ||
|
|
a9d51c0e87 | ||
|
|
4b163232e3 | ||
|
|
e003236a03 | ||
|
|
de7745370c | ||
|
|
21786eec8e | ||
|
|
036b213b2b | ||
|
|
61f5f0b725 | ||
|
|
d864d72470 | ||
|
|
7ca5962d2d | ||
|
|
8ce1562f0d | ||
|
|
49d9016275 | ||
|
|
cdc7380d00 | ||
|
|
e645b33b6c | ||
|
|
30f71a8bc4 | ||
|
|
faa030385c | ||
|
|
feb3b5db96 | ||
|
|
508b67d6f5 | ||
|
|
133aec777d | ||
|
|
389ed4e099 | ||
|
|
bc40aa5442 | ||
|
|
5fb9aca2e1 | ||
|
|
c68df310b8 | ||
|
|
70ce6c5ad3 | ||
|
|
460738f11b | ||
|
|
a09ec8988e | ||
|
|
c46aa050ae | ||
|
|
348db16245 | ||
|
|
c0ca6a53be | ||
|
|
e58d622d5f | ||
|
|
bcb294620f | ||
|
|
15937a9c5c | ||
|
|
fa160cb9a3 | ||
|
|
137d6484e2 | ||
|
|
e7c34c26cb | ||
|
|
dfb7d928c2 | ||
|
|
4bb9faaf50 | ||
|
|
a856971b6d | ||
|
|
4a88ad5ca9 | ||
|
|
17ecb0a459 | ||
|
|
afd39abc6f | ||
|
|
4babcff8de | ||
|
|
17d48b1d04 | ||
|
|
6ab20ed9a3 | ||
|
|
a0e5494569 | ||
|
|
f62c733990 | ||
|
|
d283e3167f | ||
|
|
94050d44e4 | ||
|
|
dee97cbf22 | ||
|
|
be01471968 | ||
|
|
a74c44c463 | ||
|
|
81e53d8375 | ||
|
|
ca626abcac | ||
|
|
b5e7730ac7 | ||
|
|
33b920ed2c | ||
|
|
b5b234fab2 | ||
|
|
af89d2446b | ||
|
|
791ad49651 | ||
|
|
063b26fece | ||
|
|
add079a949 | ||
|
|
6e59ad8a0d | ||
|
|
f4862b646d | ||
|
|
91fa767efb | ||
|
|
5e78711467 | ||
|
|
b4a622cfd6 | ||
|
|
8c118bf4cc | ||
|
|
728ebdaafa | ||
|
|
3a186976d7 | ||
|
|
2e232b3304 | ||
|
|
d72645bf91 | ||
|
|
7d5bcec92d | ||
|
|
215ea28db0 | ||
|
|
9b76d60b38 | ||
|
|
63ccf8199f | ||
|
|
6bc6022aa9 | ||
|
|
b68db66e97 | ||
|
|
94e1c5a41b | ||
|
|
ab16220861 | ||
|
|
c0eb3e56b4 | ||
|
|
5591aa5a0d | ||
|
|
6f946f5eea | ||
|
|
39cd5b8323 | ||
|
|
ca96db714a | ||
|
|
a7f97bf2f2 | ||
|
|
e579355b01 | ||
|
|
cfbaf7f1ae | ||
|
|
ce26234ace | ||
|
|
c28dc45d4c | ||
|
|
98b6c5e16a | ||
|
|
1d9d1111e1 | ||
|
|
2a0b55b30b | ||
|
|
1e3b167208 | ||
|
|
f7821b757c | ||
|
|
c352486abb | ||
|
|
4120b9bcc0 | ||
|
|
f7725f2eeb | ||
|
|
be06c3620c | ||
|
|
272644e39f | ||
|
|
55ef3afea4 | ||
|
|
9e07c742ff | ||
|
|
cd2e50c2c4 | ||
|
|
fdef2b8643 | ||
|
|
ce55924a02 | ||
|
|
fbca54bcaf | ||
|
|
41c25d60d6 | ||
|
|
a9b5bb351f | ||
|
|
51ed3efcd1 | ||
|
|
67cffc8db5 | ||
|
|
72abaa043b | ||
|
|
b3a511f9c5 | ||
|
|
8d1f6377c4 | ||
|
|
22dddc2eeb | ||
|
|
daab527743 | ||
|
|
dd49c0791f | ||
|
|
4b539d4b95 | ||
|
|
5eabfe5da2 | ||
|
|
36b7c8d60d | ||
|
|
aa37b728ad | ||
|
|
62085e5215 | ||
|
|
d036f8a3f0 | ||
|
|
fb5e2b8c2f | ||
|
|
4505b00733 | ||
|
|
c485b8264e | ||
|
|
8edd710c00 | ||
|
|
3984e9f5cc | ||
|
|
d4fa794c83 | ||
|
|
3892e0199e | ||
|
|
67f5de624e | ||
|
|
10234b07c4 | ||
|
|
36a83e1452 | ||
|
|
34df4ec1fd | ||
|
|
69cb94f3fb | ||
|
|
7204d71136 | ||
|
|
283f590349 | ||
|
|
27735f293b | ||
|
|
a560334fbb | ||
|
|
cd1707034c | ||
|
|
5ce1fc3c2a | ||
|
|
c78fe425a0 | ||
|
|
a58bb71c19 | ||
|
|
fc9a9593d2 | ||
|
|
eb1fdf56e6 | ||
|
|
ab960df8a9 | ||
|
|
08048bd5bb | ||
|
|
800517ac16 | ||
|
|
690eff4fa2 | ||
|
|
e47b5b775a | ||
|
|
c15e2af897 | ||
|
|
d8536b011c | ||
|
|
a2239c2b4b | ||
|
|
d3c02331d4 | ||
|
|
d707841166 | ||
|
|
affd82a885 | ||
|
|
8322c82790 | ||
|
|
08708435a7 | ||
|
|
1c6036c3ac | ||
|
|
6c3cb6f3eb | ||
|
|
a123874ac5 | ||
|
|
7931929a42 | ||
|
|
cc4a4fb2ab | ||
|
|
3d211d89c5 | ||
|
|
fbf1ccee45 | ||
|
|
0bd43bcae3 | ||
|
|
b809665cad | ||
|
|
463b49f1bd | ||
|
|
a881aad5c6 | ||
|
|
aae0995cd2 | ||
|
|
26b3f227d9 | ||
|
|
665770d602 | ||
|
|
fcafaa73d6 | ||
|
|
079b9ec2c1 | ||
|
|
7bd9a85897 | ||
|
|
2efc5d9fce | ||
|
|
1cb094e059 | ||
|
|
27fe9c8f01 | ||
|
|
9178fbd153 | ||
|
|
b2b43ab0cd | ||
|
|
1415d187eb | ||
|
|
a65078b858 | ||
|
|
7107afae5c | ||
|
|
f6a807c5a4 | ||
|
|
47b11dd19e | ||
|
|
61cdc93405 | ||
|
|
7a52bd643c | ||
|
|
63e8052e4b | ||
|
|
782d0cda46 | ||
|
|
72803eb7f8 | ||
|
|
c7fd221ab7 | ||
|
|
492185e35f | ||
|
|
845d7b08e1 | ||
|
|
49bac31d2d | ||
|
|
9baf2d4ba4 | ||
|
|
1becd26a8f | ||
|
|
ffa983552f | ||
|
|
13d0b67c00 | ||
|
|
bda8e90733 | ||
|
|
fed2057a33 | ||
|
|
a27f62f89f | ||
|
|
d53902844c | ||
|
|
9870b80c65 | ||
|
|
cde25a8b82 | ||
|
|
1b7f036199 | ||
|
|
b80157b7ea | ||
|
|
f9311f0b30 | ||
|
|
bdd933f597 | ||
|
|
ee55d38b55 | ||
|
|
62162ff3e4 | ||
|
|
98f66c8867 | ||
|
|
c132191cad | ||
|
|
d73aa1ad09 | ||
|
|
d5bc58c38d | ||
|
|
51a80e4844 | ||
|
|
54f15a8fe3 | ||
|
|
ce0826e285 | ||
|
|
b21a8b2193 | ||
|
|
8bb49642da | ||
|
|
3f78df86ac | ||
|
|
c30f7a5258 | ||
|
|
7ed94d1918 | ||
|
|
dc513a9a15 | ||
|
|
8ce6cf61b7 | ||
|
|
e63f7fe33a | ||
|
|
b589e0cdf4 | ||
|
|
2b87f909ce | ||
|
|
ec7d0b1abd | ||
|
|
5bb3ede46d | ||
|
|
5105717f78 | ||
|
|
9d1e356709 | ||
|
|
3d7192d9fa | ||
|
|
1b3cbb17c1 | ||
|
|
f1f03e4378 | ||
|
|
625a2a3652 | ||
|
|
33a31d9308 | ||
|
|
5e9bd1f05e | ||
|
|
75d9a6c844 | ||
|
|
179761fd99 | ||
|
|
b783813e54 | ||
|
|
4b29689a42 | ||
|
|
9c4853471c | ||
|
|
81e21a3a0b | ||
|
|
bec6ea00ea | ||
|
|
ed32e3be0f | ||
|
|
83fae879bf | ||
|
|
2169e6d77f | ||
|
|
850e0a5922 | ||
|
|
322ed3cc6f | ||
|
|
6d4d53c247 | ||
|
|
e6f8d40942 | ||
|
|
7e051206ad | ||
|
|
76d913796c | ||
|
|
4c2609df2c | ||
|
|
f79bd71fd4 | ||
|
|
5d4f5b6000 | ||
|
|
fe7c245a6b | ||
|
|
f24bebaca8 | ||
|
|
08afdfa8b6 | ||
|
|
05f7d65fc9 | ||
|
|
a194e07545 | ||
|
|
dbb8a5bbf4 | ||
|
|
d3005df019 | ||
|
|
ef9b103a00 | ||
|
|
f0a2924a7c | ||
|
|
f109030cd0 | ||
|
|
eebdd06532 | ||
|
|
ca2fc71a01 | ||
|
|
e61e371c16 | ||
|
|
d5f07f5b9b | ||
|
|
0a8d5dde51 | ||
|
|
5646caa572 | ||
|
|
c7e5573d08 | ||
|
|
b9a226d109 | ||
|
|
fcf8b86288 | ||
|
|
a8b08c6784 | ||
|
|
1fb6f391a1 | ||
|
|
fcbc36ecde | ||
|
|
70771f29ce | ||
|
|
121391504f | ||
|
|
8455742440 | ||
|
|
5b786a5089 | ||
|
|
a6e74e7707 | ||
|
|
acf72a6109 |
No files matched your search
@@ -1,19 +1,16 @@
|
|||||||
Copyright (c) 2004-2010 Sergey Lyubka
|
Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||||
|
Copyright (c) 2013 Cesanta Software Limited
|
||||||
|
All rights reserved
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
This code is dual-licensed: you can redistribute it and/or modify
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
it under the terms of the GNU General Public License version 2 as
|
||||||
in the Software without restriction, including without limitation the rights
|
published by the Free Software Foundation. For the terms of this
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
license, see http://www.gnu.org/licenses.
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
You are free to use this code under the terms of the GNU General
|
||||||
all copies or substantial portions of the Software.
|
Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU General Public License for more details.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
Alternatively, you can license this code under a commercial
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
license, as set out in http://cesanta.com/products.html.
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
# This file is part of Mongoose project, http://code.google.com/p/mongoose
|
|
||||||
# $Id: Makefile 473 2009-09-02 11:20:06Z valenok $
|
|
||||||
|
|
||||||
PROG= mongoose
|
|
||||||
|
|
||||||
all:
|
|
||||||
@echo "make (linux|bsd|solaris|mac|windows|mingw|cygwin)"
|
|
||||||
|
|
||||||
# Possible COPT values: (in brackets are rough numbers for 'gcc -O2' on i386)
|
|
||||||
# -DHAVE_MD5 - use system md5 library (-2kb)
|
|
||||||
# -DNDEBUG - strip off all debug code (-5kb)
|
|
||||||
# -DDEBUG - build debug version (very noisy) (+7kb)
|
|
||||||
# -DNO_CGI - disable CGI support (-5kb)
|
|
||||||
# -DNO_SSL - disable SSL functionality (-2kb)
|
|
||||||
# -DNO_SSL_DL - link against system libssl library (-1kb)
|
|
||||||
# -DCONFIG_FILE=\"file\" - use `file' as the default config file
|
|
||||||
# -DSSL_LIB=\"libssl.so.<version>\" - use system versioned SSL shared object
|
|
||||||
# -DCRYPTO_LIB=\"libcrypto.so.<version>\" - use system versioned CRYPTO so
|
|
||||||
# -DUSE_LUA - embed Lua in Mongoose (+100kb)
|
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
|
||||||
### UNIX build: linux, bsd, mac, rtems
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
# To build with Lua, download and unzip Lua 5.2.1 source code into the
|
|
||||||
# mongoose directory, and then add $(LUA_FLAGS) to CFLAGS below
|
|
||||||
LUA = lua-5.2.1/src
|
|
||||||
LUA_FLAGS = -DUSE_LUA -I$(LUA) -L$(LUA) -llua -lm
|
|
||||||
|
|
||||||
CFLAGS = -std=c99 -O2 -W -Wall -pedantic $(COPT)
|
|
||||||
LIB = lib$(PROG).so$(MONGOOSE_LIB_SUFFIX)
|
|
||||||
|
|
||||||
# Make sure that the compiler flags come last in the compilation string.
|
|
||||||
# If not so, this can break some on some Linux distros which use
|
|
||||||
# "-Wl,--as-needed" turned on by default in cc command.
|
|
||||||
# Also, this is turned in many other distros in static linkage builds.
|
|
||||||
linux:
|
|
||||||
$(CC) mongoose.c -shared -fPIC -fpic -o $(LIB) -Wl,-soname,$(LIB) -ldl -pthread $(CFLAGS)
|
|
||||||
$(CC) mongoose.c main.c -o $(PROG) -ldl -pthread $(CFLAGS)
|
|
||||||
|
|
||||||
bsd:
|
|
||||||
$(CC) mongoose.c -shared -pthread -fpic -fPIC -o $(LIB) $(CFLAGS)
|
|
||||||
$(CC) mongoose.c main.c -pthread -o $(PROG) $(CFLAGS)
|
|
||||||
|
|
||||||
mac:
|
|
||||||
$(CC) mongoose.c -pthread -o $(LIB) -flat_namespace -bundle -undefined suppress $(CFLAGS)
|
|
||||||
$(CC) mongoose.c main.c -DUSE_COCOA -pthread $(CFLAGS) -framework Cocoa -ObjC -arch i386 -arch x86_64 -o $(PROG)
|
|
||||||
V=`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`; DIR=dmg/$(PROG).app && rm -rf $$DIR && mkdir -p $$DIR/Contents/{MacOS,Resources} && install -m 644 build/mongoose_*.png $$DIR/Contents/Resources/ && install -m 644 build/Info.plist $$DIR/Contents/ && install -m 755 $(PROG) $$DIR/Contents/MacOS/ && ln -fs /Applications dmg/ ; hdiutil create $(PROG)_$$V.dmg -volname "Mongoose $$V" -srcfolder dmg -ov #; rm -rf dmg
|
|
||||||
|
|
||||||
solaris:
|
|
||||||
$(CC) mongoose.c -pthread -lnsl -lsocket -fpic -fPIC -shared -o $(LIB) $(CFLAGS)
|
|
||||||
$(CC) mongoose.c main.c -pthread -lnsl -lsocket -o $(PROG) $(CFLAGS)
|
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
|
||||||
### WINDOWS build: Using Visual Studio or Mingw
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
# Using Visual Studio 6.0. To build Mongoose:
|
|
||||||
# o Set MSVC variable below to where VS 6.0 is installed on your system
|
|
||||||
# o Run "PATH_TO_VC6\bin\nmake windows"
|
|
||||||
|
|
||||||
MSVC = e:/vc6
|
|
||||||
CYA = e:/cyassl-2.0.0rc2
|
|
||||||
#DBG = /Zi /DDEBUG /Od
|
|
||||||
DBG = /DNDEBUG /O1
|
|
||||||
CL = $(MSVC)/bin/cl /MD /TC /nologo $(DBG) /Gz /W3 /DNO_SSL_DL \
|
|
||||||
/I$(MSVC)/include /DUSE_LUA /I$(LUA)
|
|
||||||
GUILIB= user32.lib shell32.lib
|
|
||||||
LINK = /link /incremental:no /libpath:$(MSVC)/lib /machine:IX86 \
|
|
||||||
/subsystem:windows ws2_32.lib advapi32.lib cyassl.lib lua.lib
|
|
||||||
CYAFL = /c /I $(CYA)/include -I $(CYA)/include/openssl /I$(MSVC)/INCLUDE \
|
|
||||||
/I $(CYA)/ctaocrypt/include /D _LIB /D OPENSSL_EXTRA
|
|
||||||
|
|
||||||
LUA_SOURCES = $(LUA)/lapi.c $(LUA)/lcode.c $(LUA)/lctype.c \
|
|
||||||
$(LUA)/ldebug.c $(LUA)/ldo.c $(LUA)/ldump.c \
|
|
||||||
$(LUA)/lfunc.c $(LUA)/lgc.c $(LUA)/llex.c \
|
|
||||||
$(LUA)/lmem.c $(LUA)/lobject.c $(LUA)/lopcodes.c \
|
|
||||||
$(LUA)/lparser.c $(LUA)/lstate.c $(LUA)/lstring.c \
|
|
||||||
$(LUA)/ltable.c $(LUA)/ltm.c $(LUA)/lundump.c \
|
|
||||||
$(LUA)/lvm.c $(LUA)/lzio.c $(LUA)/lauxlib.c \
|
|
||||||
$(LUA)/lbaselib.c $(LUA)/lbitlib.c $(LUA)/lcorolib.c \
|
|
||||||
$(LUA)/ldblib.c $(LUA)/liolib.c $(LUA)/lmathlib.c \
|
|
||||||
$(LUA)/loslib.c $(LUA)/lstrlib.c $(LUA)/ltablib.c \
|
|
||||||
$(LUA)/loadlib.c $(LUA)/linit.c
|
|
||||||
LUA_OBJECTS = $(LUA_SOURCES:%.c=%.o)
|
|
||||||
|
|
||||||
CYA_SOURCES = $(CYA)/src/cyassl_int.c $(CYA)/src/cyassl_io.c \
|
|
||||||
$(CYA)/src/keys.c $(CYA)/src/tls.c $(CYA)/src/ssl.c \
|
|
||||||
$(CYA)/ctaocrypt/src/aes.c $(CYA)/ctaocrypt/src/arc4.c \
|
|
||||||
$(CYA)/ctaocrypt/src/asn.c $(CYA)/ctaocrypt/src/coding.c \
|
|
||||||
$(CYA)/ctaocrypt/src/ctc_asm.c $(CYA)/ctaocrypt/src/ctc_misc.c \
|
|
||||||
$(CYA)/ctaocrypt/src/cyassl_memory.c $(CYA)/ctaocrypt/src/des3.c \
|
|
||||||
$(CYA)/ctaocrypt/src/dh.c $(CYA)/ctaocrypt/src/dsa.c \
|
|
||||||
$(CYA)/ctaocrypt/src/ecc.c $(CYA)/ctaocrypt/src/hc128.c \
|
|
||||||
$(CYA)/ctaocrypt/src/hmac.c $(CYA)/ctaocrypt/src/integer.c \
|
|
||||||
$(CYA)/ctaocrypt/src/md4.c $(CYA)/ctaocrypt/src/md5.c \
|
|
||||||
$(CYA)/ctaocrypt/src/pwdbased.c $(CYA)/ctaocrypt/src/rabbit.c \
|
|
||||||
$(CYA)/ctaocrypt/src/random.c $(CYA)/ctaocrypt/src/ripemd.c \
|
|
||||||
$(CYA)/ctaocrypt/src/rsa.c $(CYA)/ctaocrypt/src/sha.c \
|
|
||||||
$(CYA)/ctaocrypt/src/sha256.c $(CYA)/ctaocrypt/src/sha512.c \
|
|
||||||
$(CYA)/ctaocrypt/src/tfm.c
|
|
||||||
|
|
||||||
cyassl.lib:
|
|
||||||
$(CL) /Fo$(CYA)/ $(CYA_SOURCES) $(CYAFL) $(DEF)
|
|
||||||
$(MSVC)/bin/lib $(CYA)/*.obj /out:$@
|
|
||||||
|
|
||||||
lua.lib:
|
|
||||||
$(CL) /c /Fo$(LUA)/ $(LUA_SOURCES)
|
|
||||||
$(MSVC)/bin/lib $(LUA_SOURCES:%.c=%.obj) /out:$@
|
|
||||||
|
|
||||||
windows: cyassl.lib lua.lib
|
|
||||||
$(MSVC)/bin/rc build\res.rc
|
|
||||||
$(CL) /Ibuild main.c mongoose.c /GA $(LINK) build\res.res \
|
|
||||||
$(GUILIB) /out:$(PROG).exe
|
|
||||||
$(CL) mongoose.c /GD $(LINK) /DLL /DEF:build\dll.def /out:$(PROG).dll
|
|
||||||
|
|
||||||
# Build for Windows under MinGW
|
|
||||||
#MINGWDBG= -DDEBUG -O0 -ggdb
|
|
||||||
MINGWDBG= -DNDEBUG -Os
|
|
||||||
MINGWOPT= -W -Wall -mthreads -Wl,--subsystem,console $(MINGWDBG) -DHAVE_STDINT $(GCC_WARNINGS) $(COPT)
|
|
||||||
#MINGWOPT= -W -Wall -mthreads -Wl,--subsystem,windows $(MINGWDBG) -DHAVE_STDINT $(GCC_WARNINGS) $(COPT)
|
|
||||||
mingw:
|
|
||||||
windres build\res.rc build\res.o
|
|
||||||
$(CC) $(MINGWOPT) mongoose.c -lws2_32 \
|
|
||||||
-shared -Wl,--out-implib=$(PROG).lib -o $(PROG).dll
|
|
||||||
$(CC) $(MINGWOPT) -build mongoose.c main.c build\res.o \
|
|
||||||
-lws2_32 -ladvapi32 -o $(PROG).exe
|
|
||||||
|
|
||||||
# Build for Windows under Cygwin
|
|
||||||
#CYGWINDBG= -DDEBUG -O0 -ggdb
|
|
||||||
CYGWINDBG= -DNDEBUG -Os
|
|
||||||
CYGWINOPT= -W -Wall -mthreads -Wl,--subsystem,console $(CYGWINDBG) -DHAVE_STDINT $(GCC_WARNINGS) $(COPT)
|
|
||||||
cygwin:
|
|
||||||
windres ./build/res.rc ./build/res.o
|
|
||||||
$(CC) $(CYGWINOPT) mongoose.c -lws2_32 \
|
|
||||||
-shared -Wl,--out-implib=$(PROG).lib -o $(PROG).dll
|
|
||||||
$(CC) $(CYGWINOPT) -Ibuild mongoose.c main.c ./build/res.o \
|
|
||||||
-lws2_32 -ladvapi32 -o $(PROG).exe
|
|
||||||
|
|
||||||
##########################################################################
|
|
||||||
### Manuals, cleanup, test, release
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
man:
|
|
||||||
groff -man -T ascii mongoose.1 | col -b > mongoose.txt
|
|
||||||
groff -man -T ascii mongoose.1 | less
|
|
||||||
|
|
||||||
# "TEST=unit make test" - perform unit test only
|
|
||||||
# "TEST=embedded" - test embedded API by building and testing test/embed.c
|
|
||||||
# "TEST=basic_tests" - perform basic tests only (no CGI, SSI..)
|
|
||||||
tests:
|
|
||||||
perl test/test.pl $(TEST)
|
|
||||||
|
|
||||||
release: clean
|
|
||||||
F=mongoose-`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`.tgz ; cd .. && tar -czf x mongoose/{LICENSE,Makefile,examples,test,build,mongoose.c,mongoose.h,mongoose.1} && mv x mongoose/$$F
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf *.o *.core $(PROG) *.obj *.so $(PROG).txt *.dSYM *.tgz $(PROG).exe *.dll *.lib build/res.o build/res.RES
|
|
||||||
@@ -1,42 +1,60 @@
|
|||||||
Project Mission
|
# Mongoose Web Server
|
||||||
---------------
|
|
||||||
|
|
||||||
Project mission is to provide simple, functional, embeddable web server to
|
Mongoose is easy to use, powerful, embeddable web server.
|
||||||
make it easy for application and device developers to implement web interface for their
|
* [Downloads](http://cesanta.com/#downloads)
|
||||||
application and devices, and to offer a simple web development environment.
|
* [Documentation](http://cesanta.com/#docs,Usage.md)
|
||||||
|
* [Mailing list](http://groups.google.com/group/mongoose-users)
|
||||||
|
|
||||||
Overview
|
# Features
|
||||||
--------
|
|
||||||
|
|
||||||
To accomplish it's mission, Mongoose keeps balance on functionality and
|
- Works on Windows, Mac, UNIX/Linux, iPhone, Android, and many other platforms
|
||||||
simplicity by carefully selected list of features:
|
- CGI, SSL, Digest auth, Websocket, WEbDAV, Resumed download, URL rewrite, file blacklist, IP-based ACL, Windows service
|
||||||
|
- Scripting and database support ([Lua](http://lua.org) +
|
||||||
|
[Sqlite](http://sqlite.org), see
|
||||||
|
[page.lp](https://github.com/cesanta/mongoose/blob/master/build/test/page.lp) ),
|
||||||
|
which provides ready to go, powerful web development platform in
|
||||||
|
one single-click executable with no dependencies
|
||||||
|
- Simple and clean
|
||||||
|
[embedding API](https://github.com/cesanta/mongoose/blob/master/mongoose.h).
|
||||||
|
The source is in single
|
||||||
|
[mongoose.c](https://github.com/cesanta/mongoose/blob/master/mongoose.c) file
|
||||||
|
to make embedding easy
|
||||||
|
- Extremely lightweight, has a core of under 40kB and tiny runtime footprint
|
||||||
|
- Asyncronouns, non-blocking core supporting single- or multi-threaded usage
|
||||||
|
- On the market since 2004, stable, mature and tested,
|
||||||
|
has several man-years invested in continuous improvement and refinement
|
||||||
|
- Professional support and consultancy available from
|
||||||
|
[Cesanta Software](http://cesanta.com), developer of Mongoose
|
||||||
|
|
||||||
- Liberal, commercial-friendly [MIT license](http://en.wikipedia.org/wiki/MIT_License)
|
# Acknowledgements
|
||||||
- Works on Windows, Mac, UNIX, iPhone, Android, and many other platforms
|
|
||||||
- Support for CGI, SSL, SSI, Digest (MD5) authorization, Websocket, WEbDAV
|
|
||||||
- Lua server pages (PHP-like functionality using Lua), see [page.lp](https://github.com/valenok/mongoose/blob/master/test/page.lp)
|
|
||||||
- Resumed download, URL rewrite, IP-based ACL, Windows service
|
|
||||||
- Excluding files from serving by URI pattern (file blacklist)
|
|
||||||
- Download speed limit based on client subnet or URI pattern
|
|
||||||
- Small footprint: executable size is 50 kB on Linux 2.6 i386 system
|
|
||||||
- 130 kilobytes Windows executable with all of the above and no dependencies
|
|
||||||
- Simple and clean embedding API ([mongoose.h](https://github.com/valenok/mongoose/blob/master/mongoose.h)). The source is in single [mongoose.c](https://github.com/valenok/mongoose/blob/master/mongoose.c) file to make things easy.
|
|
||||||
- Embedding examples: [hello.c](https://github.com/valenok/mongoose/blob/master/examples/hello.c), [post.c](https://github.com/valenok/mongoose/blob/master/examples/post.c), [upload.c](https://github.com/valenok/mongoose/blob/master/examples/upload.c), [websocket.c](https://github.com/valenok/mongoose/blob/master/examples/websocket.c)
|
|
||||||
- Extensive documentation in form of [User Manual](https://github.com/valenok/mongoose/blob/master/UserManual.md)
|
|
||||||
|
|
||||||
Questions can be asked at
|
Mongoose made better thanks to the contribution of following people:
|
||||||
[mongoose-users@google.com](http://groups.google.com/group/mongoose-users) mailing list.
|
|
||||||
|
|
||||||
|
Arnout Vandecappelle, Benoît Amiaux, Boris Pek, Cody Hanson, Colin Leitner,
|
||||||
|
Daniel Oaks, Eric Bakan, Erik Oomen, Filipp Kovalev, Ger Hobbelt,
|
||||||
|
Hendrik Polczynski, Igor Okulist, Jay, Joe Mucchiello, John Safranek,
|
||||||
|
José Miguel Gonçalves, Shueng Chuan, Katerina Blinova, Konstantin Sorokin,
|
||||||
|
Marin Atanasov, Matt Healy, Mitch Hendrickson, Nigel Stewart, Pavel Khlebovich,
|
||||||
|
Sebastian Reinhard, Stefan Doehla, abadc0de, nullable.type,
|
||||||
|
T.Barmann, D.Hughes, J.C.Sloan, R.Romeo, L.E.Spencer, S.Kotay, R.M.Shorter,
|
||||||
|
W.Mar, J.Wilander, Santa from Memphis, S.Davies, C.Beck,
|
||||||
|
O.M.Vilhunen, C.Radik, G.Woodcock, M.Szczepkowski,
|
||||||
|
Eternal Lands Dev Team, T.Tollet, C.Tangerino, G.Karsai, A.Bourgett,
|
||||||
|
C.Blakemore, D.Fonaryov, T.Andrle, O.IJsselmuiden, R.Womack, M.Tomlinson,
|
||||||
|
A.Slåttå, L.Farrell, J.D.P.Ballestero, V.Albaev, B.Harker, T.Scheffel, H.Klein,
|
||||||
|
R.Merit, T.Bennett, H.Solis, A.Zincenko, M.S., S.Krul, K.Cooke, S.McCallum,
|
||||||
|
F.Morenius, and 10 others.
|
||||||
|
|
||||||
Keep Sergey happy
|
# Licensing
|
||||||
-----------------
|
|
||||||
|
|
||||||
Since 2004, Mongoose is being constantly improved by me, Sergey Lyubka, a software engineer
|
Mongoose is released under
|
||||||
from Galway, Ireland. My other software I give to the community for free is
|
[GNU GPL v.2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
|
||||||
[Super Light Regular Expression library](http://code.google.com/p/slre).
|
Businesses have an option to get non-restrictive, royalty-free commercial
|
||||||
|
license and professional support from
|
||||||
|
[Cesanta Software](http://cesanta.com).
|
||||||
|
|
||||||
|
[Frozen JSON parser](https://github.com/cesanta/frozen),
|
||||||
If you feel grateful for the stuff I've done, you can buy me a book from my
|
[Super Light Regular Expression library](https://github.com/cesanta/slre),
|
||||||
[Amazon wishlist](http://amzn.com/w/1OC2ZCPTQYIEP?sort=priority). Many thanks to all who
|
[Super Light DNS Resolver](https://github.com/cesanta/sldr)
|
||||||
already did so: T.Barmann, D.Hughes, J.C.Sloan, R.Romeo and 4 others.
|
are other projects by Cesanta Software, developed with the same philosophy
|
||||||
Appreciated guys, you keep my brains going!
|
of functionality and simplicity.
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
|
|
||||||
**NOTE: THIS MANUAL IS WORK IN PROGRESS**
|
|
||||||
|
|
||||||
Overview
|
|
||||||
--------
|
|
||||||
|
|
||||||
Mongoose is small and easy to use web server. It is self-contained, and does
|
|
||||||
not require any external software to run.
|
|
||||||
|
|
||||||
On Windows, mongoose iconifies itself to the system tray icon when started.
|
|
||||||
Right-click on the icon pops up a menu, where it is possible to stop
|
|
||||||
mongoose, or configure it, or install it as Windows service.
|
|
||||||
|
|
||||||
On UNIX and Mac, mongoose is a command line utility. Running `mongoose` in
|
|
||||||
terminal, optionally followed by configuration parameters
|
|
||||||
(`mongoose [OPTIONS]`) or configuration file name
|
|
||||||
(`mongoose [config_file_name]`) starts the
|
|
||||||
web server. Mongoose does not detach from terminal. Pressing `Ctrl-C` keys
|
|
||||||
would stop the server.
|
|
||||||
|
|
||||||
When started, mongoose first searches for the configuration file.
|
|
||||||
If configuration file is specified explicitly in the command line, i.e.
|
|
||||||
`mongoose path_to_config_file`, then specified configuration file is used.
|
|
||||||
Otherwise, mongoose would search for file `mongoose.conf` in the same directory
|
|
||||||
where binary is located, and use it.
|
|
||||||
Configuration file is a sequence of lines, each line containing
|
|
||||||
command line argument name and it's value. Empty lines, and lines beginning
|
|
||||||
with `#`, are ignored. Here is the example of `mongoose.conf` file:
|
|
||||||
|
|
||||||
# mongoose.conf file
|
|
||||||
document_root c:\www
|
|
||||||
listening_ports 8080,8043s
|
|
||||||
ssl_certificate c:\mongoose\ssl_cert.pem
|
|
||||||
|
|
||||||
When configuration file is processed, mongoose process command line arguments,
|
|
||||||
if they are specified. Command line arguments therefore can override
|
|
||||||
configuration file settings. Command line arguments must start with `-`.
|
|
||||||
For example, if `mongoose.conf` has line
|
|
||||||
`document_root /var/www`, and mongoose has been started as
|
|
||||||
`mongoose -document_root /etc`, then `/etc` directory will be served as
|
|
||||||
document root, because command line options take priority over
|
|
||||||
configuration file.
|
|
||||||
|
|
||||||
Mongoose can also be used to modify `.htpasswd` passwords file:
|
|
||||||
|
|
||||||
mongoose -A <htpasswd_file> <realm> <user> <passwd>
|
|
||||||
|
|
||||||
Usage Examples
|
|
||||||
--------------
|
|
||||||
|
|
||||||
- How to share a Windows folder: copy mongoose executable to a folder and
|
|
||||||
double-click the executable. The folder should be accessible via
|
|
||||||
[http://localhost:8080](http://localhost:8080) in any browser.
|
|
||||||
- How to start mongoose at UNIX startup time in daemon mode, serving
|
|
||||||
directory `/var/www`: put this line in the system startup script,
|
|
||||||
`/path/to/mongoose -listening_ports 80 -document_root /var/www &`
|
|
||||||
|
|
||||||
Command Line Options
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Common Problems
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Embedding
|
|
||||||
---------
|
|
||||||
|
|
||||||
Other Resources
|
|
||||||
---------------
|
|
||||||
- Presentation made by Arnout Vandecappelle at FOSDEM 2011 on 2011-02-06
|
|
||||||
in Brussels, Belgium, called
|
|
||||||
"Creating secure web based user interfaces for Embedded Devices"
|
|
||||||
([pdf](http://mind.be/content/110206_Web-ui.pdf) |
|
|
||||||
[odp](http://mind.be/content/110206_Web-ui.odp))
|
|
||||||
- Linux Journal article by Michel J.Hammel, 2010-04-01, called
|
|
||||||
[Mongoose: an Embeddable Web Server in C](http://www.linuxjournal.com/article/10680)
|
|
||||||
+9
-12
@@ -2,17 +2,14 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key> <string>Mongoose</string>
|
||||||
<string>mongoose</string>
|
<key>CFBundlePackageType</key> <string>APPL</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundleTypeRole</key> <string>None</string>
|
||||||
<string>APPL</string>
|
<key>CFBundleIconFile</key> <string>mongoose.icns</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>LSUIElement</key> <true/>
|
||||||
<string>None</string>
|
<key>RunAtLoad</key> <true/>
|
||||||
<key>CFBundleIconFiles</key>
|
<key>Label</key> <string>com.kolkin.mongoose</string>
|
||||||
<array>
|
<key>ProgramArguments</key> <array> </array>
|
||||||
<string>mongoose_16x16.png</string>
|
<key>KeepAlive</key> <true/>
|
||||||
</array>
|
|
||||||
<key>LSUIElement</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
+165
@@ -0,0 +1,165 @@
|
|||||||
|
# This Makefile is part of Mongoose web server project,
|
||||||
|
# https://github.com/valenok/mongoose
|
||||||
|
#
|
||||||
|
# This Makefile is GNU make compatible. You can get GNU Make from
|
||||||
|
# http://gnuwin32.sourceforge.net/packages/make.htm
|
||||||
|
|
||||||
|
PROG = mongoose
|
||||||
|
EXE_SUFFIX =
|
||||||
|
CFLAGS = -std=c99 -O2 -W -Wall -pedantic -pthread -pipe -I. -I.. $(CFLAGS_EXTRA)
|
||||||
|
VERSION = $(shell perl -lne \
|
||||||
|
'print $$1 if /define\s+MONGOOSE_VERSION\s+"(\S+)"/' ../mongoose.h)
|
||||||
|
VDIR = mongoose-$(VERSION)
|
||||||
|
|
||||||
|
# Stock windows binary builds with Lua and YASSL library.
|
||||||
|
YASSL = ../../cyassl-2.4.6
|
||||||
|
YASSL_FLAGS = -I $(YASSL) -I $(YASSL)/cyassl \
|
||||||
|
-D _LIB -D OPENSSL_EXTRA -D HAVE_ERRNO_H \
|
||||||
|
-D HAVE_GETHOSTBYNAME -D HAVE_INET_NTOA -D HAVE_LIMITS_H \
|
||||||
|
-D HAVE_MEMSET -D HAVE_SOCKET -D HAVE_STDDEF_H -D HAVE_STDLIB_H \
|
||||||
|
-D HAVE_STRING_H -D HAVE_SYS_STAT_H -D HAVE_SYS_TYPES_H
|
||||||
|
YASSL_SOURCES = \
|
||||||
|
$(YASSL)/src/internal.c $(YASSL)/src/io.c $(YASSL)/src/keys.c \
|
||||||
|
$(YASSL)/src/ssl.c $(YASSL)/src/tls.c $(YASSL)/ctaocrypt/src/hmac.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/random.c $(YASSL)/ctaocrypt/src/sha.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/sha256.c $(YASSL)/ctaocrypt/src/logging.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/error.c $(YASSL)/ctaocrypt/src/rsa.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/des3.c $(YASSL)/ctaocrypt/src/asn.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/coding.c $(YASSL)/ctaocrypt/src/arc4.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/md4.c $(YASSL)/ctaocrypt/src/md5.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/dh.c $(YASSL)/ctaocrypt/src/dsa.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/pwdbased.c $(YASSL)/ctaocrypt/src/aes.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/md2.c $(YASSL)/ctaocrypt/src/ripemd.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/sha512.c $(YASSL)/src/sniffer.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/rabbit.c $(YASSL)/ctaocrypt/src/misc.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/tfm.c $(YASSL)/ctaocrypt/src/integer.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/ecc.c $(YASSL)/src/ocsp.c $(YASSL)/src/crl.c \
|
||||||
|
$(YASSL)/ctaocrypt/src/hc128.c $(YASSL)/ctaocrypt/src/memory.c
|
||||||
|
|
||||||
|
TINY_SOURCES = ../mongoose.c main.c
|
||||||
|
LUA_SOURCES = $(TINY_SOURCES) lua_5.2.1.c
|
||||||
|
LUA_SQLITE_SOURCES = $(LUA_SOURCES) sqlite3.c lsqlite3.c
|
||||||
|
BIG_SOURCES = $(TINY_SOURCES) lua_5.2.1.c sqlite3.c lsqlite3.c $(YASSL_SOURCES)
|
||||||
|
|
||||||
|
SQLITE_FLAGS = -DTHREADSAFE=1 -DSQLITE_ENABLE_FTS3 \
|
||||||
|
-DSQLITE_ENABLE_FTS3_PARENTHESIS
|
||||||
|
LUA_FLAGS = -DUSE_LUA
|
||||||
|
LUA_SQLITE_FLAGS = $(SQLITE_FLAGS) $(LUA_FLAGS) -DUSE_LUA_SQLITE3
|
||||||
|
BIG_FLAGS = -DTHREADSAFE=1 -DSQLITE_ENABLE_FTS3 \
|
||||||
|
-DSQLITE_ENABLE_FTS3_PARENTHESIS \
|
||||||
|
-DUSE_LUA -DUSE_LUA_SQLITE3 -DUSE_SSL -DUSE_CYASSL $(YASSL_FLAGS)
|
||||||
|
|
||||||
|
# Using Visual Studio 6.0. To build Mongoose:
|
||||||
|
# Set MSVC variable below to where VS 6.0 is installed on your system
|
||||||
|
# Run "PATH_TO_VC6\bin\nmake windows"
|
||||||
|
MSVC = ../../vc6
|
||||||
|
#DBG = /Zi /Od
|
||||||
|
DBG = /DNDEBUG /O1 $(CFLAGS_EXTRA)
|
||||||
|
CL = $(MSVC)/bin/cl /MD /TC /nologo $(DBG) /W3 /I$(MSVC)/include /I.. /I.
|
||||||
|
LINK_FLAGS = /incremental:no /libpath:$(MSVC)/lib /machine:IX86 \
|
||||||
|
user32.lib shell32.lib comdlg32.lib ws2_32.lib advapi32.lib
|
||||||
|
LINK = $(MSVC)/bin/link $(LINK_FLAGS)
|
||||||
|
|
||||||
|
ifeq ($(OS), Windows_NT)
|
||||||
|
EXE_SUFFIX = .exe
|
||||||
|
else
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
ifeq ($(UNAME_S), Linux)
|
||||||
|
CFLAGS += -ldl -lm
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: mongoose$(EXE_SUFFIX)
|
||||||
|
|
||||||
|
# Make sure that the compiler flags come last in the compilation string.
|
||||||
|
# If not so, this can break some on some Linux distros which use
|
||||||
|
# "-Wl,--as-needed" turned on by default in cc command.
|
||||||
|
# Also, this is turned in many other distros in static linkage builds.
|
||||||
|
$(PROG): $(TINY_SOURCES)
|
||||||
|
$(CC) $(TINY_SOURCES) -o $@ $(CFLAGS)
|
||||||
|
|
||||||
|
$(PROG)-lua: $(TINY_SOURCES) $(LUA_SOURCES)
|
||||||
|
$(CC) $(LUA_SOURCES) -o $@ $(LUA_FLAGS) $(CFLAGS)
|
||||||
|
|
||||||
|
$(PROG)-lua-sqlite: $(LUA_SQLITE_SOURCES)
|
||||||
|
$(CC) $(LUA_SQLITE_SOURCES) -o $@ $(LUA_SQLITE_FLAGS) $(CFLAGS)
|
||||||
|
|
||||||
|
$(PROG)-lua-sqlite-ssl: $(BIG_SOURCES)
|
||||||
|
$(CC) $(BIG_SOURCES) -o $@ $(BIG_FLAGS) $(CFLAGS)
|
||||||
|
|
||||||
|
$(PROG)-big: $(BIG_SOURCES)
|
||||||
|
$(CC) $(BIG_SOURCES) -o $@ $(CFLAGS) $(BIG_FLAGS)
|
||||||
|
|
||||||
|
unix_unit_test: $(LUA_SOURCES) Makefile ./test/unit_test.c
|
||||||
|
$(CC) ./test/unit_test.c lua_5.2.1.c $(CFLAGS) -g -O0 -o t && ./t
|
||||||
|
|
||||||
|
# Windows build
|
||||||
|
$(PROG)-lua-sqlite.exe: $(LUA_SQLITE_SOURCES)
|
||||||
|
$(MSVC)/bin/rc res.rc
|
||||||
|
$(CL) $(LUA_SQLITE_SOURCES) $(LUA_SQLITE_FLAGS) /link $(LINK_FLAGS) res.res /out:$@
|
||||||
|
|
||||||
|
$(PROG)-lua-sqlite-ssl.exe: $(BIG_SOURCES)
|
||||||
|
$(MSVC)/bin/rc res.rc
|
||||||
|
$(CL) $(BIG_SOURCES) $(BIG_FLAGS) /link $(LINK_FLAGS) res.res /out:$@
|
||||||
|
|
||||||
|
$(PROG)-lua.exe: $(LUA_SOURCES)
|
||||||
|
$(MSVC)/bin/rc res.rc
|
||||||
|
$(CL) $(LUA_SOURCES) $(LUA_FLAGS) /link $(LINK_FLAGS) res.res /out:$@
|
||||||
|
|
||||||
|
$(PROG).exe: $(TINY_SOURCES)
|
||||||
|
$(MSVC)/bin/rc res.rc
|
||||||
|
$(CL) $(TINY_SOURCES) /link $(LINK_FLAGS) res.res /out:$@
|
||||||
|
|
||||||
|
$(PROG).dll: $(TINY_SOURCES) Makefile
|
||||||
|
$(CL) ../mongoose.c /Gz /link $(LINK_FLAGS) /DLL /DEF:dll.def /out:$@
|
||||||
|
|
||||||
|
# This is broken now due to SSL exclusion
|
||||||
|
windows_unit_test.exe: ../mongoose.c Makefile
|
||||||
|
$(CL) ./test/unit_test.c lua_5.2.1.c \
|
||||||
|
/link /libpath:$(MSVC)/lib advapi32.lib /out:$@
|
||||||
|
./$@
|
||||||
|
|
||||||
|
# MacOS build with Cocoa GUI
|
||||||
|
# For codesign to work in non-interactive mode, unlock login keychain:
|
||||||
|
# security unlock ~/Library/Keychains/login.keychain
|
||||||
|
# See e.g. http://lists.apple.com/archives/apple-cdsa/2008/Jan/msg00027.html
|
||||||
|
macos: $(BIG_SOURCES)
|
||||||
|
DIR=dmg/Mongoose.app
|
||||||
|
rm -rf dmg
|
||||||
|
mkdir -p dmg/Mongoose.app/Contents/{MacOS,Resources}
|
||||||
|
install -m 644 *.icns *.png dmg/Mongoose.app/Contents/Resources/
|
||||||
|
install -m 644 Info.plist dmg/Mongoose.app/Contents/
|
||||||
|
$(CC) $(BIG_SOURCES) \
|
||||||
|
-DUSE_COCOA $(CFLAGS) $(BIG_FLAGS) -mmacosx-version-min=10.4 \
|
||||||
|
-framework Cocoa -ObjC -arch i386 -arch x86_64 \
|
||||||
|
-o dmg/Mongoose.app/Contents/MacOS/Mongoose
|
||||||
|
ln -fs /Applications dmg/
|
||||||
|
hdiutil create Mongoose-$(VERSION).dmg -volname "Mongoose $(VERSION)" \
|
||||||
|
-srcfolder dmg -ov
|
||||||
|
#rm -rf dmg
|
||||||
|
|
||||||
|
tests:
|
||||||
|
perl ./test/test.pl $(TEST)
|
||||||
|
|
||||||
|
tarball: clean
|
||||||
|
rm -rf $(VDIR)
|
||||||
|
install -d $(VDIR) $(VDIR)/docs $(VDIR)/examples
|
||||||
|
install -m 644 ../{LICENSE,README.md,mongoose.[ch]} $(VDIR)
|
||||||
|
install -m 644 ../docs/*.md $(VDIR)/docs
|
||||||
|
install -m 644 ../examples/{Makefile,*.c} $(VDIR)/examples
|
||||||
|
tar -czf $(VDIR).tgz $(VDIR)
|
||||||
|
|
||||||
|
release: tarball macos
|
||||||
|
wine make mongoose.exe mongoose-lua-sqlite-ssl.exe
|
||||||
|
upx mongoose.exe
|
||||||
|
upx mongoose-lua-sqlite-ssl.exe
|
||||||
|
cp mongoose.exe mongoose-$(VERSION).exe
|
||||||
|
cp mongoose-lua-sqlite-ssl.exe mongoose-lua-sqlite-ssl-$(VERSION).exe
|
||||||
|
cp mongoose-lua-sqlite-ssl.exe mongoose_php_bundle/mongoose.exe
|
||||||
|
zip -r mongoose-php-$(VERSION).zip mongoose_php_bundle/
|
||||||
|
|
||||||
|
clean:
|
||||||
|
cd ../examples && $(MAKE) clean
|
||||||
|
rm -rf *.o *.core $(PROG) *.obj *.so $(PROG).txt *.dSYM *.tgz \
|
||||||
|
$(PROG).exe *.dll *.lib res.o res.RES *.dSYM *.zip *.pdb \
|
||||||
|
*.exe *dmg* $(PROG)-* unix_unit_test
|
||||||
@@ -13,3 +13,7 @@ EXPORTS
|
|||||||
mg_version
|
mg_version
|
||||||
mg_modify_passwords_file
|
mg_modify_passwords_file
|
||||||
mg_md5
|
mg_md5
|
||||||
|
mg_upload
|
||||||
|
mg_download
|
||||||
|
mg_send_file
|
||||||
|
mg_get_builtin_mime_type
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
LOCAL_PATH := $(call my-dir)/../..
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
# To build with lua support, uncomment two lines below:
|
||||||
|
#LUA_SOURCES := build/lua_5.2.1.c build/sqlite3.c build/lsqlite3.c
|
||||||
|
#LUA_FLAGS := -I$(LOCAL_PATH)/build -DTHREADSAFE=1 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DUSE_LUA -DUSE_LUA_SQLITE3 -DLUA_COMPAT_ALL -D"getlocaledecpoint() ='.'"
|
||||||
|
|
||||||
|
LOCAL_CFLAGS := -std=c99 -O2 -W -Wall -pthread -pipe $(LUA_FLAGS) $(COPT)
|
||||||
|
LOCAL_MODULE := mongoose
|
||||||
|
LOCAL_SRC_FILES := build/main.c mongoose.c $(LUA_SOURCES)
|
||||||
|
|
||||||
|
include $(BUILD_EXECUTABLE)
|
||||||
+1883
File diff suppressed because it is too large.
Load diff
+17698
File diff suppressed because it is too large.
Load diff
+1251
File diff suppressed because it is too large.
Load diff
+1073
File diff suppressed because it is too large.
Load diff
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 669 B |
Binary file not shown.
|
After Width: | Height: | Size: 872 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
+1
-1
@@ -1 +1 @@
|
|||||||
200 ICON DISCARDABLE "systray.ico"
|
100 ICON DISCARDABLE "systray.ico"
|
||||||
+140456
File diff suppressed because it is too large.
Load diff
+7245
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,301 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
// Stringify binary data. Output buffer must be twice as big as input,
|
||||||
|
// because each byte takes 2 bytes in string representation
|
||||||
|
static void bin2str(char *to, const unsigned char *p, size_t len) {
|
||||||
|
static const char *hex = "0123456789abcdef";
|
||||||
|
|
||||||
|
for (; len--; p++) {
|
||||||
|
*to++ = hex[p[0] >> 4];
|
||||||
|
*to++ = hex[p[0] & 0x0f];
|
||||||
|
}
|
||||||
|
*to = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
|
||||||
|
char *mg_md5(char buf[33], ...) {
|
||||||
|
unsigned char hash[16];
|
||||||
|
const char *p;
|
||||||
|
va_list ap;
|
||||||
|
MD5_CTX ctx;
|
||||||
|
|
||||||
|
MD5Init(&ctx);
|
||||||
|
|
||||||
|
va_start(ap, buf);
|
||||||
|
while ((p = va_arg(ap, const char *)) != NULL) {
|
||||||
|
MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
MD5Final(hash, &ctx);
|
||||||
|
bin2str(buf, hash, sizeof(hash));
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the user's password, return 1 if OK
|
||||||
|
static int check_password(const char *method, const char *ha1, const char *uri,
|
||||||
|
const char *nonce, const char *nc, const char *cnonce,
|
||||||
|
const char *qop, const char *response) {
|
||||||
|
char ha2[32 + 1], expected_response[32 + 1];
|
||||||
|
|
||||||
|
// Some of the parameters may be NULL
|
||||||
|
if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
|
||||||
|
qop == NULL || response == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
|
||||||
|
// TODO(lsm): check for authentication timeout
|
||||||
|
if (// strcmp(dig->uri, c->ouri) != 0 ||
|
||||||
|
strlen(response) != 32
|
||||||
|
// || now - strtoul(dig->nonce, NULL, 10) > 3600
|
||||||
|
) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_md5(ha2, method, ":", uri, NULL);
|
||||||
|
mg_md5(expected_response, ha1, ":", nonce, ":", nc,
|
||||||
|
":", cnonce, ":", qop, ":", ha2, NULL);
|
||||||
|
|
||||||
|
return mg_strcasecmp(response, expected_response) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the global passwords file, if specified by auth_gpass option,
|
||||||
|
// or search for .htpasswd in the requested directory.
|
||||||
|
static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
|
||||||
|
char name[PATH_MAX];
|
||||||
|
const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
|
||||||
|
struct file file = STRUCT_FILE_INITIALIZER;
|
||||||
|
FILE *fp = NULL;
|
||||||
|
|
||||||
|
if (gpass != NULL) {
|
||||||
|
// Use global passwords file
|
||||||
|
fp = mg_fopen(gpass, "r");
|
||||||
|
// Important: using local struct file to test path for is_directory flag.
|
||||||
|
// If filep is used, mg_stat() makes it appear as if auth file was opened.
|
||||||
|
} else if (mg_stat(path, &file) && file.is_directory) {
|
||||||
|
mg_snprintf(name, sizeof(name), "%s%c%s",
|
||||||
|
path, '/', PASSWORDS_FILE_NAME);
|
||||||
|
fp = mg_fopen(name, "r");
|
||||||
|
} else {
|
||||||
|
// Try to find .htpasswd in requested directory.
|
||||||
|
for (p = path, e = p + strlen(p) - 1; e > p; e--)
|
||||||
|
if (e[0] == '/')
|
||||||
|
break;
|
||||||
|
mg_snprintf(name, sizeof(name), "%.*s%c%s",
|
||||||
|
(int) (e - p), p, '/', PASSWORDS_FILE_NAME);
|
||||||
|
fp = mg_fopen(name, "r");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsed Authorization header
|
||||||
|
struct ah {
|
||||||
|
char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return 1 on success. Always initializes the ah structure.
|
||||||
|
static int parse_auth_header(struct mg_connection *conn, char *buf,
|
||||||
|
size_t buf_size, struct ah *ah) {
|
||||||
|
char *name, *value, *s;
|
||||||
|
const char *auth_header;
|
||||||
|
|
||||||
|
(void) memset(ah, 0, sizeof(*ah));
|
||||||
|
if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
|
||||||
|
mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make modifiable copy of the auth header
|
||||||
|
(void) mg_strlcpy(buf, auth_header + 7, buf_size);
|
||||||
|
s = buf;
|
||||||
|
|
||||||
|
// Parse authorization header
|
||||||
|
for (;;) {
|
||||||
|
// Gobble initial spaces
|
||||||
|
while (isspace(* (unsigned char *) s)) {
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
name = skip_quoted(&s, "=", " ", 0);
|
||||||
|
// Value is either quote-delimited, or ends at first comma or space.
|
||||||
|
if (s[0] == '\"') {
|
||||||
|
s++;
|
||||||
|
value = skip_quoted(&s, "\"", " ", '\\');
|
||||||
|
if (s[0] == ',') {
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces
|
||||||
|
}
|
||||||
|
if (*name == '\0') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(name, "username")) {
|
||||||
|
ah->user = value;
|
||||||
|
} else if (!strcmp(name, "cnonce")) {
|
||||||
|
ah->cnonce = value;
|
||||||
|
} else if (!strcmp(name, "response")) {
|
||||||
|
ah->response = value;
|
||||||
|
} else if (!strcmp(name, "uri")) {
|
||||||
|
ah->uri = value;
|
||||||
|
} else if (!strcmp(name, "qop")) {
|
||||||
|
ah->qop = value;
|
||||||
|
} else if (!strcmp(name, "nc")) {
|
||||||
|
ah->nc = value;
|
||||||
|
} else if (!strcmp(name, "nonce")) {
|
||||||
|
ah->nonce = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CGI needs it as REMOTE_USER
|
||||||
|
if (ah->user != NULL) {
|
||||||
|
conn->request_info.remote_user = mg_strdup(ah->user);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorize against the opened passwords file. Return 1 if authorized.
|
||||||
|
static int authorize(struct mg_connection *conn, FILE *fp) {
|
||||||
|
struct ah ah;
|
||||||
|
char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN];
|
||||||
|
|
||||||
|
if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over passwords file
|
||||||
|
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||||
|
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(ah.user, f_user) &&
|
||||||
|
!strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
|
||||||
|
return check_password(conn->request_info.request_method, ha1, ah.uri,
|
||||||
|
ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return 1 if request is authorised, 0 otherwise.
|
||||||
|
static int check_authorization(struct mg_connection *conn, const char *path) {
|
||||||
|
char fname[PATH_MAX];
|
||||||
|
struct vec uri_vec, filename_vec;
|
||||||
|
const char *list;
|
||||||
|
FILE *fp = NULL;
|
||||||
|
int authorized = 1;
|
||||||
|
|
||||||
|
list = conn->ctx->config[PROTECT_URI];
|
||||||
|
while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
|
||||||
|
if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
|
||||||
|
mg_snprintf(fname, sizeof(fname), "%.*s",
|
||||||
|
(int) filename_vec.len, filename_vec.ptr);
|
||||||
|
fp = mg_fopen(fname, "r");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fp == NULL) {
|
||||||
|
fp = open_auth_file(conn, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fp != NULL) {
|
||||||
|
authorized = authorize(conn, fp);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_authorization_request(struct mg_connection *conn) {
|
||||||
|
conn->status_code = 401;
|
||||||
|
mg_printf(conn,
|
||||||
|
"HTTP/1.1 401 Unauthorized\r\n"
|
||||||
|
"Content-Length: 0\r\n"
|
||||||
|
"WWW-Authenticate: Digest qop=\"auth\", "
|
||||||
|
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
|
||||||
|
conn->ctx->config[AUTHENTICATION_DOMAIN],
|
||||||
|
(unsigned long) time(NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_authorized_for_put(struct mg_connection *conn) {
|
||||||
|
const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
|
||||||
|
FILE *fp;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (passfile != NULL && (fp = mg_fopen(passfile, "r")) != NULL) {
|
||||||
|
ret = authorize(conn, fp);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_modify_passwords_file(const char *fname, const char *domain,
|
||||||
|
const char *user, const char *pass) {
|
||||||
|
int found;
|
||||||
|
char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
|
||||||
|
FILE *fp, *fp2;
|
||||||
|
|
||||||
|
found = 0;
|
||||||
|
fp = fp2 = NULL;
|
||||||
|
|
||||||
|
// Regard empty password as no password - remove user record.
|
||||||
|
if (pass != NULL && pass[0] == '\0') {
|
||||||
|
pass = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
|
||||||
|
|
||||||
|
// Create the file if does not exist
|
||||||
|
if ((fp = fopen(fname, "a+")) != NULL) {
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the given file and temporary file
|
||||||
|
if ((fp = fopen(fname, "r")) == NULL) {
|
||||||
|
return 0;
|
||||||
|
} else if ((fp2 = fopen(tmp, "w+")) == NULL) {
|
||||||
|
fclose(fp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the stuff to temporary file
|
||||||
|
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||||
|
if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(u, user) && !strcmp(d, domain)) {
|
||||||
|
found++;
|
||||||
|
if (pass != NULL) {
|
||||||
|
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
|
||||||
|
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(fp2, "%s", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If new user, just add it
|
||||||
|
if (!found && pass != NULL) {
|
||||||
|
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
|
||||||
|
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close files
|
||||||
|
fclose(fp);
|
||||||
|
fclose(fp2);
|
||||||
|
|
||||||
|
// Put the temp file in place of real file
|
||||||
|
remove(fname);
|
||||||
|
rename(tmp, fname);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
+384
@@ -0,0 +1,384 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
static int forward_body_data(struct mg_connection *conn, FILE *fp,
|
||||||
|
SOCKET sock, SSL *ssl) {
|
||||||
|
const char *expect, *body;
|
||||||
|
char buf[MG_BUF_LEN];
|
||||||
|
int nread, buffered_len, success = 0;
|
||||||
|
int64_t left;
|
||||||
|
|
||||||
|
expect = mg_get_header(conn, "Expect");
|
||||||
|
assert(fp != NULL);
|
||||||
|
|
||||||
|
if (conn->content_len == INT64_MAX) {
|
||||||
|
send_http_error(conn, 411, "Length Required", "%s", "");
|
||||||
|
} else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) {
|
||||||
|
send_http_error(conn, 417, "Expectation Failed", "%s", "");
|
||||||
|
} else {
|
||||||
|
if (expect != NULL) {
|
||||||
|
(void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
buffered_len = conn->data_len - conn->request_len;
|
||||||
|
body = conn->buf + conn->request_len;
|
||||||
|
assert(buffered_len >= 0);
|
||||||
|
|
||||||
|
if (buffered_len > 0) {
|
||||||
|
if ((int64_t) buffered_len > conn->content_len) {
|
||||||
|
buffered_len = (int) conn->content_len;
|
||||||
|
}
|
||||||
|
push(fp, sock, ssl, body, (int64_t) buffered_len);
|
||||||
|
memmove((char *) body, body + buffered_len, buffered_len);
|
||||||
|
conn->data_len -= buffered_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
nread = 0;
|
||||||
|
while (conn->num_bytes_read < conn->content_len + conn->request_len) {
|
||||||
|
left = left_to_read(conn);
|
||||||
|
if (left > (int64_t) sizeof(buf)) {
|
||||||
|
left = sizeof(buf);
|
||||||
|
}
|
||||||
|
nread = pull(NULL, conn, buf, (int) left);
|
||||||
|
if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left_to_read(conn) == 0) {
|
||||||
|
success = nread >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each error code path in this function must send an error
|
||||||
|
if (!success) {
|
||||||
|
send_http_error(conn, 577, http_500_error, "%s", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if !defined(NO_CGI)
|
||||||
|
// This structure helps to create an environment for the spawned CGI program.
|
||||||
|
// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
|
||||||
|
// last element must be NULL.
|
||||||
|
// However, on Windows there is a requirement that all these VARIABLE=VALUE\0
|
||||||
|
// strings must reside in a contiguous buffer. The end of the buffer is
|
||||||
|
// marked by two '\0' characters.
|
||||||
|
// We satisfy both worlds: we create an envp array (which is vars), all
|
||||||
|
// entries are actually pointers inside buf.
|
||||||
|
struct cgi_env_block {
|
||||||
|
struct mg_connection *conn;
|
||||||
|
char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer
|
||||||
|
int len; // Space taken
|
||||||
|
char *vars[MAX_CGI_ENVIR_VARS]; // char **envp
|
||||||
|
int nvars; // Number of variables
|
||||||
|
};
|
||||||
|
|
||||||
|
static char *addenv(struct cgi_env_block *block,
|
||||||
|
PRINTF_FORMAT_STRING(const char *fmt), ...)
|
||||||
|
PRINTF_ARGS(2, 3);
|
||||||
|
|
||||||
|
// Append VARIABLE=VALUE\0 string to the buffer, and add a respective
|
||||||
|
// pointer into the vars array.
|
||||||
|
static char *addenv(struct cgi_env_block *block, const char *fmt, ...) {
|
||||||
|
int n, space;
|
||||||
|
char *added;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
// Calculate how much space is left in the buffer
|
||||||
|
space = sizeof(block->buf) - block->len - 2;
|
||||||
|
assert(space >= 0);
|
||||||
|
|
||||||
|
// Make a pointer to the free space int the buffer
|
||||||
|
added = block->buf + block->len;
|
||||||
|
|
||||||
|
// Copy VARIABLE=VALUE\0 string into the free space
|
||||||
|
va_start(ap, fmt);
|
||||||
|
n = mg_vsnprintf(added, (size_t) space, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
// Make sure we do not overflow buffer and the envp array
|
||||||
|
if (n > 0 && n + 1 < space &&
|
||||||
|
block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {
|
||||||
|
// Append a pointer to the added string into the envp array
|
||||||
|
block->vars[block->nvars++] = added;
|
||||||
|
// Bump up used length counter. Include \0 terminator
|
||||||
|
block->len += n + 1;
|
||||||
|
} else {
|
||||||
|
cry(block->conn, "%s: CGI env buffer truncated for [%s]", __func__, fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prepare_cgi_environment(struct mg_connection *conn,
|
||||||
|
const char *prog,
|
||||||
|
struct cgi_env_block *blk) {
|
||||||
|
const struct mg_request_info *ri = &conn->request_info;
|
||||||
|
const char *s, *slash;
|
||||||
|
struct vec var_vec;
|
||||||
|
char *p, src_addr[IP_ADDR_STR_LEN];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
blk->len = blk->nvars = 0;
|
||||||
|
blk->conn = conn;
|
||||||
|
sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
|
||||||
|
|
||||||
|
addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]);
|
||||||
|
addenv(blk, "SERVER_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
|
||||||
|
addenv(blk, "DOCUMENT_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
|
||||||
|
addenv(blk, "SERVER_SOFTWARE=%s/%s", "Mongoose", mg_version());
|
||||||
|
|
||||||
|
// Prepare the environment block
|
||||||
|
addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
|
||||||
|
addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
|
||||||
|
addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP
|
||||||
|
|
||||||
|
// TODO(lsm): fix this for IPv6 case
|
||||||
|
addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin.sin_port));
|
||||||
|
|
||||||
|
addenv(blk, "REQUEST_METHOD=%s", ri->request_method);
|
||||||
|
addenv(blk, "REMOTE_ADDR=%s", src_addr);
|
||||||
|
addenv(blk, "REMOTE_PORT=%d", ri->remote_port);
|
||||||
|
addenv(blk, "REQUEST_URI=%s%s%s", ri->uri,
|
||||||
|
ri->query_string == NULL ? "" : "?",
|
||||||
|
ri->query_string == NULL ? "" : ri->query_string);
|
||||||
|
|
||||||
|
// SCRIPT_NAME
|
||||||
|
if (conn->path_info != NULL) {
|
||||||
|
addenv(blk, "SCRIPT_NAME=%.*s",
|
||||||
|
(int) (strlen(ri->uri) - strlen(conn->path_info)), ri->uri);
|
||||||
|
addenv(blk, "PATH_INFO=%s", conn->path_info);
|
||||||
|
} else {
|
||||||
|
s = strrchr(prog, '/');
|
||||||
|
slash = strrchr(ri->uri, '/');
|
||||||
|
addenv(blk, "SCRIPT_NAME=%.*s%s",
|
||||||
|
slash == NULL ? 0 : (int) (slash - ri->uri), ri->uri,
|
||||||
|
s == NULL ? prog : s);
|
||||||
|
}
|
||||||
|
|
||||||
|
addenv(blk, "SCRIPT_FILENAME=%s", prog);
|
||||||
|
addenv(blk, "PATH_TRANSLATED=%s", prog);
|
||||||
|
addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on");
|
||||||
|
|
||||||
|
if ((s = mg_get_header(conn, "Content-Type")) != NULL)
|
||||||
|
addenv(blk, "CONTENT_TYPE=%s", s);
|
||||||
|
|
||||||
|
if (ri->query_string != NULL) {
|
||||||
|
addenv(blk, "QUERY_STRING=%s", ri->query_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((s = mg_get_header(conn, "Content-Length")) != NULL)
|
||||||
|
addenv(blk, "CONTENT_LENGTH=%s", s);
|
||||||
|
|
||||||
|
if ((s = getenv("PATH")) != NULL)
|
||||||
|
addenv(blk, "PATH=%s", s);
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if ((s = getenv("COMSPEC")) != NULL) {
|
||||||
|
addenv(blk, "COMSPEC=%s", s);
|
||||||
|
}
|
||||||
|
if ((s = getenv("SYSTEMROOT")) != NULL) {
|
||||||
|
addenv(blk, "SYSTEMROOT=%s", s);
|
||||||
|
}
|
||||||
|
if ((s = getenv("SystemDrive")) != NULL) {
|
||||||
|
addenv(blk, "SystemDrive=%s", s);
|
||||||
|
}
|
||||||
|
if ((s = getenv("ProgramFiles")) != NULL) {
|
||||||
|
addenv(blk, "ProgramFiles=%s", s);
|
||||||
|
}
|
||||||
|
if ((s = getenv("ProgramFiles(x86)")) != NULL) {
|
||||||
|
addenv(blk, "ProgramFiles(x86)=%s", s);
|
||||||
|
}
|
||||||
|
if ((s = getenv("CommonProgramFiles(x86)")) != NULL) {
|
||||||
|
addenv(blk, "CommonProgramFiles(x86)=%s", s);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if ((s = getenv("LD_LIBRARY_PATH")) != NULL)
|
||||||
|
addenv(blk, "LD_LIBRARY_PATH=%s", s);
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
if ((s = getenv("PERLLIB")) != NULL)
|
||||||
|
addenv(blk, "PERLLIB=%s", s);
|
||||||
|
|
||||||
|
if (ri->remote_user != NULL) {
|
||||||
|
addenv(blk, "REMOTE_USER=%s", ri->remote_user);
|
||||||
|
addenv(blk, "%s", "AUTH_TYPE=Digest");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all headers as HTTP_* variables
|
||||||
|
for (i = 0; i < ri->num_headers; i++) {
|
||||||
|
p = addenv(blk, "HTTP_%s=%s",
|
||||||
|
ri->http_headers[i].name, ri->http_headers[i].value);
|
||||||
|
|
||||||
|
// Convert variable name into uppercase, and change - to _
|
||||||
|
for (; *p != '=' && *p != '\0'; p++) {
|
||||||
|
if (*p == '-')
|
||||||
|
*p = '_';
|
||||||
|
*p = (char) toupper(* (unsigned char *) p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user-specified variables
|
||||||
|
s = conn->ctx->config[CGI_ENVIRONMENT];
|
||||||
|
while ((s = next_option(s, &var_vec, NULL)) != NULL) {
|
||||||
|
addenv(blk, "%.*s", (int) var_vec.len, var_vec.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
blk->vars[blk->nvars++] = NULL;
|
||||||
|
blk->buf[blk->len++] = '\0';
|
||||||
|
|
||||||
|
assert(blk->nvars < (int) ARRAY_SIZE(blk->vars));
|
||||||
|
assert(blk->len > 0);
|
||||||
|
assert(blk->len < (int) sizeof(blk->buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_cgi_request(struct mg_connection *conn, const char *prog) {
|
||||||
|
int headers_len, data_len, i, fdin[2] = {-1, -1}, fdout[2] = {-1, -1};
|
||||||
|
const char *status, *status_text;
|
||||||
|
char buf[16384], *pbuf, dir[PATH_MAX], *p;
|
||||||
|
struct mg_request_info ri;
|
||||||
|
struct cgi_env_block blk;
|
||||||
|
FILE *in = NULL, *out = NULL;
|
||||||
|
pid_t pid = (pid_t) -1;
|
||||||
|
|
||||||
|
memset(&ri, 0, sizeof(ri));
|
||||||
|
prepare_cgi_environment(conn, prog, &blk);
|
||||||
|
|
||||||
|
// CGI must be executed in its own directory. 'dir' must point to the
|
||||||
|
// directory containing executable program, 'p' must point to the
|
||||||
|
// executable program name relative to 'dir'.
|
||||||
|
(void) mg_snprintf(dir, sizeof(dir), "%s", prog);
|
||||||
|
if ((p = strrchr(dir, '/')) != NULL) {
|
||||||
|
*p++ = '\0';
|
||||||
|
} else {
|
||||||
|
dir[0] = '.', dir[1] = '\0';
|
||||||
|
p = (char *) prog;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipe(fdin) != 0 || pipe(fdout) != 0) {
|
||||||
|
send_http_error(conn, 500, http_500_error,
|
||||||
|
"Cannot create CGI pipe: %s", strerror(ERRNO));
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid = spawn_process(conn, p, blk.buf, blk.vars, fdin[0], fdout[1], dir);
|
||||||
|
if (pid == (pid_t) -1) {
|
||||||
|
send_http_error(conn, 500, http_500_error,
|
||||||
|
"Cannot spawn CGI process [%s]: %s", prog, strerror(ERRNO));
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure child closes all pipe descriptors. It must dup them to 0,1
|
||||||
|
set_close_on_exec(fdin[0]);
|
||||||
|
set_close_on_exec(fdin[1]);
|
||||||
|
set_close_on_exec(fdout[0]);
|
||||||
|
set_close_on_exec(fdout[1]);
|
||||||
|
|
||||||
|
// Parent closes only one side of the pipes.
|
||||||
|
// If we don't mark them as closed, close() attempt before
|
||||||
|
// return from this function throws an exception on Windows.
|
||||||
|
// Windows does not like when closed descriptor is closed again.
|
||||||
|
(void) close(fdin[0]);
|
||||||
|
(void) close(fdout[1]);
|
||||||
|
fdin[0] = fdout[1] = -1;
|
||||||
|
|
||||||
|
|
||||||
|
if ((in = fdopen(fdin[1], "wb")) == NULL ||
|
||||||
|
(out = fdopen(fdout[0], "rb")) == NULL) {
|
||||||
|
send_http_error(conn, 500, http_500_error,
|
||||||
|
"fopen: %s", strerror(ERRNO));
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
setbuf(in, NULL);
|
||||||
|
setbuf(out, NULL);
|
||||||
|
|
||||||
|
// Send POST data to the CGI process if needed
|
||||||
|
if (!strcmp(conn->request_info.request_method, "POST") &&
|
||||||
|
!forward_body_data(conn, in, INVALID_SOCKET, NULL)) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close so child gets an EOF.
|
||||||
|
fclose(in);
|
||||||
|
in = NULL;
|
||||||
|
fdin[1] = -1;
|
||||||
|
|
||||||
|
// Now read CGI reply into a buffer. We need to set correct
|
||||||
|
// status code, thus we need to see all HTTP headers first.
|
||||||
|
// Do not send anything back to client, until we buffer in all
|
||||||
|
// HTTP headers.
|
||||||
|
data_len = 0;
|
||||||
|
headers_len = read_request(out, conn, buf, sizeof(buf), &data_len);
|
||||||
|
if (headers_len <= 0) {
|
||||||
|
send_http_error(conn, 500, http_500_error,
|
||||||
|
"CGI program sent malformed or too big (>%u bytes) "
|
||||||
|
"HTTP headers: [%.*s]",
|
||||||
|
(unsigned) sizeof(buf), data_len, buf);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
pbuf = buf;
|
||||||
|
buf[headers_len - 1] = '\0';
|
||||||
|
parse_http_headers(&pbuf, &ri);
|
||||||
|
|
||||||
|
// Make up and send the status line
|
||||||
|
status_text = "OK";
|
||||||
|
if ((status = get_header(&ri, "Status")) != NULL) {
|
||||||
|
conn->status_code = atoi(status);
|
||||||
|
status_text = status;
|
||||||
|
while (isdigit(* (unsigned char *) status_text) || *status_text == ' ') {
|
||||||
|
status_text++;
|
||||||
|
}
|
||||||
|
} else if (get_header(&ri, "Location") != NULL) {
|
||||||
|
conn->status_code = 302;
|
||||||
|
} else {
|
||||||
|
conn->status_code = 200;
|
||||||
|
}
|
||||||
|
if (get_header(&ri, "Connection") != NULL &&
|
||||||
|
!mg_strcasecmp(get_header(&ri, "Connection"), "keep-alive")) {
|
||||||
|
conn->must_close = 1;
|
||||||
|
}
|
||||||
|
(void) mg_printf(conn, "HTTP/1.1 %d %s\r\n", conn->status_code,
|
||||||
|
status_text);
|
||||||
|
|
||||||
|
// Send headers
|
||||||
|
for (i = 0; i < ri.num_headers; i++) {
|
||||||
|
mg_printf(conn, "%s: %s\r\n",
|
||||||
|
ri.http_headers[i].name, ri.http_headers[i].value);
|
||||||
|
}
|
||||||
|
mg_write(conn, "\r\n", 2);
|
||||||
|
|
||||||
|
// Send chunk of data that may have been read after the headers
|
||||||
|
conn->num_bytes_sent += mg_write(conn, buf + headers_len,
|
||||||
|
(size_t)(data_len - headers_len));
|
||||||
|
|
||||||
|
// Read the rest of CGI output and send to the client
|
||||||
|
send_file_data(conn, out, 0, INT64_MAX);
|
||||||
|
|
||||||
|
done:
|
||||||
|
if (pid != (pid_t) -1) {
|
||||||
|
kill(pid, SIGKILL);
|
||||||
|
}
|
||||||
|
if (fdin[0] != -1) {
|
||||||
|
close(fdin[0]);
|
||||||
|
}
|
||||||
|
if (fdout[1] != -1) {
|
||||||
|
close(fdout[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in != NULL) {
|
||||||
|
fclose(in);
|
||||||
|
} else if (fdin[1] != -1) {
|
||||||
|
close(fdin[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out != NULL) {
|
||||||
|
fclose(out);
|
||||||
|
} else if (fdout[0] != -1) {
|
||||||
|
close(fdout[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // !NO_CGI
|
||||||
|
|
||||||
+2832
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||||
|
// Copyright (c) 2013 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
//
|
||||||
|
// This library is dual-licensed: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
// published by the Free Software Foundation. For the terms of this
|
||||||
|
// license, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// You are free to use this library under the terms of the GNU General
|
||||||
|
// Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
// See the GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// Alternatively, you can license this library under a commercial
|
||||||
|
// license, as set out in <http://cesanta.com/products.html>.
|
||||||
|
//
|
||||||
|
// NOTE: Detailed API documentation is at http://cesanta.com/docs.html
|
||||||
|
|
||||||
|
#ifndef MONGOOSE_HEADER_INCLUDED
|
||||||
|
#define MONGOOSE_HEADER_INCLUDED
|
||||||
|
|
||||||
|
#include <stdio.h> // required for FILE
|
||||||
|
#include <stddef.h> // required for size_t
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
// This structure contains information about HTTP request.
|
||||||
|
struct mg_connection {
|
||||||
|
const char *request_method; // "GET", "POST", etc
|
||||||
|
const char *uri; // URL-decoded URI
|
||||||
|
const char *http_version; // E.g. "1.0", "1.1"
|
||||||
|
const char *query_string; // URL part after '?', not including '?', or NULL
|
||||||
|
|
||||||
|
char remote_ip[48]; // Max IPv6 string length is 45 characters
|
||||||
|
int remote_port; // Client's port
|
||||||
|
|
||||||
|
int num_headers; // Number of HTTP headers
|
||||||
|
struct mg_header {
|
||||||
|
const char *name; // HTTP header name
|
||||||
|
const char *value; // HTTP header value
|
||||||
|
} http_headers[30];
|
||||||
|
|
||||||
|
char *content; // POST (or websocket message) data, or NULL
|
||||||
|
int content_len; // content length
|
||||||
|
|
||||||
|
int is_websocket; // Connection is a websocket connection
|
||||||
|
int status_code; // HTTP status code for HTTP error handler
|
||||||
|
unsigned char wsbits; // First byte of the websocket frame
|
||||||
|
void *server_param; // Parameter passed to mg_add_uri_handler()
|
||||||
|
void *connection_param; // Placeholder for connection-specific data
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mg_server; // Opaque structure describing server instance
|
||||||
|
typedef int (*mg_handler_t)(struct mg_connection *);
|
||||||
|
|
||||||
|
// Server management functions
|
||||||
|
struct mg_server *mg_create_server(void *server_param);
|
||||||
|
void mg_destroy_server(struct mg_server **);
|
||||||
|
const char *mg_set_option(struct mg_server *, const char *opt, const char *val);
|
||||||
|
void mg_poll_server(struct mg_server *, int milliseconds);
|
||||||
|
void mg_add_uri_handler(struct mg_server *, const char *uri, mg_handler_t);
|
||||||
|
void mg_set_error_handler(struct mg_server *, mg_handler_t);
|
||||||
|
void mg_set_log_handler(struct mg_server*, mg_handler_t);
|
||||||
|
const char **mg_get_valid_option_names(void);
|
||||||
|
const char *mg_get_option(const struct mg_server *server, const char *name);
|
||||||
|
int mg_iterate_over_connections(struct mg_server *,
|
||||||
|
void (*func)(struct mg_connection *, void *),
|
||||||
|
void *param);
|
||||||
|
|
||||||
|
// Connection management functions
|
||||||
|
int mg_write(struct mg_connection *, const void *buf, int len);
|
||||||
|
int mg_websocket_write(struct mg_connection *, int opcode,
|
||||||
|
const char *data, size_t data_len);
|
||||||
|
|
||||||
|
const char *mg_get_header(const struct mg_connection *, const char *name);
|
||||||
|
const char *mg_get_mime_type(const char *file_name);
|
||||||
|
int mg_get_var(const struct mg_connection *conn, const char *var_name,
|
||||||
|
char *buf, size_t buf_len);
|
||||||
|
int mg_parse_header(const char *hdr, const char *var_name, char *buf, size_t);
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
int mg_start_thread(void *(*func)(void *), void *param);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#endif // MONGOOSE_HEADER_INCLUDED
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
static int is_big_endian(void) {
|
||||||
|
static const int n = 1;
|
||||||
|
return ((char *) &n)[0] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_MD5
|
||||||
|
typedef struct MD5Context {
|
||||||
|
uint32_t buf[4];
|
||||||
|
uint32_t bits[2];
|
||||||
|
unsigned char in[64];
|
||||||
|
} MD5_CTX;
|
||||||
|
|
||||||
|
static void byteReverse(unsigned char *buf, unsigned longs) {
|
||||||
|
uint32_t t;
|
||||||
|
|
||||||
|
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
|
||||||
|
if (is_big_endian()) {
|
||||||
|
do {
|
||||||
|
t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
|
||||||
|
((unsigned) buf[1] << 8 | buf[0]);
|
||||||
|
* (uint32_t *) buf = t;
|
||||||
|
buf += 4;
|
||||||
|
} while (--longs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
||||||
|
#define F2(x, y, z) F1(z, x, y)
|
||||||
|
#define F3(x, y, z) (x ^ y ^ z)
|
||||||
|
#define F4(x, y, z) (y ^ (x | ~z))
|
||||||
|
|
||||||
|
#define MD5STEP(f, w, x, y, z, data, s) \
|
||||||
|
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
|
||||||
|
|
||||||
|
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
|
||||||
|
// initialization constants.
|
||||||
|
static void MD5Init(MD5_CTX *ctx) {
|
||||||
|
ctx->buf[0] = 0x67452301;
|
||||||
|
ctx->buf[1] = 0xefcdab89;
|
||||||
|
ctx->buf[2] = 0x98badcfe;
|
||||||
|
ctx->buf[3] = 0x10325476;
|
||||||
|
|
||||||
|
ctx->bits[0] = 0;
|
||||||
|
ctx->bits[1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
|
||||||
|
register uint32_t a, b, c, d;
|
||||||
|
|
||||||
|
a = buf[0];
|
||||||
|
b = buf[1];
|
||||||
|
c = buf[2];
|
||||||
|
d = buf[3];
|
||||||
|
|
||||||
|
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
|
||||||
|
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
|
||||||
|
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
|
||||||
|
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
|
||||||
|
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
|
||||||
|
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
|
||||||
|
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
|
||||||
|
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
|
||||||
|
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
|
||||||
|
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
|
||||||
|
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
|
||||||
|
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
|
||||||
|
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
|
||||||
|
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
|
||||||
|
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
|
||||||
|
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
|
||||||
|
|
||||||
|
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
|
||||||
|
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
|
||||||
|
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
|
||||||
|
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
|
||||||
|
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
|
||||||
|
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
|
||||||
|
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
|
||||||
|
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
|
||||||
|
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
|
||||||
|
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
|
||||||
|
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
|
||||||
|
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
|
||||||
|
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
|
||||||
|
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
|
||||||
|
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
|
||||||
|
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
|
||||||
|
|
||||||
|
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
|
||||||
|
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
|
||||||
|
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
|
||||||
|
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
|
||||||
|
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
|
||||||
|
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
|
||||||
|
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
|
||||||
|
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
|
||||||
|
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
|
||||||
|
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
|
||||||
|
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
|
||||||
|
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
|
||||||
|
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
|
||||||
|
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
|
||||||
|
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
|
||||||
|
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
|
||||||
|
|
||||||
|
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
|
||||||
|
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
|
||||||
|
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
|
||||||
|
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
|
||||||
|
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
|
||||||
|
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
|
||||||
|
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
|
||||||
|
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
|
||||||
|
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
|
||||||
|
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
|
||||||
|
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
|
||||||
|
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
|
||||||
|
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
|
||||||
|
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
|
||||||
|
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
|
||||||
|
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
|
||||||
|
|
||||||
|
buf[0] += a;
|
||||||
|
buf[1] += b;
|
||||||
|
buf[2] += c;
|
||||||
|
buf[3] += d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
|
||||||
|
uint32_t t;
|
||||||
|
|
||||||
|
t = ctx->bits[0];
|
||||||
|
if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
|
||||||
|
ctx->bits[1]++;
|
||||||
|
ctx->bits[1] += len >> 29;
|
||||||
|
|
||||||
|
t = (t >> 3) & 0x3f;
|
||||||
|
|
||||||
|
if (t) {
|
||||||
|
unsigned char *p = (unsigned char *) ctx->in + t;
|
||||||
|
|
||||||
|
t = 64 - t;
|
||||||
|
if (len < t) {
|
||||||
|
memcpy(p, buf, len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(p, buf, t);
|
||||||
|
byteReverse(ctx->in, 16);
|
||||||
|
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
|
||||||
|
buf += t;
|
||||||
|
len -= t;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (len >= 64) {
|
||||||
|
memcpy(ctx->in, buf, 64);
|
||||||
|
byteReverse(ctx->in, 16);
|
||||||
|
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
|
||||||
|
buf += 64;
|
||||||
|
len -= 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(ctx->in, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
|
||||||
|
unsigned count;
|
||||||
|
unsigned char *p;
|
||||||
|
uint32_t *a;
|
||||||
|
|
||||||
|
count = (ctx->bits[0] >> 3) & 0x3F;
|
||||||
|
|
||||||
|
p = ctx->in + count;
|
||||||
|
*p++ = 0x80;
|
||||||
|
count = 64 - 1 - count;
|
||||||
|
if (count < 8) {
|
||||||
|
memset(p, 0, count);
|
||||||
|
byteReverse(ctx->in, 16);
|
||||||
|
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
|
||||||
|
memset(ctx->in, 0, 56);
|
||||||
|
} else {
|
||||||
|
memset(p, 0, count - 8);
|
||||||
|
}
|
||||||
|
byteReverse(ctx->in, 14);
|
||||||
|
|
||||||
|
a = (uint32_t *)ctx->in;
|
||||||
|
a[14] = ctx->bits[0];
|
||||||
|
a[15] = ctx->bits[1];
|
||||||
|
|
||||||
|
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
|
||||||
|
byteReverse((unsigned char *) ctx->buf, 4);
|
||||||
|
memcpy(digest, ctx->buf, 16);
|
||||||
|
memset((char *) ctx, 0, sizeof(*ctx));
|
||||||
|
}
|
||||||
|
#endif // !HAVE_MD5
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
static void print_dir_entry(const struct de *de) {
|
||||||
|
char size[64], mod[64], href[PATH_MAX * 3];
|
||||||
|
const char *slash = de->file.is_directory ? "/" : "";
|
||||||
|
|
||||||
|
if (de->file.is_directory) {
|
||||||
|
mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
|
||||||
|
} else {
|
||||||
|
// We use (signed) cast below because MSVC 6 compiler cannot
|
||||||
|
// convert unsigned __int64 to double. Sigh.
|
||||||
|
if (de->file.size < 1024) {
|
||||||
|
mg_snprintf(size, sizeof(size), "%d", (int) de->file.size);
|
||||||
|
} else if (de->file.size < 0x100000) {
|
||||||
|
mg_snprintf(size, sizeof(size),
|
||||||
|
"%.1fk", (double) de->file.size / 1024.0);
|
||||||
|
} else if (de->file.size < 0x40000000) {
|
||||||
|
mg_snprintf(size, sizeof(size),
|
||||||
|
"%.1fM", (double) de->file.size / 1048576);
|
||||||
|
} else {
|
||||||
|
mg_snprintf(size, sizeof(size),
|
||||||
|
"%.1fG", (double) de->file.size / 1073741824);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M",
|
||||||
|
localtime(&de->file.modification_time));
|
||||||
|
mg_url_encode(de->file_name, href, sizeof(href));
|
||||||
|
de->conn->num_bytes_sent += mg_chunked_printf(de->conn,
|
||||||
|
"<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
|
||||||
|
"<td> %s</td><td> %s</td></tr>\n",
|
||||||
|
de->conn->request_info.uri, href, slash, de->file_name, slash, mod, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called from send_directory() and used for
|
||||||
|
// sorting directory entries by size, or name, or modification time.
|
||||||
|
// On windows, __cdecl specification is needed in case if project is built
|
||||||
|
// with __stdcall convention. qsort always requires __cdels callback.
|
||||||
|
static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
|
||||||
|
const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;
|
||||||
|
const char *query_string = a->conn->request_info.query_string;
|
||||||
|
int cmp_result = 0;
|
||||||
|
|
||||||
|
if (query_string == NULL) {
|
||||||
|
query_string = "na";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a->file.is_directory && !b->file.is_directory) {
|
||||||
|
return -1; // Always put directories on top
|
||||||
|
} else if (!a->file.is_directory && b->file.is_directory) {
|
||||||
|
return 1; // Always put directories on top
|
||||||
|
} else if (*query_string == 'n') {
|
||||||
|
cmp_result = strcmp(a->file_name, b->file_name);
|
||||||
|
} else if (*query_string == 's') {
|
||||||
|
cmp_result = a->file.size == b->file.size ? 0 :
|
||||||
|
a->file.size > b->file.size ? 1 : -1;
|
||||||
|
} else if (*query_string == 'd') {
|
||||||
|
cmp_result = a->file.modification_time == b->file.modification_time ? 0 :
|
||||||
|
a->file.modification_time > b->file.modification_time ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query_string[1] == 'd' ? -cmp_result : cmp_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int must_hide_file(struct mg_connection *conn, const char *path) {
|
||||||
|
const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
|
||||||
|
const char *pattern = conn->ctx->config[HIDE_FILES];
|
||||||
|
return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
|
||||||
|
(pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int scan_directory(struct mg_connection *conn, const char *dir,
|
||||||
|
void *data, void (*cb)(struct de *, void *)) {
|
||||||
|
char path[PATH_MAX];
|
||||||
|
struct dirent *dp;
|
||||||
|
DIR *dirp;
|
||||||
|
struct de de;
|
||||||
|
|
||||||
|
if ((dirp = opendir(dir)) == NULL) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
de.conn = conn;
|
||||||
|
|
||||||
|
while ((dp = readdir(dirp)) != NULL) {
|
||||||
|
// Do not show current dir and hidden files
|
||||||
|
if (!strcmp(dp->d_name, ".") ||
|
||||||
|
!strcmp(dp->d_name, "..") ||
|
||||||
|
must_hide_file(conn, dp->d_name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
|
||||||
|
|
||||||
|
// If we don't memset stat structure to zero, mtime will have
|
||||||
|
// garbage and strftime() will segfault later on in
|
||||||
|
// print_dir_entry(). memset is required only if mg_stat()
|
||||||
|
// fails. For more details, see
|
||||||
|
// http://code.google.com/p/mongoose/issues/detail?id=79
|
||||||
|
memset(&de.file, 0, sizeof(de.file));
|
||||||
|
mg_stat(path, &de.file);
|
||||||
|
|
||||||
|
de.file_name = dp->d_name;
|
||||||
|
cb(&de, data);
|
||||||
|
}
|
||||||
|
(void) closedir(dirp);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int remove_directory(struct mg_connection *conn, const char *dir) {
|
||||||
|
char path[PATH_MAX];
|
||||||
|
struct dirent *dp;
|
||||||
|
DIR *dirp;
|
||||||
|
struct de de;
|
||||||
|
|
||||||
|
if ((dirp = opendir(dir)) == NULL) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
de.conn = conn;
|
||||||
|
|
||||||
|
while ((dp = readdir(dirp)) != NULL) {
|
||||||
|
// Do not show current dir, but show hidden files
|
||||||
|
if (!strcmp(dp->d_name, ".") ||
|
||||||
|
!strcmp(dp->d_name, "..")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
|
||||||
|
|
||||||
|
// If we don't memset stat structure to zero, mtime will have
|
||||||
|
// garbage and strftime() will segfault later on in
|
||||||
|
// print_dir_entry(). memset is required only if mg_stat()
|
||||||
|
// fails. For more details, see
|
||||||
|
// http://code.google.com/p/mongoose/issues/detail?id=79
|
||||||
|
memset(&de.file, 0, sizeof(de.file));
|
||||||
|
mg_stat(path, &de.file);
|
||||||
|
if(de.file.modification_time) {
|
||||||
|
if(de.file.is_directory) {
|
||||||
|
remove_directory(conn, path);
|
||||||
|
} else {
|
||||||
|
mg_remove(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
(void) closedir(dirp);
|
||||||
|
|
||||||
|
rmdir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dir_scan_data {
|
||||||
|
struct de *entries;
|
||||||
|
int num_entries;
|
||||||
|
int arr_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Behaves like realloc(), but frees original pointer on failure
|
||||||
|
static void *realloc2(void *ptr, size_t size) {
|
||||||
|
void *new_ptr = realloc(ptr, size);
|
||||||
|
if (new_ptr == NULL) {
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
return new_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dir_scan_callback(struct de *de, void *data) {
|
||||||
|
struct dir_scan_data *dsd = (struct dir_scan_data *) data;
|
||||||
|
|
||||||
|
if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) {
|
||||||
|
dsd->arr_size *= 2;
|
||||||
|
dsd->entries = (struct de *) realloc2(dsd->entries, dsd->arr_size *
|
||||||
|
sizeof(dsd->entries[0]));
|
||||||
|
}
|
||||||
|
if (dsd->entries == NULL) {
|
||||||
|
// TODO(lsm): propagate an error to the caller
|
||||||
|
dsd->num_entries = 0;
|
||||||
|
} else {
|
||||||
|
dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name);
|
||||||
|
dsd->entries[dsd->num_entries].file = de->file;
|
||||||
|
dsd->entries[dsd->num_entries].conn = de->conn;
|
||||||
|
dsd->num_entries++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_directory_request(struct mg_connection *conn,
|
||||||
|
const char *dir) {
|
||||||
|
int i, sort_direction;
|
||||||
|
struct dir_scan_data data = { NULL, 0, 128 };
|
||||||
|
|
||||||
|
if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
|
||||||
|
send_http_error(conn, 500, "Cannot open directory",
|
||||||
|
"Error: opendir(%s): %s", dir, strerror(ERRNO));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sort_direction = conn->request_info.query_string != NULL &&
|
||||||
|
conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
|
||||||
|
|
||||||
|
conn->must_close = 1;
|
||||||
|
mg_printf(conn, "%s",
|
||||||
|
"HTTP/1.1 200 OK\r\n"
|
||||||
|
"Transfer-Encoding: Chunked\r\n"
|
||||||
|
"Content-Type: text/html; charset=utf-8\r\n\r\n");
|
||||||
|
|
||||||
|
conn->num_bytes_sent += mg_chunked_printf(conn,
|
||||||
|
"<html><head><title>Index of %s</title>"
|
||||||
|
"<style>th {text-align: left;}</style></head>"
|
||||||
|
"<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
|
||||||
|
"<tr><th><a href=\"?n%c\">Name</a></th>"
|
||||||
|
"<th><a href=\"?d%c\">Modified</a></th>"
|
||||||
|
"<th><a href=\"?s%c\">Size</a></th></tr>"
|
||||||
|
"<tr><td colspan=\"3\"><hr></td></tr>",
|
||||||
|
conn->request_info.uri, conn->request_info.uri,
|
||||||
|
sort_direction, sort_direction, sort_direction);
|
||||||
|
|
||||||
|
// Print first entry - link to a parent directory
|
||||||
|
conn->num_bytes_sent += mg_chunked_printf(conn,
|
||||||
|
"<tr><td><a href=\"%s%s\">%s</a></td>"
|
||||||
|
"<td> %s</td><td> %s</td></tr>\n",
|
||||||
|
conn->request_info.uri, "..", "Parent directory", "-", "-");
|
||||||
|
|
||||||
|
// Sort and print directory entries
|
||||||
|
qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
|
||||||
|
compare_dir_entries);
|
||||||
|
for (i = 0; i < data.num_entries; i++) {
|
||||||
|
print_dir_entry(&data.entries[i]);
|
||||||
|
free(data.entries[i].file_name);
|
||||||
|
}
|
||||||
|
free(data.entries);
|
||||||
|
|
||||||
|
conn->num_bytes_sent += mg_chunked_printf(conn, "%s",
|
||||||
|
"</table></body></html>");
|
||||||
|
conn->num_bytes_sent += mg_write(conn, "0\r\n\r\n", 5);
|
||||||
|
conn->status_code = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a given PUT path, create all intermediate subdirectories
|
||||||
|
// for given path. Return 0 if the path itself is a directory,
|
||||||
|
// or -1 on error, 1 if OK.
|
||||||
|
static int put_dir(const char *path) {
|
||||||
|
char buf[PATH_MAX];
|
||||||
|
const char *s, *p;
|
||||||
|
struct file file = STRUCT_FILE_INITIALIZER;
|
||||||
|
int len, res = 1;
|
||||||
|
|
||||||
|
for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) {
|
||||||
|
len = p - path;
|
||||||
|
if (len >= (int) sizeof(buf)) {
|
||||||
|
res = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
memcpy(buf, path, len);
|
||||||
|
buf[len] = '\0';
|
||||||
|
|
||||||
|
// Try to create intermediate directory
|
||||||
|
DEBUG_TRACE(("mkdir(%s)", buf));
|
||||||
|
if (!mg_stat(buf, &file) && mg_mkdir(buf, 0755) != 0) {
|
||||||
|
res = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is path itself a directory?
|
||||||
|
if (p[1] == '\0') {
|
||||||
|
res = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
static SOCKET conn2(const char *host, int port, int use_ssl,
|
||||||
|
char *ebuf, size_t ebuf_len) {
|
||||||
|
struct sockaddr_in sin;
|
||||||
|
struct hostent *he = NULL;
|
||||||
|
SOCKET sock = INVALID_SOCKET;
|
||||||
|
|
||||||
|
(void) use_ssl; // Prevent warning for -DNO_SSL case
|
||||||
|
|
||||||
|
if (host == NULL) {
|
||||||
|
snprintf(ebuf, ebuf_len, "%s", "NULL host");
|
||||||
|
#ifndef NO_SSL
|
||||||
|
} else if (use_ssl && SSLv23_client_method == NULL) {
|
||||||
|
snprintf(ebuf, ebuf_len, "%s", "SSL is not initialized");
|
||||||
|
// TODO(lsm): use something threadsafe instead of gethostbyname()
|
||||||
|
#endif
|
||||||
|
} else if ((he = gethostbyname(host)) == NULL) {
|
||||||
|
snprintf(ebuf, ebuf_len, "gethostbyname(%s): %s", host, strerror(ERRNO));
|
||||||
|
} else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
|
||||||
|
snprintf(ebuf, ebuf_len, "socket(): %s", strerror(ERRNO));
|
||||||
|
} else {
|
||||||
|
set_close_on_exec(sock);
|
||||||
|
sin.sin_family = AF_INET;
|
||||||
|
sin.sin_port = htons((uint16_t) port);
|
||||||
|
sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
|
||||||
|
if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
|
||||||
|
snprintf(ebuf, ebuf_len, "connect(%s:%d): %s",
|
||||||
|
host, port, strerror(ERRNO));
|
||||||
|
closesocket(sock);
|
||||||
|
sock = INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mg_connection *mg_connect(const char *host, int port, int use_ssl,
|
||||||
|
char *ebuf, size_t ebuf_len) {
|
||||||
|
static struct mg_context fake_ctx;
|
||||||
|
struct mg_connection *conn = NULL;
|
||||||
|
SOCKET sock;
|
||||||
|
|
||||||
|
if ((sock = conn2(host, port, use_ssl, ebuf, ebuf_len)) == INVALID_SOCKET) {
|
||||||
|
} else if ((conn = (struct mg_connection *)
|
||||||
|
calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE)) == NULL) {
|
||||||
|
snprintf(ebuf, ebuf_len, "calloc(): %s", strerror(ERRNO));
|
||||||
|
closesocket(sock);
|
||||||
|
#ifndef NO_SSL
|
||||||
|
} else if (use_ssl && (conn->client_ssl_ctx =
|
||||||
|
SSL_CTX_new(SSLv23_client_method())) == NULL) {
|
||||||
|
snprintf(ebuf, ebuf_len, "SSL_CTX_new error");
|
||||||
|
closesocket(sock);
|
||||||
|
free(conn);
|
||||||
|
conn = NULL;
|
||||||
|
#endif // NO_SSL
|
||||||
|
} else {
|
||||||
|
socklen_t len = sizeof(struct sockaddr);
|
||||||
|
conn->buf_size = MAX_REQUEST_SIZE;
|
||||||
|
conn->buf = (char *) (conn + 1);
|
||||||
|
conn->ctx = &fake_ctx;
|
||||||
|
conn->client.sock = sock;
|
||||||
|
getsockname(sock, &conn->client.rsa.sa, &len);
|
||||||
|
conn->client.is_ssl = use_ssl;
|
||||||
|
#ifndef NO_SSL
|
||||||
|
if (use_ssl) {
|
||||||
|
// SSL_CTX_set_verify call is needed to switch off server certificate
|
||||||
|
// checking, which is off by default in OpenSSL and on in yaSSL.
|
||||||
|
SSL_CTX_set_verify(conn->client_ssl_ctx, 0, 0);
|
||||||
|
sslize(conn, conn->client_ssl_ctx, SSL_connect);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mg_connection *mg_download(const char *host, int port, int use_ssl,
|
||||||
|
char *ebuf, size_t ebuf_len,
|
||||||
|
const char *fmt, ...) {
|
||||||
|
struct mg_connection *conn;
|
||||||
|
const char *msg = NULL;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
if ((conn = mg_connect(host, port, use_ssl, ebuf, ebuf_len)) == NULL) {
|
||||||
|
} else if (mg_vprintf(conn, fmt, ap) <= 0) {
|
||||||
|
snprintf(ebuf, ebuf_len, "%s", "Error sending request");
|
||||||
|
} else {
|
||||||
|
msg = getreq(conn);
|
||||||
|
}
|
||||||
|
if (msg != NULL && conn != NULL) {
|
||||||
|
mg_close_connection(conn);
|
||||||
|
conn = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||||
|
// Copyright (c) 2013 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
//
|
||||||
|
// This library is dual-licensed: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
// published by the Free Software Foundation. For the terms of this
|
||||||
|
// license, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// You are free to use this library under the terms of the GNU General
|
||||||
|
// Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
// See the GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// Alternatively, you can license this library under a commercial
|
||||||
|
// license, as set out in <http://cesanta.com/products.html>.
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#undef _UNICODE
|
||||||
|
#define _MBCS
|
||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#ifdef __linux__
|
||||||
|
#define _XOPEN_SOURCE 600 // For flockfile() on Linux
|
||||||
|
#endif
|
||||||
|
#if !defined(_LARGEFILE_SOURCE)
|
||||||
|
#define _LARGEFILE_SOURCE // Enable 64-bit file offsets
|
||||||
|
#endif
|
||||||
|
#define __STDC_FORMAT_MACROS // <inttypes.h> wants this for C++
|
||||||
|
#define __STDC_LIMIT_MACROS // C++ wants that for INT64_MAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined (_MSC_VER)
|
||||||
|
// conditional expression is constant: introduced by FD_SET(..)
|
||||||
|
#pragma warning (disable : 4127)
|
||||||
|
// non-constant aggregate initializer: issued due to missing C99 support
|
||||||
|
#pragma warning (disable : 4204)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Disable WIN32_LEAN_AND_MEAN.
|
||||||
|
// This makes windows.h always include winsock2.h
|
||||||
|
#ifdef WIN32_LEAN_AND_MEAN
|
||||||
|
#undef WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _WIN32_WCE // Some ANSI #includes are not available on Windows CE
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#endif // !_WIN32_WCE
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#if defined(_WIN32) // Windows specific
|
||||||
|
#undef _WIN32_WINNT
|
||||||
|
#define _WIN32_WINNT 0x0400 // To make it link in VS2005
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#ifndef PATH_MAX
|
||||||
|
#define PATH_MAX MAX_PATH
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _WIN32_WCE
|
||||||
|
#include <process.h>
|
||||||
|
#include <direct.h>
|
||||||
|
#include <io.h>
|
||||||
|
#else // _WIN32_WCE
|
||||||
|
#define NO_CGI // WinCE has no pipes
|
||||||
|
|
||||||
|
typedef long off_t;
|
||||||
|
|
||||||
|
#define errno GetLastError()
|
||||||
|
#define strerror(x) _ultoa(x, (char *) _alloca(sizeof(x) *3 ), 10)
|
||||||
|
#endif // _WIN32_WCE
|
||||||
|
|
||||||
|
#define MAKEUQUAD(lo, hi) ((uint64_t)(((uint32_t)(lo)) | \
|
||||||
|
((uint64_t)((uint32_t)(hi))) << 32))
|
||||||
|
#define RATE_DIFF 10000000 // 100 nsecs
|
||||||
|
#define EPOCH_DIFF MAKEUQUAD(0xd53e8000, 0x019db1de)
|
||||||
|
#define SYS2UNIX_TIME(lo, hi) \
|
||||||
|
(time_t) ((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF)
|
||||||
|
|
||||||
|
// Visual Studio 6 does not know __func__ or __FUNCTION__
|
||||||
|
// The rest of MS compilers use __FUNCTION__, not C99 __func__
|
||||||
|
// Also use _strtoui64 on modern M$ compilers
|
||||||
|
#if defined(_MSC_VER) && _MSC_VER < 1300
|
||||||
|
#define STRX(x) #x
|
||||||
|
#define STR(x) STRX(x)
|
||||||
|
#define __func__ __FILE__ ":" STR(__LINE__)
|
||||||
|
#define strtoull(x, y, z) (unsigned __int64) _atoi64(x)
|
||||||
|
#define strtoll(x, y, z) _atoi64(x)
|
||||||
|
#else
|
||||||
|
#define __func__ __FUNCTION__
|
||||||
|
#define strtoull(x, y, z) _strtoui64(x, y, z)
|
||||||
|
#define strtoll(x, y, z) _strtoi64(x, y, z)
|
||||||
|
#endif // _MSC_VER
|
||||||
|
|
||||||
|
#define ERRNO GetLastError()
|
||||||
|
#define NO_SOCKLEN_T
|
||||||
|
#define SSL_LIB "ssleay32.dll"
|
||||||
|
#define CRYPTO_LIB "libeay32.dll"
|
||||||
|
#define O_NONBLOCK 0
|
||||||
|
#if !defined(EWOULDBLOCK)
|
||||||
|
#define EWOULDBLOCK WSAEWOULDBLOCK
|
||||||
|
#endif // !EWOULDBLOCK
|
||||||
|
#define _POSIX_
|
||||||
|
#define INT64_FMT "I64d"
|
||||||
|
|
||||||
|
#define WINCDECL __cdecl
|
||||||
|
#define SHUT_WR 1
|
||||||
|
#define snprintf _snprintf
|
||||||
|
#define vsnprintf _vsnprintf
|
||||||
|
#define mg_sleep(x) Sleep(x)
|
||||||
|
|
||||||
|
#define pipe(x) _pipe(x, MG_BUF_LEN, _O_BINARY)
|
||||||
|
#ifndef popen
|
||||||
|
#define popen(x, y) _popen(x, y)
|
||||||
|
#endif
|
||||||
|
#ifndef pclose
|
||||||
|
#define pclose(x) _pclose(x)
|
||||||
|
#endif
|
||||||
|
#define close(x) _close(x)
|
||||||
|
#define dlsym(x,y) GetProcAddress((HINSTANCE) (x), (y))
|
||||||
|
#define RTLD_LAZY 0
|
||||||
|
#define fseeko(x, y, z) _lseeki64(_fileno(x), (y), (z))
|
||||||
|
#define fdopen(x, y) _fdopen((x), (y))
|
||||||
|
#define write(x, y, z) _write((x), (y), (unsigned) z)
|
||||||
|
#define read(x, y, z) _read((x), (y), (unsigned) z)
|
||||||
|
#define flockfile(x)
|
||||||
|
#define funlockfile(x)
|
||||||
|
#define sleep(x) Sleep((x) * 1000)
|
||||||
|
#define rmdir(x) _rmdir(x)
|
||||||
|
|
||||||
|
#if !defined(va_copy)
|
||||||
|
#define va_copy(x, y) x = y
|
||||||
|
#endif // !va_copy MINGW #defines va_copy
|
||||||
|
|
||||||
|
#if !defined(fileno)
|
||||||
|
#define fileno(x) _fileno(x)
|
||||||
|
#endif // !fileno MINGW #defines fileno
|
||||||
|
|
||||||
|
typedef HANDLE pthread_mutex_t;
|
||||||
|
typedef DWORD pthread_t;
|
||||||
|
#define pid_t HANDLE // MINGW typedefs pid_t to int. Using #define here.
|
||||||
|
|
||||||
|
static int pthread_mutex_lock(pthread_mutex_t *);
|
||||||
|
static int pthread_mutex_unlock(pthread_mutex_t *);
|
||||||
|
static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len);
|
||||||
|
|
||||||
|
#if defined(HAVE_STDINT)
|
||||||
|
#include <stdint.h>
|
||||||
|
#else
|
||||||
|
typedef unsigned int uint32_t;
|
||||||
|
typedef unsigned short uint16_t;
|
||||||
|
typedef unsigned __int64 uint64_t;
|
||||||
|
typedef __int64 int64_t;
|
||||||
|
#define INT64_MAX 9223372036854775807
|
||||||
|
#endif // HAVE_STDINT
|
||||||
|
|
||||||
|
// POSIX dirent interface
|
||||||
|
struct dirent {
|
||||||
|
char d_name[PATH_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct DIR {
|
||||||
|
HANDLE handle;
|
||||||
|
WIN32_FIND_DATAW info;
|
||||||
|
struct dirent result;
|
||||||
|
} DIR;
|
||||||
|
|
||||||
|
struct pollfd {
|
||||||
|
SOCKET fd;
|
||||||
|
short events;
|
||||||
|
short revents;
|
||||||
|
};
|
||||||
|
#define POLLIN 1
|
||||||
|
|
||||||
|
#ifdef HAVE_POLL
|
||||||
|
#define poll(x, y, z) WSAPoll((x), (y), (z))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// Mark required libraries
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma comment(lib, "Ws2_32.lib")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else // UNIX specific
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#if !defined(NO_SSL_DL) && !defined(NO_SSL)
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#endif
|
||||||
|
#include <pthread.h>
|
||||||
|
#if defined(__MACH__)
|
||||||
|
#define SSL_LIB "libssl.dylib"
|
||||||
|
#define CRYPTO_LIB "libcrypto.dylib"
|
||||||
|
#else
|
||||||
|
#if !defined(SSL_LIB)
|
||||||
|
#define SSL_LIB "libssl.so"
|
||||||
|
#endif
|
||||||
|
#if !defined(CRYPTO_LIB)
|
||||||
|
#define CRYPTO_LIB "libcrypto.so"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifndef O_BINARY
|
||||||
|
#define O_BINARY 0
|
||||||
|
#endif // O_BINARY
|
||||||
|
#define closesocket(a) close(a)
|
||||||
|
#define mg_mkdir(x, y) mkdir(x, y)
|
||||||
|
#define mg_remove(x) remove(x)
|
||||||
|
#define mg_sleep(x) usleep((x) * 1000)
|
||||||
|
#define ERRNO errno
|
||||||
|
#define INVALID_SOCKET (-1)
|
||||||
|
#define INT64_FMT PRId64
|
||||||
|
typedef int SOCKET;
|
||||||
|
#define WINCDECL
|
||||||
|
|
||||||
|
#endif // End of Windows and UNIX specific includes
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
#define MONGOOSE_VERSION "5.0"
|
||||||
|
#define PASSWORDS_FILE_NAME ".htpasswd"
|
||||||
|
#define CGI_ENVIRONMENT_SIZE 4096
|
||||||
|
#define MAX_CGI_ENVIR_VARS 64
|
||||||
|
#define MG_BUF_LEN 8192
|
||||||
|
#define MAX_REQUEST_SIZE 16384
|
||||||
|
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
|
||||||
|
|
||||||
|
typedef SOCKET sock_t;
|
||||||
|
|
||||||
|
#ifdef DEBUG_TRACE
|
||||||
|
#undef DEBUG_TRACE
|
||||||
|
#define DEBUG_TRACE(x)
|
||||||
|
#else
|
||||||
|
#if defined(DEBUG)
|
||||||
|
#define DEBUG_TRACE(x) do { \
|
||||||
|
flockfile(stdout); \
|
||||||
|
printf("*** %lu.%p.%s.%d: ", \
|
||||||
|
(unsigned long) time(NULL), (void *) pthread_self(), \
|
||||||
|
__func__, __LINE__); \
|
||||||
|
printf x; \
|
||||||
|
putchar('\n'); \
|
||||||
|
fflush(stdout); \
|
||||||
|
funlockfile(stdout); \
|
||||||
|
} while (0)
|
||||||
|
#else
|
||||||
|
#define DEBUG_TRACE(x)
|
||||||
|
#endif // DEBUG
|
||||||
|
#endif // DEBUG_TRACE
|
||||||
|
|
||||||
|
// Darwin prior to 7.0 and Win32 do not have socklen_t
|
||||||
|
#ifdef NO_SOCKLEN_T
|
||||||
|
typedef int socklen_t;
|
||||||
|
#endif // NO_SOCKLEN_T
|
||||||
|
#define _DARWIN_UNLIMITED_SELECT
|
||||||
|
|
||||||
|
#define IP_ADDR_STR_LEN 50 // IPv6 hex string is 46 chars
|
||||||
|
|
||||||
|
#if !defined(MSG_NOSIGNAL)
|
||||||
|
#define MSG_NOSIGNAL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(SOMAXCONN)
|
||||||
|
#define SOMAXCONN 100
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(PATH_MAX)
|
||||||
|
#define PATH_MAX 4096
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Size of the accepted socket queue
|
||||||
|
#if !defined(MGSQLEN)
|
||||||
|
#define MGSQLEN 20
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Extra HTTP headers to send in every static file reply
|
||||||
|
#if !defined(EXTRA_HTTP_HEADERS)
|
||||||
|
#define EXTRA_HTTP_HEADERS ""
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const char *http_500_error = "Internal Server Error";
|
||||||
|
|
||||||
|
#if defined(NO_SSL_DL)
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#else
|
||||||
|
// SSL loaded dynamically from DLL.
|
||||||
|
// I put the prototypes here to be independent from OpenSSL source installation.
|
||||||
|
typedef struct ssl_st SSL;
|
||||||
|
typedef struct ssl_method_st SSL_METHOD;
|
||||||
|
typedef struct ssl_ctx_st SSL_CTX;
|
||||||
|
|
||||||
|
struct ssl_func {
|
||||||
|
const char *name; // SSL function name
|
||||||
|
void (*ptr)(void); // Function pointer
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SSL_free (* (void (*)(SSL *)) ssl_sw[0].ptr)
|
||||||
|
#define SSL_accept (* (int (*)(SSL *)) ssl_sw[1].ptr)
|
||||||
|
#define SSL_connect (* (int (*)(SSL *)) ssl_sw[2].ptr)
|
||||||
|
#define SSL_read (* (int (*)(SSL *, void *, int)) ssl_sw[3].ptr)
|
||||||
|
#define SSL_write (* (int (*)(SSL *, const void *,int)) ssl_sw[4].ptr)
|
||||||
|
#define SSL_get_error (* (int (*)(SSL *, int)) ssl_sw[5].ptr)
|
||||||
|
#define SSL_set_fd (* (int (*)(SSL *, SOCKET)) ssl_sw[6].ptr)
|
||||||
|
#define SSL_new (* (SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr)
|
||||||
|
#define SSL_CTX_new (* (SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr)
|
||||||
|
#define SSLv23_server_method (* (SSL_METHOD * (*)(void)) ssl_sw[9].ptr)
|
||||||
|
#define SSL_library_init (* (int (*)(void)) ssl_sw[10].ptr)
|
||||||
|
#define SSL_CTX_use_PrivateKey_file (* (int (*)(SSL_CTX *, \
|
||||||
|
const char *, int)) ssl_sw[11].ptr)
|
||||||
|
#define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \
|
||||||
|
const char *, int)) ssl_sw[12].ptr)
|
||||||
|
#define SSL_CTX_set_default_passwd_cb \
|
||||||
|
(* (void (*)(SSL_CTX *, mg_event_handler_t)) ssl_sw[13].ptr)
|
||||||
|
#define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr)
|
||||||
|
#define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr)
|
||||||
|
#define SSL_CTX_use_certificate_chain_file \
|
||||||
|
(* (int (*)(SSL_CTX *, const char *)) ssl_sw[16].ptr)
|
||||||
|
#define SSLv23_client_method (* (SSL_METHOD * (*)(void)) ssl_sw[17].ptr)
|
||||||
|
#define SSL_pending (* (int (*)(SSL *)) ssl_sw[18].ptr)
|
||||||
|
#define SSL_CTX_set_verify (* (void (*)(SSL_CTX *, int, int)) ssl_sw[19].ptr)
|
||||||
|
#define SSL_shutdown (* (int (*)(SSL *)) ssl_sw[20].ptr)
|
||||||
|
|
||||||
|
#define CRYPTO_num_locks (* (int (*)(void)) crypto_sw[0].ptr)
|
||||||
|
#define CRYPTO_set_locking_callback \
|
||||||
|
(* (void (*)(void (*)(int, int, const char *, int))) crypto_sw[1].ptr)
|
||||||
|
#define CRYPTO_set_id_callback \
|
||||||
|
(* (void (*)(unsigned long (*)(void))) crypto_sw[2].ptr)
|
||||||
|
#define ERR_get_error (* (unsigned long (*)(void)) crypto_sw[3].ptr)
|
||||||
|
#define ERR_error_string (* (char * (*)(unsigned long,char *)) crypto_sw[4].ptr)
|
||||||
|
#endif // NO_SSL_DL
|
||||||
|
|
||||||
|
// Unified socket address. For IPv6 support, add IPv6 address structure
|
||||||
|
// in the union u.
|
||||||
|
union usa {
|
||||||
|
struct sockaddr sa;
|
||||||
|
struct sockaddr_in sin;
|
||||||
|
#if defined(USE_IPV6)
|
||||||
|
struct sockaddr_in6 sin6;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Describes a string (chunk of memory).
|
||||||
|
struct vec {
|
||||||
|
const char *ptr;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct file {
|
||||||
|
int is_directory;
|
||||||
|
time_t modification_time;
|
||||||
|
int64_t size;
|
||||||
|
// set to 1 if the content is gzipped
|
||||||
|
// in which case we need a content-encoding: gzip header
|
||||||
|
int gzipped;
|
||||||
|
};
|
||||||
|
#define STRUCT_FILE_INITIALIZER { 0, 0, 0, 0 }
|
||||||
|
|
||||||
|
// Describes listening socket, or socket which was accept()-ed by the master
|
||||||
|
// thread and queued for future handling by the worker thread.
|
||||||
|
struct socket {
|
||||||
|
SOCKET sock; // Listening socket
|
||||||
|
union usa lsa; // Local socket address
|
||||||
|
union usa rsa; // Remote socket address
|
||||||
|
unsigned is_ssl:1; // Is port SSL-ed
|
||||||
|
unsigned ssl_redir:1; // Is port supposed to redirect everything to SSL port
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE(lsm): this enum shoulds be in sync with the config_options.
|
||||||
|
enum {
|
||||||
|
CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER,
|
||||||
|
PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS,
|
||||||
|
ACCESS_LOG_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
|
||||||
|
GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST,
|
||||||
|
EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
|
||||||
|
NUM_THREADS, RUN_AS_USER, REWRITE, HIDE_FILES, REQUEST_TIMEOUT,
|
||||||
|
NUM_OPTIONS
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mg_context {
|
||||||
|
volatile int stop_flag; // Should we stop event loop
|
||||||
|
SSL_CTX *ssl_ctx; // SSL context
|
||||||
|
char *config[NUM_OPTIONS]; // Mongoose configuration parameters
|
||||||
|
mg_event_handler_t event_handler; // User-defined callback function
|
||||||
|
void *user_data; // User-defined data
|
||||||
|
struct socket *listening_sockets;
|
||||||
|
int num_listening_sockets;
|
||||||
|
int num_threads; // Number of threads
|
||||||
|
sock_t ctl[2]; // Socket pair for inter-thread communication
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mg_connection {
|
||||||
|
struct mg_request_info request_info;
|
||||||
|
struct mg_event event;
|
||||||
|
struct mg_context *ctx;
|
||||||
|
SSL *ssl; // SSL descriptor
|
||||||
|
SSL_CTX *client_ssl_ctx; // SSL context for client connections
|
||||||
|
struct socket client; // Connected client
|
||||||
|
time_t birth_time; // Time when request was received
|
||||||
|
int64_t num_bytes_sent; // Total bytes sent to client
|
||||||
|
int64_t content_len; // Content-Length header value
|
||||||
|
int64_t num_bytes_read; // Bytes read from a remote socket
|
||||||
|
char *buf; // Buffer for received data
|
||||||
|
char *path_info; // PATH_INFO part of the URL
|
||||||
|
int must_close; // 1 if connection must be closed
|
||||||
|
int buf_size; // Buffer size
|
||||||
|
int request_len; // Size of the request + headers in a buffer
|
||||||
|
int data_len; // Total size of data in a buffer
|
||||||
|
int status_code; // HTTP reply status code, e.g. 200
|
||||||
|
};
|
||||||
|
|
||||||
|
// Directory entry
|
||||||
|
struct de {
|
||||||
|
struct mg_connection *conn;
|
||||||
|
char *file_name;
|
||||||
|
struct file file;
|
||||||
|
};
|
||||||
|
|
||||||
|
static FILE *mg_fopen(const char *path, const char *mode);
|
||||||
|
static int mg_stat(const char *path, struct file *filep);
|
||||||
|
static void send_http_error(struct mg_connection *, int, const char *,
|
||||||
|
PRINTF_FORMAT_STRING(const char *fmt), ...)
|
||||||
|
PRINTF_ARGS(4, 5);
|
||||||
|
static void cry(struct mg_connection *conn,
|
||||||
|
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
|
||||||
|
static const char *getreq(struct mg_connection *conn);
|
||||||
|
|
||||||
|
#ifdef USE_LUA
|
||||||
|
#include "lua_5.2.1.h"
|
||||||
|
static int handle_lsp_request(struct mg_connection *, const char *,
|
||||||
|
struct file *, struct lua_State *);
|
||||||
|
#endif
|
||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
// Return number of bytes left to read for this connection
|
||||||
|
static int64_t left_to_read(const struct mg_connection *conn) {
|
||||||
|
return conn->content_len + conn->request_len - conn->num_bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data to the IO channel - opened file descriptor, socket or SSL
|
||||||
|
// descriptor. Return number of bytes written.
|
||||||
|
static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
|
||||||
|
int64_t len) {
|
||||||
|
int64_t sent;
|
||||||
|
int n, k;
|
||||||
|
|
||||||
|
(void) ssl; // Get rid of warning
|
||||||
|
sent = 0;
|
||||||
|
while (sent < len) {
|
||||||
|
|
||||||
|
// How many bytes we send in this iteration
|
||||||
|
k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
|
||||||
|
|
||||||
|
#if !defined(NO_SSL)
|
||||||
|
if (ssl != NULL) {
|
||||||
|
n = SSL_write(ssl, buf + sent, k);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
if (fp != NULL) {
|
||||||
|
n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
|
||||||
|
if (ferror(fp))
|
||||||
|
n = -1;
|
||||||
|
} else {
|
||||||
|
n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
sent += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
|
||||||
|
// Return negative value on error, or number of bytes read on success.
|
||||||
|
static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
|
||||||
|
int nread;
|
||||||
|
|
||||||
|
if (len <= 0) return 0;
|
||||||
|
if (fp != NULL) {
|
||||||
|
// Use read() instead of fread(), because if we're reading from the CGI
|
||||||
|
// pipe, fread() may block until IO buffer is filled up. We cannot afford
|
||||||
|
// to block and must pass all read bytes immediately to the client.
|
||||||
|
nread = read(fileno(fp), buf, (size_t) len);
|
||||||
|
#ifndef NO_SSL
|
||||||
|
} else if (conn->ssl != NULL) {
|
||||||
|
nread = SSL_read(conn->ssl, buf, len);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
nread = recv(conn->client.sock, buf, (size_t) len, 0);
|
||||||
|
}
|
||||||
|
if (nread > 0) {
|
||||||
|
conn->num_bytes_read += nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn->ctx->stop_flag ? -1 : nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
|
||||||
|
int n, nread = 0;
|
||||||
|
|
||||||
|
while (len > 0 && conn->ctx->stop_flag == 0) {
|
||||||
|
n = pull(fp, conn, buf + nread, len);
|
||||||
|
if (n < 0) {
|
||||||
|
nread = n; // Propagate the error
|
||||||
|
break;
|
||||||
|
} else if (n == 0) {
|
||||||
|
break; // No more data to read
|
||||||
|
} else {
|
||||||
|
nread += n;
|
||||||
|
len -= n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_read(struct mg_connection *conn, void *buf, int len) {
|
||||||
|
int n, buffered_len, nread = 0;
|
||||||
|
int64_t left;
|
||||||
|
|
||||||
|
if (conn->content_len <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// conn->buf body
|
||||||
|
// |=================|==========|===============|
|
||||||
|
// |<--request_len-->| |
|
||||||
|
// |<-----------data_len------->| conn->buf + conn->buf_size
|
||||||
|
|
||||||
|
// First, check for data buffered in conn->buf by read_request().
|
||||||
|
if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) {
|
||||||
|
char *body = conn->buf + conn->request_len;
|
||||||
|
if (buffered_len > len) buffered_len = len;
|
||||||
|
if (buffered_len > conn->content_len) buffered_len = (int)conn->content_len;
|
||||||
|
|
||||||
|
memcpy(buf, body, (size_t) buffered_len);
|
||||||
|
memmove(body, body + buffered_len,
|
||||||
|
&conn->buf[conn->data_len] - &body[buffered_len]);
|
||||||
|
len -= buffered_len;
|
||||||
|
conn->data_len -= buffered_len;
|
||||||
|
nread += buffered_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data from the socket.
|
||||||
|
if (len > 0 && (left = left_to_read(conn)) > 0) {
|
||||||
|
if (left < len) {
|
||||||
|
len = (int) left;
|
||||||
|
}
|
||||||
|
n = pull_all(NULL, conn, (char *) buf + nread, (int) len);
|
||||||
|
nread = n >= 0 ? nread + n : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_write(struct mg_connection *conn, const void *buf, int len) {
|
||||||
|
return (int) push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
|
||||||
|
(int64_t) len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep reading the input (either opened file descriptor fd, or socket sock,
|
||||||
|
// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
|
||||||
|
// buffer (which marks the end of HTTP request). Buffer buf may already
|
||||||
|
// have some data. The length of the data is stored in nread.
|
||||||
|
// Upon every read operation, increase nread by the number of bytes read.
|
||||||
|
static int read_request(FILE *fp, struct mg_connection *conn,
|
||||||
|
char *buf, int bufsiz, int *nread) {
|
||||||
|
int request_len, n = 0;
|
||||||
|
|
||||||
|
request_len = get_request_len(buf, *nread);
|
||||||
|
while (conn->ctx->stop_flag == 0 &&
|
||||||
|
*nread < bufsiz &&
|
||||||
|
request_len == 0 &&
|
||||||
|
(n = pull(fp, conn, buf + *nread, bufsiz - *nread)) > 0) {
|
||||||
|
*nread += n;
|
||||||
|
assert(*nread <= bufsiz);
|
||||||
|
request_len = get_request_len(buf, *nread);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request_len <= 0 && n <= 0 ? -1 : request_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send len bytes from the opened file to the client.
|
||||||
|
static void send_file_data(struct mg_connection *conn, FILE *fp,
|
||||||
|
int64_t offset, int64_t len) {
|
||||||
|
char buf[MG_BUF_LEN];
|
||||||
|
int num_read, num_written, to_read;
|
||||||
|
|
||||||
|
// If offset is beyond file boundaries, don't send anything
|
||||||
|
if (offset > 0 && fseeko(fp, offset, SEEK_SET) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (len > 0) {
|
||||||
|
// Calculate how much to read from the file in the buffer
|
||||||
|
to_read = sizeof(buf);
|
||||||
|
if ((int64_t) to_read > len) {
|
||||||
|
to_read = (int) len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read from file, exit the loop on error
|
||||||
|
if ((num_read = fread(buf, 1, (size_t) to_read, fp)) <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send read bytes to the client, exit the loop on error
|
||||||
|
if ((num_written = mg_write(conn, buf, (size_t) num_read)) != num_read) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both read and were successful, adjust counters
|
||||||
|
conn->num_bytes_sent += num_written;
|
||||||
|
len -= num_written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
static void log_header(const struct mg_connection *conn, const char *header,
|
||||||
|
FILE *fp) {
|
||||||
|
const char *header_value;
|
||||||
|
|
||||||
|
if ((header_value = mg_get_header(conn, header)) == NULL) {
|
||||||
|
(void) fprintf(fp, "%s", " -");
|
||||||
|
} else {
|
||||||
|
(void) fprintf(fp, " \"%s\"", header_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_access(const struct mg_connection *conn) {
|
||||||
|
const struct mg_request_info *ri;
|
||||||
|
FILE *fp;
|
||||||
|
char date[64], src_addr[IP_ADDR_STR_LEN];
|
||||||
|
|
||||||
|
fp = conn->ctx->config[ACCESS_LOG_FILE] == NULL ? NULL :
|
||||||
|
fopen(conn->ctx->config[ACCESS_LOG_FILE], "a+");
|
||||||
|
|
||||||
|
if (fp == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z",
|
||||||
|
localtime(&conn->birth_time));
|
||||||
|
|
||||||
|
ri = &conn->request_info;
|
||||||
|
flockfile(fp);
|
||||||
|
|
||||||
|
sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
|
||||||
|
fprintf(fp, "%s - %s [%s] \"%s %s HTTP/%s\" %d %" INT64_FMT,
|
||||||
|
src_addr, ri->remote_user == NULL ? "-" : ri->remote_user, date,
|
||||||
|
ri->request_method ? ri->request_method : "-",
|
||||||
|
ri->uri ? ri->uri : "-", ri->http_version,
|
||||||
|
conn->status_code, conn->num_bytes_sent);
|
||||||
|
log_header(conn, "Referer", fp);
|
||||||
|
log_header(conn, "User-Agent", fp);
|
||||||
|
fputc('\n', fp);
|
||||||
|
fflush(fp);
|
||||||
|
|
||||||
|
funlockfile(fp);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
+390
@@ -0,0 +1,390 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#ifdef USE_LUA
|
||||||
|
#ifdef _WIN32
|
||||||
|
static void *mmap(void *addr, int64_t len, int prot, int flags, int fd,
|
||||||
|
int offset) {
|
||||||
|
HANDLE fh = (HANDLE) _get_osfhandle(fd);
|
||||||
|
HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0);
|
||||||
|
void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t) len);
|
||||||
|
CloseHandle(mh);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
#define munmap(x, y) UnmapViewOfFile(x)
|
||||||
|
#define MAP_FAILED NULL
|
||||||
|
#define MAP_PRIVATE 0
|
||||||
|
#define PROT_READ 0
|
||||||
|
#else
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const char *LUASOCKET = "luasocket";
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
static int handle_lsp_request(struct mg_connection *, const char *,
|
||||||
|
struct file *, struct lua_State *);
|
||||||
|
|
||||||
|
static void reg_string(struct lua_State *L, const char *name, const char *val) {
|
||||||
|
lua_pushstring(L, name);
|
||||||
|
lua_pushstring(L, val);
|
||||||
|
lua_rawset(L, -3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reg_int(struct lua_State *L, const char *name, int val) {
|
||||||
|
lua_pushstring(L, name);
|
||||||
|
lua_pushinteger(L, val);
|
||||||
|
lua_rawset(L, -3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reg_function(struct lua_State *L, const char *name,
|
||||||
|
lua_CFunction func, struct mg_connection *conn) {
|
||||||
|
lua_pushstring(L, name);
|
||||||
|
lua_pushlightuserdata(L, conn);
|
||||||
|
lua_pushcclosure(L, func, 1);
|
||||||
|
lua_rawset(L, -3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lsp_sock_close(lua_State *L) {
|
||||||
|
if (lua_gettop(L) > 0 && lua_istable(L, -1)) {
|
||||||
|
lua_getfield(L, -1, "sock");
|
||||||
|
closesocket((SOCKET) lua_tonumber(L, -1));
|
||||||
|
} else {
|
||||||
|
return luaL_error(L, "invalid :close() call");
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lsp_sock_recv(lua_State *L) {
|
||||||
|
char buf[2000];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (lua_gettop(L) > 0 && lua_istable(L, -1)) {
|
||||||
|
lua_getfield(L, -1, "sock");
|
||||||
|
n = recv((SOCKET) lua_tonumber(L, -1), buf, sizeof(buf), 0);
|
||||||
|
if (n <= 0) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
} else {
|
||||||
|
lua_pushlstring(L, buf, n);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return luaL_error(L, "invalid :close() call");
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lsp_sock_send(lua_State *L) {
|
||||||
|
const char *buf;
|
||||||
|
size_t len, sent = 0;
|
||||||
|
int n, sock;
|
||||||
|
|
||||||
|
if (lua_gettop(L) > 1 && lua_istable(L, -2) && lua_isstring(L, -1)) {
|
||||||
|
buf = lua_tolstring(L, -1, &len);
|
||||||
|
lua_getfield(L, -2, "sock");
|
||||||
|
sock = (int) lua_tonumber(L, -1);
|
||||||
|
while (sent < len) {
|
||||||
|
if ((n = send(sock, buf + sent, len - sent, 0)) <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sent += n;
|
||||||
|
}
|
||||||
|
lua_pushnumber(L, n);
|
||||||
|
} else {
|
||||||
|
return luaL_error(L, "invalid :close() call");
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct luaL_Reg luasocket_methods[] = {
|
||||||
|
{"close", lsp_sock_close},
|
||||||
|
{"send", lsp_sock_send},
|
||||||
|
{"recv", lsp_sock_recv},
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int lsp_connect(lua_State *L) {
|
||||||
|
char ebuf[100];
|
||||||
|
SOCKET sock;
|
||||||
|
|
||||||
|
if (lua_isstring(L, -3) && lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
|
||||||
|
sock = conn2(lua_tostring(L, -3), (int) lua_tonumber(L, -2),
|
||||||
|
(int) lua_tonumber(L, -1), ebuf, sizeof(ebuf));
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
return luaL_error(L, ebuf);
|
||||||
|
} else {
|
||||||
|
lua_newtable(L);
|
||||||
|
reg_int(L, "sock", sock);
|
||||||
|
reg_string(L, "host", lua_tostring(L, -4));
|
||||||
|
luaL_getmetatable(L, LUASOCKET);
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return luaL_error(L, "connect(host,port,is_ssl): invalid parameter given.");
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lsp_error(lua_State *L) {
|
||||||
|
lua_getglobal(L, "mg");
|
||||||
|
lua_getfield(L, -1, "onerror");
|
||||||
|
lua_pushvalue(L, -3);
|
||||||
|
lua_pcall(L, 1, 0, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Silently stop processing chunks.
|
||||||
|
static void lsp_abort(lua_State *L) {
|
||||||
|
int top = lua_gettop(L);
|
||||||
|
lua_getglobal(L, "mg");
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_setfield(L, -2, "onerror");
|
||||||
|
lua_settop(L, top);
|
||||||
|
lua_pushstring(L, "aborting");
|
||||||
|
lua_error(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lsp(struct mg_connection *conn, const char *path,
|
||||||
|
const char *p, int64_t len, lua_State *L) {
|
||||||
|
int i, j, pos = 0, lines = 1, lualines = 0;
|
||||||
|
char chunkname[MG_BUF_LEN];
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
if (p[i] == '\n') lines++;
|
||||||
|
if (p[i] == '<' && p[i + 1] == '?') {
|
||||||
|
for (j = i + 1; j < len ; j++) {
|
||||||
|
if (p[j] == '\n') lualines++;
|
||||||
|
if (p[j] == '?' && p[j + 1] == '>') {
|
||||||
|
mg_write(conn, p + pos, i - pos);
|
||||||
|
|
||||||
|
snprintf(chunkname, sizeof(chunkname), "@%s+%i", path, lines);
|
||||||
|
lua_pushlightuserdata(L, conn);
|
||||||
|
lua_pushcclosure(L, lsp_error, 1);
|
||||||
|
if (luaL_loadbuffer(L, p + (i + 2), j - (i + 2), chunkname)) {
|
||||||
|
// Syntax error or OOM. Error message is pushed on stack.
|
||||||
|
lua_pcall(L, 1, 0, 0);
|
||||||
|
} else {
|
||||||
|
// Success loading chunk. Call it.
|
||||||
|
lua_pcall(L, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = j + 2;
|
||||||
|
i = pos - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lualines > 0) {
|
||||||
|
lines += lualines;
|
||||||
|
lualines = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > pos) {
|
||||||
|
mg_write(conn, p + pos, i - pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lsp_write(lua_State *L) {
|
||||||
|
int i, num_args;
|
||||||
|
const char *str;
|
||||||
|
size_t size;
|
||||||
|
struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
|
||||||
|
|
||||||
|
num_args = lua_gettop(L);
|
||||||
|
for (i = 1; i <= num_args; i++) {
|
||||||
|
if (lua_isstring(L, i)) {
|
||||||
|
str = lua_tolstring(L, i, &size);
|
||||||
|
mg_write(conn, str, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lsp_read(lua_State *L) {
|
||||||
|
struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
|
||||||
|
char buf[1024];
|
||||||
|
int len = mg_read(conn, buf, sizeof(buf));
|
||||||
|
|
||||||
|
if (len <= 0) return 0;
|
||||||
|
lua_pushlstring(L, buf, len);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mg.include: Include another .lp file
|
||||||
|
static int lsp_include(lua_State *L) {
|
||||||
|
struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
|
||||||
|
struct file file = STRUCT_FILE_INITIALIZER;
|
||||||
|
if (handle_lsp_request(conn, lua_tostring(L, -1), &file, L)) {
|
||||||
|
// handle_lsp_request returned an error code, meaning an error occured in
|
||||||
|
// the included page and mg.onerror returned non-zero. Stop processing.
|
||||||
|
lsp_abort(L);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mg.cry: Log an error. Default value for mg.onerror.
|
||||||
|
static int lsp_cry(lua_State *L){
|
||||||
|
struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
|
||||||
|
cry(conn, "%s", lua_tostring(L, -1));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mg.redirect: Redirect the request (internally).
|
||||||
|
static int lsp_redirect(lua_State *L) {
|
||||||
|
struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
|
||||||
|
conn->request_info.uri = lua_tostring(L, -1);
|
||||||
|
handle_request(conn);
|
||||||
|
lsp_abort(L);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prepare_lua_environment(struct mg_connection *conn, lua_State *L) {
|
||||||
|
const struct mg_request_info *ri = &conn->request_info;
|
||||||
|
extern void luaL_openlibs(lua_State *);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
luaL_openlibs(L);
|
||||||
|
#ifdef USE_LUA_SQLITE3
|
||||||
|
{ extern int luaopen_lsqlite3(lua_State *); luaopen_lsqlite3(L); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
luaL_newmetatable(L, LUASOCKET);
|
||||||
|
lua_pushliteral(L, "__index");
|
||||||
|
luaL_newlib(L, luasocket_methods);
|
||||||
|
lua_rawset(L, -3);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
lua_register(L, "connect", lsp_connect);
|
||||||
|
|
||||||
|
if (conn == NULL) return;
|
||||||
|
|
||||||
|
// Register mg module
|
||||||
|
lua_newtable(L);
|
||||||
|
|
||||||
|
reg_function(L, "read", lsp_read, conn);
|
||||||
|
reg_function(L, "write", lsp_write, conn);
|
||||||
|
reg_function(L, "cry", lsp_cry, conn);
|
||||||
|
reg_function(L, "include", lsp_include, conn);
|
||||||
|
reg_function(L, "redirect", lsp_redirect, conn);
|
||||||
|
reg_string(L, "version", MONGOOSE_VERSION);
|
||||||
|
|
||||||
|
// Export request_info
|
||||||
|
lua_pushstring(L, "request_info");
|
||||||
|
lua_newtable(L);
|
||||||
|
reg_string(L, "request_method", ri->request_method);
|
||||||
|
reg_string(L, "uri", ri->uri);
|
||||||
|
reg_string(L, "http_version", ri->http_version);
|
||||||
|
reg_string(L, "query_string", ri->query_string);
|
||||||
|
reg_int(L, "remote_ip", ri->remote_ip);
|
||||||
|
reg_int(L, "remote_port", ri->remote_port);
|
||||||
|
reg_int(L, "num_headers", ri->num_headers);
|
||||||
|
lua_pushstring(L, "http_headers");
|
||||||
|
lua_newtable(L);
|
||||||
|
for (i = 0; i < ri->num_headers; i++) {
|
||||||
|
reg_string(L, ri->http_headers[i].name, ri->http_headers[i].value);
|
||||||
|
}
|
||||||
|
lua_rawset(L, -3);
|
||||||
|
lua_rawset(L, -3);
|
||||||
|
|
||||||
|
lua_setglobal(L, "mg");
|
||||||
|
|
||||||
|
// Register default mg.onerror function
|
||||||
|
luaL_dostring(L, "mg.onerror = function(e) mg.write('\\nLua error:\\n', "
|
||||||
|
"debug.traceback(e, 1)) end");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lua_error_handler(lua_State *L) {
|
||||||
|
const char *error_msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "?\n";
|
||||||
|
|
||||||
|
lua_getglobal(L, "mg");
|
||||||
|
if (!lua_isnil(L, -1)) {
|
||||||
|
lua_getfield(L, -1, "write"); // call mg.write()
|
||||||
|
lua_pushstring(L, error_msg);
|
||||||
|
lua_pushliteral(L, "\n");
|
||||||
|
lua_call(L, 2, 0);
|
||||||
|
luaL_dostring(L, "mg.write(debug.traceback(), '\\n')");
|
||||||
|
} else {
|
||||||
|
printf("Lua error: [%s]\n", error_msg);
|
||||||
|
luaL_dostring(L, "print(debug.traceback(), '\\n')");
|
||||||
|
}
|
||||||
|
// TODO(lsm): leave the stack balanced
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mg_exec_lua_script(struct mg_connection *conn, const char *path,
|
||||||
|
const void **exports) {
|
||||||
|
int i;
|
||||||
|
lua_State *L;
|
||||||
|
|
||||||
|
if (path != NULL && (L = luaL_newstate()) != NULL) {
|
||||||
|
prepare_lua_environment(conn, L);
|
||||||
|
lua_pushcclosure(L, &lua_error_handler, 0);
|
||||||
|
|
||||||
|
lua_pushglobaltable(L);
|
||||||
|
if (exports != NULL) {
|
||||||
|
for (i = 0; exports[i] != NULL && exports[i + 1] != NULL; i += 2) {
|
||||||
|
lua_pushstring(L, exports[i]);
|
||||||
|
lua_pushcclosure(L, (lua_CFunction) exports[i + 1], 0);
|
||||||
|
lua_rawset(L, -3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (luaL_loadfile(L, path) != 0) {
|
||||||
|
lua_error_handler(L);
|
||||||
|
}
|
||||||
|
lua_pcall(L, 0, 0, -2);
|
||||||
|
lua_close(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lsp_send_err(struct mg_connection *conn, struct lua_State *L,
|
||||||
|
const char *fmt, ...) {
|
||||||
|
char buf[MG_BUF_LEN];
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
if (L == NULL) {
|
||||||
|
send_http_error(conn, 500, http_500_error, "%s", buf);
|
||||||
|
} else {
|
||||||
|
lua_pushstring(L, buf);
|
||||||
|
lua_error(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_lsp_request(struct mg_connection *conn, const char *path,
|
||||||
|
struct file *filep, struct lua_State *ls) {
|
||||||
|
void *p = NULL;
|
||||||
|
lua_State *L = NULL;
|
||||||
|
FILE *fp = NULL;
|
||||||
|
int error = 1;
|
||||||
|
|
||||||
|
// We need both mg_stat to get file size, and mg_fopen to get fd
|
||||||
|
if (!mg_stat(path, filep) || (fp = mg_fopen(path, "r")) == NULL) {
|
||||||
|
lsp_send_err(conn, ls, "File [%s] not found", path);
|
||||||
|
} else if ((p = mmap(NULL, (size_t) filep->size, PROT_READ, MAP_PRIVATE,
|
||||||
|
fileno(fp), 0)) == MAP_FAILED) {
|
||||||
|
lsp_send_err(conn, ls, "mmap(%s, %zu, %d): %s", path, (size_t) filep->size,
|
||||||
|
fileno(fp), strerror(errno));
|
||||||
|
} else if ((L = ls != NULL ? ls : luaL_newstate()) == NULL) {
|
||||||
|
send_http_error(conn, 500, http_500_error, "%s", "luaL_newstate failed");
|
||||||
|
} else {
|
||||||
|
// We're not sending HTTP headers here, Lua page must do it.
|
||||||
|
if (ls == NULL) {
|
||||||
|
prepare_lua_environment(conn, L);
|
||||||
|
}
|
||||||
|
error = lsp(conn, path, p, filep->size, L);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L != NULL && ls == NULL) lua_close(L);
|
||||||
|
if (p != NULL) munmap(p, filep->size);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
#endif // USE_LUA
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
// Print message to buffer. If buffer is large enough to hold the message,
|
||||||
|
// return buffer. If buffer is to small, allocate large enough buffer on heap,
|
||||||
|
// and return allocated buffer.
|
||||||
|
static int alloc_vprintf(char **buf, size_t size, const char *fmt, va_list ap) {
|
||||||
|
va_list ap_copy;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
// Windows is not standard-compliant, and vsnprintf() returns -1 if
|
||||||
|
// buffer is too small. Also, older versions of msvcrt.dll do not have
|
||||||
|
// _vscprintf(). However, if size is 0, vsnprintf() behaves correctly.
|
||||||
|
// Therefore, we make two passes: on first pass, get required message length.
|
||||||
|
// On second pass, actually print the message.
|
||||||
|
va_copy(ap_copy, ap);
|
||||||
|
len = vsnprintf(NULL, 0, fmt, ap_copy);
|
||||||
|
|
||||||
|
if (len > (int) size &&
|
||||||
|
(size = len + 1) > 0 &&
|
||||||
|
(*buf = (char *) malloc(size)) == NULL) {
|
||||||
|
len = -1; // Allocation failed, mark failure
|
||||||
|
} else {
|
||||||
|
va_copy(ap_copy, ap);
|
||||||
|
vsnprintf(*buf, size, fmt, ap_copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) {
|
||||||
|
char mem[MG_BUF_LEN], *buf = mem;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
|
||||||
|
len = mg_write(conn, buf, (size_t) len);
|
||||||
|
}
|
||||||
|
if (buf != mem && buf != NULL) {
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
return mg_vprintf(conn, fmt, ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mg_chunked_printf(struct mg_connection *conn, const char *fmt, ...) {
|
||||||
|
char mem[MG_BUF_LEN], *buf = mem;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
|
||||||
|
len = mg_printf(conn, "%X\r\n%s\r\n", len, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf != mem && buf != NULL) {
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
const char *extension;
|
||||||
|
size_t ext_len;
|
||||||
|
const char *mime_type;
|
||||||
|
} builtin_mime_types[] = {
|
||||||
|
{".html", 5, "text/html"},
|
||||||
|
{".htm", 4, "text/html"},
|
||||||
|
{".shtm", 5, "text/html"},
|
||||||
|
{".shtml", 6, "text/html"},
|
||||||
|
{".css", 4, "text/css"},
|
||||||
|
{".js", 3, "application/x-javascript"},
|
||||||
|
{".ico", 4, "image/x-icon"},
|
||||||
|
{".gif", 4, "image/gif"},
|
||||||
|
{".jpg", 4, "image/jpeg"},
|
||||||
|
{".jpeg", 5, "image/jpeg"},
|
||||||
|
{".png", 4, "image/png"},
|
||||||
|
{".svg", 4, "image/svg+xml"},
|
||||||
|
{".txt", 4, "text/plain"},
|
||||||
|
{".torrent", 8, "application/x-bittorrent"},
|
||||||
|
{".wav", 4, "audio/x-wav"},
|
||||||
|
{".mp3", 4, "audio/x-mp3"},
|
||||||
|
{".mid", 4, "audio/mid"},
|
||||||
|
{".m3u", 4, "audio/x-mpegurl"},
|
||||||
|
{".ogg", 4, "application/ogg"},
|
||||||
|
{".ram", 4, "audio/x-pn-realaudio"},
|
||||||
|
{".xml", 4, "text/xml"},
|
||||||
|
{".json", 5, "text/json"},
|
||||||
|
{".xslt", 5, "application/xml"},
|
||||||
|
{".xsl", 4, "application/xml"},
|
||||||
|
{".ra", 3, "audio/x-pn-realaudio"},
|
||||||
|
{".doc", 4, "application/msword"},
|
||||||
|
{".exe", 4, "application/octet-stream"},
|
||||||
|
{".zip", 4, "application/x-zip-compressed"},
|
||||||
|
{".xls", 4, "application/excel"},
|
||||||
|
{".tgz", 4, "application/x-tar-gz"},
|
||||||
|
{".tar", 4, "application/x-tar"},
|
||||||
|
{".gz", 3, "application/x-gunzip"},
|
||||||
|
{".arj", 4, "application/x-arj-compressed"},
|
||||||
|
{".rar", 4, "application/x-arj-compressed"},
|
||||||
|
{".rtf", 4, "application/rtf"},
|
||||||
|
{".pdf", 4, "application/pdf"},
|
||||||
|
{".swf", 4, "application/x-shockwave-flash"},
|
||||||
|
{".mpg", 4, "video/mpeg"},
|
||||||
|
{".webm", 5, "video/webm"},
|
||||||
|
{".mpeg", 5, "video/mpeg"},
|
||||||
|
{".mov", 4, "video/quicktime"},
|
||||||
|
{".mp4", 4, "video/mp4"},
|
||||||
|
{".m4v", 4, "video/x-m4v"},
|
||||||
|
{".asf", 4, "video/x-ms-asf"},
|
||||||
|
{".avi", 4, "video/x-msvideo"},
|
||||||
|
{".bmp", 4, "image/bmp"},
|
||||||
|
{".ttf", 4, "application/x-font-ttf"},
|
||||||
|
{NULL, 0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *mg_get_builtin_mime_type(const char *path) {
|
||||||
|
const char *ext;
|
||||||
|
size_t i, path_len;
|
||||||
|
|
||||||
|
path_len = strlen(path);
|
||||||
|
|
||||||
|
for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
|
||||||
|
ext = path + (path_len - builtin_mime_types[i].ext_len);
|
||||||
|
if (path_len > builtin_mime_types[i].ext_len &&
|
||||||
|
mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) {
|
||||||
|
return builtin_mime_types[i].mime_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "text/plain";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look at the "path" extension and figure what mime type it has.
|
||||||
|
// Store mime type in the vector.
|
||||||
|
static void get_mime_type(struct mg_context *ctx, const char *path,
|
||||||
|
struct vec *vec) {
|
||||||
|
struct vec ext_vec, mime_vec;
|
||||||
|
const char *list, *ext;
|
||||||
|
size_t path_len;
|
||||||
|
|
||||||
|
path_len = strlen(path);
|
||||||
|
|
||||||
|
// Scan user-defined mime types first, in case user wants to
|
||||||
|
// override default mime types.
|
||||||
|
list = ctx->config[EXTRA_MIME_TYPES];
|
||||||
|
while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
|
||||||
|
// ext now points to the path suffix
|
||||||
|
ext = path + path_len - ext_vec.len;
|
||||||
|
if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
|
||||||
|
*vec = mime_vec;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec->ptr = mg_get_builtin_mime_type(path);
|
||||||
|
vec->len = strlen(vec->ptr);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,56 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
// This array must be in sync with enum in internal.h
|
||||||
|
static const char *config_options[] = {
|
||||||
|
"cgi_pattern", "**.cgi$|**.pl$|**.php$",
|
||||||
|
"cgi_environment", NULL,
|
||||||
|
"put_delete_auth_file", NULL,
|
||||||
|
"cgi_interpreter", NULL,
|
||||||
|
"protect_uri", NULL,
|
||||||
|
"authentication_domain", "mydomain.com",
|
||||||
|
"ssi_pattern", "**.shtml$|**.shtm$",
|
||||||
|
"access_log_file", NULL,
|
||||||
|
"enable_directory_listing", "yes",
|
||||||
|
"error_log_file", NULL,
|
||||||
|
"global_auth_file", NULL,
|
||||||
|
"index_files",
|
||||||
|
"index.html,index.htm,index.cgi,index.shtml,index.php,index.lp",
|
||||||
|
"enable_keep_alive", "no",
|
||||||
|
"access_control_list", NULL,
|
||||||
|
"extra_mime_types", NULL,
|
||||||
|
"listening_ports", "8080",
|
||||||
|
"document_root", NULL,
|
||||||
|
"ssl_certificate", NULL,
|
||||||
|
"num_threads", "50",
|
||||||
|
"run_as_user", NULL,
|
||||||
|
"url_rewrite_patterns", NULL,
|
||||||
|
"hide_files_patterns", NULL,
|
||||||
|
"request_timeout_ms", "30000",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
const char **mg_get_valid_option_names(void) {
|
||||||
|
return config_options;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_option_index(const char *name) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; config_options[i * 2] != NULL; i++) {
|
||||||
|
if (strcmp(config_options[i * 2], name) == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mg_get_option(const struct mg_context *ctx, const char *name) {
|
||||||
|
int i;
|
||||||
|
if ((i = get_option_index(name)) == -1) {
|
||||||
|
return NULL;
|
||||||
|
} else if (ctx->config[i] == NULL) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return ctx->config[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
static const char *month_names[] = {
|
||||||
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||||
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert month to the month number. Return -1 on error, or month number
|
||||||
|
static int get_month_index(const char *s) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < (int) ARRAY_SIZE(month_names); i++)
|
||||||
|
if (!strcmp(s, month_names[i]))
|
||||||
|
return i;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int num_leap_years(int year) {
|
||||||
|
return year / 4 - year / 100 + year / 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse UTC date-time string, and return the corresponding time_t value.
|
||||||
|
static time_t parse_date_string(const char *datetime) {
|
||||||
|
static const unsigned short days_before_month[] = {
|
||||||
|
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
|
||||||
|
};
|
||||||
|
char month_str[32];
|
||||||
|
int second, minute, hour, day, month, year, leap_days, days;
|
||||||
|
time_t result = (time_t) 0;
|
||||||
|
|
||||||
|
if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d",
|
||||||
|
&day, month_str, &year, &hour, &minute, &second) == 6) ||
|
||||||
|
(sscanf(datetime, "%d %3s %d %d:%d:%d",
|
||||||
|
&day, month_str, &year, &hour, &minute, &second) == 6) ||
|
||||||
|
(sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d",
|
||||||
|
&day, month_str, &year, &hour, &minute, &second) == 6) ||
|
||||||
|
(sscanf(datetime, "%d-%3s-%d %d:%d:%d",
|
||||||
|
&day, month_str, &year, &hour, &minute, &second) == 6)) &&
|
||||||
|
year > 1970 &&
|
||||||
|
(month = get_month_index(month_str)) != -1) {
|
||||||
|
leap_days = num_leap_years(year) - num_leap_years(1970);
|
||||||
|
year -= 1970;
|
||||||
|
days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
|
||||||
|
result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
// Parse HTTP headers from the given buffer, advance buffer to the point
|
||||||
|
// where parsing stopped.
|
||||||
|
static void parse_http_headers(char **buf, struct mg_request_info *ri) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {
|
||||||
|
ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0);
|
||||||
|
ri->http_headers[i].value = skip(buf, "\r\n");
|
||||||
|
if (ri->http_headers[i].name[0] == '\0')
|
||||||
|
break;
|
||||||
|
ri->num_headers = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_valid_http_method(const char *method) {
|
||||||
|
return !strcmp(method, "GET") || !strcmp(method, "POST") ||
|
||||||
|
!strcmp(method, "HEAD") || !strcmp(method, "CONNECT") ||
|
||||||
|
!strcmp(method, "PUT") || !strcmp(method, "DELETE") ||
|
||||||
|
!strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND")
|
||||||
|
|| !strcmp(method, "MKCOL");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse HTTP request, fill in mg_request_info structure.
|
||||||
|
// This function modifies the buffer by NUL-terminating
|
||||||
|
// HTTP request components, header names and header values.
|
||||||
|
static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
|
||||||
|
int is_request, request_length = get_request_len(buf, len);
|
||||||
|
if (request_length > 0) {
|
||||||
|
// Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
|
||||||
|
ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;
|
||||||
|
ri->num_headers = 0;
|
||||||
|
|
||||||
|
buf[request_length - 1] = '\0';
|
||||||
|
|
||||||
|
// RFC says that all initial whitespaces should be ingored
|
||||||
|
while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
|
||||||
|
buf++;
|
||||||
|
}
|
||||||
|
ri->request_method = skip(&buf, " ");
|
||||||
|
ri->uri = skip(&buf, " ");
|
||||||
|
ri->http_version = skip(&buf, "\r\n");
|
||||||
|
|
||||||
|
// HTTP message could be either HTTP request or HTTP response, e.g.
|
||||||
|
// "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
|
||||||
|
is_request = is_valid_http_method(ri->request_method);
|
||||||
|
if ((is_request && memcmp(ri->http_version, "HTTP/", 5) != 0) ||
|
||||||
|
(!is_request && memcmp(ri->request_method, "HTTP/", 5) != 0)) {
|
||||||
|
request_length = -1;
|
||||||
|
} else {
|
||||||
|
if (is_request) {
|
||||||
|
ri->http_version += 5;
|
||||||
|
}
|
||||||
|
parse_http_headers(&buf, ri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return request_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
|
||||||
|
return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
|
||||||
|
}
|
||||||
+178
@@ -0,0 +1,178 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#if !defined(NO_SSL)
|
||||||
|
// set_ssl_option() function updates this array.
|
||||||
|
// It loads SSL library dynamically and changes NULLs to the actual addresses
|
||||||
|
// of respective functions. The macros above (like SSL_connect()) are really
|
||||||
|
// just calling these functions indirectly via the pointer.
|
||||||
|
static struct ssl_func ssl_sw[] = {
|
||||||
|
{"SSL_free", NULL},
|
||||||
|
{"SSL_accept", NULL},
|
||||||
|
{"SSL_connect", NULL},
|
||||||
|
{"SSL_read", NULL},
|
||||||
|
{"SSL_write", NULL},
|
||||||
|
{"SSL_get_error", NULL},
|
||||||
|
{"SSL_set_fd", NULL},
|
||||||
|
{"SSL_new", NULL},
|
||||||
|
{"SSL_CTX_new", NULL},
|
||||||
|
{"SSLv23_server_method", NULL},
|
||||||
|
{"SSL_library_init", NULL},
|
||||||
|
{"SSL_CTX_use_PrivateKey_file", NULL},
|
||||||
|
{"SSL_CTX_use_certificate_file",NULL},
|
||||||
|
{"SSL_CTX_set_default_passwd_cb",NULL},
|
||||||
|
{"SSL_CTX_free", NULL},
|
||||||
|
{"SSL_load_error_strings", NULL},
|
||||||
|
{"SSL_CTX_use_certificate_chain_file", NULL},
|
||||||
|
{"SSLv23_client_method", NULL},
|
||||||
|
{"SSL_pending", NULL},
|
||||||
|
{"SSL_CTX_set_verify", NULL},
|
||||||
|
{"SSL_shutdown", NULL},
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Similar array as ssl_sw. These functions could be located in different lib.
|
||||||
|
static struct ssl_func crypto_sw[] = {
|
||||||
|
{"CRYPTO_num_locks", NULL},
|
||||||
|
{"CRYPTO_set_locking_callback", NULL},
|
||||||
|
{"CRYPTO_set_id_callback", NULL},
|
||||||
|
{"ERR_get_error", NULL},
|
||||||
|
{"ERR_error_string", NULL},
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
static pthread_mutex_t *ssl_mutexes;
|
||||||
|
|
||||||
|
static int sslize(struct mg_connection *conn, SSL_CTX *s, int (*func)(SSL *)) {
|
||||||
|
return (conn->ssl = SSL_new(s)) != NULL &&
|
||||||
|
SSL_set_fd(conn->ssl, conn->client.sock) == 1 &&
|
||||||
|
func(conn->ssl) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return OpenSSL error message
|
||||||
|
static const char *ssl_error(void) {
|
||||||
|
unsigned long err;
|
||||||
|
err = ERR_get_error();
|
||||||
|
return err == 0 ? "" : ERR_error_string(err, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ssl_locking_callback(int mode, int mutex_num, const char *file,
|
||||||
|
int line) {
|
||||||
|
(void) line;
|
||||||
|
(void) file;
|
||||||
|
|
||||||
|
if (mode & 1) { // 1 is CRYPTO_LOCK
|
||||||
|
(void) pthread_mutex_lock(&ssl_mutexes[mutex_num]);
|
||||||
|
} else {
|
||||||
|
(void) pthread_mutex_unlock(&ssl_mutexes[mutex_num]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long ssl_id_callback(void) {
|
||||||
|
return (unsigned long) pthread_self();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(NO_SSL_DL)
|
||||||
|
static int load_dll(struct mg_context *ctx, const char *dll_name,
|
||||||
|
struct ssl_func *sw) {
|
||||||
|
union {void *p; void (*fp)(void);} u;
|
||||||
|
void *dll_handle;
|
||||||
|
struct ssl_func *fp;
|
||||||
|
|
||||||
|
if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) {
|
||||||
|
cry(fc(ctx), "%s: cannot load %s", __func__, dll_name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (fp = sw; fp->name != NULL; fp++) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
// GetProcAddress() returns pointer to function
|
||||||
|
u.fp = (void (*)(void)) dlsym(dll_handle, fp->name);
|
||||||
|
#else
|
||||||
|
// dlsym() on UNIX returns void *. ISO C forbids casts of data pointers to
|
||||||
|
// function pointers. We need to use a union to make a cast.
|
||||||
|
u.p = dlsym(dll_handle, fp->name);
|
||||||
|
#endif // _WIN32
|
||||||
|
if (u.fp == NULL) {
|
||||||
|
cry(fc(ctx), "%s: %s: cannot find %s", __func__, dll_name, fp->name);
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
fp->ptr = u.fp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif // NO_SSL_DL
|
||||||
|
|
||||||
|
// Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
|
||||||
|
static int set_ssl_option(struct mg_context *ctx) {
|
||||||
|
int i, size;
|
||||||
|
const char *pem;
|
||||||
|
|
||||||
|
// If PEM file is not specified and the init_ssl callback
|
||||||
|
// is not specified, skip SSL initialization.
|
||||||
|
if ((pem = ctx->config[SSL_CERTIFICATE]) == NULL) {
|
||||||
|
// MG_INIT_SSL
|
||||||
|
// ctx->callbacks.init_ssl == NULL) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(NO_SSL_DL)
|
||||||
|
if (!load_dll(ctx, SSL_LIB, ssl_sw) ||
|
||||||
|
!load_dll(ctx, CRYPTO_LIB, crypto_sw)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif // NO_SSL_DL
|
||||||
|
|
||||||
|
// Initialize SSL library
|
||||||
|
SSL_library_init();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
|
||||||
|
if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
|
||||||
|
cry(fc(ctx), "SSL_CTX_new (server) error: %s", ssl_error());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user callback returned non-NULL, that means that user callback has
|
||||||
|
// set up certificate itself. In this case, skip sertificate setting.
|
||||||
|
// MG_INIT_SSL
|
||||||
|
if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 ||
|
||||||
|
SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0) {
|
||||||
|
cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pem != NULL) {
|
||||||
|
(void) SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, pem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize locking callbacks, needed for thread safety.
|
||||||
|
// http://www.openssl.org/support/faq.html#PROG1
|
||||||
|
size = sizeof(pthread_mutex_t) * CRYPTO_num_locks();
|
||||||
|
if ((ssl_mutexes = (pthread_mutex_t *) malloc((size_t)size)) == NULL) {
|
||||||
|
cry(fc(ctx), "%s: cannot allocate mutexes: %s", __func__, ssl_error());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < CRYPTO_num_locks(); i++) {
|
||||||
|
pthread_mutex_init(&ssl_mutexes[i], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
CRYPTO_set_locking_callback(&ssl_locking_callback);
|
||||||
|
CRYPTO_set_id_callback(&ssl_id_callback);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void uninitialize_ssl(struct mg_context *ctx) {
|
||||||
|
int i;
|
||||||
|
if (ctx->ssl_ctx != NULL) {
|
||||||
|
CRYPTO_set_locking_callback(NULL);
|
||||||
|
for (i = 0; i < CRYPTO_num_locks(); i++) {
|
||||||
|
pthread_mutex_destroy(&ssl_mutexes[i]);
|
||||||
|
}
|
||||||
|
CRYPTO_set_locking_callback(NULL);
|
||||||
|
CRYPTO_set_id_callback(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // !NO_SSL
|
||||||
@@ -0,0 +1,423 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
static void mg_strlcpy(register char *dst, register const char *src, size_t n) {
|
||||||
|
for (; *src != '\0' && n > 1; n--) {
|
||||||
|
*dst++ = *src++;
|
||||||
|
}
|
||||||
|
*dst = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lowercase(const char *s) {
|
||||||
|
return tolower(* (const unsigned char *) s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mg_strncasecmp(const char *s1, const char *s2, size_t len) {
|
||||||
|
int diff = 0;
|
||||||
|
|
||||||
|
if (len > 0)
|
||||||
|
do {
|
||||||
|
diff = lowercase(s1++) - lowercase(s2++);
|
||||||
|
} while (diff == 0 && s1[-1] != '\0' && --len > 0);
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mg_strcasecmp(const char *s1, const char *s2) {
|
||||||
|
int diff;
|
||||||
|
|
||||||
|
do {
|
||||||
|
diff = lowercase(s1++) - lowercase(s2++);
|
||||||
|
} while (diff == 0 && s1[-1] != '\0');
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char * mg_strndup(const char *ptr, size_t len) {
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
if ((p = (char *) malloc(len + 1)) != NULL) {
|
||||||
|
mg_strlcpy(p, ptr, len + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char * mg_strdup(const char *str) {
|
||||||
|
return mg_strndup(str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *mg_strcasestr(const char *big_str, const char *small_str) {
|
||||||
|
int i, big_len = strlen(big_str), small_len = strlen(small_str);
|
||||||
|
|
||||||
|
for (i = 0; i <= big_len - small_len; i++) {
|
||||||
|
if (mg_strncasecmp(big_str + i, small_str, small_len) == 0) {
|
||||||
|
return big_str + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like snprintf(), but never returns negative value, or a value
|
||||||
|
// that is larger than a supplied buffer.
|
||||||
|
// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability
|
||||||
|
// in his audit report.
|
||||||
|
static int mg_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap) {
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (buflen == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
n = vsnprintf(buf, buflen, fmt, ap);
|
||||||
|
|
||||||
|
if (n < 0) {
|
||||||
|
n = 0;
|
||||||
|
} else if (n >= (int) buflen) {
|
||||||
|
n = (int) buflen - 1;
|
||||||
|
}
|
||||||
|
buf[n] = '\0';
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mg_snprintf(char *buf, size_t buflen,
|
||||||
|
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(3, 4);
|
||||||
|
|
||||||
|
static int mg_snprintf(char *buf, size_t buflen, const char *fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
n = mg_vsnprintf(buf, buflen, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the characters until one of the delimiters characters found.
|
||||||
|
// 0-terminate resulting word. Skip the delimiter and following whitespaces.
|
||||||
|
// Advance pointer to buffer to the next word. Return found 0-terminated word.
|
||||||
|
// Delimiters can be quoted with quotechar.
|
||||||
|
static char *skip_quoted(char **buf, const char *delimiters,
|
||||||
|
const char *whitespace, char quotechar) {
|
||||||
|
char *p, *begin_word, *end_word, *end_whitespace;
|
||||||
|
|
||||||
|
begin_word = *buf;
|
||||||
|
end_word = begin_word + strcspn(begin_word, delimiters);
|
||||||
|
|
||||||
|
// Check for quotechar
|
||||||
|
if (end_word > begin_word) {
|
||||||
|
p = end_word - 1;
|
||||||
|
while (*p == quotechar) {
|
||||||
|
// If there is anything beyond end_word, copy it
|
||||||
|
if (*end_word == '\0') {
|
||||||
|
*p = '\0';
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
size_t end_off = strcspn(end_word + 1, delimiters);
|
||||||
|
memmove (p, end_word, end_off + 1);
|
||||||
|
p += end_off; // p must correspond to end_word - 1
|
||||||
|
end_word += end_off + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (p++; p < end_word; p++) {
|
||||||
|
*p = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*end_word == '\0') {
|
||||||
|
*buf = end_word;
|
||||||
|
} else {
|
||||||
|
end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace);
|
||||||
|
|
||||||
|
for (p = end_word; p < end_whitespace; p++) {
|
||||||
|
*p = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
*buf = end_whitespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return begin_word;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simplified version of skip_quoted without quote char
|
||||||
|
// and whitespace == delimiters
|
||||||
|
static char *skip(char **buf, const char *delimiters) {
|
||||||
|
return skip_quoted(buf, delimiters, delimiters, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Return HTTP header value, or NULL if not found.
|
||||||
|
static const char *get_header(const struct mg_request_info *ri,
|
||||||
|
const char *name) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < ri->num_headers; i++)
|
||||||
|
if (!mg_strcasecmp(name, ri->http_headers[i].name))
|
||||||
|
return ri->http_headers[i].value;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mg_get_header(const struct mg_connection *conn, const char *name) {
|
||||||
|
return get_header(&conn->request_info, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A helper function for traversing a comma separated list of values.
|
||||||
|
// It returns a list pointer shifted to the next value, or NULL if the end
|
||||||
|
// of the list found.
|
||||||
|
// Value is stored in val vector. If value has form "x=y", then eq_val
|
||||||
|
// vector is initialized to point to the "y" part, and val vector length
|
||||||
|
// is adjusted to point only to "x".
|
||||||
|
static const char *next_option(const char *list, struct vec *val,
|
||||||
|
struct vec *eq_val) {
|
||||||
|
if (list == NULL || *list == '\0') {
|
||||||
|
// End of the list
|
||||||
|
list = NULL;
|
||||||
|
} else {
|
||||||
|
val->ptr = list;
|
||||||
|
if ((list = strchr(val->ptr, ',')) != NULL) {
|
||||||
|
// Comma found. Store length and shift the list ptr
|
||||||
|
val->len = list - val->ptr;
|
||||||
|
list++;
|
||||||
|
} else {
|
||||||
|
// This value is the last one
|
||||||
|
list = val->ptr + strlen(val->ptr);
|
||||||
|
val->len = list - val->ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eq_val != NULL) {
|
||||||
|
// Value has form "x=y", adjust pointers and lengths
|
||||||
|
// so that val points to "x", and eq_val points to "y".
|
||||||
|
eq_val->len = 0;
|
||||||
|
eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len);
|
||||||
|
if (eq_val->ptr != NULL) {
|
||||||
|
eq_val->ptr++; // Skip over '=' character
|
||||||
|
eq_val->len = val->ptr + val->len - eq_val->ptr;
|
||||||
|
val->len = (eq_val->ptr - val->ptr) - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform case-insensitive match of string against pattern
|
||||||
|
static int match_prefix(const char *pattern, int pattern_len, const char *str) {
|
||||||
|
const char *or_str;
|
||||||
|
int i, j, len, res;
|
||||||
|
|
||||||
|
if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) {
|
||||||
|
res = match_prefix(pattern, or_str - pattern, str);
|
||||||
|
return res > 0 ? res :
|
||||||
|
match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = j = 0;
|
||||||
|
res = -1;
|
||||||
|
for (; i < pattern_len; i++, j++) {
|
||||||
|
if (pattern[i] == '?' && str[j] != '\0') {
|
||||||
|
continue;
|
||||||
|
} else if (pattern[i] == '$') {
|
||||||
|
return str[j] == '\0' ? j : -1;
|
||||||
|
} else if (pattern[i] == '*') {
|
||||||
|
i++;
|
||||||
|
if (pattern[i] == '*') {
|
||||||
|
i++;
|
||||||
|
len = (int) strlen(str + j);
|
||||||
|
} else {
|
||||||
|
len = (int) strcspn(str + j, "/");
|
||||||
|
}
|
||||||
|
if (i == pattern_len) {
|
||||||
|
return j + len;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
res = match_prefix(pattern + i, pattern_len - i, str + j + len);
|
||||||
|
} while (res == -1 && len-- > 0);
|
||||||
|
return res == -1 ? -1 : j + res + len;
|
||||||
|
} else if (lowercase(&pattern[i]) != lowercase(&str[j])) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protect against directory disclosure attack by removing '..',
|
||||||
|
// excessive '/' and '\' characters
|
||||||
|
static void remove_double_dots_and_double_slashes(char *s) {
|
||||||
|
char *p = s;
|
||||||
|
|
||||||
|
while (*s != '\0') {
|
||||||
|
*p++ = *s++;
|
||||||
|
if (s[-1] == '/' || s[-1] == '\\') {
|
||||||
|
// Skip all following slashes, backslashes and double-dots
|
||||||
|
while (s[0] != '\0') {
|
||||||
|
if (s[0] == '/' || s[0] == '\\') {
|
||||||
|
s++;
|
||||||
|
} else if (s[0] == '.' && s[1] == '.') {
|
||||||
|
s += 2;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*p = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void mg_url_encode(const char *src, char *dst, size_t dst_len) {
|
||||||
|
static const char *dont_escape = "._-$,;~()";
|
||||||
|
static const char *hex = "0123456789abcdef";
|
||||||
|
const char *end = dst + dst_len - 1;
|
||||||
|
|
||||||
|
for (; *src != '\0' && dst < end; src++, dst++) {
|
||||||
|
if (isalnum(*(const unsigned char *) src) ||
|
||||||
|
strchr(dont_escape, * (const unsigned char *) src) != NULL) {
|
||||||
|
*dst = *src;
|
||||||
|
} else if (dst + 2 < end) {
|
||||||
|
dst[0] = '%';
|
||||||
|
dst[1] = hex[(* (const unsigned char *) src) >> 4];
|
||||||
|
dst[2] = hex[(* (const unsigned char *) src) & 0xf];
|
||||||
|
dst += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*dst = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_url_decode(const char *src, int src_len, char *dst,
|
||||||
|
int dst_len, int is_form_url_encoded) {
|
||||||
|
int i, j, a, b;
|
||||||
|
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
|
||||||
|
|
||||||
|
for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
|
||||||
|
if (src[i] == '%' && i < src_len - 2 &&
|
||||||
|
isxdigit(* (const unsigned char *) (src + i + 1)) &&
|
||||||
|
isxdigit(* (const unsigned char *) (src + i + 2))) {
|
||||||
|
a = tolower(* (const unsigned char *) (src + i + 1));
|
||||||
|
b = tolower(* (const unsigned char *) (src + i + 2));
|
||||||
|
dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
|
||||||
|
i += 2;
|
||||||
|
} else if (is_form_url_encoded && src[i] == '+') {
|
||||||
|
dst[j] = ' ';
|
||||||
|
} else {
|
||||||
|
dst[j] = src[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dst[j] = '\0'; // Null-terminate the destination
|
||||||
|
|
||||||
|
return i >= src_len ? j : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether full request is buffered. Return:
|
||||||
|
// -1 if request is malformed
|
||||||
|
// 0 if request is not yet fully buffered
|
||||||
|
// >0 actual request length, including last \r\n\r\n
|
||||||
|
static int get_request_len(const char *buf, int buf_len) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < buf_len; i++) {
|
||||||
|
// Control characters are not allowed but >=128 is.
|
||||||
|
// Abort scan as soon as one malformed character is found;
|
||||||
|
// don't let subsequent \r\n\r\n win us over anyhow
|
||||||
|
if (!isprint(* (const unsigned char *) &buf[i]) && buf[i] != '\r' &&
|
||||||
|
buf[i] != '\n' && * (const unsigned char *) &buf[i] < 128) {
|
||||||
|
return -1;
|
||||||
|
} else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
|
||||||
|
return i + 2;
|
||||||
|
} else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
|
||||||
|
buf[i + 2] == '\n') {
|
||||||
|
return i + 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_get_cookie(const char *cookie_header, const char *var_name,
|
||||||
|
char *dst, size_t dst_size) {
|
||||||
|
const char *s, *p, *end;
|
||||||
|
int name_len, len = -1;
|
||||||
|
|
||||||
|
if (dst == NULL || dst_size == 0) {
|
||||||
|
len = -2;
|
||||||
|
} else if (var_name == NULL || (s = cookie_header) == NULL) {
|
||||||
|
len = -1;
|
||||||
|
dst[0] = '\0';
|
||||||
|
} else {
|
||||||
|
name_len = (int) strlen(var_name);
|
||||||
|
end = s + strlen(s);
|
||||||
|
dst[0] = '\0';
|
||||||
|
|
||||||
|
for (; (s = mg_strcasestr(s, var_name)) != NULL; s += name_len) {
|
||||||
|
if (s[name_len] == '=') {
|
||||||
|
s += name_len + 1;
|
||||||
|
if ((p = strchr(s, ' ')) == NULL)
|
||||||
|
p = end;
|
||||||
|
if (p[-1] == ';')
|
||||||
|
p--;
|
||||||
|
if (*s == '"' && p[-1] == '"' && p > s + 1) {
|
||||||
|
s++;
|
||||||
|
p--;
|
||||||
|
}
|
||||||
|
if ((size_t) (p - s) < dst_size) {
|
||||||
|
len = p - s;
|
||||||
|
mg_strlcpy(dst, s, (size_t) len + 1);
|
||||||
|
} else {
|
||||||
|
len = -3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_get_var(const char *data, size_t data_len, const char *name,
|
||||||
|
char *dst, size_t dst_len) {
|
||||||
|
const char *p, *e, *s;
|
||||||
|
size_t name_len;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if (dst == NULL || dst_len == 0) {
|
||||||
|
len = -2;
|
||||||
|
} else if (data == NULL || name == NULL || data_len == 0) {
|
||||||
|
len = -1;
|
||||||
|
dst[0] = '\0';
|
||||||
|
} else {
|
||||||
|
name_len = strlen(name);
|
||||||
|
e = data + data_len;
|
||||||
|
len = -1;
|
||||||
|
dst[0] = '\0';
|
||||||
|
|
||||||
|
// data is "var1=val1&var2=val2...". Find variable first
|
||||||
|
for (p = data; p + name_len < e; p++) {
|
||||||
|
if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
|
||||||
|
!mg_strncasecmp(name, p, name_len)) {
|
||||||
|
|
||||||
|
// Point p to variable value
|
||||||
|
p += name_len + 1;
|
||||||
|
|
||||||
|
// Point s to the end of the value
|
||||||
|
s = (const char *) memchr(p, '&', (size_t)(e - p));
|
||||||
|
if (s == NULL) {
|
||||||
|
s = e;
|
||||||
|
}
|
||||||
|
assert(s >= p);
|
||||||
|
|
||||||
|
// Decode variable into destination buffer
|
||||||
|
len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
|
||||||
|
|
||||||
|
// Redirect error code from -1 to -2 (destination buffer too small).
|
||||||
|
if (len == -1) {
|
||||||
|
len = -2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#if !defined(_WIN32)
|
||||||
|
static int mg_stat(const char *path, struct file *filep) {
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
filep->modification_time = (time_t) 0;
|
||||||
|
if (stat(path, &st) == 0) {
|
||||||
|
filep->size = st.st_size;
|
||||||
|
filep->modification_time = st.st_mtime;
|
||||||
|
filep->is_directory = S_ISDIR(st.st_mode);
|
||||||
|
|
||||||
|
// See https://github.com/cesanta/mongoose/issues/109
|
||||||
|
// Some filesystems report modification time as 0. Artificially
|
||||||
|
// bump it up to mark mg_stat() success.
|
||||||
|
if (filep->modification_time == (time_t) 0) {
|
||||||
|
filep->modification_time = (time_t) 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filep->modification_time != (time_t) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_close_on_exec(int fd) {
|
||||||
|
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_start_thread(mg_thread_func_t func, void *param) {
|
||||||
|
pthread_t thread_id;
|
||||||
|
pthread_attr_t attr;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
(void) pthread_attr_init(&attr);
|
||||||
|
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||||
|
|
||||||
|
#if USE_STACK_SIZE > 1
|
||||||
|
// Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
|
||||||
|
(void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
result = pthread_create(&thread_id, &attr, func, param);
|
||||||
|
pthread_attr_destroy(&attr);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NO_CGI
|
||||||
|
static pid_t spawn_process(struct mg_connection *conn, const char *prog,
|
||||||
|
char *envblk, char *envp[], int fdin,
|
||||||
|
int fdout, const char *dir) {
|
||||||
|
pid_t pid;
|
||||||
|
const char *interp;
|
||||||
|
|
||||||
|
(void) envblk;
|
||||||
|
|
||||||
|
if ((pid = fork()) == -1) {
|
||||||
|
// Parent
|
||||||
|
send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));
|
||||||
|
} else if (pid == 0) {
|
||||||
|
// Child
|
||||||
|
if (chdir(dir) != 0) {
|
||||||
|
cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));
|
||||||
|
} else if (dup2(fdin, 0) == -1) {
|
||||||
|
cry(conn, "%s: dup2(%d, 0): %s", __func__, fdin, strerror(ERRNO));
|
||||||
|
} else if (dup2(fdout, 1) == -1) {
|
||||||
|
cry(conn, "%s: dup2(%d, 1): %s", __func__, fdout, strerror(ERRNO));
|
||||||
|
} else {
|
||||||
|
// Not redirecting stderr to stdout, to avoid output being littered
|
||||||
|
// with the error messages.
|
||||||
|
(void) close(fdin);
|
||||||
|
(void) close(fdout);
|
||||||
|
|
||||||
|
// After exec, all signal handlers are restored to their default values,
|
||||||
|
// with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
|
||||||
|
// implementation, SIGCHLD's handler will leave unchanged after exec
|
||||||
|
// if it was set to be ignored. Restore it to default action.
|
||||||
|
signal(SIGCHLD, SIG_DFL);
|
||||||
|
|
||||||
|
interp = conn->ctx->config[CGI_INTERPRETER];
|
||||||
|
if (interp == NULL) {
|
||||||
|
(void) execle(prog, prog, NULL, envp);
|
||||||
|
cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));
|
||||||
|
} else {
|
||||||
|
(void) execle(interp, interp, prog, NULL, envp);
|
||||||
|
cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,
|
||||||
|
strerror(ERRNO));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
#endif // !NO_CGI
|
||||||
|
|
||||||
|
static int set_non_blocking_mode(SOCKET sock) {
|
||||||
|
int flags;
|
||||||
|
|
||||||
|
flags = fcntl(sock, F_GETFL, 0);
|
||||||
|
(void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
|
||||||
|
char *path, int path_len) {
|
||||||
|
const char *content_type_header, *boundary_start;
|
||||||
|
char *buf, fname[1024], boundary[100], *s, *p;
|
||||||
|
int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0;
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
|
// Request looks like this:
|
||||||
|
//
|
||||||
|
// POST /upload HTTP/1.1
|
||||||
|
// Host: 127.0.0.1:8080
|
||||||
|
// Content-Length: 244894
|
||||||
|
// Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr
|
||||||
|
//
|
||||||
|
// ------WebKitFormBoundaryRVr
|
||||||
|
// Content-Disposition: form-data; name="file"; filename="accum.png"
|
||||||
|
// Content-Type: image/png
|
||||||
|
//
|
||||||
|
// <89>PNG
|
||||||
|
// <PNG DATA>
|
||||||
|
// ------WebKitFormBoundaryRVr
|
||||||
|
|
||||||
|
// Extract boundary string from the Content-Type header
|
||||||
|
if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL ||
|
||||||
|
(boundary_start = mg_strcasestr(content_type_header,
|
||||||
|
"boundary=")) == NULL ||
|
||||||
|
(sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 &&
|
||||||
|
sscanf(boundary_start, "boundary=%99s", boundary) == 0) ||
|
||||||
|
boundary[0] == '\0') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
boundary_len = strlen(boundary);
|
||||||
|
bl = boundary_len + 4; // \r\n--<boundary>
|
||||||
|
|
||||||
|
// buf
|
||||||
|
// conn->buf |<--------- buf_len ------>|
|
||||||
|
// |=================|==========|===============|
|
||||||
|
// |<--request_len-->|<--len--->| |
|
||||||
|
// |<-----------data_len------->| conn->buf + conn->buf_size
|
||||||
|
|
||||||
|
buf = conn->buf + conn->request_len;
|
||||||
|
buf_len = conn->buf_size - conn->request_len;
|
||||||
|
len = conn->data_len - conn->request_len;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
// Pull in headers
|
||||||
|
assert(len >= 0 && len <= buf_len);
|
||||||
|
to_read = buf_len - len;
|
||||||
|
if (to_read > left_to_read(conn)) {
|
||||||
|
to_read = (int) left_to_read(conn);
|
||||||
|
}
|
||||||
|
while (len < buf_len &&
|
||||||
|
(n = pull(NULL, conn, buf + len, to_read)) > 0) {
|
||||||
|
len += n;
|
||||||
|
}
|
||||||
|
if ((headers_len = get_request_len(buf, len)) <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch file name.
|
||||||
|
fname[0] = '\0';
|
||||||
|
for (i = j = 0; i < headers_len; i++) {
|
||||||
|
if (buf[i] == '\r' && buf[i + 1] == '\n') {
|
||||||
|
buf[i] = buf[i + 1] = '\0';
|
||||||
|
// TODO(lsm): don't expect filename to be the 3rd field,
|
||||||
|
// parse the header properly instead.
|
||||||
|
sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]",
|
||||||
|
fname);
|
||||||
|
j = i + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give up if the headers are not what we expect
|
||||||
|
if (fname[0] == '\0') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move data to the beginning of the buffer
|
||||||
|
assert(len >= headers_len);
|
||||||
|
memmove(buf, &buf[headers_len], len - headers_len);
|
||||||
|
len -= headers_len;
|
||||||
|
conn->data_len = conn->request_len + len;
|
||||||
|
|
||||||
|
// We open the file with exclusive lock held. This guarantee us
|
||||||
|
// there is no other thread can save into the same file simultaneously.
|
||||||
|
fp = NULL;
|
||||||
|
|
||||||
|
// Construct destination file name. Do not allow paths to have slashes.
|
||||||
|
s = fname;
|
||||||
|
if ((p = strrchr(fname, '/')) != NULL && p > s) s = p;
|
||||||
|
if ((p = strrchr(fname, '\\')) != NULL && p > s) s = p;
|
||||||
|
|
||||||
|
// Open file in binary mode. TODO: set an exclusive lock.
|
||||||
|
snprintf(path, path_len, "%s/%s", destination_dir, s);
|
||||||
|
if ((fp = fopen(path, "wb")) == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read POST data, write into file until boundary is found.
|
||||||
|
eof = n = 0;
|
||||||
|
do {
|
||||||
|
len += n;
|
||||||
|
for (i = 0; i < len - bl; i++) {
|
||||||
|
if (!memcmp(&buf[i], "\r\n--", 4) &&
|
||||||
|
!memcmp(&buf[i + 4], boundary, boundary_len)) {
|
||||||
|
// Found boundary, that's the end of file data.
|
||||||
|
fwrite(buf, 1, i, fp);
|
||||||
|
eof = 1;
|
||||||
|
memmove(buf, &buf[i + bl], len - (i + bl));
|
||||||
|
len -= i + bl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!eof && len > bl) {
|
||||||
|
fwrite(buf, 1, len - bl, fp);
|
||||||
|
memmove(buf, &buf[len - bl], bl);
|
||||||
|
len = bl;
|
||||||
|
}
|
||||||
|
to_read = buf_len - len;
|
||||||
|
if (to_read > left_to_read(conn)) {
|
||||||
|
to_read = (int) left_to_read(conn);
|
||||||
|
}
|
||||||
|
} while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0);
|
||||||
|
conn->data_len = conn->request_len + len;
|
||||||
|
|
||||||
|
if (eof) {
|
||||||
|
rewind(fp);
|
||||||
|
return fp;
|
||||||
|
} else {
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
// Return fake connection structure. Used for logging, if connection
|
||||||
|
// is not applicable at the moment of logging.
|
||||||
|
static struct mg_connection *fc(struct mg_context *ctx) {
|
||||||
|
static struct mg_connection fake_connection;
|
||||||
|
fake_connection.ctx = ctx;
|
||||||
|
// See https://github.com/cesanta/mongoose/issues/236
|
||||||
|
fake_connection.event.user_data = ctx->user_data;
|
||||||
|
return &fake_connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sockaddr_to_string(char *buf, size_t len,
|
||||||
|
const union usa *usa) {
|
||||||
|
buf[0] = '\0';
|
||||||
|
#if defined(USE_IPV6)
|
||||||
|
inet_ntop(usa->sa.sa_family, usa->sa.sa_family == AF_INET ?
|
||||||
|
(void *) &usa->sin.sin_addr :
|
||||||
|
(void *) &usa->sin6.sin6_addr, buf, len);
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
// Only Windoze Vista (and newer) have inet_ntop()
|
||||||
|
strncpy(buf, inet_ntoa(usa->sin.sin_addr), len);
|
||||||
|
#else
|
||||||
|
inet_ntop(usa->sa.sa_family, (void *) &usa->sin.sin_addr, buf, len);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
|
||||||
|
strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
// Writes PROPFIND properties for a collection element
|
||||||
|
static void print_props(struct mg_connection *conn, const char* uri,
|
||||||
|
struct file *filep) {
|
||||||
|
char mtime[64];
|
||||||
|
gmt_time_string(mtime, sizeof(mtime), &filep->modification_time);
|
||||||
|
conn->num_bytes_sent += mg_printf(conn,
|
||||||
|
"<d:response>"
|
||||||
|
"<d:href>%s</d:href>"
|
||||||
|
"<d:propstat>"
|
||||||
|
"<d:prop>"
|
||||||
|
"<d:resourcetype>%s</d:resourcetype>"
|
||||||
|
"<d:getcontentlength>%" INT64_FMT "</d:getcontentlength>"
|
||||||
|
"<d:getlastmodified>%s</d:getlastmodified>"
|
||||||
|
"</d:prop>"
|
||||||
|
"<d:status>HTTP/1.1 200 OK</d:status>"
|
||||||
|
"</d:propstat>"
|
||||||
|
"</d:response>\n",
|
||||||
|
uri,
|
||||||
|
filep->is_directory ? "<d:collection/>" : "",
|
||||||
|
filep->size,
|
||||||
|
mtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_dav_dir_entry(struct de *de, void *data) {
|
||||||
|
char href[PATH_MAX];
|
||||||
|
char href_encoded[PATH_MAX];
|
||||||
|
struct mg_connection *conn = (struct mg_connection *) data;
|
||||||
|
mg_snprintf(href, sizeof(href), "%s%s",
|
||||||
|
conn->request_info.uri, de->file_name);
|
||||||
|
mg_url_encode(href, href_encoded, PATH_MAX-1);
|
||||||
|
print_props(conn, href_encoded, &de->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_propfind(struct mg_connection *conn, const char *path,
|
||||||
|
struct file *filep) {
|
||||||
|
const char *depth = mg_get_header(conn, "Depth");
|
||||||
|
|
||||||
|
conn->must_close = 1;
|
||||||
|
conn->status_code = 207;
|
||||||
|
mg_printf(conn, "HTTP/1.1 207 Multi-Status\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"Content-Type: text/xml; charset=utf-8\r\n\r\n");
|
||||||
|
|
||||||
|
conn->num_bytes_sent += mg_printf(conn,
|
||||||
|
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||||
|
"<d:multistatus xmlns:d='DAV:'>\n");
|
||||||
|
|
||||||
|
// Print properties for the requested resource itself
|
||||||
|
print_props(conn, conn->request_info.uri, filep);
|
||||||
|
|
||||||
|
// If it is a directory, print directory entries too if Depth is not 0
|
||||||
|
if (filep->is_directory &&
|
||||||
|
!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes") &&
|
||||||
|
(depth == NULL || strcmp(depth, "0") != 0)) {
|
||||||
|
scan_directory(conn, path, conn, &print_dav_dir_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->num_bytes_sent += mg_printf(conn, "%s\n", "</d:multistatus>");
|
||||||
|
}
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#if defined(USE_WEBSOCKET)
|
||||||
|
|
||||||
|
// START OF SHA-1 code
|
||||||
|
// Copyright(c) By Steve Reid <steve@edmweb.com>
|
||||||
|
#define SHA1HANDSOFF
|
||||||
|
#if defined(__sun)
|
||||||
|
#include "solarisfixes.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
union char64long16 { unsigned char c[64]; uint32_t l[16]; };
|
||||||
|
|
||||||
|
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
|
||||||
|
|
||||||
|
static uint32_t blk0(union char64long16 *block, int i) {
|
||||||
|
// Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN
|
||||||
|
if (!is_big_endian()) {
|
||||||
|
block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
|
||||||
|
(rol(block->l[i], 8) & 0x00FF00FF);
|
||||||
|
}
|
||||||
|
return block->l[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
|
||||||
|
^block->l[(i+2)&15]^block->l[i&15],1))
|
||||||
|
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(block, i)+0x5A827999+rol(v,5);w=rol(w,30);
|
||||||
|
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
|
||||||
|
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
|
||||||
|
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
|
||||||
|
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t state[5];
|
||||||
|
uint32_t count[2];
|
||||||
|
unsigned char buffer[64];
|
||||||
|
} SHA1_CTX;
|
||||||
|
|
||||||
|
static void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) {
|
||||||
|
uint32_t a, b, c, d, e;
|
||||||
|
union char64long16 block[1];
|
||||||
|
|
||||||
|
memcpy(block, buffer, 64);
|
||||||
|
a = state[0];
|
||||||
|
b = state[1];
|
||||||
|
c = state[2];
|
||||||
|
d = state[3];
|
||||||
|
e = state[4];
|
||||||
|
R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
|
||||||
|
R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
|
||||||
|
R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
|
||||||
|
R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
|
||||||
|
R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
|
||||||
|
R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
|
||||||
|
R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
|
||||||
|
R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
|
||||||
|
R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
|
||||||
|
R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
|
||||||
|
R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
|
||||||
|
R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
|
||||||
|
R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
|
||||||
|
R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
|
||||||
|
R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
|
||||||
|
R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
|
||||||
|
R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
|
||||||
|
R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
|
||||||
|
R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
|
||||||
|
R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
|
||||||
|
state[0] += a;
|
||||||
|
state[1] += b;
|
||||||
|
state[2] += c;
|
||||||
|
state[3] += d;
|
||||||
|
state[4] += e;
|
||||||
|
a = b = c = d = e = 0;
|
||||||
|
memset(block, '\0', sizeof(block));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SHA1Init(SHA1_CTX* context) {
|
||||||
|
context->state[0] = 0x67452301;
|
||||||
|
context->state[1] = 0xEFCDAB89;
|
||||||
|
context->state[2] = 0x98BADCFE;
|
||||||
|
context->state[3] = 0x10325476;
|
||||||
|
context->state[4] = 0xC3D2E1F0;
|
||||||
|
context->count[0] = context->count[1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SHA1Update(SHA1_CTX* context, const unsigned char* data,
|
||||||
|
uint32_t len) {
|
||||||
|
uint32_t i, j;
|
||||||
|
|
||||||
|
j = context->count[0];
|
||||||
|
if ((context->count[0] += len << 3) < j)
|
||||||
|
context->count[1]++;
|
||||||
|
context->count[1] += (len>>29);
|
||||||
|
j = (j >> 3) & 63;
|
||||||
|
if ((j + len) > 63) {
|
||||||
|
memcpy(&context->buffer[j], data, (i = 64-j));
|
||||||
|
SHA1Transform(context->state, context->buffer);
|
||||||
|
for ( ; i + 63 < len; i += 64) {
|
||||||
|
SHA1Transform(context->state, &data[i]);
|
||||||
|
}
|
||||||
|
j = 0;
|
||||||
|
}
|
||||||
|
else i = 0;
|
||||||
|
memcpy(&context->buffer[j], &data[i], len - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SHA1Final(unsigned char digest[20], SHA1_CTX* context) {
|
||||||
|
unsigned i;
|
||||||
|
unsigned char finalcount[8], c;
|
||||||
|
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
|
||||||
|
>> ((3-(i & 3)) * 8) ) & 255);
|
||||||
|
}
|
||||||
|
c = 0200;
|
||||||
|
SHA1Update(context, &c, 1);
|
||||||
|
while ((context->count[0] & 504) != 448) {
|
||||||
|
c = 0000;
|
||||||
|
SHA1Update(context, &c, 1);
|
||||||
|
}
|
||||||
|
SHA1Update(context, finalcount, 8);
|
||||||
|
for (i = 0; i < 20; i++) {
|
||||||
|
digest[i] = (unsigned char)
|
||||||
|
((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
|
||||||
|
}
|
||||||
|
memset(context, '\0', sizeof(*context));
|
||||||
|
memset(&finalcount, '\0', sizeof(finalcount));
|
||||||
|
}
|
||||||
|
// END OF SHA1 CODE
|
||||||
|
|
||||||
|
static void base64_encode(const unsigned char *src, int src_len, char *dst) {
|
||||||
|
static const char *b64 =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
int i, j, a, b, c;
|
||||||
|
|
||||||
|
for (i = j = 0; i < src_len; i += 3) {
|
||||||
|
a = src[i];
|
||||||
|
b = i + 1 >= src_len ? 0 : src[i + 1];
|
||||||
|
c = i + 2 >= src_len ? 0 : src[i + 2];
|
||||||
|
|
||||||
|
dst[j++] = b64[a >> 2];
|
||||||
|
dst[j++] = b64[((a & 3) << 4) | (b >> 4)];
|
||||||
|
if (i + 1 < src_len) {
|
||||||
|
dst[j++] = b64[(b & 15) << 2 | (c >> 6)];
|
||||||
|
}
|
||||||
|
if (i + 2 < src_len) {
|
||||||
|
dst[j++] = b64[c & 63];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (j % 4 != 0) {
|
||||||
|
dst[j++] = '=';
|
||||||
|
}
|
||||||
|
dst[j++] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void mg_websocket_handshake(struct mg_connection *conn) {
|
||||||
|
static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
char buf[100], sha[20], b64_sha[sizeof(sha) * 2];
|
||||||
|
SHA1_CTX sha_ctx;
|
||||||
|
|
||||||
|
mg_snprintf(buf, sizeof(buf), "%s%s",
|
||||||
|
mg_get_header(conn, "Sec-WebSocket-Key"), magic);
|
||||||
|
SHA1Init(&sha_ctx);
|
||||||
|
SHA1Update(&sha_ctx, (unsigned char *) buf, strlen(buf));
|
||||||
|
SHA1Final((unsigned char *) sha, &sha_ctx);
|
||||||
|
base64_encode((unsigned char *) sha, sizeof(sha), b64_sha);
|
||||||
|
mg_printf(conn, "%s%s%s",
|
||||||
|
"HTTP/1.1 101 Switching Protocols\r\n"
|
||||||
|
"Upgrade: websocket\r\n"
|
||||||
|
"Connection: Upgrade\r\n"
|
||||||
|
"Sec-WebSocket-Accept: ", b64_sha, "\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_websocket_read(struct mg_connection *conn, int *bits, char **data) {
|
||||||
|
// Pointer to the beginning of the portion of the incoming websocket message
|
||||||
|
// queue. The original websocket upgrade request is never removed,
|
||||||
|
// so the queue begins after it.
|
||||||
|
unsigned char *buf = (unsigned char *) conn->buf + conn->request_len;
|
||||||
|
int n, stop = 0;
|
||||||
|
size_t i, len, mask_len, data_len, header_len, body_len;
|
||||||
|
char mask[4];
|
||||||
|
|
||||||
|
assert(conn->content_len == 0);
|
||||||
|
|
||||||
|
// Loop continuously, reading messages from the socket, invoking the callback,
|
||||||
|
// and waiting repeatedly until an error occurs.
|
||||||
|
while (!stop) {
|
||||||
|
header_len = 0;
|
||||||
|
// body_len is the length of the entire queue in bytes
|
||||||
|
// len is the length of the current message
|
||||||
|
// data_len is the length of the current message's data payload
|
||||||
|
// header_len is the length of the current message's header
|
||||||
|
if ((body_len = conn->data_len - conn->request_len) >= 2) {
|
||||||
|
len = buf[1] & 127;
|
||||||
|
mask_len = buf[1] & 128 ? 4 : 0;
|
||||||
|
if (len < 126 && body_len >= mask_len) {
|
||||||
|
data_len = len;
|
||||||
|
header_len = 2 + mask_len;
|
||||||
|
} else if (len == 126 && body_len >= 4 + mask_len) {
|
||||||
|
header_len = 4 + mask_len;
|
||||||
|
data_len = ((((int) buf[2]) << 8) + buf[3]);
|
||||||
|
} else if (body_len >= 10 + mask_len) {
|
||||||
|
header_len = 10 + mask_len;
|
||||||
|
data_len = (((uint64_t) htonl(* (uint32_t *) &buf[2])) << 32) +
|
||||||
|
htonl(* (uint32_t *) &buf[6]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data layout is as follows:
|
||||||
|
// conn->buf buf
|
||||||
|
// v v frame1 | frame2
|
||||||
|
// |---------------------|----------------|--------------|-------
|
||||||
|
// | |<--header_len-->|<--data_len-->|
|
||||||
|
// |<-conn->request_len->|<-----body_len----------->|
|
||||||
|
// |<-------------------conn->data_len------------->|
|
||||||
|
|
||||||
|
if (header_len > 0) {
|
||||||
|
// Allocate space to hold websocket payload
|
||||||
|
if ((*data = malloc(data_len)) == NULL) {
|
||||||
|
// Allocation failed, exit the loop and then close the connection
|
||||||
|
// TODO: notify user about the failure
|
||||||
|
data_len = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save mask and bits, otherwise it may be clobbered by memmove below
|
||||||
|
*bits = buf[0];
|
||||||
|
memcpy(mask, buf + header_len - mask_len, mask_len);
|
||||||
|
|
||||||
|
// Read frame payload into the allocated buffer.
|
||||||
|
assert(body_len >= header_len);
|
||||||
|
if (data_len + header_len > body_len) {
|
||||||
|
len = body_len - header_len;
|
||||||
|
memcpy(*data, buf + header_len, len);
|
||||||
|
// TODO: handle pull error
|
||||||
|
pull_all(NULL, conn, *data + len, data_len - len);
|
||||||
|
conn->data_len = conn->request_len;
|
||||||
|
} else {
|
||||||
|
len = data_len + header_len;
|
||||||
|
memcpy(*data, buf + header_len, data_len);
|
||||||
|
memmove(buf, buf + len, body_len - len);
|
||||||
|
conn->data_len -= len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply mask if necessary
|
||||||
|
if (mask_len > 0) {
|
||||||
|
for (i = 0; i < data_len; i++) {
|
||||||
|
(*data)[i] ^= mask[i % 4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data_len;
|
||||||
|
} else {
|
||||||
|
// Buffering websocket request
|
||||||
|
if ((n = pull(NULL, conn, conn->buf + conn->data_len,
|
||||||
|
conn->buf_size - conn->data_len)) <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
conn->data_len += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_websocket_write(struct mg_connection* conn, int opcode,
|
||||||
|
const char *data, size_t data_len) {
|
||||||
|
unsigned char *copy;
|
||||||
|
size_t copy_len = 0;
|
||||||
|
int retval = -1;
|
||||||
|
|
||||||
|
if ((copy = (unsigned char *) malloc(data_len + 10)) == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy[0] = 0x80 + (opcode & 0x0f);
|
||||||
|
|
||||||
|
// Frame format: http://tools.ietf.org/html/rfc6455#section-5.2
|
||||||
|
if (data_len < 126) {
|
||||||
|
// Inline 7-bit length field
|
||||||
|
copy[1] = data_len;
|
||||||
|
memcpy(copy + 2, data, data_len);
|
||||||
|
copy_len = 2 + data_len;
|
||||||
|
} else if (data_len <= 0xFFFF) {
|
||||||
|
// 16-bit length field
|
||||||
|
copy[1] = 126;
|
||||||
|
* (uint16_t *) (copy + 2) = htons(data_len);
|
||||||
|
memcpy(copy + 4, data, data_len);
|
||||||
|
copy_len = 4 + data_len;
|
||||||
|
} else {
|
||||||
|
// 64-bit length field
|
||||||
|
copy[1] = 127;
|
||||||
|
* (uint32_t *) (copy + 2) = htonl((uint64_t) data_len >> 32);
|
||||||
|
* (uint32_t *) (copy + 6) = htonl(data_len & 0xffffffff);
|
||||||
|
memcpy(copy + 10, data, data_len);
|
||||||
|
copy_len = 10 + data_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not thread safe
|
||||||
|
if (copy_len > 0) {
|
||||||
|
retval = mg_write(conn, copy, copy_len);
|
||||||
|
}
|
||||||
|
free(copy);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
#endif // !USE_WEBSOCKET
|
||||||
|
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
static pthread_t pthread_self(void) {
|
||||||
|
return GetCurrentThreadId();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) {
|
||||||
|
(void) unused;
|
||||||
|
*mutex = CreateMutex(NULL, FALSE, NULL);
|
||||||
|
return *mutex == NULL ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pthread_mutex_destroy(pthread_mutex_t *mutex) {
|
||||||
|
return CloseHandle(*mutex) == 0 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pthread_mutex_lock(pthread_mutex_t *mutex) {
|
||||||
|
return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pthread_mutex_unlock(pthread_mutex_t *mutex) {
|
||||||
|
return ReleaseMutex(*mutex) == 0 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Windows, change all slashes to backslashes in path names.
|
||||||
|
static void change_slashes_to_backslashes(char *path) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; path[i] != '\0'; i++) {
|
||||||
|
if (path[i] == '/')
|
||||||
|
path[i] = '\\';
|
||||||
|
// i > 0 check is to preserve UNC paths, like \\server\file.txt
|
||||||
|
if (path[i] == '\\' && i > 0)
|
||||||
|
while (path[i + 1] == '\\' || path[i + 1] == '/')
|
||||||
|
(void) memmove(path + i + 1,
|
||||||
|
path + i + 2, strlen(path + i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
|
||||||
|
// wbuf and wbuf_len is a target buffer and its length.
|
||||||
|
static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {
|
||||||
|
char buf[PATH_MAX * 2], buf2[PATH_MAX * 2];
|
||||||
|
|
||||||
|
mg_strlcpy(buf, path, sizeof(buf));
|
||||||
|
change_slashes_to_backslashes(buf);
|
||||||
|
|
||||||
|
// Convert to Unicode and back. If doubly-converted string does not
|
||||||
|
// match the original, something is fishy, reject.
|
||||||
|
memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
|
||||||
|
NULL, NULL);
|
||||||
|
if (strcmp(buf, buf2) != 0) {
|
||||||
|
wbuf[0] = L'\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_WIN32_WCE)
|
||||||
|
static time_t time(time_t *ptime) {
|
||||||
|
time_t t;
|
||||||
|
SYSTEMTIME st;
|
||||||
|
FILETIME ft;
|
||||||
|
|
||||||
|
GetSystemTime(&st);
|
||||||
|
SystemTimeToFileTime(&st, &ft);
|
||||||
|
t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);
|
||||||
|
|
||||||
|
if (ptime != NULL) {
|
||||||
|
*ptime = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct tm *localtime(const time_t *ptime, struct tm *ptm) {
|
||||||
|
int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF;
|
||||||
|
FILETIME ft, lft;
|
||||||
|
SYSTEMTIME st;
|
||||||
|
TIME_ZONE_INFORMATION tzinfo;
|
||||||
|
|
||||||
|
if (ptm == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
* (int64_t *) &ft = t;
|
||||||
|
FileTimeToLocalFileTime(&ft, &lft);
|
||||||
|
FileTimeToSystemTime(&lft, &st);
|
||||||
|
ptm->tm_year = st.wYear - 1900;
|
||||||
|
ptm->tm_mon = st.wMonth - 1;
|
||||||
|
ptm->tm_wday = st.wDayOfWeek;
|
||||||
|
ptm->tm_mday = st.wDay;
|
||||||
|
ptm->tm_hour = st.wHour;
|
||||||
|
ptm->tm_min = st.wMinute;
|
||||||
|
ptm->tm_sec = st.wSecond;
|
||||||
|
ptm->tm_yday = 0; // hope nobody uses this
|
||||||
|
ptm->tm_isdst =
|
||||||
|
GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0;
|
||||||
|
|
||||||
|
return ptm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct tm *gmtime(const time_t *ptime, struct tm *ptm) {
|
||||||
|
// FIXME(lsm): fix this.
|
||||||
|
return localtime(ptime, ptm);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t strftime(char *dst, size_t dst_size, const char *fmt,
|
||||||
|
const struct tm *tm) {
|
||||||
|
(void) snprintf(dst, dst_size, "implement strftime() for WinCE");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Windows happily opens files with some garbage at the end of file name.
|
||||||
|
// For example, fopen("a.cgi ", "r") on Windows successfully opens
|
||||||
|
// "a.cgi", despite one would expect an error back.
|
||||||
|
// This function returns non-0 if path ends with some garbage.
|
||||||
|
static int path_cannot_disclose_cgi(const char *path) {
|
||||||
|
static const char *allowed_last_characters = "_-";
|
||||||
|
int last = path[strlen(path) - 1];
|
||||||
|
return isalnum(last) || strchr(allowed_last_characters, last) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mg_stat(const char *path, struct file *filep) {
|
||||||
|
wchar_t wbuf[PATH_MAX] = L"\\\\?\\";
|
||||||
|
WIN32_FILE_ATTRIBUTE_DATA info;
|
||||||
|
|
||||||
|
filep->modification_time = 0;
|
||||||
|
to_unicode(path, wbuf + 4, ARRAY_SIZE(wbuf) - 4);
|
||||||
|
if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
|
||||||
|
filep->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
|
||||||
|
filep->modification_time = SYS2UNIX_TIME(
|
||||||
|
info.ftLastWriteTime.dwLowDateTime,
|
||||||
|
info.ftLastWriteTime.dwHighDateTime);
|
||||||
|
filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
|
||||||
|
// If file name is fishy, reset the file structure and return error.
|
||||||
|
// Note it is important to reset, not just return the error, cause
|
||||||
|
// functions like is_file_opened() check the struct.
|
||||||
|
if (!filep->is_directory && !path_cannot_disclose_cgi(path)) {
|
||||||
|
memset(filep, 0, sizeof(*filep));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filep->modification_time != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mg_remove(const char *path) {
|
||||||
|
wchar_t wbuf[PATH_MAX];
|
||||||
|
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
|
||||||
|
return DeleteFileW(wbuf) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mg_mkdir(const char *path, int mode) {
|
||||||
|
char buf[PATH_MAX];
|
||||||
|
wchar_t wbuf[PATH_MAX];
|
||||||
|
|
||||||
|
(void) mode;
|
||||||
|
mg_strlcpy(buf, path, sizeof(buf));
|
||||||
|
change_slashes_to_backslashes(buf);
|
||||||
|
|
||||||
|
(void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, ARRAY_SIZE(wbuf));
|
||||||
|
|
||||||
|
return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of POSIX opendir/closedir/readdir for Windows.
|
||||||
|
static DIR * opendir(const char *name) {
|
||||||
|
DIR *dir = NULL;
|
||||||
|
wchar_t wpath[PATH_MAX];
|
||||||
|
DWORD attrs;
|
||||||
|
|
||||||
|
if (name == NULL) {
|
||||||
|
SetLastError(ERROR_BAD_ARGUMENTS);
|
||||||
|
} else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
|
||||||
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||||||
|
} else {
|
||||||
|
to_unicode(name, wpath, ARRAY_SIZE(wpath));
|
||||||
|
attrs = GetFileAttributesW(wpath);
|
||||||
|
if (attrs != 0xFFFFFFFF &&
|
||||||
|
((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
|
||||||
|
(void) wcscat(wpath, L"\\*");
|
||||||
|
dir->handle = FindFirstFileW(wpath, &dir->info);
|
||||||
|
dir->result.d_name[0] = '\0';
|
||||||
|
} else {
|
||||||
|
free(dir);
|
||||||
|
dir = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int closedir(DIR *dir) {
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
if (dir != NULL) {
|
||||||
|
if (dir->handle != INVALID_HANDLE_VALUE)
|
||||||
|
result = FindClose(dir->handle) ? 0 : -1;
|
||||||
|
|
||||||
|
free(dir);
|
||||||
|
} else {
|
||||||
|
result = -1;
|
||||||
|
SetLastError(ERROR_BAD_ARGUMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct dirent *readdir(DIR *dir) {
|
||||||
|
struct dirent *result = 0;
|
||||||
|
|
||||||
|
if (dir) {
|
||||||
|
if (dir->handle != INVALID_HANDLE_VALUE) {
|
||||||
|
result = &dir->result;
|
||||||
|
(void) WideCharToMultiByte(CP_UTF8, 0,
|
||||||
|
dir->info.cFileName, -1, result->d_name,
|
||||||
|
sizeof(result->d_name), NULL, NULL);
|
||||||
|
|
||||||
|
if (!FindNextFileW(dir->handle, &dir->info)) {
|
||||||
|
(void) FindClose(dir->handle);
|
||||||
|
dir->handle = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SetLastError(ERROR_BAD_ARGUMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_POLL
|
||||||
|
static int poll(struct pollfd *pfd, int n, int milliseconds) {
|
||||||
|
struct timeval tv;
|
||||||
|
fd_set set;
|
||||||
|
int i, result;
|
||||||
|
SOCKET maxfd = 0;
|
||||||
|
|
||||||
|
tv.tv_sec = milliseconds / 1000;
|
||||||
|
tv.tv_usec = (milliseconds % 1000) * 1000;
|
||||||
|
FD_ZERO(&set);
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
FD_SET((SOCKET) pfd[i].fd, &set);
|
||||||
|
pfd[i].revents = 0;
|
||||||
|
|
||||||
|
if (pfd[i].fd > maxfd) {
|
||||||
|
maxfd = pfd[i].fd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((result = select(maxfd + 1, &set, NULL, NULL, &tv)) > 0) {
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
if (FD_ISSET(pfd[i].fd, &set)) {
|
||||||
|
pfd[i].revents = POLLIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif // HAVE_POLL
|
||||||
|
|
||||||
|
static void set_close_on_exec(SOCKET sock) {
|
||||||
|
(void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int mg_start_thread(mg_thread_func_t f, void *p) {
|
||||||
|
return (long)_beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HANDLE dlopen(const char *dll_name, int flags) {
|
||||||
|
wchar_t wbuf[PATH_MAX];
|
||||||
|
(void) flags;
|
||||||
|
to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
|
||||||
|
return LoadLibraryW(wbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(NO_CGI)
|
||||||
|
#define SIGKILL 0
|
||||||
|
static int kill(pid_t pid, int sig_num) {
|
||||||
|
(void) TerminateProcess(pid, sig_num);
|
||||||
|
(void) CloseHandle(pid);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trim_trailing_whitespaces(char *s) {
|
||||||
|
char *e = s + strlen(s) - 1;
|
||||||
|
while (e > s && isspace(* (unsigned char *) e)) {
|
||||||
|
*e-- = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static pid_t spawn_process(struct mg_connection *conn, const char *prog,
|
||||||
|
char *envblk, char *envp[], int fdin,
|
||||||
|
int fdout, const char *dir) {
|
||||||
|
HANDLE me;
|
||||||
|
char *interp, full_interp[PATH_MAX], full_dir[PATH_MAX],
|
||||||
|
cmdline[PATH_MAX], buf[PATH_MAX];
|
||||||
|
FILE *fp;
|
||||||
|
STARTUPINFOA si;
|
||||||
|
PROCESS_INFORMATION pi = { 0 };
|
||||||
|
|
||||||
|
(void) envp;
|
||||||
|
|
||||||
|
memset(&si, 0, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
|
||||||
|
// TODO(lsm): redirect CGI errors to the error log file
|
||||||
|
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
||||||
|
si.wShowWindow = SW_HIDE;
|
||||||
|
|
||||||
|
me = GetCurrentProcess();
|
||||||
|
DuplicateHandle(me, (HANDLE) _get_osfhandle(fdin), me,
|
||||||
|
&si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
||||||
|
DuplicateHandle(me, (HANDLE) _get_osfhandle(fdout), me,
|
||||||
|
&si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
||||||
|
|
||||||
|
// If CGI file is a script, try to read the interpreter line
|
||||||
|
interp = conn->ctx->config[CGI_INTERPRETER];
|
||||||
|
if (interp == NULL) {
|
||||||
|
buf[0] = buf[1] = '\0';
|
||||||
|
|
||||||
|
// Read the first line of the script into the buffer
|
||||||
|
snprintf(cmdline, sizeof(cmdline), "%s%c%s", dir, '/', prog);
|
||||||
|
if ((fp = mg_fopen(cmdline, "r")) != NULL) {
|
||||||
|
fgets(buf, sizeof(buf), fp);
|
||||||
|
fclose(fp);
|
||||||
|
buf[sizeof(buf) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf[0] == '#' && buf[1] == '!') {
|
||||||
|
trim_trailing_whitespaces(buf + 2);
|
||||||
|
} else {
|
||||||
|
buf[2] = '\0';
|
||||||
|
}
|
||||||
|
interp = buf + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interp[0] != '\0') {
|
||||||
|
GetFullPathNameA(interp, sizeof(full_interp), full_interp, NULL);
|
||||||
|
interp = full_interp;
|
||||||
|
}
|
||||||
|
GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL);
|
||||||
|
|
||||||
|
mg_snprintf(cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"",
|
||||||
|
interp, interp[0] == '\0' ? "" : " ", full_dir, prog);
|
||||||
|
|
||||||
|
DEBUG_TRACE(("Running [%s]", cmdline));
|
||||||
|
if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
|
||||||
|
CREATE_NEW_PROCESS_GROUP, envblk, NULL, &si, &pi) == 0) {
|
||||||
|
cry(conn, "%s: CreateProcess(%s): %ld",
|
||||||
|
__func__, cmdline, ERRNO);
|
||||||
|
pi.hProcess = (pid_t) -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) CloseHandle(si.hStdOutput);
|
||||||
|
(void) CloseHandle(si.hStdInput);
|
||||||
|
(void) CloseHandle(pi.hThread);
|
||||||
|
|
||||||
|
return (pid_t) pi.hProcess;
|
||||||
|
}
|
||||||
|
#endif // !NO_CGI
|
||||||
|
|
||||||
|
static int set_non_blocking_mode(SOCKET sock) {
|
||||||
|
unsigned long on = 1;
|
||||||
|
return ioctlsocket(sock, FIONBIO, &on);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 17 KiB |
File renamed without changes.
File renamed without changes.
File renamed without changes.
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
print "Status: 302 Please pass me to the client\r\n\r\n";
|
||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
print "Content-Type: text/plain\n\nhello\n";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
куку!
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,43 @@
|
|||||||
|
mg.write('HTTP/1.0 200 OK\r\n', 'Content-Type: text/plain\r\n', '\r\n')
|
||||||
|
mg.write(os.date("%A"))
|
||||||
|
|
||||||
|
-- for k,v in pairs(_G) do mg.write(k, '\n') end
|
||||||
|
|
||||||
|
-- Open database
|
||||||
|
local db = sqlite3.open('requests.db')
|
||||||
|
|
||||||
|
-- Setup a trace callback, to show SQL statements we'll be executing.
|
||||||
|
-- db:trace(function(data, sql) mg.write('Executing: ', sql: '\n') end, nil)
|
||||||
|
|
||||||
|
-- Create a table if it is not created already
|
||||||
|
db:exec([[
|
||||||
|
CREATE TABLE IF NOT EXISTS requests (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
timestamp NOT NULL,
|
||||||
|
method NOT NULL,
|
||||||
|
uri NOT NULL,
|
||||||
|
addr
|
||||||
|
);
|
||||||
|
]])
|
||||||
|
|
||||||
|
|
||||||
|
-- Add entry about this request
|
||||||
|
local stmt = db:prepare(
|
||||||
|
'INSERT INTO requests VALUES(NULL, datetime("now"), ?, ?, ?);');
|
||||||
|
stmt:bind_values(mg.request_info.request_method,
|
||||||
|
mg.request_info.uri,
|
||||||
|
mg.request_info.remote_port)
|
||||||
|
stmt:step()
|
||||||
|
stmt:finalize()
|
||||||
|
|
||||||
|
-- Show all previous records
|
||||||
|
mg.write('Previous requests:\n')
|
||||||
|
stmt = db:prepare('SELECT * FROM requests ORDER BY id DESC;')
|
||||||
|
while stmt:step() == sqlite3.ROW do
|
||||||
|
local v = stmt:get_values()
|
||||||
|
mg.write(v[1] .. ' ' .. v[2] .. ' ' .. v[3] .. ' '
|
||||||
|
.. v[4] .. ' ' .. v[5] .. '\n')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Close database
|
||||||
|
db:close()
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
HTTP/1.0 200 OK
|
||||||
|
Content-Type: text/html
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<p>This is an example Lua server page served by
|
||||||
|
<a href="http://code.google.com/p/mongoose">Mongoose web server</a>.
|
||||||
|
Mongoose has Lua, Sqlite, and other functionality built in the binary.
|
||||||
|
This example page stores the request in the Sqlite database, and shows
|
||||||
|
all requests done previously.</p>
|
||||||
|
<p> Today is <? mg.write(os.date("%A")) ?>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<?
|
||||||
|
-- for k,v in pairs(_G) do mg.write(k, '\n') end
|
||||||
|
|
||||||
|
-- Open database
|
||||||
|
local db = sqlite3.open('requests.db')
|
||||||
|
|
||||||
|
-- Setup a trace callback, to show SQL statements we'll be executing.
|
||||||
|
-- db:trace(function(data, sql) mg.write('Executing: ', sql: '\n') end, nil)
|
||||||
|
|
||||||
|
-- Create a table if it is not created already
|
||||||
|
db:exec('PRAGMA encoding="UTF-8"; ')
|
||||||
|
db:exec([[
|
||||||
|
CREATE TABLE IF NOT EXISTS requests (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
timestamp NOT NULL,
|
||||||
|
method NOT NULL,
|
||||||
|
uri NOT NULL,
|
||||||
|
addr
|
||||||
|
);
|
||||||
|
]])
|
||||||
|
|
||||||
|
|
||||||
|
-- Add entry about this request
|
||||||
|
local stmt = db:prepare(
|
||||||
|
'INSERT INTO requests VALUES(NULL, datetime("now"), ?, ?, ?);');
|
||||||
|
stmt:bind_values(mg.request_info.request_method,
|
||||||
|
mg.request_info.uri,
|
||||||
|
mg.request_info.remote_port)
|
||||||
|
stmt:step()
|
||||||
|
stmt:finalize()
|
||||||
|
|
||||||
|
-- Show all previous records
|
||||||
|
mg.write('Previous requests:\n')
|
||||||
|
stmt = db:prepare('SELECT * FROM requests ORDER BY id DESC;')
|
||||||
|
while stmt:step() == sqlite3.ROW do
|
||||||
|
local v = stmt:get_values()
|
||||||
|
mg.write(v[1] .. ' ' .. v[2] .. ' ' .. v[3] .. ' '
|
||||||
|
.. v[4] .. ' ' .. v[5] .. '\n')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Close database
|
||||||
|
db:close()
|
||||||
|
?>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
This is an example on how to include files a-la SSI:
|
||||||
|
<pre><? mg.write(io.open('dll.def'):read('*all')) ?></pre>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
File renamed without changes.
File renamed without changes.
@@ -1,5 +1,5 @@
|
|||||||
<html><pre>
|
<html><pre>
|
||||||
ssi_begin
|
ssi_begin
|
||||||
<!--#include file="Makefile" -->
|
<!--#include file="../Makefile" -->
|
||||||
ssi_end
|
ssi_end
|
||||||
</pre></html>
|
</pre></html>
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,5 +1,5 @@
|
|||||||
<html><pre>
|
<html><pre>
|
||||||
ssi_begin
|
ssi_begin
|
||||||
<!--#include file="/etc/passwd" -->
|
<!--#include abspath="/etc/passwd" -->
|
||||||
ssi_end
|
ssi_end
|
||||||
</pre></html>
|
</pre></html>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<html><pre>
|
<html><pre>
|
||||||
ssi_begin
|
ssi_begin
|
||||||
<!--#include file="c:\boot.ini" -->
|
<!--#include abspath="c:\boot.ini" -->
|
||||||
ssi_end
|
ssi_end
|
||||||
</pre></html>
|
</pre></html>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
<html><pre>
|
<html><pre>
|
||||||
ssi_begin
|
ssi_begin
|
||||||
<!--#include "embed.c" -->
|
<!--#include "unit_test.c" -->
|
||||||
ssi_end
|
ssi_end
|
||||||
</pre></html>
|
</pre></html>
|
||||||
File renamed without changes.
@@ -0,0 +1,3 @@
|
|||||||
|
ssi_begin
|
||||||
|
<!--#include file="../Makefile" -->
|
||||||
|
ssi_end
|
||||||
@@ -4,24 +4,29 @@
|
|||||||
|
|
||||||
use IO::Socket;
|
use IO::Socket;
|
||||||
use File::Path;
|
use File::Path;
|
||||||
|
use File::Basename;
|
||||||
|
use Cwd;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
#use diagnostics;
|
#use diagnostics;
|
||||||
|
|
||||||
sub on_windows { $^O =~ /win32/i; }
|
sub on_windows { $^O =~ /win32/i; }
|
||||||
|
|
||||||
my $port = 23456;
|
my $port = 31295;
|
||||||
my $pid = undef;
|
my $pid = undef;
|
||||||
my $num_requests;
|
my $num_requests;
|
||||||
my $dir_separator = on_windows() ? '\\' : '/';
|
my $dir_separator = on_windows() ? '\\' : '/';
|
||||||
my $copy_cmd = on_windows() ? 'copy' : 'cp';
|
my $copy_cmd = on_windows() ? 'copy' : 'cp';
|
||||||
my $test_dir_uri = "test_dir";
|
my $test_dir_uri = "test_dir";
|
||||||
my $root = 'test';
|
my $root = './test';
|
||||||
my $test_dir = $root . $dir_separator. $test_dir_uri;
|
my $abs_root = Cwd::abs_path(dirname($0) . $dir_separator);
|
||||||
|
my $test_dir = $abs_root . $dir_separator. $test_dir_uri;
|
||||||
|
#print "$test_dir\n"; exit 0;
|
||||||
my $config = 'mongoose.conf';
|
my $config = 'mongoose.conf';
|
||||||
my $exe = '.' . $dir_separator . 'mongoose';
|
my $exe_ext = on_windows() ? '.exe' : '';
|
||||||
my $embed_exe = '.' . $dir_separator . 'embed';
|
my $mongoose_exe = '.' . $dir_separator . 'mongoose' . $exe_ext;
|
||||||
my $unit_test_exe = '.' . $dir_separator . 'unit_test';
|
my $embed_exe = '.' . $dir_separator . 'embed' . $exe_ext;
|
||||||
|
my $unit_test_exe = '.' . $dir_separator . 'unit_test' . $exe_ext;
|
||||||
my $exit_code = 0;
|
my $exit_code = 0;
|
||||||
|
|
||||||
my @files_to_delete = ('debug.log', 'access.log', $config, "$root/a/put.txt",
|
my @files_to_delete = ('debug.log', 'access.log', $config, "$root/a/put.txt",
|
||||||
@@ -29,6 +34,7 @@ my @files_to_delete = ('debug.log', 'access.log', $config, "$root/a/put.txt",
|
|||||||
"$root/myperl", $embed_exe, $unit_test_exe);
|
"$root/myperl", $embed_exe, $unit_test_exe);
|
||||||
|
|
||||||
END {
|
END {
|
||||||
|
#system('cat access.log');
|
||||||
unlink @files_to_delete;
|
unlink @files_to_delete;
|
||||||
kill_spawned_child();
|
kill_spawned_child();
|
||||||
File::Path::rmtree($test_dir);
|
File::Path::rmtree($test_dir);
|
||||||
@@ -51,9 +57,9 @@ sub get_num_of_log_entries {
|
|||||||
# Send the request to the 127.0.0.1:$port and return the reply
|
# Send the request to the 127.0.0.1:$port and return the reply
|
||||||
sub req {
|
sub req {
|
||||||
my ($request, $inc, $timeout) = @_;
|
my ($request, $inc, $timeout) = @_;
|
||||||
my $sock = IO::Socket::INET->new(Proto=>"tcp",
|
my $sock = IO::Socket::INET->new(Proto => 6,
|
||||||
PeerAddr=>'127.0.0.1', PeerPort=>$port);
|
PeerAddr => '127.0.0.1', PeerPort => $port);
|
||||||
fail("Cannot connect: $!") unless $sock;
|
fail("Cannot connect to http://127.0.0.1:$port : $!") unless $sock;
|
||||||
$sock->autoflush(1);
|
$sock->autoflush(1);
|
||||||
foreach my $byte (split //, $request) {
|
foreach my $byte (split //, $request) {
|
||||||
last unless print $sock $byte;
|
last unless print $sock $byte;
|
||||||
@@ -97,7 +103,6 @@ sub spawn {
|
|||||||
if (on_windows()) {
|
if (on_windows()) {
|
||||||
my @args = split /\s+/, $cmdline;
|
my @args = split /\s+/, $cmdline;
|
||||||
my $executable = $args[0];
|
my $executable = $args[0];
|
||||||
$executable .= '.exe';
|
|
||||||
Win32::Spawn($executable, $cmdline, $pid);
|
Win32::Spawn($executable, $cmdline, $pid);
|
||||||
die "Cannot spawn @_: $!" unless $pid;
|
die "Cannot spawn @_: $!" unless $pid;
|
||||||
} else {
|
} else {
|
||||||
@@ -146,44 +151,39 @@ if ($^O =~ /darwin|bsd|linux/) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scalar(@ARGV) > 0 and $ARGV[0] eq 'embedded') {
|
|
||||||
do_embedded_test();
|
|
||||||
exit 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scalar(@ARGV) > 0 and $ARGV[0] eq 'unit') {
|
|
||||||
do_unit_test();
|
|
||||||
exit 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Make sure we load config file if no options are given.
|
# Make sure we load config file if no options are given.
|
||||||
# Command line options override config files settings
|
# Command line options override config files settings
|
||||||
write_file($config, "access_log_file access.log\nlistening_ports 12345\n");
|
write_file($config, "access_log_file access.log\n" .
|
||||||
spawn("$exe -p $port");
|
"document_root $root\n" .
|
||||||
o("GET /test/hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
|
"listening_port 127.0.0.1:23164\n");
|
||||||
|
spawn("$mongoose_exe -listening_port 127.0.0.1:$port");
|
||||||
|
o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
|
||||||
unlink $config;
|
unlink $config;
|
||||||
kill_spawned_child();
|
kill_spawned_child();
|
||||||
|
|
||||||
|
# "-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
|
||||||
|
# "-enable_keep_alive yes ".
|
||||||
|
|
||||||
# Spawn the server on port $port
|
# Spawn the server on port $port
|
||||||
my $cmd = "$exe ".
|
my $cmd = "$mongoose_exe ".
|
||||||
"-listening_ports $port ".
|
"-listening_port 127.0.0.1:$port ".
|
||||||
"-access_log_file access.log ".
|
"-access_log_file access.log ".
|
||||||
"-error_log_file debug.log ".
|
"-error_log_file debug.log ".
|
||||||
"-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
|
|
||||||
"-extra_mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
|
"-extra_mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
|
||||||
'-put_delete_passwords_file test/passfile ' .
|
"-dav_auth_file $abs_root/passfile " .
|
||||||
'-access_control_list -0.0.0.0/0,+127.0.0.1 ' .
|
'-access_control_list -0.0.0.0/0,+127.0.0.1 ' .
|
||||||
"-document_root $root ".
|
"-document_root $root ".
|
||||||
"-hide_files_patterns **exploit.pl ".
|
"-hide_files_patterns **exploit.PL ".
|
||||||
"-enable_keep_alive yes ".
|
"-url_rewrites /aiased=/etc/,/ta=$test_dir";
|
||||||
"-url_rewrite_patterns /aiased=/etc/,/ta=$test_dir";
|
|
||||||
$cmd .= ' -cgi_interpreter perl' if on_windows();
|
$cmd .= ' -cgi_interpreter perl' if on_windows();
|
||||||
spawn($cmd);
|
spawn($cmd);
|
||||||
|
|
||||||
|
o("GET /dir%20with%20spaces/桌面/ HTTP/1.0\r\n\r\n", 'куку!',
|
||||||
|
'Non-ascii chars in path');
|
||||||
o("GET /hello.txt HTTP/1.1\nConnection: close\nRange: bytes=3-50\r\n\r\n",
|
o("GET /hello.txt HTTP/1.1\nConnection: close\nRange: bytes=3-50\r\n\r\n",
|
||||||
'Content-Length: 15\s', 'Range past the file end');
|
'Content-Length: 15\s', 'Range past the file end');
|
||||||
|
o("GET /hello.txt HTTP/1.1\nContent-Length: 0\n\n ".
|
||||||
o("GET /hello.txt HTTP/1.1\n\n GET /hello.txt HTTP/1.0\n\n",
|
"GET /hello.txt HTTP/1.0\nContent-Length: 0\n\n",
|
||||||
'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close',
|
'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close',
|
||||||
'Request pipelining', 2);
|
'Request pipelining', 2);
|
||||||
|
|
||||||
@@ -203,6 +203,10 @@ o("GET /hello.txt HTTP/1.0\n\n", 'Content-Length: 17\s',
|
|||||||
o("GET /%68%65%6c%6c%6f%2e%74%78%74 HTTP/1.0\n\n",
|
o("GET /%68%65%6c%6c%6f%2e%74%78%74 HTTP/1.0\n\n",
|
||||||
'HTTP/1.1 200 OK', 'URL-decoding');
|
'HTTP/1.1 200 OK', 'URL-decoding');
|
||||||
|
|
||||||
|
# '+' in URI must not be URL-decoded to space
|
||||||
|
write_file("$root/a+.txt", ':-)');
|
||||||
|
o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');
|
||||||
|
|
||||||
# Break CGI reading after 1 second. We must get full output.
|
# Break CGI reading after 1 second. We must get full output.
|
||||||
# Since CGI script does sleep, we sleep as well and increase request count
|
# Since CGI script does sleep, we sleep as well and increase request count
|
||||||
# manually.
|
# manually.
|
||||||
@@ -211,19 +215,14 @@ print "==> Slow CGI output ... ";
|
|||||||
fail('Slow CGI output forward reply=', $slow_cgi_reply) unless
|
fail('Slow CGI output forward reply=', $slow_cgi_reply) unless
|
||||||
($slow_cgi_reply = req("GET /timeout.cgi HTTP/1.0\r\n\r\n", 0, 1)) =~ /Some data/s;
|
($slow_cgi_reply = req("GET /timeout.cgi HTTP/1.0\r\n\r\n", 0, 1)) =~ /Some data/s;
|
||||||
print "OK\n";
|
print "OK\n";
|
||||||
sleep 3;
|
sleep 2;
|
||||||
$num_requests++;
|
#$num_requests++;
|
||||||
|
|
||||||
# '+' in URI must not be URL-decoded to space
|
|
||||||
write_file("$root/a+.txt", '');
|
|
||||||
o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');
|
|
||||||
|
|
||||||
# Test HTTP version parsing
|
# Test HTTP version parsing
|
||||||
o("GET / HTTPX/1.0\r\n\r\n", '400 Bad Request', 'Bad HTTP Version', 0);
|
o("GET / HTTPX/1.0\r\n\r\n", '^HTTP/1.1 400', 'Bad HTTP Version', 0);
|
||||||
o("GET / HTTP/x.1\r\n\r\n", '505 HTTP', 'Bad HTTP maj Version');
|
o("GET / HTTP/x.1\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP maj Version', 1);
|
||||||
o("GET / HTTP/1.1z\r\n\r\n", '505 HTTP', 'Bad HTTP min Version');
|
o("GET / HTTP/1.1z\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP min Version', 1);
|
||||||
o("GET / HTTP/02.0\r\n\r\n", '505 HTTP version not supported',
|
o("GET / HTTP/02.0\r\n\r\n", '^HTTP/1.1 505', 'HTTP Version >1.1', 1);
|
||||||
'HTTP Version >1.1');
|
|
||||||
|
|
||||||
# File with leading single dot
|
# File with leading single dot
|
||||||
o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
|
o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
|
||||||
@@ -248,8 +247,9 @@ write_file($path, read_file($root . $dir_separator . 'env.cgi'));
|
|||||||
chmod(0755, $path);
|
chmod(0755, $path);
|
||||||
o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", "Content-Type: text/html\r\n\r\n",
|
o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", "Content-Type: text/html\r\n\r\n",
|
||||||
'index.cgi execution');
|
'index.cgi execution');
|
||||||
|
|
||||||
o("GET /$test_dir_uri/x/ HTTP/1.0\n\n",
|
o("GET /$test_dir_uri/x/ HTTP/1.0\n\n",
|
||||||
"SCRIPT_FILENAME=test/test_dir/x/index.cgi", 'SCRIPT_FILENAME');
|
"SCRIPT_FILENAME=$test_dir/x/index.cgi", 'SCRIPT_FILENAME');
|
||||||
o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi",
|
o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi",
|
||||||
'Aliases SCRIPT_NAME');
|
'Aliases SCRIPT_NAME');
|
||||||
o("GET /hello.txt HTTP/1.1\nConnection: close\n\n", 'Connection: close',
|
o("GET /hello.txt HTTP/1.1\nConnection: close\n\n", 'Connection: close',
|
||||||
@@ -345,6 +345,9 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
|
|||||||
my $auth_header = "Digest username=\"user with space, \\\" and comma\", ".
|
my $auth_header = "Digest username=\"user with space, \\\" and comma\", ".
|
||||||
"realm=\"mydomain.com\", nonce=\"1291376417\", uri=\"/\",".
|
"realm=\"mydomain.com\", nonce=\"1291376417\", uri=\"/\",".
|
||||||
"response=\"e8dec0c2a1a0c8a7e9a97b4b5ea6a6e6\", qop=auth, nc=00000001, cnonce=\"1a49b53a47a66e82\"";
|
"response=\"e8dec0c2a1a0c8a7e9a97b4b5ea6a6e6\", qop=auth, nc=00000001, cnonce=\"1a49b53a47a66e82\"";
|
||||||
|
# TODO(lsm): re-enable auth checks
|
||||||
|
unlink "$root/.htpasswd";
|
||||||
|
|
||||||
o("GET /hello.txt HTTP/1.0\nAuthorization: $auth_header\n\n", 'HTTP/1.1 200 OK', 'GET regular file with auth');
|
o("GET /hello.txt HTTP/1.0\nAuthorization: $auth_header\n\n", 'HTTP/1.1 200 OK', 'GET regular file with auth');
|
||||||
o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(.htpasswd)))*$',
|
o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(.htpasswd)))*$',
|
||||||
'.htpasswd is hidden from the directory list');
|
'.htpasswd is hidden from the directory list');
|
||||||
@@ -354,11 +357,12 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
|
|||||||
'^HTTP/1.1 404 ', '.htpasswd must not be shown');
|
'^HTTP/1.1 404 ', '.htpasswd must not be shown');
|
||||||
o("GET /exploit.pl HTTP/1.0\nAuthorization: $auth_header\n\n",
|
o("GET /exploit.pl HTTP/1.0\nAuthorization: $auth_header\n\n",
|
||||||
'^HTTP/1.1 404', 'hidden files must not be shown');
|
'^HTTP/1.1 404', 'hidden files must not be shown');
|
||||||
unlink "$root/.htpasswd";
|
|
||||||
|
|
||||||
|
|
||||||
|
o("GET /dir%20with%20spaces/hello.cgi HTTP/1.0\n\r\n",
|
||||||
|
'HTTP/1.1 200 OK.+hello', 'CGI script with spaces in path');
|
||||||
o("GET /env.cgi HTTP/1.0\n\r\n", 'HTTP/1.1 200 OK', 'GET CGI file');
|
o("GET /env.cgi HTTP/1.0\n\r\n", 'HTTP/1.1 200 OK', 'GET CGI file');
|
||||||
o("GET /bad2.cgi HTTP/1.0\n\n", "HTTP/1.1 123 Please pass me to the client\r",
|
o("GET /bad2.cgi HTTP/1.0\n\n", "HTTP/1.1 302 Please pass me to the client\r",
|
||||||
'CGI Status code text');
|
'CGI Status code text');
|
||||||
o("GET /sh.cgi HTTP/1.0\n\r\n", 'shell script CGI',
|
o("GET /sh.cgi HTTP/1.0\n\r\n", 'shell script CGI',
|
||||||
'GET sh CGI file') unless on_windows();
|
'GET sh CGI file') unless on_windows();
|
||||||
@@ -382,11 +386,10 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
|
|||||||
o("GET /env.cgi%2b HTTP/1.0\n\r\n",
|
o("GET /env.cgi%2b HTTP/1.0\n\r\n",
|
||||||
'HTTP/1.1 404', 'CGI Win32 code disclosure (%2b)');
|
'HTTP/1.1 404', 'CGI Win32 code disclosure (%2b)');
|
||||||
o("GET /env.cgi HTTP/1.0\n\r\n", '\nHTTPS=off\n', 'CGI HTTPS');
|
o("GET /env.cgi HTTP/1.0\n\r\n", '\nHTTPS=off\n', 'CGI HTTPS');
|
||||||
o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_FOO=foo\n', '-cgi_env 1');
|
|
||||||
o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAR=bar\n', '-cgi_env 2');
|
|
||||||
o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAZ=baz\n', '-cgi_env 3');
|
|
||||||
o("GET /env.cgi/a/b/98 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/98\n', 'PATH_INFO');
|
o("GET /env.cgi/a/b/98 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/98\n', 'PATH_INFO');
|
||||||
o("GET /env.cgi/a/b/9 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/9\n', 'PATH_INFO');
|
o("GET /env.cgi/a/b/9 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/9\n', 'PATH_INFO');
|
||||||
|
o("GET /env.cgi/foo/bar?a=b HTTP/1.0\n\n",
|
||||||
|
'SCRIPT_NAME=/env.cgi\s', 'SCRIPT_NAME for CGI with PATH_INFO');
|
||||||
|
|
||||||
# Check that CGI's current directory is set to script's directory
|
# Check that CGI's current directory is set to script's directory
|
||||||
my $copy_cmd = on_windows() ? 'copy' : 'cp';
|
my $copy_cmd = on_windows() ? 'copy' : 'cp';
|
||||||
@@ -395,29 +398,12 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
|
|||||||
o("GET /$test_dir_uri/env.cgi HTTP/1.0\n\n",
|
o("GET /$test_dir_uri/env.cgi HTTP/1.0\n\n",
|
||||||
"CURRENT_DIR=.*$root/$test_dir_uri", "CGI chdir()");
|
"CURRENT_DIR=.*$root/$test_dir_uri", "CGI chdir()");
|
||||||
|
|
||||||
# SSI tests
|
|
||||||
o("GET /ssi1.shtml HTTP/1.0\n\n",
|
|
||||||
'ssi_begin.+CFLAGS.+ssi_end', 'SSI #include file=');
|
|
||||||
o("GET /ssi2.shtml HTTP/1.0\n\n",
|
|
||||||
'ssi_begin.+Unit test.+ssi_end', 'SSI #include virtual=');
|
|
||||||
my $ssi_exec = on_windows() ? 'ssi4.shtml' : 'ssi3.shtml';
|
|
||||||
o("GET /$ssi_exec HTTP/1.0\n\n",
|
|
||||||
'ssi_begin.+Makefile.+ssi_end', 'SSI #exec');
|
|
||||||
my $abs_path = on_windows() ? 'ssi6.shtml' : 'ssi5.shtml';
|
|
||||||
my $word = on_windows() ? 'boot loader' : 'root';
|
|
||||||
o("GET /$abs_path HTTP/1.0\n\n",
|
|
||||||
"ssi_begin.+$word.+ssi_end", 'SSI #include file= (absolute)');
|
|
||||||
o("GET /ssi7.shtml HTTP/1.0\n\n",
|
|
||||||
'ssi_begin.+Unit test.+ssi_end', 'SSI #include "..."');
|
|
||||||
o("GET /ssi8.shtml HTTP/1.0\n\n",
|
|
||||||
'ssi_begin.+CFLAGS.+ssi_end', 'SSI nested #includes');
|
|
||||||
|
|
||||||
# Manipulate the passwords file
|
# Manipulate the passwords file
|
||||||
my $path = 'test_htpasswd';
|
my $path = 'test_htpasswd';
|
||||||
unlink $path;
|
unlink $path;
|
||||||
system("$exe -A $path a b c") == 0
|
system("$mongoose_exe -A $path a b c") == 0
|
||||||
or fail("Cannot add user in a passwd file");
|
or fail("Cannot add user in a passwd file");
|
||||||
system("$exe -A $path a b c2") == 0
|
system("$mongoose_exe -A $path a b c2") == 0
|
||||||
or fail("Cannot edit user in a passwd file");
|
or fail("Cannot edit user in a passwd file");
|
||||||
my $content = read_file($path);
|
my $content = read_file($path);
|
||||||
$content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file");
|
$content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file");
|
||||||
@@ -425,8 +411,6 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
|
|||||||
|
|
||||||
do_PUT_test();
|
do_PUT_test();
|
||||||
kill_spawned_child();
|
kill_spawned_child();
|
||||||
do_unit_test();
|
|
||||||
do_embedded_test();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub do_PUT_test {
|
sub do_PUT_test {
|
||||||
@@ -447,88 +431,9 @@ sub do_PUT_test {
|
|||||||
unless read_file("$root/a/put.txt") eq 'abcd';
|
unless read_file("$root/a/put.txt") eq 'abcd';
|
||||||
o("PUT /a/put.txt HTTP/1.0\n$auth_header\nabcd",
|
o("PUT /a/put.txt HTTP/1.0\n$auth_header\nabcd",
|
||||||
"HTTP/1.1 411 Length Required", 'PUT 411 error');
|
"HTTP/1.1 411 Length Required", 'PUT 411 error');
|
||||||
o("PUT /a/put.txt HTTP/1.0\nExpect: blah\nContent-Length: 1\n".
|
|
||||||
"$auth_header\nabcd",
|
|
||||||
"HTTP/1.1 417 Expectation Failed", 'PUT 417 error');
|
|
||||||
o("PUT /a/put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n".
|
o("PUT /a/put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n".
|
||||||
"$auth_header\nabcd",
|
"$auth_header\nabcd",
|
||||||
"HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue');
|
"HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue');
|
||||||
}
|
}
|
||||||
|
|
||||||
sub do_unit_test {
|
|
||||||
my $cmd = "cc -g -W -Wall -o $unit_test_exe $root/unit_test.c -I. ".
|
|
||||||
"-pthread -DNO_SSL ";
|
|
||||||
if (on_windows()) {
|
|
||||||
$cmd = "cl $root/embed.c mongoose.c /I. /nologo /DNO_SSL ".
|
|
||||||
"/DLISTENING_PORT=\\\"$port\\\" /link /out:$embed_exe.exe ws2_32.lib ";
|
|
||||||
}
|
|
||||||
print $cmd, "\n";
|
|
||||||
system($cmd) == 0 or fail("Cannot compile unit test");
|
|
||||||
system($unit_test_exe) == 0 or fail("Unit test failed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
sub do_embedded_test {
|
|
||||||
my $cmd = "cc -W -Wall -o $embed_exe $root/embed.c mongoose.c -I. ".
|
|
||||||
"-pthread -DNO_SSL -DLISTENING_PORT=\\\"$port\\\"";
|
|
||||||
if (on_windows()) {
|
|
||||||
$cmd = "cl $root/embed.c mongoose.c /I. /nologo /DNO_SSL ".
|
|
||||||
"/DLISTENING_PORT=\\\"$port\\\" /link /out:$embed_exe.exe ws2_32.lib ";
|
|
||||||
}
|
|
||||||
print $cmd, "\n";
|
|
||||||
system($cmd) == 0 or fail("Cannot compile embedded unit test");
|
|
||||||
|
|
||||||
spawn("./$embed_exe");
|
|
||||||
o("GET /test_get_header HTTP/1.0\nHost: blah\n\n",
|
|
||||||
'Value: \[blah\]', 'mg_get_header', 0);
|
|
||||||
o("GET /test_get_var?a=b&my_var=foo&c=d HTTP/1.0\n\n",
|
|
||||||
'Value: \[foo\]', 'mg_get_var 1', 0);
|
|
||||||
o("GET /test_get_var?my_var=foo&c=d HTTP/1.0\n\n",
|
|
||||||
'Value: \[foo\]', 'mg_get_var 2', 0);
|
|
||||||
o("GET /test_get_var?a=b&my_var=foo HTTP/1.0\n\n",
|
|
||||||
'Value: \[foo\]', 'mg_get_var 3', 0);
|
|
||||||
o("POST /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
|
|
||||||
"my_var=foo", 'Value: \[foo\]', 'mg_get_var 4', 0);
|
|
||||||
o("POST /test_get_var HTTP/1.0\nContent-Length: 18\n\n".
|
|
||||||
"a=b&my_var=foo&c=d", 'Value: \[foo\]', 'mg_get_var 5', 0);
|
|
||||||
o("POST /test_get_var HTTP/1.0\nContent-Length: 14\n\n".
|
|
||||||
"a=b&my_var=foo", 'Value: \[foo\]', 'mg_get_var 6', 0);
|
|
||||||
o("GET /test_get_var?a=one%2btwo&my_var=foo& HTTP/1.0\n\n",
|
|
||||||
'Value: \[foo\]', 'mg_get_var 7', 0);
|
|
||||||
o("GET /test_get_var?my_var=one%2btwo&b=two%2b HTTP/1.0\n\n",
|
|
||||||
'Value: \[one\+two\]', 'mg_get_var 8', 0);
|
|
||||||
|
|
||||||
# + in form data MUST be decoded to space
|
|
||||||
o("POST /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
|
|
||||||
"my_var=b+c", 'Value: \[b c\]', 'mg_get_var 9', 0);
|
|
||||||
|
|
||||||
# Test that big POSTed vars are not truncated
|
|
||||||
my $my_var = 'x' x 64000;
|
|
||||||
o("POST /test_get_var HTTP/1.0\nContent-Length: 64007\n\n".
|
|
||||||
"my_var=$my_var", 'Value size: \[64000\]', 'mg_get_var 10', 0);
|
|
||||||
|
|
||||||
# Other methods should also work
|
|
||||||
o("PUT /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
|
|
||||||
"my_var=foo", 'Value: \[foo\]', 'mg_get_var 11', 0);
|
|
||||||
|
|
||||||
o("POST /test_get_request_info?xx=yy HTTP/1.0\nFoo: bar\n".
|
|
||||||
"Content-Length: 3\n\na=b",
|
|
||||||
'Method: \[POST\].URI: \[/test_get_request_info\].'.
|
|
||||||
'HTTP version: \[1.0\].HTTP header \[Foo\]: \[bar\].'.
|
|
||||||
'HTTP header \[Content-Length\]: \[3\].'.
|
|
||||||
'Query string: \[xx=yy\].'.
|
|
||||||
'Remote IP: \[\d+\].Remote port: \[\d+\].'.
|
|
||||||
'Remote user: \[\]'
|
|
||||||
, 'request_info', 0);
|
|
||||||
o("GET /not_exist HTTP/1.0\n\n", 'Error: \[404\]', '404 handler', 0);
|
|
||||||
o("bad request\n\n", 'Error: \[400\]', '* error handler', 0);
|
|
||||||
# o("GET /foo/secret HTTP/1.0\n\n",
|
|
||||||
# '401 Unauthorized', 'mg_protect_uri', 0);
|
|
||||||
# o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=bill\n\n",
|
|
||||||
# '401 Unauthorized', 'mg_protect_uri (bill)', 0);
|
|
||||||
# o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=joe\n\n",
|
|
||||||
# '200 OK', 'mg_protect_uri (joe)', 0);
|
|
||||||
|
|
||||||
kill_spawned_child();
|
|
||||||
}
|
|
||||||
|
|
||||||
print "SUCCESS! All tests passed.\n";
|
print "SUCCESS! All tests passed.\n";
|
||||||
File renamed without changes.
@@ -0,0 +1,454 @@
|
|||||||
|
// Unit test for the mongoose web server.
|
||||||
|
|
||||||
|
#define USE_WEBSOCKET
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#define USE_IPV6
|
||||||
|
#include <netdb.h> // For gethostbyname()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// USE_* definitions must be made before #include "mongoose.c" !
|
||||||
|
#include "../mongoose.c"
|
||||||
|
|
||||||
|
#define FAIL(str, line) do { \
|
||||||
|
printf("Fail on line %d: [%s]\n", line, str); \
|
||||||
|
return str; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define ASSERT(expr) do { \
|
||||||
|
static_num_tests++; \
|
||||||
|
if (!(expr)) FAIL(#expr, __LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define RUN_TEST(test) do { const char *msg = test(); \
|
||||||
|
if (msg) return msg; } while (0)
|
||||||
|
|
||||||
|
#define HTTP_PORT "45772"
|
||||||
|
#define LISTENING_ADDR "127.0.0.1:" HTTP_PORT
|
||||||
|
|
||||||
|
static int static_num_tests = 0;
|
||||||
|
|
||||||
|
// Connects to host:port, and sends formatted request to it. Returns
|
||||||
|
// malloc-ed reply and reply length, or NULL on error. Reply contains
|
||||||
|
// everything including headers, not just the message body.
|
||||||
|
static char *wget(const char *host, int port, int *len, const char *fmt, ...) {
|
||||||
|
char buf[2000], *reply = NULL;
|
||||||
|
int request_len, reply_size = 0, n, sock = -1;
|
||||||
|
struct sockaddr_in sin;
|
||||||
|
struct hostent *he = NULL;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
if (host != NULL &&
|
||||||
|
(he = gethostbyname(host)) != NULL &&
|
||||||
|
(sock = socket(PF_INET, SOCK_STREAM, 0)) != -1) {
|
||||||
|
sin.sin_family = AF_INET;
|
||||||
|
sin.sin_port = htons((uint16_t) port);
|
||||||
|
sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
|
||||||
|
if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) == 0) {
|
||||||
|
|
||||||
|
// Format and send the request.
|
||||||
|
va_start(ap, fmt);
|
||||||
|
request_len = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
while (request_len > 0 && (n = send(sock, buf, request_len, 0)) > 0) {
|
||||||
|
request_len -= n;
|
||||||
|
}
|
||||||
|
if (request_len == 0) {
|
||||||
|
*len = 0;
|
||||||
|
while ((n = recv(sock, buf, sizeof(buf), 0)) > 0) {
|
||||||
|
if (*len + n > reply_size) {
|
||||||
|
reply = realloc(reply, reply_size + sizeof(buf)); // Leak possible
|
||||||
|
reply_size += sizeof(buf);
|
||||||
|
}
|
||||||
|
if (reply != NULL) {
|
||||||
|
memcpy(reply + *len, buf, n);
|
||||||
|
*len += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closesocket(sock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *read_file(const char *path, int *size) {
|
||||||
|
FILE *fp;
|
||||||
|
struct stat st;
|
||||||
|
char *data = NULL;
|
||||||
|
if ((fp = fopen(path, "rb")) != NULL && !fstat(fileno(fp), &st)) {
|
||||||
|
*size = (int) st.st_size;
|
||||||
|
assert((data = malloc(*size)) != NULL);
|
||||||
|
assert(fread(data, 1, *size, fp) == (size_t) *size);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_parse_http_message() {
|
||||||
|
struct mg_connection ri;
|
||||||
|
char req1[] = "GET / HTTP/1.1\r\n\r\n";
|
||||||
|
char req2[] = "BLAH / HTTP/1.1\r\n\r\n";
|
||||||
|
char req3[] = "GET / HTTP/1.1\r\nBah\r\n";
|
||||||
|
char req4[] = "GET / HTTP/1.1\r\nA: foo bar\r\nB: bar\r\nbaz\r\n\r\n";
|
||||||
|
char req5[] = "GET / HTTP/1.1\r\n\r\n";
|
||||||
|
char req6[] = "G";
|
||||||
|
char req7[] = " blah ";
|
||||||
|
char req8[] = " HTTP/1.1 200 OK \n\n";
|
||||||
|
char req9[] = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
|
||||||
|
|
||||||
|
ASSERT(get_request_len("\r\n", 3) == -1);
|
||||||
|
ASSERT(get_request_len("\r\n", 2) == 0);
|
||||||
|
ASSERT(get_request_len("GET", 3) == 0);
|
||||||
|
ASSERT(get_request_len("\n\n", 2) == 2);
|
||||||
|
ASSERT(get_request_len("\n\r\n", 3) == 3);
|
||||||
|
ASSERT(get_request_len("\xdd\xdd", 2) == 0);
|
||||||
|
ASSERT(get_request_len("\xdd\x03", 2) == -1);
|
||||||
|
ASSERT(get_request_len(req3, sizeof(req3) - 1) == 0);
|
||||||
|
ASSERT(get_request_len(req6, sizeof(req6) - 1) == 0);
|
||||||
|
ASSERT(get_request_len(req7, sizeof(req7) - 1) == 0);
|
||||||
|
|
||||||
|
ASSERT(parse_http_message(req9, sizeof(req9) - 1, &ri) == sizeof(req9) - 1);
|
||||||
|
ASSERT(ri.num_headers == 1);
|
||||||
|
|
||||||
|
ASSERT(parse_http_message(req1, sizeof(req1) - 1, &ri) == sizeof(req1) - 1);
|
||||||
|
ASSERT(strcmp(ri.http_version, "1.1") == 0);
|
||||||
|
ASSERT(ri.num_headers == 0);
|
||||||
|
|
||||||
|
ASSERT(parse_http_message(req2, sizeof(req2) - 1, &ri) == -1);
|
||||||
|
ASSERT(parse_http_message(req6, 0, &ri) == -1);
|
||||||
|
ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1);
|
||||||
|
|
||||||
|
// TODO(lsm): Fix this. Header value may span multiple lines.
|
||||||
|
ASSERT(parse_http_message(req4, sizeof(req4) - 1, &ri) == sizeof(req4) - 1);
|
||||||
|
ASSERT(strcmp(ri.http_version, "1.1") == 0);
|
||||||
|
ASSERT(ri.num_headers == 3);
|
||||||
|
ASSERT(strcmp(ri.http_headers[0].name, "A") == 0);
|
||||||
|
ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0);
|
||||||
|
ASSERT(strcmp(ri.http_headers[1].name, "B") == 0);
|
||||||
|
ASSERT(strcmp(ri.http_headers[1].value, "bar") == 0);
|
||||||
|
ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0);
|
||||||
|
ASSERT(strcmp(ri.http_headers[2].value, "") == 0);
|
||||||
|
|
||||||
|
ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1);
|
||||||
|
ASSERT(strcmp(ri.request_method, "GET") == 0);
|
||||||
|
ASSERT(strcmp(ri.http_version, "1.1") == 0);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_should_keep_alive(void) {
|
||||||
|
struct mg_connection conn;
|
||||||
|
char req1[] = "GET / HTTP/1.1\r\n\r\n";
|
||||||
|
char req2[] = "GET / HTTP/1.0\r\n\r\n";
|
||||||
|
char req3[] = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n";
|
||||||
|
char req4[] = "GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n";
|
||||||
|
|
||||||
|
memset(&conn, 0, sizeof(conn));
|
||||||
|
ASSERT(parse_http_message(req1, sizeof(req1) - 1, &conn) == sizeof(req1) - 1);
|
||||||
|
ASSERT(should_keep_alive(&conn) != 0);
|
||||||
|
|
||||||
|
parse_http_message(req2, sizeof(req2) - 1, &conn);
|
||||||
|
ASSERT(should_keep_alive(&conn) == 0);
|
||||||
|
|
||||||
|
parse_http_message(req3, sizeof(req3) - 1, &conn);
|
||||||
|
ASSERT(should_keep_alive(&conn) == 0);
|
||||||
|
|
||||||
|
parse_http_message(req4, sizeof(req4) - 1, &conn);
|
||||||
|
ASSERT(should_keep_alive(&conn) != 0);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_match_prefix(void) {
|
||||||
|
ASSERT(match_prefix("/api", 4, "/api") == 4);
|
||||||
|
ASSERT(match_prefix("/a/", 3, "/a/b/c") == 3);
|
||||||
|
ASSERT(match_prefix("/a/", 3, "/ab/c") == -1);
|
||||||
|
ASSERT(match_prefix("/*/", 3, "/ab/c") == 4);
|
||||||
|
ASSERT(match_prefix("**", 2, "/a/b/c") == 6);
|
||||||
|
ASSERT(match_prefix("/*", 2, "/a/b/c") == 2);
|
||||||
|
ASSERT(match_prefix("*/*", 3, "/a/b/c") == 2);
|
||||||
|
ASSERT(match_prefix("**/", 3, "/a/b/c") == 5);
|
||||||
|
ASSERT(match_prefix("**.foo|**.bar", 13, "a.bar") == 5);
|
||||||
|
ASSERT(match_prefix("a|b|cd", 6, "cdef") == 2);
|
||||||
|
ASSERT(match_prefix("a|b|c?", 6, "cdef") == 2);
|
||||||
|
ASSERT(match_prefix("a|?|cd", 6, "cdef") == 1);
|
||||||
|
ASSERT(match_prefix("/a/**.cgi", 9, "/foo/bar/x.cgi") == -1);
|
||||||
|
ASSERT(match_prefix("/a/**.cgi", 9, "/a/bar/x.cgi") == 12);
|
||||||
|
ASSERT(match_prefix("**/", 3, "/a/b/c") == 5);
|
||||||
|
ASSERT(match_prefix("**/$", 4, "/a/b/c") == -1);
|
||||||
|
ASSERT(match_prefix("**/$", 4, "/a/b/") == 5);
|
||||||
|
ASSERT(match_prefix("$", 1, "") == 0);
|
||||||
|
ASSERT(match_prefix("$", 1, "x") == -1);
|
||||||
|
ASSERT(match_prefix("*$", 2, "x") == 1);
|
||||||
|
ASSERT(match_prefix("/$", 2, "/") == 1);
|
||||||
|
ASSERT(match_prefix("**/$", 4, "/a/b/c") == -1);
|
||||||
|
ASSERT(match_prefix("**/$", 4, "/a/b/") == 5);
|
||||||
|
ASSERT(match_prefix("*", 1, "/hello/") == 0);
|
||||||
|
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/b.b/") == -1);
|
||||||
|
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/b.b") == 6);
|
||||||
|
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/B.A") == 6);
|
||||||
|
ASSERT(match_prefix("**o$", 4, "HELLO") == 5);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_remove_double_dots() {
|
||||||
|
struct { char before[20], after[20]; } data[] = {
|
||||||
|
{"////a", "/a"},
|
||||||
|
{"/.....", "/."},
|
||||||
|
{"/......", "/"},
|
||||||
|
{"...", "..."},
|
||||||
|
{"/...///", "/./"},
|
||||||
|
{"/a...///", "/a.../"},
|
||||||
|
{"/.x", "/.x"},
|
||||||
|
{"/\\", "/"},
|
||||||
|
{"/a\\", "/a\\"},
|
||||||
|
{"/a\\\\...", "/a\\."},
|
||||||
|
};
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(data); i++) {
|
||||||
|
remove_double_dots_and_double_slashes(data[i].before);
|
||||||
|
ASSERT(strcmp(data[i].before, data[i].after) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_get_var(void) {
|
||||||
|
static const char *post[] = {
|
||||||
|
"a=1&&b=2&d&=&c=3%20&e=",
|
||||||
|
"q=&st=2012%2F11%2F13+17%3A05&et=&team_id=",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
char buf[20];
|
||||||
|
|
||||||
|
ASSERT(get_var(post[0], strlen(post[0]), "a", buf, sizeof(buf)) == 1);
|
||||||
|
ASSERT(buf[0] == '1' && buf[1] == '\0');
|
||||||
|
ASSERT(get_var(post[0], strlen(post[0]), "b", buf, sizeof(buf)) == 1);
|
||||||
|
ASSERT(buf[0] == '2' && buf[1] == '\0');
|
||||||
|
ASSERT(get_var(post[0], strlen(post[0]), "c", buf, sizeof(buf)) == 2);
|
||||||
|
ASSERT(buf[0] == '3' && buf[1] == ' ' && buf[2] == '\0');
|
||||||
|
ASSERT(get_var(post[0], strlen(post[0]), "e", buf, sizeof(buf)) == 0);
|
||||||
|
ASSERT(buf[0] == '\0');
|
||||||
|
|
||||||
|
ASSERT(get_var(post[0], strlen(post[0]), "d", buf, sizeof(buf)) == -1);
|
||||||
|
ASSERT(get_var(post[0], strlen(post[0]), "c", buf, 2) == -2);
|
||||||
|
|
||||||
|
ASSERT(get_var(post[0], strlen(post[0]), "x", NULL, 10) == -2);
|
||||||
|
ASSERT(get_var(post[0], strlen(post[0]), "x", buf, 0) == -2);
|
||||||
|
ASSERT(get_var(post[1], strlen(post[1]), "st", buf, 16) == -2);
|
||||||
|
ASSERT(get_var(post[1], strlen(post[1]), "st", buf, 17) == 16);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_url_decode(void) {
|
||||||
|
char buf[100];
|
||||||
|
|
||||||
|
ASSERT(mg_url_decode("foo", 3, buf, 3, 0) == -1); // No space for \0
|
||||||
|
ASSERT(mg_url_decode("foo", 3, buf, 4, 0) == 3);
|
||||||
|
ASSERT(strcmp(buf, "foo") == 0);
|
||||||
|
|
||||||
|
ASSERT(mg_url_decode("a+", 2, buf, sizeof(buf), 0) == 2);
|
||||||
|
ASSERT(strcmp(buf, "a+") == 0);
|
||||||
|
|
||||||
|
ASSERT(mg_url_decode("a+", 2, buf, sizeof(buf), 1) == 2);
|
||||||
|
ASSERT(strcmp(buf, "a ") == 0);
|
||||||
|
|
||||||
|
ASSERT(mg_url_decode("%61", 1, buf, sizeof(buf), 1) == 1);
|
||||||
|
ASSERT(strcmp(buf, "%") == 0);
|
||||||
|
|
||||||
|
ASSERT(mg_url_decode("%61", 2, buf, sizeof(buf), 1) == 2);
|
||||||
|
ASSERT(strcmp(buf, "%6") == 0);
|
||||||
|
|
||||||
|
ASSERT(mg_url_decode("%61", 3, buf, sizeof(buf), 1) == 1);
|
||||||
|
ASSERT(strcmp(buf, "a") == 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_to64(void) {
|
||||||
|
ASSERT(to64("0") == 0);
|
||||||
|
ASSERT(to64("") == 0);
|
||||||
|
ASSERT(to64("123") == 123);
|
||||||
|
ASSERT(to64("-34") == -34);
|
||||||
|
ASSERT(to64("3566626116") == 3566626116);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_parse_port_string(void) {
|
||||||
|
static const char *valid[] = {
|
||||||
|
"1", "1.2.3.4:1",
|
||||||
|
#if defined(USE_IPV6)
|
||||||
|
"[::1]:123", "[3ffe:2a00:100:7031::1]:900",
|
||||||
|
#endif
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
static const char *invalid[] = {
|
||||||
|
"0", "99999", "1k", "1.2.3", "1.2.3.4:", "1.2.3.4:2p", NULL
|
||||||
|
};
|
||||||
|
union socket_address sa;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; valid[i] != NULL; i++) {
|
||||||
|
ASSERT(parse_port_string(valid[i], &sa) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; invalid[i] != NULL; i++) {
|
||||||
|
ASSERT(parse_port_string(invalid[i], &sa) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_base64_encode(void) {
|
||||||
|
const char *in[] = {"a", "ab", "abc", "abcd", NULL};
|
||||||
|
const char *out[] = {"YQ==", "YWI=", "YWJj", "YWJjZA=="};
|
||||||
|
char buf[100];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; in[i] != NULL; i++) {
|
||||||
|
base64_encode((unsigned char *) in[i], strlen(in[i]), buf);
|
||||||
|
ASSERT(!strcmp(buf, out[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_mg_parse_header(void) {
|
||||||
|
const char *str = "xx=1 kl yy, ert=234 kl=123, "
|
||||||
|
"ii=\"12\\\"34\" zz='aa bb', gf=\"xx d=1234";
|
||||||
|
char buf[10];
|
||||||
|
ASSERT(mg_parse_header(str, "yy", buf, sizeof(buf)) == 0);
|
||||||
|
ASSERT(mg_parse_header(str, "ert", buf, sizeof(buf)) == 3);
|
||||||
|
ASSERT(strcmp(buf, "234") == 0);
|
||||||
|
ASSERT(mg_parse_header(str, "ert", buf, 2) == 0);
|
||||||
|
ASSERT(mg_parse_header(str, "ert", buf, 3) == 0);
|
||||||
|
ASSERT(mg_parse_header(str, "ert", buf, 4) == 3);
|
||||||
|
ASSERT(mg_parse_header(str, "gf", buf, sizeof(buf)) == 0);
|
||||||
|
ASSERT(mg_parse_header(str, "zz", buf, sizeof(buf)) == 5);
|
||||||
|
ASSERT(strcmp(buf, "aa bb") == 0);
|
||||||
|
ASSERT(mg_parse_header(str, "d", buf, sizeof(buf)) == 4);
|
||||||
|
ASSERT(strcmp(buf, "1234") == 0);
|
||||||
|
buf[0] = 'x';
|
||||||
|
ASSERT(mg_parse_header(str, "MMM", buf, sizeof(buf)) == 0);
|
||||||
|
ASSERT(buf[0] == '\0');
|
||||||
|
ASSERT(mg_parse_header(str, "kl", buf, sizeof(buf)) == 3);
|
||||||
|
ASSERT(strcmp(buf, "123") == 0);
|
||||||
|
ASSERT(mg_parse_header(str, "xx", buf, sizeof(buf)) == 1);
|
||||||
|
ASSERT(strcmp(buf, "1") == 0);
|
||||||
|
ASSERT(mg_parse_header(str, "ii", buf, sizeof(buf)) == 5);
|
||||||
|
ASSERT(strcmp(buf, "12\"34") == 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_next_option(void) {
|
||||||
|
const char *p, *list = "x/8,/y**=1;2k,z";
|
||||||
|
struct vec a, b;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
ASSERT(next_option(NULL, &a, &b) == NULL);
|
||||||
|
for (i = 0, p = list; (p = next_option(p, &a, &b)) != NULL; i++) {
|
||||||
|
ASSERT(i != 0 || (a.ptr == list && a.len == 3 && b.len == 0));
|
||||||
|
ASSERT(i != 1 || (a.ptr == list + 4 && a.len == 4 && b.ptr == list + 9 &&
|
||||||
|
b.len == 4));
|
||||||
|
|
||||||
|
ASSERT(i != 2 || (a.ptr == list + 14 && a.len == 1 && b.len == 0));
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cb1(struct mg_connection *conn) {
|
||||||
|
// We're not sending HTTP headers here, to make testing easier
|
||||||
|
mg_printf(conn, "%s %s %s",
|
||||||
|
conn->server_param == NULL ? "?" : (char *) conn->server_param,
|
||||||
|
conn->connection_param == NULL ? "?" : "!", conn->remote_ip);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_regular_file(void) {
|
||||||
|
static const char *fname = "main.c";
|
||||||
|
int reply_len, file_len;
|
||||||
|
char *reply, *file_data;
|
||||||
|
file_stat_t st;
|
||||||
|
|
||||||
|
ASSERT(stat(fname, &st) == 0);
|
||||||
|
ASSERT(st.st_size > 0);
|
||||||
|
|
||||||
|
ASSERT((file_data = read_file(fname, &file_len)) != NULL);
|
||||||
|
ASSERT(file_len == st.st_size);
|
||||||
|
|
||||||
|
reply = wget("127.0.0.1", atoi(HTTP_PORT), &reply_len,
|
||||||
|
"GET /%s.c HTTP/1.0\r\n\r\n", fname);
|
||||||
|
ASSERT(reply != NULL);
|
||||||
|
ASSERT(reply_len > 0);
|
||||||
|
// TODO(lsm): test headers and content
|
||||||
|
|
||||||
|
free(reply);
|
||||||
|
free(file_data);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_server_param(void) {
|
||||||
|
int reply_len;
|
||||||
|
char *reply;
|
||||||
|
|
||||||
|
reply = wget("127.0.0.1", atoi(HTTP_PORT), &reply_len, "%s",
|
||||||
|
"GET /cb1 HTTP/1.0\r\n\r\n");
|
||||||
|
ASSERT(reply != NULL);
|
||||||
|
ASSERT(reply_len == 15);
|
||||||
|
// cb1() does not send HTTP headers
|
||||||
|
ASSERT(memcmp(reply, "foo ? 127.0.0.1", 5) == 0);
|
||||||
|
//printf("%d [%.*s]\n", reply_len, reply_len, reply);
|
||||||
|
free(reply);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *server_thread(void *param) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 10; i++) mg_poll_server((struct mg_server *) param, 1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *test_server(void) {
|
||||||
|
struct mg_server *server = mg_create_server("foo");
|
||||||
|
|
||||||
|
ASSERT(server != NULL);
|
||||||
|
ASSERT(mg_set_option(server, "listening_port", LISTENING_ADDR) == NULL);
|
||||||
|
ASSERT(mg_set_option(server, "document_root", ".") == NULL);
|
||||||
|
mg_add_uri_handler(server, "/cb1", cb1);
|
||||||
|
mg_start_thread(server_thread, server);
|
||||||
|
RUN_TEST(test_regular_file);
|
||||||
|
RUN_TEST(test_server_param);
|
||||||
|
|
||||||
|
// TODO(lsm): come up with a better way of thread sync
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
mg_destroy_server(&server);
|
||||||
|
ASSERT(server == NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *run_all_tests(void) {
|
||||||
|
RUN_TEST(test_should_keep_alive);
|
||||||
|
RUN_TEST(test_match_prefix);
|
||||||
|
RUN_TEST(test_remove_double_dots);
|
||||||
|
RUN_TEST(test_parse_http_message);
|
||||||
|
RUN_TEST(test_parse_port_string);
|
||||||
|
RUN_TEST(test_to64);
|
||||||
|
RUN_TEST(test_url_decode);
|
||||||
|
RUN_TEST(test_base64_encode);
|
||||||
|
RUN_TEST(test_mg_parse_header);
|
||||||
|
RUN_TEST(test_get_var);
|
||||||
|
RUN_TEST(test_next_option);
|
||||||
|
RUN_TEST(test_server);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int __cdecl main(void) {
|
||||||
|
const char *fail_msg = run_all_tests();
|
||||||
|
printf("%s, tests run: %d\n", fail_msg ? "FAIL" : "PASS", static_num_tests);
|
||||||
|
return fail_msg == NULL ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||||
|
}
|
||||||
File renamed without changes.
+185
@@ -0,0 +1,185 @@
|
|||||||
|
# Mongoose API Reference
|
||||||
|
|
||||||
|
struct mg_server *mg_create_server(void *server_param);
|
||||||
|
|
||||||
|
Creates web server instance. Returns opaque instance pointer, or NULL if
|
||||||
|
there is not enough memory. `server_param`: Could be any pointer, or NULL.
|
||||||
|
This pointer will be passed
|
||||||
|
to the callback functions as `struct mg_connection::server_param` field.
|
||||||
|
A common use case is to pass `this` pointer of the C++ wrapper class
|
||||||
|
as `user_param`, to let the callback get the pointer to the C++
|
||||||
|
object.
|
||||||
|
|
||||||
|
Note that this function doesn't make the
|
||||||
|
server instance to serve. Serving is done by `mg_poll_server()` function.
|
||||||
|
Mongoose has single-threaded, event-driven, asynchronous, non-blocking core.
|
||||||
|
When server instance is created, it contains an information about
|
||||||
|
the configuration and the state of each connection.
|
||||||
|
Server instance is capable on listening on only one port. After creation,
|
||||||
|
`struct mg_server` has a list
|
||||||
|
of active connections, initially empty. It has a list of URI handlers,
|
||||||
|
initially empty, and configuration parameters. Configuration can be
|
||||||
|
altered by `mg_set_option()`, and new URI handlers could be added by
|
||||||
|
`mg_add_uri_handler()`.
|
||||||
|
|
||||||
|
Side-effect: on UNIX, `mg_create_server()` ignores SIGPIPE signals. If custom
|
||||||
|
processing is required SIGPIPE, signal handler must be set up after
|
||||||
|
calling `mg_create_server()`.
|
||||||
|
|
||||||
|
Important: Mongoose does not install `SIGCHLD` handler. If CGI is used,
|
||||||
|
`SIGCHLD` handler must be set up to reap CGI zombie processes.
|
||||||
|
|
||||||
|
|
||||||
|
void mg_destroy_server(struct mg_server **server);
|
||||||
|
|
||||||
|
Deallocates web server instance, closes all pending connections, and makes
|
||||||
|
server pointer a NULL pointer.
|
||||||
|
|
||||||
|
const char mg_set_option(struct mg_server *server, const char *name,
|
||||||
|
const char *value);
|
||||||
|
|
||||||
|
Sets a particular server option. Please refer to a separate documentation page
|
||||||
|
that lists all available option names. Note that at least one option,
|
||||||
|
`listening_port`, must be specified. To serve static files, `document_root`
|
||||||
|
must be specified too. If `document_root` option is left unset, Mongoose
|
||||||
|
will not access filesystem at all. This function returns NULL if option was
|
||||||
|
set successfully, otherwise it returns human-readable error string. It is
|
||||||
|
allowed to call `mg_set_option()` by the same thread that does
|
||||||
|
`mg_poll_server()` (an IO thread) and change server configuration while it
|
||||||
|
is serving, in between `mg_poll_server()` calls.
|
||||||
|
|
||||||
|
void mg_poll_server(struct mg_server *server, int milliseconds);
|
||||||
|
|
||||||
|
This function performs one iteration of IO loop by iterating over all
|
||||||
|
active connections, performing `select()` syscall on all sockets, and sleeping
|
||||||
|
for `milliseconds` number of milliseconds. When `select()` returns, Mongoose
|
||||||
|
does an IO for each socket that has data to be sent or received. Application
|
||||||
|
code must call `mg_poll_server()` in a loop. It is an error to have more then
|
||||||
|
one thread calling `mg_poll_server()`, `mg_set_option()` or any other function
|
||||||
|
that take `struct mg_server *` parameter. Mongoose does not
|
||||||
|
mutex-protect `struct mg_server *`, therefore the best practice is
|
||||||
|
to call server management functions from the same thread (an IO thread).
|
||||||
|
|
||||||
|
void mg_add_uri_handler(struct mg_server *, const char *uri, mg_handler_t);
|
||||||
|
|
||||||
|
Adds an URI handler. If Mongoose gets a request and request's URI starts
|
||||||
|
with `uri`, then specified handler is called to serve the request. Thus, an
|
||||||
|
`uri` is a match prefix. For example, if `uri` is "/", then all requests will
|
||||||
|
be routed to the handler, because all URIs start with `/` character.
|
||||||
|
|
||||||
|
When mongoose buffers in HTTP request and successfully parses it, it calls
|
||||||
|
appropriate URI handler immediately for GET requests. For POST requests,
|
||||||
|
Mongoose delays the call until the whole POST request is buffered in memory.
|
||||||
|
POST data is available to the callback as `struct mg_connection::content`,
|
||||||
|
and POST data length is in `struct mg_connection::content_len`.
|
||||||
|
|
||||||
|
Note that websocket connections are treated the same way. Mongoose buffers
|
||||||
|
websocket frame in memory, and calls URI handler when frame is fully
|
||||||
|
buffered. Frame data is available `struct mg_connection::content`, and
|
||||||
|
data length is in `struct mg_connection::content_len`, i.e. very similar to
|
||||||
|
the POST request. `struct mg_connection::is_websocket` flag indicates
|
||||||
|
whether the request is websocket or not. Also, for websocket requests,
|
||||||
|
there is `struct mg_connection::wsbits` field which contains first byte
|
||||||
|
of the websocket frame which URI handler can examine. Note that to
|
||||||
|
reply to the websocket client, `mg_websocket_write()` should be used.
|
||||||
|
To reply to the plain HTTP client, `mg_write()` should be used.
|
||||||
|
|
||||||
|
void mg_set_http_error_handler(struct mg_server *, mg_handler_t);
|
||||||
|
|
||||||
|
Adds HTTP error handler. An actual HTTP error is passed as
|
||||||
|
`struct mg_connection::status_code` parameter. If handler returns 0, it
|
||||||
|
means a handler has not processed the connection, and mongoose proceeds
|
||||||
|
with sending HTTP error to the client. Otherwise, mongoose does nothing.
|
||||||
|
|
||||||
|
const char **mg_get_valid_option_names(void);
|
||||||
|
|
||||||
|
Returns a NULL-terminated array of option names and their default values.
|
||||||
|
There are two entries per option in an array: an option name followed by a
|
||||||
|
default value. A default value could be NULL. A NULL name indicates an end
|
||||||
|
of the array.
|
||||||
|
|
||||||
|
const char *mg_get_option(const struct mg_server *server, const char *name);
|
||||||
|
|
||||||
|
Returns the value of particular configuration parameter. If
|
||||||
|
given parameter name is not valid, NULL is returned. For valid names, return
|
||||||
|
value is guaranteed to be non-NULL. If parameter is not set, zero-length string
|
||||||
|
is returned.
|
||||||
|
|
||||||
|
|
||||||
|
int mg_iterate_over_connections(struct mg_server *,
|
||||||
|
void (*func)(struct mg_connection *, void *),
|
||||||
|
void *param);
|
||||||
|
|
||||||
|
This is an interface primarily designed to push arbitrary data to websocket
|
||||||
|
connections at any time. This function could be called from any thread. When
|
||||||
|
it returns, an IO thread called `func()` on each active connection,
|
||||||
|
passing `param` as an extra parameter. It is allowed to call `mg_send_data()` or
|
||||||
|
`mg_websocket_write()` within a callback, cause `func` is executed in the
|
||||||
|
context of the IO thread.
|
||||||
|
|
||||||
|
void mg_send_status(struct mg_connection *, int status_code);
|
||||||
|
void mg_send_header(struct mg_connection *, const char *name,
|
||||||
|
const char *value);
|
||||||
|
void mg_send_data(struct mg_connection *, const void *data, int data_len);
|
||||||
|
void mg_printf_data(struct mg_connection *, const char *format, ...);
|
||||||
|
|
||||||
|
These functions are used to construct a response to the client. HTTP response
|
||||||
|
consists of three parts: a status line, zero or more HTTP headers,
|
||||||
|
a response body. Mongoose provides functions for all three parts:
|
||||||
|
* `mg_send_status()` is used to create status line. This function can be
|
||||||
|
called zero or once. If `mg_send_status()` is not called, then Mongoose
|
||||||
|
will send status 200 (success) implicitly.
|
||||||
|
* `mg_send_header()` adds HTTP header to the response. This function could
|
||||||
|
be called zero or more times.
|
||||||
|
* `mg_send_data()` and `mg_printf_data()` are used to send data to the
|
||||||
|
client. Note that Mongoose adds `Transfer-Encoding: chunked` header
|
||||||
|
implicitly, and sends data in chunks. Therefore, it is not necessary to
|
||||||
|
set `Content-Length` header.
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
int mg_websocket_write(struct mg_connection* conn, int opcode,
|
||||||
|
const char *data, size_t data_len);
|
||||||
|
|
||||||
|
Similar to `mg_write()`, but wraps the data into a websocket frame with a
|
||||||
|
given websocket `opcode`. This function is available when mongoose is
|
||||||
|
compiled with `-DUSE_WEBSOCKET`.
|
||||||
|
|
||||||
|
const char *mg_get_header(const struct mg_connection *, const char *name);
|
||||||
|
|
||||||
|
Get the value of particular HTTP header. This is a helper function.
|
||||||
|
It traverses http_headers array, and if the header is present in the array,
|
||||||
|
returns its value. If it is not present, NULL is returned.
|
||||||
|
|
||||||
|
|
||||||
|
int mg_get_var(const struct mg_connection *conn, const char *var_name,
|
||||||
|
char *buf, size_t buf_len);
|
||||||
|
|
||||||
|
Gets HTTP form variable. Both POST buffer and query string are inspected.
|
||||||
|
Form variable is url-decoded and written to the buffer. On success, this
|
||||||
|
function returns the length of decoded variable. On error, -1 is returned if
|
||||||
|
variable not found, and -2 is returned if destination buffer is too small
|
||||||
|
to hold the variable. Destination buffer is guaranteed to be
|
||||||
|
'\0' - terminated if it is not NULL or zero length.
|
||||||
|
|
||||||
|
int mg_parse_header(const char *hdr, const char *var_name, char *buf,
|
||||||
|
size_t buf_size);
|
||||||
|
|
||||||
|
This function parses HTTP header and fetches given variable's value in a buffer.
|
||||||
|
A header should be like `x=123, y=345, z="other value"`. This function is
|
||||||
|
designed to parse Cookie headers, Authorization headers, and similar. Returns
|
||||||
|
the length of the fetched value, or 0 if variable not found.
|
||||||
|
|
||||||
|
int mg_modify_passwords_file(const char *passwords_file_name,
|
||||||
|
const char *domain,
|
||||||
|
const char *user,
|
||||||
|
const char *password);
|
||||||
|
|
||||||
|
Add, edit or delete the entry in the passwords file.
|
||||||
|
This function allows an application to manipulate .htpasswd files on the
|
||||||
|
fly by adding, deleting and changing user records. This is one of the
|
||||||
|
several ways of implementing authentication on the server side. For another,
|
||||||
|
cookie-based way please refer to the examples/chat.c in the source tree.
|
||||||
|
If password is not NULL, entry is added (or modified if already exists).
|
||||||
|
If password is NULL, entry is deleted.
|
||||||
|
Return: 1 on success, 0 on error.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Mongoose Build on Android
|
||||||
|
|
||||||
|
This is a small guide to help you run mongoose on Android. Currently it is
|
||||||
|
tested on the HTC Wildfire. If you have managed to run it on other devices
|
||||||
|
as well, please comment or drop an email in the mailing list.
|
||||||
|
Note : You dont need root access to run mongoose on Android.
|
||||||
|
|
||||||
|
- Clone Mongoose Git repo
|
||||||
|
- Download the Android NDK from [http://developer.android.com/tools/sdk/ndk/index.html](http://developer.android.com/tools/sdk/ndk/index.html)
|
||||||
|
- Run `/path-to-ndk/ndk-build -C /path-to-mongoose/build`
|
||||||
|
That should generate mongoose/lib/armeabi/mongoose
|
||||||
|
- Using the adb tool (you need to have Android SDK installed for that),
|
||||||
|
push the generated mongoose binary to `/data/local` folder on device.
|
||||||
|
- From adb shell, navigate to `/data/local` and execute `./mongoose`.
|
||||||
|
- To test if the server is running fine, visit your web-browser and
|
||||||
|
navigate to `http://127.0.0.1:8080` You should see the `Index of /` page.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `jni` stands for Java Native Interface. Read up on Android NDK if you want
|
||||||
|
to know how to interact with the native C functions of mongoose in Android
|
||||||
|
Java applications.
|
||||||
|
- TODO: A Java application that interacts with the native binary or a
|
||||||
|
shared library.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Mongoose Embedding Guide
|
||||||
|
|
||||||
|
Embedding Mongoose is done in two steps:
|
||||||
|
|
||||||
|
1. Copy
|
||||||
|
[mongoose.c](https://raw.github.com/cesanta/mongoose/master/mongoose.c) and
|
||||||
|
[mongoose.h](https://raw.github.com/cesanta/mongoose/master/mongoose.h)
|
||||||
|
to your application's source tree and include them in the build.
|
||||||
|
2. Somewhere in the application code, call `mg_create_server()` to create
|
||||||
|
a server, configure it with `mg_set_option()` and loop with
|
||||||
|
`mg_poll_server()` until done. Call `mg_destroy_server()` to cleanup.
|
||||||
|
|
||||||
|
Here's a minimal application `app.c` that embeds mongoose:
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
|
int main(void) {
|
||||||
|
struct mg_server *server = mg_create_server(NULL);
|
||||||
|
mg_set_option(server, "document_root", ".");
|
||||||
|
mg_set_option(server, "listening_port", "8080");
|
||||||
|
for (;;) mg_poll_server(server, 1000); // Infinite loop, Ctrl-C to stop
|
||||||
|
mg_destroy_server(&server);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
To compile it, put `mongoose.c`, `mongoose.h` and `minimal.c` into one
|
||||||
|
folder, start terminal on UNIX or Visual Studio command line prompt on Windows,
|
||||||
|
and run the following command:
|
||||||
|
|
||||||
|
cc app.c mongoose.c -o app # on Unix
|
||||||
|
cl app.c mongoose.c /TC /MD # on Windows
|
||||||
|
|
||||||
|
When run, this simple application opens port 8080 and serves static files,
|
||||||
|
CGI files and lists directory content in the current working directory.
|
||||||
|
|
||||||
|
Mongoose can call user-defined functions when certain URIs are requested.
|
||||||
|
These functions are _called uri handlers_. `mg_add_uri_handler()` registers
|
||||||
|
an URI handler, and there is no restriction exist on the number of URI handlers.
|
||||||
|
Also, mongoose can call a user-defined function when it is about to send
|
||||||
|
HTTP error back to client. That function is called _http error handler_ and
|
||||||
|
can be registered by `mg_set_http_error_handler()`. Handlers are called
|
||||||
|
by Mongoose with `struct mg_connection *` pointer as a parameter, which
|
||||||
|
has all information about the request: HTTP headers, POST or websocket
|
||||||
|
data buffer, etcetera.
|
||||||
|
|
||||||
|
Let's extend our minimal application example and
|
||||||
|
create an URI that will be served by user's C code. The app will handle
|
||||||
|
`/hello` URI by showing a hello message. So, when app is run,
|
||||||
|
http://127.0.0.1:8080/hello will say hello, and here's the code:
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
static int handle_hello(struct mg_connection *conn) {
|
||||||
|
static const char *reply = "Hello world!\n";
|
||||||
|
mg_write_data(conn, reply, strlen(reply));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
struct mg_server *server = mg_create_server(NULL);
|
||||||
|
mg_set_option(server, "document_root", ".");
|
||||||
|
mg_set_option(server, "listening_port", "8080");
|
||||||
|
mg_add_uri_handler(server, "/hello", &handle_hello);
|
||||||
|
for (;;) mg_poll_server(server, 1000); // Infinite loop, Ctrl-C to stop
|
||||||
|
mg_destroy_server(&server);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Below is the list of compilation flags that enable or disable certain
|
||||||
|
features. By default, some features are enabled, and could be disabled
|
||||||
|
by setting appropriate `NO_*` flag. Features that are disabled by default
|
||||||
|
could be enabled by setting appropriate `USE_*` flag. Bare bones Mongoose
|
||||||
|
is quite small, about 30 kilobytes of compiled x86 code. Each feature adds
|
||||||
|
a couple of kilobytes to the executable size, and also has some runtime penalty.
|
||||||
|
|
||||||
|
-DNO_AUTH Disable MD5 authorization support
|
||||||
|
-DNO_CGI Disable CGI support
|
||||||
|
-DNO_DAV Disable WebDAV support (PUT, DELETE, MKCOL, PROPFIND)
|
||||||
|
-DNO_DIRECTORY_LISTING Disable directory listing
|
||||||
|
-DNO_LOGGING Disable access/error logging
|
||||||
|
-DNO_WEBSOCKET Disable WebSocket support
|
||||||
|
|
||||||
|
-DUSE_IPV6 Enable IPv6 support
|
||||||
|
-DUSE_LUA Enable Lua scripting
|
||||||
|
-DUSE_LUA_SQLITE3 Enable sqlite3 binding for Lua
|
||||||
|
-DUSE_SSL Enable SSL
|
||||||
|
-DUSE_POST_SIZE_LIMIT=X POST requests larger than X will be rejected
|
||||||
|
-DUSE_EXTRA_HTTP_HEADERS=X Append X to the HTTP headers for static files
|
||||||
|
|
||||||
|
Mongoose source code contains a well-commented example code, listed below:
|
||||||
|
|
||||||
|
* [hello.c](https://github.com/cesanta/mongoose/blob/master/examples/hello.c)
|
||||||
|
is a minimalisting hello world example
|
||||||
|
* [post.c](https://github.com/cesanta/mongoose/blob/master/examples/post.c)
|
||||||
|
shows how to handle form input
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
# Mongoose FAQ
|
||||||
|
|
||||||
|
### PHP doesn't work: getting empty page, or 'File not found' error
|
||||||
|
|
||||||
|
The reason for that is wrong paths to the interpreter. Remember that with PHP,
|
||||||
|
correct interpreter is `php-cgi.exe` (`php-cgi` on UNIX). Solution: specify
|
||||||
|
full path to the PHP interpreter, e.g.:
|
||||||
|
|
||||||
|
mongoose -cgi_interpreter /full/path/to/php-cgi
|
||||||
|
|
||||||
|
### Mongoose fails to start
|
||||||
|
|
||||||
|
If Mongoose exits immediately when run, this
|
||||||
|
usually indicates a syntax error in the configuration file
|
||||||
|
(named `mongoose.conf` by default) or the command-line arguments.
|
||||||
|
Syntax checking is omitted from Mongoose to keep its size low. However,
|
||||||
|
the Manual should be of help. Note: the syntax changes from time to time,
|
||||||
|
so updating the config file might be necessary after executable update.
|
||||||
|
|
||||||
|
### Embedding with OpenSSL on Windows might fail because of calling convention
|
||||||
|
|
||||||
|
To force Mongoose to use `__stdcall` convention, add `/Gz` compilation
|
||||||
|
flag to the Visual Studio project settings.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Mongoose Internals
|
||||||
|
|
||||||
|
Mongoose has single-threaded, event-driven, asynchronous, non-blocking core.
|
||||||
|
When mongoose server instance is created, it contains an information about
|
||||||
|
the configuration and the state of each connection:
|
||||||
|
|
||||||
|
struct mg_server {
|
||||||
|
sock_t listening_sock; // Listening socket
|
||||||
|
union socket_address lsa; // Listening socket address
|
||||||
|
struct linked_list_link active_connections;
|
||||||
|
struct linked_list_link uri_handlers;
|
||||||
|
char *config_options[NUM_OPTIONS];
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
A server instance is capable on listening on only one port. After creation,
|
||||||
|
a server instance has a list
|
||||||
|
of active connections, initially empty. It has a list of URI handlers,
|
||||||
|
initially empty, and configuration parameters. Configuration can be
|
||||||
|
altered by `mg_set_option()`, and new URI handlers could be added by
|
||||||
|
`mg_add_uri_handler()`.
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Mongoose Lua Server Pages
|
||||||
|
|
||||||
|
Pre-built Windows and Mac mongoose binaries support Lua Server Pages
|
||||||
|
functionality.
|
||||||
|
That means it is possible to write PHP-like scripts with mongoose
|
||||||
|
using Lua programming language instead of PHP. Lua is known
|
||||||
|
for it's speed and small size. Mongoose uses Lua version 5.2.1, the
|
||||||
|
documentation for it can be found at
|
||||||
|
[Lua 5.2 reference manual](http://www.lua.org/manual/5.2/).
|
||||||
|
|
||||||
|
To create a Lua Page, make a file that is called `<something>.lp`. For example,
|
||||||
|
let's say it is going to be `my_page.lp`. It is important to have a file
|
||||||
|
name that ends up with `.lp`, cause this is the way mongoose recognises
|
||||||
|
Lua Page file. The contents of the file, just like
|
||||||
|
with PHP, is HTML with embedded Lua code. Lua code must be enclosed within
|
||||||
|
`<? ?>` blocks, and can appear anywhere on the page.
|
||||||
|
|
||||||
|
Mongoose does not send HTTP headers for Lua pages. Therefore,
|
||||||
|
every Lua Page must begin with HTTP status line and headers, like this:
|
||||||
|
|
||||||
|
<? mg.write('HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n') ?>
|
||||||
|
<html><body>
|
||||||
|
<span>Today is:</span> <? mg.write(os.date("%A")) ?>
|
||||||
|
</body></html>
|
||||||
|
|
||||||
|
Note that this example uses function `mg.write()`, which prints data to the
|
||||||
|
web page. Using function `mg.write()` is the way to generate web content from
|
||||||
|
inside Lua code. In addition to `mg.write()`, all standard library functions
|
||||||
|
are accessible from the Lua code (please check reference manual for details).
|
||||||
|
Information about the request is available in `mg.request_info` object.
|
||||||
|
I contains request method, all headers, etcetera. Please refer to
|
||||||
|
`struct mg_request_info` definition in
|
||||||
|
[mongoose.h](https://github.com/cesanta/mongoose/blob/master/mongoose.h)
|
||||||
|
to see what is available in `mg.request_info`. Also,
|
||||||
|
[page.lp](https://github.com/cesanta/mongoose/blob/master/test/page.lp) and
|
||||||
|
[prime_numbers.lp](https://github.com/cesanta/mongoose/blob/master/examples/lua/prime_numbers.lp)
|
||||||
|
contain some example code that uses `request_info` and other functions,
|
||||||
|
like form submission.
|
||||||
|
|
||||||
|
Mongoose exports the following to the Lua server page:
|
||||||
|
|
||||||
|
mg.write(str) -- writes string to the client
|
||||||
|
mg.onerror(msg) -- error handler, can be overridden
|
||||||
|
mg.request_info -- a table with request information
|
||||||
|
sqlite3 -- Sqlite3 interface
|
||||||
|
|
||||||
|
slite3 functions are documented at [lua.sqlite.org](http://lua.sqlite.org/),
|
||||||
|
and usage example is at
|
||||||
|
[page.lp](https://github.com/cesanta/mongoose/blob/master/test/page.lp).
|
||||||
|
|
||||||
|
Using Lua scripting it is easy to emulate SSI functionality. For example,
|
||||||
|
to include the content of another file, one can write:
|
||||||
|
|
||||||
|
<? mg.write(io.open('MY_FILE.TXT'):read('*all')) ?>
|
||||||
|
|
||||||
|
To serve a Lua Page, mongoose creates Lua context. That context is used for
|
||||||
|
all Lua blocks within the page. That means, all Lua blocks on the same page
|
||||||
|
share the same context. If one block defines a variable, for example, that
|
||||||
|
variable is visible in the block that follows.
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
# Mongoose Configuration Options
|
||||||
|
|
||||||
|
### access\_control\_list
|
||||||
|
An Access Control List (ACL) allows restrictions to be put on the list of IP
|
||||||
|
addresses which have access to the web server. In the case of the Mongoose
|
||||||
|
web server, the ACL is a comma separated list of IP subnets, where each
|
||||||
|
subnet is prepended by either a `-` or a `+` sign. A plus sign means allow,
|
||||||
|
where a minus sign means deny. If a subnet mask is omitted, such as `-1.2.3.4`,
|
||||||
|
this means to deny only that single IP address.
|
||||||
|
|
||||||
|
Subnet masks may vary from 0 to 32, inclusive. The default setting is to allow
|
||||||
|
all accesses. On each request the full list is traversed, and
|
||||||
|
the last match wins. Example: `$ mongoose -access_control_list -0.0.0.0/0,+192.168/16` to deny all acccesses except from 192.168/16 subnet. To learn
|
||||||
|
more about subnet masks, see the
|
||||||
|
[Wikipedia page on Subnetwork](http://en.wikipedia.org/wiki/Subnetwork)
|
||||||
|
|
||||||
|
Default: not set, all accesses are allowed.
|
||||||
|
|
||||||
|
### access\_log\_file
|
||||||
|
Path to a file for access logs. Either full path, or relative to the
|
||||||
|
mongoose executable. Default: not set, no query logging is done.
|
||||||
|
|
||||||
|
### auth_domain
|
||||||
|
Authorization realm used in `.htpasswd` authorization. Default: `mydomain.com`
|
||||||
|
|
||||||
|
### cgi_interpreter
|
||||||
|
Path to an executable to use as CGI interpreter for __all__ CGI scripts
|
||||||
|
regardless script extension.
|
||||||
|
for an interpreter. Default: not set, Mongoose looks at
|
||||||
|
[shebang line](http://en.wikipedia.org/wiki/Shebang_(Unix\).
|
||||||
|
|
||||||
|
For example, if both PHP and perl CGIs are used, then
|
||||||
|
`#!/path/to/php-cgi.exe` and `#!/path/to/perl.exe` must be first lines of the
|
||||||
|
respective CGI scripts. Note that paths should be either full file paths,
|
||||||
|
or file paths relative to the directory where mongoose server is located.
|
||||||
|
|
||||||
|
If all CGIs use the same interpreter, for example they are all PHP, then
|
||||||
|
`cgi_interpreter` option can be set to the path to `php-cgi.exe` executable and
|
||||||
|
shebang line in the CGI scripts can be omitted.
|
||||||
|
Note that PHP scripts must use `php-cgi.exe` executable, not `php.exe`.
|
||||||
|
|
||||||
|
### cgi_pattern
|
||||||
|
All files that match `cgi_pattern` are treated as CGI files. Default pattern
|
||||||
|
allows CGI files be anywhere. To restrict CGIs to a certain directory,
|
||||||
|
use `/path/to/cgi-bin/**.cgi` as pattern. Note that full file path is
|
||||||
|
matched against the pattern, not the URI. Note: if `MONGOOSE_CGI` environment
|
||||||
|
variable is set, then Mongoose passes it to the CGI script.
|
||||||
|
|
||||||
|
Default: `**.cgi$|**.pl$|**.php$`
|
||||||
|
|
||||||
|
### dav\_auth\_file
|
||||||
|
Authentication file for WebDAV mutation requests: `PUT`, `DELETE`, `MKCOL`.
|
||||||
|
The format of that file is the same as for the `.htpasswd` file
|
||||||
|
used for digest authentication. It can be created and managed by
|
||||||
|
`mongoose -A` command. Default: not set, WebDAV mutations are disallowed.
|
||||||
|
|
||||||
|
### document_root
|
||||||
|
A directory to serve. Default: current working directory.
|
||||||
|
|
||||||
|
### enable\_directory\_listing
|
||||||
|
Enable directory listing, either `yes` or `no`. Default: `yes`.
|
||||||
|
|
||||||
|
### error\_log\_file
|
||||||
|
Path to a file for error logs. Either full path, or relative to the
|
||||||
|
mongoose executable. Default: not set, no errors are logged.
|
||||||
|
|
||||||
|
### extra\_mime\_types
|
||||||
|
Extra mime types to recognize, in form `extension1=type1,extension2=type2,...`.
|
||||||
|
Extension must include dot. Example:
|
||||||
|
`mongoose -extra_mime_types .cpp=plain/text,.java=plain/text`. Default: not set.
|
||||||
|
|
||||||
|
|
||||||
|
### global\_auth\_file
|
||||||
|
Path to a global passwords file, either full path or relative to the mongoose
|
||||||
|
executable. If set, per-directory `.htpasswd` files are ignored,
|
||||||
|
and all requests are authorised against that file. Use `mongoose -A` to
|
||||||
|
manage passwords, or third party utilities like
|
||||||
|
[htpasswd-generator](http://www.askapache.com/online-tools/htpasswd-generator).
|
||||||
|
Default: not set, per-directory `.htpasswd` files are respected.
|
||||||
|
|
||||||
|
### hide\_files\_patterns
|
||||||
|
A pattern for the files to hide. Files that match the pattern will not
|
||||||
|
show up in directory listing and return `404 Not Found` if requested. Pattern
|
||||||
|
must be for a file name only, not including directory name, e.g.
|
||||||
|
`mongoose -hide_files_patterns secret.txt|even_more_secret.txt`. Default:
|
||||||
|
not set.
|
||||||
|
|
||||||
|
### idle\_timeout\_ms
|
||||||
|
Timeout for idle connections. Default: 30000 (30 seconds)
|
||||||
|
|
||||||
|
### index_files
|
||||||
|
Comma-separated list of files to be treated as directory index
|
||||||
|
files. Default: `index.html,index.htm,index.cgi,index.shtml,index.php`
|
||||||
|
|
||||||
|
### ssl_certificate
|
||||||
|
Path to SSL certificate file. The file must be in PEM format,
|
||||||
|
and it must have both private key and certificate, see for example
|
||||||
|
[ssl_cert.pem](https://github.com/cesanta/mongoose/blob/master/build/ssl_cert.pem). If this option is set, then Mongoose serves SSL connections on
|
||||||
|
`listening_port`. Default: not set.
|
||||||
|
|
||||||
|
### listening_port
|
||||||
|
Port to listen on. Port could be prepended by the specific IP address to bind
|
||||||
|
to, e.g. `mongoose -listening_port 127.0.0.1:8080`. Otherwise Mongoose
|
||||||
|
will bind to all addresses. Default: 8080.
|
||||||
|
|
||||||
|
### run\_as\_user
|
||||||
|
Switch to given user credentials after startup. UNIX-only. This option is
|
||||||
|
required when mongoose needs to bind on privileged port on UNIX, e.g.
|
||||||
|
|
||||||
|
$ sudo mongoose -listening_ports 80 -run_as_user nobody
|
||||||
|
|
||||||
|
Default: not set.
|
||||||
|
|
||||||
|
### url\_rewrite\_patterns
|
||||||
|
Comma-separated list of URL rewrites in the form of
|
||||||
|
`uri_pattern=file_or_directory_path`. When Mongoose receives the request,
|
||||||
|
it constructs the file name to show by combining `document_root` and the URI.
|
||||||
|
However, if the rewrite option is used and `uri_pattern` matches the
|
||||||
|
requested URI, then `document_root` is ignored. Insted,
|
||||||
|
`file_or_directory_path` is used, which should be a full path name or
|
||||||
|
a path relative to the web server's current working directory. Note that
|
||||||
|
`uri_pattern`, as all mongoose patterns, is a prefix pattern.
|
||||||
|
|
||||||
|
This makes it possible to serve many directories outside from `document_root`,
|
||||||
|
redirect all requests to scripts, and do other tricky things. For example,
|
||||||
|
to redirect all accesses to `.doc` files to a special script, do:
|
||||||
|
|
||||||
|
mongoose -url_rewrite_patterns **.doc$=/path/to/cgi-bin/handle_doc.cgi
|
||||||
|
|
||||||
|
Or, to imitate user home directories support, do:
|
||||||
|
|
||||||
|
mongoose -url_rewrite_patterns /~joe/=/home/joe/,/~bill=/home/bill/
|
||||||
|
|
||||||
|
Default: not set.
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
# Mongoose SSL guide
|
||||||
|
|
||||||
|
SSL is a protocol that makes web communication secure. To enable SSL
|
||||||
|
in mongoose, 3 steps are required:
|
||||||
|
|
||||||
|
1. Valid certificate file must be created
|
||||||
|
2. `ssl_certificate` options must be set to contain path to the
|
||||||
|
certificate file.
|
||||||
|
3. `listening_ports` option must contain a port number with letter `s`
|
||||||
|
appended to it, which instructs Mongoose to use SSL for all connections
|
||||||
|
made to that port.
|
||||||
|
|
||||||
|
Below is the `mongoose.conf` file snippet for typical SSL setup:
|
||||||
|
|
||||||
|
document_root www_root # Serve files in www_root directory
|
||||||
|
listening_ports 80r,443s # Redirect all HTTP requests to HTTPS
|
||||||
|
ssl_certificate ssl_cert.pem # Location of certificate file
|
||||||
|
|
||||||
|
## How to create SSL certificate file
|
||||||
|
|
||||||
|
SSL certificate file is a text file that must contain at least two
|
||||||
|
sections:
|
||||||
|
|
||||||
|
1. A private key
|
||||||
|
2. A certificate
|
||||||
|
|
||||||
|
Both sections should be chunks of text in PEM format. When PEM file is
|
||||||
|
opened in a text editor, it looks like this:
|
||||||
|
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEAwONaLOP7EdegqjRuQKSDXzvHmFMZfBufjhELhNjo5KsL4ieH
|
||||||
|
hYN0Zii2yTb63jGxKY6gH1R/r9dL8kXaJmcZrfSa3AgywnteJWg=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDBjCCAe4CCQCX05m0b053QzANBgkqhkiG9w0BAQQFADBFMQswCQYDVQQGEwJB
|
||||||
|
SEGI4JSxV56lYg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Two aforementioned sections are clearly seen. Typically, those section
|
||||||
|
are bigger then in the example shown. The text between the `BEGIN` and
|
||||||
|
`END` is the text representation of binary data, a private key and a
|
||||||
|
certificate. Therefore, in order to create a certificate file,
|
||||||
|
|
||||||
|
* private key must be converted to PEM format
|
||||||
|
* certificate must be converted to PEM format
|
||||||
|
* those two should be concatenated into a single file
|
||||||
|
|
||||||
|
If the certificate chain in used, a chain file also needs to be
|
||||||
|
converted into PEM format and appended to the certificate file.
|
||||||
|
|
||||||
|
## How SSL works
|
||||||
|
|
||||||
|
SSL is a protocol that can encrypt communication between two parties. If third
|
||||||
|
party observes all messages passed by, it would be very
|
||||||
|
hard for the third party (though not impossible) to decrypt the communication.
|
||||||
|
|
||||||
|
The idea is based on so-called public key encryption. Communicating parties
|
||||||
|
have two keys: a public key and a private key. A public key is advertised
|
||||||
|
to everybody, and it is contained in a certificate. A private key is kept
|
||||||
|
secret. Security algorithm works in a way that anybody can encrypt
|
||||||
|
a message using public key, and only private key can decrypt it.
|
||||||
|
|
||||||
|
This is why web server needs both private key and certificate: private key
|
||||||
|
is used to decrypt incoming messages, and certificate is used to tell the
|
||||||
|
public key to the other party. When communication starts, parties exchange
|
||||||
|
their public keys, and keep private keys to themselves. Man-in-the-middle
|
||||||
|
who observes the communication is unable to decrypt the messages cause
|
||||||
|
private keys are required for decryption.
|
||||||
|
|
||||||
|
Encryption algorithms are built on top of hard mathematical problem, which
|
||||||
|
makes it very expensive for man-in-the-middle to compute private keys.
|
||||||
|
For example, RSA algorithm is based on a mathematical problem of factorization.
|
||||||
|
It is easy to generate two very large prime numbers `P` and `Q` and make
|
||||||
|
a product `P * Q`. But given a product, it is very hard to recover these
|
||||||
|
two prime numbers - this is called factorization.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Mongoose User Guide
|
||||||
|
|
||||||
|
Mongoose is small and easy to use web server built on top of
|
||||||
|
mongoose library. It is designed with maximum simplicity in mind. For example,
|
||||||
|
to share any directory, just drop mongoose executable file in that directory,
|
||||||
|
double-click it (on UNIX, run it from shell) and launch a browser at
|
||||||
|
[http://localhost:8080](http://localhost:8080) Note that 'localhost' should
|
||||||
|
be changed to a machine's name if a folder is accessed from other computer.
|
||||||
|
|
||||||
|
On Windows and Mac, Mongoose iconifies itself to the system tray when started.
|
||||||
|
Right-click on the icon pops up a menu, where it is possible to stop
|
||||||
|
mongoose, or configure it.
|
||||||
|
|
||||||
|
On UNIX, `mongoose` is a command line utility. Running `mongoose` in
|
||||||
|
terminal, optionally followed by configuration parameters
|
||||||
|
(`mongoose [OPTIONS]`) or configuration file name
|
||||||
|
(`mongoose [config_file_name]`) starts the
|
||||||
|
web server:
|
||||||
|
|
||||||
|
$ mongoose -document_root /var/www # Running mongoose with cmdline options
|
||||||
|
$ mongoose /etc/my_config.txt # Running mongoose with config file
|
||||||
|
$ mongoose # Running with no parameters. This will
|
||||||
|
# serve current directory on port 8080
|
||||||
|
|
||||||
|
Mongoose does not detach from terminal. Pressing `Ctrl-C` keys
|
||||||
|
stops the server.
|
||||||
|
|
||||||
|
When started, mongoose first searches for the configuration file.
|
||||||
|
If configuration file is specified explicitly in the command line, then
|
||||||
|
specified configuration file is used.
|
||||||
|
Otherwise, mongoose would search for file `mongoose.conf` in the same directory
|
||||||
|
where binary is located, and use it. Configuration file can be absent.
|
||||||
|
|
||||||
|
Configuration file is a sequence of lines, each line containing
|
||||||
|
command line argument name and it's value. Empty lines and lines beginning
|
||||||
|
with `#` are ignored. Here is the example of `mongoose.conf` file:
|
||||||
|
|
||||||
|
# This is a comment
|
||||||
|
document_root C:\www
|
||||||
|
listening_port 80
|
||||||
|
ssl_certificate C:\mongoose\ssl_cert.pem
|
||||||
|
|
||||||
|
Command line arguments are highest priority and can override
|
||||||
|
configuration file settings. For example, if `mongoose.conf` has line
|
||||||
|
`document_root /var/www`, and mongoose has been started as
|
||||||
|
`mongoose -document_root /etc`, then `/etc` directory will be used as
|
||||||
|
document root.
|
||||||
|
|
||||||
|
Note that configuration options on the command line must start with `-`,
|
||||||
|
and their names are the same as in the config file. Exampli gratia,
|
||||||
|
the following two setups are equivalent:
|
||||||
|
|
||||||
|
$ mongoose -listening_port 1234 -document_root /var/www
|
||||||
|
|
||||||
|
$ cat > mongoose.conf
|
||||||
|
listening_ports 1234
|
||||||
|
document_root /var/www
|
||||||
|
^D
|
||||||
|
$ mongoose
|
||||||
|
|
||||||
|
Mongoose can also be used to modify `.htpasswd` passwords file:
|
||||||
|
|
||||||
|
$ mongoose -A .htpasswd mydomain.com user_name user_password
|
||||||
|
|
||||||
|
Unlike other web servers, mongoose does not require CGI scripts be located in
|
||||||
|
a special directory. CGI scripts can be anywhere. CGI (and SSI) files are
|
||||||
|
recognized by the file name pattern. Mongoose uses shell-like glob
|
||||||
|
patterns. Pattern match starts at the beginning of the string, so essentially
|
||||||
|
patterns are prefix patterns. Syntax is as follows:
|
||||||
|
|
||||||
|
** Matches everything
|
||||||
|
* Matches everything but slash character, '/'
|
||||||
|
? Matches any character
|
||||||
|
$ Matches the end of the string
|
||||||
|
| Matches if pattern on the left side or the right side matches.
|
||||||
|
|
||||||
|
All other characters in the pattern match themselves. Examples:
|
||||||
|
|
||||||
|
**.cgi$ Any string that ends with .cgi
|
||||||
|
/foo Any string that begins with /foo
|
||||||
|
**a$|**b$ Any string that ends with a or b
|
||||||
|
|
||||||
|
To restrict CGI files only to `/cgi-bin/` directory, use this setting:
|
||||||
|
|
||||||
|
$ mongoose -cgi_pattern /cgi-bin/*.cgi # Emulate /cgi-bin/ restriction
|
||||||
+36
-18
@@ -1,28 +1,46 @@
|
|||||||
CFLAGS= -W -Wall -I.. -pthread -g
|
CFLAGS = -W -Wall -I.. -pthread -g -pipe $(COPT)
|
||||||
|
DLL_FLAGS = -DLUA_COMPAT_ALL -I../build
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
else
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
DLL_FLAGS += -shared
|
||||||
|
|
||||||
|
ifeq ($(UNAME_S),Linux)
|
||||||
|
CFLAGS += -ldl
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
# DLL_FLAGS += -bundle -undefined dynamic_lookup -dynamiclib
|
||||||
|
DLL_FLAGS += -flat_namespace -undefined suppress -dynamiclib
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
all:
|
all:
|
||||||
OS=`uname`; \
|
$(CC) hello.c ../mongoose.c -o hello $(CFLAGS)
|
||||||
test "$$OS" = Linux && LIBS="-ldl" ; \
|
$(CC) qcomm.c ../mongoose.c -o qcomm $(CFLAGS)
|
||||||
$(CC) $(CFLAGS) hello.c ../mongoose.c $$LIBS $(ADD) -o hello;
|
$(CC) post.c ../mongoose.c -o post $(CFLAGS)
|
||||||
$(CC) $(CFLAGS) upload.c ../mongoose.c $$LIBS $(ADD) -o upload;
|
$(CC) multi_threaded.c ../mongoose.c -o multi_threaded $(CFLAGS)
|
||||||
$(CC) $(CFLAGS) post.c ../mongoose.c $$LIBS $(ADD) -o post;
|
|
||||||
$(CC) $(CFLAGS) -DUSE_WEBSOCKET websocket.c ../mongoose.c $$LIBS $(ADD) -o websocket;
|
# $(CC) upload.c ../mongoose.c -o upload $(CFLAGS)
|
||||||
$(CC) $(CFLAGS) chat.c ../mongoose.c $$LIBS $(ADD) -o chat
|
# $(CC) -DUSE_WEBSOCKET websocket.c ../mongoose.c -o $@ $(CFLAGS)
|
||||||
|
# $(CC) chat.c ../mongoose.c -o chat $(CFLAGS)
|
||||||
|
# $(CC) lua_dll.c ../build/lua_5.2.1.c -o $@.so $(CFLAGS) $(DLL_FLAGS)
|
||||||
|
|
||||||
|
|
||||||
MSVC = e:/vc6
|
MSVC = ../../vc6
|
||||||
CL = $(MSVC)/bin/cl
|
CL = $(MSVC)/bin/cl
|
||||||
CLFLAGS = /MD /TC /nologo $(DBG) /W3 /DNO_SSL \
|
CLFLAGS = /MD /TC /nologo $(DBG) /W3 /DNO_SSL \
|
||||||
/I$(MSVC)/include /I.. /Dsnprintf=_snprintf \
|
/I$(MSVC)/include /I.. /Dsnprintf=_snprintf
|
||||||
/link /incremental:no /libpath:$(MSVC)/lib /machine:IX86
|
LFLAGS = /link /incremental:no /libpath:$(MSVC)/lib /machine:IX86
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
$(CL) upload.c ../mongoose.c $(CLFLAGS)
|
$(CL) hello.c ../mongoose.c $(CLFLAGS) $(LFLAGS)
|
||||||
$(CL) hello.c ../mongoose.c $(CLFLAGS)
|
$(CL) post.c ../mongoose.c $(CLFLAGS) $(LFLAGS)
|
||||||
$(CL) upload.c ../mongoose.c $(CLFLAGS)
|
$(CL) multi_threaded.c ../mongoose.c $(CLFLAGS) $(LFLAGS)
|
||||||
$(CL) post.c ../mongoose.c $(CLFLAGS)
|
# $(CL) upload.c ../mongoose.c $(CLFLAGS) $(LFLAGS)
|
||||||
$(CL) post.c ../mongoose.c $(CLFLAGS)
|
# $(CL) /DUSE_WEBSOCKET websocket.c ../mongoose.c $(CLFLAGS) $(LFLAGS)
|
||||||
$(CL) /DUSE_WEBSOCKET websocket.c ../mongoose.c $(CLFLAGS)
|
#$(CL) lua_dll.c $(CLFLAGS) $(DLL_FLAGS) /DLL $(LFLAGS) /SUBSYSTEM:WINDOWS /ENTRY:luaopen_lua_dll /EXPORT:luaopen_lua_dll /out:lua_dll.dll
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf hello hello.exe upload upload.exe post post.exe websocket websocket.exe chat chat.exe *.dSYM *.obj
|
rm -rf hello upload post websocket chat *.exe *.dSYM *.obj .*o
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// This file is part of mongoose web server project,
|
||||||
|
// https://github.com/cesanta/mongoose
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
public class Program {
|
||||||
|
static private int EventHandler(MongooseEvent ev) {
|
||||||
|
if (ev.type != 1) {
|
||||||
|
return 0; // Mark as unhandled
|
||||||
|
}
|
||||||
|
|
||||||
|
MongooseRequestInfo request_info = (MongooseRequestInfo)
|
||||||
|
Marshal.PtrToStructure(ev.request_info, typeof(MongooseRequestInfo));
|
||||||
|
|
||||||
|
if (request_info.uri != "/test") {
|
||||||
|
return 0; // Mark as unhandled
|
||||||
|
}
|
||||||
|
|
||||||
|
Mongoose.write(ev.conn, "HTTP/1.1 200 OK\r\n\r\n");
|
||||||
|
Mongoose.write(ev.conn, "Hello from C#!\n");
|
||||||
|
|
||||||
|
return 1; // Mark as handled
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Main() {
|
||||||
|
Mongoose web_server = new Mongoose(".", "9000",
|
||||||
|
new MongooseEventHandler(EventHandler));
|
||||||
|
|
||||||
|
Console.WriteLine("Mongoose v." + web_server.version_ + " started.");
|
||||||
|
Console.WriteLine("Press enter to exit program.");
|
||||||
|
|
||||||
|
// Serve requests until user presses "enter" on a keyboard
|
||||||
|
Console.ReadLine();
|
||||||
|
web_server.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// This file is part of mongoose web server project,
|
||||||
|
// https://github.com/cesanta/mongoose
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)] public struct MongooseHeader {
|
||||||
|
[MarshalAs(UnmanagedType.LPTStr)] public IntPtr name;
|
||||||
|
[MarshalAs(UnmanagedType.LPTStr)] public IntPtr value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// mongoose.h :: struct mg_request_info
|
||||||
|
[StructLayout(LayoutKind.Sequential)] public struct MongooseRequestInfo {
|
||||||
|
[MarshalAs(UnmanagedType.LPTStr)] public string request_method;
|
||||||
|
[MarshalAs(UnmanagedType.LPTStr)] public string uri;
|
||||||
|
[MarshalAs(UnmanagedType.LPTStr)] public string http_version;
|
||||||
|
[MarshalAs(UnmanagedType.LPTStr)] public string query_string;
|
||||||
|
[MarshalAs(UnmanagedType.LPTStr)] public string remote_user;
|
||||||
|
public int remote_ip;
|
||||||
|
public int remote_port;
|
||||||
|
public int is_ssl;
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray,SizeConst=64)]
|
||||||
|
public MongooseHeader[] http_headers;
|
||||||
|
};
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)] public struct MongooseEvent {
|
||||||
|
public int type;
|
||||||
|
public IntPtr user_data;
|
||||||
|
public IntPtr conn_data;
|
||||||
|
public IntPtr event_param;
|
||||||
|
public IntPtr conn;
|
||||||
|
public IntPtr request_info;
|
||||||
|
};
|
||||||
|
|
||||||
|
public delegate int MongooseEventHandlerN(ref MongooseEvent ev);
|
||||||
|
public delegate int MongooseEventHandler(MongooseEvent ev);
|
||||||
|
|
||||||
|
public class Mongoose {
|
||||||
|
public const string dll_name_ = "mongoose";
|
||||||
|
public string version_ = "??";
|
||||||
|
|
||||||
|
// These are here to store a ref to the callbacks
|
||||||
|
// while they are over in unmanaged code, to prevent garbage collection.
|
||||||
|
private event MongooseEventHandlerN delegates;
|
||||||
|
|
||||||
|
private IntPtr ctx_;
|
||||||
|
|
||||||
|
[DllImport(dll_name_)] private static extern
|
||||||
|
IntPtr mg_start([MarshalAs(UnmanagedType.LPArray,
|
||||||
|
ArraySubType=UnmanagedType.LPTStr)] string[] options,
|
||||||
|
MongooseEventHandlerN callback,
|
||||||
|
IntPtr user_data);
|
||||||
|
[DllImport(dll_name_)] private static extern void mg_stop(IntPtr ctx);
|
||||||
|
[DllImport(dll_name_)] private static extern IntPtr mg_version();
|
||||||
|
[DllImport(dll_name_)] public static extern int mg_write(IntPtr conn,
|
||||||
|
string data, int length);
|
||||||
|
|
||||||
|
public Mongoose(string document_root,
|
||||||
|
string listening_ports,
|
||||||
|
MongooseEventHandler event_handler) {
|
||||||
|
version_ = Marshal.PtrToStringAnsi(mg_version());
|
||||||
|
|
||||||
|
string[] options = {
|
||||||
|
"document_root", document_root,
|
||||||
|
"listening_ports", listening_ports,
|
||||||
|
null
|
||||||
|
};
|
||||||
|
|
||||||
|
MongooseEventHandlerN cb = delegate(ref MongooseEvent ev) {
|
||||||
|
return event_handler(ev);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prevent garbage collection
|
||||||
|
delegates += cb;
|
||||||
|
|
||||||
|
ctx_ = mg_start(options, cb, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int write(IntPtr conn, string data) {
|
||||||
|
return mg_write(conn, data, data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (this.ctx_ != IntPtr.Zero) {
|
||||||
|
mg_stop(this.ctx_);
|
||||||
|
}
|
||||||
|
this.ctx_ = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Mongoose() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-27
@@ -56,9 +56,10 @@ static pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
|
|||||||
// Get session object for the connection. Caller must hold the lock.
|
// Get session object for the connection. Caller must hold the lock.
|
||||||
static struct session *get_session(const struct mg_connection *conn) {
|
static struct session *get_session(const struct mg_connection *conn) {
|
||||||
int i;
|
int i;
|
||||||
|
const char *cookie = mg_get_header(conn, "Cookie");
|
||||||
char session_id[33];
|
char session_id[33];
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
mg_get_cookie(conn, "session", session_id, sizeof(session_id));
|
mg_get_cookie(cookie, "session", session_id, sizeof(session_id));
|
||||||
for (i = 0; i < MAX_SESSIONS; i++) {
|
for (i = 0; i < MAX_SESSIONS; i++) {
|
||||||
if (sessions[i].expire != 0 &&
|
if (sessions[i].expire != 0 &&
|
||||||
sessions[i].expire > now &&
|
sessions[i].expire > now &&
|
||||||
@@ -325,35 +326,30 @@ static void redirect_to_ssl(struct mg_connection *conn,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *event_handler(enum mg_event event,
|
static int event_handler(struct mg_event *event) {
|
||||||
struct mg_connection *conn) {
|
struct mg_request_info *request_info = event->request_info;
|
||||||
const struct mg_request_info *request_info = mg_get_request_info(conn);
|
struct mg_connection *conn = event->conn;
|
||||||
void *processed = "yes";
|
int result = 1;
|
||||||
|
|
||||||
if (event == MG_NEW_REQUEST) {
|
if (event->type != MG_REQUEST_BEGIN) return 0;
|
||||||
if (!request_info->is_ssl) {
|
|
||||||
redirect_to_ssl(conn, request_info);
|
if (!request_info->is_ssl) {
|
||||||
} else if (!is_authorized(conn, request_info)) {
|
redirect_to_ssl(conn, request_info);
|
||||||
redirect_to_login(conn, request_info);
|
} else if (!is_authorized(conn, request_info)) {
|
||||||
} else if (strcmp(request_info->uri, authorize_url) == 0) {
|
redirect_to_login(conn, request_info);
|
||||||
authorize(conn, request_info);
|
} else if (strcmp(request_info->uri, authorize_url) == 0) {
|
||||||
} else if (strcmp(request_info->uri, "/ajax/get_messages") == 0) {
|
authorize(conn, request_info);
|
||||||
ajax_get_messages(conn, request_info);
|
} else if (strcmp(request_info->uri, "/ajax/get_messages") == 0) {
|
||||||
} else if (strcmp(request_info->uri, "/ajax/send_message") == 0) {
|
ajax_get_messages(conn, request_info);
|
||||||
ajax_send_message(conn, request_info);
|
} else if (strcmp(request_info->uri, "/ajax/send_message") == 0) {
|
||||||
} else {
|
ajax_send_message(conn, request_info);
|
||||||
// No suitable handler found, mark as not processed. Mongoose will
|
|
||||||
// try to serve the request.
|
|
||||||
processed = NULL;
|
|
||||||
}
|
|
||||||
} else if (event == MG_EVENT_LOG) {
|
|
||||||
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
|
|
||||||
processed = NULL;
|
|
||||||
} else {
|
} else {
|
||||||
processed = NULL;
|
// No suitable handler found, mark as not processed. Mongoose will
|
||||||
|
// try to serve the request.
|
||||||
|
result = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return processed;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *options[] = {
|
static const char *options[] = {
|
||||||
@@ -372,7 +368,7 @@ int main(void) {
|
|||||||
srand((unsigned) time(0));
|
srand((unsigned) time(0));
|
||||||
|
|
||||||
// Setup and start Mongoose
|
// Setup and start Mongoose
|
||||||
if ((ctx = mg_start(&event_handler, NULL, options)) == NULL) {
|
if ((ctx = mg_start(options, event_handler, NULL)) == NULL) {
|
||||||
printf("%s\n", "Cannot start chat server, fatal exit");
|
printf("%s\n", "Cannot start chat server, fatal exit");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-26
@@ -2,36 +2,30 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "mongoose.h"
|
#include "mongoose.h"
|
||||||
|
|
||||||
static void *callback(enum mg_event event,
|
// This function will be called by mongoose on every new request
|
||||||
struct mg_connection *conn) {
|
static int index_html(struct mg_connection *conn) {
|
||||||
const struct mg_request_info *request_info = mg_get_request_info(conn);
|
mg_printf_data(conn, "Hello! Requested URI is [%s], query string is [%s]",
|
||||||
|
conn->uri,
|
||||||
if (event == MG_NEW_REQUEST) {
|
conn->query_string == NULL ? "(none)" : conn->query_string);
|
||||||
char content[1024];
|
return 0;
|
||||||
int content_length = snprintf(content, sizeof(content),
|
|
||||||
"Hello from mongoose! Remote port: %d",
|
|
||||||
request_info->remote_port);
|
|
||||||
mg_printf(conn,
|
|
||||||
"HTTP/1.1 200 OK\r\n"
|
|
||||||
"Content-Type: text/plain\r\n"
|
|
||||||
"Content-Length: %d\r\n" // Always set Content-Length
|
|
||||||
"\r\n"
|
|
||||||
"%s",
|
|
||||||
content_length, content);
|
|
||||||
// Mark as processed
|
|
||||||
return "";
|
|
||||||
} else {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
struct mg_context *ctx;
|
struct mg_server *server;
|
||||||
const char *options[] = {"listening_ports", "8080", NULL};
|
|
||||||
|
|
||||||
ctx = mg_start(&callback, NULL, options);
|
// Create and configure the server
|
||||||
getchar(); // Wait until user hits "enter"
|
server = mg_create_server(NULL);
|
||||||
mg_stop(ctx);
|
mg_set_option(server, "listening_port", "8080");
|
||||||
|
mg_add_uri_handler(server, "/", index_html);
|
||||||
|
|
||||||
|
// Serve request. Hit Ctrl-C to terminate the program
|
||||||
|
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||||
|
for (;;) {
|
||||||
|
mg_poll_server(server, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup, and free server instance
|
||||||
|
mg_destroy_server(&server);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
HTTP/1.0 200 OK
|
||||||
|
Content-Type: text/plain
|
||||||
|
|
||||||
|
<?
|
||||||
|
-- Directory scanning example
|
||||||
|
|
||||||
|
function scandir(dir)
|
||||||
|
local t = {}
|
||||||
|
for f in io.popen('dir ' .. dir):lines() do table.insert(t, f) end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local tab = scandir('C:\\')
|
||||||
|
for k,v in pairs(tab) do mg.write(v, '\n') end
|
||||||
|
?>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
HTTP/1.0 200 OK
|
||||||
|
Content-Type: text/html
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<p>Prime numbers from 0 to 100, calculated by Lua:</p>
|
||||||
|
<?
|
||||||
|
function is_prime(n)
|
||||||
|
if n <= 0 then return false end
|
||||||
|
if n <= 2 then return true end
|
||||||
|
if (n % 2 == 0) then return false end
|
||||||
|
for i = 3, n / 2, 2 do
|
||||||
|
if (n % i == 0) then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, 100 do
|
||||||
|
if is_prime(i) then mg.write('<span>' .. i .. '</span> ') end
|
||||||
|
end
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<p>Reading POST data from Lua (click submit):</p>
|
||||||
|
<form method="POST" ><input type="text" name="t1"/><input type="submit"></form>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
POST data: [<?
|
||||||
|
local post_data = ''
|
||||||
|
if mg.request_info.request_method == 'POST' then
|
||||||
|
post_data = mg.read()
|
||||||
|
end
|
||||||
|
mg.write(post_data)
|
||||||
|
?>]
|
||||||
|
request method: [<? mg.write(mg.request_info.request_method) ?>]
|
||||||
|
IP/port: [<? mg.write(mg.request_info.remote_ip, ':',
|
||||||
|
mg.request_info.remote_port) ?>]
|
||||||
|
URI: [<? mg.write(mg.request_info.uri) ?>]
|
||||||
|
HTTP version [<? mg.write(mg.request_info.http_version) ?>]
|
||||||
|
HEADERS:
|
||||||
|
<?
|
||||||
|
for name, value in pairs(mg.request_info.http_headers) do
|
||||||
|
mg.write(name, ':', value, '\n')
|
||||||
|
end
|
||||||
|
?>
|
||||||
|
</pre>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include "lua_5.2.1.h"
|
||||||
|
|
||||||
|
static int smile(lua_State *L) {
|
||||||
|
(void) L; // Unused
|
||||||
|
printf("%s\n", ":-)");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LUA_API luaopen_lua_dll(lua_State *L) {
|
||||||
|
static const struct luaL_Reg api[] = {
|
||||||
|
{"smile", smile},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
|
luaL_openlib(L, "lua_dll", api, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
// Start a browser and hit refresh couple of times. The replies will
|
||||||
|
// come from both server instances.
|
||||||
|
static int request_handler(struct mg_connection *conn) {
|
||||||
|
mg_printf(conn, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n"
|
||||||
|
"This is a reply from server instance # %s",
|
||||||
|
(char *) conn->server_param);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *serve(void *server) {
|
||||||
|
for (;;) mg_poll_server((struct mg_server *) server, 1000);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
struct mg_server *server1, *server2;
|
||||||
|
|
||||||
|
server1 = mg_create_server((void *) "1");
|
||||||
|
server2 = mg_create_server((void *) "2");
|
||||||
|
|
||||||
|
mg_add_uri_handler(server1, "/", request_handler);
|
||||||
|
mg_add_uri_handler(server2, "/", request_handler);
|
||||||
|
|
||||||
|
// Make both server1 and server2 listen on the same socket
|
||||||
|
mg_set_option(server1, "listening_port", "8080");
|
||||||
|
mg_set_listening_socket(server2, mg_get_listening_socket(server1));
|
||||||
|
|
||||||
|
// server1 goes to separate thread, server 2 runs in main thread.
|
||||||
|
// IMPORTANT: NEVER LET DIFFERENT THREADS HANDLE THE SAME SERVER.
|
||||||
|
mg_start_thread(serve, server1);
|
||||||
|
serve(server2);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+31
-40
@@ -10,52 +10,43 @@ static const char *html_form =
|
|||||||
"<input type=\"submit\" />"
|
"<input type=\"submit\" />"
|
||||||
"</form></body></html>";
|
"</form></body></html>";
|
||||||
|
|
||||||
static void *callback(enum mg_event event,
|
static int handler(struct mg_connection *conn) {
|
||||||
struct mg_connection *conn) {
|
char var1[500], var2[500];
|
||||||
const struct mg_request_info *ri = mg_get_request_info(conn);
|
|
||||||
|
|
||||||
if (event == MG_NEW_REQUEST) {
|
if (strcmp(conn->uri, "/handle_post_request") == 0) {
|
||||||
if (!strcmp(ri->uri, "/handle_post_request")) {
|
// User has submitted a form, show submitted data and a variable value
|
||||||
// User has submitted a form, show submitted data and a variable value
|
// Parse form data. var1 and var2 are guaranteed to be NUL-terminated
|
||||||
char post_data[1024],
|
mg_get_var(conn, "input_1", var1, sizeof(var1));
|
||||||
input1[sizeof(post_data)], input2[sizeof(post_data)];
|
mg_get_var(conn, "input_2", var2, sizeof(var2));
|
||||||
int post_data_len;
|
|
||||||
|
|
||||||
// Read POST data
|
// Send reply to the client, showing submitted form values.
|
||||||
post_data_len = mg_read(conn, post_data, sizeof(post_data));
|
// POST data is in conn->content, data length is in conn->content_len
|
||||||
|
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
|
||||||
// Parse form data. input1 and input2 are guaranteed to be NUL-terminated
|
"Content-Type: text/plain\r\n\r\n"
|
||||||
mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1));
|
"Submitted data: [%.*s]\n"
|
||||||
mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2));
|
"Submitted data length: %d bytes\n"
|
||||||
|
"input_1: [%s]\n"
|
||||||
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
|
"input_2: [%s]\n",
|
||||||
"Content-Type: text/plain\r\n\r\n"
|
conn->content_len, conn->content, conn->content_len, var1, var2);
|
||||||
"Submitted data: [%.*s]\n"
|
|
||||||
"Submitted data length: %d bytes\n"
|
|
||||||
"input_1: [%s]\n"
|
|
||||||
"input_2: [%s]\n",
|
|
||||||
post_data_len, post_data, post_data_len, input1, input2);
|
|
||||||
} else {
|
|
||||||
// Show HTML form.
|
|
||||||
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
|
|
||||||
"Content-Length: %d\r\n"
|
|
||||||
"Content-Type: text/html\r\n\r\n%s",
|
|
||||||
(int) strlen(html_form), html_form);
|
|
||||||
}
|
|
||||||
// Mark as processed
|
|
||||||
return "";
|
|
||||||
} else {
|
} else {
|
||||||
return NULL;
|
// Show HTML form.
|
||||||
|
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
|
||||||
|
"Content-Length: %d\r\n"
|
||||||
|
"Content-Type: text/html\r\n\r\n%s",
|
||||||
|
(int) strlen(html_form), html_form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
struct mg_context *ctx;
|
struct mg_server *server = mg_create_server(NULL);
|
||||||
const char *options[] = {"listening_ports", "8080", NULL};
|
mg_set_option(server, "listening_port", "8080");
|
||||||
|
mg_add_uri_handler(server, "/", handler);
|
||||||
ctx = mg_start(&callback, NULL, options);
|
printf("Starting on port %s\n", mg_get_option(server, "listening_port"));
|
||||||
getchar(); // Wait until user hits "enter"
|
for (;;) {
|
||||||
mg_stop(ctx);
|
mg_poll_server(server, 1000);
|
||||||
|
}
|
||||||
|
mg_destroy_server(&server);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
static void iterate_callback(struct mg_connection *c, void *param) {
|
||||||
|
if (c->is_websocket) {
|
||||||
|
char buf[20];
|
||||||
|
int len = snprintf(buf, sizeof(buf), "%d", * (int *) param);
|
||||||
|
mg_websocket_write(c, 1, buf, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This handler is called for each incoming websocket frame, one or more
|
||||||
|
// times for connection lifetime.
|
||||||
|
static int handler(struct mg_connection *conn) {
|
||||||
|
static const char oops[] = "HTTP/1.0 200 OK\r\n\r\nwebsocket data expected\n";
|
||||||
|
|
||||||
|
if (!conn->is_websocket) {
|
||||||
|
mg_write(conn, oops, sizeof(oops) - 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_websocket_write(conn, 1, conn->content, conn->content_len);
|
||||||
|
|
||||||
|
return conn->content_len == 4 && !memcmp(conn->content, "exit", 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
struct mg_server *server = mg_create_server(NULL);
|
||||||
|
unsigned int current_timer = 0, last_timer = 0;
|
||||||
|
|
||||||
|
mg_set_option(server, "listening_port", "8080");
|
||||||
|
mg_set_option(server, "document_root", argc > 1 ? argv[1] : ".");
|
||||||
|
mg_add_uri_handler(server, "/ws", handler);
|
||||||
|
|
||||||
|
printf("Started on port %s\n", mg_get_option(server, "listening_port"));
|
||||||
|
for (;;) {
|
||||||
|
current_timer = mg_poll_server(server, 1);
|
||||||
|
if (current_timer - last_timer > 4) {
|
||||||
|
last_timer = current_timer;
|
||||||
|
mg_iterate_over_connections(server, iterate_callback, ¤t_timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_destroy_server(&server);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+22
-29
@@ -3,25 +3,20 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#include <io.h>
|
|
||||||
#define strtoll strtol
|
|
||||||
typedef __int64 int64_t;
|
|
||||||
#else
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif // !_WIN32
|
|
||||||
|
|
||||||
#include "mongoose.h"
|
#include "mongoose.h"
|
||||||
|
|
||||||
static void *callback(enum mg_event event, struct mg_connection *conn) {
|
static int event_handler(struct mg_event *event) {
|
||||||
if (event == MG_NEW_REQUEST) {
|
|
||||||
if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) {
|
if (event->type == MG_REQUEST_BEGIN) {
|
||||||
mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
|
if (!strcmp(event->request_info->uri, "/handle_post_request")) {
|
||||||
mg_upload(conn, "/tmp");
|
char path[200];
|
||||||
|
FILE *fp = mg_upload(event->conn, "/tmp", path, sizeof(path));
|
||||||
|
if (fp != NULL) {
|
||||||
|
fclose(fp);
|
||||||
|
mg_printf(event->conn, "HTTP/1.0 200 OK\r\n\r\nSaved: [%s]", path);
|
||||||
|
} else {
|
||||||
|
mg_printf(event->conn, "%s", "HTTP/1.0 200 OK\r\n\r\nNo files sent");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Show HTML form. Make sure it has enctype="multipart/form-data" attr.
|
// Show HTML form. Make sure it has enctype="multipart/form-data" attr.
|
||||||
static const char *html_form =
|
static const char *html_form =
|
||||||
@@ -32,27 +27,25 @@ static void *callback(enum mg_event event, struct mg_connection *conn) {
|
|||||||
"<input type=\"submit\" value=\"Upload\" />"
|
"<input type=\"submit\" value=\"Upload\" />"
|
||||||
"</form></body></html>";
|
"</form></body></html>";
|
||||||
|
|
||||||
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
|
mg_printf(event->conn, "HTTP/1.0 200 OK\r\n"
|
||||||
"Content-Length: %d\r\n"
|
"Content-Length: %d\r\n"
|
||||||
"Content-Type: text/html\r\n\r\n%s",
|
"Content-Type: text/html\r\n\r\n%s",
|
||||||
(int) strlen(html_form), html_form);
|
(int) strlen(html_form), html_form);
|
||||||
}
|
}
|
||||||
// Mark as processed
|
|
||||||
return "";
|
// Mark request as processed
|
||||||
} else if (event == MG_UPLOAD) {
|
return 1;
|
||||||
mg_printf(conn, "Saved [%s]", mg_get_request_info(conn)->ev_data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
// All other events left unprocessed
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
struct mg_context *ctx;
|
struct mg_context *ctx;
|
||||||
const char *options[] = {"listening_ports", "8080", NULL};
|
const char *options[] = {"listening_ports", "8080", NULL};
|
||||||
|
ctx = mg_start(options, event_handler, NULL);
|
||||||
ctx = mg_start(&callback, NULL, options);
|
|
||||||
getchar(); // Wait until user hits "enter"
|
getchar(); // Wait until user hits "enter"
|
||||||
pause();
|
|
||||||
mg_stop(ctx);
|
mg_stop(ctx);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+40
-48
@@ -3,60 +3,52 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include "mongoose.h"
|
#include "mongoose.h"
|
||||||
|
|
||||||
static void *callback(enum mg_event event, struct mg_connection *conn) {
|
static int event_handler(struct mg_event *event) {
|
||||||
if (event == MG_WEBSOCKET_READY) {
|
|
||||||
unsigned char buf[40];
|
|
||||||
buf[0] = 0x81;
|
|
||||||
buf[1] = snprintf((char *) buf + 2, sizeof(buf) - 2, "%s", "server ready");
|
|
||||||
mg_write(conn, buf, 2 + buf[1]);
|
|
||||||
return ""; // MG_WEBSOCKET_READY return value is ignored
|
|
||||||
} else if (event == MG_WEBSOCKET_MESSAGE) {
|
|
||||||
unsigned char buf[200], reply[200];
|
|
||||||
int n, i, mask_len, xor, msg_len, len;
|
|
||||||
|
|
||||||
// Read message from the client.
|
if (event->type == MG_REQUEST_BEGIN) {
|
||||||
// Accept only small (<126 bytes) messages.
|
const char *version_header = mg_get_header(event->conn,
|
||||||
len = 0;
|
"Sec-WebSocket-Version");
|
||||||
msg_len = mask_len = 0;
|
|
||||||
for (;;) {
|
if (version_header != NULL) {
|
||||||
if ((n = mg_read(conn, buf + len, sizeof(buf) - len)) <= 0) {
|
// Websocket request, process it
|
||||||
return ""; // Read error, close websocket
|
if (strcmp(version_header, "13") != 0) {
|
||||||
}
|
mg_printf(event->conn, "%s", "HTTP/1.1 426 Upgrade Required\r\n\r\n");
|
||||||
len += n;
|
} else {
|
||||||
if (len >= 2) {
|
static const char *server_ready_message = "server ready";
|
||||||
msg_len = buf[1] & 127;
|
char *data;
|
||||||
mask_len = (buf[1] & 128) ? 4 : 0;
|
int bits, len, must_exit = 0;
|
||||||
if (msg_len > 125) {
|
|
||||||
return ""; // Message is too long, close websocket
|
// Handshake, and send initial server message
|
||||||
}
|
mg_websocket_handshake(event->conn);
|
||||||
// If we've buffered the whole message, exit the loop
|
mg_websocket_write(event->conn, WEBSOCKET_OPCODE_TEXT,
|
||||||
if (len >= 2 + mask_len + msg_len) {
|
server_ready_message, strlen(server_ready_message));
|
||||||
break;
|
|
||||||
|
// Read messages sent by client. Echo them back.
|
||||||
|
while (must_exit == 0 &&
|
||||||
|
(len = mg_websocket_read(event->conn, &bits, &data)) > 0) {
|
||||||
|
|
||||||
|
printf("got message: [%.*s]\n", len, data);
|
||||||
|
mg_websocket_write(event->conn, WEBSOCKET_OPCODE_TEXT, data, len);
|
||||||
|
|
||||||
|
// If the message is "exit", close the connection, exit the loop
|
||||||
|
if (memcmp(data, "exit", 4) == 0) {
|
||||||
|
mg_websocket_write(event->conn,
|
||||||
|
WEBSOCKET_OPCODE_CONNECTION_CLOSE, "", 0);
|
||||||
|
must_exit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's our responsibility to free allocated message
|
||||||
|
free(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare frame
|
|
||||||
reply[0] = 0x81; // text, FIN set
|
|
||||||
reply[1] = msg_len;
|
|
||||||
|
|
||||||
// Copy message from request to reply, applying the mask if required.
|
|
||||||
for (i = 0; i < msg_len; i++) {
|
|
||||||
xor = mask_len == 0 ? 0 : buf[2 + (i % 4)];
|
|
||||||
reply[i + 2] = buf[i + 2 + mask_len] ^ xor;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Echo the message back to the client
|
|
||||||
mg_write(conn, reply, 2 + msg_len);
|
|
||||||
|
|
||||||
// Return non-NULL means stoping websocket conversation.
|
|
||||||
// Close the conversation if client has sent us "exit" string.
|
|
||||||
return memcmp(reply + 2, "exit", 4) == 0 ? "" : NULL;
|
|
||||||
} else {
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
@@ -67,7 +59,7 @@ int main(void) {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx = mg_start(&callback, NULL, options);
|
ctx = mg_start(options, &event_handler, NULL);
|
||||||
getchar(); // Wait until user hits "enter"
|
getchar(); // Wait until user hits "enter"
|
||||||
mg_stop(ctx);
|
mg_stop(ctx);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>WebSocket Test</title>
|
||||||
|
<script language="javascript" type="text/javascript">
|
||||||
|
|
||||||
|
var out = function(message) {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = message;
|
||||||
|
document.getElementById('output').appendChild(div);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
var url = 'ws://' + location.host + '/ws';
|
||||||
|
var num_messages = 0;
|
||||||
|
|
||||||
|
websocket = new WebSocket(url);
|
||||||
|
websocket.onopen = function(ev) {
|
||||||
|
out('CONNECTED');
|
||||||
|
var msg = 'Не всё подчиняется разуму. Но всё подчиняется упорству. ';
|
||||||
|
out('SENT: ' + msg);
|
||||||
|
websocket.send(msg);
|
||||||
|
};
|
||||||
|
websocket.onclose = function(ev) {
|
||||||
|
out('DISCONNECTED');
|
||||||
|
};
|
||||||
|
websocket.onmessage = function(ev) {
|
||||||
|
if (!ev.data) return; // No data, this is a PING message, ignore it
|
||||||
|
out('<span style="color: blue;">RESPONSE: ' + ev.data + ' </span>');
|
||||||
|
num_messages++;
|
||||||
|
if (num_messages > 100) {
|
||||||
|
websocket.send('exit');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
websocket.onerror = function(ev) {
|
||||||
|
out('<span style="color: red; ">ERROR: </span> ' + ev.data);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style> div {font: small Verdana; } </style>
|
||||||
|
<h2>Qualcomm WebSocket Test</h2>
|
||||||
|
|
||||||
|
<div id="output"></div>
|
||||||
|
</html>
|
||||||
@@ -1,592 +0,0 @@
|
|||||||
// Copyright (c) 2004-2011 Sergey Lyubka
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005
|
|
||||||
#else
|
|
||||||
#define _XOPEN_SOURCE 600 // For PATH_MAX on linux
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
|
|
||||||
#include "mongoose.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#include <winsvc.h>
|
|
||||||
#define PATH_MAX MAX_PATH
|
|
||||||
#define S_ISDIR(x) ((x) & _S_IFDIR)
|
|
||||||
#define DIRSEP '\\'
|
|
||||||
#define snprintf _snprintf
|
|
||||||
#define vsnprintf _vsnprintf
|
|
||||||
#define sleep(x) Sleep((x) * 1000)
|
|
||||||
#define WINCDECL __cdecl
|
|
||||||
#else
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#define DIRSEP '/'
|
|
||||||
#define WINCDECL
|
|
||||||
#endif // _WIN32
|
|
||||||
|
|
||||||
#define MAX_OPTIONS 40
|
|
||||||
#define MAX_CONF_FILE_LINE_SIZE (8 * 1024)
|
|
||||||
|
|
||||||
static int exit_flag;
|
|
||||||
static char server_name[40]; // Set by init_server_name()
|
|
||||||
static char config_file[PATH_MAX]; // Set by process_command_line_arguments()
|
|
||||||
static struct mg_context *ctx; // Set by start_mongoose()
|
|
||||||
|
|
||||||
#if !defined(CONFIG_FILE)
|
|
||||||
#define CONFIG_FILE "mongoose.conf"
|
|
||||||
#endif /* !CONFIG_FILE */
|
|
||||||
|
|
||||||
static void WINCDECL signal_handler(int sig_num) {
|
|
||||||
exit_flag = sig_num;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void die(const char *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
char msg[200];
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
MessageBox(NULL, msg, "Error", MB_OK);
|
|
||||||
#else
|
|
||||||
fprintf(stderr, "%s\n", msg);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void show_usage_and_exit(void) {
|
|
||||||
const char **names;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
fprintf(stderr, "Mongoose version %s (c) Sergey Lyubka, built %s\n",
|
|
||||||
mg_version(), __DATE__);
|
|
||||||
fprintf(stderr, "Usage:\n");
|
|
||||||
fprintf(stderr, " mongoose -A <htpasswd_file> <realm> <user> <passwd>\n");
|
|
||||||
fprintf(stderr, " mongoose <config_file>\n");
|
|
||||||
fprintf(stderr, " mongoose [-option value ...]\n");
|
|
||||||
fprintf(stderr, "\nOPTIONS:\n");
|
|
||||||
|
|
||||||
names = mg_get_valid_option_names();
|
|
||||||
for (i = 0; names[i] != NULL; i += 3) {
|
|
||||||
fprintf(stderr, " -%s %s (default: \"%s\")\n",
|
|
||||||
names[i], names[i + 1], names[i + 2] == NULL ? "" : names[i + 2]);
|
|
||||||
}
|
|
||||||
fprintf(stderr, "\nSee http://code.google.com/p/mongoose/wiki/MongooseManual"
|
|
||||||
" for more details.\n");
|
|
||||||
fprintf(stderr, "Example:\n mongoose -s cert.pem -p 80,443s -d no\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void verify_document_root(const char *root) {
|
|
||||||
const char *p, *path;
|
|
||||||
char buf[PATH_MAX];
|
|
||||||
struct stat st;
|
|
||||||
|
|
||||||
path = root;
|
|
||||||
if ((p = strchr(root, ',')) != NULL && (size_t) (p - root) < sizeof(buf)) {
|
|
||||||
memcpy(buf, root, p - root);
|
|
||||||
buf[p - root] = '\0';
|
|
||||||
path = buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
|
|
||||||
die("Invalid root directory: [%s]: %s", root, strerror(errno));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *sdup(const char *str) {
|
|
||||||
char *p;
|
|
||||||
if ((p = (char *) malloc(strlen(str) + 1)) != NULL) {
|
|
||||||
strcpy(p, str);
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void set_option(char **options, const char *name, const char *value) {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (!strcmp(name, "document_root") || !(strcmp(name, "r"))) {
|
|
||||||
verify_document_root(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_OPTIONS - 3; i++) {
|
|
||||||
if (options[i] == NULL) {
|
|
||||||
options[i] = sdup(name);
|
|
||||||
options[i + 1] = sdup(value);
|
|
||||||
options[i + 2] = NULL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == MAX_OPTIONS - 3) {
|
|
||||||
die("%s", "Too many options specified");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void process_command_line_arguments(char *argv[], char **options) {
|
|
||||||
char line[MAX_CONF_FILE_LINE_SIZE], opt[sizeof(line)], val[sizeof(line)], *p;
|
|
||||||
FILE *fp = NULL;
|
|
||||||
size_t i, cmd_line_opts_start = 1, line_no = 0;
|
|
||||||
|
|
||||||
options[0] = NULL;
|
|
||||||
|
|
||||||
// Should we use a config file ?
|
|
||||||
if (argv[1] != NULL && argv[1][0] != '-') {
|
|
||||||
snprintf(config_file, sizeof(config_file), "%s", argv[1]);
|
|
||||||
cmd_line_opts_start = 2;
|
|
||||||
} else if ((p = strrchr(argv[0], DIRSEP)) == NULL) {
|
|
||||||
// No command line flags specified. Look where binary lives
|
|
||||||
snprintf(config_file, sizeof(config_file), "%s", CONFIG_FILE);
|
|
||||||
} else {
|
|
||||||
snprintf(config_file, sizeof(config_file), "%.*s%c%s",
|
|
||||||
(int) (p - argv[0]), argv[0], DIRSEP, CONFIG_FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
fp = fopen(config_file, "r");
|
|
||||||
|
|
||||||
// If config file was set in command line and open failed, die
|
|
||||||
if (cmd_line_opts_start == 2 && fp == NULL) {
|
|
||||||
die("Cannot open config file %s: %s", config_file, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load config file settings first
|
|
||||||
if (fp != NULL) {
|
|
||||||
fprintf(stderr, "Loading config file %s\n", config_file);
|
|
||||||
|
|
||||||
// Loop over the lines in config file
|
|
||||||
while (fgets(line, sizeof(line), fp) != NULL) {
|
|
||||||
|
|
||||||
line_no++;
|
|
||||||
|
|
||||||
// Ignore empty lines and comments
|
|
||||||
for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++;
|
|
||||||
if (line[i] == '#' || line[i] == '\0')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (sscanf(line, "%s %[^\r\n#]", opt, val) != 2) {
|
|
||||||
die("%s: line %d is invalid", config_file, (int) line_no);
|
|
||||||
}
|
|
||||||
set_option(options, opt, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
(void) fclose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle command line flags. They override config file and default settings.
|
|
||||||
for (i = cmd_line_opts_start; argv[i] != NULL; i += 2) {
|
|
||||||
if (argv[i][0] != '-' || argv[i + 1] == NULL) {
|
|
||||||
show_usage_and_exit();
|
|
||||||
}
|
|
||||||
set_option(options, &argv[i][1], argv[i + 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void init_server_name(void) {
|
|
||||||
snprintf(server_name, sizeof(server_name), "Mongoose web server v. %s",
|
|
||||||
mg_version());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *mongoose_callback(enum mg_event ev, struct mg_connection *conn) {
|
|
||||||
if (ev == MG_EVENT_LOG) {
|
|
||||||
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returning NULL marks request as not handled, signalling mongoose to
|
|
||||||
// proceed with handling it.
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void start_mongoose(int argc, char *argv[]) {
|
|
||||||
char *options[MAX_OPTIONS];
|
|
||||||
int i;
|
|
||||||
|
|
||||||
// Edit passwords file if -A option is specified
|
|
||||||
if (argc > 1 && !strcmp(argv[1], "-A")) {
|
|
||||||
if (argc != 6) {
|
|
||||||
show_usage_and_exit();
|
|
||||||
}
|
|
||||||
exit(mg_modify_passwords_file(argv[2], argv[3], argv[4], argv[5]) ?
|
|
||||||
EXIT_SUCCESS : EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show usage if -h or --help options are specified
|
|
||||||
if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
|
|
||||||
show_usage_and_exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update config based on command line arguments */
|
|
||||||
process_command_line_arguments(argv, options);
|
|
||||||
|
|
||||||
/* Setup signal handler: quit on Ctrl-C */
|
|
||||||
signal(SIGTERM, signal_handler);
|
|
||||||
signal(SIGINT, signal_handler);
|
|
||||||
|
|
||||||
/* Start Mongoose */
|
|
||||||
ctx = mg_start(&mongoose_callback, NULL, (const char **) options);
|
|
||||||
for (i = 0; options[i] != NULL; i++) {
|
|
||||||
free(options[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx == NULL) {
|
|
||||||
die("%s", "Failed to start Mongoose.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
static SERVICE_STATUS ss;
|
|
||||||
static SERVICE_STATUS_HANDLE hStatus;
|
|
||||||
static const char *service_magic_argument = "--";
|
|
||||||
|
|
||||||
static void WINAPI ControlHandler(DWORD code) {
|
|
||||||
if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
|
|
||||||
ss.dwWin32ExitCode = 0;
|
|
||||||
ss.dwCurrentState = SERVICE_STOPPED;
|
|
||||||
}
|
|
||||||
SetServiceStatus(hStatus, &ss);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void WINAPI ServiceMain(void) {
|
|
||||||
ss.dwServiceType = SERVICE_WIN32;
|
|
||||||
ss.dwCurrentState = SERVICE_RUNNING;
|
|
||||||
ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
|
|
||||||
|
|
||||||
hStatus = RegisterServiceCtrlHandler(server_name, ControlHandler);
|
|
||||||
SetServiceStatus(hStatus, &ss);
|
|
||||||
|
|
||||||
while (ss.dwCurrentState == SERVICE_RUNNING) {
|
|
||||||
Sleep(1000);
|
|
||||||
}
|
|
||||||
mg_stop(ctx);
|
|
||||||
|
|
||||||
ss.dwCurrentState = SERVICE_STOPPED;
|
|
||||||
ss.dwWin32ExitCode = (DWORD) -1;
|
|
||||||
SetServiceStatus(hStatus, &ss);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ID_TRAYICON 100
|
|
||||||
#define ID_QUIT 101
|
|
||||||
#define ID_EDIT_CONFIG 102
|
|
||||||
#define ID_SEPARATOR 103
|
|
||||||
#define ID_INSTALL_SERVICE 104
|
|
||||||
#define ID_REMOVE_SERVICE 105
|
|
||||||
#define ID_ICON 200
|
|
||||||
static NOTIFYICONDATA TrayIcon;
|
|
||||||
|
|
||||||
static void edit_config_file(void) {
|
|
||||||
const char **names, *value;
|
|
||||||
FILE *fp;
|
|
||||||
int i;
|
|
||||||
char cmd[200];
|
|
||||||
|
|
||||||
// Create config file if it is not present yet
|
|
||||||
if ((fp = fopen(config_file, "r")) != NULL) {
|
|
||||||
fclose(fp);
|
|
||||||
} else if ((fp = fopen(config_file, "a+")) != NULL) {
|
|
||||||
fprintf(fp,
|
|
||||||
"# Mongoose web server configuration file.\n"
|
|
||||||
"# Lines starting with '#' and empty lines are ignored.\n"
|
|
||||||
"# For detailed description of every option, visit\n"
|
|
||||||
"# http://code.google.com/p/mongoose/wiki/MongooseManual\n\n");
|
|
||||||
names = mg_get_valid_option_names();
|
|
||||||
for (i = 0; names[i] != NULL; i += 3) {
|
|
||||||
value = mg_get_option(ctx, names[i]);
|
|
||||||
fprintf(fp, "# %s %s\n", names[i + 1], *value ? value : "<value>");
|
|
||||||
}
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof(cmd), "notepad.exe %s", config_file);
|
|
||||||
WinExec(cmd, SW_SHOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void show_error(void) {
|
|
||||||
char buf[256];
|
|
||||||
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
||||||
NULL, GetLastError(),
|
|
||||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
||||||
buf, sizeof(buf), NULL);
|
|
||||||
MessageBox(NULL, buf, "Error", MB_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int manage_service(int action) {
|
|
||||||
static const char *service_name = "Mongoose";
|
|
||||||
SC_HANDLE hSCM = NULL, hService = NULL;
|
|
||||||
SERVICE_DESCRIPTION descr = {server_name};
|
|
||||||
char path[PATH_MAX + 20]; // Path to executable plus magic argument
|
|
||||||
int success = 1;
|
|
||||||
|
|
||||||
if ((hSCM = OpenSCManager(NULL, NULL, action == ID_INSTALL_SERVICE ?
|
|
||||||
GENERIC_WRITE : GENERIC_READ)) == NULL) {
|
|
||||||
success = 0;
|
|
||||||
show_error();
|
|
||||||
} else if (action == ID_INSTALL_SERVICE) {
|
|
||||||
GetModuleFileName(NULL, path, sizeof(path));
|
|
||||||
strncat(path, " ", sizeof(path));
|
|
||||||
strncat(path, service_magic_argument, sizeof(path));
|
|
||||||
hService = CreateService(hSCM, service_name, service_name,
|
|
||||||
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
|
|
||||||
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
|
|
||||||
path, NULL, NULL, NULL, NULL, NULL);
|
|
||||||
if (hService) {
|
|
||||||
ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &descr);
|
|
||||||
} else {
|
|
||||||
show_error();
|
|
||||||
}
|
|
||||||
} else if (action == ID_REMOVE_SERVICE) {
|
|
||||||
if ((hService = OpenService(hSCM, service_name, DELETE)) == NULL ||
|
|
||||||
!DeleteService(hService)) {
|
|
||||||
show_error();
|
|
||||||
}
|
|
||||||
} else if ((hService = OpenService(hSCM, service_name,
|
|
||||||
SERVICE_QUERY_STATUS)) == NULL) {
|
|
||||||
success = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseServiceHandle(hService);
|
|
||||||
CloseServiceHandle(hSCM);
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
|
|
||||||
LPARAM lParam) {
|
|
||||||
static SERVICE_TABLE_ENTRY service_table[] = {
|
|
||||||
{server_name, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
|
|
||||||
{NULL, NULL}
|
|
||||||
};
|
|
||||||
int service_installed;
|
|
||||||
char buf[200], *service_argv[] = {__argv[0], NULL};
|
|
||||||
POINT pt;
|
|
||||||
HMENU hMenu;
|
|
||||||
|
|
||||||
switch (msg) {
|
|
||||||
case WM_CREATE:
|
|
||||||
if (__argv[1] != NULL &&
|
|
||||||
!strcmp(__argv[1], service_magic_argument)) {
|
|
||||||
start_mongoose(1, service_argv);
|
|
||||||
StartServiceCtrlDispatcher(service_table);
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
} else {
|
|
||||||
start_mongoose(__argc, __argv);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WM_COMMAND:
|
|
||||||
switch (LOWORD(wParam)) {
|
|
||||||
case ID_QUIT:
|
|
||||||
mg_stop(ctx);
|
|
||||||
Shell_NotifyIcon(NIM_DELETE, &TrayIcon);
|
|
||||||
PostQuitMessage(0);
|
|
||||||
break;
|
|
||||||
case ID_EDIT_CONFIG:
|
|
||||||
edit_config_file();
|
|
||||||
break;
|
|
||||||
case ID_INSTALL_SERVICE:
|
|
||||||
case ID_REMOVE_SERVICE:
|
|
||||||
manage_service(LOWORD(wParam));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WM_USER:
|
|
||||||
switch (lParam) {
|
|
||||||
case WM_RBUTTONUP:
|
|
||||||
case WM_LBUTTONUP:
|
|
||||||
case WM_LBUTTONDBLCLK:
|
|
||||||
hMenu = CreatePopupMenu();
|
|
||||||
AppendMenu(hMenu, MF_STRING | MF_GRAYED, ID_SEPARATOR, server_name);
|
|
||||||
AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, "");
|
|
||||||
service_installed = manage_service(0);
|
|
||||||
snprintf(buf, sizeof(buf), "NT service: %s installed",
|
|
||||||
service_installed ? "" : "not");
|
|
||||||
AppendMenu(hMenu, MF_STRING | MF_GRAYED, ID_SEPARATOR, buf);
|
|
||||||
AppendMenu(hMenu, MF_STRING | (service_installed ? MF_GRAYED : 0),
|
|
||||||
ID_INSTALL_SERVICE, "Install service");
|
|
||||||
AppendMenu(hMenu, MF_STRING | (!service_installed ? MF_GRAYED : 0),
|
|
||||||
ID_REMOVE_SERVICE, "Deinstall service");
|
|
||||||
AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, "");
|
|
||||||
AppendMenu(hMenu, MF_STRING, ID_EDIT_CONFIG, "Edit config file");
|
|
||||||
AppendMenu(hMenu, MF_STRING, ID_QUIT, "Exit");
|
|
||||||
GetCursorPos(&pt);
|
|
||||||
SetForegroundWindow(hWnd);
|
|
||||||
TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL);
|
|
||||||
PostMessage(hWnd, WM_NULL, 0, 0);
|
|
||||||
DestroyMenu(hMenu);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WM_CLOSE:
|
|
||||||
mg_stop(ctx);
|
|
||||||
Shell_NotifyIcon(NIM_DELETE, &TrayIcon);
|
|
||||||
PostQuitMessage(0);
|
|
||||||
return 0; // We've just sent our own quit message, with proper hwnd.
|
|
||||||
}
|
|
||||||
|
|
||||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) {
|
|
||||||
WNDCLASS cls;
|
|
||||||
HWND hWnd;
|
|
||||||
MSG msg;
|
|
||||||
|
|
||||||
init_server_name();
|
|
||||||
memset(&cls, 0, sizeof(cls));
|
|
||||||
cls.lpfnWndProc = (WNDPROC) WindowProc;
|
|
||||||
cls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
|
||||||
cls.lpszClassName = server_name;
|
|
||||||
|
|
||||||
RegisterClass(&cls);
|
|
||||||
hWnd = CreateWindow(cls.lpszClassName, server_name, WS_OVERLAPPEDWINDOW,
|
|
||||||
0, 0, 0, 0, NULL, NULL, NULL, NULL);
|
|
||||||
ShowWindow(hWnd, SW_HIDE);
|
|
||||||
|
|
||||||
TrayIcon.cbSize = sizeof(TrayIcon);
|
|
||||||
TrayIcon.uID = ID_TRAYICON;
|
|
||||||
TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
|
||||||
TrayIcon.hIcon = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_ICON),
|
|
||||||
IMAGE_ICON, 16, 16, 0);
|
|
||||||
TrayIcon.hWnd = hWnd;
|
|
||||||
snprintf(TrayIcon.szTip, sizeof(TrayIcon.szTip), "%s", server_name);
|
|
||||||
TrayIcon.uCallbackMessage = WM_USER;
|
|
||||||
Shell_NotifyIcon(NIM_ADD, &TrayIcon);
|
|
||||||
|
|
||||||
while (GetMessage(&msg, hWnd, 0, 0) > 0) {
|
|
||||||
TranslateMessage(&msg);
|
|
||||||
DispatchMessage(&msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the WM_QUIT value.
|
|
||||||
return msg.wParam;
|
|
||||||
}
|
|
||||||
#elif defined(USE_COCOA)
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
@interface Mongoose : NSObject<NSApplicationDelegate>
|
|
||||||
- (void) openBrowser;
|
|
||||||
- (void) shutDown;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation Mongoose
|
|
||||||
- (void) openBrowser {
|
|
||||||
[[NSWorkspace sharedWorkspace]
|
|
||||||
openURL:[NSURL URLWithString:
|
|
||||||
[NSString stringWithUTF8String:"http://www.yahoo.com"]]];
|
|
||||||
}
|
|
||||||
- (void) editConfig {
|
|
||||||
[[NSWorkspace sharedWorkspace]
|
|
||||||
openFile:@"mongoose.conf" withApplication:@"TextEdit"];
|
|
||||||
}
|
|
||||||
- (void)shutDown{
|
|
||||||
[NSApp terminate:nil];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
init_server_name();
|
|
||||||
start_mongoose(argc, argv);
|
|
||||||
|
|
||||||
[NSAutoreleasePool new];
|
|
||||||
[NSApplication sharedApplication];
|
|
||||||
|
|
||||||
// Add delegate to process menu item actions
|
|
||||||
Mongoose *myDelegate = [[Mongoose alloc] autorelease];
|
|
||||||
[NSApp setDelegate: myDelegate];
|
|
||||||
|
|
||||||
// Run this app as agent
|
|
||||||
ProcessSerialNumber psn = { 0, kCurrentProcess };
|
|
||||||
TransformProcessType(&psn, kProcessTransformToBackgroundApplication);
|
|
||||||
SetFrontProcess(&psn);
|
|
||||||
|
|
||||||
// Add status bar menu
|
|
||||||
id menu = [[NSMenu new] autorelease];
|
|
||||||
|
|
||||||
// Add version menu item
|
|
||||||
[menu addItem:[[[NSMenuItem alloc]
|
|
||||||
//initWithTitle:[NSString stringWithFormat:@"%s", server_name]
|
|
||||||
initWithTitle:[NSString stringWithUTF8String:server_name]
|
|
||||||
action:@selector(noexist) keyEquivalent:@""] autorelease]];
|
|
||||||
|
|
||||||
// Add configuration menu item
|
|
||||||
[menu addItem:[[[NSMenuItem alloc]
|
|
||||||
initWithTitle:@"Edit configuration"
|
|
||||||
action:@selector(editConfig) keyEquivalent:@""] autorelease]];
|
|
||||||
|
|
||||||
// Add connect menu item
|
|
||||||
[menu addItem:[[[NSMenuItem alloc]
|
|
||||||
initWithTitle:@"Open web root in a browser"
|
|
||||||
action:@selector(openBrowser) keyEquivalent:@""] autorelease]];
|
|
||||||
|
|
||||||
// Separator
|
|
||||||
[menu addItem:[NSMenuItem separatorItem]];
|
|
||||||
|
|
||||||
// Add quit menu item
|
|
||||||
[menu addItem:[[[NSMenuItem alloc]
|
|
||||||
initWithTitle:@"Quit"
|
|
||||||
action:@selector(shutDown) keyEquivalent:@"q"] autorelease]];
|
|
||||||
|
|
||||||
// Attach menu to the status bar
|
|
||||||
id item = [[[NSStatusBar systemStatusBar]
|
|
||||||
statusItemWithLength:NSVariableStatusItemLength] retain];
|
|
||||||
[item setHighlightMode:YES];
|
|
||||||
[item setImage:[NSImage imageNamed:@"mongoose_22x22.png"]];
|
|
||||||
[item setMenu:menu];
|
|
||||||
|
|
||||||
// Run the app
|
|
||||||
[NSApp activateIgnoringOtherApps:YES];
|
|
||||||
[NSApp run];
|
|
||||||
|
|
||||||
mg_stop(ctx);
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
init_server_name();
|
|
||||||
start_mongoose(argc, argv);
|
|
||||||
printf("%s started on port(s) %s with web root [%s]\n",
|
|
||||||
server_name, mg_get_option(ctx, "listening_ports"),
|
|
||||||
mg_get_option(ctx, "document_root"));
|
|
||||||
while (exit_flag == 0) {
|
|
||||||
sleep(1);
|
|
||||||
}
|
|
||||||
printf("Exiting on signal %d, waiting for all threads to finish...",
|
|
||||||
exit_flag);
|
|
||||||
fflush(stdout);
|
|
||||||
mg_stop(ctx);
|
|
||||||
printf("%s", " done.\n");
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
#endif /* _WIN32 */
|
|
||||||
-184
@@ -1,184 +0,0 @@
|
|||||||
.\" Process this file with
|
|
||||||
.\" groff -man -Tascii mongoose.1
|
|
||||||
.\" $Id: mongoose.1,v 1.12 2008/11/29 15:32:42 drozd Exp $
|
|
||||||
.Dd Sep 23, 2012
|
|
||||||
.Dt mongoose 1
|
|
||||||
.Sh NAME
|
|
||||||
.Nm mongoose
|
|
||||||
.Nd lightweight web server
|
|
||||||
.Sh SYNOPSIS
|
|
||||||
.Nm
|
|
||||||
.Op Ar config_file
|
|
||||||
.Op Ar OPTIONS
|
|
||||||
.Nm
|
|
||||||
.Fl A Ar htpasswd_file domain_name user_name password
|
|
||||||
.Sh DESCRIPTION
|
|
||||||
.Nm
|
|
||||||
is small, fast and easy to use web server with CGI, SSL, MD5 authorization,
|
|
||||||
and basic SSI support.
|
|
||||||
.Pp
|
|
||||||
.Nm
|
|
||||||
does not detach from terminal, and uses current working directory
|
|
||||||
as the web root, unless
|
|
||||||
.Fl r
|
|
||||||
option is specified.
|
|
||||||
It is possible to specify multiple ports to listen on. For example, to make
|
|
||||||
mongoose listen on HTTP port 80 and HTTPS port 443, one should start it as:
|
|
||||||
.Nm
|
|
||||||
.Fl s Ar cert.pem Fl p Ar 80,443s
|
|
||||||
.Pp
|
|
||||||
Unlike other web servers,
|
|
||||||
.Nm
|
|
||||||
does not require CGI scripts be put in a special directory. CGI scripts can
|
|
||||||
be anywhere. CGI (and SSI) files are recognized by the file name pattern.
|
|
||||||
.Nm
|
|
||||||
uses shell-like glob patterns with the following syntax:
|
|
||||||
.Bl -tag -compact -width indent
|
|
||||||
.It **
|
|
||||||
Matches everything
|
|
||||||
.It *
|
|
||||||
Matches everything but slash character, '/'
|
|
||||||
.It ?
|
|
||||||
Matches any character
|
|
||||||
.It $
|
|
||||||
Matches the end of the string
|
|
||||||
.It |
|
|
||||||
Matches if pattern on the left side or the right side matches. Pattern on the
|
|
||||||
left side is matched first
|
|
||||||
.El
|
|
||||||
All other characters in the pattern match themselves.
|
|
||||||
.Pp
|
|
||||||
If no arguments are given,
|
|
||||||
.Nm
|
|
||||||
searches for a configuration file called "mongoose.conf" in the same directory
|
|
||||||
where mongoose binary is located. Alternatively, a file name could be
|
|
||||||
specified in the command line. Format of the configuration file is the same
|
|
||||||
as for the command line options except that each option must be specified
|
|
||||||
on a separate line, leading dashes for option names must be omitted.
|
|
||||||
Lines beginning with '#' and empty lines are ignored.
|
|
||||||
.Pp
|
|
||||||
.Sh OPTIONS
|
|
||||||
.Bl -tag -width indent
|
|
||||||
.It Fl A Ar htpasswd_file domain_name user_name password
|
|
||||||
Add/edit user's password in the passwords file. Deleting users can be done
|
|
||||||
with any text editor. Functionality is similar to Apache's
|
|
||||||
.Ic htdigest
|
|
||||||
utility.
|
|
||||||
.It Fl C Ar cgi_pattern
|
|
||||||
All files that fully match cgi_pattern are treated as CGI.
|
|
||||||
Default pattern allows CGI files be
|
|
||||||
anywhere. To restrict CGIs to certain directory, use e.g. "-C /cgi-bin/**.cgi".
|
|
||||||
Default: "**.cgi$|**.pl$|**.php$"
|
|
||||||
.It Fl E Ar cgi_environment
|
|
||||||
Extra environment variables to be passed to the CGI script in addition to
|
|
||||||
standard ones. The list must be comma-separated list of X=Y pairs, like this:
|
|
||||||
"VARIABLE1=VALUE1,VARIABLE2=VALUE2". Default: ""
|
|
||||||
.It Fl G Ar put_delete_passwords_file
|
|
||||||
PUT and DELETE passwords file. This must be specified if PUT or
|
|
||||||
DELETE methods are used. Default: ""
|
|
||||||
.It Fl I Ar cgi_interpreter
|
|
||||||
Use
|
|
||||||
.Ar cgi_interpreter
|
|
||||||
as a CGI interpreter for all CGI scripts regardless script extension.
|
|
||||||
Mongoose decides which interpreter to use by looking at
|
|
||||||
the first line of a CGI script. Default: "".
|
|
||||||
.It Fl P Ar protect_uri
|
|
||||||
Comma separated list of URI=PATH pairs, specifying that given URIs
|
|
||||||
must be protected with respected password files. Default: ""
|
|
||||||
.It Fl R Ar authentication_domain
|
|
||||||
Authorization realm. Default: "mydomain.com"
|
|
||||||
.It Fl S Ar ssi_pattern
|
|
||||||
All files that fully match ssi_pattern are treated as SSI.
|
|
||||||
Unknown SSI directives are silently ignored. Currently, two SSI directives
|
|
||||||
are supported, "include" and "exec". Default: "**.shtml$|**.shtm$"
|
|
||||||
.It Fl T Ar throttle
|
|
||||||
Limit download speed for clients.
|
|
||||||
.Ar throttle
|
|
||||||
is a comma-separated list of key=value pairs, where
|
|
||||||
key could be a '*' character (limit for all connections), a subnet in form
|
|
||||||
x.x.x.x/mask (limit for a given subnet, for example 10.0.0.0/8), or an
|
|
||||||
URI prefix pattern (limit for the set of URIs, for example /foo/**). The value
|
|
||||||
is a floating-point number of bytes per second, optionally followed by a
|
|
||||||
`k' or `m' character, meaning kilobytes and megabytes respectively. A limit
|
|
||||||
of 0 means unlimited rate. The last matching rule wins. For example,
|
|
||||||
"*=1k,10.0.0.0/8" means limit everybody to 1 kilobyte per second, but give
|
|
||||||
people from 10/8 subnet unlimited speed. Default: ""
|
|
||||||
.It Fl a Ar access_log_file
|
|
||||||
Access log file. Default: "", no logging is done.
|
|
||||||
.It Fl d Ar enable_directory_listing
|
|
||||||
Enable/disable directory listing. Default: "yes"
|
|
||||||
.It Fl e Ar error_log_file
|
|
||||||
Error log file. Default: "", no errors are logged.
|
|
||||||
.It Fl g Ar global_passwords_file
|
|
||||||
Location of a global passwords file. If set, per-directory .htpasswd files are
|
|
||||||
ignored, and all requests must be authorised against that file. Default: ""
|
|
||||||
.It Fl i Ar index_files
|
|
||||||
Comma-separated list of files to be treated as directory index files.
|
|
||||||
Default: "index.html,index.htm,index.cgi"
|
|
||||||
.It Fl l Ar access_control_list
|
|
||||||
Specify access control list (ACL). ACL is a comma separated list
|
|
||||||
of IP subnets, each subnet is prepended by '-' or '+' sign. Plus means allow,
|
|
||||||
minus means deny. If subnet mask is
|
|
||||||
omitted, like "-1.2.3.4", then it means single IP address. Mask may vary
|
|
||||||
from 0 to 32 inclusive. On each request, full list is traversed, and
|
|
||||||
last match wins. Default setting is to allow all. For example, to allow only
|
|
||||||
192.168/16 subnet to connect, run "mongoose -0.0.0.0/0,+192.168/16".
|
|
||||||
Default: ""
|
|
||||||
.It Fl m Ar extra_mime_types
|
|
||||||
Extra mime types to recognize, in form
|
|
||||||
"extension1=type1,extension2=type2,...". Extension must include dot.
|
|
||||||
Example: "mongoose -m .cpp=plain/text,.java=plain/text". Default: ""
|
|
||||||
.It Fl p Ar listening_ports
|
|
||||||
Comma-separated list of ports to listen on. If the port is SSL, a letter 's'
|
|
||||||
must be appeneded, for example, "-p 80,443s" will open port 80 and port 443,
|
|
||||||
and connections on port 443 will be SSL-ed. It is possible to specify an
|
|
||||||
IP address to bind to. In this case, an IP address and a colon must be
|
|
||||||
prepended to the port number. For example, to bind to a loopback interface
|
|
||||||
on port 80 and to all interfaces on HTTPS port 443, use
|
|
||||||
"mongoose -p 127.0.0.1:80,443s". Default: "8080"
|
|
||||||
.It Fl r Ar document_root
|
|
||||||
Location of the WWW root directory. Default: "."
|
|
||||||
.It Fl s Ar ssl_certificate
|
|
||||||
Location of SSL certificate file. Default: ""
|
|
||||||
.It Fl t Ar num_threads
|
|
||||||
Number of worker threads to start. Default: "10"
|
|
||||||
.It Fl u Ar run_as_user
|
|
||||||
Switch to given user's credentials after startup. Default: ""
|
|
||||||
.It Fl w Ar url_rewrite_patterns
|
|
||||||
Comma-separated list of URL rewrites in the form of
|
|
||||||
"pattern=substitution,..." If the "pattern" matches some prefix
|
|
||||||
of the requested URL, then matched prefix gets substituted with "substitution".
|
|
||||||
For example, "-w /config=/etc,**.doc|**.rtf=/path/to/cgi-bin/handle_doc.cgi"
|
|
||||||
will serve all URLs that start with "/config" from the "/etc" directory, and
|
|
||||||
call handle_doc.cgi script for .doc and .rtf file requests. If some pattern
|
|
||||||
matches, no further matching/substitution is performed
|
|
||||||
(first matching pattern wins). Use full paths in substitutions. Default: ""
|
|
||||||
.It Fl x Ar hide_files_patterns
|
|
||||||
A prefix pattern for the files to hide. Files that match the pattern will not
|
|
||||||
show up in directory listing and return 404 Not Found if requested. Default: ""
|
|
||||||
.El
|
|
||||||
.Pp
|
|
||||||
.Sh EMBEDDING
|
|
||||||
.Nm
|
|
||||||
was designed to be embeddable into C/C++ applications. Since the
|
|
||||||
source code is contained in single C file, it is fairly easy to embed it
|
|
||||||
and follow the updates. Please refer to http://code.google.com/p/mongoose
|
|
||||||
for details.
|
|
||||||
.Pp
|
|
||||||
.Sh EXAMPLES
|
|
||||||
.Bl -tag -width indent
|
|
||||||
.It Nm Fl r Ar /var/www Fl s Ar /etc/cert.pem Fl p Ar 8080,8043s
|
|
||||||
Start serving files from /var/www. Listen on port 8080 for HTTP, and 8043
|
|
||||||
for HTTPS connections. Use /etc/cert.pem as SSL certificate file.
|
|
||||||
.It Nm Fl l Ar -0.0.0.0/0,+10.0.0.0/8,+1.2.3.4
|
|
||||||
Deny connections from everywhere, allow only IP address 1.2.3.4 and
|
|
||||||
all IP addresses from 10.0.0.0/8 subnet to connect.
|
|
||||||
.It Nm Fl w Ar **=/usr/bin/script.cgi
|
|
||||||
Invoke /usr/bin/script.cgi for every incoming request, regardless of the URL.
|
|
||||||
.El
|
|
||||||
.Pp
|
|
||||||
.Sh COPYRIGHT
|
|
||||||
.Nm
|
|
||||||
is licensed under the terms of the MIT license.
|
|
||||||
.Sh AUTHOR
|
|
||||||
.An Sergey Lyubka Aq valenok@gmail.com .
|
|
||||||
+2996
-4434
File diff suppressed because it is too large.
Load diff
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user