Compare commits
966 commits
fix_readme
...
master
Author | SHA1 | Date | |
---|---|---|---|
7f6db4fe8d | |||
![]() |
debb7aa043 | ||
![]() |
ca045d4a8e | ||
![]() |
4ce725903d | ||
![]() |
a2943ca41d | ||
![]() |
04134742f9 | ||
![]() |
2e5110e731 | ||
![]() |
afc9c0ac26 | ||
![]() |
5b23b8c99f | ||
![]() |
b66e8e1898 | ||
![]() |
0bb3646256 | ||
![]() |
c60b2413a4 | ||
![]() |
58195fbc1e | ||
![]() |
c2abf59c68 | ||
![]() |
4b24369d64 | ||
![]() |
872e8d7e9d | ||
![]() |
5da9ce2f06 | ||
![]() |
b93c057472 | ||
![]() |
5a38d88fdd | ||
![]() |
a9f74f2dbe | ||
![]() |
219a367a4c | ||
![]() |
93a5b586a3 | ||
![]() |
040b408029 | ||
![]() |
16ef37ef5d | ||
![]() |
e6bd6296bd | ||
![]() |
4a9c23e9ec | ||
![]() |
c6233ce403 | ||
![]() |
7849c5a8ad | ||
![]() |
414f114b8f | ||
![]() |
855f7e8712 | ||
![]() |
9f31e94e09 | ||
![]() |
855453d1ae | ||
![]() |
d86641ed3a | ||
![]() |
41c15511eb | ||
![]() |
d766c99861 | ||
![]() |
ba45f7cf9f | ||
![]() |
19a320a578 | ||
![]() |
61e58885f4 | ||
![]() |
3562e441e3 | ||
![]() |
003bd7dcee | ||
![]() |
75bbb3b2c0 | ||
![]() |
8ccc64679f | ||
![]() |
6c09952065 | ||
![]() |
09f3947b4c | ||
![]() |
941e93ea33 | ||
![]() |
c9da1ffa2e | ||
![]() |
ad15082563 | ||
![]() |
2d313bdc02 | ||
![]() |
3526fde154 | ||
![]() |
bd1dd7efd4 | ||
![]() |
1690581cd2 | ||
![]() |
3c360d5192 | ||
![]() |
8a4881a142 | ||
![]() |
6f11c1361a | ||
![]() |
3b0066cb3f | ||
![]() |
faa1526b39 | ||
![]() |
632a0acc99 | ||
![]() |
dbd9262ac5 | ||
![]() |
d5f960f14b | ||
![]() |
5106e9141f | ||
![]() |
61cc61817e | ||
![]() |
3077b7776e | ||
![]() |
81d5899c89 | ||
![]() |
a15007269e | ||
![]() |
ffa5898afe | ||
![]() |
6f2a470ee2 | ||
![]() |
e2a926de2f | ||
![]() |
05d74e45dc | ||
![]() |
1b41322c28 | ||
![]() |
9b4fa24909 | ||
![]() |
7262e14dc1 | ||
![]() |
6a89c7e931 | ||
![]() |
ff9a24de1c | ||
![]() |
e4d8acc85a | ||
![]() |
a637d8f41f | ||
![]() |
8f4f692945 | ||
![]() |
0b629d4037 | ||
![]() |
0e222ab084 | ||
![]() |
5939d3a961 | ||
![]() |
55d9037c32 | ||
![]() |
bd23a401f6 | ||
![]() |
47da96664f | ||
![]() |
04830cceef | ||
![]() |
eb7233ae97 | ||
![]() |
34ef9743d2 | ||
![]() |
12a9592426 | ||
![]() |
ede9ad2b13 | ||
![]() |
abc2a5cd2e | ||
![]() |
d6394b6dba | ||
![]() |
c70f6692f8 | ||
![]() |
3ff720ff66 | ||
![]() |
1725332d47 | ||
![]() |
be4efcfe58 | ||
![]() |
31e26a4fab | ||
![]() |
1e762dcde4 | ||
![]() |
581b86efd0 | ||
![]() |
449eec9b54 | ||
![]() |
af718ff5c2 | ||
![]() |
0be9e98aae | ||
![]() |
568aae9c2d | ||
![]() |
02638f6758 | ||
![]() |
37053b3a9c | ||
![]() |
393b38aed9 | ||
![]() |
156363f3de | ||
![]() |
af7f943e05 | ||
![]() |
2ce39fe26c | ||
![]() |
a4f88d543e | ||
![]() |
ce2e74e5e3 | ||
![]() |
978605b39e | ||
![]() |
989ff82ca0 | ||
![]() |
5d1d756106 | ||
![]() |
348887ea53 | ||
![]() |
3ec32b1762 | ||
![]() |
49769a7c24 | ||
![]() |
decbbc506b | ||
![]() |
c28f436098 | ||
![]() |
a369b42910 | ||
![]() |
d12b3df953 | ||
![]() |
3844988b6c | ||
![]() |
a6a68785f7 | ||
![]() |
de12c860db | ||
![]() |
ed92b6dfe7 | ||
![]() |
8f16f92cac | ||
![]() |
3cf570ec52 | ||
![]() |
3198446d3c | ||
![]() |
fbc1ff6e7b | ||
![]() |
b778cc54ca | ||
![]() |
b2f8bc5638 | ||
![]() |
54236bd437 | ||
![]() |
a7def3ce44 | ||
![]() |
aff8d237f8 | ||
![]() |
2790d5b28c | ||
![]() |
5468812946 | ||
![]() |
57e020a826 | ||
![]() |
e304d7bba7 | ||
![]() |
a1210f73af | ||
![]() |
f13db3e722 | ||
![]() |
d1aa92011b | ||
![]() |
6384eb79e0 | ||
![]() |
98218bd6b7 | ||
![]() |
991f6ec644 | ||
![]() |
aeb3397c8f | ||
![]() |
ca6242a1d3 | ||
![]() |
0b9686224d | ||
![]() |
ff151d0710 | ||
![]() |
6018c0519a | ||
![]() |
f58241c4f2 | ||
![]() |
ba9d639792 | ||
![]() |
b4b3da06a1 | ||
![]() |
4adf590e71 | ||
![]() |
8a74348839 | ||
![]() |
47bbb044ad | ||
![]() |
2b9fff73f9 | ||
![]() |
d7d8c081aa | ||
![]() |
19242db55b | ||
![]() |
54c22623ab | ||
![]() |
00081a2b1f | ||
![]() |
6cae994750 | ||
![]() |
a7326fc8b5 | ||
![]() |
36e04f33f5 | ||
![]() |
89212d6668 | ||
![]() |
4ae029c3a1 | ||
![]() |
d1dda25c6d | ||
![]() |
34822f190d | ||
![]() |
30195eb79d | ||
![]() |
7faa71637f | ||
![]() |
00ae90978b | ||
![]() |
422f56c9dc | ||
![]() |
db64bfcefa | ||
![]() |
73e12e7b93 | ||
![]() |
7a0dead82a | ||
![]() |
d58353f98a | ||
![]() |
7aadd14398 | ||
![]() |
92f0200204 | ||
![]() |
a4d23648f7 | ||
![]() |
311e3a585d | ||
![]() |
629e356bb2 | ||
![]() |
73ff23e288 | ||
![]() |
49036d571f | ||
![]() |
ec50ebac12 | ||
![]() |
f84338ac1c | ||
![]() |
c35661c45c | ||
![]() |
9287f7d1b3 | ||
f0c800ada6 | |||
![]() |
2e874dea50 | ||
![]() |
ed27be42c9 | ||
![]() |
e769af41db | ||
![]() |
102d40e6b6 | ||
![]() |
e0572d3bef | ||
![]() |
4198c20920 | ||
![]() |
c1b5c8cabd | ||
![]() |
3c02868408 | ||
![]() |
34842d7492 | ||
![]() |
6be573f2c9 | ||
![]() |
2f3b4c8f96 | ||
![]() |
3a09c3b7d6 | ||
![]() |
c53a76f0ef | ||
![]() |
4219a3281d | ||
![]() |
b9dd61bb6f | ||
![]() |
205ccedf6e | ||
![]() |
4578036b53 | ||
![]() |
b6e0c89750 | ||
![]() |
688b819112 | ||
![]() |
21d83b44ea | ||
![]() |
19cc7acb24 | ||
![]() |
2f4dfa4ea7 | ||
![]() |
753f1d642c | ||
![]() |
cb35969fce | ||
![]() |
04054b4072 | ||
![]() |
60f4569bb8 | ||
![]() |
2495afa604 | ||
![]() |
faf260ccfa | ||
![]() |
1ada132932 | ||
![]() |
be8d791b5e | ||
![]() |
8bb3dffc86 | ||
![]() |
16ee4b6f0d | ||
![]() |
6d0c96e06a | ||
![]() |
ab89246467 | ||
![]() |
4ca114fbcf | ||
![]() |
73dda1f765 | ||
![]() |
1789d0a483 | ||
![]() |
96b91cc7ec | ||
![]() |
2e1dcc542e | ||
![]() |
3db9eca06a | ||
![]() |
d26d7b0e04 | ||
![]() |
a5488f9c27 | ||
![]() |
0aed7a06d5 | ||
![]() |
91593d8886 | ||
![]() |
390ba34400 | ||
![]() |
88d6c1a63b | ||
![]() |
3e0006c206 | ||
![]() |
3d6a1e11c2 | ||
![]() |
0db9b9f346 | ||
![]() |
dc4934d2bc | ||
![]() |
297c0313fa | ||
![]() |
a5f2d8b688 | ||
![]() |
7832b236a4 | ||
![]() |
052a685435 | ||
![]() |
b8034bb1e5 | ||
![]() |
c7c4ae0aa4 | ||
![]() |
936bf08a0f | ||
![]() |
40d37e2105 | ||
![]() |
4241af99cb | ||
![]() |
4fd91fa60d | ||
![]() |
186ba832b3 | ||
![]() |
238766be6b | ||
![]() |
f2c6e48eb8 | ||
![]() |
f9f5a57607 | ||
![]() |
baa0f74bf2 | ||
![]() |
1d393aba9e | ||
![]() |
2e11efa84b | ||
![]() |
21faff3deb | ||
![]() |
0b3e9e62cb | ||
![]() |
807313b157 | ||
![]() |
31c5130802 | ||
![]() |
5e25dc07c8 | ||
![]() |
c8b20910ec | ||
![]() |
388f6c97fb | ||
![]() |
73f5e58682 | ||
![]() |
9395eb62ce | ||
![]() |
066f4156d6 | ||
![]() |
cde0371a83 | ||
![]() |
af9ddec2f7 | ||
![]() |
a17992b638 | ||
![]() |
fdb25fcd6e | ||
![]() |
7df395695b | ||
![]() |
ce37ee9624 | ||
![]() |
be1f0a3a92 | ||
![]() |
e96b69887f | ||
![]() |
b5ebd599b2 | ||
![]() |
24e77a04ab | ||
![]() |
ad945976f7 | ||
![]() |
c2208640dd | ||
![]() |
71bb400805 | ||
![]() |
76985f256d | ||
![]() |
716aaeeb8b | ||
![]() |
cc806bb77e | ||
![]() |
bcce73201f | ||
![]() |
1bc025cfa7 | ||
![]() |
51c96d8c2a | ||
![]() |
e680fb9193 | ||
![]() |
3f55e5e964 | ||
![]() |
b837f93dcf | ||
![]() |
9e562fcf1b | ||
![]() |
8b7704f1b3 | ||
![]() |
a7bf178606 | ||
![]() |
1e22719f5e | ||
![]() |
c001a15973 | ||
![]() |
d79d8b792d | ||
![]() |
b3c2427cfd | ||
![]() |
a0c4a0e253 | ||
![]() |
9b129c137a | ||
![]() |
58a26ebf45 | ||
![]() |
714f66f5b7 | ||
![]() |
85f0c283dd | ||
![]() |
a14be2664c | ||
![]() |
dd0dfaecb7 | ||
![]() |
f3ad9540fa | ||
![]() |
63627e7e86 | ||
![]() |
acdf0c7066 | ||
![]() |
be9fb919d1 | ||
![]() |
0a3859f623 | ||
![]() |
8694d1bdbc | ||
![]() |
5bc0343a85 | ||
![]() |
5767095208 | ||
![]() |
e0bed8da33 | ||
![]() |
ad99232f8c | ||
![]() |
023ac896e9 | ||
![]() |
ed4acef481 | ||
![]() |
fdddf5af51 | ||
![]() |
b1d4d3bb9e | ||
![]() |
ba8fb14e85 | ||
![]() |
69c88d9f0c | ||
![]() |
ee94621b40 | ||
![]() |
7a77c0b572 | ||
![]() |
6e436dca39 | ||
![]() |
fded366ea0 | ||
![]() |
f2a130f9bf | ||
![]() |
39ec84202d | ||
![]() |
62a86ac9c9 | ||
![]() |
0fdca9f782 | ||
![]() |
53bed78950 | ||
![]() |
2926553c41 | ||
![]() |
c541e2d7e3 | ||
![]() |
5466b9d253 | ||
![]() |
b3160f9fd0 | ||
![]() |
d2cef1515e | ||
![]() |
e7cc1ffc1e | ||
![]() |
86ab060cd9 | ||
![]() |
fbe66065ff | ||
![]() |
b46c401245 | ||
![]() |
3b93e344f6 | ||
![]() |
202e8a7428 | ||
![]() |
0733610c0e | ||
![]() |
63477ded0c | ||
![]() |
abe0c5e57e | ||
![]() |
4be9a5234d | ||
![]() |
14f994f52a | ||
![]() |
3f8082fecf | ||
![]() |
73557509bd | ||
![]() |
443b31d012 | ||
![]() |
8b0dd360aa | ||
![]() |
4cfe81dfba | ||
![]() |
4d79dc553e | ||
![]() |
53ec6b1789 | ||
![]() |
6e8f360e58 | ||
![]() |
65135f2bc4 | ||
![]() |
8187c76943 | ||
![]() |
939bb7f6e9 | ||
![]() |
fb71b790cd | ||
![]() |
f94bef42c4 | ||
![]() |
b0ff623526 | ||
![]() |
668a6fbf29 | ||
![]() |
e468e15a2b | ||
![]() |
0c984014f0 | ||
![]() |
a496700b3a | ||
![]() |
c15714f35a | ||
![]() |
b8199ba4ab | ||
![]() |
2083674aa0 | ||
![]() |
9c7e84d346 | ||
![]() |
6ecbe3e93f | ||
![]() |
9c04a107c9 | ||
![]() |
316960b87d | ||
![]() |
9de43fc9d7 | ||
![]() |
723b3f16fa | ||
![]() |
64dd359192 | ||
![]() |
72502e86f5 | ||
![]() |
d11bee7fc5 | ||
![]() |
01020b60f6 | ||
![]() |
d6761e87b5 | ||
![]() |
d8e051dd97 | ||
![]() |
2f863cf59a | ||
![]() |
cd02a15669 | ||
![]() |
e0212b8c85 | ||
![]() |
16f3e4cacf | ||
![]() |
e35d91a860 | ||
![]() |
46646f9ebf | ||
![]() |
396b58ce70 | ||
![]() |
a5ec7212ff | ||
![]() |
6fe25fd1fd | ||
![]() |
b49b0fbafa | ||
![]() |
1172a89bb3 | ||
![]() |
90f513b209 | ||
![]() |
fa9ebb394f | ||
![]() |
5162d876fd | ||
![]() |
d0abf9bb5e | ||
![]() |
387e50bd8f | ||
![]() |
3ffd0eb84a | ||
![]() |
95fff47a91 | ||
![]() |
bbf93693d8 | ||
![]() |
7acf76cd3d | ||
![]() |
f7a1c7ed47 | ||
![]() |
4606ad4377 | ||
![]() |
f32d25051c | ||
![]() |
0fa6821214 | ||
![]() |
2671b7469a | ||
![]() |
722cc4a366 | ||
![]() |
02f880d9b6 | ||
![]() |
efef6bf9a0 | ||
![]() |
fc0e52da9e | ||
![]() |
5f73048d58 | ||
![]() |
5ab523ec3f | ||
![]() |
2b304a933f | ||
![]() |
675c349cc5 | ||
![]() |
fee9f006f6 | ||
![]() |
0aa382f6fb | ||
![]() |
cd6a70672b | ||
![]() |
ae0d8d74f9 | ||
![]() |
906006e21e | ||
![]() |
8860eddeb2 | ||
![]() |
81c221ed35 | ||
![]() |
e8439168dc | ||
![]() |
f25eefdd41 | ||
![]() |
8fdff1053c | ||
![]() |
9ec76d4b72 | ||
![]() |
e7bf0aa1dc | ||
![]() |
6ff571f854 | ||
![]() |
ca44730b36 | ||
![]() |
014481e912 | ||
![]() |
4e75ae3b4e | ||
![]() |
7f92ee802c | ||
![]() |
7a2fc442b6 | ||
![]() |
920daf10a1 | ||
![]() |
4bd5b96735 | ||
![]() |
36f87b068f | ||
![]() |
f1bdb0fefe | ||
![]() |
528adbde7f | ||
![]() |
3f17a8e252 | ||
![]() |
6cd27e8186 | ||
![]() |
c55fd27d75 | ||
![]() |
70c60d81ab | ||
![]() |
1ec8ba893a | ||
![]() |
d13c1471c1 | ||
![]() |
a0d21c3abd | ||
![]() |
d5466c97ae | ||
![]() |
602d884028 | ||
![]() |
ed7c63e07d | ||
![]() |
57513b7d62 | ||
![]() |
7201057d28 | ||
![]() |
011f915dd0 | ||
![]() |
5780783c0c | ||
![]() |
326c91b966 | ||
![]() |
33403a474e | ||
![]() |
4507da486d | ||
![]() |
c16c6c8536 | ||
![]() |
2dc7ddecfd | ||
![]() |
1dca916bec | ||
![]() |
4889566d5d | ||
![]() |
eda447e74a | ||
![]() |
261f23622c | ||
![]() |
d3c33936b5 | ||
![]() |
d6fba75433 | ||
![]() |
3358c53445 | ||
![]() |
488e05a6b4 | ||
![]() |
9de9d0dacd | ||
![]() |
5c27334b39 | ||
![]() |
264786db4d | ||
![]() |
3a42fb545a | ||
![]() |
92b0d8a614 | ||
![]() |
82e883f7ad | ||
![]() |
7c2d5964f7 | ||
![]() |
e7c1c757cd | ||
![]() |
9b03cb9828 | ||
![]() |
7ad94de897 | ||
![]() |
cbd11f749e | ||
![]() |
81a5a125f4 | ||
![]() |
3dcbf6abc1 | ||
![]() |
34b8161e04 | ||
![]() |
fa34a82ca1 | ||
![]() |
7a9970f90d | ||
![]() |
0463895b1b | ||
![]() |
8ef14c0e0a | ||
![]() |
d6c5bdb0f9 | ||
![]() |
6954daabd2 | ||
![]() |
d6be6776a8 | ||
![]() |
c64e4baccc | ||
![]() |
54b856fea4 | ||
![]() |
b9c095db47 | ||
![]() |
97164368dd | ||
![]() |
ecaed5ec94 | ||
![]() |
bf31c1bc87 | ||
![]() |
2700d1c0c3 | ||
![]() |
fb8be0507e | ||
![]() |
b6d229d726 | ||
![]() |
03e628b9a6 | ||
![]() |
5e203f78e2 | ||
![]() |
0a4da2c71f | ||
![]() |
677d6c1516 | ||
![]() |
f530de84ac | ||
![]() |
5afc56770d | ||
![]() |
b293f52672 | ||
![]() |
b24aaeb822 | ||
![]() |
4010694d04 | ||
![]() |
4e3b0ce973 | ||
![]() |
584f7109a0 | ||
![]() |
11d8efef88 | ||
![]() |
ad45efa434 | ||
![]() |
43bfd951fa | ||
![]() |
26019a2586 | ||
![]() |
3db2c65157 | ||
![]() |
6cca3fbb12 | ||
![]() |
f6585285cb | ||
![]() |
f4767497d3 | ||
![]() |
a709cbb2f5 | ||
![]() |
6a81b08dc2 | ||
![]() |
624b23df5e | ||
![]() |
b6e2ad6b50 | ||
![]() |
7731a3e575 | ||
![]() |
aef3dc4ad7 | ||
![]() |
5500643f3c | ||
![]() |
ad40037c80 | ||
![]() |
c25b290b20 | ||
![]() |
8b02454634 | ||
![]() |
4eab21976c | ||
![]() |
8554bd43bd | ||
![]() |
5474221a0c | ||
![]() |
d098ba47cb | ||
![]() |
8ea8d08623 | ||
![]() |
f1fd882d57 | ||
![]() |
cae57ad98d | ||
![]() |
4d5ac9c910 | ||
![]() |
d5dc5dff13 | ||
![]() |
2f72f67a65 | ||
![]() |
952fcdede1 | ||
![]() |
4664f71ed3 | ||
![]() |
72e0e2a516 | ||
![]() |
67729c954f | ||
![]() |
ff32d01742 | ||
![]() |
8087959acf | ||
![]() |
f14f7237dc | ||
![]() |
3f41a9de5b | ||
![]() |
734f10a19b | ||
![]() |
12aeba73c0 | ||
![]() |
f79534e0a4 | ||
![]() |
24208879ce | ||
![]() |
fe3f60da92 | ||
![]() |
119bdaadc2 | ||
![]() |
8dd32ae7c1 | ||
![]() |
2f216280f8 | ||
![]() |
a2757c74b1 | ||
![]() |
16146cdad8 | ||
![]() |
516f388585 | ||
![]() |
5dd57a9430 | ||
![]() |
09490aeb93 | ||
![]() |
1536c5beb6 | ||
![]() |
5bf2ce193b | ||
![]() |
2110ce9727 | ||
![]() |
81bf43b34f | ||
![]() |
42e8b189dc | ||
![]() |
748a2210bd | ||
![]() |
d02028bf25 | ||
![]() |
5c3a3c5e95 | ||
![]() |
912c42f291 | ||
![]() |
5f947cdcd9 | ||
![]() |
1759e32c7b | ||
![]() |
c6496b2510 | ||
![]() |
c673125ce0 | ||
![]() |
451cee5d47 | ||
![]() |
19f61e59b0 | ||
![]() |
980614035f | ||
![]() |
4714cf02f4 | ||
![]() |
7e2c3851bf | ||
![]() |
b21c20b5ff | ||
![]() |
cec2fd55a8 | ||
![]() |
2f0bc93d46 | ||
![]() |
30cdb3ac8f | ||
![]() |
f3e9c7347e | ||
![]() |
57f2007105 | ||
![]() |
868b9f2ecb | ||
![]() |
a1ab201436 | ||
![]() |
144594dd43 | ||
![]() |
1042b34127 | ||
![]() |
cc3c6fb047 | ||
![]() |
ddd9bfaaac | ||
![]() |
44182452c8 | ||
![]() |
3c6a8c1a24 | ||
![]() |
b46e4a866d | ||
![]() |
5b95081000 | ||
![]() |
5e799a45cc | ||
![]() |
f5efa74cb7 | ||
![]() |
22d6f39e7f | ||
![]() |
a5e73a1a4b | ||
![]() |
6e7b79b263 | ||
![]() |
0a183545b2 | ||
![]() |
e01a551a17 | ||
![]() |
d459a6fadc | ||
![]() |
37098ae2a2 | ||
![]() |
efcf496b45 | ||
![]() |
da77a6007d | ||
![]() |
c5376b141a | ||
![]() |
150233d61f | ||
![]() |
fd12fc2781 | ||
![]() |
8d21c350fc | ||
![]() |
020770566e | ||
![]() |
09998e3ad8 | ||
![]() |
e8009155ba | ||
![]() |
513580d1bd | ||
![]() |
2e15d0ad57 | ||
![]() |
d5ff4bf1bc | ||
![]() |
a19ff9b24d | ||
![]() |
45dead99e0 | ||
![]() |
f29fbdf78a | ||
![]() |
9694ebefe8 | ||
![]() |
1c428c3154 | ||
![]() |
bbcd24167f | ||
![]() |
02ff145f50 | ||
![]() |
7d5b1531ef | ||
![]() |
1a3f17700e | ||
![]() |
f632e449de | ||
![]() |
7fe02e3af5 | ||
![]() |
eefb13a0a7 | ||
![]() |
cd0c696a0d | ||
![]() |
d526c50c22 | ||
![]() |
e2cf3e1cc0 | ||
![]() |
476329f3f8 | ||
![]() |
c6fd360160 | ||
![]() |
f107e4bb85 | ||
![]() |
de9f794b27 | ||
![]() |
1a13885afd | ||
![]() |
1e5a9ad2dc | ||
![]() |
5c8906e931 | ||
![]() |
c3c2d6d953 | ||
![]() |
8007cc6829 | ||
![]() |
7bc87ab662 | ||
![]() |
cea07d6f1f | ||
![]() |
0f9904fedf | ||
![]() |
599cad9436 | ||
![]() |
e32c0ba29b | ||
![]() |
5960158958 | ||
![]() |
04935e28b0 | ||
![]() |
c793944f17 | ||
![]() |
9a720bbb50 | ||
![]() |
4f5ebec3ef | ||
![]() |
43befcba91 | ||
![]() |
4a1c6297e8 | ||
![]() |
f66a12abb2 | ||
![]() |
7d22c29722 | ||
![]() |
fb5f33bb2f | ||
![]() |
494418abac | ||
![]() |
fe16003644 | ||
![]() |
442ae80b87 | ||
![]() |
f3d1d41bdd | ||
![]() |
a661ee5d53 | ||
![]() |
2d8d35a1be | ||
![]() |
f859e2d3ed | ||
![]() |
e3d141dae9 | ||
![]() |
ee699c0036 | ||
![]() |
442e1c850d | ||
![]() |
83af28a747 | ||
![]() |
03384721e7 | ||
![]() |
38a1cbb498 | ||
![]() |
f57a2b5a8c | ||
![]() |
c0495892bc | ||
![]() |
1bcc110299 | ||
![]() |
58cc41bc1c | ||
![]() |
4ba798dd7d | ||
![]() |
897197146a | ||
![]() |
73321ccfb0 | ||
![]() |
d8267a43b8 | ||
![]() |
0a9bd7e971 | ||
![]() |
a11018089f | ||
![]() |
d61bed3ce6 | ||
![]() |
6a8099b071 | ||
![]() |
dbfbbb41b1 | ||
![]() |
5c0bcf49a8 | ||
![]() |
51d9afadd6 | ||
![]() |
26d4249a3f | ||
![]() |
9dbfe07798 | ||
![]() |
cc551fc0bf | ||
![]() |
3ccac4d583 | ||
![]() |
401d49aadc | ||
![]() |
0ad1a6e5bf | ||
![]() |
d9da2c469f | ||
![]() |
aa85024f9a | ||
![]() |
ce0326b839 | ||
![]() |
57a1df4c88 | ||
![]() |
0a3d31b8a6 | ||
![]() |
ecf7f00772 | ||
![]() |
2904de5ff2 | ||
![]() |
f84be14677 | ||
![]() |
0c3cc2efcb | ||
![]() |
d0249a82ec | ||
![]() |
c6b0b607d4 | ||
![]() |
e34512e5e9 | ||
![]() |
8582d47a00 | ||
![]() |
c1e6e38709 | ||
![]() |
9231afca4b | ||
![]() |
3d4353650c | ||
![]() |
d7fddadaec | ||
![]() |
79afbe34de | ||
![]() |
a6d411fa92 | ||
![]() |
78773b6da0 | ||
![]() |
d32bb055ea | ||
![]() |
a0ca2d098b | ||
![]() |
ff69648ae2 | ||
![]() |
834cb8525e | ||
![]() |
bcb5ec5f4e | ||
![]() |
9c60ed9d07 | ||
![]() |
722da0b500 | ||
![]() |
0b85a64bf0 | ||
![]() |
0fba8e6ef8 | ||
![]() |
27966c29e1 | ||
![]() |
1e32c2b67a | ||
![]() |
c62d882939 | ||
![]() |
200a6a296e | ||
![]() |
eb91ad0964 | ||
![]() |
c9259876d2 | ||
![]() |
2c218e705f | ||
![]() |
8acdd2e7ed | ||
![]() |
b414d52066 | ||
![]() |
42f93ffbfa | ||
![]() |
21f128433b | ||
![]() |
86ed65ff00 | ||
![]() |
19bb09133d | ||
![]() |
a43c2ac272 | ||
![]() |
082e33b91f | ||
![]() |
cd5e7bff82 | ||
f9267170a3 | |||
![]() |
502521e728 | ||
![]() |
892f03a7d8 | ||
![]() |
083c73e7e9 | ||
![]() |
d2c140c8ed | ||
![]() |
99e876d8f7 | ||
![]() |
1b89b933a6 | ||
![]() |
4939a708f8 | ||
![]() |
70e065b5db | ||
![]() |
8e1b7cb692 | ||
![]() |
4019aab8da | ||
![]() |
1b2c9a23e1 | ||
![]() |
ada3531aff | ||
![]() |
e6dbaa236a | ||
![]() |
6f4e2d297b | ||
![]() |
b39a151c9c | ||
![]() |
92f96f3edf | ||
![]() |
4bcbfe1ad4 | ||
![]() |
6dd7e86f23 | ||
![]() |
9c444d2709 | ||
![]() |
4e9c7f3504 | ||
![]() |
8d5351a0c4 | ||
![]() |
ba119e9057 | ||
![]() |
8c1f6026a7 | ||
![]() |
dcbe30a28e | ||
![]() |
aa3c8cd371 | ||
![]() |
0b09734953 | ||
![]() |
9666e1d438 | ||
![]() |
5a0e7866d3 | ||
![]() |
7d83bf93e4 | ||
![]() |
cb17ad8740 | ||
![]() |
5086597878 | ||
![]() |
e99a83cb96 | ||
![]() |
af607da082 | ||
![]() |
f38ad33ddf | ||
![]() |
bc4012916d | ||
![]() |
05ae649b66 | ||
![]() |
72dba09932 | ||
![]() |
ed6b3961c5 | ||
![]() |
1f8ec7c865 | ||
![]() |
4ccc13d3ee | ||
![]() |
e34d4b4b35 | ||
![]() |
2e3bf06139 | ||
![]() |
2ec7c85e99 | ||
![]() |
d3a5505542 | ||
![]() |
3f63560876 | ||
![]() |
d738f84ed1 | ||
![]() |
15c92b1bf6 | ||
![]() |
2a6df60fdc | ||
![]() |
cdcf5fc9c0 | ||
![]() |
2a0dbcf7ee | ||
![]() |
9a9d5eaeeb | ||
![]() |
4319b4d78d | ||
![]() |
a547554aa2 | ||
![]() |
1d719ec87f | ||
![]() |
d5060d25a1 | ||
![]() |
ec1296a667 | ||
![]() |
29cfeebcde | ||
![]() |
7c507e4700 | ||
![]() |
809d69aa85 | ||
![]() |
7a0fcf2fc5 | ||
![]() |
e73d446461 | ||
![]() |
7c15eb1e42 | ||
![]() |
389a4bb9f0 | ||
![]() |
8308073464 | ||
![]() |
9928cccdb6 | ||
![]() |
5b3736682d | ||
![]() |
b6289d1153 | ||
![]() |
6711266a50 | ||
![]() |
58ad3d6e09 | ||
![]() |
5783b1a169 | ||
![]() |
5eeb4bd0d3 | ||
![]() |
27174e64de | ||
![]() |
034e514a0d | ||
![]() |
0c7e5a9418 | ||
![]() |
2b35e123f4 | ||
![]() |
86c1ef1e7c | ||
![]() |
9b81d3c967 | ||
![]() |
44a7f65e1a | ||
![]() |
0c7b38b144 | ||
![]() |
7c2868486e | ||
![]() |
fde3684384 | ||
![]() |
91d624e38a | ||
![]() |
977734ee07 | ||
![]() |
44e0b088fa | ||
![]() |
f05167dee6 | ||
![]() |
d5be3bf749 | ||
![]() |
7dbf21efc2 | ||
![]() |
41434fad53 | ||
![]() |
4c9d895768 | ||
![]() |
20db310908 | ||
![]() |
e7d8e20f56 | ||
![]() |
07f194686f | ||
![]() |
40e607b876 | ||
![]() |
4e2ac7685d | ||
![]() |
daa6b8ff25 | ||
![]() |
1fb95e8084 | ||
![]() |
066a7a1801 | ||
![]() |
a24b40860f | ||
![]() |
5b85e39d99 | ||
![]() |
3246ebb3f5 | ||
![]() |
de1a6a539f | ||
![]() |
87bac69f55 | ||
![]() |
aa775fd9b0 | ||
![]() |
da2eab3f66 | ||
![]() |
de132b94b1 | ||
![]() |
3df580066b | ||
![]() |
b3d21f164d | ||
![]() |
ba38a7e6b4 | ||
![]() |
a4244f08ad | ||
![]() |
c35b36b1cf | ||
![]() |
400e5a7fed | ||
![]() |
d8383ca5fc | ||
![]() |
8933eaf2f3 | ||
![]() |
cc5dff9bb1 | ||
![]() |
e974f0c252 | ||
![]() |
ab5ed3c8e1 | ||
![]() |
9f54c1ff73 | ||
![]() |
3ba035dc2c | ||
![]() |
8b9fe99dee | ||
![]() |
03d7e9d331 | ||
![]() |
f1cf5bec53 | ||
![]() |
1849e5e74a | ||
![]() |
9e8d148912 | ||
![]() |
4588e4e415 | ||
![]() |
e70c63ba8f | ||
![]() |
77ec61e44f | ||
![]() |
da46eeea6b | ||
![]() |
6d64539fcb | ||
![]() |
54806b1541 | ||
![]() |
ddd79cbbb5 | ||
![]() |
eee0d8af5f | ||
![]() |
71e267574d | ||
![]() |
00c6d1579c | ||
![]() |
375a193ff4 | ||
![]() |
11c05bfa09 | ||
![]() |
8a97c06cd1 | ||
![]() |
4eb36373d7 | ||
![]() |
f35801b19d | ||
![]() |
fac92eb7cb | ||
![]() |
566eb4381c | ||
![]() |
6b5de70f4f | ||
![]() |
e59908e7fc | ||
![]() |
ea592fa869 | ||
![]() |
3decfe60ab | ||
![]() |
38f3225b22 | ||
![]() |
ab4a94dd46 | ||
![]() |
1fa7685a28 | ||
![]() |
c718ff30d8 | ||
![]() |
40110e84ab | ||
![]() |
a01fb52d09 | ||
![]() |
3f59996e67 | ||
![]() |
3f62870e67 | ||
![]() |
1a366bf946 | ||
![]() |
891628b33c | ||
![]() |
d2ac22c27a | ||
![]() |
8203be6742 | ||
![]() |
0161f01082 | ||
![]() |
a951396cc2 | ||
![]() |
21da5613c6 | ||
![]() |
cca0d7d27f | ||
![]() |
4812551234 | ||
![]() |
d1c6d0bb06 | ||
![]() |
95181d96a7 | ||
![]() |
4e442b7743 | ||
![]() |
11260dac18 | ||
![]() |
4e512dd3a9 | ||
![]() |
510c3c86c0 | ||
![]() |
4fd5ce3361 | ||
![]() |
4386e36b0b | ||
![]() |
4fee9de6bc | ||
![]() |
cb0cbfaec1 | ||
![]() |
8266450ba2 | ||
![]() |
e97378960f | ||
![]() |
ee435adb7c | ||
![]() |
ac19bf6801 | ||
![]() |
dc0731b3a5 | ||
![]() |
90a0dd6b75 | ||
![]() |
0fd63763f6 | ||
![]() |
e96b2c680a | ||
![]() |
ef951221d6 | ||
![]() |
9b780806f0 | ||
![]() |
576d3ec0df | ||
![]() |
dc8a3a817a | ||
![]() |
d478fb8c38 | ||
![]() |
1cc03887f4 | ||
![]() |
6ab6ee2c0f | ||
![]() |
819d418bf2 | ||
![]() |
d607bb9bfe | ||
![]() |
e53f2ed5e6 | ||
![]() |
f012c4423c | ||
![]() |
dd6aaa898f | ||
![]() |
dc6e47cafc | ||
![]() |
9c9f2180f3 | ||
![]() |
cef609df80 | ||
![]() |
02d60d6d21 | ||
![]() |
5c9660da61 | ||
![]() |
4b85c1d4ac | ||
![]() |
6d41720233 | ||
![]() |
3ef579b4f9 | ||
![]() |
6230476455 | ||
![]() |
29a44cf2ab | ||
![]() |
112f9806ac | ||
![]() |
bb085dab04 | ||
![]() |
972eaf91a0 | ||
![]() |
622d9f2ff3 | ||
![]() |
ad2248a207 | ||
![]() |
765914cab1 | ||
![]() |
abc8635824 | ||
![]() |
09b8a71b14 | ||
![]() |
f411d8e1d8 | ||
![]() |
ffe64567da | ||
![]() |
4cfc42cdaa | ||
![]() |
1700265081 | ||
![]() |
b39c22fc38 | ||
![]() |
c78fe91564 | ||
![]() |
87eeb0a8bd | ||
![]() |
1c6e7bffb7 | ||
![]() |
ae7b27c77a | ||
![]() |
0a05792d8d | ||
![]() |
c0b0c3d1e2 | ||
![]() |
b7fed3532a | ||
![]() |
5a3b1d307b | ||
![]() |
adf9059d82 | ||
![]() |
eb15ea0198 | ||
![]() |
9494c66d20 | ||
![]() |
f2280d6ce7 | ||
![]() |
d55ac45c39 | ||
![]() |
0779964ec1 | ||
![]() |
649f602a18 | ||
![]() |
974bed41b8 | ||
![]() |
bdaa86ff3c | ||
![]() |
758d97fa10 | ||
![]() |
11f27345d2 | ||
![]() |
8c0d3c035c | ||
![]() |
2cd3074746 | ||
![]() |
3b19386b10 | ||
![]() |
5f2a8155f1 | ||
![]() |
618b7b75ac | ||
![]() |
fc772ccc00 | ||
![]() |
c53f7cad97 | ||
![]() |
35fef0534b | ||
![]() |
1064cd7619 | ||
![]() |
46d9b8e06f | ||
![]() |
9ffb42efec | ||
![]() |
6e405565f5 | ||
![]() |
140ac6e9cb | ||
![]() |
79a3c5d425 | ||
![]() |
cc64fb535b |
198 changed files with 55272 additions and 13136 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
|||
#Ignore build files
|
||||
Makefile
|
||||
config.h
|
||||
config.log
|
||||
config.status
|
||||
|
@ -23,6 +22,7 @@ win32port/zlib/Release*/
|
|||
*.suo
|
||||
*.su
|
||||
*.m4
|
||||
*.a
|
||||
missing
|
||||
depcomp
|
||||
install-sh
|
||||
|
@ -37,3 +37,4 @@ config.sub
|
|||
ar-lib
|
||||
libwebsockets.pc
|
||||
build/
|
||||
*.swp
|
||||
|
|
16
.travis.yml
16
.travis.yml
|
@ -2,30 +2,36 @@ env:
|
|||
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
|
||||
# via the "travis encrypt" command using the project repo's public key
|
||||
global:
|
||||
- secure: "amfzN1OzBBZYPJVx8TCYsV1nQ5SPm7QswgGpuHcNKaMAixn1s4tKliR0wyVs1aiMqKco1zrJ3vXll+D5gknKr5obWOeZ61T3PYyZmhjB0n/D+7Y41EikNa1Hn1pP6etcHh3ciJ0qe8FC+9YB5yEII3G/jHsltge8Nu+5o2YCSCw="
|
||||
- secure: "KhAdQ9ja+LBObWNQTYO7Df5J4DyOih6S+eerDMu8UPSO+CoWV2pWoQzbOfocjyOscGOwC+2PrrHDNZyGfqkCLDXg1BxynXPCFerHC1yc2IajvKpGXmAAygNIvp4KACDfGv/dkXrViqIzr/CdcNaU4vIMHSVb5xkeLi0W1dPnQOI="
|
||||
matrix:
|
||||
- LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON"
|
||||
- LWS_METHOD=default
|
||||
- LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON"
|
||||
- LWS_METHOD=noclient CMAKE_ARGS="-DLWS_WITHOUT_CLIENT=ON"
|
||||
- LWS_METHOD=noext CMAKE_ARGS="-DLWS_WITHOUT_EXTENSIONS=ON"
|
||||
- LWS_METHOD=libev CMAKE_ARGS="-DLWS_WITH_LIBEV=ON"
|
||||
- LWS_METHOD=noipv6 CMAKE_ARGS="-DLWS_IPV6=OFF"
|
||||
- LWS_METHOD=http2 CMAKE_ARGS="-DLWS_WITH_HTTP2=ON"
|
||||
- LWS_METHOD=nossl CMAKE_ARGS="-DLWS_WITH_SSL=OFF"
|
||||
- LWS_METHOD=nodaemon CMAKE_ARGS="-DLWS_WITHOUT_DAEMONIZE=ON"
|
||||
- LWS_METHOD=cgi CMAKE_ARGS="-DLWS_WITH_CGI=ON"
|
||||
- LWS_METHOD=nologs CMAKE_ARGS="-DLWS_WITH_NO_LOGS=ON"
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
language: c
|
||||
language: generic
|
||||
install:
|
||||
- ./travis_install.sh
|
||||
script:
|
||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then mkdir build && cd build && cmake $CMAKE_ARGS .. && cmake --build .; fi
|
||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "osx" ]; then mkdir build && cd build && cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" $CMAKE_ARGS .. && cmake --build .; else if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "linux" ]; then mkdir build && cd build && cmake $CMAKE_ARGS .. && cmake --build .; fi ; fi
|
||||
sudo: required
|
||||
dist: trusty
|
||||
addons:
|
||||
coverity_scan:
|
||||
project:
|
||||
name: "warmcat/libwebsockets"
|
||||
notification_email: andy.green@linaro.org
|
||||
notification_email: andy@warmcat.com
|
||||
build_command_prepend: "mkdir build && cd build && cmake .."
|
||||
build_command: "cmake --build ."
|
||||
branch_pattern: coverity_scan
|
||||
|
||||
|
|
1090
CMakeLists.txt
1090
CMakeLists.txt
File diff suppressed because it is too large
Load diff
32
Kconfig
Normal file
32
Kconfig
Normal file
|
@ -0,0 +1,32 @@
|
|||
menu "Libwebsockets"
|
||||
|
||||
config LWS_MODEL_NAME
|
||||
string "Model name of device firmware is for"
|
||||
default "lws"
|
||||
|
||||
config LWS_IS_FACTORY_APPLICATION
|
||||
bool "Is this application is designed for the FACTORY flash slot"
|
||||
default "n"
|
||||
|
||||
config LWS_OTA_SERVER_FQDN
|
||||
depends on LWS_IS_FACTORY_APPLICATION
|
||||
string "Domain name of OTA update server, eg, warmcat.com"
|
||||
default ""
|
||||
|
||||
config LWS_OTA_SERVER_BASE_URL
|
||||
depends on LWS_IS_FACTORY_APPLICATION
|
||||
string "Base URL on OTA update server, eg, /esp32-ota (model is added)"
|
||||
default "/esp32-ota"
|
||||
|
||||
config LWS_OTA_SERVER_UPLOAD_USER
|
||||
depends on LWS_IS_FACTORY_APPLICATION
|
||||
string "User to scp to upload server with"
|
||||
default "root"
|
||||
|
||||
config LWS_OTA_SERVER_UPLOAD_PATH
|
||||
depends on LWS_IS_FACTORY_APPLICATION
|
||||
string "Path served in upload server (eg, \"/var/www/libwebsockets.org\""
|
||||
default "/var/www/libwebsockets.org"
|
||||
|
||||
endmenu
|
||||
|
34
LICENSE
34
LICENSE
|
@ -1,7 +1,13 @@
|
|||
Libwebsockets and included programs are provided under the terms of the GNU
|
||||
Library General Public License (LGPL) 2.1, with the following exceptions:
|
||||
|
||||
1) Static linking of programs with the libwebsockets library does not
|
||||
1) Any reference, whether in these modifications or in the GNU
|
||||
Library General Public License 2.1, to this License, these terms, the
|
||||
GNU Lesser Public License, GNU Library General Public License, LGPL, or
|
||||
any similar reference shall refer to the GNU Library General Public
|
||||
License 2.1 as modified by these paragraphs 1) through 4).
|
||||
|
||||
2) Static linking of programs with the libwebsockets library does not
|
||||
constitute a derivative work and does not require the author to provide
|
||||
source code for the program, use the shared libwebsockets libraries, or
|
||||
link their program against a user-supplied version of libwebsockets.
|
||||
|
@ -10,7 +16,7 @@ If you link the program to a modified version of libwebsockets, then the
|
|||
changes to libwebsockets must be provided under the terms of the LGPL in
|
||||
sections 1, 2, and 4.
|
||||
|
||||
2) You do not have to provide a copy of the libwebsockets license with
|
||||
3) You do not have to provide a copy of the libwebsockets license with
|
||||
programs that are linked to the libwebsockets library, nor do you have to
|
||||
identify the libwebsockets license in your program or documentation as
|
||||
required by section 6 of the LGPL.
|
||||
|
@ -20,8 +26,30 @@ following example statement can be included in user documentation to
|
|||
satisfy this requirement:
|
||||
|
||||
"[program] is based in part on the work of the libwebsockets project
|
||||
(http://libwebsockets.org)"
|
||||
(https://libwebsockets.org)"
|
||||
|
||||
4) Some sources included have their own, more liberal licenses, or options
|
||||
to get original sources with the liberal terms.
|
||||
|
||||
Original liberal license retained
|
||||
|
||||
- lib/sha-1.c - 3-clause BSD license retained, link to original
|
||||
- win32port/zlib - ZLIB license (see zlib.h)
|
||||
|
||||
Relicensed to libwebsocket license
|
||||
|
||||
- lib/base64-decode.c - relicensed to LGPL2.1+SLE, link to original
|
||||
- lib/daemonize.c - relicensed from Public Domain to LGPL2.1+SLE,
|
||||
link to original Public Domain version
|
||||
|
||||
Public Domain (CC-zero) to simplify reuse
|
||||
|
||||
- test-server/*.c
|
||||
- test-server/*.h
|
||||
- lwsws/*
|
||||
|
||||
------ end of exceptions
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
|
|
396
README.build.md
396
README.build.md
|
@ -1,5 +1,8 @@
|
|||
Introduction to CMake
|
||||
---------------------
|
||||
Notes about building lws
|
||||
========================
|
||||
|
||||
|
||||
@section cm Introduction to CMake
|
||||
|
||||
CMake is a multi-platform build tool that can generate build files for many
|
||||
different target platforms. See more info at http://www.cmake.org
|
||||
|
@ -10,7 +13,7 @@ create elaborate clean scripts to get a clean source tree, instead you
|
|||
simply remove your build directory.
|
||||
|
||||
Libwebsockets has been tested to build successfully on the following platforms
|
||||
with SSL support (both OpenSSL/wolfSSL):
|
||||
with SSL support (for OpenSSL/wolfSSL/BoringSSL):
|
||||
|
||||
- Windows (Visual Studio)
|
||||
- Windows (MinGW)
|
||||
|
@ -18,8 +21,8 @@ with SSL support (both OpenSSL/wolfSSL):
|
|||
- OSX
|
||||
- NetBSD
|
||||
|
||||
Building the library and test apps
|
||||
----------------------------------
|
||||
|
||||
@section build1 Building the library and test apps
|
||||
|
||||
The project settings used by CMake to generate the platform specific build
|
||||
files is called [CMakeLists.txt](CMakeLists.txt). CMake then uses one of its "Generators" to
|
||||
|
@ -29,8 +32,8 @@ the available generators for your platform, simply run the "cmake" command.
|
|||
Note that by default OpenSSL will be linked, if you don't want SSL support
|
||||
see below on how to toggle compile options.
|
||||
|
||||
Building on Unix:
|
||||
-----------------
|
||||
|
||||
@section bu Building on Unix:
|
||||
|
||||
1. Install CMake 2.8 or greater: http://cmake.org/cmake/resources/software.html
|
||||
(Most Unix distributions comes with a packaged version also)
|
||||
|
@ -38,69 +41,109 @@ Building on Unix:
|
|||
2. Install OpenSSL.
|
||||
|
||||
3. Generate the build files (default is Make files):
|
||||
```
|
||||
$ cd /path/to/src
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake ..
|
||||
```
|
||||
|
||||
```bash
|
||||
$ cd /path/to/src
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake ..
|
||||
```
|
||||
4. Finally you can build using the generated Makefile:
|
||||
```
|
||||
$ make && sudo make install
|
||||
```
|
||||
**NOTE**: The `build/`` directory can have any name and be located anywhere
|
||||
on your filesystem, and that the argument `..` given to cmake is simply
|
||||
the source directory of **libwebsockets** containing the [CMakeLists.txt](CMakeLists.txt)
|
||||
project file. All examples in this file assumes you use ".."
|
||||
|
||||
(**NOTE**: The `build/`` directory can have any name and be located anywhere
|
||||
on your filesystem, and that the argument `..` given to cmake is simply
|
||||
the source directory of **libwebsockets** containing the [CMakeLists.txt](CMakeLists.txt)
|
||||
project file. All examples in this file assumes you use "..")
|
||||
**NOTE2**:
|
||||
A common option you may want to give is to set the install path, same
|
||||
as --prefix= with autotools. It defaults to /usr/local.
|
||||
You can do this by, eg
|
||||
```
|
||||
$ cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr .
|
||||
```
|
||||
|
||||
**NOTE2**:
|
||||
A common option you may want to give is to set the install path, same
|
||||
as --prefix= with autotools. It defaults to /usr/local.
|
||||
You can do this by, eg
|
||||
|
||||
```bash
|
||||
$ cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr ..
|
||||
```
|
||||
|
||||
**NOTE3**:
|
||||
On machines that want libraries in lib64, you can also add the
|
||||
following to the cmake line
|
||||
|
||||
```bash
|
||||
**NOTE3**:
|
||||
On machines that want libraries in lib64, you can also add the
|
||||
following to the cmake line
|
||||
```
|
||||
-DLIB_SUFFIX=64
|
||||
```
|
||||
```
|
||||
|
||||
**NOTE4**:
|
||||
If you are building against a non-distro OpenSSL (eg, in order to get
|
||||
access to ALPN support only in newer OpenSSL versions) the nice way to
|
||||
express that in one cmake command is eg,
|
||||
|
||||
```bash
|
||||
**NOTE4**:
|
||||
If you are building against a non-distro OpenSSL (eg, in order to get
|
||||
access to ALPN support only in newer OpenSSL versions) the nice way to
|
||||
express that in one cmake command is eg,
|
||||
```
|
||||
$ cmake .. -DOPENSSL_ROOT_DIR=/usr/local/ssl \
|
||||
-DCMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE=/usr/local/ssl \
|
||||
-DLWS_WITH_HTTP2=1
|
||||
```
|
||||
```
|
||||
|
||||
When you run the test apps using non-distro SSL, you have to force them
|
||||
to use your libs, not the distro ones
|
||||
|
||||
```bash
|
||||
When you run the test apps using non-distro SSL, you have to force them
|
||||
to use your libs, not the distro ones
|
||||
```
|
||||
$ LD_LIBRARY_PATH=/usr/local/ssl/lib libwebsockets-test-server --ssl
|
||||
```
|
||||
```
|
||||
|
||||
To get it to build on latest openssl (2016-04-10) it needed this approach
|
||||
```
|
||||
cmake .. -DLWS_WITH_HTTP2=1 -DLWS_OPENSSL_INCLUDE_DIRS=/usr/local/include/openssl -DLWS_OPENSSL_LIBRARIES="/usr/local/lib64/libssl.so;/usr/local/lib64/libcrypto.so"
|
||||
```
|
||||
|
||||
Mac users have reported
|
||||
|
||||
```
|
||||
$ export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2k; cmake ..; make -j4
|
||||
```
|
||||
|
||||
worked for them when using "homebrew" OpenSSL
|
||||
|
||||
**NOTE5**:
|
||||
To build with debug info and _DEBUG for lower priority debug messages
|
||||
compiled in, use
|
||||
```
|
||||
$ cmake .. -DCMAKE_BUILD_TYPE=DEBUG
|
||||
```
|
||||
|
||||
**NOTE6**
|
||||
To build on Solaris the linker needs to be informed to use lib socket
|
||||
and libnsl, and only builds in 64bit mode.
|
||||
|
||||
```bash
|
||||
$ cmake .. -DCMAKE_C_FLAGS=-m64 -DCMAKE_EXE_LINKER_FLAGS="-lsocket -lnsl"
|
||||
```
|
||||
|
||||
4. Finally you can build using the generated Makefile:
|
||||
|
||||
```bash
|
||||
```bash
|
||||
$ make
|
||||
```
|
||||
```
|
||||
|
||||
Quirk of cmake
|
||||
--------------
|
||||
@section lcap Linux Capabilities
|
||||
|
||||
On Linux, lws now lets you retain selected root capabilities when dropping
|
||||
privileges. If libcap-dev or similar package is installed providing
|
||||
sys/capabilities.h, and libcap or similar package is installed providing
|
||||
libcap.so, CMake will enable the capability features.
|
||||
|
||||
The context creation info struct .caps[] and .count_caps members can then
|
||||
be set by user code to enable selected root capabilities to survive the
|
||||
transition to running under an unprivileged user.
|
||||
|
||||
@section cmq Quirk of cmake
|
||||
|
||||
When changing cmake options, for some reason the only way to get it to see the
|
||||
changes sometimes is delete the contents of your build directory and do the
|
||||
cmake from scratch.
|
||||
|
||||
Building on Windows (Visual Studio)
|
||||
-----------------------------------
|
||||
deleting build/CMakeCache.txt may be enough.
|
||||
|
||||
|
||||
@section cmw Building on Windows (Visual Studio)
|
||||
|
||||
1. Install CMake 2.6 or greater: http://cmake.org/cmake/resources/software.html
|
||||
|
||||
2. Install OpenSSL binaries. http://www.openssl.org/related/binaries.html
|
||||
|
@ -113,12 +156,12 @@ Building on Windows (Visual Studio)
|
|||
|
||||
3. Generate the Visual studio project by opening the Visual Studio cmd prompt:
|
||||
|
||||
```bash
|
||||
cd <path to src>
|
||||
md build
|
||||
cd build
|
||||
cmake -G "Visual Studio 10" ..
|
||||
```
|
||||
```
|
||||
cd <path to src>
|
||||
md build
|
||||
cd build
|
||||
cmake -G "Visual Studio 10" ..
|
||||
```
|
||||
|
||||
(**NOTE**: There is also a cmake-gui available on Windows if you prefer that)
|
||||
|
||||
|
@ -129,35 +172,49 @@ Building on Windows (Visual Studio)
|
|||
4. Now you should have a generated Visual Studio Solution in your
|
||||
`<path to src>/build` directory, which can be used to build.
|
||||
|
||||
Building on Windows (MinGW)
|
||||
---------------------------
|
||||
5. Some additional deps may be needed
|
||||
|
||||
- iphlpapi.lib
|
||||
- psapi.lib
|
||||
- userenv.lib
|
||||
|
||||
6. If you're using libuv, you must make sure to compile libuv with the same multithread-dll / Mtd attributes as libwebsockets itself
|
||||
|
||||
|
||||
@section cmwmgw Building on Windows (MinGW)
|
||||
|
||||
1. Install MinGW: http://sourceforge.net/projects/mingw/files
|
||||
|
||||
(**NOTE**: Preferably in the default location C:\MinGW)
|
||||
|
||||
2. Fix up MinGW headers
|
||||
|
||||
a) Add the following lines to C:\MinGW\include\winsock2.h:
|
||||
|
||||
```c
|
||||
#if(_WIN32_WINNT >= 0x0600)
|
||||
a) If still necessary, sdd the following lines to C:\MinGW\include\winsock2.h:
|
||||
```
|
||||
#if(_WIN32_WINNT >= 0x0600)
|
||||
|
||||
typedef struct pollfd {
|
||||
typedef struct pollfd {
|
||||
|
||||
SOCKET fd;
|
||||
SHORT events;
|
||||
SHORT revents;
|
||||
SOCKET fd;
|
||||
SHORT events;
|
||||
SHORT revents;
|
||||
|
||||
} WSAPOLLFD, *PWSAPOLLFD, FAR *LPWSAPOLLFD;
|
||||
} WSAPOLLFD, *PWSAPOLLFD, FAR *LPWSAPOLLFD;
|
||||
|
||||
WINSOCK_API_LINKAGE int WSAAPI WSAPoll(LPWSAPOLLFD fdArray, ULONG fds, INT timeout);
|
||||
WINSOCK_API_LINKAGE int WSAAPI WSAPoll(LPWSAPOLLFD fdArray, ULONG fds, INT timeout);
|
||||
|
||||
#endif // (_WIN32_WINNT >= 0x0600)
|
||||
```
|
||||
#endif // (_WIN32_WINNT >= 0x0600)
|
||||
```
|
||||
|
||||
Update crtdefs.h line 47 to say:
|
||||
|
||||
```
|
||||
typedef __int64 ssize_t;
|
||||
```
|
||||
|
||||
b) Create C:\MinGW\include\mstcpip.h and copy and paste the content from following link into it:
|
||||
|
||||
http://wine-unstable.sourcearchive.com/documentation/1.1.32/mstcpip_8h-source.html
|
||||
|
||||
https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-headers/include/mstcpip.h
|
||||
|
||||
3. Install CMake 2.6 or greater: http://cmake.org/cmake/resources/software.html
|
||||
|
||||
|
@ -170,14 +227,12 @@ Building on Windows (MinGW)
|
|||
<OpenSSL install location>\bin\openssl.cfg
|
||||
|
||||
5. Generate the build files (default is Make files) using MSYS shell:
|
||||
|
||||
```bash
|
||||
$ cd /drive/path/to/src
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=C:/MinGW ..
|
||||
```
|
||||
|
||||
```
|
||||
$ cd /drive/path/to/src
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=C:/MinGW ..
|
||||
```
|
||||
(**NOTE**: The `build/`` directory can have any name and be located anywhere
|
||||
on your filesystem, and that the argument `..` given to cmake is simply
|
||||
the source directory of **libwebsockets** containing the [CMakeLists.txt](CMakeLists.txt)
|
||||
|
@ -186,27 +241,35 @@ Building on Windows (MinGW)
|
|||
**NOTE2**:
|
||||
To generate build files allowing to create libwebsockets binaries with debug information
|
||||
set the CMAKE_BUILD_TYPE flag to DEBUG:
|
||||
|
||||
```bash
|
||||
$ cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=C:/MinGW -DCMAKE_BUILD_TYPE=DEBUG ..
|
||||
```
|
||||
|
||||
```
|
||||
$ cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=C:/MinGW -DCMAKE_BUILD_TYPE=DEBUG ..
|
||||
```
|
||||
6. Finally you can build using the generated Makefile and get the results deployed into your MinGW installation:
|
||||
|
||||
```bash
|
||||
$ make
|
||||
$ make install
|
||||
```
|
||||
```
|
||||
$ make
|
||||
$ make install
|
||||
```
|
||||
|
||||
Setting compile options
|
||||
-----------------------
|
||||
@section optee Building for OP-TEE
|
||||
|
||||
OP-TEE is a "Secure World" Trusted Execution Environment.
|
||||
|
||||
Although lws is only part of the necessary picture to have an https-enabled
|
||||
TA, it does support OP-TEE as a platform and if you provide the other
|
||||
pieces, does work very well.
|
||||
|
||||
Select it in cmake with `-DLWS_PLAT_OPTEE=1`
|
||||
|
||||
|
||||
@section cmco Setting compile options
|
||||
|
||||
To set compile time flags you can either use one of the CMake gui applications
|
||||
or do it via command line.
|
||||
or do it via the command line.
|
||||
|
||||
Command line
|
||||
------------
|
||||
To list avaialable options (ommit the H if you don't want the help text):
|
||||
@subsection cmcocl Command line
|
||||
|
||||
To list available options (omit the H if you don't want the help text):
|
||||
|
||||
cmake -LH ..
|
||||
|
||||
|
@ -216,20 +279,21 @@ Then to set an option and build (for example turn off SSL support):
|
|||
or
|
||||
cmake -DLWS_WITH_SSL:BOOL=OFF ..
|
||||
|
||||
Unix GUI
|
||||
--------
|
||||
@subsection cmcoug Unix GUI
|
||||
|
||||
If you have a curses-enabled build you simply type:
|
||||
(not all packages include this, my debian install does not for example).
|
||||
|
||||
ccmake
|
||||
|
||||
Windows GUI
|
||||
-----------
|
||||
@subsection cmcowg Windows GUI
|
||||
|
||||
On windows CMake comes with a gui application:
|
||||
Start -> Programs -> CMake -> CMake (cmake-gui)
|
||||
|
||||
wolfSSL/CyaSSL replacement for OpenSSL
|
||||
--------------------------------------
|
||||
|
||||
@section wolf wolfSSL/CyaSSL replacement for OpenSSL
|
||||
|
||||
wolfSSL/CyaSSL is a lightweight SSL library targeted at embedded systems:
|
||||
https://www.wolfssl.com/wolfSSL/Products-wolfssl.html
|
||||
|
||||
|
@ -239,57 +303,102 @@ much link to it instead of OpenSSL, giving a much smaller footprint.
|
|||
**NOTE**: wolfssl needs to be compiled using the `--enable-opensslextra` flag for
|
||||
this to work.
|
||||
|
||||
Compiling libwebsockets with wolfSSL
|
||||
------------------------------------
|
||||
@section wolf1 Compiling libwebsockets with wolfSSL
|
||||
|
||||
```bash
|
||||
cmake .. -DLWS_USE_WOLFSSL=1 \
|
||||
-DLWS_WOLFSSL_INCLUDE_DIRS=/path/to/wolfssl \
|
||||
-DLWS_WOLFSSL_LIBRARIES=/path/to/wolfssl/wolfssl.a ..
|
||||
```
|
||||
cmake .. -DLWS_USE_WOLFSSL=1 \
|
||||
-DLWS_WOLFSSL_INCLUDE_DIRS=/path/to/wolfssl \
|
||||
-DLWS_WOLFSSL_LIBRARIES=/path/to/wolfssl/wolfssl.a ..
|
||||
```
|
||||
|
||||
**NOTE**: On windows use the .lib file extension for `LWS_WOLFSSL_LIBRARIES` instead.
|
||||
|
||||
Compiling libwebsockets with CyaSSL
|
||||
-----------------------------------
|
||||
@section cya Compiling libwebsockets with CyaSSL
|
||||
|
||||
```bash
|
||||
cmake .. -DLWS_USE_CYASSL=1 \
|
||||
-DLWS_CYASSL_INCLUDE_DIRS=/path/to/cyassl \
|
||||
-DLWS_CYASSL_LIBRARIES=/path/to/wolfssl/cyassl.a ..
|
||||
```
|
||||
cmake .. -DLWS_USE_CYASSL=1 \
|
||||
-DLWS_CYASSL_INCLUDE_DIRS=/path/to/cyassl \
|
||||
-DLWS_CYASSL_LIBRARIES=/path/to/wolfssl/cyassl.a ..
|
||||
```
|
||||
|
||||
**NOTE**: On windows use the .lib file extension for `LWS_CYASSL_LIBRARIES` instead.
|
||||
|
||||
Reproducing HTTP2.0 tests
|
||||
-------------------------
|
||||
@section esp32 Building for ESP32
|
||||
|
||||
Step 1, get ESP-IDF with lws integrated as a component
|
||||
|
||||
```
|
||||
$ git clone --int --recursive https://github.com/lws-team/lws-esp-idf
|
||||
```
|
||||
|
||||
Step 2: Get Application including the test plugins
|
||||
|
||||
```
|
||||
$ git clone https://github.com/lws-team/lws-esp32
|
||||
```
|
||||
|
||||
Set your IDF_PATH to point to the esp-idf you downloaded in 1)
|
||||
|
||||
There's docs for how to build the lws-esp32 test app and reproduce it in the README.md here
|
||||
|
||||
https://github.com/lws-team/lws-esp32/blob/master/README.md
|
||||
|
||||
|
||||
@section extplugins Building plugins outside of lws itself
|
||||
|
||||
The directory ./plugin-standalone/ shows how easy it is to create plugins
|
||||
outside of lws itself. First build lws itself with -DLWS_WITH_PLUGINS,
|
||||
then use the same flow to build the standalone plugin
|
||||
```
|
||||
cd ./plugin-standalone
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make && sudo make install
|
||||
```
|
||||
|
||||
if you changed the default plugin directory when you built lws, you must
|
||||
also give the same arguments to cmake here (eg,
|
||||
` -DCMAKE_INSTALL_PREFIX:PATH=/usr/something/else...` )
|
||||
|
||||
Otherwise if you run lwsws or libwebsockets-test-server-v2.0, it will now
|
||||
find the additional plugin "libprotocol_example_standalone.so"
|
||||
```
|
||||
lwsts[21257]: Plugins:
|
||||
lwsts[21257]: libprotocol_dumb_increment.so
|
||||
lwsts[21257]: libprotocol_example_standalone.so
|
||||
lwsts[21257]: libprotocol_lws_mirror.so
|
||||
lwsts[21257]: libprotocol_lws_server_status.so
|
||||
lwsts[21257]: libprotocol_lws_status.so
|
||||
```
|
||||
If you have multiple vhosts, you must enable plugins at the vhost
|
||||
additionally, discovered plugins are not enabled automatically for security
|
||||
reasons. You do this using info->pvo or for lwsws, in the JSON config.
|
||||
|
||||
|
||||
@section http2rp Reproducing HTTP2.0 tests
|
||||
|
||||
You must have built and be running lws against a version of openssl that has
|
||||
ALPN / NPN. Most distros still have older versions. You'll know it's right by
|
||||
seeing
|
||||
|
||||
```bash
|
||||
lwsts[4752]: Compiled with OpenSSL support
|
||||
lwsts[4752]: Using SSL mode
|
||||
lwsts[4752]: HTTP2 / ALPN enabled
|
||||
```
|
||||
|
||||
lwsts[4752]: Compiled with OpenSSL support
|
||||
lwsts[4752]: Using SSL mode
|
||||
lwsts[4752]: HTTP2 / ALPN enabled
|
||||
```
|
||||
at lws startup.
|
||||
|
||||
For non-SSL HTTP2.0 upgrade
|
||||
|
||||
```bash
|
||||
$ nghttp -nvasu http://localhost:7681/test.htm
|
||||
```
|
||||
|
||||
$ nghttp -nvasu http://localhost:7681/test.htm
|
||||
```
|
||||
For SSL / ALPN HTTP2.0 upgrade
|
||||
|
||||
```
|
||||
$ nghttp -nvas https://localhost:7681/test.html
|
||||
$ nghttp -nvas https://localhost:7681/test.html
|
||||
```
|
||||
|
||||
Cross compiling
|
||||
---------------
|
||||
@section cross Cross compiling
|
||||
|
||||
To enable cross-compiling **libwebsockets** using CMake you need to create
|
||||
a "Toolchain file" that you supply to CMake when generating your build files.
|
||||
CMake will then use the cross compilers and build paths specified in this file
|
||||
|
@ -299,13 +408,11 @@ to look for dependencies and such.
|
|||
you can use as a starting point.
|
||||
|
||||
The commandline to configure for cross with this would look like
|
||||
|
||||
```bash
|
||||
$ cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr \
|
||||
-DCMAKE_TOOLCHAIN_FILE=../cross-arm-linux-gnueabihf.cmake \
|
||||
-DWITHOUT_EXTENSIONS=1 -DWITH_SSL=0
|
||||
```
|
||||
|
||||
$ cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr \
|
||||
-DCMAKE_TOOLCHAIN_FILE=../cross-arm-linux-gnueabihf.cmake \
|
||||
-DLWS_WITHOUT_EXTENSIONS=1 -DLWS_WITH_SSL=0
|
||||
```
|
||||
The example shows how to build with no external cross lib dependencies, you
|
||||
need to provide the cross libraries otherwise.
|
||||
|
||||
|
@ -315,25 +422,22 @@ need to provide the cross libraries otherwise.
|
|||
Additional information on cross compilation with CMake:
|
||||
http://www.vtk.org/Wiki/CMake_Cross_Compiling
|
||||
|
||||
Memory efficiency
|
||||
-----------------
|
||||
@section mem Memory efficiency
|
||||
|
||||
Embedded server-only configuration without extensions (ie, no compression
|
||||
on websocket connections), but with full v13 websocket features and http
|
||||
server, built on ARM Cortex-A9:
|
||||
|
||||
Update at 8dac94d (2013-02-18)
|
||||
|
||||
```bash
|
||||
$ ./configure --without-client --without-extensions --disable-debug --without-daemonize
|
||||
|
||||
Context Creation, 1024 fd limit[2]: 16720 (includes 12 bytes per fd)
|
||||
Per-connection [3]: 72 bytes, +1328 during headers
|
||||
|
||||
.text .rodata .data .bss
|
||||
11512 2784 288 4
|
||||
```
|
||||
$ ./configure --without-client --without-extensions --disable-debug --without-daemonize
|
||||
|
||||
Context Creation, 1024 fd limit[2]: 16720 (includes 12 bytes per fd)
|
||||
Per-connection [3]: 72 bytes, +1328 during headers
|
||||
|
||||
.text .rodata .data .bss
|
||||
11512 2784 288 4
|
||||
```
|
||||
This shows the impact of the major configuration with/without options at
|
||||
13ba5bbc633ea962d46d using Ubuntu ARM on a PandaBoard ES.
|
||||
|
||||
|
|
799
README.coding.md
799
README.coding.md
|
@ -1,5 +1,7 @@
|
|||
Daemonization
|
||||
-------------
|
||||
Notes about coding with lws
|
||||
===========================
|
||||
|
||||
@section dae Daemonization
|
||||
|
||||
There's a helper api `lws_daemonize` built by default that does everything you
|
||||
need to daemonize well, including creating a lock file. If you're making
|
||||
|
@ -11,8 +13,7 @@ daemon is headless, so you'll need to sort out alternative logging, by, eg,
|
|||
syslog.
|
||||
|
||||
|
||||
Maximum number of connections
|
||||
-----------------------------
|
||||
@section conns Maximum number of connections
|
||||
|
||||
The maximum number of connections the library can deal with is decided when
|
||||
it starts by querying the OS to find out how many file descriptors it is
|
||||
|
@ -21,12 +22,13 @@ allow up to that many connections, minus whatever other file descriptors are
|
|||
in use by the user code.
|
||||
|
||||
If you want to restrict that allocation, or increase it, you can use ulimit or
|
||||
similar to change the avaiable number of file descriptors, and when restarted
|
||||
similar to change the available number of file descriptors, and when restarted
|
||||
**libwebsockets** will adapt accordingly.
|
||||
|
||||
|
||||
Libwebsockets is singlethreaded
|
||||
-------------------------------
|
||||
@section evtloop Libwebsockets is singlethreaded
|
||||
|
||||
Libwebsockets works in a serialized event loop, in a single thread.
|
||||
|
||||
Directly performing websocket actions from other threads is not allowed.
|
||||
Aside from the internal data being inconsistent in `forked()` processes,
|
||||
|
@ -36,6 +38,19 @@ with the socket closing and the `wsi` freed.
|
|||
Websocket write activities should only take place in the
|
||||
`LWS_CALLBACK_SERVER_WRITEABLE` callback as described below.
|
||||
|
||||
[This network-programming necessity to link the issue of new data to
|
||||
the peer taking the previous data is not obvious to all users so let's
|
||||
repeat that in other words:
|
||||
|
||||
***ONLY DO LWS_WRITE FROM THE WRITEABLE CALLBACK***
|
||||
|
||||
There is another network-programming truism that surprises some people which
|
||||
is if the sink for the data cannot accept more:
|
||||
|
||||
***YOU MUST PERFORM RX FLOW CONTROL***
|
||||
|
||||
See the mirror protocol implementations for example code.
|
||||
|
||||
Only live connections appear in the user callbacks, so this removes any
|
||||
possibility of trying to used closed and freed wsis.
|
||||
|
||||
|
@ -51,8 +66,7 @@ SSL_library_init() is called from the context create api and it also is not
|
|||
reentrant. So at least create the contexts sequentially.
|
||||
|
||||
|
||||
Only send data when socket writeable
|
||||
------------------------------------
|
||||
@section writeable Only send data when socket writeable
|
||||
|
||||
You should only send data on a websocket connection from the user callback
|
||||
`LWS_CALLBACK_SERVER_WRITEABLE` (or `LWS_CALLBACK_CLIENT_WRITEABLE` for
|
||||
|
@ -61,8 +75,9 @@ clients).
|
|||
If you want to send something, do not just send it but request a callback
|
||||
when the socket is writeable using
|
||||
|
||||
- `libwebsocket_callback_on_writable(context, wsi)`` for a specific `wsi`, or
|
||||
- `libwebsocket_callback_on_writable_all_protocol(protocol)` for all connections
|
||||
- `lws_callback_on_writable(context, wsi)` for a specific `wsi`, or
|
||||
|
||||
- `lws_callback_on_writable_all_protocol(protocol)` for all connections
|
||||
using that protocol to get a callback when next writeable.
|
||||
|
||||
Usually you will get called back immediately next time around the service
|
||||
|
@ -73,8 +88,7 @@ in the ...WRITEABLE callback.
|
|||
See the test server code for an example of how to do this.
|
||||
|
||||
|
||||
Do not rely on only your own WRITEABLE requests appearing
|
||||
---------------------------------------------------------
|
||||
@section otherwr Do not rely on only your own WRITEABLE requests appearing
|
||||
|
||||
Libwebsockets may generate additional `LWS_CALLBACK_CLIENT_WRITEABLE` events
|
||||
if it met network conditions where it had to buffer your send data internally.
|
||||
|
@ -87,13 +101,12 @@ It's quite possible you get an 'extra' writeable callback at any time and
|
|||
just need to `return 0` and wait for the expected callback later.
|
||||
|
||||
|
||||
Closing connections from the user side
|
||||
--------------------------------------
|
||||
@section closing Closing connections from the user side
|
||||
|
||||
When you want to close a connection, you do it by returning `-1` from a
|
||||
callback for that connection.
|
||||
|
||||
You can provoke a callback by calling `libwebsocket_callback_on_writable` on
|
||||
You can provoke a callback by calling `lws_callback_on_writable` on
|
||||
the wsi, then notice in the callback you want to close it and just return -1.
|
||||
But usually, the decision to close is made in a callback already and returning
|
||||
-1 is simple.
|
||||
|
@ -105,44 +118,70 @@ take care of closing the connection automatically.
|
|||
If you have a silently dead connection, it's possible to enter a state where
|
||||
the send pipe on the connection is choked but no ack will ever come, so the
|
||||
dead connection will never become writeable. To cover that, you can use TCP
|
||||
keepalives (see later in this document)
|
||||
keepalives (see later in this document) or pings.
|
||||
|
||||
@section gzip Serving from inside a zip file
|
||||
|
||||
Fragmented messages
|
||||
-------------------
|
||||
Lws now supports serving gzipped files from inside a zip container. Thanks to
|
||||
Per Bothner for contributing the code.
|
||||
|
||||
This has the advtantage that if the client can accept GZIP encoding, lws can
|
||||
simply send the gzip-compressed file from inside the zip file with no further
|
||||
processing, saving time and bandwidth.
|
||||
|
||||
In the case the client can't understand gzip compression, lws automatically
|
||||
decompressed the file and sends it normally.
|
||||
|
||||
Clients with limited storage and RAM will find this useful; the memory needed
|
||||
for the inflate case is constrained so that only one input buffer at a time
|
||||
is ever in memory.
|
||||
|
||||
To use this feature, ensure LWS_WITH_ZIP_FOPS is enabled at CMake (it is by
|
||||
default).
|
||||
|
||||
`libwebsockets-test-server-v2.0` includes a mount using this technology
|
||||
already, run that test server and navigate to http://localhost:7681/ziptest/candide.html
|
||||
|
||||
This will serve the book Candide in html, together with two jpgs, all from
|
||||
inside a .zip file in /usr/[local/]share-libwebsockets-test-server/candide.zip
|
||||
|
||||
Usage is otherwise automatic, if you arrange a mount that points to the zipfile,
|
||||
eg, "/ziptest" -> "mypath/test.zip", then URLs like `/ziptest/index.html` will be
|
||||
servied from `index.html` inside `mypath/test.zip`
|
||||
|
||||
@section frags Fragmented messages
|
||||
|
||||
To support fragmented messages you need to check for the final
|
||||
frame of a message with `libwebsocket_is_final_fragment`. This
|
||||
frame of a message with `lws_is_final_fragment`. This
|
||||
check can be combined with `libwebsockets_remaining_packet_payload`
|
||||
to gather the whole contents of a message, eg:
|
||||
|
||||
```
|
||||
case LWS_CALLBACK_RECEIVE:
|
||||
{
|
||||
Client * const client = (Client *)user;
|
||||
const size_t remaining = libwebsockets_remaining_packet_payload(wsi);
|
||||
|
||||
if (!remaining && libwebsocket_is_final_fragment(wsi)) {
|
||||
if (client->HasFragments()) {
|
||||
client->AppendMessageFragment(in, len, 0);
|
||||
in = (void *)client->GetMessage();
|
||||
len = client->GetMessageLength();
|
||||
}
|
||||
|
||||
client->ProcessMessage((char *)in, len, wsi);
|
||||
client->ResetMessage();
|
||||
} else
|
||||
client->AppendMessageFragment(in, len, remaining);
|
||||
}
|
||||
break;
|
||||
case LWS_CALLBACK_RECEIVE:
|
||||
{
|
||||
Client * const client = (Client *)user;
|
||||
const size_t remaining = lws_remaining_packet_payload(wsi);
|
||||
|
||||
if (!remaining && lws_is_final_fragment(wsi)) {
|
||||
if (client->HasFragments()) {
|
||||
client->AppendMessageFragment(in, len, 0);
|
||||
in = (void *)client->GetMessage();
|
||||
len = client->GetMessageLength();
|
||||
}
|
||||
|
||||
client->ProcessMessage((char *)in, len, wsi);
|
||||
client->ResetMessage();
|
||||
} else
|
||||
client->AppendMessageFragment(in, len, remaining);
|
||||
}
|
||||
break;
|
||||
```
|
||||
|
||||
The test app libwebsockets-test-fraggle sources also show how to
|
||||
deal with fragmented messages.
|
||||
|
||||
|
||||
Debug Logging
|
||||
-------------
|
||||
@section debuglog Debug Logging
|
||||
|
||||
Also using `lws_set_log_level` api you may provide a custom callback to actually
|
||||
emit the log string. By default, this points to an internal emit function
|
||||
|
@ -163,9 +202,17 @@ The logging apis are made available for user code.
|
|||
The difference between notice and info is that notice will be logged by default
|
||||
whereas info is ignored by default.
|
||||
|
||||
If you are not building with _DEBUG defined, ie, without this
|
||||
|
||||
External Polling Loop support
|
||||
-----------------------------
|
||||
```
|
||||
$ cmake .. -DCMAKE_BUILD_TYPE=DEBUG
|
||||
```
|
||||
|
||||
then log levels below notice do not actually get compiled in.
|
||||
|
||||
|
||||
|
||||
@section extpoll External Polling Loop support
|
||||
|
||||
**libwebsockets** maintains an internal `poll()` array for all of its
|
||||
sockets, but you can instead integrate the sockets into an
|
||||
|
@ -178,28 +225,39 @@ Four callbacks `LWS_CALLBACK_ADD_POLL_FD`, `LWS_CALLBACK_DEL_POLL_FD`,
|
|||
appear in the callback for protocol 0 and allow interface code to
|
||||
manage socket descriptors in other poll loops.
|
||||
|
||||
You can pass all pollfds that need service to `libwebsocket_service_fd()`, even
|
||||
You can pass all pollfds that need service to `lws_service_fd()`, even
|
||||
if the socket or file does not belong to **libwebsockets** it is safe.
|
||||
|
||||
If **libwebsocket** handled it, it zeros the pollfd `revents` field before returning.
|
||||
So you can let **libwebsockets** try and if `pollfd->revents` is nonzero on return,
|
||||
you know it needs handling by your code.
|
||||
|
||||
Also note that when integrating a foreign event loop like libev or libuv where
|
||||
it doesn't natively use poll() semantics, and you must return a fake pollfd
|
||||
reflecting the real event:
|
||||
|
||||
Using with in c++ apps
|
||||
----------------------
|
||||
- be sure you set .events to .revents value as well in the synthesized pollfd
|
||||
|
||||
- check the built-in support for the event loop if possible (eg, ./lib/libuv.c)
|
||||
to see how it interfaces to lws
|
||||
|
||||
- use LWS_POLLHUP / LWS_POLLIN / LWS_POLLOUT from libwebsockets.h to avoid
|
||||
losing windows compatibility
|
||||
|
||||
|
||||
@section cpp Using with in c++ apps
|
||||
|
||||
The library is ready for use by C++ apps. You can get started quickly by
|
||||
copying the test server
|
||||
|
||||
```bash
|
||||
$ cp test-server/test-server.c test.cpp
|
||||
```
|
||||
$ cp test-server/test-server.c test.cpp
|
||||
```
|
||||
|
||||
and building it in C++ like this
|
||||
|
||||
```bash
|
||||
$ g++ -DINSTALL_DATADIR=\"/usr/share\" -ocpptest test.cpp -lwebsockets
|
||||
```
|
||||
$ g++ -DINSTALL_DATADIR=\"/usr/share\" -ocpptest test.cpp -lwebsockets
|
||||
```
|
||||
|
||||
`INSTALL_DATADIR` is only needed because the test server uses it as shipped, if
|
||||
|
@ -207,18 +265,21 @@ you remove the references to it in your app you don't need to define it on
|
|||
the g++ line either.
|
||||
|
||||
|
||||
Availability of header information
|
||||
----------------------------------
|
||||
@section headerinfo Availability of header information
|
||||
|
||||
From v1.2 of the library onwards, the HTTP header content is `free()`d as soon
|
||||
as the websocket connection is established. For websocket servers, you can
|
||||
copy interesting headers by handling `LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION`
|
||||
callback, for clients there's a new callback just for this purpose
|
||||
`LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH`.
|
||||
HTTP Header information is managed by a pool of "ah" structs. These are a
|
||||
limited resource so there is pressure to free the headers and return the ah to
|
||||
the pool for reuse.
|
||||
|
||||
For that reason header information on HTTP connections that get upgraded to
|
||||
websockets is lost after the ESTABLISHED callback. Anything important that
|
||||
isn't processed by user code before then should be copied out for later.
|
||||
|
||||
For HTTP connections that don't upgrade, header info remains available the
|
||||
whole time.
|
||||
|
||||
|
||||
TCP Keepalive
|
||||
-------------
|
||||
@section ka TCP Keepalive
|
||||
|
||||
It is possible for a connection which is not being used to send to die
|
||||
silently somewhere between the peer and the side not sending. In this case
|
||||
|
@ -243,8 +304,8 @@ like Linux does. On those systems you can enable keepalive by a nonzero
|
|||
value in `ka_time`, but the systemwide kernel settings for the time / probes/
|
||||
interval are used, regardless of what nonzero value is in `ka_time`.
|
||||
|
||||
Optimizing SSL connections
|
||||
--------------------------
|
||||
|
||||
@section sslopt Optimizing SSL connections
|
||||
|
||||
There's a member `ssl_cipher_list` in the `lws_context_creation_info` struct
|
||||
which allows the user code to restrict the possible cipher selection at
|
||||
|
@ -253,16 +314,17 @@ context-creation time.
|
|||
You might want to look into that to stop the ssl peers selecting a cipher which
|
||||
is too computationally expensive. To use it, point it to a string like
|
||||
|
||||
`"RC4-MD5:RC4-SHA:AES128-SHA:AES256-SHA:HIGH:!DSS:!aNULL"`
|
||||
`"RC4-MD5:RC4-SHA:AES128-SHA:AES256-SHA:HIGH:!DSS:!aNULL"`
|
||||
|
||||
if left `NULL`, then the "DEFAULT" set of ciphers are all possible to select.
|
||||
|
||||
You can also set it to `"ALL"` to allow everything (including insecure ciphers).
|
||||
|
||||
Async nature of client connections
|
||||
----------------------------------
|
||||
|
||||
When you call `libwebsocket_client_connect(..)` and get a `wsi` back, it does not
|
||||
mean your connection is active. It just mean it started trying to connect.
|
||||
@section clientasync Async nature of client connections
|
||||
|
||||
When you call `lws_client_connect_info(..)` and get a `wsi` back, it does not
|
||||
mean your connection is active. It just means it started trying to connect.
|
||||
|
||||
Your client connection is actually active only when you receive
|
||||
`LWS_CALLBACK_CLIENT_ESTABLISHED` for it.
|
||||
|
@ -273,6 +335,611 @@ other reasons, if any of that happens you'll get a
|
|||
`wsi`.
|
||||
|
||||
After attempting the connection and getting back a non-`NULL` `wsi` you should
|
||||
loop calling `libwebsocket_service()` until one of the above callbacks occurs.
|
||||
loop calling `lws_service()` until one of the above callbacks occurs.
|
||||
|
||||
As usual, see [test-client.c](test-server/test-client.c) for example code.
|
||||
|
||||
Notice that the client connection api tries to progress the connection
|
||||
somewhat before returning. That means it's possible to get callbacks like
|
||||
CONNECTION_ERROR on the new connection before your user code had a chance to
|
||||
get the wsi returned to identify it (in fact if the connection did fail early,
|
||||
NULL will be returned instead of the wsi anyway).
|
||||
|
||||
To avoid that problem, you can fill in `pwsi` in the client connection info
|
||||
struct to point to a struct lws that get filled in early by the client
|
||||
connection api with the related wsi. You can then check for that in the
|
||||
callback to confirm the identity of the failing client connection.
|
||||
|
||||
|
||||
@section fileapi Lws platform-independent file access apis
|
||||
|
||||
lws now exposes his internal platform file abstraction in a way that can be
|
||||
both used by user code to make it platform-agnostic, and be overridden or
|
||||
subclassed by user code. This allows things like handling the URI "directory
|
||||
space" as a virtual filesystem that may or may not be backed by a regular
|
||||
filesystem. One example use is serving files from inside large compressed
|
||||
archive storage without having to unpack anything except the file being
|
||||
requested.
|
||||
|
||||
The test server shows how to use it, basically the platform-specific part of
|
||||
lws prepares a file operations structure that lives in the lws context.
|
||||
|
||||
The user code can get a pointer to the file operations struct
|
||||
|
||||
```
|
||||
LWS_VISIBLE LWS_EXTERN struct lws_plat_file_ops *
|
||||
`lws_get_fops`(struct lws_context *context);
|
||||
```
|
||||
|
||||
and then can use helpers to also leverage these platform-independent
|
||||
file handling apis
|
||||
|
||||
```
|
||||
lws_fop_fd_t
|
||||
`lws_plat_file_open`(struct lws_plat_file_ops *fops, const char *filename,
|
||||
lws_fop_flags_t *flags)
|
||||
int
|
||||
`lws_plat_file_close`(lws_fop_fd_t fop_fd)
|
||||
|
||||
unsigned long
|
||||
`lws_plat_file_seek_cur`(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
|
||||
|
||||
int
|
||||
`lws_plat_file_read`(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
|
||||
uint8_t *buf, lws_filepos_t len)
|
||||
|
||||
int
|
||||
`lws_plat_file_write`(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
|
||||
uint8_t *buf, lws_filepos_t len )
|
||||
```
|
||||
|
||||
Generic helpers are provided which provide access to generic fops information or
|
||||
call through to the above fops
|
||||
|
||||
```
|
||||
lws_filepos_t
|
||||
lws_vfs_tell(lws_fop_fd_t fop_fd);
|
||||
|
||||
lws_filepos_t
|
||||
lws_vfs_get_length(lws_fop_fd_t fop_fd);
|
||||
|
||||
uint32_t
|
||||
lws_vfs_get_mod_time(lws_fop_fd_t fop_fd);
|
||||
|
||||
lws_fileofs_t
|
||||
lws_vfs_file_seek_set(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
|
||||
|
||||
lws_fileofs_t
|
||||
lws_vfs_file_seek_end(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
|
||||
```
|
||||
|
||||
|
||||
The user code can also override or subclass the file operations, to either
|
||||
wrap or replace them. An example is shown in test server.
|
||||
|
||||
### Changes from v2.1 and before fops
|
||||
|
||||
There are several changes:
|
||||
|
||||
1) Pre-2.2 fops directly used platform file descriptors. Current fops returns and accepts a wrapper type lws_fop_fd_t which is a pointer to a malloc'd struct containing information specific to the filesystem implementation.
|
||||
|
||||
2) Pre-2.2 fops bound the fops to a wsi. This is completely removed, you just give a pointer to the fops struct that applies to this file when you open it. Afterwards, the operations in the fops just need the lws_fop_fd_t returned from the open.
|
||||
|
||||
3) Everything is wrapped in typedefs. See lws-plat-unix.c for examples of how to implement.
|
||||
|
||||
4) Position in the file, File Length, and a copy of Flags left after open are now generically held in the fop_fd.
|
||||
VFS implementation must set and manage this generic information now. See the implementations in lws-plat-unix.c for
|
||||
examples.
|
||||
|
||||
5) The file length is no longer set at a pointer provided by the open() fop. The api `lws_vfs_get_length()` is provided to
|
||||
get the file length after open.
|
||||
|
||||
6) If your file namespace is virtual, ie, is not reachable by platform fops directly, you must set LWS_FOP_FLAG_VIRTUAL
|
||||
on the flags during open.
|
||||
|
||||
7) There is an optional `mod_time` uint32_t member in the generic fop_fd. If you are able to set it during open, you
|
||||
should indicate it by setting `LWS_FOP_FLAG_MOD_TIME_VALID` on the flags.
|
||||
|
||||
@section rawfd RAW file descriptor polling
|
||||
|
||||
LWS allows you to include generic platform file descriptors in the lws service / poll / event loop.
|
||||
|
||||
Open your fd normally and then
|
||||
|
||||
```
|
||||
lws_sock_file_fd_type u;
|
||||
|
||||
u.filefd = your_open_file_fd;
|
||||
|
||||
if (!lws_adopt_descriptor_vhost(vhost, 0, u,
|
||||
"protocol-name-to-bind-to",
|
||||
optional_wsi_parent_or_NULL)) {
|
||||
// failed
|
||||
}
|
||||
|
||||
// OK
|
||||
```
|
||||
|
||||
A wsi is created for the file fd that acts like other wsi, you will get these
|
||||
callbacks on the named protocol
|
||||
|
||||
```
|
||||
LWS_CALLBACK_RAW_ADOPT_FILE
|
||||
LWS_CALLBACK_RAW_RX_FILE
|
||||
LWS_CALLBACK_RAW_WRITEABLE_FILE
|
||||
LWS_CALLBACK_RAW_CLOSE_FILE
|
||||
```
|
||||
|
||||
starting with LWS_CALLBACK_RAW_ADOPT_FILE.
|
||||
|
||||
`protocol-lws-raw-test` plugin provides a method for testing this with
|
||||
`libwebsockets-test-server-v2.0`:
|
||||
|
||||
The plugin creates a FIFO on your system called "/tmp/lws-test-raw"
|
||||
|
||||
You can feed it data through the FIFO like this
|
||||
|
||||
```
|
||||
$ sudo sh -c "echo hello > /tmp/lws-test-raw"
|
||||
```
|
||||
|
||||
This plugin simply prints the data. But it does it through the lws event
|
||||
loop / service poll.
|
||||
|
||||
@section rawsrvsocket RAW server socket descriptor polling
|
||||
|
||||
You can also enable your vhost to accept RAW socket connections, in addition to
|
||||
HTTP[s] and WS[s]. If the first bytes written on the connection are not a
|
||||
valid HTTP method, then the connection switches to RAW mode.
|
||||
|
||||
This is disabled by default, you enable it by setting the `.options` flag
|
||||
LWS_SERVER_OPTION_FALLBACK_TO_RAW when creating the vhost.
|
||||
|
||||
RAW mode socket connections receive the following callbacks
|
||||
|
||||
```
|
||||
LWS_CALLBACK_RAW_ADOPT
|
||||
LWS_CALLBACK_RAW_RX
|
||||
LWS_CALLBACK_RAW_WRITEABLE
|
||||
LWS_CALLBACK_RAW_CLOSE
|
||||
```
|
||||
|
||||
You can control which protocol on your vhost handles these RAW mode
|
||||
incoming connections by marking the selected protocol with a pvo `raw`, eg
|
||||
|
||||
```
|
||||
"protocol-lws-raw-test": {
|
||||
"status": "ok",
|
||||
"raw": "1"
|
||||
},
|
||||
```
|
||||
|
||||
The "raw" pvo marks this protocol as being used for RAW connections.
|
||||
|
||||
`protocol-lws-raw-test` plugin provides a method for testing this with
|
||||
`libwebsockets-test-server-v2.0`:
|
||||
|
||||
Run libwebsockets-test-server-v2.0 and connect to it by telnet, eg
|
||||
|
||||
```
|
||||
$ telnet 127.0.0.1 7681
|
||||
```
|
||||
|
||||
type something that isn't a valid HTTP method and enter, before the
|
||||
connection times out. The connection will switch to RAW mode using this
|
||||
protocol, and pass the unused rx as a raw RX callback.
|
||||
|
||||
The test protocol echos back what was typed on telnet to telnet.
|
||||
|
||||
@section rawclientsocket RAW client socket descriptor polling
|
||||
|
||||
You can now also open RAW socket connections in client mode.
|
||||
|
||||
Follow the usual method for creating a client connection, but set the
|
||||
`info.method` to "RAW". When the connection is made, the wsi will be
|
||||
converted to RAW mode and operate using the same callbacks as the
|
||||
server RAW sockets described above.
|
||||
|
||||
The libwebsockets-test-client supports this using raw:// URLS. To
|
||||
test, open a netcat listener in one window
|
||||
|
||||
```
|
||||
$ nc -l 9999
|
||||
```
|
||||
|
||||
and in another window, connect to it using the test client
|
||||
|
||||
```
|
||||
$ libwebsockets-test-client raw://127.0.0.1:9999
|
||||
```
|
||||
|
||||
The connection should succeed, and text typed in the netcat window (including a CRLF)
|
||||
will be received in the client.
|
||||
|
||||
@section ecdh ECDH Support
|
||||
|
||||
ECDH Certs are now supported. Enable the CMake option
|
||||
|
||||
cmake .. -DLWS_SSL_SERVER_WITH_ECDH_CERT=1
|
||||
|
||||
**and** the info->options flag
|
||||
|
||||
LWS_SERVER_OPTION_SSL_ECDH
|
||||
|
||||
to build in support and select it at runtime.
|
||||
|
||||
@section sslinfo SSL info callbacks
|
||||
|
||||
OpenSSL allows you to receive callbacks for various events defined in a
|
||||
bitmask in openssl/ssl.h. The events include stuff like TLS Alerts.
|
||||
|
||||
By default, lws doesn't register for these callbacks.
|
||||
|
||||
However if you set the info.ssl_info_event_mask to nonzero (ie, set some
|
||||
of the bits in it like `SSL_CB_ALERT` at vhost creation time, then
|
||||
connections to that vhost will call back using LWS_CALLBACK_SSL_INFO
|
||||
for the wsi, and the `in` parameter will be pointing to a struct of
|
||||
related args:
|
||||
|
||||
```
|
||||
struct lws_ssl_info {
|
||||
int where;
|
||||
int ret;
|
||||
};
|
||||
```
|
||||
|
||||
The default callback handler in lws has a handler for LWS_CALLBACK_SSL_INFO
|
||||
which prints the related information, You can test it using the switch
|
||||
-S -s on `libwebsockets-test-server-v2.0`.
|
||||
|
||||
Returning nonzero from the callback will close the wsi.
|
||||
|
||||
@section smp SMP / Multithreaded service
|
||||
|
||||
SMP support is integrated into LWS without any internal threading. It's
|
||||
very simple to use, libwebsockets-test-server-pthread shows how to do it,
|
||||
use -j <n> argument there to control the number of service threads up to 32.
|
||||
|
||||
Two new members are added to the info struct
|
||||
|
||||
unsigned int count_threads;
|
||||
unsigned int fd_limit_per_thread;
|
||||
|
||||
leave them at the default 0 to get the normal singlethreaded service loop.
|
||||
|
||||
Set count_threads to n to tell lws you will have n simultaneous service threads
|
||||
operating on the context.
|
||||
|
||||
There is still a single listen socket on one port, no matter how many
|
||||
service threads.
|
||||
|
||||
When a connection is made, it is accepted by the service thread with the least
|
||||
connections active to perform load balancing.
|
||||
|
||||
The user code is responsible for spawning n threads running the service loop
|
||||
associated to a specific tsi (Thread Service Index, 0 .. n - 1). See
|
||||
the libwebsockets-test-server-pthread for how to do.
|
||||
|
||||
If you leave fd_limit_per_thread at 0, then the process limit of fds is shared
|
||||
between the service threads; if you process was allowed 1024 fds overall then
|
||||
each thread is limited to 1024 / n.
|
||||
|
||||
You can set fd_limit_per_thread to a nonzero number to control this manually, eg
|
||||
the overall supported fd limit is less than the process allowance.
|
||||
|
||||
You can control the context basic data allocation for multithreading from Cmake
|
||||
using -DLWS_MAX_SMP=, if not given it's set to 32. The serv_buf allocation
|
||||
for the threads (currently 4096) is made at runtime only for active threads.
|
||||
|
||||
Because lws will limit the requested number of actual threads supported
|
||||
according to LWS_MAX_SMP, there is an api lws_get_count_threads(context) to
|
||||
discover how many threads were actually allowed when the context was created.
|
||||
|
||||
It's required to implement locking in the user code in the same way that
|
||||
libwebsockets-test-server-pthread does it, for the FD locking callbacks.
|
||||
|
||||
There is no knowledge or dependency in lws itself about pthreads. How the
|
||||
locking is implemented is entirely up to the user code.
|
||||
|
||||
|
||||
@section libevuv Libev / Libuv support
|
||||
|
||||
You can select either or both
|
||||
|
||||
-DLWS_WITH_LIBEV=1
|
||||
-DLWS_WITH_LIBUV=1
|
||||
|
||||
at cmake configure-time. The user application may use one of the
|
||||
context init options flags
|
||||
|
||||
LWS_SERVER_OPTION_LIBEV
|
||||
LWS_SERVER_OPTION_LIBUV
|
||||
|
||||
to indicate it will use either of the event libraries.
|
||||
|
||||
|
||||
@section extopts Extension option control from user code
|
||||
|
||||
User code may set per-connection extension options now, using a new api
|
||||
`lws_set_extension_option()`.
|
||||
|
||||
This should be called from the ESTABLISHED callback like this
|
||||
```
|
||||
lws_set_extension_option(wsi, "permessage-deflate",
|
||||
"rx_buf_size", "12"); /* 1 << 12 */
|
||||
```
|
||||
|
||||
If the extension is not active (missing or not negotiated for the
|
||||
connection, or extensions are disabled on the library) the call is
|
||||
just returns -1. Otherwise the connection's extension has its
|
||||
named option changed.
|
||||
|
||||
The extension may decide to alter or disallow the change, in the
|
||||
example above permessage-deflate restricts the size of his rx
|
||||
output buffer also considering the protocol's rx_buf_size member.
|
||||
|
||||
|
||||
@section httpsclient Client connections as HTTP[S] rather than WS[S]
|
||||
|
||||
You may open a generic http client connection using the same
|
||||
struct lws_client_connect_info used to create client ws[s]
|
||||
connections.
|
||||
|
||||
To stay in http[s], set the optional info member "method" to
|
||||
point to the string "GET" instead of the default NULL.
|
||||
|
||||
After the server headers are processed, when payload from the
|
||||
server is available the callback LWS_CALLBACK_RECEIVE_CLIENT_HTTP
|
||||
will be made.
|
||||
|
||||
You can choose whether to process the data immediately, or
|
||||
queue a callback when an outgoing socket is writeable to provide
|
||||
flow control, and process the data in the writable callback.
|
||||
|
||||
Either way you use the api `lws_http_client_read()` to access the
|
||||
data, eg
|
||||
|
||||
```
|
||||
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
|
||||
{
|
||||
char buffer[1024 + LWS_PRE];
|
||||
char *px = buffer + LWS_PRE;
|
||||
int lenx = sizeof(buffer) - LWS_PRE;
|
||||
|
||||
lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP\n");
|
||||
|
||||
/*
|
||||
* Often you need to flow control this by something
|
||||
* else being writable. In that case call the api
|
||||
* to get a callback when writable here, and do the
|
||||
* pending client read in the writeable callback of
|
||||
* the output.
|
||||
*/
|
||||
if (lws_http_client_read(wsi, &px, &lenx) < 0)
|
||||
return -1;
|
||||
while (lenx--)
|
||||
putchar(*px++);
|
||||
}
|
||||
break;
|
||||
```
|
||||
|
||||
Notice that if you will use SSL client connections on a vhost, you must
|
||||
prepare the client SSL context for the vhost after creating the vhost, since
|
||||
this is not normally done if the vhost was set up to listen / serve. Call
|
||||
the api lws_init_vhost_client_ssl() to also allow client SSL on the vhost.
|
||||
|
||||
|
||||
|
||||
@section vhosts Using lws vhosts
|
||||
|
||||
If you set LWS_SERVER_OPTION_EXPLICIT_VHOSTS options flag when you create
|
||||
your context, it won't create a default vhost using the info struct
|
||||
members for compatibility. Instead you can call lws_create_vhost()
|
||||
afterwards to attach one or more vhosts manually.
|
||||
|
||||
```
|
||||
LWS_VISIBLE struct lws_vhost *
|
||||
lws_create_vhost(struct lws_context *context,
|
||||
struct lws_context_creation_info *info);
|
||||
```
|
||||
|
||||
lws_create_vhost() uses the same info struct as lws_create_context(),
|
||||
it ignores members related to context and uses the ones meaningful
|
||||
for vhost (marked with VH in libwebsockets.h).
|
||||
|
||||
```
|
||||
struct lws_context_creation_info {
|
||||
int port; /* VH */
|
||||
const char *iface; /* VH */
|
||||
const struct lws_protocols *protocols; /* VH */
|
||||
const struct lws_extension *extensions; /* VH */
|
||||
...
|
||||
```
|
||||
|
||||
When you attach the vhost, if the vhost's port already has a listen socket
|
||||
then both vhosts share it and use SNI (is SSL in use) or the Host: header
|
||||
from the client to select the right one. Or if no other vhost already
|
||||
listening the a new listen socket is created.
|
||||
|
||||
There are some new members but mainly it's stuff you used to set at
|
||||
context creation time.
|
||||
|
||||
|
||||
@section sni How lws matches hostname or SNI to a vhost
|
||||
|
||||
LWS first strips any trailing :port number.
|
||||
|
||||
Then it tries to find an exact name match for a vhost listening on the correct
|
||||
port, ie, if SNI or the Host: header provided abc.com:1234, it will match on a
|
||||
vhost named abc.com that is listening on port 1234.
|
||||
|
||||
If there is no exact match, lws will consider wildcard matches, for example
|
||||
if cats.abc.com:1234 is provided by the client by SNI or Host: header, it will
|
||||
accept a vhost "abc.com" listening on port 1234. If there was a better, exact,
|
||||
match, it will have been chosen in preference to this.
|
||||
|
||||
Connections with SSL will still have the client go on to check the
|
||||
certificate allows wildcards and error out if not.
|
||||
|
||||
|
||||
|
||||
@section mounts Using lws mounts on a vhost
|
||||
|
||||
The last argument to lws_create_vhost() lets you associate a linked
|
||||
list of lws_http_mount structures with that vhost's URL 'namespace', in
|
||||
a similar way that unix lets you mount filesystems into areas of your /
|
||||
filesystem how you like and deal with the contents transparently.
|
||||
|
||||
```
|
||||
struct lws_http_mount {
|
||||
struct lws_http_mount *mount_next;
|
||||
const char *mountpoint; /* mountpoint in http pathspace, eg, "/" */
|
||||
const char *origin; /* path to be mounted, eg, "/var/www/warmcat.com" */
|
||||
const char *def; /* default target, eg, "index.html" */
|
||||
|
||||
struct lws_protocol_vhost_options *cgienv;
|
||||
|
||||
int cgi_timeout;
|
||||
int cache_max_age;
|
||||
|
||||
unsigned int cache_reusable:1;
|
||||
unsigned int cache_revalidate:1;
|
||||
unsigned int cache_intermediaries:1;
|
||||
|
||||
unsigned char origin_protocol;
|
||||
unsigned char mountpoint_len;
|
||||
};
|
||||
```
|
||||
|
||||
The last mount structure should have a NULL mount_next, otherwise it should
|
||||
point to the 'next' mount structure in your list.
|
||||
|
||||
Both the mount structures and the strings must persist until the context is
|
||||
destroyed, since they are not copied but used in place.
|
||||
|
||||
`.origin_protocol` should be one of
|
||||
|
||||
```
|
||||
enum {
|
||||
LWSMPRO_HTTP,
|
||||
LWSMPRO_HTTPS,
|
||||
LWSMPRO_FILE,
|
||||
LWSMPRO_CGI,
|
||||
LWSMPRO_REDIR_HTTP,
|
||||
LWSMPRO_REDIR_HTTPS,
|
||||
LWSMPRO_CALLBACK,
|
||||
};
|
||||
```
|
||||
|
||||
- LWSMPRO_FILE is used for mapping url namespace to a filesystem directory and
|
||||
serve it automatically.
|
||||
|
||||
- LWSMPRO_CGI associates the url namespace with the given CGI executable, which
|
||||
runs when the URL is accessed and the output provided to the client.
|
||||
|
||||
- LWSMPRO_REDIR_HTTP and LWSMPRO_REDIR_HTTPS auto-redirect clients to the given
|
||||
origin URL.
|
||||
|
||||
- LWSMPRO_CALLBACK causes the http connection to attach to the callback
|
||||
associated with the named protocol (which may be a plugin).
|
||||
|
||||
|
||||
@section mountcallback Operation of LWSMPRO_CALLBACK mounts
|
||||
|
||||
The feature provided by CALLBACK type mounts is binding a part of the URL
|
||||
namespace to a named protocol callback handler.
|
||||
|
||||
This allows protocol plugins to handle areas of the URL namespace. For example
|
||||
in test-server-v2.0.c, the URL area "/formtest" is associated with the plugin
|
||||
providing "protocol-post-demo" like this
|
||||
|
||||
```
|
||||
static const struct lws_http_mount mount_post = {
|
||||
NULL, /* linked-list pointer to next*/
|
||||
"/formtest", /* mountpoint in URL namespace on this vhost */
|
||||
"protocol-post-demo", /* handler */
|
||||
NULL, /* default filename if none given */
|
||||
NULL,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
LWSMPRO_CALLBACK, /* origin points to a callback */
|
||||
9, /* strlen("/formtest"), ie length of the mountpoint */
|
||||
};
|
||||
```
|
||||
|
||||
Client access to /formtest[anything] will be passed to the callback registered
|
||||
with the named protocol, which in this case is provided by a protocol plugin.
|
||||
|
||||
Access by all methods, eg, GET and POST are handled by the callback.
|
||||
|
||||
protocol-post-demo deals with accepting and responding to the html form that
|
||||
is in the test server HTML.
|
||||
|
||||
When a connection accesses a URL related to a CALLBACK type mount, the
|
||||
connection protocol is changed until the next access on the connection to a
|
||||
URL outside the same CALLBACK mount area. User space on the connection is
|
||||
arranged to be the size of the new protocol user space allocation as given in
|
||||
the protocol struct.
|
||||
|
||||
This allocation is only deleted / replaced when the connection accesses a
|
||||
URL region with a different protocol (or the default protocols[0] if no
|
||||
CALLBACK area matches it).
|
||||
|
||||
@section BINDTODEV SO_BIND_TO_DEVICE
|
||||
|
||||
The .bind_iface flag in the context / vhost creation struct lets you
|
||||
declare that you want all traffic for listen and transport on that
|
||||
vhost to be strictly bound to the network interface named in .iface.
|
||||
|
||||
This Linux-only feature requires SO_BIND_TO_DEVICE, which in turn
|
||||
requires CAP_NET_RAW capability... root has this capability.
|
||||
|
||||
However this feature needs to apply the binding also to accepted
|
||||
sockets during normal operation, which implies the server must run
|
||||
the whole time as root.
|
||||
|
||||
You can avoid this by using the Linux capabilities feature to have
|
||||
the unprivileged user inherit just the CAP_NET_RAW capability.
|
||||
|
||||
You can confirm this with the test server
|
||||
|
||||
|
||||
```
|
||||
$ sudo /usr/local/bin/libwebsockets-test-server -u agreen -i eno1 -k
|
||||
```
|
||||
|
||||
The part that ensures the capability is inherited by the unprivileged
|
||||
user is
|
||||
|
||||
```
|
||||
#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
|
||||
info.caps[0] = CAP_NET_RAW;
|
||||
info.count_caps = 1;
|
||||
#endif
|
||||
```
|
||||
|
||||
|
||||
@section dim Dimming webpage when connection lost
|
||||
|
||||
The lws test plugins' html provides useful feedback on the webpage about if it
|
||||
is still connected to the server, by greying out the page if not. You can
|
||||
also add this to your own html easily
|
||||
|
||||
- include lws-common.js from your HEAD section
|
||||
|
||||
<script src="/lws-common.js"></script>
|
||||
|
||||
- dim the page during initialization, in a script section on your page
|
||||
|
||||
lws_gray_out(true,{'zindex':'499'});
|
||||
|
||||
- in your ws onOpen(), remove the dimming
|
||||
|
||||
lws_gray_out(false);
|
||||
|
||||
- in your ws onClose(), reapply the dimming
|
||||
|
||||
lws_gray_out(true,{'zindex':'499'});
|
||||
|
|
23
README.esp32.md
Normal file
23
README.esp32.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
ESP32 Support
|
||||
=============
|
||||
|
||||
Lws provides a "factory" application
|
||||
|
||||
https://github.com/warmcat/lws-esp32-factory
|
||||
|
||||
and a test application which implements the generic lws server test apps
|
||||
|
||||
https://github.com/warmcat/lws-esp32-test-server-demos
|
||||
|
||||
The behaviours of the generic factory are are quite rich, and cover uploading SSL certs through factory and user configuration, AP selection and passphrase entry, and managing a switch to allow the user to force entry to user setup mode at boot subsequently.
|
||||
|
||||
The factory app comes with partitioning for a 1MB factory partition containing that app and data, and a single 2.9MB OTA partition containing the main app.
|
||||
|
||||
The factory app is able to do OTA updates for both the factory and OTA partition slots; updating the factory slot first writes the new image to the OTA slot and copies it into place at the next boot, after which the user can reload the OTA slot.
|
||||
|
||||
State|Image|AP SSID|Port|URL|Mode
|
||||
---|---|---|---|---|---
|
||||
Factory Reset or Uninitialized|Factory|AP: ESP_012345|80|http://192.168.4.1|factory.html - to set certificates and serial
|
||||
User configuration|Factory|AP: config-model-serial|443|https://192.168.4.1|index.html - user set up his AP information
|
||||
Operation|OTA|Station only|443|https://model-serial.local|OTA application
|
||||
|
34
README.esp8266.md
Normal file
34
README.esp8266.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
ESP8266 lws port
|
||||
----------------
|
||||
|
||||
lws can now work well on the ESP8266.
|
||||
|
||||
You should get the ESP8266 Espressif SDK-based project here
|
||||
|
||||
https://github.com/lws-team/esplws
|
||||
|
||||
which includes lws as an "app" in the build. The project provides full AP-based setup over the web, and once the device has been configured to associate to a local AP, a separate station vhost with the lws test protocols.
|
||||
|
||||
Instructions for building that are here
|
||||
|
||||
https://github.com/lws-team/esplws/blob/master/README.md
|
||||
|
||||
There are also instructions there for how to remove the test apps from the build and customize your own station content.
|
||||
|
||||
|
||||
Information about lws integration on ESP8266
|
||||
--------------------------------------------
|
||||
|
||||
The following existing lws features are used to make a nice integration:
|
||||
|
||||
- vhosts: there are separate vhosts for the configuration AP mode and the normal station mode.
|
||||
|
||||
- file_ops: the lws file operations are overridden and handled by a ROMFS parser
|
||||
|
||||
- mounts: mounts are used to serve files automatically from the ROMFS
|
||||
|
||||
- plugins: standalone protocol plugins are included into the build, so there are clean individual implementations for each protocol, while everything is statically linked
|
||||
|
||||
- lws stability and security features like bytewise parsers, sophisticated timeouts, http/1.1 keepalive support
|
||||
|
||||
|
373
README.generic-sessions.md
Normal file
373
README.generic-sessions.md
Normal file
|
@ -0,0 +1,373 @@
|
|||
Notes about generic-sessions Plugin
|
||||
===================================
|
||||
|
||||
@section gseb Enabling lwsgs for build
|
||||
|
||||
Enable at CMake with -DLWS_WITH_GENERIC_SESSIONS=1
|
||||
|
||||
This also needs sqlite3 (libsqlite3-dev or similar package)
|
||||
|
||||
|
||||
@section gsi lwsgs Introduction
|
||||
|
||||
The generic-sessions protocol plugin provides cookie-based login
|
||||
authentication for lws web and ws connections.
|
||||
|
||||
The plugin handles everything about generic account registration,
|
||||
email verification, lost password, account deletion, and other generic account
|
||||
management.
|
||||
|
||||
Other code, in another eg, ws protocol handler, only needs very high-level
|
||||
state information from generic-sessions, ie, which user the client is
|
||||
authenticated as. Everything underneath is managed in generic-sessions.
|
||||
|
||||
|
||||
- random 20-byte session id managed in a cookie
|
||||
|
||||
- all information related to the session held at the server, nothing managed clientside
|
||||
|
||||
- sqlite3 used at the server to manage active sessions and users
|
||||
|
||||
- defaults to creating anonymous sessions with no user associated
|
||||
|
||||
- admin account (with user-selectable username) is defined in config with a SHA-1 of the password; rest of the accounts are in sqlite3
|
||||
|
||||
- user account passwords stored as salted SHA-1 with additional confounder
|
||||
only stored in the JSON config, not the database
|
||||
|
||||
- login, logout, register account + email verification built-in with examples
|
||||
|
||||
- in a mount, some file suffixes (ie, .js) can be associated with a protocol for the purposes of rewriting symbolnames. These are read-only copies of logged-in server state.
|
||||
|
||||
- When your page fetches .js or other rewritten files from that mount, "$lwsgs_user" and so on are rewritten on the fly using chunked transfer encoding
|
||||
|
||||
- Eliminates server-side scripting with a few rewritten symbols and
|
||||
javascript on client side
|
||||
|
||||
- 32-bit bitfield for authentication sectoring, mounts can provide a mask on the loggin-in session's associated server-side bitfield that must be set for access.
|
||||
|
||||
- No code (just config) required for, eg, private URL namespace that requires login to access.
|
||||
|
||||
|
||||
@section gsin Lwsgs Integration to HTML
|
||||
|
||||
Only three steps are needed to integrate lwsgs in your HTML.
|
||||
|
||||
1) lwsgs HTML UI is bundled with the javascript it uses in `lwsgs.js`, so
|
||||
import that script file in your head section
|
||||
|
||||
2) define an empty div of id "lwsgs" somewhere
|
||||
|
||||
3) Call lwsgs_initial() in your page
|
||||
|
||||
That's it. An example is below
|
||||
|
||||
```
|
||||
<html>
|
||||
<head>
|
||||
<script src="lwsgs.js"></script>
|
||||
<style>
|
||||
.body { font-size: 12 }
|
||||
.gstitle { font-size: 18 }
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-image:url(seats.jpg)">
|
||||
<table style="width:100%;transition: max-height 2s;">
|
||||
<tr>
|
||||
<td style="vertical-align:top;text-align:left;width=200px">
|
||||
<img src="lwsgs-logo.png">
|
||||
</td>
|
||||
<td style="vertical-align:top;float:right">
|
||||
<div id=lwsgs style="text-align:right;background-color: rgba(255, 255, 255, 0.8);"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<script>lwsgs_initial();</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
@section gsof Lwsgs Overall Flow@
|
||||
|
||||
When the protocol is initialized, it gets per-vhost information from the config, such
|
||||
as where the sqlite3 databases are to be stored. The admin username and sha-1 of the
|
||||
admin password are also taken from here.
|
||||
|
||||
In the mounts using protocol-generic-sessions, a cookie is maintained against any requests; if no cookie was active on the initial request a new session is
|
||||
created with no attached user.
|
||||
|
||||
So there should always be an active session after any transactions with the server.
|
||||
|
||||
In the example html going to the mount /lwsgs loads a login / register page as the default.
|
||||
|
||||
The <form> in the login page contains 'next url' hidden inputs that let the html 'program' where the form handler will go after a successful admin login, a successful user login and a failed login.
|
||||
|
||||
After a successful login, the sqlite record at the server for the current session is updated to have the logged-in username associated with it.
|
||||
|
||||
|
||||
|
||||
@section gsconf Lwsgs Configuration
|
||||
|
||||
"auth-mask" defines the authorization sector bits that must be enabled on the session to gain access.
|
||||
|
||||
"auth-mask" 0 is the default.
|
||||
|
||||
- b0 is set if you are logged in as a user at all.
|
||||
- b1 is set if you are logged in with the user configured to be admin
|
||||
- b2 is set if the account has been verified (the account configured for admin is always verified)
|
||||
- b3 is set if your session just did the forgot password flow successfully
|
||||
|
||||
```
|
||||
{
|
||||
# things in here can always be served
|
||||
"mountpoint": "/lwsgs",
|
||||
"origin": "file:///usr/share/libwebsockets-test-server/generic-sessions",
|
||||
"origin": "callback://protocol-lws-messageboard",
|
||||
"default": "generic-sessions-login-example.html",
|
||||
"auth-mask": "0",
|
||||
"interpret": {
|
||||
".js": "protocol-lws-messageboard"
|
||||
}
|
||||
}, {
|
||||
# things in here can only be served if logged in as a user
|
||||
"mountpoint": "/lwsgs/needauth",
|
||||
"origin": "file:///usr/share/libwebsockets-test-server/generic-sessions/needauth",
|
||||
"origin": "callback://protocol-lws-messageboard",
|
||||
"default": "generic-sessions-login-example.html",
|
||||
"auth-mask": "5", # logged in as a verified user
|
||||
"interpret": {
|
||||
".js": "protocol-lws-messageboard"
|
||||
}
|
||||
}, {
|
||||
# things in here can only be served if logged in as admin
|
||||
"mountpoint": "/lwsgs/needadmin",
|
||||
"origin": "file:///usr/share/libwebsockets-test-server/generic-sessions/needadmin",
|
||||
"origin": "callback://protocol-lws-messageboard",
|
||||
"default": "generic-sessions-login-example.html",
|
||||
"auth-mask": "7", # b2 = verified (by email / or admin), b1 = admin, b0 = logged in with any user name
|
||||
"interpret": {
|
||||
".js": "protocol-lws-messageboard"
|
||||
}
|
||||
}
|
||||
```
|
||||
Note that the name of the real application protocol that uses generic-sessions
|
||||
is used, not generic-sessions itself.
|
||||
|
||||
The vhost configures the storage dir, admin credentials and session cookie lifetimes:
|
||||
|
||||
```
|
||||
"ws-protocols": [{
|
||||
"protocol-generic-sessions": {
|
||||
"status": "ok",
|
||||
"admin-user": "admin",
|
||||
|
||||
# create the pw hash like this (for the example pw, "jipdocesExunt" )
|
||||
# $ echo -n "jipdocesExunt" | sha1sum
|
||||
# 046ce9a9cca769e85798133be06ef30c9c0122c9 -
|
||||
#
|
||||
# Obviously ** change this password hash to a secret one before deploying **
|
||||
#
|
||||
"admin-password-sha1": "046ce9a9cca769e85798133be06ef30c9c0122c9",
|
||||
"session-db": "/var/www/sessions/lws.sqlite3",
|
||||
"timeout-idle-secs": "600",
|
||||
"timeout-anon-idle-secs": "1200",
|
||||
"timeout-absolute-secs": "6000",
|
||||
# the confounder is part of the salted password hashes. If this config
|
||||
# file is in a 0700 root:root dir, an attacker with apache credentials
|
||||
# will have to get the confounder out of the process image to even try
|
||||
# to guess the password hashes.
|
||||
"confounder": "Change to <=31 chars of junk",
|
||||
|
||||
"email-from": "noreply@example.com",
|
||||
"email-smtp-ip": "127.0.0.1",
|
||||
"email-expire": "3600",
|
||||
"email-helo": "myhost.com",
|
||||
"email-contact-person": "Set Me <real-person@email.com>",
|
||||
"email-confirm-url-base": "http://localhost:7681/lwsgs"
|
||||
}
|
||||
```
|
||||
|
||||
The email- related settings control generation of automatic emails for
|
||||
registration and forgotten password.
|
||||
|
||||
- `email-from`: The email address automatic emails are sent from
|
||||
|
||||
- `email-smtp-ip`: Normally 127.0.0.1, if you have a suitable server on port
|
||||
25 on your lan you can use this instead here.
|
||||
|
||||
- `email-expire`: Seconds that links sent in email will work before being
|
||||
deleted
|
||||
|
||||
- `email-helo`: HELO to use when communicating with your SMTP server
|
||||
|
||||
- `email-contact-person`: mentioned in the automatic emails as a human who can
|
||||
answer questions
|
||||
|
||||
- `email-confirm-url-base`: the URL to start links with in the emails, so the
|
||||
recipient can get back to the web server
|
||||
|
||||
The real protocol that makes use of generic-sessions must also be listed and
|
||||
any configuration it needs given
|
||||
|
||||
```
|
||||
"protocol-lws-messageboard": {
|
||||
"status": "ok",
|
||||
"message-db": "/var/www/sessions/messageboard.sqlite3"
|
||||
},
|
||||
```
|
||||
|
||||
Notice the real application uses his own sqlite db, no details about how
|
||||
generic-sessions works or how it stores data are available to it.
|
||||
|
||||
|
||||
@section gspwc Lwsgs Password Confounder
|
||||
|
||||
You can also define a per-vhost confounder shown in the example above, used
|
||||
when aggregating the password with the salt when it is hashed. Any attacker
|
||||
will also need to get the confounder along with the database, which you can
|
||||
make harder by making the config dir only eneterable / readable by root.
|
||||
|
||||
|
||||
@section gsprep Lwsgs Preparing the db directory
|
||||
|
||||
You will have to prepare the db directory so it's suitable for the lwsws user to use,
|
||||
that usually means apache, eg
|
||||
|
||||
```
|
||||
# mkdir -p /var/www/sessions
|
||||
# chown root:apache /var/www/sessions
|
||||
# chmod 770 /var/www/sessions
|
||||
```
|
||||
|
||||
@section gsrmail Lwsgs Email configuration
|
||||
|
||||
lwsgs will can send emails by talking to an SMTP server on localhost:25. That
|
||||
will usually be sendmail or postfix, you should confirm that works first by
|
||||
itself using the `mail` application to send on it.
|
||||
|
||||
lwsgs has been tested on stock Fedora sendmail and postfix.
|
||||
|
||||
|
||||
@section gsap Lwsgs Integration with another protocol
|
||||
|
||||
lwsgs is designed to provide sessions and accounts in a standalone and generic way.
|
||||
|
||||
But it's not useful by itself, there will always be the actual application who wants
|
||||
to make use of generic-sessions features.
|
||||
|
||||
We provide the "messageboard" plugin as an example of how to integrate with
|
||||
your actual application protocol.
|
||||
|
||||
The basic approach is the 'real' protocol handler (usually a plugin itself)
|
||||
subclasses the generic-sessions plugin and calls through to it by default.
|
||||
|
||||
The "real" protocol handler entirely deals with ws-related stuff itself, since
|
||||
generic-sessions does not use ws. But for
|
||||
|
||||
- LWS_CALLBACK_HTTP
|
||||
- LWS_CALLBACK_HTTP_BODY
|
||||
- LWS_CALLBACK_HTTP_BODY_COMPLETION
|
||||
- LWS_CALLBACK_HTTP_DROP_PROTOCOL
|
||||
|
||||
the "real" protocol handler checks if it recognizes the activity (eg, his own
|
||||
POST form URL) and if not, passes stuff through to the generic-sessions protocol callback to handle it. To simplify matters the real protocol can just pass
|
||||
through any unhandled messages to generic-sessions.
|
||||
|
||||
The "real" protocol can get a pointer to generic-sessions protocol on the
|
||||
same vhost using
|
||||
|
||||
```
|
||||
vhd->gsp = lws_vhost_name_to_protocol(vhd->vh, "protocol-generic-sessions");
|
||||
```
|
||||
|
||||
The "real" protocol must also arrange generic-sessions per_session_data in his
|
||||
own per-session allocation. To allow keeping generic-sessions opaque, the
|
||||
real protocol must allocate that space at runtime, using the pss size
|
||||
the generic-sessions protocol struct exposes
|
||||
|
||||
```
|
||||
struct per_session_data__myapp {
|
||||
void *pss_gs;
|
||||
...
|
||||
|
||||
pss->pss_gs = malloc(vhd->gsp->per_session_data_size);
|
||||
```
|
||||
|
||||
The allocation reserved for generic-sessions is then used as user_space when
|
||||
the real protocol calls through to the generic-sessions callback
|
||||
|
||||
```
|
||||
vhd->gsp->callback(wsi, reason, &pss->pss_gs, in, len);
|
||||
```
|
||||
|
||||
In that way the "real" protocol can subclass generic-sessions functionality.
|
||||
|
||||
|
||||
To ease management of these secondary allocations, there are callbacks that
|
||||
occur when a wsi binds to a protocol and when the binding is dropped. These
|
||||
should be used to malloc and free and kind of per-connection
|
||||
secondary allocations.
|
||||
|
||||
```
|
||||
case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
|
||||
if (!pss || pss->pss_gs)
|
||||
break;
|
||||
|
||||
pss->pss_gs = malloc(vhd->gsp->per_session_data_size);
|
||||
if (!pss->pss_gs)
|
||||
return -1;
|
||||
|
||||
memset(pss->pss_gs, 0, vhd->gsp->per_session_data_size);
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
|
||||
if (vhd->gsp->callback(wsi, reason, pss ? pss->pss_gs : NULL, in, len))
|
||||
return -1;
|
||||
|
||||
if (pss->pss_gs) {
|
||||
free(pss->pss_gs);
|
||||
pss->pss_gs = NULL;
|
||||
}
|
||||
break;
|
||||
```
|
||||
|
||||
|
||||
#section gsapsib Getting session-specific information from another protocol
|
||||
|
||||
At least at the time when someone tries to upgrade an http(s) connection to
|
||||
ws(s) with your real protocol, it is necessary to confirm the cookie the http(s)
|
||||
connection has with generic-sessions and find out his username and other info.
|
||||
|
||||
Generic sessions lets another protocol check it again by calling his callback,
|
||||
and lws itself provides a generic session info struct to pass the related data
|
||||
|
||||
```
|
||||
struct lws_session_info {
|
||||
char username[32];
|
||||
char email[100];
|
||||
char ip[72];
|
||||
unsigned int mask;
|
||||
char session[42];
|
||||
};
|
||||
|
||||
struct lws_session_info sinfo;
|
||||
...
|
||||
vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO,
|
||||
&pss->pss_gs, &sinfo, 0);
|
||||
```
|
||||
|
||||
After the call to generic-sessions, the results can be
|
||||
|
||||
- all the strings will be zero-length and .mask zero, there is no usable cookie
|
||||
|
||||
- only .ip and .session are set: the cookie is OK but no user logged in
|
||||
|
||||
- all the strings contain information about the logged-in user
|
||||
|
||||
the real protocol can use this to reject attempts to open ws connections from
|
||||
http connections that are not authenticated; afterwards there's no need to
|
||||
check the ws connection auth status again.
|
||||
|
219
README.generic-table.md
Normal file
219
README.generic-table.md
Normal file
|
@ -0,0 +1,219 @@
|
|||
Notes about generic-table
|
||||
=========================
|
||||
|
||||
@section gtint What is generic-table?
|
||||
|
||||
Generic-table is a JSON schema and client-side JS file that makes it easy to
|
||||
display live, table structured HTML over a ws link.
|
||||
|
||||
An example plugin and index.html using it are provided, but lwsgt itself doesn't
|
||||
have its own plugin, it's just a JSON schema and client-side JS that other
|
||||
plugins can use to simplify displaying live, table-based data without having
|
||||
to reinvent the wheel each time.
|
||||
|
||||
The ws protocol sends JSON describing the table, and then JSON updating the table
|
||||
contents when it chooses, the brower table is updated automatically, live.
|
||||
|
||||
\image html lwsgt-overview.png
|
||||
|
||||
- Example protocol plugin (displays directory contents): https://github.com/warmcat/libwebsockets/tree/master/plugins/generic-table/protocol_table_dirlisting.c
|
||||
|
||||
- Example HTML: https://github.com/warmcat/libwebsockets/tree/master/plugins/generic-table/assets/index.html
|
||||
|
||||
- lwsgt.js (client-side table rendering / ws link management): https://github.com/warmcat/libwebsockets/tree/master/plugins/generic-table/assets/lwsgt.js
|
||||
|
||||
|
||||
@section gteb Enabling for build
|
||||
|
||||
Enable the demo plugin at CMake with -DLWS_WITH_PLUGINS=1
|
||||
|
||||
|
||||
@section gtinth Integrating with your html
|
||||
|
||||
- In your HEAD section, include lwsgt.js
|
||||
|
||||
```
|
||||
<script src="lwsgt.js"></script>
|
||||
```
|
||||
|
||||
- Also in your HEAD section, style the lwsgt CSS, eg
|
||||
|
||||
```
|
||||
<style>
|
||||
.lwsgt_title { font-size: 24; text-align:center }
|
||||
.lwsgt_breadcrumbs { font-size: 18; text-align:left }
|
||||
.lwsgt_table { font-size: 14; padding:12px; margin: 12px; align:center }
|
||||
.lwsgt_hdr { font-size: 18; text-align:center;
|
||||
background-color: rgba(40, 40, 40, 0.8); color: white }
|
||||
.lwsgt_tr { padding: 10px }
|
||||
.lwsgt_td { padding: 3px }
|
||||
</style>
|
||||
```
|
||||
|
||||
You can skip this but the result will be less beautiful until some CSS is
|
||||
provided.
|
||||
|
||||
- In your body section, declare a div with an id (can be whatever you want)
|
||||
|
||||
```
|
||||
<tr><td><div id="lwsgt1" class="group1"></div></td></tr>
|
||||
```
|
||||
|
||||
lwsgt JS will put its content there.
|
||||
|
||||
- Finally in a <script> at the end of your page, instantiate lwsgt and
|
||||
provide a custom callback for clickable links
|
||||
|
||||
```
|
||||
<script>
|
||||
var v1 = new lwsgt_initial("Dir listing demo",
|
||||
"protocol-lws-table-dirlisting",
|
||||
"lwsgt1", "lwsgt_dir_click", "v1");
|
||||
|
||||
function lwsgt_dir_click(gt, u, col, row)
|
||||
{
|
||||
if (u[0] == '=') { /* change directory */
|
||||
window[gt].lwsgt_ws.send(u.substring(1, u.length));
|
||||
return;
|
||||
}
|
||||
var win = window.open(u, '_blank');
|
||||
win.focus();
|
||||
}
|
||||
|
||||
</script>
|
||||
```
|
||||
|
||||
In the callback, you can recover the ws object by `window[gt].lwsgt_ws`.
|
||||
|
||||
|
||||
@section gtc Lwsgt constructor
|
||||
|
||||
To instantiate the ws link and lwsgt instance, your HTML must call a lwsgt
|
||||
constructor for each region on the page managed by lwsgt.
|
||||
|
||||
`var myvar = new lwsgt_initial(title, ws_protocol, div_id, click_cb, myvar);`
|
||||
|
||||
All of the arguments are strings.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------------|---------------------------------------------------------|
|
||||
| title | Title string to go above the table |
|
||||
| ws_protocol | Protocol name string to use when making ws connection |
|
||||
| div_id | HTML id of div to fill with content |
|
||||
| click_cb | Callback function name string to handle clickable links |
|
||||
| myvar | Name of var used to hold this instantiation globally |
|
||||
|
||||
Note "myvar" is needed so it can be passed to the click handling callback.
|
||||
|
||||
|
||||
@section gtclick Lwsgt click handling function
|
||||
|
||||
When a clickable link produced by lwsgt is clicked, the function named in the
|
||||
click_cb parameter to lwsgt_initial is called.
|
||||
|
||||
That function is expected to take four parameters, eg
|
||||
|
||||
`function lwsgt_dir_click(gt, u, col, row)`
|
||||
|
||||
| Parameter | Description |
|
||||
|------- ---|-----------------------------------------------------------|
|
||||
| gt | Name of global var holding this lwsgt context (ie, myvar) |
|
||||
| u | Link "url" string |
|
||||
| col | Table column number link is from |
|
||||
| row | Table row number link is from |
|
||||
|
||||
|
||||
|
||||
@section gtgj Generic-table JSON
|
||||
|
||||
### Column layout
|
||||
|
||||
When the ws connection is established, the protocol should send a JSON message
|
||||
describing the table columns. For example
|
||||
|
||||
```
|
||||
"cols": [
|
||||
{ "name": "Date" },
|
||||
{ "name": "Size", "align": "right" },
|
||||
{ "name": "Icon" },
|
||||
{ "name": "Name", "href": "uri"},
|
||||
{ "name": "uri", "hide": "1" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- This describes 5 columns
|
||||
|
||||
- Only four columns (not "uri") should be visible
|
||||
|
||||
- "Name" should be presented as a clickable link using "uri" as the
|
||||
destination, when a "uri" field is presented.
|
||||
|
||||
- "Size" field should be presented aligned to the right
|
||||
|
||||
### Breadcrumbs
|
||||
|
||||
When a view is hierarchical, it's useful to provide a "path" with links back
|
||||
in the "path", known as "breadcrumbs".
|
||||
|
||||
Elements before the last one should provide a "url" member as well as the
|
||||
displayable name, which is used to create the link destination.
|
||||
|
||||
The last element, being the current displayed page should not have a url
|
||||
member and be displayed without link style.
|
||||
|
||||
|
||||
```
|
||||
"breadcrumbs":[{"name":"top", "url": "/" }, {"name":"mydir"}]
|
||||
```
|
||||
|
||||
### Table data
|
||||
|
||||
The actual file data consists of an array of rows, containing the columns
|
||||
mentioned in the original "cols" section.
|
||||
|
||||
```
|
||||
"data":[
|
||||
{
|
||||
"Icon":" ",
|
||||
"Date":"2015-Feb-06 03:08:35 +0000",
|
||||
"Size":"1406",
|
||||
"uri":"./serve//favicon.ico",
|
||||
"Name":"favicon.ico"
|
||||
}
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
@section gtdirl Setting up protocol-lws-table-dirlisting
|
||||
|
||||
The example protocol needs two mounts, one to provide the index.html, js and
|
||||
the protocol itself
|
||||
|
||||
```
|
||||
{
|
||||
"mountpoint": "/dirtest",
|
||||
"origin": "file:///usr/share/libwebsockets-test-server/generic-table",
|
||||
"origin": "callback://protocol-lws-table-dirlisting",
|
||||
"default": "index.html",
|
||||
"pmo": [{
|
||||
"dir": "/usr/share/libwebsockets-test-server"
|
||||
}]
|
||||
},
|
||||
```
|
||||
|
||||
The protocol wants a per-mount option (PMO) to tell it the base directory it
|
||||
is serving from, named "dir".
|
||||
|
||||
The other mount is there to simply serve items that get clicked on from the
|
||||
table in a secure way
|
||||
|
||||
```
|
||||
{
|
||||
"mountpoint": "/dirtest/serve",
|
||||
"origin": "file:///usr/share/libwebsockets-test-server",
|
||||
"default": "index.html"
|
||||
},
|
||||
```
|
||||
|
||||
This last bit is not related to using lwsgt itself.
|
192
README.lws-meta.md
Normal file
192
README.lws-meta.md
Normal file
|
@ -0,0 +1,192 @@
|
|||
# lws-meta protocol
|
||||
|
||||
lws-meta is a lightweight ws subprotocol that accepts other ws connections
|
||||
to the same server inside it and multiplexes their access to the connection.
|
||||
|
||||
```
|
||||
Client Server
|
||||
|
||||
conn1: \ / :conn1
|
||||
conn2: = mux ------ lws-meta ws protocol ----- mux = :conn2
|
||||
conn3: / \ :conn3
|
||||
```
|
||||
|
||||
You may have n client ws connections back to the server, but you now
|
||||
only have one tcp connection (and one SSL wrapper if using SSL) instead
|
||||
of n of those.
|
||||
|
||||
If you currently make multiple ws connections back to the server, so you
|
||||
can have different protocols active in one webpage, this if for you.
|
||||
|
||||
- The subprotocol code for the connections inside a lws-meta connection
|
||||
need zero changes from being a normal ws connection. It is unaware
|
||||
it is inside an lws-meta parent connection.
|
||||
|
||||
- The traffic on the lws-meta connection is indistinguishable from
|
||||
standard ws traffic, so intermediaries won't object to it
|
||||
|
||||
- The multiplexing is done in the protocol, **not by an extension**. So
|
||||
it's compatible with all browsers.
|
||||
|
||||
- Javascript helper code is provided to very simply use lws-meta
|
||||
protocol instead of direct connections. The lws test server has
|
||||
been converted to use this by default.
|
||||
|
||||
# Converting your server
|
||||
|
||||
1) include the provided lws-meta plugin (plugins/protocl_lws_meta.c) as an
|
||||
active protocol for your server. You can do that using runtime plugins, or
|
||||
include the plugin sources into your server at build-time. The lws test
|
||||
server uses the latter approach.
|
||||
|
||||
That's all you need to do on the server side.
|
||||
|
||||
# Converting your browser JS
|
||||
|
||||
1) import lws-common.js
|
||||
|
||||
2) Instantiate a parent lws-meta connection object
|
||||
|
||||
```
|
||||
var lws_meta = new lws_meta_ws();
|
||||
```
|
||||
|
||||
3) Connect the lws-meta object to your server
|
||||
|
||||
```
|
||||
lws_meta.new_parent(get_appropriate_ws_url("?mirror=" + mirror_name));
|
||||
```
|
||||
|
||||
4) Convert your actual ws connections to go via the lws_meta object
|
||||
|
||||
```
|
||||
var my_ws = lws_meta.new_ws("", "dumb-increment-protocol");
|
||||
```
|
||||
|
||||
The first arg is the URL path, the second arg is the ws protocol you want.
|
||||
|
||||
That's it. my_ws will get `onopen()`, `onmessage()` etc calls as before.
|
||||
|
||||
# lws-meta wire protocol
|
||||
|
||||
lws-meta works by adding some bytes at the start of a message indicating
|
||||
which channel the message applies to.
|
||||
|
||||
Channel messages are atomic on the wire. The reason is if we tried to
|
||||
intersperse other channel fragments between one channels message fragments,
|
||||
an intermediary would observe violations of the ws framing rule about
|
||||
having to start a message with TEXT or BINARY, and use only CONTINUATION
|
||||
for the subsequent fragments. Eg
|
||||
|
||||
```
|
||||
[ ch1 TEXT NOFIN ] [ ch2 BINARY FIN ] [ ch1 CONTINUATION FIN ]
|
||||
```
|
||||
|
||||
is illegal to an observer that doesn't understand lws-meta headers in the
|
||||
packet payloads. So to avoid this situation, only complete messages may
|
||||
be sent from one subchannel in each direction at a time.
|
||||
|
||||
Consequently, only the first fragment of each message is modified to
|
||||
have the extra two bytes identifying the subchannel it is aimed at, since
|
||||
the rest of the message from the same subchannel is defined to follow.
|
||||
|
||||
If it makes latencies, modify the protocol sending large messages to
|
||||
send smaller messages, so the transmission of messages from other channels
|
||||
can be sent inbetween the smaller messages.
|
||||
|
||||
## lws-meta commands
|
||||
|
||||
1) CSTRING indicates a string terminated by 0x00 byte
|
||||
|
||||
2) Channel IDs are sent with 0x20 added to them, to guarantee valid UTF-8
|
||||
|
||||
### 0x41: RX: LWS_META_CMD_OPEN_SUBCHANNEL
|
||||
|
||||
- CSTRING: protocol name
|
||||
- CSTRING: url
|
||||
- CSTRING: cookie (7 bytes max)
|
||||
|
||||
Client is requesting to open a new channel with the given protocol name,
|
||||
at the given url. The cookie (eg, channel name) is only used in
|
||||
LWS_META_CMD_OPEN_RESULT, when the channel id is assigned, so it is
|
||||
applied to the right channel.
|
||||
|
||||
### 0x42: TX: LWS_META_CMD_OPEN_RESULT
|
||||
|
||||
- CSTRING cookie
|
||||
- BYTE channel id (0 indicates failed)
|
||||
- CSTRING: selected protocol name
|
||||
|
||||
The server is informing the client of the results of a previous
|
||||
open request. The cookie the client sent to identify the request
|
||||
is returned along with a channel id to be used subsequently. If
|
||||
the channel ID is 0 (after subtracting the transport offset of
|
||||
0x20) then the open request has failed.
|
||||
|
||||
### 0x43: TX: LWS_META_CMD_CLOSE_NOTIFY
|
||||
|
||||
- BYTE channel id
|
||||
- BYTE: payload length + 0x20
|
||||
- BYTE: close code MSB
|
||||
- BYTE: close code LSB
|
||||
- PAYLOAD: payload (< 123 bytes)
|
||||
|
||||
Server notifies the client that a child has closed, for whatever reason.
|
||||
|
||||
### 0x44: RX: LWS_META_CMD_CLOSE_RQ
|
||||
- BYTE: channel id
|
||||
- BYTE: payload length + 0x20
|
||||
- BYTE: close code MSB
|
||||
- BYTE: close code LSB
|
||||
- PAYLOAD: payload (< 123 bytes)
|
||||
|
||||
The client requests to close a child connection
|
||||
|
||||
### 0x45: TX: LWS_META_CMD_WRITE
|
||||
|
||||
- BYTE: channel id
|
||||
|
||||
Normal write of payload n from lws-meta perspective is actually
|
||||
LWS_META_CMD_WRITE, channel id, then (n - 2) bytes of payload
|
||||
|
||||
The command only appears at the start of a message, continuations do
|
||||
not have the command.
|
||||
|
||||
## Protocol Notes
|
||||
|
||||
- Once the subchannel is up, overhead is only +2 bytes per message
|
||||
|
||||
- Close reasons are supported in both directions
|
||||
|
||||
- Ping and Pong are only supported at the lws-meta level, using normal ws ping and pong packets.
|
||||
|
||||
- Only the final close of the tcp lws-meta connection itself goes out as
|
||||
a normal ws close frame. Subchannels close is done in a normal TEXT
|
||||
message using LWS_META_CMD_CLOSE_RQ and then the close packet payload.
|
||||
This is so intermediaries do not mistake subchannel closures for the
|
||||
tcp / ws link going down.
|
||||
|
||||
Messages that start with LWS_META_CMD_OPEN_SUBCHANNEL only contain those
|
||||
commands but may contain any number of them for the whole duration of the
|
||||
message. The lws-meta js support collects child open requests made before
|
||||
the parent lws-meta connection is open, and dumps them all in a single
|
||||
message when it does open.
|
||||
|
||||
Messages that start with LWS_META_CMD_OPEN_RESULT or LWS_META_CMD_CLOSE_NOTIFY
|
||||
only contain those two commands, but they may contain any number of them
|
||||
for the whole duration of the message.
|
||||
|
||||
|
||||
# Current Implemention Limitations
|
||||
|
||||
- only server side is supported in lws. The client side JS for
|
||||
a browser is supported.
|
||||
|
||||
- max number of child connections per parent at the moment is 8
|
||||
|
||||
- child connection URL paramter when opening the connection is
|
||||
ignored
|
||||
|
||||
- there is no ah attached when the child connections are
|
||||
established inside the lws-meta parent. So header access
|
||||
functions will fail.
|
601
README.lwsws.md
Normal file
601
README.lwsws.md
Normal file
|
@ -0,0 +1,601 @@
|
|||
Notes about lwsws
|
||||
=================
|
||||
|
||||
@section lwsws Libwebsockets Web Server
|
||||
|
||||
lwsws is an implementation of a very lightweight, ws-capable generic web
|
||||
server, which uses libwebsockets to implement everything underneath.
|
||||
|
||||
If you are basically implementing a standalone server with lws, you can avoid
|
||||
reinventing the wheel and use a debugged server including lws.
|
||||
|
||||
|
||||
@section lwswsb Build
|
||||
|
||||
Just enable -DLWS_WITH_LWSWS=1 at cmake-time.
|
||||
|
||||
It enables libuv and plugin support automatically.
|
||||
|
||||
NOTICE on Ubuntu, the default libuv package is called "libuv-0.10". This is ancient.
|
||||
|
||||
You should replace this with libuv1 and libuv1-dev before proceeding.
|
||||
|
||||
@section lwswsc Lwsws Configuration
|
||||
|
||||
lwsws uses JSON config files, they're pure JSON except:
|
||||
|
||||
- '#' may be used to turn the rest of the line into a comment.
|
||||
|
||||
- There's also a single substitution, if a string contains "_lws_ddir_", then that is
|
||||
replaced with the LWS install data directory path, eg, "/usr/share" or whatever was
|
||||
set when LWS was built + installed. That lets you refer to installed paths without
|
||||
having to change the config if your install path was different.
|
||||
|
||||
There is a single file intended for global settings
|
||||
|
||||
/etc/lwsws/conf
|
||||
```
|
||||
# these are the server global settings
|
||||
# stuff related to vhosts should go in one
|
||||
# file per vhost in ../conf.d/
|
||||
|
||||
{
|
||||
"global": {
|
||||
"uid": "48", # apache user
|
||||
"gid": "48", # apache user
|
||||
"count-threads": "1",
|
||||
"server-string": "myserver v1", # returned in http headers
|
||||
"ws-pingpong-secs": "200", # confirm idle established ws connections this often
|
||||
"init-ssl": "yes"
|
||||
}
|
||||
}
|
||||
```
|
||||
and a config directory intended to take one file per vhost
|
||||
|
||||
/etc/lwsws/conf.d/warmcat.com
|
||||
```
|
||||
{
|
||||
"vhosts": [{
|
||||
"name": "warmcat.com",
|
||||
"port": "443",
|
||||
"interface": "eth0", # optional
|
||||
"host-ssl-key": "/etc/pki/tls/private/warmcat.com.key", # if given enable ssl
|
||||
"host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt",
|
||||
"host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer",
|
||||
"mounts": [{ # autoserve
|
||||
"mountpoint": "/",
|
||||
"origin": "file:///var/www/warmcat.com",
|
||||
"default": "index.html"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
```
|
||||
To get started quickly, an example config reproducing the old test server
|
||||
on port 7681, non-SSL is provided. To set it up
|
||||
```
|
||||
# mkdir -p /etc/lwsws/conf.d /var/log/lwsws
|
||||
# cp ./lwsws/etc-lwsws-conf-EXAMPLE /etc/lwsws/conf
|
||||
# cp ./lwsws/etc-lwsws-conf.d-localhost-EXAMPLE /etc/lwsws/conf.d/test-server
|
||||
# sudo lwsws
|
||||
```
|
||||
|
||||
@section lwsogo Other Global Options
|
||||
|
||||
- `reject-service-keywords` allows you to return an HTTP error code and message of your choice
|
||||
if a keyword is found in the user agent
|
||||
|
||||
```
|
||||
"reject-service-keywords": [{
|
||||
"scumbot": "404 Not Found"
|
||||
}]
|
||||
```
|
||||
|
||||
- `timeout-secs` lets you set the global timeout for various network-related
|
||||
operations in lws, in seconds. It defaults to 5.
|
||||
|
||||
@section lwswsv Lwsws Vhosts
|
||||
|
||||
One server can run many vhosts, where SSL is in use SNI is used to match
|
||||
the connection to a vhost and its vhost-specific SSL keys during SSL
|
||||
negotiation.
|
||||
|
||||
Listing multiple vhosts looks something like this
|
||||
```
|
||||
{
|
||||
"vhosts": [ {
|
||||
"name": "localhost",
|
||||
"port": "443",
|
||||
"host-ssl-key": "/etc/pki/tls/private/libwebsockets.org.key",
|
||||
"host-ssl-cert": "/etc/pki/tls/certs/libwebsockets.org.crt",
|
||||
"host-ssl-ca": "/etc/pki/tls/certs/libwebsockets.org.cer",
|
||||
"mounts": [{
|
||||
"mountpoint": "/",
|
||||
"origin": "file:///var/www/libwebsockets.org",
|
||||
"default": "index.html"
|
||||
}, {
|
||||
"mountpoint": "/testserver",
|
||||
"origin": "file:///usr/local/share/libwebsockets-test-server",
|
||||
"default": "test.html"
|
||||
}],
|
||||
# which protocols are enabled for this vhost, and optional
|
||||
# vhost-specific config options for the protocol
|
||||
#
|
||||
"ws-protocols": [{
|
||||
"warmcat,timezoom": {
|
||||
"status": "ok"
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "localhost",
|
||||
"port": "7681",
|
||||
"host-ssl-key": "/etc/pki/tls/private/libwebsockets.org.key",
|
||||
"host-ssl-cert": "/etc/pki/tls/certs/libwebsockets.org.crt",
|
||||
"host-ssl-ca": "/etc/pki/tls/certs/libwebsockets.org.cer",
|
||||
"mounts": [{
|
||||
"mountpoint": "/",
|
||||
"origin": ">https://localhost"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "localhost",
|
||||
"port": "80",
|
||||
"mounts": [{
|
||||
"mountpoint": "/",
|
||||
"origin": ">https://localhost"
|
||||
}]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
That sets up three vhosts all called "localhost" on ports 443 and 7681 with SSL, and port 80 without SSL but with a forced redirect to https://localhost
|
||||
|
||||
|
||||
@section lwswsvn Lwsws Vhost name and port sharing
|
||||
|
||||
The vhost name field is used to match on incoming SNI or Host: header, so it
|
||||
must always be the host name used to reach the vhost externally.
|
||||
|
||||
- Vhosts may have the same name and different ports, these will each create a
|
||||
listening socket on the appropriate port.
|
||||
|
||||
- Vhosts may also have the same port and different name: these will be treated as
|
||||
true vhosts on one listening socket and the active vhost decided at SSL
|
||||
negotiation time (via SNI) or if no SSL, then after the Host: header from
|
||||
the client has been parsed.
|
||||
|
||||
|
||||
@section lwswspr Lwsws Protocols
|
||||
|
||||
Vhosts by default have available the union of any initial protocols from context creation time, and
|
||||
any protocols exposed by plugins.
|
||||
|
||||
Vhosts can select which plugins they want to offer and give them per-vhost settings using this syntax
|
||||
```
|
||||
"ws-protocols": [{
|
||||
"warmcat-timezoom": {
|
||||
"status": "ok"
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
The "x":"y" parameters like "status":"ok" are made available to the protocol during its per-vhost
|
||||
LWS_CALLBACK_PROTOCOL_INIT (@in is a pointer to a linked list of struct lws_protocol_vhost_options
|
||||
containing the name and value pointers).
|
||||
|
||||
To indicate that a protocol should be used when no Protocol: header is sent
|
||||
by the client, you can use "default": "1"
|
||||
```
|
||||
"ws-protocols": [{
|
||||
"warmcat-timezoom": {
|
||||
"status": "ok",
|
||||
"default": "1"
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
|
||||
@section lwswsovo Lwsws Other vhost options
|
||||
|
||||
- If the three options `host-ssl-cert`, `host-ssl-ca` and `host-ssl-key` are given, then the vhost supports SSL.
|
||||
|
||||
Each vhost may have its own certs, SNI is used during the initial connection negotiation to figure out which certs to use by the server name it's asking for from the request DNS name.
|
||||
|
||||
- `keeplive-timeout` (in secs) defaults to 60 for lwsws, it may be set as a vhost option
|
||||
|
||||
- `interface` lets you specify which network interface to listen on, if not given listens on all
|
||||
|
||||
- "`unix-socket`": "1" causes the unix socket specified in the interface option to be used instead of an INET socket
|
||||
|
||||
- "`sts`": "1" causes lwsws to send a Strict Transport Security header with responses that informs the client he should never accept to connect to this address using http. This is needed to get the A+ security rating from SSL Labs for your server.
|
||||
|
||||
- "`access-log`": "filepath" sets where apache-compatible access logs will be written
|
||||
|
||||
- `"enable-client-ssl"`: `"1"` enables the vhost's client SSL context, you will need this if you plan to create client conections on the vhost that will use SSL. You don't need it if you only want http / ws client connections.
|
||||
|
||||
- "`ciphers`": "<cipher list>" sets the allowed list of ciphers and key exchange protocols for the vhost. The default list is restricted to only those providing PFS (Perfect Forward Secrecy) on the author's Fedora system.
|
||||
|
||||
If you need to allow weaker ciphers,you can provide an alternative list here per-vhost.
|
||||
|
||||
- "`ecdh-curve`": "<curve name>" The default ecdh curve is "prime256v1", but you can override it here, per-vhost
|
||||
|
||||
- "`noipv6`": "on" Disable ipv6 completely for this vhost
|
||||
|
||||
- "`ipv6only`": "on" Only allow ipv6 on this vhost / "off" only allow ipv4 on this vhost
|
||||
|
||||
- "`ssl-option-set`": "<decimal>" Sets the SSL option flag value for the vhost.
|
||||
It may be used multiple times and OR's the flags together.
|
||||
|
||||
The values are derived from /usr/include/openssl/ssl.h
|
||||
```
|
||||
# define SSL_OP_NO_TLSv1_1 0x10000000L
|
||||
```
|
||||
|
||||
would equate to
|
||||
|
||||
```
|
||||
"`ssl-option-set`": "268435456"
|
||||
```
|
||||
- "`ssl-option-clear'": "<decimal>" Clears the SSL option flag value for the vhost.
|
||||
It may be used multiple times and OR's the flags together.
|
||||
|
||||
- "`headers':: [{ "header1": "h1value", "header2": "h2value" }]
|
||||
|
||||
allows you to set arbitrary headers on every file served by the vhost
|
||||
|
||||
recommended vhost headers for good client security are
|
||||
|
||||
```
|
||||
"headers": [{
|
||||
"Content-Security-Policy": "script-src 'self'",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"X-XSS-Protection": "1; mode=block",
|
||||
"X-Frame-Options": "SAMEORIGIN"
|
||||
}]
|
||||
|
||||
```
|
||||
|
||||
@section lwswsm Lwsws Mounts
|
||||
|
||||
Where mounts are given in the vhost definition, then directory contents may
|
||||
be auto-served if it matches the mountpoint.
|
||||
|
||||
Mount protocols are used to control what kind of translation happens
|
||||
|
||||
- file:// serve the uri using the remainder of the url past the mountpoint based on the origin directory.
|
||||
|
||||
Eg, with this mountpoint
|
||||
```
|
||||
{
|
||||
"mountpoint": "/",
|
||||
"origin": "file:///var/www/mysite.com",
|
||||
"default": "/"
|
||||
}
|
||||
```
|
||||
The uri /file.jpg would serve /var/www/mysite.com/file.jpg, since / matched.
|
||||
|
||||
- ^http:// or ^https:// these cause any url matching the mountpoint to issue a redirect to the origin url
|
||||
|
||||
- cgi:// this causes any matching url to be given to the named cgi, eg
|
||||
```
|
||||
{
|
||||
"mountpoint": "/git",
|
||||
"origin": "cgi:///var/www/cgi-bin/cgit",
|
||||
"default": "/"
|
||||
}, {
|
||||
"mountpoint": "/cgit-data",
|
||||
"origin": "file:///usr/share/cgit",
|
||||
"default": "/"
|
||||
},
|
||||
```
|
||||
would cause the url /git/myrepo to pass "myrepo" to the cgi /var/www/cgi-bin/cgit and send the results to the client.
|
||||
|
||||
- http:// or https:// these perform reverse proxying, serving the remote origin content from the mountpoint. Eg
|
||||
|
||||
```
|
||||
{
|
||||
"mountpoint": "/proxytest",
|
||||
"origin": "https://libwebsockets.org"
|
||||
}
|
||||
```
|
||||
|
||||
This will cause your local url `/proxytest` to serve content fetched from libwebsockets.org over ssl; whether it's served from your server using ssl is unrelated and depends how you configured your local server. Notice if you will use the proxying feature, `LWS_WITH_HTTP_PROXY` is required to be enabled at cmake, and for `https` proxy origins, your lwsws configuration must include `"init-ssl": "1"` and the vhost with the proxy mount must have `"enable-client-ssl": "1"`, even if you are not using ssl to serve.
|
||||
|
||||
`/proxytest/abc`, or `/proxytest/abc?def=ghi` etc map to the origin + the part past `/proxytest`, so links and img src urls etc work as do all urls under the origin path.
|
||||
|
||||
In addition link and src urls in the document are rewritten so / or the origin url part are rewritten to the mountpoint part.
|
||||
|
||||
|
||||
@section lwswsomo Lwsws Other mount options
|
||||
|
||||
1) Some protocols may want "per-mount options" in name:value format. You can
|
||||
provide them using "pmo"
|
||||
|
||||
{
|
||||
"mountpoint": "/stuff",
|
||||
"origin": "callback://myprotocol",
|
||||
"pmo": [{
|
||||
"myname": "myvalue"
|
||||
}]
|
||||
}
|
||||
|
||||
2) When using a cgi:// protcol origin at a mountpoint, you may also give cgi environment variables specific to the mountpoint like this
|
||||
```
|
||||
{
|
||||
"mountpoint": "/git",
|
||||
"origin": "cgi:///var/www/cgi-bin/cgit",
|
||||
"default": "/",
|
||||
"cgi-env": [{
|
||||
"CGIT_CONFIG": "/etc/cgitrc/libwebsockets.org"
|
||||
}]
|
||||
}
|
||||
```
|
||||
This allows you to customize one cgi depending on the mountpoint (and / or vhost).
|
||||
|
||||
3) It's also possible to set the cgi timeout (in secs) per cgi:// mount, like this
|
||||
```
|
||||
"cgi-timeout": "30"
|
||||
```
|
||||
4) `callback://` protocol may be used when defining a mount to associate a
|
||||
named protocol callback with the URL namespace area. For example
|
||||
```
|
||||
{
|
||||
"mountpoint": "/formtest",
|
||||
"origin": "callback://protocol-post-demo"
|
||||
}
|
||||
```
|
||||
All handling of client access to /formtest[anything] will be passed to the
|
||||
callback registered to the protocol "protocol-post-demo".
|
||||
|
||||
This is useful for handling POST http body content or general non-cgi http
|
||||
payload generation inside a plugin.
|
||||
|
||||
See the related notes in README.coding.md
|
||||
|
||||
5) Cache policy of the files in the mount can also be set. If no
|
||||
options are given, the content is marked uncacheable.
|
||||
```
|
||||
{
|
||||
"mountpoint": "/",
|
||||
"origin": "file:///var/www/mysite.com",
|
||||
"cache-max-age": "60", # seconds
|
||||
"cache-reuse": "1", # allow reuse at client at all
|
||||
"cache-revalidate": "1", # check it with server each time
|
||||
"cache-intermediaries": "1" # allow intermediary caches to hold
|
||||
}
|
||||
```
|
||||
|
||||
6) You can also define a list of additional mimetypes per-mount
|
||||
```
|
||||
"extra-mimetypes": {
|
||||
".zip": "application/zip",
|
||||
".doc": "text/evil"
|
||||
}
|
||||
```
|
||||
|
||||
Normally a file suffix MUST match one of the canned mimetypes or one of the extra
|
||||
mimetypes, or the file is not served. This adds a little bit of security because
|
||||
even if there is a bug somewhere and the mount dirs are circumvented, lws will not
|
||||
serve, eg, /etc/passwd.
|
||||
|
||||
If you provide an extra mimetype entry
|
||||
|
||||
"*": ""
|
||||
|
||||
Then any file is served, if the mimetype was not known then it is served without a
|
||||
Content-Type: header.
|
||||
|
||||
7) A mount can be protected by HTTP Basic Auth. This only makes sense when using
|
||||
https, since otherwise the password can be sniffed.
|
||||
|
||||
You can add a `basic-auth` entry on a mount like this
|
||||
|
||||
```
|
||||
{
|
||||
"mountpoint": "/basic-auth",
|
||||
"origin": "file://_lws_ddir_/libwebsockets-test-server/private",
|
||||
"basic-auth": "/var/www/balogins-private"
|
||||
}
|
||||
```
|
||||
|
||||
Before serving anything, lws will signal to the browser that a username / password
|
||||
combination is required, and it will pop up a dialog. When the user has filled it
|
||||
in, lwsws checks the user:password string against the text file named in the `basic-auth`
|
||||
entry.
|
||||
|
||||
The file should contain user:pass one per line
|
||||
|
||||
```
|
||||
testuser:testpass
|
||||
myuser:hispass
|
||||
```
|
||||
|
||||
The file should be readable by lwsws, and for a little bit of extra security not
|
||||
have a file suffix, so lws would reject to serve it even if it could find it on
|
||||
a mount.
|
||||
|
||||
|
||||
@section lwswspl Lwsws Plugins
|
||||
|
||||
Protcols and extensions may also be provided from "plugins", these are
|
||||
lightweight dynamic libraries. They are scanned for at init time, and
|
||||
any protocols and extensions found are added to the list given at context
|
||||
creation time.
|
||||
|
||||
Protocols receive init (LWS_CALLBACK_PROTOCOL_INIT) and destruction
|
||||
(LWS_CALLBACK_PROTOCOL_DESTROY) callbacks per-vhost, and there are arrangements
|
||||
they can make per-vhost allocations and get hold of the correct pointer from
|
||||
the wsi at the callback.
|
||||
|
||||
This allows a protocol to choose to strictly segregate data on a per-vhost
|
||||
basis, and also allows the plugin to handle its own initialization and
|
||||
context storage.
|
||||
|
||||
To help that happen conveniently, there are some new apis
|
||||
|
||||
- lws_vhost_get(wsi)
|
||||
- lws_protocol_get(wsi)
|
||||
- lws_callback_on_writable_all_protocol_vhost(vhost, protocol)
|
||||
- lws_protocol_vh_priv_zalloc(vhost, protocol, size)
|
||||
- lws_protocol_vh_priv_get(vhost, protocol)
|
||||
|
||||
dumb increment, mirror and status protocol plugins are provided as examples.
|
||||
|
||||
|
||||
@section lwswsplaplp Additional plugin search paths
|
||||
|
||||
Packages that have their own lws plugins can install them in their own
|
||||
preferred dir and ask lwsws to scan there by using a config fragment
|
||||
like this, in its own conf.d/ file managed by the other package
|
||||
```
|
||||
{
|
||||
"global": {
|
||||
"plugin-dir": "/usr/local/share/coherent-timeline/plugins"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@section lwswsssp lws-server-status plugin
|
||||
|
||||
One provided protocol can be used to monitor the server status.
|
||||
|
||||
Enable the protocol like this on a vhost's ws-protocols section
|
||||
```
|
||||
"lws-server-status": {
|
||||
"status": "ok",
|
||||
"update-ms": "5000"
|
||||
}
|
||||
```
|
||||
`"update-ms"` is used to control how often updated JSON is sent on a ws link.
|
||||
|
||||
And map the provided HTML into the vhost in the mounts section
|
||||
```
|
||||
{
|
||||
"mountpoint": "/server-status",
|
||||
"origin": "file:///usr/local/share/libwebsockets-test-server/server-status",
|
||||
"default": "server-status.html"
|
||||
}
|
||||
```
|
||||
You might choose to put it on its own vhost which has "interface": "lo", so it's not
|
||||
externally visible, or use the Basic Auth support to require authentication to
|
||||
access it.
|
||||
|
||||
`"hide-vhosts": "{0 | 1}"` lets you control if information about your vhosts is included.
|
||||
Since this includes mounts, you might not want to leak that information, mount names,
|
||||
etc.
|
||||
|
||||
`"filespath":"{path}"` lets you give a server filepath which is read and sent to the browser
|
||||
on each refresh. For example, you can provide server temperature information on most
|
||||
Linux systems by giving an appropriate path down /sys.
|
||||
|
||||
This may be given multiple times.
|
||||
|
||||
|
||||
@section lwswsreload Lwsws Configuration Reload
|
||||
|
||||
You may send lwsws a `HUP` signal, by, eg
|
||||
|
||||
```
|
||||
$ sudo killall -HUP lwsws
|
||||
```
|
||||
|
||||
This causes lwsws to "deprecate" the existing lwsws process, and remove and close all of
|
||||
its listen sockets, but otherwise allowing it to continue to run, until all
|
||||
of its open connections close.
|
||||
|
||||
When a deprecated lwsws process has no open connections left, it is destroyed
|
||||
automatically.
|
||||
|
||||
After sending the SIGHUP to the main lwsws process, a new lwsws process, which can
|
||||
pick up the newly-available listen sockets, and use the current configuration
|
||||
files, is automatically started.
|
||||
|
||||
The new configuration may differ from the original one in arbitrary ways, the new
|
||||
context is created from scratch each time without reference to the original one.
|
||||
|
||||
Notes
|
||||
|
||||
1) Protocols that provide a "shared world" like mirror will have as many "worlds"
|
||||
as there are lwsws processes still active. People connected to a deprecated lwsws
|
||||
process remain connected to the existing peers.
|
||||
|
||||
But any new connections will apply to the new lwsws process, which does not share
|
||||
per-vhost "shared world" data with the deprecated process. That means no new
|
||||
connections on the deprecated context, ie a "shrinking world" for those guys, and a
|
||||
"growing world" for people who connect after the SIGHUP.
|
||||
|
||||
2) The new lwsws process owes nothing to the previous one. It starts with fresh
|
||||
plugins, fresh configuration, fresh root privileges if that how you start it.
|
||||
|
||||
The plugins may have been updated in arbitrary ways including struct size changes
|
||||
etc, and lwsws or lws may also have been updated arbitrarily.
|
||||
|
||||
3) A root parent process is left up that is not able to do anything except
|
||||
respond to SIGHUP or SIGTERM. Actual serving and network listening etc happens
|
||||
in child processes which use the privileges set in the lwsws config files.
|
||||
|
||||
@section lwswssysd Lwsws Integration with Systemd
|
||||
|
||||
lwsws needs a service file like this as `/usr/lib/systemd/system/lwsws.service`
|
||||
```
|
||||
[Unit]
|
||||
Description=Libwebsockets Web Server
|
||||
After=syslog.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/lwsws
|
||||
ExecReload=/usr/bin/killall -s SIGHUP lwsws ; sleep 1 ; /usr/local/bin/lwsws
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
You can find this prepared in `./lwsws/usr-lib-systemd-system-lwsws.service`
|
||||
|
||||
|
||||
@section lwswslr Lwsws Integration with logrotate
|
||||
|
||||
For correct operation with logrotate, `/etc/logrotate.d/lwsws` (if that's
|
||||
where we're putting the logs) should contain
|
||||
```
|
||||
/var/log/lwsws/*log {
|
||||
copytruncate
|
||||
missingok
|
||||
notifempty
|
||||
delaycompress
|
||||
}
|
||||
```
|
||||
You can find this prepared in `/lwsws/etc-logrotate.d-lwsws`
|
||||
|
||||
Prepare the log directory like this
|
||||
|
||||
```
|
||||
sudo mkdir /var/log/lwsws
|
||||
sudo chmod 700 /var/log/lwsws
|
||||
```
|
||||
|
||||
@section lwswsgdb Debugging lwsws with gdb
|
||||
|
||||
Hopefully you won't need to debug lwsws itself, but you may want to debug your plugins. start lwsws like this to have everything running under gdb
|
||||
|
||||
```
|
||||
sudo gdb -ex "set follow-fork-mode child" -ex "run" --args /usr/local/bin/lwsws
|
||||
|
||||
```
|
||||
|
||||
this will give nice backtraces in lwsws itself and in plugins, if they were built with symbols.
|
||||
|
||||
@section lwswsvgd Running lwsws under valgrind
|
||||
|
||||
You can just run lwsws under galgrind as usual and get valid results. However the results / analysis part of valgrind runs
|
||||
after the plugins have removed themselves, this means valgrind backtraces into plugin code is opaque, without
|
||||
source-level info because the dynamic library is gone.
|
||||
|
||||
There's a simple workaround, use LD_PRELOAD=<plugin.so> before running lwsws, this has the loader bring the plugin
|
||||
in before executing lwsws as if it was a direct dependency. That means it's still mapped until the whole process
|
||||
exits after valgtind has done its thing.
|
||||
|
||||
|
29
README.md
29
README.md
|
@ -1,29 +1,38 @@
|
|||
[](https://travis-ci.org/warmcat/libwebsockets)
|
||||
[](https://ci.appveyor.com/project/warmcat/libwebsockets)
|
||||
[](https://travis-ci.org/warmcat/libwebsockets)
|
||||
[](https://ci.appveyor.com/project/lws-team/libwebsockets)
|
||||
[](https://scan.coverity.com/projects/3576)
|
||||
|
||||
libwebsockets
|
||||
-------------
|
||||
|
||||
News
|
||||
----
|
||||
|
||||
v2.3 is out... see the changelog https://github.com/warmcat/libwebsockets/blob/v2.3-stable/changelog
|
||||
|
||||
ESP32 is now supported in lws! Download the
|
||||
|
||||
- factory https://github.com/warmcat/lws-esp32-factory and
|
||||
- test server app https://github.com/warmcat/lws-esp32-test-server-demos
|
||||
|
||||
|
||||
This is the libwebsockets C library for lightweight websocket clients and
|
||||
servers. For support, visit
|
||||
|
||||
http://libwebsockets.org
|
||||
https://libwebsockets.org
|
||||
https://github.com/warmcat/libwebsockets
|
||||
|
||||
and consider joining the project mailing list at
|
||||
|
||||
http://ml.libwebsockets.org/mailman/listinfo/libwebsockets
|
||||
https://libwebsockets.org/mailman/listinfo/libwebsockets
|
||||
|
||||
You can get the latest version of the library from git
|
||||
You can get the latest version of the library from git:
|
||||
|
||||
- http://git.libwebsockets.org
|
||||
- https://github.com/warmcat/libwebsockets
|
||||
- https://libwebsockets.org/git
|
||||
|
||||
for more information:
|
||||
Doxygen API docs for master: https://libwebsockets.org/lws-api-doc-master/html/index.html
|
||||
|
||||
- [README.build.md](README.build.md) - information on building the library
|
||||
- [README.coding.md](README.coding.md) - information for writing code using the library
|
||||
- [README.test-apps.md](README.test-apps.md) - information about the test apps built with the library
|
||||
|
||||
After libwebsockets 1.3, tags will be signed using a key corresponding to this public key
|
||||
|
||||
|
|
43
README.problems.md
Normal file
43
README.problems.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
Debugging problems
|
||||
==================
|
||||
|
||||
Library is a component
|
||||
----------------------
|
||||
|
||||
As a library, lws is always just a component in a bigger application.
|
||||
|
||||
When users have a problem involving lws, what is happening in the bigger
|
||||
application is usually critical to understand what is going on (and where the
|
||||
solution lies).
|
||||
|
||||
Many users are able to share their sources, but others decide not to, for
|
||||
presumed "commercial advantage" or whatever. (In any event, it can be painful
|
||||
looking through large chunks of someone else's sources for problems when that
|
||||
is not the library author's responsibility.)
|
||||
|
||||
This makes answering questions like "what is wrong with my code I am not
|
||||
going to show you?" or even "what is wrong with my code?" very difficult.
|
||||
|
||||
Even if it's clear there is a problem somewhere, it cannot be understood or
|
||||
reproduced by anyone else if it needs user code that isn't provided.
|
||||
|
||||
The biggest question is, "is this an lws problem actually"?
|
||||
|
||||
|
||||
Use the test apps as sanity checks
|
||||
----------------------------------
|
||||
|
||||
The test server and client are extremely useful for sanity checks and debugging
|
||||
guidance.
|
||||
|
||||
- test apps work on your platform, then either
|
||||
- your user code is broken, align it to how the test apps work, or,
|
||||
- something from your code is required to show an lws problem, provide a
|
||||
minimal patch on a test app so it can be reproduced
|
||||
|
||||
- test apps break on your platform, but work on, eg, x86_64, either
|
||||
- toolchain or platform-specific (eg, OS) issue, or
|
||||
- lws platform support issue
|
||||
|
||||
- test apps break everywhere
|
||||
- sounds like lws problem, info to reproduce and / or a patch is appreciated
|
|
@ -1,10 +1,58 @@
|
|||
Testing server with a browser
|
||||
-----------------------------
|
||||
Overview of lws test apps
|
||||
=========================
|
||||
|
||||
Are you building a client? You just need to look at the test client
|
||||
[libwebsockets-test-client](test-server/test-client.c).
|
||||
|
||||
If you are building a standalone server, there are three choices, in order of
|
||||
preferability.
|
||||
|
||||
1) lwsws + protocol plugins
|
||||
|
||||
Lws provides a generic web server app that can be configured with JSON
|
||||
config files. https://libwebsockets.org itself uses this method.
|
||||
|
||||
With lwsws handling the serving part, you only need to write an lws protocol
|
||||
plugin. See [plugin-standalone](plugin-standalone) for an example of how
|
||||
to do that outside lws itself, using lws public apis.
|
||||
|
||||
$ cmake .. -DLWS_WITH_LWSWS=1
|
||||
|
||||
See [README.lwsws.md](README.lwsws.md) for information on how to configure
|
||||
lwsws.
|
||||
|
||||
NOTE this method implies libuv is used by lws, to provide crossplatform
|
||||
implementations of timers, dynamic lib loading etc for plugins and lwsws.
|
||||
|
||||
2) test-server-v2.0.c
|
||||
|
||||
This method lets you configure web serving in code, instead of using lwsws.
|
||||
|
||||
Plugins are still used, which implies libuv needed.
|
||||
|
||||
$ cmake .. -DLWS_WITH_PLUGINS=1
|
||||
|
||||
See [test-server-v2.0.c](test-server/test-server-v2.0.c)
|
||||
|
||||
3) protocols in the server app
|
||||
|
||||
This is the original way lws implemented servers, plugins and libuv are not
|
||||
required, but without plugins separating the protocol code directly, the
|
||||
combined code is all squidged together and is much less maintainable.
|
||||
|
||||
This method is still supported in lws but all ongoing and future work is
|
||||
being done in protocol plugins only.
|
||||
|
||||
|
||||
Notes about lws test apps
|
||||
=========================
|
||||
|
||||
@section tsb Testing server with a browser
|
||||
|
||||
If you run [libwebsockets-test-server](test-server/test-server.c) and point your browser
|
||||
(eg, Chrome) to
|
||||
|
||||
http://127.0.0.1:7681
|
||||
http://127.0.0.1:7681
|
||||
|
||||
It will fetch a script in the form of `test.html`, and then run the
|
||||
script in there on the browser to open a websocket connection.
|
||||
|
@ -14,8 +62,7 @@ By default the test server logs to both stderr and syslog, you can control
|
|||
what is logged using `-d <log level>`, see later.
|
||||
|
||||
|
||||
Running test server as a Daemon
|
||||
-------------------------------
|
||||
@section tsd Running test server as a Daemon
|
||||
|
||||
You can use the -D option on the test server to have it fork into the
|
||||
background and return immediately. In this daemonized mode all stderr is
|
||||
|
@ -26,11 +73,9 @@ of the master process, and deletes this file when the master process
|
|||
terminates.
|
||||
|
||||
To stop the daemon, do
|
||||
|
||||
```bash
|
||||
$ kill `cat /tmp/.lwsts-lock`
|
||||
```
|
||||
|
||||
$ kill cat /tmp/.lwsts-lock
|
||||
```
|
||||
If it finds a stale lock (the pid mentioned in the file does not exist
|
||||
any more) it will delete the lock and create a new one during startup.
|
||||
|
||||
|
@ -38,19 +83,16 @@ If the lock is valid, the daemon will exit with a note on stderr that
|
|||
it was already running.
|
||||
|
||||
|
||||
Using SSL on the server side
|
||||
----------------------------
|
||||
@section sssl Using SSL on the server side
|
||||
|
||||
To test it using SSL/WSS, just run the test server with
|
||||
|
||||
```bash
|
||||
$ libwebsockets-test-server --ssl
|
||||
```
|
||||
|
||||
$ libwebsockets-test-server --ssl
|
||||
```
|
||||
and use the URL
|
||||
|
||||
https://127.0.0.1:7681
|
||||
|
||||
```
|
||||
https://127.0.0.1:7681
|
||||
```
|
||||
The connection will be entirely encrypted using some generated
|
||||
certificates that your browser will not accept, since they are
|
||||
not signed by any real Certificate Authority. Just accept the
|
||||
|
@ -61,15 +103,21 @@ same.
|
|||
[test-server.c](test-server/test-server.c) is all that is needed to use libwebsockets for
|
||||
serving both the script html over http and websockets.
|
||||
|
||||
@section lwstsdynvhost Dynamic Vhosts
|
||||
|
||||
Testing websocket client support
|
||||
--------------------------------
|
||||
You can send libwebsockets-test-server or libwebsockets-test-server-v2.0 a SIGUSR1
|
||||
to toggle the creation and destruction of an identical second vhost on port + 1.
|
||||
|
||||
This is intended as a test and demonstration for how to bring up and remove
|
||||
vhosts dynamically.
|
||||
|
||||
@section wscl Testing websocket client support
|
||||
|
||||
If you run the test server as described above, you can also
|
||||
connect to it using the test client as well as a browser.
|
||||
|
||||
```bash
|
||||
$ libwebsockets-test-client localhost
|
||||
```
|
||||
$ libwebsockets-test-client localhost
|
||||
```
|
||||
|
||||
will by default connect to the test server on localhost:7681
|
||||
|
@ -78,76 +126,89 @@ same time as drawing random circles in the mirror protocol;
|
|||
if you connect to the test server using a browser at the
|
||||
same time you will be able to see the circles being drawn.
|
||||
|
||||
The test client supports SSL too, use
|
||||
|
||||
Testing simple echo
|
||||
-------------------
|
||||
```
|
||||
$ libwebsockets-test-client localhost --ssl -s
|
||||
```
|
||||
|
||||
the -s tells it to accept the default self-signed cert from the server,
|
||||
otherwise it will strictly fail the connection if there is no CA cert to
|
||||
validate the server's certificate.
|
||||
|
||||
|
||||
@section choosingts Choosing between test server variations
|
||||
|
||||
If you will be doing standalone serving with lws, ideally you should avoid
|
||||
making your own server at all, and use lwsws with your own protocol plugins.
|
||||
|
||||
The second best option is follow test-server-v2.0.c, which uses a mount to
|
||||
autoserve a directory, and lws protocol plugins for ws, without needing any
|
||||
user callback code (other than what's needed in the protocol plugin).
|
||||
|
||||
For those two options libuv is needed to support the protocol plugins, if
|
||||
that's not possible then the other variations with their own protocol code
|
||||
should be considered.
|
||||
|
||||
|
||||
@section echo Testing simple echo
|
||||
|
||||
You can test against `echo.websockets.org` as a sanity test like
|
||||
this (the client connects to port `80` by default):
|
||||
|
||||
```bash
|
||||
$ libwebsockets-test-echo --client echo.websocket.org
|
||||
```
|
||||
$ libwebsockets-test-echo --client echo.websocket.org
|
||||
```
|
||||
|
||||
This echo test is of limited use though because it doesn't
|
||||
negotiate any protocol. You can run the same test app as a
|
||||
local server, by default on localhost:7681
|
||||
|
||||
```bash
|
||||
$ libwebsockets-test-echo
|
||||
```
|
||||
|
||||
$ libwebsockets-test-echo
|
||||
```
|
||||
and do the echo test against the local echo server
|
||||
|
||||
```bash
|
||||
$ libwebsockets-test-echo --client localhost --port 7681
|
||||
```
|
||||
|
||||
$ libwebsockets-test-echo --client localhost --port 7681
|
||||
```
|
||||
If you add the `--ssl` switch to both the client and server, you can also test
|
||||
with an encrypted link.
|
||||
|
||||
|
||||
Testing SSL on the client side
|
||||
------------------------------
|
||||
@section tassl Testing SSL on the client side
|
||||
|
||||
To test SSL/WSS client action, just run the client test with
|
||||
|
||||
```bash
|
||||
$ libwebsockets-test-client localhost --ssl
|
||||
```
|
||||
|
||||
By default the client test applet is set to accept selfsigned
|
||||
$ libwebsockets-test-client localhost --ssl
|
||||
```
|
||||
By default the client test applet is set to accept self-signed
|
||||
certificates used by the test server, this is indicated by the
|
||||
`use_ssl` var being set to `2`. Set it to `1` to reject any server
|
||||
certificate that it doesn't have a trusted CA cert for.
|
||||
|
||||
|
||||
Using the websocket ping utility
|
||||
--------------------------------
|
||||
@section taping Using the websocket ping utility
|
||||
|
||||
libwebsockets-test-ping connects as a client to a remote
|
||||
websocket server using 04 protocol and pings it like the
|
||||
websocket server and pings it like the
|
||||
normal unix ping utility.
|
||||
|
||||
```bash
|
||||
$ libwebsockets-test-ping localhost
|
||||
handshake OK for protocol lws-mirror-protocol
|
||||
Websocket PING localhost.localdomain (127.0.0.1) 64 bytes of data.
|
||||
64 bytes from localhost: req=1 time=0.1ms
|
||||
64 bytes from localhost: req=2 time=0.1ms
|
||||
64 bytes from localhost: req=3 time=0.1ms
|
||||
64 bytes from localhost: req=4 time=0.2ms
|
||||
64 bytes from localhost: req=5 time=0.1ms
|
||||
64 bytes from localhost: req=6 time=0.2ms
|
||||
64 bytes from localhost: req=7 time=0.2ms
|
||||
64 bytes from localhost: req=8 time=0.1ms
|
||||
^C
|
||||
--- localhost.localdomain websocket ping statistics ---
|
||||
8 packets transmitted, 8 received, 0% packet loss, time 7458ms
|
||||
rtt min/avg/max = 0.110/0.185/0.218 ms
|
||||
$
|
||||
```
|
||||
|
||||
$ libwebsockets-test-ping localhost
|
||||
handshake OK for protocol lws-mirror-protocol
|
||||
Websocket PING localhost.localdomain (127.0.0.1) 64 bytes of data.
|
||||
64 bytes from localhost: req=1 time=0.1ms
|
||||
64 bytes from localhost: req=2 time=0.1ms
|
||||
64 bytes from localhost: req=3 time=0.1ms
|
||||
64 bytes from localhost: req=4 time=0.2ms
|
||||
64 bytes from localhost: req=5 time=0.1ms
|
||||
64 bytes from localhost: req=6 time=0.2ms
|
||||
64 bytes from localhost: req=7 time=0.2ms
|
||||
64 bytes from localhost: req=8 time=0.1ms
|
||||
^C
|
||||
--- localhost.localdomain websocket ping statistics ---
|
||||
8 packets transmitted, 8 received, 0% packet loss, time 7458ms
|
||||
rtt min/avg/max = 0.110/0.185/0.218 ms
|
||||
$
|
||||
```
|
||||
By default it sends 64 byte payload packets using the 04
|
||||
PING packet opcode type. You can change the payload size
|
||||
using the `-s=` flag, up to a maximum of 125 mandated by the
|
||||
|
@ -167,49 +228,44 @@ Before you can even use the PING opcode that is part of the
|
|||
standard, you must complete a handshake with a specified
|
||||
protocol. By default lws-mirror-protocol is used which is
|
||||
supported by the test server. But if you are using it on
|
||||
another server, you can specify the protcol to handshake with
|
||||
another server, you can specify the protocol to handshake with
|
||||
by `--protocol=protocolname`
|
||||
|
||||
|
||||
Fraggle test app
|
||||
----------------
|
||||
@section ta fraggle Fraggle test app
|
||||
|
||||
By default it runs in server mode
|
||||
|
||||
```bash
|
||||
$ libwebsockets-test-fraggle
|
||||
libwebsockets test fraggle
|
||||
(C) Copyright 2010-2011 Andy Green <andy@warmcat.com> licensed under LGPL2.1
|
||||
Compiled with SSL support, not using it
|
||||
Listening on port 7681
|
||||
server sees client connect
|
||||
accepted v06 connection
|
||||
Spamming 360 random fragments
|
||||
Spamming session over, len = 371913. sum = 0x2D3C0AE
|
||||
Spamming 895 random fragments
|
||||
Spamming session over, len = 875970. sum = 0x6A74DA1
|
||||
...
|
||||
```
|
||||
|
||||
$ libwebsockets-test-fraggle
|
||||
libwebsockets test fraggle
|
||||
(C) Copyright 2010-2011 Andy Green <andy@warmcat.com> licensed under LGPL2.1
|
||||
Compiled with SSL support, not using it
|
||||
Listening on port 7681
|
||||
server sees client connect
|
||||
accepted v06 connection
|
||||
Spamming 360 random fragments
|
||||
Spamming session over, len = 371913. sum = 0x2D3C0AE
|
||||
Spamming 895 random fragments
|
||||
Spamming session over, len = 875970. sum = 0x6A74DA1
|
||||
...
|
||||
```
|
||||
You need to run a second session in client mode, you have to
|
||||
give the `-c` switch and the server address at least:
|
||||
|
||||
```bash
|
||||
$ libwebsockets-test-fraggle -c localhost
|
||||
libwebsockets test fraggle
|
||||
(C) Copyright 2010-2011 Andy Green <andy@warmcat.com> licensed under LGPL2.1
|
||||
Client mode
|
||||
Connecting to localhost:7681
|
||||
denied deflate-stream extension
|
||||
handshake OK for protocol fraggle-protocol
|
||||
client connects to server
|
||||
EOM received 371913 correctly from 360 fragments
|
||||
EOM received 875970 correctly from 895 fragments
|
||||
EOM received 247140 correctly from 258 fragments
|
||||
EOM received 695451 correctly from 692 fragments
|
||||
...
|
||||
```
|
||||
|
||||
$ libwebsockets-test-fraggle -c localhost
|
||||
libwebsockets test fraggle
|
||||
(C) Copyright 2010-2011 Andy Green <andy@warmcat.com> licensed under LGPL2.1
|
||||
Client mode
|
||||
Connecting to localhost:7681
|
||||
denied deflate-stream extension
|
||||
handshake OK for protocol fraggle-protocol
|
||||
client connects to server
|
||||
EOM received 371913 correctly from 360 fragments
|
||||
EOM received 875970 correctly from 895 fragments
|
||||
EOM received 247140 correctly from 258 fragments
|
||||
EOM received 695451 correctly from 692 fragments
|
||||
...
|
||||
```
|
||||
The fraggle test sends a random number up to 1024 fragmented websocket frames
|
||||
each of a random size between 1 and 2001 bytes in a single message, then sends
|
||||
a checksum and starts sending a new randomly sized and fragmented message.
|
||||
|
@ -219,31 +275,30 @@ same checksum using websocket framing to see when the message has ended. It
|
|||
then accepts the server checksum message and compares that to its checksum.
|
||||
|
||||
|
||||
proxy support
|
||||
-------------
|
||||
@section taproxy proxy support
|
||||
|
||||
The http_proxy environment variable is respected by the client
|
||||
connection code for both `ws://` and `wss://`. It doesn't support
|
||||
authentication.
|
||||
|
||||
You use it like this
|
||||
|
||||
```bash
|
||||
$ export http_proxy=myproxy.com:3128
|
||||
$ libwebsockets-test-client someserver.com
|
||||
```
|
||||
$ export http_proxy=myproxy.com:3128
|
||||
$ libwebsockets-test-client someserver.com
|
||||
```
|
||||
|
||||
|
||||
debug logging
|
||||
-------------
|
||||
@section talog debug logging
|
||||
|
||||
By default logging of severity "notice", "warn" or "err" is enabled to stderr.
|
||||
|
||||
Again by default other logging is compiled in but disabled from printing.
|
||||
|
||||
If you want to eliminate the debug logging below notice in severity, use the
|
||||
`--disable-debug` configure option to have it removed from the code by the
|
||||
preprocesser.
|
||||
By default debug logs below "notice" in severity are not compiled in. To get
|
||||
them included, add this option in CMAKE
|
||||
|
||||
```
|
||||
$ cmake .. -DCMAKE_BUILD_TYPE=DEBUG
|
||||
```
|
||||
|
||||
If you want to see more detailed debug logs, you can control a bitfield to
|
||||
select which logs types may print using the `lws_set_log_level()` api, in the
|
||||
|
@ -262,15 +317,13 @@ available are (OR together the numbers to select multiple)
|
|||
- 512 LATENCY
|
||||
|
||||
|
||||
Websocket version supported
|
||||
---------------------------
|
||||
@section ws13 Websocket version supported
|
||||
|
||||
The final IETF standard is supported for both client and server, protocol
|
||||
version 13.
|
||||
|
||||
|
||||
Latency Tracking
|
||||
----------------
|
||||
@section latency Latency Tracking
|
||||
|
||||
Since libwebsockets runs using `poll()` and a single threaded approach, any
|
||||
unexpected latency coming from system calls would be bad news. There's now
|
||||
|
@ -291,3 +344,47 @@ in the logging is the time taken by this particular attempt. High figures
|
|||
here may indicate a problem, or if you system is loaded with another app at
|
||||
that time, such as the browser, it may simply indicate the OS gave preferential
|
||||
treatment to the other app during that call.
|
||||
|
||||
|
||||
@section autobahn Autobahn Test Suite
|
||||
|
||||
Lws can be tested against the autobahn websocket fuzzer.
|
||||
|
||||
1) pip install autobahntestsuite
|
||||
|
||||
2) wstest -m fuzzingserver
|
||||
|
||||
3) Run tests like this
|
||||
|
||||
libwebsockets-test-echo --client localhost --port 9001 -u "/runCase?case=20&agent=libwebsockets" -v -d 65535 -n 1
|
||||
|
||||
(this runs test 20)
|
||||
|
||||
4) In a browser, go here
|
||||
|
||||
http://localhost:8080/test_browser.html
|
||||
|
||||
fill in "libwebsockets" in "User Agent Identifier" and press "Update Reports (Manual)"
|
||||
|
||||
5) In a browser go to the directory you ran wstest in (eg, /projects/libwebsockets)
|
||||
|
||||
file:///projects/libwebsockets/reports/clients/index.html
|
||||
|
||||
to see the results
|
||||
|
||||
|
||||
@section autobahnnotes Autobahn Test Notes
|
||||
|
||||
1) Autobahn tests the user code + lws implementation. So to get the same
|
||||
results, you need to follow test-echo.c in terms of user implementation.
|
||||
|
||||
2) Two of the tests make no sense for Libwebsockets to support and we fail them.
|
||||
|
||||
- Tests 2.10 + 2.11: sends multiple pings on one connection. Lws policy is to
|
||||
only allow one active ping in flight on each connection, the rest are dropped.
|
||||
The autobahn test itself admits this is not part of the standard, just someone's
|
||||
random opinion about how they think a ws server should act. So we will fail
|
||||
this by design and it is no problem about RFC6455 compliance.
|
||||
|
||||
|
||||
|
||||
|
|
34
appveyor.yml
34
appveyor.yml
|
@ -1,5 +1,8 @@
|
|||
environment:
|
||||
matrix:
|
||||
- LWS_METHOD: lwsws
|
||||
CMAKE_ARGS: -DLWS_WITH_LWSWS=1 -DSQLITE3_INCLUDE_DIRS=C:\assets\sqlite3 -DSQLITE3_LIBRARIES=C:\assets\sqlite3\sqlite3.lib -DLIBUV_INCLUDE_DIRS=C:\assets\libuv\include -DLIBUV_LIBRARIES=C:\assets\libuv\libuv.lib
|
||||
|
||||
- LWS_METHOD: default
|
||||
|
||||
- LWS_METHOD: noserver
|
||||
|
@ -14,10 +17,19 @@ environment:
|
|||
- LWS_METHOD: nossl
|
||||
CMAKE_ARGS: -DLWS_WITH_SSL=OFF
|
||||
install:
|
||||
- appveyor DownloadFile https://slproweb.com/download/Win32OpenSSL-1_0_2d.exe
|
||||
- Win32OpenSSL-1_0_2d.exe /silent /verysilent /sp- /suppressmsgboxes
|
||||
- cinst -y nsis
|
||||
- SET PATH=C:\Program Files\NSIS\;C:\Program Files (x86)\NSIS\;%PATH%
|
||||
- appveyor DownloadFile https://libwebsockets.org:444/win-libuv.zip
|
||||
- mkdir c:\assets
|
||||
- mkdir c:\assets\libuv
|
||||
- 7z x -oc:\assets\libuv win-libuv.zip
|
||||
# - appveyor DownloadFile https://slproweb.com/download/Win32OpenSSL-1_0_2h.exe
|
||||
# - appveyor DownloadFile https://libwebsockets.org:444/Win32OpenSSL-1_0_2L.exe
|
||||
# - Win32OpenSSL-1_0_2L.exe /silent /verysilent /sp- /suppressmsgboxes
|
||||
- appveyor DownloadFile https://libwebsockets.org:444/nsis-3.0rc1-setup.exe
|
||||
- cmd /c start /wait nsis-3.0rc1-setup.exe /S /D=C:\nsis
|
||||
- appveyor DownloadFile https://libwebsockets.org:444/sqlite-dll-win32-x86-3130000.zip
|
||||
- mkdir c:\assets\sqlite3
|
||||
- 7z x -oc:\assets\sqlite3 sqlite-dll-win32-x86-3130000.zip
|
||||
- SET PATH=C:\Program Files\NSIS\;C:\Program Files (x86)\NSIS\;c:\nsis;%PATH%
|
||||
build:
|
||||
|
||||
build_script:
|
||||
|
@ -26,19 +38,21 @@ build_script:
|
|||
- cmake -DCMAKE_BUILD_TYPE=Release %CMAKE_ARGS% ..
|
||||
- cmake --build . --config Release
|
||||
|
||||
# TODO: Keeps braking Windows build, should be rewritten using CPack properly instead...
|
||||
#after_build:
|
||||
# TODO: Keeps breaking Windows build, should be rewritten using CPack properly instead...
|
||||
after_build:
|
||||
- 7z a lws.zip %APPVEYOR_BUILD_FOLDER%\build\lib\Release\websockets.lib %APPVEYOR_BUILD_FOLDER%\build\lib\Release\websockets.exp %APPVEYOR_BUILD_FOLDER%\build\bin\Release\websockets.dll %APPVEYOR_BUILD_FOLDER%\lib\libwebsockets.h %APPVEYOR_BUILD_FOLDER%\build\lws_config.h %APPVEYOR_BUILD_FOLDER%\build\bin\Release\*.exe
|
||||
# - cd ..
|
||||
# - cd win32port
|
||||
# - makensis -DVERSION=%APPVEYOR_BUILD_VERSION% libwebsockets.nsi
|
||||
|
||||
|
||||
artifacts:
|
||||
- name: Installer
|
||||
path: 'win32port/libwebsockets-*-install.exe'
|
||||
- path: lws.zip
|
||||
name: lws.zip
|
||||
type: Zip
|
||||
|
||||
cache:
|
||||
- C:\OpenSSL-Win32
|
||||
#cache:
|
||||
# - C:\OpenSSL-Win32
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
|
41
autobahn-test.sh
Executable file
41
autobahn-test.sh
Executable file
|
@ -0,0 +1,41 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -u
|
||||
|
||||
N=1
|
||||
OS=`uname`
|
||||
|
||||
for i in '1.1.1' '1.1.2' '1.1.3' '1.1.4' '1.1.5' '1.1.6' '1.1.7' '1.1.8' '1.2.1' '1.2.2' '1.2.3' '1.2.4' '1.2.5' '1.2.6' '1.2.7' '1.2.8' '2.1' '2.2' '2.3' '2.4' '2.5' '2.6' '2.7' '2.8' '2.9' '2.10' '2.11' '3.1' '3.2' '3.3' '3.4' '3.5' '3.6' '3.7' '4.1.1' '4.1.2' '4.1.3' '4.1.4' '4.1.5' '4.2.1' '4.2.2' '4.2.3' '4.2.4' '4.2.5' '5.1' '5.2' '5.3' '5.4' '5.5' '5.6' '5.7' '5.8' '5.9' '5.10' '5.11' '5.12' '5.13' '5.14' '5.15' '5.16' '5.17' '5.18' '5.19' '5.20' '6.1.1' '6.1.2' '6.1.3' '6.2.1' '6.2.2' '6.2.3' '6.2.4' '6.3.1' '6.3.2' '6.4.1' '6.4.2' '6.4.3' '6.4.4' '6.5.1' '6.5.2' '6.5.3' '6.5.4' '6.5.5' '6.6.1' '6.6.2' '6.6.3' '6.6.4' '6.6.5' '6.6.6' '6.6.7' '6.6.8' '6.6.9' '6.6.10' '6.6.11' '6.7.1' '6.7.2' '6.7.3' '6.7.4' '6.8.1' '6.8.2' '6.9.1' '6.9.2' '6.9.3' '6.9.4' '6.10.1' '6.10.2' '6.10.3' '6.11.1' '6.11.2' '6.11.3' '6.11.4' '6.11.5' '6.12.1' '6.12.2' '6.12.3' '6.12.4' '6.12.5' '6.12.6' '6.12.7' '6.12.8' '6.13.1' '6.13.2' '6.13.3' '6.13.4' '6.13.5' '6.14.1' '6.14.2' '6.14.3' '6.14.4' '6.14.5' '6.14.6' '6.14.7' '6.14.8' '6.14.9' '6.14.10' '6.15.1' '6.16.1' '6.16.2' '6.16.3' '6.17.1' '6.17.2' '6.17.3' '6.17.4' '6.17.5' '6.18.1' '6.18.2' '6.18.3' '6.18.4' '6.18.5' '6.19.1' '6.19.2' '6.19.3' '6.19.4' '6.19.5' '6.20.1' '6.20.2' '6.20.3' '6.20.4' '6.20.5' '6.20.6' '6.20.7' '6.21.1' '6.21.2' '6.21.3' '6.21.4' '6.21.5' '6.21.6' '6.21.7' '6.21.8' '6.22.1' '6.22.2' '6.22.3' '6.22.4' '6.22.5' '6.22.6' '6.22.7' '6.22.8' '6.22.9' '6.22.10' '6.22.11' '6.22.12' '6.22.13' '6.22.14' '6.22.15' '6.22.16' '6.22.17' '6.22.18' '6.22.19' '6.22.20' '6.22.21' '6.22.22' '6.22.23' '6.22.24' '6.22.25' '6.22.26' '6.22.27' '6.22.28' '6.22.29' '6.22.30' '6.22.31' '6.22.32' '6.22.33' '6.22.34' '6.23.1' '6.23.2' '6.23.3' '6.23.4' '6.23.5' '6.23.6' '6.23.7' '7.1.1' '7.1.2' '7.1.3' '7.1.4' '7.1.5' '7.1.6' '7.3.1' '7.3.2' '7.3.3' '7.3.4' '7.3.5' '7.3.6' '7.5.1' '7.7.1' '7.7.2' '7.7.3' '7.7.4' '7.7.5' '7.7.6' '7.7.7' '7.7.8' '7.7.9' '7.7.10' '7.7.11' '7.7.12' '7.7.13' '7.9.1' '7.9.2' '7.9.3' '7.9.4' '7.9.5' '7.9.6' '7.9.7' '7.9.8' '7.9.9' '7.9.10' '7.9.11' '7.9.12' '7.9.13' '7.13.1' '7.13.2' '9.1.1' '9.1.2' '9.1.3' '9.1.4' '9.1.5' '9.1.6' '9.2.1' '9.2.2' '9.2.3' '9.2.4' '9.2.5' '9.2.6' '9.3.1' '9.3.2' '9.3.3' '9.3.4' '9.3.5' '9.3.6' '9.3.7' '9.3.8' '9.3.9' '9.4.1' '9.4.2' '9.4.3' '9.4.4' '9.4.5' '9.4.6' '9.4.7' '9.4.8' '9.4.9' '9.5.1' '9.5.2' '9.5.3' '9.5.4' '9.5.5' '9.5.6' '9.6.1' '9.6.2' '9.6.3' '9.6.4' '9.6.5' '9.6.6' '9.7.1' '9.7.2' '9.7.3' '9.7.4' '9.7.5' '9.7.6' '9.8.1' '9.8.2' '9.8.3' '9.8.4' '9.8.5' '9.8.6' '10.1.1' '12.1.1' '12.1.2' '12.1.3' '12.1.4' '12.1.5' '12.1.6' '12.1.7' '12.1.8' '12.1.9' '12.1.10' '12.1.11' '12.1.12' '12.1.13' '12.1.14' '12.1.15' '12.1.16' '12.1.17' '12.1.18' '12.2.1' '12.2.2' '12.2.3' '12.2.4' '12.2.5' '12.2.6' '12.2.7' '12.2.8' '12.2.9' '12.2.10' '12.2.11' '12.2.12' '12.2.13' '12.2.14' '12.2.15' '12.2.16' '12.2.17' '12.2.18' '12.3.1' '12.3.2' '12.3.3' '12.3.4' '12.3.5' '12.3.6' '12.3.7' '12.3.8' '12.3.9' '12.3.10' '12.3.11' '12.3.12' '12.3.13' '12.3.14' '12.3.15' '12.3.16' '12.3.17' '12.3.18' '12.4.1' '12.4.2' '12.4.3' '12.4.4' '12.4.5' '12.4.6' '12.4.7' '12.4.8' '12.4.9' '12.4.10' '12.4.11' '12.4.12' '12.4.13' '12.4.14' '12.4.15' '12.4.16' '12.4.17' '12.4.18' '12.5.1' '12.5.2' '12.5.3' '12.5.4' '12.5.5' '12.5.6' '12.5.7' '12.5.8' '12.5.9' '12.5.10' '12.5.11' '12.5.12' '12.5.13' '12.5.14' '12.5.15' '12.5.16' '12.5.17' '12.5.18' '13.1.1' '13.1.2' '13.1.3' '13.1.4' '13.1.5' '13.1.6' '13.1.7' '13.1.8' '13.1.9' '13.1.10' '13.1.11' '13.1.12' '13.1.13' '13.1.14' '13.1.15' '13.1.16' '13.1.17' '13.1.18' '13.2.1' '13.2.2' '13.2.3' '13.2.4' '13.2.5' '13.2.6' '13.2.7' '13.2.8' '13.2.9' '13.2.10' '13.2.11' '13.2.12' '13.2.13' '13.2.14' '13.2.15' '13.2.16' '13.2.17' '13.2.18' '13.3.1' '13.3.2' '13.3.3' '13.3.4' '13.3.5' '13.3.6' '13.3.7' '13.3.8' '13.3.9' '13.3.10' '13.3.11' '13.3.12' '13.3.13' '13.3.14' '13.3.15' '13.3.16' '13.3.17' '13.3.18' '13.4.1' '13.4.2' '13.4.3' '13.4.4' '13.4.5' '13.4.6' '13.4.7' '13.4.8' '13.4.9' '13.4.10' '13.4.11' '13.4.12' '13.4.13' '13.4.14' '13.4.15' '13.4.16' '13.4.17' '13.4.18' '13.5.1' '13.5.2' '13.5.3' '13.5.4' '13.5.5' '13.5.6' '13.5.7' '13.5.8' '13.5.9' '13.5.10' '13.5.11' '13.5.12' '13.5.13' '13.5.14' '13.5.15' '13.5.16' '13.5.17' '13.5.18' '13.6.1' '13.6.2' '13.6.3' '13.6.4' '13.6.5' '13.6.6' '13.6.7' '13.6.8' '13.6.9' '13.6.10' '13.6.11' '13.6.12' '13.6.13' '13.6.14' '13.6.15' '13.6.16' '13.6.17' '13.6.18' '13.7.1' '13.7.2' '13.7.3' '13.7.4' '13.7.5' '13.7.6' '13.7.7' '13.7.8' '13.7.9' '13.7.10' '13.7.11' '13.7.12' '13.7.13' '13.7.14' '13.7.15' '13.7.16' '13.7.17' '13.7.18' ; do
|
||||
libwebsockets-test-echo --client 127.0.0.1 --port 9001 -u "/runCase?case=$N&agent=libwebsockets" -v -n 1 &
|
||||
|
||||
C=99
|
||||
while [ $C -gt 8 ] ; do
|
||||
if [ $OS=SunOS ] ; then
|
||||
C=`ps -ef | grep libwebsockets-test-echo | wc -l`
|
||||
else
|
||||
C=`ps fax | grep libwebsockets-test-echo | wc -l`
|
||||
fi
|
||||
if [ $C -gt 8 ] ; then
|
||||
sleep 1s
|
||||
fi
|
||||
done
|
||||
|
||||
N=$(( $N + 1 ))
|
||||
done
|
||||
|
||||
echo "waiting for forks to complete..."
|
||||
|
||||
while [ 1 ] ; do
|
||||
if [ $OS=SunOS ] ; then
|
||||
n=`ps -ef | grep libwebsocket | grep -v grep | wc -l`
|
||||
else
|
||||
n=`ps fax | grep libwebsocket | grep -v grep | wc -l`
|
||||
fi
|
||||
echo "$n forks running..."
|
||||
if [ $n -eq 0 ] ; then
|
||||
echo "Completed"
|
||||
exit 0
|
||||
fi
|
||||
sleep 1s
|
||||
done
|
||||
|
41
component.mk
Normal file
41
component.mk
Normal file
|
@ -0,0 +1,41 @@
|
|||
COMPONENT_ADD_INCLUDEDIRS := ../../../../../../../../../../../../../../../../../../$(COMPONENT_BUILD_DIR)/include
|
||||
|
||||
COMPONENT_OWNBUILDTARGET:= 1
|
||||
|
||||
CROSS_PATH1:=$(shell which xtensa-esp32-elf-gcc )
|
||||
CROSS_PATH:= $(shell dirname $(CROSS_PATH1) )/..
|
||||
|
||||
#-DLWS_USE_BORINGSSL=1 \
|
||||
# -DOPENSSL_ROOT_DIR="${PWD}/../../boringssl" \
|
||||
# -DOPENSSL_LIBRARIES="${PWD}/../../boringssl/build/ssl/libssl.a;${PWD}/../../boringssl/build/crypto/libcrypto.a" \
|
||||
# -DOPENSSL_INCLUDE_DIRS="${PWD}/../../boringssl/include" \
|
||||
|
||||
# -DNDEBUG=1 after cflags
|
||||
# -DOPENSSL_LIBRARIES=x \
|
||||
# -DCOMPONENT_PATH=$(COMPONENT_PATH) \
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
cd $(COMPONENT_BUILD_DIR) ; \
|
||||
echo "doing lws cmake" ; \
|
||||
cmake $(COMPONENT_PATH) -DLWS_C_FLAGS="$(CFLAGS) -DNDEBUG=1" \
|
||||
-DIDF_PATH=$(IDF_PATH) \
|
||||
-DCROSS_PATH=$(CROSS_PATH) \
|
||||
-DBUILD_DIR_BASE=$(BUILD_DIR_BASE) \
|
||||
-DCMAKE_TOOLCHAIN_FILE=$(COMPONENT_PATH)/cross-esp32.cmake \
|
||||
-DCMAKE_BUILD_TYPE=RELEASE \
|
||||
-DOPENSSL_INCLUDE_DIR=${IDF_PATH}/components/openssl/include \
|
||||
-DLWS_WITH_STATS=0 \
|
||||
-DZLIB_LIBRARY=$(BUILD_DIR_BASE)/zlib/libzlib.a \
|
||||
-DZLIB_INCLUDE_DIR=$(COMPONENT_PATH)/../zlib \
|
||||
-DLWS_WITH_ESP32=1 ;\
|
||||
make && \
|
||||
cp ${COMPONENT_BUILD_DIR}/lib/libwebsockets.a ${COMPONENT_BUILD_DIR}/liblibwebsockets.a
|
||||
|
||||
clean: myclean
|
||||
|
||||
myclean:
|
||||
rm -rf ./build
|
||||
|
||||
INCLUDES := $(INCLUDES) -I build/
|
||||
|
52
contrib/abi/README.md
Normal file
52
contrib/abi/README.md
Normal file
|
@ -0,0 +1,52 @@
|
|||
ABI Compatility Tracking
|
||||
========================
|
||||
|
||||
This directory contains files that can be used to generate an ABI compatibility
|
||||
timeline for libwebsockets. This gives users an idea of where the library has
|
||||
changed and can be used by the developers to see when incompatible changes have
|
||||
been introduced and either increase the library SO version or fix the changes.
|
||||
|
||||
The tools used are the abi-\* family available at https://github.com/lvc/ and
|
||||
some example output is here: http://abi-laboratory.pro/tracker/timeline/libuv/
|
||||
|
||||
The tools download existing source tarballs and git repository to generate this
|
||||
data, so past versions are compared and in-development code can be compared as
|
||||
well.
|
||||
|
||||
Although the application is not being included here, FYI the license is dual
|
||||
LGPL2 / GPL2 at your choice.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The author provides an easy way to install the various tools he provides:
|
||||
|
||||
git clone https://github.com/lvc/installer
|
||||
cd installer
|
||||
make prefix=/usr/local target=abi-tracker
|
||||
|
||||
It will also list any dependencies that you need to install through normal
|
||||
means. (Although in the case of needing "elfutils-libelf-devel", it may
|
||||
crash during install of vtable-dumper without giving a nice list)
|
||||
|
||||
|
||||
Generating the output
|
||||
---------------------
|
||||
|
||||
Use the `lws-abi-update.sh` script to download the source files, build them and
|
||||
generate the output html. The output can be deployed to a directory on a web
|
||||
server for example. Modify the commented line in lws-abi-update.sh to do this.
|
||||
|
||||
As it is configured, lws-abi-update.sh will only download new source - ones
|
||||
that it hasn't built before - so is suitable for use with a cron job.
|
||||
|
||||
|
||||
Viewing the output
|
||||
------------------
|
||||
|
||||
The best place to start looking at the data is the `timeline/libwebsockets`
|
||||
directory. If your path is on a web server, navigate there, otherwise you could
|
||||
try:
|
||||
|
||||
lynx timeline/libwebsockets/
|
107
contrib/abi/libwebsockets.json
Normal file
107
contrib/abi/libwebsockets.json
Normal file
|
@ -0,0 +1,107 @@
|
|||
{
|
||||
"Name": "libwebsockets",
|
||||
"SourceUrl": "https://github.com/warmcat/libwebsockets/releases",
|
||||
"Git": "https://github.com/warmcat/libwebsockets",
|
||||
|
||||
"Versions": [
|
||||
{
|
||||
"Number": "current",
|
||||
"Installed": "installed/libwebsockets/current",
|
||||
"Source": "src/libwebsockets/current",
|
||||
"Changelog": "On",
|
||||
"HeadersDiff": "On",
|
||||
"PkgDiff": "Off",
|
||||
"ABIView": "Off",
|
||||
"ABIDiff": "Off"
|
||||
},
|
||||
{
|
||||
"Number": "1.7.4",
|
||||
"Installed": "installed/libwebsockets/1.7.4",
|
||||
"Source": "src/libwebsockets/1.7.4/libwebsockets-1.7.4.tar.gz",
|
||||
"Changelog": "Off",
|
||||
"HeadersDiff": "On",
|
||||
"PkgDiff": "Off",
|
||||
"ABIView": "Off",
|
||||
"ABIDiff": "Off"
|
||||
},
|
||||
{
|
||||
"Number": "1.7.3",
|
||||
"Installed": "installed/libwebsockets/1.7.3",
|
||||
"Source": "src/libwebsockets/1.7.3/libwebsockets-1.7.3.tar.gz",
|
||||
"Changelog": "Off",
|
||||
"HeadersDiff": "On",
|
||||
"PkgDiff": "Off",
|
||||
"ABIView": "Off",
|
||||
"ABIDiff": "Off"
|
||||
},
|
||||
{
|
||||
"Number": "1.7.2",
|
||||
"Installed": "installed/libwebsockets/1.7.2",
|
||||
"Source": "src/libwebsockets/1.7.2/libwebsockets-1.7.2.tar.gz",
|
||||
"Changelog": "Off",
|
||||
"HeadersDiff": "On",
|
||||
"PkgDiff": "Off",
|
||||
"ABIView": "Off",
|
||||
"ABIDiff": "Off"
|
||||
},
|
||||
{
|
||||
"Number": "1.7.1",
|
||||
"Installed": "installed/libwebsockets/1.7.1",
|
||||
"Source": "src/libwebsockets/1.7.1/libwebsockets-1.7.1.tar.gz",
|
||||
"Changelog": "Off",
|
||||
"HeadersDiff": "On",
|
||||
"PkgDiff": "Off",
|
||||
"ABIView": "Off",
|
||||
"ABIDiff": "Off"
|
||||
},
|
||||
{
|
||||
"Number": "1.7.0",
|
||||
"Installed": "installed/libwebsockets/1.7.0",
|
||||
"Source": "src/libwebsockets/1.7.0/libwebsockets-1.7.0.tar.gz",
|
||||
"Changelog": "Off",
|
||||
"HeadersDiff": "On",
|
||||
"PkgDiff": "Off",
|
||||
"ABIView": "Off",
|
||||
"ABIDiff": "Off"
|
||||
},
|
||||
{
|
||||
"Number": "1.6.3",
|
||||
"Installed": "installed/libwebsockets/1.6.3",
|
||||
"Source": "src/libwebsockets/1.6.3/libwebsockets-1.6.3.tar.gz",
|
||||
"Changelog": "Off",
|
||||
"HeadersDiff": "On",
|
||||
"PkgDiff": "Off",
|
||||
"ABIView": "Off",
|
||||
"ABIDiff": "Off"
|
||||
},
|
||||
{
|
||||
"Number": "1.6.2",
|
||||
"Installed": "installed/libwebsockets/1.6.2",
|
||||
"Source": "src/libwebsockets/1.6.2/libwebsockets-1.6.2.tar.gz",
|
||||
"Changelog": "Off",
|
||||
"HeadersDiff": "On",
|
||||
"PkgDiff": "Off",
|
||||
"ABIView": "Off",
|
||||
"ABIDiff": "Off"
|
||||
},
|
||||
{
|
||||
"Number": "1.6.1",
|
||||
"Installed": "installed/libwebsockets/1.6.1",
|
||||
"Source": "src/libwebsockets/1.6.1/libwebsockets-1.6.1.tar.gz",
|
||||
"Changelog": "Off",
|
||||
"HeadersDiff": "On",
|
||||
"PkgDiff": "Off",
|
||||
"ABIView": "Off",
|
||||
"ABIDiff": "Off"
|
||||
},
|
||||
{
|
||||
"Number": "1.5.1",
|
||||
"Installed": "installed/libwebsockets/1.5.1",
|
||||
"Source": "src/libwebsockets/1.5.1/libwebsockets-1.5.1.tar.gz",
|
||||
"Changelog": "Off",
|
||||
"HeadersDiff": "On",
|
||||
"PkgDiff": "Off",
|
||||
"ABIView": "Off",
|
||||
"ABIDiff": "Off"
|
||||
}]
|
||||
}
|
17
contrib/abi/lws-abi-update.sh
Executable file
17
contrib/abi/lws-abi-update.sh
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ ! -z "$1" ] ; then
|
||||
OUT=$1
|
||||
else
|
||||
OUT="/tmp/lws-abi-track-htdocs"
|
||||
fi
|
||||
|
||||
D=`dirname $0`
|
||||
if [ ! -z "$D" ] ; then
|
||||
D=$D/
|
||||
fi
|
||||
J=$D"libwebsockets.json"
|
||||
|
||||
abi-monitor -get -build-new $J
|
||||
abi-tracker -build $J
|
||||
abi-tracker -deploy $OUT $J
|
114
contrib/android-make-script.sh
Executable file
114
contrib/android-make-script.sh
Executable file
|
@ -0,0 +1,114 @@
|
|||
#!/bin/bash
|
||||
|
||||
#
|
||||
# Build libwebsockets static library for Android
|
||||
#
|
||||
# requires debian package xutils-dev for makedepend (openssl make depend)
|
||||
#
|
||||
|
||||
# This is based on http://stackoverflow.com/questions/11929773/compiling-the-latest-openssl-for-android/
|
||||
# via https://github.com/warmcat/libwebsockets/pull/502
|
||||
|
||||
# path to NDK
|
||||
export NDK=/opt/Android/SDK/ndk-bundle
|
||||
|
||||
set -e
|
||||
|
||||
# Download packages libz, openssl and libwebsockets
|
||||
|
||||
[ ! -f zlib-1.2.8.tar.gz ] && {
|
||||
wget http://prdownloads.sourceforge.net/libpng/zlib-1.2.8.tar.gz
|
||||
}
|
||||
|
||||
[ ! -f openssl-1.0.2g.tar.gz ] && {
|
||||
wget https://openssl.org/source/openssl-1.0.2g.tar.gz
|
||||
}
|
||||
|
||||
[ ! -f libwebsockets.tar.gz ] && {
|
||||
git clone https://github.com/warmcat/libwebsockets.git
|
||||
tar caf libwebsockets.tar.gz libwebsockets
|
||||
}
|
||||
|
||||
# Clean then Unzip
|
||||
|
||||
[ -d zlib-1.2.8 ] && rm -fr zlib-1.2.8
|
||||
[ -d openssl-1.0.2g ] && rm -fr openssl-1.0.2g
|
||||
[ -d libwebsockets ] && rm -fr libwebsockets
|
||||
[ -d android-toolchain-arm ] && rm -fr android-toolchain-arm
|
||||
tar xf zlib-1.2.8.tar.gz
|
||||
tar xf openssl-1.0.2g.tar.gz
|
||||
tar xf libwebsockets.tar.gz
|
||||
|
||||
# create a local android toolchain
|
||||
$NDK/build/tools/make-standalone-toolchain.sh \
|
||||
--platform=android-9 \
|
||||
--toolchain=arm-linux-androideabi-4.9 \
|
||||
--install-dir=`pwd`/android-toolchain-arm
|
||||
|
||||
# setup environment to use the gcc/ld from the android toolchain
|
||||
export TOOLCHAIN_PATH=`pwd`/android-toolchain-arm/bin
|
||||
export TOOL=arm-linux-androideabi
|
||||
export NDK_TOOLCHAIN_BASENAME=${TOOLCHAIN_PATH}/${TOOL}
|
||||
export CC=$NDK_TOOLCHAIN_BASENAME-gcc
|
||||
export CXX=$NDK_TOOLCHAIN_BASENAME-g++
|
||||
export LINK=${CXX}
|
||||
export LD=$NDK_TOOLCHAIN_BASENAME-ld
|
||||
export AR=$NDK_TOOLCHAIN_BASENAME-ar
|
||||
export RANLIB=$NDK_TOOLCHAIN_BASENAME-ranlib
|
||||
export STRIP=$NDK_TOOLCHAIN_BASENAME-strip
|
||||
|
||||
# setup buildflags
|
||||
export ARCH_FLAGS="-mthumb"
|
||||
export ARCH_LINK=
|
||||
export CPPFLAGS=" ${ARCH_FLAGS} -fpic -ffunction-sections -funwind-tables -fstack-protector -fno-strict-aliasing -finline-limit=64 "
|
||||
export CXXFLAGS=" ${ARCH_FLAGS} -fpic -ffunction-sections -funwind-tables -fstack-protector -fno-strict-aliasing -finline-limit=64 -frtti -fexceptions "
|
||||
export CFLAGS=" ${ARCH_FLAGS} -fpic -ffunction-sections -funwind-tables -fstack-protector -fno-strict-aliasing -finline-limit=64 "
|
||||
export LDFLAGS=" ${ARCH_LINK} "
|
||||
|
||||
# configure and build zlib
|
||||
[ ! -f ./android-toolchain-arm/lib/libz.a ] && {
|
||||
cd zlib-1.2.8
|
||||
PATH=$TOOLCHAIN_PATH:$PATH ./configure --static --prefix=$TOOLCHAIN_PATH/..
|
||||
PATH=$TOOLCHAIN_PATH:$PATH make
|
||||
PATH=$TOOLCHAIN_PATH:$PATH make install
|
||||
cd ..
|
||||
}
|
||||
|
||||
# configure and build openssl
|
||||
[ ! -f ./android-toolchain-arm/lib/libssl.a ] && {
|
||||
PREFIX=$TOOLCHAIN_PATH/..
|
||||
cd openssl-1.0.2g
|
||||
./Configure android --prefix=${PREFIX} no-shared no-idea no-mdc2 no-rc5 no-zlib no-zlib-dynamic enable-tlsext no-ssl2 no-ssl3 enable-ec enable-ecdh enable-ecp
|
||||
PATH=$TOOLCHAIN_PATH:$PATH make depend
|
||||
PATH=$TOOLCHAIN_PATH:$PATH make
|
||||
PATH=$TOOLCHAIN_PATH:$PATH make install_sw
|
||||
cd ..
|
||||
}
|
||||
|
||||
# configure and build libwebsockets
|
||||
[ ! -f ./android-toolchain-arm/lib/libwebsockets.a ] && {
|
||||
cd libwebsockets
|
||||
[ ! -d build ] && mkdir build
|
||||
cd build
|
||||
PATH=$TOOLCHAIN_PATH:$PATH cmake \
|
||||
-DCMAKE_C_COMPILER=$CC \
|
||||
-DCMAKE_AR=$AR \
|
||||
-DCMAKE_RANLIB=$RANLIB \
|
||||
-DCMAKE_C_FLAGS="$CFLAGS" \
|
||||
-DCMAKE_INSTALL_PREFIX=$TOOLCHAIN_PATH/.. \
|
||||
-DLWS_WITH_SHARED=OFF \
|
||||
-DLWS_WITH_STATIC=ON \
|
||||
-DLWS_WITHOUT_DAEMONIZE=ON \
|
||||
-DLWS_WITHOUT_TESTAPPS=ON \
|
||||
-DLWS_IPV6=OFF \
|
||||
-DLWS_USE_BUNDLED_ZLIB=OFF \
|
||||
-DLWS_WITH_SSL=ON \
|
||||
-DLWS_WITH_HTTP2=ON \
|
||||
-DLWS_OPENSSL_LIBRARIES="$TOOLCHAIN_PATH/../lib/libssl.a;$TOOLCHAIN_PATH/../lib/libcrypto.a" \
|
||||
-DLWS_OPENSSL_INCLUDE_DIRS=$TOOLCHAIN_PATH/../include \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
..
|
||||
PATH=$TOOLCHAIN_PATH:$PATH make
|
||||
PATH=$TOOLCHAIN_PATH:$PATH make install
|
||||
cd ../..
|
||||
}
|
32
cross-aarch64.cmake
Normal file
32
cross-aarch64.cmake
Normal file
|
@ -0,0 +1,32 @@
|
|||
#
|
||||
# CMake Toolchain file for crosscompiling on ARM.
|
||||
#
|
||||
# This can be used when running cmake in the following way:
|
||||
# cd build/
|
||||
# cmake .. -DCMAKE_TOOLCHAIN_FILE=../cross-arm-linux-gnueabihf.cmake
|
||||
#
|
||||
|
||||
# Target operating system name.
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
# Name of C compiler.
|
||||
set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc")
|
||||
set(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++")
|
||||
|
||||
#-nostdlib
|
||||
SET(CMAKE_C_FLAGS "-DGCC_VER=\"\\\"$(GCC_VER)\\\"\" -DARM64=1 -D__LP64__=1 -Os -g3 -fpie -mstrict-align -DOPTEE_DEV_KIT=../../../optee_os/out/arm-plat-hikey/export-ta_arm64/include -fPIC -ffunction-sections -fdata-sections" CACHE STRING "" FORCE)
|
||||
|
||||
|
||||
# Where to look for the target environment. (More paths can be added here)
|
||||
set(CMAKE_FIND_ROOT_PATH "/projects/aist-tb/arm64-tc/")
|
||||
|
||||
# Adjust the default behavior of the FIND_XXX() commands:
|
||||
# search programs in the host environment only.
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
|
||||
# Search headers and libraries in the target environment only.
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
||||
|
46
cross-esp32.cmake
Normal file
46
cross-esp32.cmake
Normal file
|
@ -0,0 +1,46 @@
|
|||
#
|
||||
# CMake Toolchain file for crosscompiling on ARM.
|
||||
#
|
||||
# This can be used when running cmake in the following way:
|
||||
# cd build/
|
||||
# cmake .. -DCMAKE_TOOLCHAIN_FILE=../cross-arm-linux-gnueabihf.cmake
|
||||
#
|
||||
|
||||
# Target operating system name.
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
|
||||
# Name of C compiler.
|
||||
set(CMAKE_C_COMPILER "${CROSS_PATH}/bin/xtensa-esp32-elf-gcc")
|
||||
set(CMAKE_AR "${CROSS_PATH}/bin/xtensa-esp32-elf-ar")
|
||||
set(CMAKE_RANLIB "${CROSS_PATH}/bin/xtensa-esp32-elf-ranlib")
|
||||
set(CMAKE_LINKER "${CROSS_PATH}/bin/xtensa-esp32-elf-ld")
|
||||
|
||||
SET(CMAKE_C_FLAGS "-nostdlib -Wall -Werror \
|
||||
-I${BUILD_DIR_BASE}/include \
|
||||
-I${IDF_PATH}/components/mdns/include \
|
||||
-I${IDF_PATH}/components/driver/include \
|
||||
-I${IDF_PATH}/components/spi_flash/include \
|
||||
-I${IDF_PATH}/components/nvs_flash/include \
|
||||
-I${IDF_PATH}/components/tcpip_adapter/include \
|
||||
-I${IDF_PATH}/components/lwip/include/lwip/posix \
|
||||
-I${IDF_PATH}/components/lwip/include/lwip \
|
||||
-I${IDF_PATH}/components/lwip/include/lwip/port \
|
||||
-I${IDF_PATH}/components/esp32/include/ \
|
||||
-I${IDF_PATH}/components/bootloader_support/include/ \
|
||||
-I${IDF_PATH}/components/app_update/include/ \
|
||||
-I$(IDF_PATH)/components/soc/esp32/include/ \
|
||||
${LWS_C_FLAGS} -Os \
|
||||
-I${IDF_PATH}/components/nvs_flash/test_nvs_host \
|
||||
-I${IDF_PATH}/components/freertos/include" CACHE STRING "" FORCE)
|
||||
|
||||
# Where to look for the target environment. (More paths can be added here)
|
||||
set(CMAKE_FIND_ROOT_PATH "${CROSS_PATH}")
|
||||
|
||||
# Adjust the default behavior of the FIND_XXX() commands:
|
||||
# search programs in the host environment only.
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
|
||||
# Search headers and libraries in the target environment only.
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
BIN
doc-assets/lwsgt-overview.png
Normal file
BIN
doc-assets/lwsgt-overview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
55
lib/alloc.c
55
lib/alloc.c
|
@ -1,9 +1,61 @@
|
|||
#include "private-libwebsockets.h"
|
||||
|
||||
#if defined(LWS_PLAT_OPTEE)
|
||||
|
||||
#define TEE_USER_MEM_HINT_NO_FILL_ZERO 0x80000000
|
||||
|
||||
void *__attribute__((weak))
|
||||
TEE_Malloc(uint32_t size, uint32_t hint)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
void *__attribute__((weak))
|
||||
TEE_Realloc(void *buffer, uint32_t newSize)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
void __attribute__((weak))
|
||||
TEE_Free(void *buffer)
|
||||
{
|
||||
}
|
||||
|
||||
void *lws_realloc(void *ptr, size_t size)
|
||||
{
|
||||
return TEE_Realloc(ptr, size);
|
||||
}
|
||||
|
||||
void *lws_malloc(size_t size)
|
||||
{
|
||||
return TEE_Malloc(size, TEE_USER_MEM_HINT_NO_FILL_ZERO);
|
||||
}
|
||||
|
||||
void lws_free(void *p)
|
||||
{
|
||||
TEE_Free(p);
|
||||
}
|
||||
|
||||
void *lws_zalloc(size_t size)
|
||||
{
|
||||
void *ptr = TEE_Malloc(size, TEE_USER_MEM_HINT_NO_FILL_ZERO);
|
||||
if (ptr)
|
||||
memset(ptr, 0, size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void lws_set_allocator(void *(*cb)(void *ptr, size_t size))
|
||||
{
|
||||
(void)cb;
|
||||
}
|
||||
#else
|
||||
|
||||
static void *_realloc(void *ptr, size_t size)
|
||||
{
|
||||
if (size)
|
||||
return realloc(ptr, size);
|
||||
#if defined(LWS_PLAT_OPTEE)
|
||||
return (void *)TEE_Realloc(ptr, size);
|
||||
#else
|
||||
return (void *)realloc(ptr, size);
|
||||
#endif
|
||||
else if (ptr)
|
||||
free(ptr);
|
||||
return NULL;
|
||||
|
@ -28,3 +80,4 @@ void lws_set_allocator(void *(*cb)(void *ptr, size_t size))
|
|||
{
|
||||
_lws_realloc = cb;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -33,12 +33,11 @@
|
|||
* Bob Trower 08/04/01 -- Create Version 0.00.00B
|
||||
*
|
||||
* I cleaned it up quite a bit to match the (linux kernel) style of the rest
|
||||
* of libwebsockets; this version is under LGPL2 like the rest of libwebsockets
|
||||
* of libwebsockets; this version is under LGPL2.1 + SLE like the rest of lws
|
||||
* since he explicitly allows sublicensing, but I give the URL above so you can
|
||||
* get the original with Bob's super-liberal terms directly if you prefer.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "private-libwebsockets.h"
|
||||
|
@ -98,11 +97,8 @@ lws_b64_encode_string(const char *in, int in_len, char *out, int out_size)
|
|||
LWS_VISIBLE int
|
||||
lws_b64_decode_string(const char *in, char *out, int out_size)
|
||||
{
|
||||
int len;
|
||||
int i;
|
||||
int done = 0;
|
||||
unsigned char v;
|
||||
unsigned char quad[4];
|
||||
int len, i, c = 0, done = 0;
|
||||
unsigned char v, quad[4];
|
||||
|
||||
while (*in) {
|
||||
|
||||
|
@ -110,25 +106,34 @@ lws_b64_decode_string(const char *in, char *out, int out_size)
|
|||
for (i = 0; i < 4 && *in; i++) {
|
||||
|
||||
v = 0;
|
||||
c = 0;
|
||||
while (*in && !v) {
|
||||
|
||||
v = *in++;
|
||||
c = v = *in++;
|
||||
v = (v < 43 || v > 122) ? 0 : decode[v - 43];
|
||||
if (v)
|
||||
v = (v == '$') ? 0 : v - 61;
|
||||
if (*in) {
|
||||
len++;
|
||||
if (v)
|
||||
quad[i] = v - 1;
|
||||
} else
|
||||
quad[i] = 0;
|
||||
}
|
||||
if (c) {
|
||||
len++;
|
||||
if (v)
|
||||
quad[i] = v - 1;
|
||||
} else
|
||||
quad[i] = 0;
|
||||
}
|
||||
|
||||
if (out_size < (done + len - 1))
|
||||
/* out buffer is too small */
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* "The '==' sequence indicates that the last group contained
|
||||
* only one byte, and '=' indicates that it contained two
|
||||
* bytes." (wikipedia)
|
||||
*/
|
||||
|
||||
if (!*in && c == '=')
|
||||
len--;
|
||||
|
||||
if (len >= 2)
|
||||
*out++ = quad[0] << 2 | quad[1] >> 4;
|
||||
if (len >= 3)
|
||||
|
@ -142,22 +147,34 @@ lws_b64_decode_string(const char *in, char *out, int out_size)
|
|||
if (done + 1 >= out_size)
|
||||
return -1;
|
||||
|
||||
*out++ = '\0';
|
||||
*out = '\0';
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int
|
||||
lws_b64_selftest(void)
|
||||
{
|
||||
char buf[64];
|
||||
int n;
|
||||
int test;
|
||||
unsigned int n, r = 0;
|
||||
unsigned int test;
|
||||
/* examples from https://en.wikipedia.org/wiki/Base64 */
|
||||
static const char * const plaintext[] = {
|
||||
"sanity check base 64"
|
||||
"any carnal pleasure.",
|
||||
"any carnal pleasure",
|
||||
"any carnal pleasur",
|
||||
"any carnal pleasu",
|
||||
"any carnal pleas",
|
||||
"Admin:kloikloi"
|
||||
};
|
||||
static const char * const coded[] = {
|
||||
"c2FuaXR5IGNoZWNrIGJhc2UgNjQ="
|
||||
"YW55IGNhcm5hbCBwbGVhc3VyZS4=",
|
||||
"YW55IGNhcm5hbCBwbGVhc3VyZQ==",
|
||||
"YW55IGNhcm5hbCBwbGVhc3Vy",
|
||||
"YW55IGNhcm5hbCBwbGVhc3U=",
|
||||
"YW55IGNhcm5hbCBwbGVhcw==",
|
||||
"QWRtaW46a2xvaWtsb2k="
|
||||
};
|
||||
|
||||
for (test = 0; test < sizeof plaintext / sizeof(plaintext[0]); test++) {
|
||||
|
@ -168,7 +185,7 @@ lws_b64_selftest(void)
|
|||
if (n != strlen(coded[test]) || strcmp(buf, coded[test])) {
|
||||
lwsl_err("Failed lws_b64 encode selftest "
|
||||
"%d result '%s' %d\n", test, buf, n);
|
||||
return -1;
|
||||
r = -1;
|
||||
}
|
||||
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
@ -176,10 +193,14 @@ lws_b64_selftest(void)
|
|||
if (n != strlen(plaintext[test]) ||
|
||||
strcmp(buf, plaintext[test])) {
|
||||
lwsl_err("Failed lws_b64 decode selftest "
|
||||
"%d result '%s' %d\n", test, buf, n);
|
||||
return -1;
|
||||
"%d result '%s' / '%s', %d / %d\n",
|
||||
test, buf, plaintext[test], n, strlen(plaintext[test]));
|
||||
r = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
lwsl_notice("Base 64 selftests passed\n");
|
||||
|
||||
return r;
|
||||
}
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,27 +21,118 @@
|
|||
|
||||
#include "private-libwebsockets.h"
|
||||
|
||||
int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c)
|
||||
/*
|
||||
* parsers.c: lws_rx_sm() needs to be roughly kept in
|
||||
* sync with changes here, esp related to ext draining
|
||||
*/
|
||||
|
||||
int lws_client_rx_sm(struct lws *wsi, unsigned char c)
|
||||
{
|
||||
int callback_action = LWS_CALLBACK_CLIENT_RECEIVE;
|
||||
int handled;
|
||||
int handled, n, m, rx_draining_ext = 0;
|
||||
unsigned short close_code;
|
||||
struct lws_tokens eff_buf;
|
||||
int m;
|
||||
unsigned char *pp;
|
||||
|
||||
if (wsi->u.ws.rx_draining_ext) {
|
||||
assert(!c);
|
||||
eff_buf.token = NULL;
|
||||
eff_buf.token_len = 0;
|
||||
lws_remove_wsi_from_draining_ext_list(wsi);
|
||||
rx_draining_ext = 1;
|
||||
lwsl_debug("%s: doing draining flow\n", __func__);
|
||||
|
||||
goto drain_extension;
|
||||
}
|
||||
|
||||
if (wsi->socket_is_permanently_unusable)
|
||||
return -1;
|
||||
|
||||
switch (wsi->lws_rx_parse_state) {
|
||||
case LWS_RXPS_NEW:
|
||||
/* control frames (PING) may interrupt checkable sequences */
|
||||
wsi->u.ws.defeat_check_utf8 = 0;
|
||||
|
||||
switch (wsi->ietf_spec_revision) {
|
||||
|
||||
case 13:
|
||||
wsi->u.ws.opcode = c & 0xf;
|
||||
wsi->u.ws.rsv = (c & 0x70);
|
||||
wsi->u.ws.final = !!((c >> 7) & 1);
|
||||
/* revisit if an extension wants them... */
|
||||
switch (wsi->u.ws.opcode) {
|
||||
case LWS_WS_OPCODE_07__TEXT_FRAME:
|
||||
case LWS_WS_OPCODE_07__BINARY_FRAME:
|
||||
case LWSWSOPC_TEXT_FRAME:
|
||||
wsi->u.ws.rsv_first_msg = (c & 0x70);
|
||||
wsi->u.ws.continuation_possible = 1;
|
||||
wsi->u.ws.check_utf8 = lws_check_opt(
|
||||
wsi->context->options,
|
||||
LWS_SERVER_OPTION_VALIDATE_UTF8);
|
||||
wsi->u.ws.utf8 = 0;
|
||||
break;
|
||||
case LWSWSOPC_BINARY_FRAME:
|
||||
wsi->u.ws.rsv_first_msg = (c & 0x70);
|
||||
wsi->u.ws.check_utf8 = 0;
|
||||
wsi->u.ws.continuation_possible = 1;
|
||||
break;
|
||||
case LWSWSOPC_CONTINUATION:
|
||||
if (!wsi->u.ws.continuation_possible) {
|
||||
lwsl_info("disordered continuation\n");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case LWSWSOPC_CLOSE:
|
||||
wsi->u.ws.check_utf8 = 0;
|
||||
wsi->u.ws.utf8 = 0;
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 0xb:
|
||||
case 0xc:
|
||||
case 0xd:
|
||||
case 0xe:
|
||||
case 0xf:
|
||||
lwsl_info("illegal opcode\n");
|
||||
return -1;
|
||||
default:
|
||||
wsi->u.ws.defeat_check_utf8 = 1;
|
||||
break;
|
||||
}
|
||||
wsi->u.ws.rsv = (c & 0x70);
|
||||
/* revisit if an extension wants them... */
|
||||
if (
|
||||
#ifndef LWS_NO_EXTENSIONS
|
||||
!wsi->count_act_ext &&
|
||||
#endif
|
||||
wsi->u.ws.rsv) {
|
||||
lwsl_info("illegal rsv bits set\n");
|
||||
return -1;
|
||||
}
|
||||
wsi->u.ws.final = !!((c >> 7) & 1);
|
||||
lwsl_ext("%s: This RX frame Final %d\n", __func__, wsi->u.ws.final);
|
||||
|
||||
if (wsi->u.ws.owed_a_fin &&
|
||||
(wsi->u.ws.opcode == LWSWSOPC_TEXT_FRAME ||
|
||||
wsi->u.ws.opcode == LWSWSOPC_BINARY_FRAME)) {
|
||||
lwsl_info("hey you owed us a FIN\n");
|
||||
return -1;
|
||||
}
|
||||
if ((!(wsi->u.ws.opcode & 8)) && wsi->u.ws.final) {
|
||||
wsi->u.ws.continuation_possible = 0;
|
||||
wsi->u.ws.owed_a_fin = 0;
|
||||
}
|
||||
|
||||
if ((wsi->u.ws.opcode & 8) && !wsi->u.ws.final) {
|
||||
lwsl_info("control message cannot be fragmented\n");
|
||||
return -1;
|
||||
}
|
||||
if (!wsi->u.ws.final)
|
||||
wsi->u.ws.owed_a_fin = 1;
|
||||
|
||||
switch (wsi->u.ws.opcode) {
|
||||
case LWSWSOPC_TEXT_FRAME:
|
||||
case LWSWSOPC_BINARY_FRAME:
|
||||
wsi->u.ws.frame_is_binary = wsi->u.ws.opcode ==
|
||||
LWS_WS_OPCODE_07__BINARY_FRAME;
|
||||
LWSWSOPC_BINARY_FRAME;
|
||||
break;
|
||||
}
|
||||
wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN;
|
||||
|
@ -54,7 +145,6 @@ int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c)
|
|||
}
|
||||
break;
|
||||
|
||||
|
||||
case LWS_RXPS_04_FRAME_HDR_LEN:
|
||||
|
||||
wsi->u.ws.this_frame_masked = !!(c & 0x80);
|
||||
|
@ -98,8 +188,7 @@ int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c)
|
|||
case LWS_RXPS_04_FRAME_HDR_LEN16_1:
|
||||
wsi->u.ws.rx_packet_length |= c;
|
||||
if (wsi->u.ws.this_frame_masked)
|
||||
wsi->lws_rx_parse_state =
|
||||
LWS_RXPS_07_COLLECT_FRAME_KEY_1;
|
||||
wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_1;
|
||||
else {
|
||||
if (wsi->u.ws.rx_packet_length)
|
||||
wsi->lws_rx_parse_state =
|
||||
|
@ -178,28 +267,28 @@ int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c)
|
|||
break;
|
||||
|
||||
case LWS_RXPS_07_COLLECT_FRAME_KEY_1:
|
||||
wsi->u.ws.frame_masking_nonce_04[0] = c;
|
||||
wsi->u.ws.mask[0] = c;
|
||||
if (c)
|
||||
wsi->u.ws.all_zero_nonce = 0;
|
||||
wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2;
|
||||
break;
|
||||
|
||||
case LWS_RXPS_07_COLLECT_FRAME_KEY_2:
|
||||
wsi->u.ws.frame_masking_nonce_04[1] = c;
|
||||
wsi->u.ws.mask[1] = c;
|
||||
if (c)
|
||||
wsi->u.ws.all_zero_nonce = 0;
|
||||
wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3;
|
||||
break;
|
||||
|
||||
case LWS_RXPS_07_COLLECT_FRAME_KEY_3:
|
||||
wsi->u.ws.frame_masking_nonce_04[2] = c;
|
||||
wsi->u.ws.mask[2] = c;
|
||||
if (c)
|
||||
wsi->u.ws.all_zero_nonce = 0;
|
||||
wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4;
|
||||
break;
|
||||
|
||||
case LWS_RXPS_07_COLLECT_FRAME_KEY_4:
|
||||
wsi->u.ws.frame_masking_nonce_04[3] = c;
|
||||
wsi->u.ws.mask[3] = c;
|
||||
if (c)
|
||||
wsi->u.ws.all_zero_nonce = 0;
|
||||
|
||||
|
@ -214,19 +303,15 @@ int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c)
|
|||
|
||||
case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED:
|
||||
|
||||
if (!wsi->u.ws.rx_user_buffer) {
|
||||
lwsl_err("NULL client rx_user_buffer\n");
|
||||
return 1;
|
||||
}
|
||||
assert(wsi->u.ws.rx_ubuf);
|
||||
|
||||
if ((!wsi->u.ws.this_frame_masked) || wsi->u.ws.all_zero_nonce)
|
||||
wsi->u.ws.rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING +
|
||||
(wsi->u.ws.rx_user_buffer_head++)] = c;
|
||||
else
|
||||
wsi->u.ws.rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING +
|
||||
(wsi->u.ws.rx_user_buffer_head++)] =
|
||||
c ^ wsi->u.ws.frame_masking_nonce_04[
|
||||
(wsi->u.ws.frame_mask_index++) & 3];
|
||||
if (wsi->u.ws.rx_draining_ext)
|
||||
goto drain_extension;
|
||||
|
||||
if (wsi->u.ws.this_frame_masked && !wsi->u.ws.all_zero_nonce)
|
||||
c ^= wsi->u.ws.mask[(wsi->u.ws.mask_idx++) & 3];
|
||||
|
||||
wsi->u.ws.rx_ubuf[LWS_PRE + (wsi->u.ws.rx_ubuf_head++)] = c;
|
||||
|
||||
if (--wsi->u.ws.rx_packet_length == 0) {
|
||||
/* spill because we have the whole frame */
|
||||
|
@ -236,17 +321,14 @@ int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c)
|
|||
|
||||
/*
|
||||
* if there's no protocol max frame size given, we are
|
||||
* supposed to default to LWS_MAX_SOCKET_IO_BUF
|
||||
* supposed to default to context->pt_serv_buf_size
|
||||
*/
|
||||
|
||||
if (!wsi->protocol->rx_buffer_size &&
|
||||
wsi->u.ws.rx_user_buffer_head !=
|
||||
LWS_MAX_SOCKET_IO_BUF)
|
||||
wsi->u.ws.rx_ubuf_head != wsi->context->pt_serv_buf_size)
|
||||
break;
|
||||
else
|
||||
if (wsi->protocol->rx_buffer_size &&
|
||||
wsi->u.ws.rx_user_buffer_head !=
|
||||
wsi->protocol->rx_buffer_size)
|
||||
|
||||
if (wsi->protocol->rx_buffer_size &&
|
||||
wsi->u.ws.rx_ubuf_head != wsi->protocol->rx_buffer_size)
|
||||
break;
|
||||
|
||||
/* spill because we filled our rx buffer */
|
||||
|
@ -260,9 +342,17 @@ spill:
|
|||
*/
|
||||
|
||||
switch (wsi->u.ws.opcode) {
|
||||
case LWS_WS_OPCODE_07__CLOSE:
|
||||
case LWSWSOPC_CLOSE:
|
||||
pp = (unsigned char *)&wsi->u.ws.rx_ubuf[LWS_PRE];
|
||||
if (lws_check_opt(wsi->context->options,
|
||||
LWS_SERVER_OPTION_VALIDATE_UTF8) &&
|
||||
wsi->u.ws.rx_ubuf_head > 2 &&
|
||||
lws_check_utf8(&wsi->u.ws.utf8, pp + 2,
|
||||
wsi->u.ws.rx_ubuf_head - 2))
|
||||
goto utf8_fail;
|
||||
|
||||
/* is this an acknowledgement of our close? */
|
||||
if (wsi->state == WSI_STATE_AWAITING_CLOSE_ACK) {
|
||||
if (wsi->state == LWSS_AWAITING_CLOSE_ACK) {
|
||||
/*
|
||||
* fine he has told us he is closing too, let's
|
||||
* finish our close
|
||||
|
@ -270,24 +360,59 @@ spill:
|
|||
lwsl_parser("seen server's close ack\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
lwsl_parser("client sees server close len = %d\n",
|
||||
wsi->u.ws.rx_user_buffer_head);
|
||||
/*
|
||||
* parrot the close packet payload back
|
||||
* we do not care about how it went, we are closing
|
||||
* immediately afterwards
|
||||
*/
|
||||
libwebsocket_write(wsi, (unsigned char *)
|
||||
&wsi->u.ws.rx_user_buffer[
|
||||
LWS_SEND_BUFFER_PRE_PADDING],
|
||||
wsi->u.ws.rx_user_buffer_head, LWS_WRITE_CLOSE);
|
||||
wsi->state = WSI_STATE_RETURNED_CLOSE_ALREADY;
|
||||
wsi->u.ws.rx_ubuf_head);
|
||||
if (wsi->u.ws.rx_ubuf_head >= 2) {
|
||||
close_code = (pp[0] << 8) | pp[1];
|
||||
if (close_code < 1000 ||
|
||||
close_code == 1004 ||
|
||||
close_code == 1005 ||
|
||||
close_code == 1006 ||
|
||||
close_code == 1012 ||
|
||||
close_code == 1013 ||
|
||||
close_code == 1014 ||
|
||||
close_code == 1015 ||
|
||||
(close_code >= 1016 && close_code < 3000)
|
||||
) {
|
||||
pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff;
|
||||
pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff;
|
||||
}
|
||||
}
|
||||
if (user_callback_handle_rxflow(
|
||||
wsi->protocol->callback, wsi,
|
||||
LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
|
||||
wsi->user_space, pp,
|
||||
wsi->u.ws.rx_ubuf_head))
|
||||
return -1;
|
||||
|
||||
if (lws_partial_buffered(wsi))
|
||||
/*
|
||||
* if we're in the middle of something,
|
||||
* we can't do a normal close response and
|
||||
* have to just close our end.
|
||||
*/
|
||||
wsi->socket_is_permanently_unusable = 1;
|
||||
else
|
||||
/*
|
||||
* parrot the close packet payload back
|
||||
* we do not care about how it went, we are closing
|
||||
* immediately afterwards
|
||||
*/
|
||||
lws_write(wsi, (unsigned char *)&wsi->u.ws.rx_ubuf[LWS_PRE],
|
||||
wsi->u.ws.rx_ubuf_head,
|
||||
LWS_WRITE_CLOSE);
|
||||
wsi->state = LWSS_RETURNED_CLOSE_ALREADY;
|
||||
/* close the connection */
|
||||
return -1;
|
||||
|
||||
case LWS_WS_OPCODE_07__PING:
|
||||
case LWSWSOPC_PING:
|
||||
lwsl_info("received %d byte ping, sending pong\n",
|
||||
wsi->u.ws.rx_user_buffer_head);
|
||||
wsi->u.ws.rx_ubuf_head);
|
||||
|
||||
/* he set a close reason on this guy, ignore PING */
|
||||
if (wsi->u.ws.close_in_ping_buffer_len)
|
||||
goto ping_drop;
|
||||
|
||||
if (wsi->u.ws.ping_pending_flag) {
|
||||
/*
|
||||
|
@ -299,52 +424,43 @@ spill:
|
|||
}
|
||||
|
||||
/* control packets can only be < 128 bytes long */
|
||||
if (wsi->u.ws.rx_user_buffer_head > 128 - 4) {
|
||||
if (wsi->u.ws.rx_ubuf_head > 128 - 3) {
|
||||
lwsl_parser("DROP PING payload too large\n");
|
||||
goto ping_drop;
|
||||
}
|
||||
|
||||
/* if existing buffer is too small, drop it */
|
||||
if (wsi->u.ws.ping_payload_buf &&
|
||||
wsi->u.ws.ping_payload_alloc < wsi->u.ws.rx_user_buffer_head)
|
||||
lws_free2(wsi->u.ws.ping_payload_buf);
|
||||
|
||||
/* if no buffer, allocate it */
|
||||
if (!wsi->u.ws.ping_payload_buf) {
|
||||
wsi->u.ws.ping_payload_buf = lws_malloc(wsi->u.ws.rx_user_buffer_head
|
||||
+ LWS_SEND_BUFFER_PRE_PADDING);
|
||||
wsi->u.ws.ping_payload_alloc =
|
||||
wsi->u.ws.rx_user_buffer_head;
|
||||
}
|
||||
|
||||
/* stash the pong payload */
|
||||
memcpy(wsi->u.ws.ping_payload_buf + LWS_SEND_BUFFER_PRE_PADDING,
|
||||
&wsi->u.ws.rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING],
|
||||
wsi->u.ws.rx_user_buffer_head);
|
||||
memcpy(wsi->u.ws.ping_payload_buf + LWS_PRE,
|
||||
&wsi->u.ws.rx_ubuf[LWS_PRE],
|
||||
wsi->u.ws.rx_ubuf_head);
|
||||
|
||||
wsi->u.ws.ping_payload_len = wsi->u.ws.rx_user_buffer_head;
|
||||
wsi->u.ws.ping_payload_len = wsi->u.ws.rx_ubuf_head;
|
||||
wsi->u.ws.ping_pending_flag = 1;
|
||||
|
||||
/* get it sent as soon as possible */
|
||||
libwebsocket_callback_on_writable(wsi->protocol->owning_server, wsi);
|
||||
lws_callback_on_writable(wsi);
|
||||
ping_drop:
|
||||
wsi->u.ws.rx_user_buffer_head = 0;
|
||||
wsi->u.ws.rx_ubuf_head = 0;
|
||||
handled = 1;
|
||||
break;
|
||||
|
||||
case LWS_WS_OPCODE_07__PONG:
|
||||
case LWSWSOPC_PONG:
|
||||
lwsl_info("client receied pong\n");
|
||||
lwsl_hexdump(&wsi->u.ws.rx_user_buffer[
|
||||
LWS_SEND_BUFFER_PRE_PADDING],
|
||||
wsi->u.ws.rx_user_buffer_head);
|
||||
lwsl_hexdump(&wsi->u.ws.rx_ubuf[LWS_PRE],
|
||||
wsi->u.ws.rx_ubuf_head);
|
||||
|
||||
if (wsi->pending_timeout == PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) {
|
||||
lwsl_info("received expected PONG on wsi %p\n", wsi);
|
||||
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
||||
}
|
||||
|
||||
/* issue it */
|
||||
callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG;
|
||||
break;
|
||||
|
||||
case LWS_WS_OPCODE_07__CONTINUATION:
|
||||
case LWS_WS_OPCODE_07__TEXT_FRAME:
|
||||
case LWS_WS_OPCODE_07__BINARY_FRAME:
|
||||
case LWSWSOPC_CONTINUATION:
|
||||
case LWSWSOPC_TEXT_FRAME:
|
||||
case LWSWSOPC_BINARY_FRAME:
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -357,17 +473,15 @@ ping_drop:
|
|||
* state machine.
|
||||
*/
|
||||
|
||||
eff_buf.token = &wsi->u.ws.rx_user_buffer[
|
||||
LWS_SEND_BUFFER_PRE_PADDING];
|
||||
eff_buf.token_len = wsi->u.ws.rx_user_buffer_head;
|
||||
|
||||
if (lws_ext_callback_for_each_active(wsi,
|
||||
LWS_EXT_CALLBACK_EXTENDED_PAYLOAD_RX,
|
||||
eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE];
|
||||
eff_buf.token_len = wsi->u.ws.rx_ubuf_head;
|
||||
|
||||
if (lws_ext_cb_active(wsi,
|
||||
LWS_EXT_CB_EXTENDED_PAYLOAD_RX,
|
||||
&eff_buf, 0) <= 0) { /* not handle or fail */
|
||||
|
||||
lwsl_ext("Unhandled ext opc 0x%x\n",
|
||||
wsi->u.ws.opcode);
|
||||
wsi->u.ws.rx_user_buffer_head = 0;
|
||||
lwsl_ext("Unhandled ext opc 0x%x\n", wsi->u.ws.opcode);
|
||||
wsi->u.ws.rx_ubuf_head = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -378,22 +492,51 @@ ping_drop:
|
|||
/*
|
||||
* No it's real payload, pass it up to the user callback.
|
||||
* It's nicely buffered with the pre-padding taken care of
|
||||
* so it can be sent straight out again using libwebsocket_write
|
||||
* so it can be sent straight out again using lws_write
|
||||
*/
|
||||
if (handled)
|
||||
goto already_done;
|
||||
|
||||
eff_buf.token = &wsi->u.ws.rx_user_buffer[
|
||||
LWS_SEND_BUFFER_PRE_PADDING];
|
||||
eff_buf.token_len = wsi->u.ws.rx_user_buffer_head;
|
||||
|
||||
if (lws_ext_callback_for_each_active(wsi,
|
||||
LWS_EXT_CALLBACK_PAYLOAD_RX,
|
||||
&eff_buf, 0) < 0) /* fail */
|
||||
return -1;
|
||||
eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE];
|
||||
eff_buf.token_len = wsi->u.ws.rx_ubuf_head;
|
||||
|
||||
if (eff_buf.token_len <= 0 &&
|
||||
callback_action != LWS_CALLBACK_CLIENT_RECEIVE_PONG)
|
||||
drain_extension:
|
||||
lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len);
|
||||
|
||||
n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0);
|
||||
lwsl_ext("Ext RX returned %d\n", n);
|
||||
if (n < 0) {
|
||||
wsi->socket_is_permanently_unusable = 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
lwsl_ext("post inflate eff_buf len %d\n", eff_buf.token_len);
|
||||
|
||||
if (rx_draining_ext && !eff_buf.token_len) {
|
||||
lwsl_err(" --- ignoring zero drain result, ending drain\n");
|
||||
goto already_done;
|
||||
}
|
||||
|
||||
if (wsi->u.ws.check_utf8 && !wsi->u.ws.defeat_check_utf8) {
|
||||
if (lws_check_utf8(&wsi->u.ws.utf8,
|
||||
(unsigned char *)eff_buf.token,
|
||||
eff_buf.token_len))
|
||||
goto utf8_fail;
|
||||
|
||||
/* we are ending partway through utf-8 character? */
|
||||
if (!wsi->u.ws.rx_packet_length && wsi->u.ws.final &&
|
||||
wsi->u.ws.utf8 && !n) {
|
||||
lwsl_info("FINAL utf8 error\n");
|
||||
utf8_fail: lwsl_info("utf8 error\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (eff_buf.token_len < 0 &&
|
||||
callback_action != LWS_CALLBACK_CLIENT_RECEIVE_PONG)
|
||||
goto already_done;
|
||||
|
||||
if (!eff_buf.token)
|
||||
goto already_done;
|
||||
|
||||
eff_buf.token[eff_buf.token_len] = '\0';
|
||||
|
@ -404,20 +547,31 @@ ping_drop:
|
|||
if (callback_action == LWS_CALLBACK_CLIENT_RECEIVE_PONG)
|
||||
lwsl_info("Client doing pong callback\n");
|
||||
|
||||
m = wsi->protocol->callback(
|
||||
wsi->protocol->owning_server,
|
||||
wsi,
|
||||
(enum libwebsocket_callback_reasons)callback_action,
|
||||
wsi->user_space,
|
||||
eff_buf.token,
|
||||
eff_buf.token_len);
|
||||
if (n && eff_buf.token_len)
|
||||
/* extension had more... main loop will come back
|
||||
* we want callback to be done with this set, if so,
|
||||
* because lws_is_final() hides it was final until the
|
||||
* last chunk
|
||||
*/
|
||||
lws_add_wsi_to_draining_ext_list(wsi);
|
||||
else
|
||||
lws_remove_wsi_from_draining_ext_list(wsi);
|
||||
|
||||
if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY ||
|
||||
wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION ||
|
||||
wsi->state == LWSS_AWAITING_CLOSE_ACK)
|
||||
goto already_done;
|
||||
|
||||
m = wsi->protocol->callback(wsi,
|
||||
(enum lws_callback_reasons)callback_action,
|
||||
wsi->user_space, eff_buf.token, eff_buf.token_len);
|
||||
|
||||
/* if user code wants to close, let caller know */
|
||||
if (m)
|
||||
return 1;
|
||||
|
||||
already_done:
|
||||
wsi->u.ws.rx_user_buffer_head = 0;
|
||||
wsi->u.ws.rx_ubuf_head = 0;
|
||||
break;
|
||||
default:
|
||||
lwsl_err("client rx illegal state\n");
|
||||
|
@ -427,7 +581,6 @@ already_done:
|
|||
return 0;
|
||||
|
||||
illegal_ctl_length:
|
||||
|
||||
lwsl_warn("Control frame asking for extended length is illegal\n");
|
||||
/* kill the connection */
|
||||
return -1;
|
||||
|
|
1382
lib/client.c
Normal file → Executable file
1382
lib/client.c
Normal file → Executable file
File diff suppressed because it is too large
Load diff
1526
lib/context.c
1526
lib/context.c
File diff suppressed because it is too large
Load diff
134
lib/daemonize.c
134
lib/daemonize.c
|
@ -7,7 +7,8 @@
|
|||
* he replied it is Public Domain. Use the URL above to get the original
|
||||
* Public Domain version if you want it.
|
||||
*
|
||||
* This version is LGPL2 and is (c)2006 - 2013 Andy Green <andy@warmcat.com>
|
||||
* This version is LGPL2.1+SLE like the rest of libwebsockets and is
|
||||
* Copyright (c)2006 - 2013 Andy Green <andy@warmcat.com>
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
@ -34,39 +35,41 @@ int get_daemonize_pid()
|
|||
static void
|
||||
child_handler(int signum)
|
||||
{
|
||||
int fd;
|
||||
int len;
|
||||
int sent;
|
||||
int fd, len, sent;
|
||||
char sz[20];
|
||||
|
||||
switch (signum) {
|
||||
|
||||
case SIGALRM: /* timed out daemonizing */
|
||||
exit(1);
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
case SIGUSR1: /* positive confirmation we daemonized well */
|
||||
/* Create the lock file as the current user */
|
||||
|
||||
fd = open(lock_path, O_TRUNC | O_RDWR | O_CREAT, 0640);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr,
|
||||
"unable to create lock file %s, code=%d (%s)\n",
|
||||
lock_path, errno, strerror(errno));
|
||||
exit(1);
|
||||
if (lock_path) {
|
||||
/* Create the lock file as the current user */
|
||||
|
||||
fd = open(lock_path, O_TRUNC | O_RDWR | O_CREAT, 0640);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr,
|
||||
"unable to create lock file %s, code=%d (%s)\n",
|
||||
lock_path, errno, strerror(errno));
|
||||
exit(0);
|
||||
}
|
||||
len = sprintf(sz, "%u", pid_daemon);
|
||||
sent = write(fd, sz, len);
|
||||
if (sent != len)
|
||||
fprintf(stderr,
|
||||
"unable to write pid to lock file %s, code=%d (%s)\n",
|
||||
lock_path, errno, strerror(errno));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
len = sprintf(sz, "%u", pid_daemon);
|
||||
sent = write(fd, sz, len);
|
||||
if (sent != len)
|
||||
fprintf(stderr,
|
||||
"unable to write pid to lock file %s, code=%d (%s)\n",
|
||||
lock_path, errno, strerror(errno));
|
||||
|
||||
close(fd);
|
||||
exit(!!(sent == len));
|
||||
exit(0);
|
||||
//!!(sent == len));
|
||||
|
||||
case SIGCHLD: /* daemonization failed */
|
||||
exit(1);
|
||||
exit(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +79,7 @@ static void lws_daemon_closing(int sigact)
|
|||
if (getpid() == pid_daemon)
|
||||
if (lock_path) {
|
||||
unlink(lock_path);
|
||||
lws_free2(lock_path);
|
||||
lws_free_set_NULL(lock_path);
|
||||
}
|
||||
|
||||
kill(getpid(), SIGKILL);
|
||||
|
@ -93,42 +96,43 @@ static void lws_daemon_closing(int sigact)
|
|||
LWS_VISIBLE int
|
||||
lws_daemonize(const char *_lock_path)
|
||||
{
|
||||
pid_t sid, parent;
|
||||
int fd;
|
||||
char buf[10];
|
||||
int n, ret;
|
||||
struct sigaction act;
|
||||
pid_t sid, parent;
|
||||
int n, fd, ret;
|
||||
char buf[10];
|
||||
|
||||
/* already a daemon */
|
||||
if (getppid() == 1)
|
||||
return 1;
|
||||
// if (getppid() == 1)
|
||||
// return 1;
|
||||
|
||||
fd = open(_lock_path, O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
n = read(fd, buf, sizeof(buf));
|
||||
close(fd);
|
||||
if (n) {
|
||||
n = atoi(buf);
|
||||
ret = kill(n, 0);
|
||||
if (ret >= 0) {
|
||||
if (_lock_path) {
|
||||
fd = open(_lock_path, O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
n = read(fd, buf, sizeof(buf));
|
||||
close(fd);
|
||||
if (n) {
|
||||
n = atoi(buf);
|
||||
ret = kill(n, 0);
|
||||
if (ret >= 0) {
|
||||
fprintf(stderr,
|
||||
"Daemon already running from pid %d\n", n);
|
||||
exit(1);
|
||||
}
|
||||
fprintf(stderr,
|
||||
"Daemon already running from pid %d\n", n);
|
||||
exit(1);
|
||||
"Removing stale lock file %s from dead pid %d\n",
|
||||
_lock_path, n);
|
||||
unlink(lock_path);
|
||||
}
|
||||
fprintf(stderr,
|
||||
"Removing stale lock file %s from dead pid %d\n",
|
||||
_lock_path, n);
|
||||
unlink(lock_path);
|
||||
}
|
||||
}
|
||||
|
||||
n = strlen(_lock_path) + 1;
|
||||
lock_path = lws_malloc(n);
|
||||
if (!lock_path) {
|
||||
fprintf(stderr, "Out of mem in lws_daemonize\n");
|
||||
return 1;
|
||||
n = strlen(_lock_path) + 1;
|
||||
lock_path = lws_malloc(n);
|
||||
if (!lock_path) {
|
||||
fprintf(stderr, "Out of mem in lws_daemonize\n");
|
||||
return 1;
|
||||
}
|
||||
strcpy(lock_path, _lock_path);
|
||||
}
|
||||
strcpy(lock_path, _lock_path);
|
||||
|
||||
/* Trap signals that we expect to receive */
|
||||
signal(SIGCHLD, child_handler); /* died */
|
||||
|
@ -140,23 +144,23 @@ lws_daemonize(const char *_lock_path)
|
|||
if (pid_daemon < 0) {
|
||||
fprintf(stderr, "unable to fork daemon, code=%d (%s)",
|
||||
errno, strerror(errno));
|
||||
exit(1);
|
||||
exit(9);
|
||||
}
|
||||
|
||||
/* If we got a good PID, then we can exit the parent process. */
|
||||
/* If we got a good PID, then we can exit the parent process. */
|
||||
if (pid_daemon > 0) {
|
||||
|
||||
/*
|
||||
* Wait for confirmation signal from the child via
|
||||
* SIGCHILD / USR1, or for two seconds to elapse
|
||||
* (SIGALRM). pause() should not return.
|
||||
*/
|
||||
alarm(2);
|
||||
/*
|
||||
* Wait for confirmation signal from the child via
|
||||
* SIGCHILD / USR1, or for two seconds to elapse
|
||||
* (SIGALRM). pause() should not return.
|
||||
*/
|
||||
alarm(2);
|
||||
|
||||
pause();
|
||||
/* should not be reachable */
|
||||
exit(1);
|
||||
}
|
||||
pause();
|
||||
/* should not be reachable */
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* At this point we are executing as the child process */
|
||||
parent = getppid();
|
||||
|
@ -178,18 +182,18 @@ lws_daemonize(const char *_lock_path)
|
|||
fprintf(stderr,
|
||||
"unable to create a new session, code %d (%s)",
|
||||
errno, strerror(errno));
|
||||
exit(1);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Change the current working directory. This prevents the current
|
||||
* directory from being locked; hence not being able to remove it.
|
||||
*/
|
||||
if (chdir("/") < 0) {
|
||||
if (chdir("/tmp") < 0) {
|
||||
fprintf(stderr,
|
||||
"unable to change directory to %s, code %d (%s)",
|
||||
"/", errno, strerror(errno));
|
||||
exit(1);
|
||||
exit(3);
|
||||
}
|
||||
|
||||
/* Redirect standard files to /dev/null */
|
||||
|
|
|
@ -1,288 +0,0 @@
|
|||
#include "private-libwebsockets.h"
|
||||
#include "extension-deflate-frame.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define LWS_ZLIB_WINDOW_BITS 15
|
||||
#define LWS_ZLIB_MEMLEVEL 8
|
||||
|
||||
int lws_extension_callback_deflate_frame(
|
||||
struct libwebsocket_context *context,
|
||||
struct libwebsocket_extension *ext,
|
||||
struct libwebsocket *wsi,
|
||||
enum libwebsocket_extension_callback_reasons reason,
|
||||
void *user, void *in, size_t len)
|
||||
{
|
||||
struct lws_ext_deflate_frame_conn *conn =
|
||||
(struct lws_ext_deflate_frame_conn *)user;
|
||||
struct lws_tokens *eff_buf = (struct lws_tokens *)in;
|
||||
size_t current_payload, remaining_payload, total_payload;
|
||||
int n;
|
||||
size_t len_so_far;
|
||||
|
||||
switch (reason) {
|
||||
|
||||
/*
|
||||
* for deflate-frame, both client and server sides act the same
|
||||
*/
|
||||
|
||||
case LWS_EXT_CALLBACK_CLIENT_CONSTRUCT:
|
||||
case LWS_EXT_CALLBACK_CONSTRUCT:
|
||||
conn->zs_in.zalloc = conn->zs_out.zalloc = Z_NULL;
|
||||
conn->zs_in.zfree = conn->zs_out.zfree = Z_NULL;
|
||||
conn->zs_in.opaque = conn->zs_out.opaque = Z_NULL;
|
||||
n = inflateInit2(&conn->zs_in, -LWS_ZLIB_WINDOW_BITS);
|
||||
if (n != Z_OK) {
|
||||
lwsl_ext("deflateInit returned %d\n", n);
|
||||
return 1;
|
||||
}
|
||||
n = deflateInit2(&conn->zs_out,
|
||||
(context->listen_port ?
|
||||
DEFLATE_FRAME_COMPRESSION_LEVEL_SERVER :
|
||||
DEFLATE_FRAME_COMPRESSION_LEVEL_CLIENT),
|
||||
Z_DEFLATED,
|
||||
-LWS_ZLIB_WINDOW_BITS, LWS_ZLIB_MEMLEVEL,
|
||||
Z_DEFAULT_STRATEGY);
|
||||
if (n != Z_OK) {
|
||||
lwsl_ext("deflateInit2 returned %d\n", n);
|
||||
return 1;
|
||||
}
|
||||
conn->buf_pre_used = 0;
|
||||
conn->buf_pre_length = 0;
|
||||
conn->buf_in_length = sizeof(conn->buf_in);
|
||||
conn->buf_out_length = sizeof(conn->buf_out);
|
||||
conn->compressed_out = 0;
|
||||
conn->buf_pre = NULL;
|
||||
conn->buf_in = lws_malloc(LWS_SEND_BUFFER_PRE_PADDING +
|
||||
conn->buf_in_length +
|
||||
LWS_SEND_BUFFER_POST_PADDING);
|
||||
if (!conn->buf_in)
|
||||
goto bail;
|
||||
conn->buf_out = lws_malloc(LWS_SEND_BUFFER_PRE_PADDING +
|
||||
conn->buf_out_length +
|
||||
LWS_SEND_BUFFER_POST_PADDING);
|
||||
if (!conn->buf_out)
|
||||
goto bail;
|
||||
lwsl_ext("zlibs constructed\n");
|
||||
break;
|
||||
bail:
|
||||
lwsl_err("Out of mem\n");
|
||||
(void)inflateEnd(&conn->zs_in);
|
||||
(void)deflateEnd(&conn->zs_out);
|
||||
return -1;
|
||||
|
||||
case LWS_EXT_CALLBACK_DESTROY:
|
||||
lws_free(conn->buf_pre);
|
||||
lws_free(conn->buf_in);
|
||||
lws_free(conn->buf_out);
|
||||
conn->buf_pre_used = 0;
|
||||
conn->buf_pre_length = 0;
|
||||
conn->buf_in_length = 0;
|
||||
conn->buf_out_length = 0;
|
||||
conn->compressed_out = 0;
|
||||
(void)inflateEnd(&conn->zs_in);
|
||||
(void)deflateEnd(&conn->zs_out);
|
||||
lwsl_ext("zlibs destructed\n");
|
||||
break;
|
||||
|
||||
case LWS_EXT_CALLBACK_PAYLOAD_RX:
|
||||
if (!(wsi->u.ws.rsv & 0x40))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* inflate the incoming payload
|
||||
*/
|
||||
current_payload = eff_buf->token_len;
|
||||
|
||||
remaining_payload = wsi->u.ws.rx_packet_length;
|
||||
if (remaining_payload) {
|
||||
total_payload = conn->buf_pre_used +
|
||||
current_payload +
|
||||
remaining_payload;
|
||||
|
||||
if (conn->buf_pre_length < total_payload) {
|
||||
conn->buf_pre_length = total_payload;
|
||||
lws_free(conn->buf_pre);
|
||||
conn->buf_pre = lws_malloc(total_payload + 4);
|
||||
if (!conn->buf_pre) {
|
||||
lwsl_err("Out of memory\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(conn->buf_pre + conn->buf_pre_used,
|
||||
eff_buf->token, current_payload);
|
||||
conn->buf_pre_used += current_payload;
|
||||
|
||||
eff_buf->token = NULL;
|
||||
eff_buf->token_len = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (conn->buf_pre_used) {
|
||||
total_payload = conn->buf_pre_used +
|
||||
current_payload;
|
||||
|
||||
memcpy(conn->buf_pre + conn->buf_pre_used,
|
||||
eff_buf->token, current_payload);
|
||||
conn->buf_pre_used = 0;
|
||||
|
||||
conn->zs_in.next_in = conn->buf_pre;
|
||||
} else {
|
||||
total_payload = current_payload;
|
||||
|
||||
conn->zs_in.next_in = (unsigned char *)eff_buf->token;
|
||||
}
|
||||
|
||||
conn->zs_in.next_in[total_payload + 0] = 0;
|
||||
conn->zs_in.next_in[total_payload + 1] = 0;
|
||||
conn->zs_in.next_in[total_payload + 2] = 0xff;
|
||||
conn->zs_in.next_in[total_payload + 3] = 0xff;
|
||||
|
||||
conn->zs_in.avail_in = total_payload + 4;
|
||||
|
||||
conn->zs_in.next_out =
|
||||
conn->buf_in + LWS_SEND_BUFFER_PRE_PADDING;
|
||||
conn->zs_in.avail_out = conn->buf_in_length;
|
||||
|
||||
while (1) {
|
||||
n = inflate(&conn->zs_in, Z_SYNC_FLUSH);
|
||||
switch (n) {
|
||||
case Z_NEED_DICT:
|
||||
case Z_STREAM_ERROR:
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
/*
|
||||
* screwed.. close the connection...
|
||||
* we will get a destroy callback to take care
|
||||
* of closing nicely
|
||||
*/
|
||||
lwsl_info("zlib error inflate %d: %s\n",
|
||||
n, conn->zs_in.msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (conn->zs_in.avail_out)
|
||||
break;
|
||||
|
||||
len_so_far = conn->zs_in.next_out -
|
||||
(conn->buf_in + LWS_SEND_BUFFER_PRE_PADDING);
|
||||
|
||||
conn->buf_in_length *= 2;
|
||||
if (conn->buf_in_length > LWS_MAX_ZLIB_CONN_BUFFER) {
|
||||
lwsl_ext("zlib in buffer hit limit %u\n",
|
||||
LWS_MAX_ZLIB_CONN_BUFFER);
|
||||
return -1;
|
||||
}
|
||||
conn->buf_in = lws_realloc(conn->buf_in,
|
||||
LWS_SEND_BUFFER_PRE_PADDING +
|
||||
conn->buf_in_length +
|
||||
LWS_SEND_BUFFER_POST_PADDING);
|
||||
if (!conn->buf_in) {
|
||||
lwsl_err("Out of memory\n");
|
||||
return -1;
|
||||
}
|
||||
lwsl_debug(
|
||||
"deflate-frame ext RX did realloc to %ld\n",
|
||||
conn->buf_in_length);
|
||||
conn->zs_in.next_out = conn->buf_in +
|
||||
LWS_SEND_BUFFER_PRE_PADDING + len_so_far;
|
||||
conn->zs_in.avail_out =
|
||||
conn->buf_in_length - len_so_far;
|
||||
}
|
||||
|
||||
/* rewrite the buffer pointers and length */
|
||||
eff_buf->token =
|
||||
(char *)(conn->buf_in + LWS_SEND_BUFFER_PRE_PADDING);
|
||||
eff_buf->token_len = (int)(conn->zs_in.next_out -
|
||||
(conn->buf_in + LWS_SEND_BUFFER_PRE_PADDING));
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_EXT_CALLBACK_PAYLOAD_TX:
|
||||
/*
|
||||
* deflate the outgoing payload
|
||||
*/
|
||||
current_payload = eff_buf->token_len;
|
||||
|
||||
conn->zs_out.next_in = (unsigned char *)eff_buf->token;
|
||||
conn->zs_out.avail_in = current_payload;
|
||||
|
||||
conn->zs_out.next_out =
|
||||
conn->buf_out + LWS_SEND_BUFFER_PRE_PADDING;
|
||||
conn->zs_out.avail_out = conn->buf_out_length;
|
||||
|
||||
while (1) {
|
||||
n = deflate(&conn->zs_out, Z_SYNC_FLUSH);
|
||||
if (n == Z_STREAM_ERROR) {
|
||||
/*
|
||||
* screwed.. close the connection... we will
|
||||
* get a destroy callback to take care of
|
||||
* closing nicely
|
||||
*/
|
||||
lwsl_ext("zlib error deflate\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (conn->zs_out.avail_out)
|
||||
break;
|
||||
|
||||
len_so_far = (conn->zs_out.next_out -
|
||||
(conn->buf_out +
|
||||
LWS_SEND_BUFFER_PRE_PADDING));
|
||||
conn->buf_out_length *= 2;
|
||||
if (conn->buf_out_length > LWS_MAX_ZLIB_CONN_BUFFER) {
|
||||
lwsl_ext("zlib out hit limit %u\n",
|
||||
LWS_MAX_ZLIB_CONN_BUFFER);
|
||||
return -1;
|
||||
}
|
||||
conn->buf_out = lws_realloc(conn->buf_out,
|
||||
LWS_SEND_BUFFER_PRE_PADDING +
|
||||
conn->buf_out_length +
|
||||
LWS_SEND_BUFFER_POST_PADDING);
|
||||
if (!conn->buf_out) {
|
||||
lwsl_err("Out of memory\n");
|
||||
return -1;
|
||||
}
|
||||
lwsl_debug(
|
||||
"deflate-frame ext TX did realloc to %ld\n",
|
||||
conn->buf_out_length);
|
||||
|
||||
conn->zs_out.next_out = (conn->buf_out +
|
||||
LWS_SEND_BUFFER_PRE_PADDING + len_so_far);
|
||||
conn->zs_out.avail_out =
|
||||
(conn->buf_out_length - len_so_far);
|
||||
}
|
||||
|
||||
conn->compressed_out = 1;
|
||||
|
||||
/* rewrite the buffer pointers and length */
|
||||
eff_buf->token = (char *)(conn->buf_out +
|
||||
LWS_SEND_BUFFER_PRE_PADDING);
|
||||
eff_buf->token_len = (int)(conn->zs_out.next_out -
|
||||
(conn->buf_out + LWS_SEND_BUFFER_PRE_PADDING)) - 4;
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_EXT_CALLBACK_PACKET_TX_PRESEND:
|
||||
if (conn->compressed_out) {
|
||||
conn->compressed_out = 0;
|
||||
*((unsigned char *)eff_buf->token) |= 0x40;
|
||||
}
|
||||
break;
|
||||
|
||||
case LWS_EXT_CALLBACK_CHECK_OK_TO_PROPOSE_EXTENSION:
|
||||
/* Avoid x-webkit-deflate-frame extension on client */
|
||||
if (!strcmp((char *)in, "x-webkit-deflate-frame"))
|
||||
return 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
|
||||
#include <zlib.h>
|
||||
|
||||
#define DEFLATE_FRAME_COMPRESSION_LEVEL_SERVER 1
|
||||
#define DEFLATE_FRAME_COMPRESSION_LEVEL_CLIENT Z_DEFAULT_COMPRESSION
|
||||
|
||||
struct lws_ext_deflate_frame_conn {
|
||||
z_stream zs_in;
|
||||
z_stream zs_out;
|
||||
size_t buf_pre_used;
|
||||
size_t buf_pre_length;
|
||||
size_t buf_in_length;
|
||||
size_t buf_out_length;
|
||||
int compressed_out;
|
||||
unsigned char *buf_pre;
|
||||
unsigned char *buf_in;
|
||||
unsigned char *buf_out;
|
||||
};
|
||||
|
||||
extern int lws_extension_callback_deflate_frame(
|
||||
struct libwebsocket_context *context,
|
||||
struct libwebsocket_extension *ext,
|
||||
struct libwebsocket *wsi,
|
||||
enum libwebsocket_extension_callback_reasons reason,
|
||||
void *user, void *in, size_t len);
|
|
@ -1,166 +0,0 @@
|
|||
#include "private-libwebsockets.h"
|
||||
#include "extension-deflate-stream.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define LWS_ZLIB_WINDOW_BITS 15
|
||||
#define LWS_ZLIB_MEMLEVEL 8
|
||||
|
||||
int lws_extension_callback_deflate_stream(
|
||||
struct libwebsocket_context *context,
|
||||
struct libwebsocket_extension *ext,
|
||||
struct libwebsocket *wsi,
|
||||
enum libwebsocket_extension_callback_reasons reason,
|
||||
void *user, void *in, size_t len)
|
||||
{
|
||||
struct lws_ext_deflate_stream_conn *conn =
|
||||
(struct lws_ext_deflate_stream_conn *)user;
|
||||
int n;
|
||||
struct lws_tokens *eff_buf = (struct lws_tokens *)in;
|
||||
|
||||
switch (reason) {
|
||||
|
||||
/*
|
||||
* for deflate-stream, both client and server sides act the same
|
||||
*/
|
||||
|
||||
case LWS_EXT_CALLBACK_CLIENT_CONSTRUCT:
|
||||
case LWS_EXT_CALLBACK_CONSTRUCT:
|
||||
conn->zs_in.zalloc = conn->zs_out.zalloc = Z_NULL;
|
||||
conn->zs_in.zfree = conn->zs_out.zfree = Z_NULL;
|
||||
conn->zs_in.opaque = conn->zs_out.opaque = Z_NULL;
|
||||
n = inflateInit2(&conn->zs_in, -LWS_ZLIB_WINDOW_BITS);
|
||||
if (n != Z_OK) {
|
||||
lwsl_err("deflateInit returned %d\n", n);
|
||||
return 1;
|
||||
}
|
||||
n = deflateInit2(&conn->zs_out,
|
||||
DEFLATE_STREAM_COMPRESSION_LEVEL, Z_DEFLATED,
|
||||
-LWS_ZLIB_WINDOW_BITS, LWS_ZLIB_MEMLEVEL,
|
||||
Z_DEFAULT_STRATEGY);
|
||||
if (n != Z_OK) {
|
||||
lwsl_err("deflateInit returned %d\n", n);
|
||||
return 1;
|
||||
}
|
||||
lwsl_ext("zlibs constructed\n");
|
||||
conn->remaining_in = 0;
|
||||
break;
|
||||
|
||||
case LWS_EXT_CALLBACK_DESTROY:
|
||||
(void)inflateEnd(&conn->zs_in);
|
||||
(void)deflateEnd(&conn->zs_out);
|
||||
lwsl_ext("zlibs destructed\n");
|
||||
break;
|
||||
|
||||
case LWS_EXT_CALLBACK_PACKET_RX_PREPARSE:
|
||||
|
||||
/*
|
||||
* inflate the incoming compressed data
|
||||
* Notice, length may be 0 and pointer NULL
|
||||
* in the case we are flushing with nothing new coming in
|
||||
*/
|
||||
if (conn->remaining_in) {
|
||||
conn->zs_in.next_in = conn->buf_in;
|
||||
conn->zs_in.avail_in = conn->remaining_in;
|
||||
conn->remaining_in = 0;
|
||||
} else {
|
||||
conn->zs_in.next_in = (unsigned char *)eff_buf->token;
|
||||
conn->zs_in.avail_in = eff_buf->token_len;
|
||||
}
|
||||
|
||||
conn->zs_in.next_out = conn->buf_out;
|
||||
conn->zs_in.avail_out = sizeof(conn->buf_out);
|
||||
|
||||
n = inflate(&conn->zs_in, Z_SYNC_FLUSH);
|
||||
switch (n) {
|
||||
case Z_NEED_DICT:
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
/*
|
||||
* screwed.. close the connection... we will get a
|
||||
* destroy callback to take care of closing nicely
|
||||
*/
|
||||
lwsl_err("zlib error inflate %d\n", n);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* rewrite the buffer pointers and length */
|
||||
|
||||
eff_buf->token = (char *)conn->buf_out;
|
||||
eff_buf->token_len =
|
||||
sizeof(conn->buf_out) - conn->zs_in.avail_out;
|
||||
|
||||
/* copy avail data if not consumed */
|
||||
if (conn->zs_in.avail_in > 0) {
|
||||
conn->remaining_in = conn->zs_in.avail_in;
|
||||
memcpy(conn->buf_in, conn->zs_in.next_in,
|
||||
conn->zs_in.avail_in);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* if we filled the output buffer, signal that we likely have
|
||||
* more and need to be called again
|
||||
*/
|
||||
|
||||
if (eff_buf->token_len == sizeof(conn->buf_out))
|
||||
return 1;
|
||||
|
||||
/* we don't need calling again until new input data comes */
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_EXT_CALLBACK_FLUSH_PENDING_TX:
|
||||
case LWS_EXT_CALLBACK_PACKET_TX_PRESEND:
|
||||
|
||||
/*
|
||||
* deflate the outgoing compressed data
|
||||
*/
|
||||
|
||||
conn->zs_out.next_in = (unsigned char *)eff_buf->token;
|
||||
conn->zs_out.avail_in = eff_buf->token_len;
|
||||
|
||||
conn->zs_out.next_out = conn->buf_out;
|
||||
conn->zs_out.avail_out = sizeof(conn->buf_out);
|
||||
|
||||
n = Z_PARTIAL_FLUSH;
|
||||
if (reason == LWS_EXT_CALLBACK_FLUSH_PENDING_TX)
|
||||
n = Z_FULL_FLUSH;
|
||||
|
||||
n = deflate(&conn->zs_out, n);
|
||||
if (n == Z_STREAM_ERROR) {
|
||||
/*
|
||||
* screwed.. close the connection... we will get a
|
||||
* destroy callback to take care of closing nicely
|
||||
*/
|
||||
lwsl_ext("zlib error deflate\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* rewrite the buffer pointers and length */
|
||||
|
||||
eff_buf->token = (char *)conn->buf_out;
|
||||
eff_buf->token_len =
|
||||
sizeof(conn->buf_out) - conn->zs_out.avail_out;
|
||||
|
||||
/*
|
||||
* if we filled the output buffer, signal that we likely have
|
||||
* more and need to be called again... even in deflate case
|
||||
* we might sometimes need to spill more than came in
|
||||
*/
|
||||
|
||||
if (eff_buf->token_len == sizeof(conn->buf_out))
|
||||
return 1;
|
||||
|
||||
/* we don't need calling again until new input data comes */
|
||||
|
||||
return 0;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
|
||||
#include <zlib.h>
|
||||
|
||||
#define DEFLATE_STREAM_CHUNK 128
|
||||
#define DEFLATE_STREAM_COMPRESSION_LEVEL 1
|
||||
|
||||
struct lws_ext_deflate_stream_conn {
|
||||
z_stream zs_in;
|
||||
z_stream zs_out;
|
||||
int remaining_in;
|
||||
unsigned char buf_in[LWS_MAX_SOCKET_IO_BUF];
|
||||
unsigned char buf_out[LWS_MAX_SOCKET_IO_BUF];
|
||||
};
|
||||
|
||||
extern int lws_extension_callback_deflate_stream(
|
||||
struct libwebsocket_context *context,
|
||||
struct libwebsocket_extension *ext,
|
||||
struct libwebsocket *wsi,
|
||||
enum libwebsocket_extension_callback_reasons reason,
|
||||
void *user, void *in, size_t len);
|
473
lib/extension-permessage-deflate.c
Normal file
473
lib/extension-permessage-deflate.c
Normal file
|
@ -0,0 +1,473 @@
|
|||
/*
|
||||
* ./lib/extension-permessage-deflate.c
|
||||
*
|
||||
* Copyright (C) 2016 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation:
|
||||
* version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
#include "extension-permessage-deflate.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define LWS_ZLIB_MEMLEVEL 8
|
||||
|
||||
const struct lws_ext_options lws_ext_pm_deflate_options[] = {
|
||||
/* public RFC7692 settings */
|
||||
{ "server_no_context_takeover", EXTARG_NONE },
|
||||
{ "client_no_context_takeover", EXTARG_NONE },
|
||||
{ "server_max_window_bits", EXTARG_OPT_DEC },
|
||||
{ "client_max_window_bits", EXTARG_OPT_DEC },
|
||||
/* ones only user code can set */
|
||||
{ "rx_buf_size", EXTARG_DEC },
|
||||
{ "tx_buf_size", EXTARG_DEC },
|
||||
{ "compression_level", EXTARG_DEC },
|
||||
{ "mem_level", EXTARG_DEC },
|
||||
{ NULL, 0 }, /* sentinel */
|
||||
};
|
||||
|
||||
static void
|
||||
lws_extension_pmdeflate_restrict_args(struct lws *wsi,
|
||||
struct lws_ext_pm_deflate_priv *priv)
|
||||
{
|
||||
int n, extra;
|
||||
|
||||
/* cap the RX buf at the nearest power of 2 to protocol rx buf */
|
||||
|
||||
n = wsi->context->pt_serv_buf_size;
|
||||
if (wsi->protocol->rx_buffer_size)
|
||||
n = wsi->protocol->rx_buffer_size;
|
||||
|
||||
extra = 7;
|
||||
while (n >= 1 << (extra + 1))
|
||||
extra++;
|
||||
|
||||
if (extra < priv->args[PMD_RX_BUF_PWR2]) {
|
||||
priv->args[PMD_RX_BUF_PWR2] = extra;
|
||||
lwsl_err(" Capping pmd rx to %d\n", 1 << extra);
|
||||
}
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_extension_callback_pm_deflate(struct lws_context *context,
|
||||
const struct lws_extension *ext,
|
||||
struct lws *wsi,
|
||||
enum lws_extension_callback_reasons reason,
|
||||
void *user, void *in, size_t len)
|
||||
{
|
||||
struct lws_ext_pm_deflate_priv *priv =
|
||||
(struct lws_ext_pm_deflate_priv *)user;
|
||||
struct lws_tokens *eff_buf = (struct lws_tokens *)in;
|
||||
static unsigned char trail[] = { 0, 0, 0xff, 0xff };
|
||||
int n, ret = 0, was_fin = 0, extra;
|
||||
struct lws_ext_option_arg *oa;
|
||||
|
||||
switch (reason) {
|
||||
case LWS_EXT_CB_NAMED_OPTION_SET:
|
||||
oa = in;
|
||||
if (!oa->option_name)
|
||||
break;
|
||||
for (n = 0; n < ARRAY_SIZE(lws_ext_pm_deflate_options); n++)
|
||||
if (!strcmp(lws_ext_pm_deflate_options[n].name, oa->option_name))
|
||||
break;
|
||||
|
||||
if (n == ARRAY_SIZE(lws_ext_pm_deflate_options))
|
||||
break;
|
||||
oa->option_index = n;
|
||||
|
||||
/* fallthru */
|
||||
|
||||
case LWS_EXT_CB_OPTION_SET:
|
||||
oa = in;
|
||||
lwsl_notice("%s: option set: idx %d, %s, len %d\n", __func__,
|
||||
oa->option_index, oa->start, oa->len);
|
||||
if (oa->start)
|
||||
priv->args[oa->option_index] = atoi(oa->start);
|
||||
else
|
||||
priv->args[oa->option_index] = 1;
|
||||
|
||||
if (priv->args[PMD_CLIENT_MAX_WINDOW_BITS] == 8)
|
||||
priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 9;
|
||||
|
||||
lws_extension_pmdeflate_restrict_args(wsi, priv);
|
||||
break;
|
||||
|
||||
case LWS_EXT_CB_OPTION_CONFIRM:
|
||||
if (priv->args[PMD_SERVER_MAX_WINDOW_BITS] < 8 ||
|
||||
priv->args[PMD_SERVER_MAX_WINDOW_BITS] > 15 ||
|
||||
priv->args[PMD_CLIENT_MAX_WINDOW_BITS] < 8 ||
|
||||
priv->args[PMD_CLIENT_MAX_WINDOW_BITS] > 15)
|
||||
return -1;
|
||||
break;
|
||||
|
||||
case LWS_EXT_CB_CLIENT_CONSTRUCT:
|
||||
case LWS_EXT_CB_CONSTRUCT:
|
||||
|
||||
n = context->pt_serv_buf_size;
|
||||
if (wsi->protocol->rx_buffer_size)
|
||||
n = wsi->protocol->rx_buffer_size;
|
||||
|
||||
if (n < 128) {
|
||||
lwsl_err(" permessage-deflate requires the protocol (%s) to have an RX buffer >= 128\n",
|
||||
wsi->protocol->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* fill in **user */
|
||||
priv = lws_zalloc(sizeof(*priv));
|
||||
*((void **)user) = priv;
|
||||
lwsl_ext("%s: LWS_EXT_CB_*CONSTRUCT\n", __func__);
|
||||
memset(priv, 0, sizeof(*priv));
|
||||
|
||||
/* fill in pointer to options list */
|
||||
if (in)
|
||||
*((const struct lws_ext_options **)in) =
|
||||
lws_ext_pm_deflate_options;
|
||||
|
||||
/* fallthru */
|
||||
|
||||
case LWS_EXT_CB_OPTION_DEFAULT:
|
||||
|
||||
/* set the public, RFC7692 defaults... */
|
||||
|
||||
priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER] = 0,
|
||||
priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER] = 0;
|
||||
priv->args[PMD_SERVER_MAX_WINDOW_BITS] = 15;
|
||||
priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 15;
|
||||
|
||||
/* ...and the ones the user code can override */
|
||||
|
||||
priv->args[PMD_RX_BUF_PWR2] = 10; /* ie, 1024 */
|
||||
priv->args[PMD_TX_BUF_PWR2] = 10; /* ie, 1024 */
|
||||
priv->args[PMD_COMP_LEVEL] = 1;
|
||||
priv->args[PMD_MEM_LEVEL] = 8;
|
||||
|
||||
lws_extension_pmdeflate_restrict_args(wsi, priv);
|
||||
break;
|
||||
|
||||
case LWS_EXT_CB_DESTROY:
|
||||
lwsl_ext("%s: LWS_EXT_CB_DESTROY\n", __func__);
|
||||
lws_free(priv->buf_rx_inflated);
|
||||
lws_free(priv->buf_tx_deflated);
|
||||
if (priv->rx_init)
|
||||
(void)inflateEnd(&priv->rx);
|
||||
if (priv->tx_init)
|
||||
(void)deflateEnd(&priv->tx);
|
||||
lws_free(priv);
|
||||
return ret;
|
||||
|
||||
case LWS_EXT_CB_PAYLOAD_RX:
|
||||
lwsl_ext(" %s: LWS_EXT_CB_PAYLOAD_RX: in %d, existing in %d\n",
|
||||
__func__, eff_buf->token_len, priv->rx.avail_in);
|
||||
if (!(wsi->u.ws.rsv_first_msg & 0x40))
|
||||
return 0;
|
||||
|
||||
#if 0
|
||||
for (n = 0; n < eff_buf->token_len; n++) {
|
||||
printf("%02X ", (unsigned char)eff_buf->token[n]);
|
||||
if ((n & 15) == 15)
|
||||
printf("\n");
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
if (!priv->rx_init)
|
||||
if (inflateInit2(&priv->rx, -priv->args[PMD_SERVER_MAX_WINDOW_BITS]) != Z_OK) {
|
||||
lwsl_err("%s: iniflateInit failed\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
priv->rx_init = 1;
|
||||
if (!priv->buf_rx_inflated)
|
||||
priv->buf_rx_inflated = lws_malloc(LWS_PRE + 7 + 5 +
|
||||
(1 << priv->args[PMD_RX_BUF_PWR2]));
|
||||
if (!priv->buf_rx_inflated) {
|
||||
lwsl_err("%s: OOM\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to leave the input stream alone if we didn't
|
||||
* finish with it yet. The input stream is held in the wsi
|
||||
* rx buffer by the caller, so this assumption is safe while
|
||||
* we block new rx while draining the existing rx
|
||||
*/
|
||||
if (!priv->rx.avail_in && eff_buf->token && eff_buf->token_len) {
|
||||
priv->rx.next_in = (unsigned char *)eff_buf->token;
|
||||
priv->rx.avail_in = eff_buf->token_len;
|
||||
}
|
||||
priv->rx.next_out = priv->buf_rx_inflated + LWS_PRE;
|
||||
eff_buf->token = (char *)priv->rx.next_out;
|
||||
priv->rx.avail_out = 1 << priv->args[PMD_RX_BUF_PWR2];
|
||||
|
||||
if (priv->rx_held_valid) {
|
||||
lwsl_ext("-- RX piling on held byte --\n");
|
||||
*(priv->rx.next_out++) = priv->rx_held;
|
||||
priv->rx.avail_out--;
|
||||
priv->rx_held_valid = 0;
|
||||
}
|
||||
|
||||
/* if...
|
||||
*
|
||||
* - he has no remaining input content for this message, and
|
||||
* - and this is the final fragment, and
|
||||
* - we used everything that could be drained on the input side
|
||||
*
|
||||
* ...then put back the 00 00 FF FF the sender stripped as our
|
||||
* input to zlib
|
||||
*/
|
||||
if (!priv->rx.avail_in && wsi->u.ws.final &&
|
||||
!wsi->u.ws.rx_packet_length) {
|
||||
lwsl_ext("RX APPEND_TRAILER-DO\n");
|
||||
was_fin = 1;
|
||||
priv->rx.next_in = trail;
|
||||
priv->rx.avail_in = sizeof(trail);
|
||||
}
|
||||
|
||||
n = inflate(&priv->rx, Z_NO_FLUSH);
|
||||
lwsl_ext("inflate ret %d, avi %d, avo %d, wsifinal %d\n", n,
|
||||
priv->rx.avail_in, priv->rx.avail_out, wsi->u.ws.final);
|
||||
switch (n) {
|
||||
case Z_NEED_DICT:
|
||||
case Z_STREAM_ERROR:
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
lwsl_info("zlib error inflate %d: %s\n",
|
||||
n, priv->rx.msg);
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
* If we did not already send in the 00 00 FF FF, and he's
|
||||
* out of input, he did not EXACTLY fill the output buffer
|
||||
* (which is ambiguous and we will force it to go around
|
||||
* again by withholding a byte), and he's otherwise working on
|
||||
* being a FIN fragment, then do the FIN message processing
|
||||
* of faking up the 00 00 FF FF that the sender stripped.
|
||||
*/
|
||||
if (!priv->rx.avail_in && wsi->u.ws.final &&
|
||||
!wsi->u.ws.rx_packet_length && !was_fin &&
|
||||
priv->rx.avail_out /* ambiguous as to if it is the end */
|
||||
) {
|
||||
lwsl_ext("RX APPEND_TRAILER-DO\n");
|
||||
was_fin = 1;
|
||||
priv->rx.next_in = trail;
|
||||
priv->rx.avail_in = sizeof(trail);
|
||||
n = inflate(&priv->rx, Z_SYNC_FLUSH);
|
||||
lwsl_ext("RX trailer inf returned %d, avi %d, avo %d\n", n,
|
||||
priv->rx.avail_in, priv->rx.avail_out);
|
||||
switch (n) {
|
||||
case Z_NEED_DICT:
|
||||
case Z_STREAM_ERROR:
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
lwsl_info("zlib error inflate %d: %s\n",
|
||||
n, priv->rx.msg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* we must announce in our returncode now if there is more
|
||||
* output to be expected from inflate, so we can decide to
|
||||
* set the FIN bit on this bufferload or not. However zlib
|
||||
* is ambiguous when we exactly filled the inflate buffer. It
|
||||
* does not give us a clue as to whether we should understand
|
||||
* that to mean he ended on a buffer boundary, or if there is
|
||||
* more in the pipeline.
|
||||
*
|
||||
* So to work around that safely, if it used all output space
|
||||
* exactly, we ALWAYS say there is more coming and we withhold
|
||||
* the last byte of the buffer to guarantee that is true.
|
||||
*
|
||||
* That still leaves us at least one byte to finish with a FIN
|
||||
* on, even if actually nothing more is coming from the next
|
||||
* inflate action itself.
|
||||
*/
|
||||
if (!priv->rx.avail_out) { /* he used all available out buf */
|
||||
lwsl_ext("-- rx grabbing held --\n");
|
||||
/* snip the last byte and hold it for next time */
|
||||
priv->rx_held = *(--priv->rx.next_out);
|
||||
priv->rx_held_valid = 1;
|
||||
}
|
||||
|
||||
eff_buf->token_len = (char *)priv->rx.next_out - eff_buf->token;
|
||||
priv->count_rx_between_fin += eff_buf->token_len;
|
||||
|
||||
lwsl_ext(" %s: RX leaving with new effbuff len %d, "
|
||||
"ret %d, rx.avail_in=%d, TOTAL RX since FIN %lu\n",
|
||||
__func__, eff_buf->token_len, priv->rx_held_valid,
|
||||
priv->rx.avail_in,
|
||||
(unsigned long)priv->count_rx_between_fin);
|
||||
|
||||
if (was_fin) {
|
||||
priv->count_rx_between_fin = 0;
|
||||
if (priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER]) {
|
||||
(void)inflateEnd(&priv->rx);
|
||||
priv->rx_init = 0;
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
for (n = 0; n < eff_buf->token_len; n++)
|
||||
putchar(eff_buf->token[n]);
|
||||
puts("\n");
|
||||
#endif
|
||||
|
||||
return priv->rx_held_valid;
|
||||
|
||||
case LWS_EXT_CB_PAYLOAD_TX:
|
||||
|
||||
if (!priv->tx_init) {
|
||||
n = deflateInit2(&priv->tx, priv->args[PMD_COMP_LEVEL],
|
||||
Z_DEFLATED,
|
||||
-priv->args[PMD_SERVER_MAX_WINDOW_BITS +
|
||||
(wsi->vhost->listen_port <= 0)],
|
||||
priv->args[PMD_MEM_LEVEL],
|
||||
Z_DEFAULT_STRATEGY);
|
||||
if (n != Z_OK) {
|
||||
lwsl_ext("inflateInit2 failed %d\n", n);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
priv->tx_init = 1;
|
||||
if (!priv->buf_tx_deflated)
|
||||
priv->buf_tx_deflated = lws_malloc(LWS_PRE + 7 + 5 +
|
||||
(1 << priv->args[PMD_TX_BUF_PWR2]));
|
||||
if (!priv->buf_tx_deflated) {
|
||||
lwsl_err("%s: OOM\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (eff_buf->token) {
|
||||
lwsl_ext("%s: TX: eff_buf length %d\n", __func__,
|
||||
eff_buf->token_len);
|
||||
priv->tx.next_in = (unsigned char *)eff_buf->token;
|
||||
priv->tx.avail_in = eff_buf->token_len;
|
||||
}
|
||||
|
||||
#if 0
|
||||
for (n = 0; n < eff_buf->token_len; n++) {
|
||||
printf("%02X ", (unsigned char)eff_buf->token[n]);
|
||||
if ((n & 15) == 15)
|
||||
printf("\n");
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
priv->tx.next_out = priv->buf_tx_deflated + LWS_PRE + 5;
|
||||
eff_buf->token = (char *)priv->tx.next_out;
|
||||
priv->tx.avail_out = 1 << priv->args[PMD_TX_BUF_PWR2];
|
||||
|
||||
n = deflate(&priv->tx, Z_SYNC_FLUSH);
|
||||
if (n == Z_STREAM_ERROR) {
|
||||
lwsl_ext("%s: Z_STREAM_ERROR\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (priv->tx_held_valid) {
|
||||
priv->tx_held_valid = 0;
|
||||
if (priv->tx.avail_out == 1 << priv->args[PMD_TX_BUF_PWR2])
|
||||
/*
|
||||
* we can get a situation he took something in
|
||||
* but did not generate anything out, at the end
|
||||
* of a message (eg, next thing he sends is 80
|
||||
* 00, a zero length FIN, like Authobahn can
|
||||
* send).
|
||||
* If we have come back as a FIN, we must not
|
||||
* place the pending trailer 00 00 FF FF, just
|
||||
* the 1 byte of live data
|
||||
*/
|
||||
*(--eff_buf->token) = priv->tx_held[0];
|
||||
else {
|
||||
/* he generated data, prepend whole pending */
|
||||
eff_buf->token -= 5;
|
||||
for (n = 0; n < 5; n++)
|
||||
eff_buf->token[n] = priv->tx_held[n];
|
||||
|
||||
}
|
||||
}
|
||||
priv->compressed_out = 1;
|
||||
eff_buf->token_len = (int)(priv->tx.next_out -
|
||||
(unsigned char *)eff_buf->token);
|
||||
|
||||
/*
|
||||
* we must announce in our returncode now if there is more
|
||||
* output to be expected from inflate, so we can decide to
|
||||
* set the FIN bit on this bufferload or not. However zlib
|
||||
* is ambiguous when we exactly filled the inflate buffer. It
|
||||
* does not give us a clue as to whether we should understand
|
||||
* that to mean he ended on a buffer boundary, or if there is
|
||||
* more in the pipeline.
|
||||
*
|
||||
* Worse, the guy providing the stuff we are sending may not
|
||||
* know until after that this was, actually, the last chunk,
|
||||
* that can happen even if we did not fill the output buf, ie
|
||||
* he may send after this a zero-length FIN fragment.
|
||||
*
|
||||
* This is super difficult because we must snip the last 4
|
||||
* bytes in the case this is the last compressed output of the
|
||||
* message. The only way to deal with it is defer sending the
|
||||
* last 5 bytes of each frame until the next one, when we will
|
||||
* be in a position to understand if that has a FIN or not.
|
||||
*/
|
||||
|
||||
extra = !!(len & LWS_WRITE_NO_FIN) || !priv->tx.avail_out;
|
||||
|
||||
if (eff_buf->token_len >= 4 + extra) {
|
||||
lwsl_ext("tx held %d\n", 4 + extra);
|
||||
priv->tx_held_valid = extra;
|
||||
for (n = 3 + extra; n >= 0; n--)
|
||||
priv->tx_held[n] = *(--priv->tx.next_out);
|
||||
eff_buf->token_len -= 4 + extra;
|
||||
}
|
||||
lwsl_ext(" TX rewritten with new effbuff len %d, ret %d\n",
|
||||
eff_buf->token_len, !priv->tx.avail_out);
|
||||
|
||||
return !priv->tx.avail_out; /* 1 == have more tx pending */
|
||||
|
||||
case LWS_EXT_CB_PACKET_TX_PRESEND:
|
||||
if (!priv->compressed_out)
|
||||
break;
|
||||
priv->compressed_out = 0;
|
||||
|
||||
if ((*(eff_buf->token) & 0x80) &&
|
||||
priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER]) {
|
||||
lwsl_debug("PMD_CLIENT_NO_CONTEXT_TAKEOVER\n");
|
||||
(void)deflateEnd(&priv->tx);
|
||||
priv->tx_init = 0;
|
||||
}
|
||||
|
||||
n = *(eff_buf->token) & 15;
|
||||
/* set RSV1, but not on CONTINUATION */
|
||||
if (n == LWSWSOPC_TEXT_FRAME || n == LWSWSOPC_BINARY_FRAME)
|
||||
*eff_buf->token |= 0x40;
|
||||
#if 0
|
||||
for (n = 0; n < eff_buf->token_len; n++) {
|
||||
printf("%02X ", (unsigned char)eff_buf->token[n]);
|
||||
if ((n & 15) == 15)
|
||||
puts("\n");
|
||||
}
|
||||
puts("\n");
|
||||
#endif
|
||||
lwsl_ext("%s: tx opcode 0x%02X\n", __func__,
|
||||
(unsigned char)*eff_buf->token);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
41
lib/extension-permessage-deflate.h
Normal file
41
lib/extension-permessage-deflate.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
|
||||
#include <zlib.h>
|
||||
|
||||
#define DEFLATE_FRAME_COMPRESSION_LEVEL_SERVER 1
|
||||
#define DEFLATE_FRAME_COMPRESSION_LEVEL_CLIENT Z_DEFAULT_COMPRESSION
|
||||
|
||||
enum arg_indexes {
|
||||
PMD_SERVER_NO_CONTEXT_TAKEOVER,
|
||||
PMD_CLIENT_NO_CONTEXT_TAKEOVER,
|
||||
PMD_SERVER_MAX_WINDOW_BITS,
|
||||
PMD_CLIENT_MAX_WINDOW_BITS,
|
||||
PMD_RX_BUF_PWR2,
|
||||
PMD_TX_BUF_PWR2,
|
||||
PMD_COMP_LEVEL,
|
||||
PMD_MEM_LEVEL,
|
||||
|
||||
PMD_ARG_COUNT
|
||||
};
|
||||
|
||||
struct lws_ext_pm_deflate_priv {
|
||||
z_stream rx;
|
||||
z_stream tx;
|
||||
|
||||
unsigned char *buf_rx_inflated; /* RX inflated output buffer */
|
||||
unsigned char *buf_tx_deflated; /* TX deflated output buffer */
|
||||
|
||||
size_t count_rx_between_fin;
|
||||
|
||||
unsigned char args[PMD_ARG_COUNT];
|
||||
unsigned char tx_held[5];
|
||||
unsigned char rx_held;
|
||||
|
||||
unsigned char tx_init:1;
|
||||
unsigned char rx_init:1;
|
||||
unsigned char compressed_out:1;
|
||||
unsigned char rx_held_valid:1;
|
||||
unsigned char tx_held_valid:1;
|
||||
unsigned char rx_append_trailer:1;
|
||||
unsigned char pending_tx_trailer:1;
|
||||
};
|
||||
|
276
lib/extension.c
276
lib/extension.c
|
@ -1,87 +1,204 @@
|
|||
#include "private-libwebsockets.h"
|
||||
|
||||
#include "extension-deflate-frame.h"
|
||||
#include "extension-deflate-stream.h"
|
||||
|
||||
struct libwebsocket_extension libwebsocket_internal_extensions[] = {
|
||||
#ifdef LWS_EXT_DEFLATE_STREAM
|
||||
{
|
||||
"deflate-stream",
|
||||
lws_extension_callback_deflate_stream,
|
||||
sizeof(struct lws_ext_deflate_stream_conn)
|
||||
},
|
||||
#else
|
||||
{
|
||||
"x-webkit-deflate-frame",
|
||||
lws_extension_callback_deflate_frame,
|
||||
sizeof(struct lws_ext_deflate_frame_conn)
|
||||
},
|
||||
{
|
||||
"deflate-frame",
|
||||
lws_extension_callback_deflate_frame,
|
||||
sizeof(struct lws_ext_deflate_frame_conn)
|
||||
},
|
||||
#endif
|
||||
{ /* terminator */
|
||||
NULL, NULL, 0
|
||||
}
|
||||
};
|
||||
#include "extension-permessage-deflate.h"
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_context_init_extensions(struct lws_context_creation_info *info,
|
||||
struct libwebsocket_context *context)
|
||||
struct lws_context *context)
|
||||
{
|
||||
context->extensions = info->extensions;
|
||||
lwsl_info(" LWS_MAX_EXTENSIONS_ACTIVE: %u\n", LWS_MAX_EXTENSIONS_ACTIVE);
|
||||
}
|
||||
|
||||
LWS_VISIBLE struct libwebsocket_extension *libwebsocket_get_internal_extensions()
|
||||
enum lws_ext_option_parser_states {
|
||||
LEAPS_SEEK_NAME,
|
||||
LEAPS_EAT_NAME,
|
||||
LEAPS_SEEK_VAL,
|
||||
LEAPS_EAT_DEC,
|
||||
LEAPS_SEEK_ARG_TERM
|
||||
};
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi,
|
||||
void *ext_user, const struct lws_ext_options *opts,
|
||||
const char *in, int len)
|
||||
{
|
||||
return libwebsocket_internal_extensions;
|
||||
enum lws_ext_option_parser_states leap = LEAPS_SEEK_NAME;
|
||||
unsigned int match_map = 0, n, m, w = 0, count_options = 0,
|
||||
pending_close_quote = 0;
|
||||
struct lws_ext_option_arg oa;
|
||||
|
||||
oa.option_name = NULL;
|
||||
|
||||
while (opts[count_options].name)
|
||||
count_options++;
|
||||
while (len) {
|
||||
lwsl_ext("'%c' %d", *in, leap);
|
||||
switch (leap) {
|
||||
case LEAPS_SEEK_NAME:
|
||||
if (*in == ' ')
|
||||
break;
|
||||
if (*in == ',') {
|
||||
len = 1;
|
||||
break;
|
||||
}
|
||||
match_map = (1 << count_options) - 1;
|
||||
leap = LEAPS_EAT_NAME;
|
||||
w = 0;
|
||||
|
||||
/* fallthru */
|
||||
|
||||
case LEAPS_EAT_NAME:
|
||||
oa.start = NULL;
|
||||
oa.len = 0;
|
||||
m = match_map;
|
||||
n = 0;
|
||||
pending_close_quote = 0;
|
||||
while (m) {
|
||||
if (m & 1) {
|
||||
lwsl_ext(" m=%d, n=%d, w=%d\n", m, n, w);
|
||||
|
||||
if (*in == opts[n].name[w]) {
|
||||
if (!opts[n].name[w + 1]) {
|
||||
oa.option_index = n;
|
||||
lwsl_ext("hit %d\n", oa.option_index);
|
||||
leap = LEAPS_SEEK_VAL;
|
||||
if (len == 1)
|
||||
goto set_arg;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
match_map &= ~(1 << n);
|
||||
if (!match_map) {
|
||||
lwsl_ext("empty match map\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
m >>= 1;
|
||||
n++;
|
||||
}
|
||||
w++;
|
||||
break;
|
||||
case LEAPS_SEEK_VAL:
|
||||
if (*in == ' ')
|
||||
break;
|
||||
if (*in == ',') {
|
||||
len = 1;
|
||||
break;
|
||||
}
|
||||
if (*in == ';' || len == 1) { /* ie,nonoptional */
|
||||
if (opts[oa.option_index].type == EXTARG_DEC)
|
||||
return -1;
|
||||
leap = LEAPS_SEEK_NAME;
|
||||
goto set_arg;
|
||||
}
|
||||
if (*in == '=') {
|
||||
w = 0;
|
||||
pending_close_quote = 0;
|
||||
if (opts[oa.option_index].type == EXTARG_NONE)
|
||||
return -1;
|
||||
|
||||
leap = LEAPS_EAT_DEC;
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
|
||||
case LEAPS_EAT_DEC:
|
||||
if (*in >= '0' && *in <= '9') {
|
||||
if (!w)
|
||||
oa.start = in;
|
||||
w++;
|
||||
if (len != 1)
|
||||
break;
|
||||
}
|
||||
if (!w && *in =='"') {
|
||||
pending_close_quote = 1;
|
||||
break;
|
||||
}
|
||||
if (!w)
|
||||
return -1;
|
||||
if (pending_close_quote && *in != '"' && len != 1)
|
||||
return -1;
|
||||
leap = LEAPS_SEEK_ARG_TERM;
|
||||
if (oa.start)
|
||||
oa.len = in - oa.start;
|
||||
if (len == 1)
|
||||
oa.len++;
|
||||
|
||||
set_arg:
|
||||
ext->callback(lws_get_context(wsi),
|
||||
ext, wsi, LWS_EXT_CB_OPTION_SET,
|
||||
ext_user, (char *)&oa, 0);
|
||||
if (len == 1)
|
||||
break;
|
||||
if (pending_close_quote && *in == '"')
|
||||
break;
|
||||
|
||||
/* fallthru */
|
||||
|
||||
case LEAPS_SEEK_ARG_TERM:
|
||||
if (*in == ' ')
|
||||
break;
|
||||
if (*in == ';') {
|
||||
leap = LEAPS_SEEK_NAME;
|
||||
break;
|
||||
}
|
||||
if (*in == ',') {
|
||||
len = 1;
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
len--;
|
||||
in++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* 0 = nobody had nonzero return, 1 = somebody had positive return, -1 = fail */
|
||||
|
||||
int lws_ext_callback_for_each_active(struct libwebsocket *wsi, int reason,
|
||||
void *arg, int len)
|
||||
int lws_ext_cb_active(struct lws *wsi, int reason, void *arg, int len)
|
||||
{
|
||||
int n, m, handled = 0;
|
||||
|
||||
for (n = 0; n < wsi->count_active_extensions; n++) {
|
||||
m = wsi->active_extensions[n]->callback(
|
||||
wsi->protocol->owning_server,
|
||||
wsi->active_extensions[n], wsi,
|
||||
reason,
|
||||
wsi->active_extensions_user[n],
|
||||
arg, len);
|
||||
for (n = 0; n < wsi->count_act_ext; n++) {
|
||||
m = wsi->active_extensions[n]->callback(lws_get_context(wsi),
|
||||
wsi->active_extensions[n], wsi, reason,
|
||||
wsi->act_ext_user[n], arg, len);
|
||||
if (m < 0) {
|
||||
lwsl_ext(
|
||||
"Extension '%s' failed to handle callback %d!\n",
|
||||
wsi->active_extensions[n]->name, reason);
|
||||
lwsl_ext("Ext '%s' failed to handle callback %d!\n",
|
||||
wsi->active_extensions[n]->name, reason);
|
||||
return -1;
|
||||
}
|
||||
/* valgrind... */
|
||||
if (reason == LWS_EXT_CB_DESTROY)
|
||||
wsi->act_ext_user[n] = NULL;
|
||||
if (m > handled)
|
||||
handled = m;
|
||||
}
|
||||
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
int lws_ext_callback_for_each_extension_type(
|
||||
struct libwebsocket_context *context, struct libwebsocket *wsi,
|
||||
int reason, void *arg, int len)
|
||||
int lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi,
|
||||
int reason, void *arg, int len)
|
||||
{
|
||||
int n = 0, m, handled = 0;
|
||||
struct libwebsocket_extension *ext = context->extensions;
|
||||
const struct lws_extension *ext;
|
||||
|
||||
if (!wsi || !wsi->vhost)
|
||||
return 0;
|
||||
|
||||
ext = wsi->vhost->extensions;
|
||||
|
||||
while (ext && ext->callback && !handled) {
|
||||
m = ext->callback(context, ext, wsi, reason,
|
||||
(void *)(long)n, arg, len);
|
||||
(void *)(lws_intptr_t)n, arg, len);
|
||||
if (m < 0) {
|
||||
lwsl_ext(
|
||||
"Extension '%s' failed to handle callback %d!\n",
|
||||
wsi->active_extensions[n]->name, reason);
|
||||
lwsl_ext("Ext '%s' failed to handle callback %d!\n",
|
||||
wsi->active_extensions[n]->name, reason);
|
||||
return -1;
|
||||
}
|
||||
if (m)
|
||||
|
@ -90,18 +207,15 @@ int lws_ext_callback_for_each_extension_type(
|
|||
ext++;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
lws_issue_raw_ext_access(struct libwebsocket *wsi,
|
||||
unsigned char *buf, size_t len)
|
||||
lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len)
|
||||
{
|
||||
int ret;
|
||||
struct lws_tokens eff_buf;
|
||||
int m;
|
||||
int n = 0;
|
||||
int ret, m, n = 0;
|
||||
|
||||
eff_buf.token = (char *)buf;
|
||||
eff_buf.token_len = len;
|
||||
|
@ -119,8 +233,8 @@ lws_issue_raw_ext_access(struct libwebsocket *wsi,
|
|||
ret = 0;
|
||||
|
||||
/* show every extension the new incoming data */
|
||||
m = lws_ext_callback_for_each_active(wsi,
|
||||
LWS_EXT_CALLBACK_PACKET_TX_PRESEND, &eff_buf, 0);
|
||||
m = lws_ext_cb_active(wsi,
|
||||
LWS_EXT_CB_PACKET_TX_PRESEND, &eff_buf, 0);
|
||||
if (m < 0)
|
||||
return -1;
|
||||
if (m) /* handled */
|
||||
|
@ -165,7 +279,7 @@ lws_issue_raw_ext_access(struct libwebsocket *wsi,
|
|||
* Or we had to hold on to some of it?
|
||||
*/
|
||||
|
||||
if (!lws_send_pipe_choked(wsi) && !wsi->truncated_send_len)
|
||||
if (!lws_send_pipe_choked(wsi) && !wsi->trunc_len)
|
||||
/* no we could add more, lets's do that */
|
||||
continue;
|
||||
|
||||
|
@ -175,8 +289,7 @@ lws_issue_raw_ext_access(struct libwebsocket *wsi,
|
|||
* Yes, he's choked. Don't spill the rest now get a callback
|
||||
* when he is ready to send and take care of it there
|
||||
*/
|
||||
libwebsocket_callback_on_writable(
|
||||
wsi->protocol->owning_server, wsi);
|
||||
lws_callback_on_writable(wsi);
|
||||
wsi->extension_data_pending = 1;
|
||||
ret = 0;
|
||||
}
|
||||
|
@ -185,24 +298,47 @@ lws_issue_raw_ext_access(struct libwebsocket *wsi,
|
|||
}
|
||||
|
||||
int
|
||||
lws_any_extension_handled(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi,
|
||||
enum libwebsocket_extension_callback_reasons r,
|
||||
void *v, size_t len)
|
||||
lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r,
|
||||
void *v, size_t len)
|
||||
{
|
||||
int n;
|
||||
int handled = 0;
|
||||
struct lws_context *context = wsi->context;
|
||||
int n, handled = 0;
|
||||
|
||||
/* maybe an extension will take care of it for us */
|
||||
|
||||
for (n = 0; n < wsi->count_active_extensions && !handled; n++) {
|
||||
for (n = 0; n < wsi->count_act_ext && !handled; n++) {
|
||||
if (!wsi->active_extensions[n]->callback)
|
||||
continue;
|
||||
|
||||
handled |= wsi->active_extensions[n]->callback(context,
|
||||
wsi->active_extensions[n], wsi,
|
||||
r, wsi->active_extensions_user[n], v, len);
|
||||
r, wsi->act_ext_user[n], v, len);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
int
|
||||
lws_set_extension_option(struct lws *wsi, const char *ext_name,
|
||||
const char *opt_name, const char *opt_val)
|
||||
{
|
||||
struct lws_ext_option_arg oa;
|
||||
int idx = 0;
|
||||
|
||||
/* first identify if the ext is active on this wsi */
|
||||
while (idx < wsi->count_act_ext &&
|
||||
strcmp(wsi->active_extensions[idx]->name, ext_name))
|
||||
idx++;
|
||||
|
||||
if (idx == wsi->count_act_ext)
|
||||
return -1; /* request ext not active on this wsi */
|
||||
|
||||
oa.option_name = opt_name;
|
||||
oa.option_index = 0;
|
||||
oa.start = opt_val;
|
||||
oa.len = 0;
|
||||
|
||||
return wsi->active_extensions[idx]->callback(
|
||||
wsi->context, wsi->active_extensions[idx], wsi,
|
||||
LWS_EXT_CB_NAMED_OPTION_SET, wsi->act_ext_user[idx], &oa, 0);
|
||||
}
|
||||
|
|
669
lib/fops-zip.c
Normal file
669
lib/fops-zip.c
Normal file
|
@ -0,0 +1,669 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Original code used in this source file:
|
||||
*
|
||||
* https://github.com/PerBothner/DomTerm.git @912add15f3d0aec
|
||||
*
|
||||
* ./lws-term/io.c
|
||||
* ./lws-term/junzip.c
|
||||
*
|
||||
* Copyright (C) 2017 Per Bothner <per@bothner.com>
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* lws rewrite:
|
||||
*
|
||||
* Copyright (C) 2017 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation:
|
||||
* version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
/*
|
||||
* This code works with zip format containers which may have files compressed
|
||||
* with gzip deflate (type 8) or store uncompressed (type 0).
|
||||
*
|
||||
* Linux zip produces such zipfiles by default, eg
|
||||
*
|
||||
* $ zip ../myzip.zip file1 file2 file3
|
||||
*/
|
||||
|
||||
#define ZIP_COMPRESSION_METHOD_STORE 0
|
||||
#define ZIP_COMPRESSION_METHOD_DEFLATE 8
|
||||
|
||||
typedef struct {
|
||||
lws_filepos_t filename_start;
|
||||
uint32_t crc32;
|
||||
uint32_t comp_size;
|
||||
uint32_t uncomp_size;
|
||||
uint32_t offset;
|
||||
uint32_t mod_time;
|
||||
uint16_t filename_len;
|
||||
uint16_t extra;
|
||||
uint16_t method;
|
||||
uint16_t file_com_len;
|
||||
} lws_fops_zip_hdr_t;
|
||||
|
||||
typedef struct {
|
||||
struct lws_fop_fd fop_fd; /* MUST BE FIRST logical fop_fd into
|
||||
* file inside zip: fops_zip fops */
|
||||
lws_fop_fd_t zip_fop_fd; /* logical fop fd on to zip file
|
||||
* itself: using platform fops */
|
||||
lws_fops_zip_hdr_t hdr;
|
||||
z_stream inflate;
|
||||
lws_filepos_t content_start;
|
||||
lws_filepos_t exp_uncomp_pos;
|
||||
union {
|
||||
uint8_t trailer8[8];
|
||||
uint32_t trailer32[2];
|
||||
} u;
|
||||
uint8_t rbuf[128]; /* decompression chunk size */
|
||||
int entry_count;
|
||||
|
||||
unsigned int decompress:1; /* 0 = direct from file */
|
||||
unsigned int add_gzip_container:1;
|
||||
} *lws_fops_zip_t;
|
||||
|
||||
struct lws_plat_file_ops fops_zip;
|
||||
#define fop_fd_to_priv(FD) ((lws_fops_zip_t)(FD))
|
||||
|
||||
static const uint8_t hd[] = { 31, 139, 8, 0, 0, 0, 0, 0, 0, 3 };
|
||||
|
||||
enum {
|
||||
ZC_SIGNATURE = 0,
|
||||
ZC_VERSION_MADE_BY = 4,
|
||||
ZC_VERSION_NEEDED_TO_EXTRACT = 6,
|
||||
ZC_GENERAL_PURPOSE_BIT_FLAG = 8,
|
||||
ZC_COMPRESSION_METHOD = 10,
|
||||
ZC_LAST_MOD_FILE_TIME = 12,
|
||||
ZC_LAST_MOD_FILE_DATE = 14,
|
||||
ZC_CRC32 = 16,
|
||||
ZC_COMPRESSED_SIZE = 20,
|
||||
ZC_UNCOMPRESSED_SIZE = 24,
|
||||
ZC_FILE_NAME_LENGTH = 28,
|
||||
ZC_EXTRA_FIELD_LENGTH = 30,
|
||||
|
||||
ZC_FILE_COMMENT_LENGTH = 32,
|
||||
ZC_DISK_NUMBER_START = 34,
|
||||
ZC_INTERNAL_FILE_ATTRIBUTES = 36,
|
||||
ZC_EXTERNAL_FILE_ATTRIBUTES = 38,
|
||||
ZC_REL_OFFSET_LOCAL_HEADER = 42,
|
||||
ZC_DIRECTORY_LENGTH = 46,
|
||||
|
||||
ZE_SIGNATURE_OFFSET = 0,
|
||||
ZE_DESK_NUMBER = 4,
|
||||
ZE_CENTRAL_DIRECTORY_DISK_NUMBER = 6,
|
||||
ZE_NUM_ENTRIES_THIS_DISK = 8,
|
||||
ZE_NUM_ENTRIES = 10,
|
||||
ZE_CENTRAL_DIRECTORY_SIZE = 12,
|
||||
ZE_CENTRAL_DIR_OFFSET = 16,
|
||||
ZE_ZIP_COMMENT_LENGTH = 20,
|
||||
ZE_DIRECTORY_LENGTH = 22,
|
||||
|
||||
ZL_REL_OFFSET_CONTENT = 28,
|
||||
ZL_HEADER_LENGTH = 30,
|
||||
|
||||
LWS_FZ_ERR_SEEK_END_RECORD = 1,
|
||||
LWS_FZ_ERR_READ_END_RECORD,
|
||||
LWS_FZ_ERR_END_RECORD_MAGIC,
|
||||
LWS_FZ_ERR_END_RECORD_SANITY,
|
||||
LWS_FZ_ERR_CENTRAL_SEEK,
|
||||
LWS_FZ_ERR_CENTRAL_READ,
|
||||
LWS_FZ_ERR_CENTRAL_SANITY,
|
||||
LWS_FZ_ERR_NAME_TOO_LONG,
|
||||
LWS_FZ_ERR_NAME_SEEK,
|
||||
LWS_FZ_ERR_NAME_READ,
|
||||
LWS_FZ_ERR_CONTENT_SANITY,
|
||||
LWS_FZ_ERR_CONTENT_SEEK,
|
||||
LWS_FZ_ERR_SCAN_SEEK,
|
||||
LWS_FZ_ERR_NOT_FOUND,
|
||||
LWS_FZ_ERR_ZLIB_INIT,
|
||||
LWS_FZ_ERR_READ_CONTENT,
|
||||
LWS_FZ_ERR_SEEK_COMPRESSED,
|
||||
};
|
||||
|
||||
static uint16_t
|
||||
get_u16(void *p)
|
||||
{
|
||||
const uint8_t *c = (const uint8_t *)p;
|
||||
|
||||
return (uint16_t)((c[0] | (c[1] << 8)));
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
get_u32(void *p)
|
||||
{
|
||||
const uint8_t *c = (const uint8_t *)p;
|
||||
|
||||
return (uint32_t)((c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)));
|
||||
}
|
||||
|
||||
int
|
||||
lws_fops_zip_scan(lws_fops_zip_t priv, const char *name, int len)
|
||||
{
|
||||
lws_filepos_t amount;
|
||||
uint8_t buf[96];
|
||||
int i;
|
||||
|
||||
if (lws_vfs_file_seek_end(priv->zip_fop_fd, -ZE_DIRECTORY_LENGTH) < 0)
|
||||
return LWS_FZ_ERR_SEEK_END_RECORD;
|
||||
|
||||
if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
|
||||
ZE_DIRECTORY_LENGTH))
|
||||
return LWS_FZ_ERR_READ_END_RECORD;
|
||||
|
||||
if (amount != ZE_DIRECTORY_LENGTH)
|
||||
return LWS_FZ_ERR_READ_END_RECORD;
|
||||
|
||||
/*
|
||||
* We require the zip to have the last record right at the end
|
||||
* Linux zip always does this if no zip comment.
|
||||
*/
|
||||
if (buf[0] != 'P' || buf[1] != 'K' || buf[2] != 5 || buf[3] != 6)
|
||||
return LWS_FZ_ERR_END_RECORD_MAGIC;
|
||||
|
||||
i = get_u16(buf + ZE_NUM_ENTRIES);
|
||||
|
||||
if (get_u16(buf + ZE_DESK_NUMBER) ||
|
||||
get_u16(buf + ZE_CENTRAL_DIRECTORY_DISK_NUMBER) ||
|
||||
i != get_u16(buf + ZE_NUM_ENTRIES_THIS_DISK))
|
||||
return LWS_FZ_ERR_END_RECORD_SANITY;
|
||||
|
||||
/* end record is OK... look for our file in the central dir */
|
||||
|
||||
if (lws_vfs_file_seek_set(priv->zip_fop_fd,
|
||||
get_u32(buf + ZE_CENTRAL_DIR_OFFSET)) < 0)
|
||||
return LWS_FZ_ERR_CENTRAL_SEEK;
|
||||
|
||||
while (i--) {
|
||||
priv->content_start = lws_vfs_tell(priv->zip_fop_fd);
|
||||
|
||||
if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
|
||||
ZC_DIRECTORY_LENGTH))
|
||||
return LWS_FZ_ERR_CENTRAL_READ;
|
||||
|
||||
if (amount != ZC_DIRECTORY_LENGTH)
|
||||
return LWS_FZ_ERR_CENTRAL_READ;
|
||||
|
||||
if (get_u32(buf + ZC_SIGNATURE) != 0x02014B50)
|
||||
return LWS_FZ_ERR_CENTRAL_SANITY;
|
||||
|
||||
lwsl_debug("cstart 0x%lx\n", (unsigned long)priv->content_start);
|
||||
|
||||
priv->hdr.filename_len = get_u16(buf + ZC_FILE_NAME_LENGTH);
|
||||
priv->hdr.extra = get_u16(buf + ZC_EXTRA_FIELD_LENGTH);
|
||||
priv->hdr.filename_start = lws_vfs_tell(priv->zip_fop_fd);
|
||||
|
||||
priv->hdr.method = get_u16(buf + ZC_COMPRESSION_METHOD);
|
||||
priv->hdr.crc32 = get_u32(buf + ZC_CRC32);
|
||||
priv->hdr.comp_size = get_u32(buf + ZC_COMPRESSED_SIZE);
|
||||
priv->hdr.uncomp_size = get_u32(buf + ZC_UNCOMPRESSED_SIZE);
|
||||
priv->hdr.offset = get_u32(buf + ZC_REL_OFFSET_LOCAL_HEADER);
|
||||
priv->hdr.mod_time = get_u32(buf + ZC_LAST_MOD_FILE_TIME);
|
||||
priv->hdr.file_com_len = get_u16(buf + ZC_FILE_COMMENT_LENGTH);
|
||||
|
||||
if (priv->hdr.filename_len != len)
|
||||
goto next;
|
||||
|
||||
if (len >= sizeof(buf) - 1)
|
||||
return LWS_FZ_ERR_NAME_TOO_LONG;
|
||||
|
||||
if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
|
||||
&amount, buf, len))
|
||||
return LWS_FZ_ERR_NAME_READ;
|
||||
if (amount != len)
|
||||
return LWS_FZ_ERR_NAME_READ;
|
||||
|
||||
buf[len] = '\0';
|
||||
lwsl_debug("check %s vs %s\n", buf, name);
|
||||
|
||||
if (strcmp((const char *)buf, name))
|
||||
goto next;
|
||||
|
||||
/* we found a match */
|
||||
if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->hdr.offset) < 0)
|
||||
return LWS_FZ_ERR_NAME_SEEK;
|
||||
if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
|
||||
&amount, buf,
|
||||
ZL_HEADER_LENGTH))
|
||||
return LWS_FZ_ERR_NAME_READ;
|
||||
if (amount != ZL_HEADER_LENGTH)
|
||||
return LWS_FZ_ERR_NAME_READ;
|
||||
|
||||
priv->content_start = priv->hdr.offset +
|
||||
ZL_HEADER_LENGTH +
|
||||
priv->hdr.filename_len +
|
||||
get_u16(buf + ZL_REL_OFFSET_CONTENT);
|
||||
|
||||
lwsl_debug("content supposed to start at 0x%lx\n",
|
||||
(unsigned long)priv->content_start);
|
||||
|
||||
if (priv->content_start > priv->zip_fop_fd->len)
|
||||
return LWS_FZ_ERR_CONTENT_SANITY;
|
||||
|
||||
if (lws_vfs_file_seek_set(priv->zip_fop_fd,
|
||||
priv->content_start) < 0)
|
||||
return LWS_FZ_ERR_CONTENT_SEEK;
|
||||
|
||||
/* we are aligned at the start of the content */
|
||||
|
||||
priv->exp_uncomp_pos = 0;
|
||||
|
||||
return 0;
|
||||
|
||||
next:
|
||||
if (i && lws_vfs_file_seek_set(priv->zip_fop_fd,
|
||||
priv->content_start +
|
||||
ZC_DIRECTORY_LENGTH +
|
||||
priv->hdr.filename_len +
|
||||
priv->hdr.extra +
|
||||
priv->hdr.file_com_len) < 0)
|
||||
return LWS_FZ_ERR_SCAN_SEEK;
|
||||
}
|
||||
|
||||
return LWS_FZ_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
static int
|
||||
lws_fops_zip_reset_inflate(lws_fops_zip_t priv)
|
||||
{
|
||||
if (priv->decompress)
|
||||
inflateEnd(&priv->inflate);
|
||||
|
||||
priv->inflate.zalloc = Z_NULL;
|
||||
priv->inflate.zfree = Z_NULL;
|
||||
priv->inflate.opaque = Z_NULL;
|
||||
priv->inflate.avail_in = 0;
|
||||
priv->inflate.next_in = Z_NULL;
|
||||
|
||||
if (inflateInit2(&priv->inflate, -MAX_WBITS) != Z_OK) {
|
||||
lwsl_err("inflate init failed\n");
|
||||
return LWS_FZ_ERR_ZLIB_INIT;
|
||||
}
|
||||
|
||||
if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->content_start) < 0)
|
||||
return LWS_FZ_ERR_CONTENT_SEEK;
|
||||
|
||||
priv->exp_uncomp_pos = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static lws_fop_fd_t
|
||||
lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
|
||||
const char *vpath, lws_fop_flags_t *flags)
|
||||
{
|
||||
lws_fop_flags_t local_flags = 0;
|
||||
lws_fops_zip_t priv;
|
||||
char rp[192];
|
||||
int m;
|
||||
|
||||
/*
|
||||
* vpath points at the / after the fops signature in vfs_path, eg
|
||||
* with a vfs_path "/var/www/docs/manual.zip/index.html", vpath
|
||||
* will come pointing at "/index.html"
|
||||
*/
|
||||
|
||||
priv = lws_zalloc(sizeof(*priv));
|
||||
if (!priv)
|
||||
return NULL;
|
||||
|
||||
priv->fop_fd.fops = &fops_zip;
|
||||
|
||||
m = sizeof(rp) - 1;
|
||||
if ((vpath - vfs_path - 1) < m)
|
||||
m = vpath - vfs_path - 1;
|
||||
strncpy(rp, vfs_path, m);
|
||||
rp[m] = '\0';
|
||||
|
||||
/* open the zip file itself using the incoming fops, not fops_zip */
|
||||
|
||||
priv->zip_fop_fd = fops->LWS_FOP_OPEN(fops, rp, NULL, &local_flags);
|
||||
if (!priv->zip_fop_fd) {
|
||||
lwsl_err("unable to open zip %s\n", rp);
|
||||
goto bail1;
|
||||
}
|
||||
|
||||
if (*vpath == '/')
|
||||
vpath++;
|
||||
|
||||
m = lws_fops_zip_scan(priv, vpath, strlen(vpath));
|
||||
if (m) {
|
||||
lwsl_err("unable to find record matching '%s' %d\n", vpath, m);
|
||||
goto bail2;
|
||||
}
|
||||
|
||||
/* the directory metadata tells us modification time, so pass it on */
|
||||
priv->fop_fd.mod_time = priv->hdr.mod_time;
|
||||
*flags |= LWS_FOP_FLAG_MOD_TIME_VALID | LWS_FOP_FLAG_VIRTUAL;
|
||||
priv->fop_fd.flags = *flags;
|
||||
|
||||
/* The zip fop_fd is left pointing at the start of the content.
|
||||
*
|
||||
* 1) Content could be uncompressed (STORE), and we can always serve
|
||||
* that directly
|
||||
*
|
||||
* 2) Content could be compressed (GZIP), and the client can handle
|
||||
* receiving GZIP... we can wrap it in a GZIP header and trailer
|
||||
* and serve the content part directly. The flag indicating we
|
||||
* are providing GZIP directly is set so lws will send the right
|
||||
* headers.
|
||||
*
|
||||
* 3) Content could be compressed (GZIP) but the client can't handle
|
||||
* receiving GZIP... we can decompress it and serve as it is
|
||||
* inflated piecemeal.
|
||||
*
|
||||
* 4) Content may be compressed some unknown way... fail
|
||||
*
|
||||
*/
|
||||
if (priv->hdr.method == ZIP_COMPRESSION_METHOD_STORE) {
|
||||
/*
|
||||
* it is stored uncompressed, leave it indicated as
|
||||
* uncompressed, and just serve it from inside the
|
||||
* zip with no gzip container;
|
||||
*/
|
||||
|
||||
lwsl_info("direct zip serving (stored)\n");
|
||||
|
||||
priv->fop_fd.len = priv->hdr.uncomp_size;
|
||||
|
||||
return &priv->fop_fd;
|
||||
}
|
||||
|
||||
if ((*flags & LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP) &&
|
||||
priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
|
||||
|
||||
/*
|
||||
* We can serve the gzipped file contents directly as gzip
|
||||
* from inside the zip container; client says it is OK.
|
||||
*
|
||||
* To convert to standalone gzip, we have to add a 10-byte
|
||||
* constant header and a variable 8-byte trailer around the
|
||||
* content.
|
||||
*
|
||||
* The 8-byte trailer is prepared now and held in the priv.
|
||||
*/
|
||||
|
||||
lwsl_info("direct zip serving (gzipped)\n");
|
||||
|
||||
priv->fop_fd.len = sizeof(hd) + priv->hdr.comp_size +
|
||||
sizeof(priv->u);
|
||||
|
||||
if (lws_is_be()) {
|
||||
uint8_t *p = priv->u.trailer8;
|
||||
|
||||
*p++ = (uint8_t)priv->hdr.crc32;
|
||||
*p++ = (uint8_t)(priv->hdr.crc32 >> 8);
|
||||
*p++ = (uint8_t)(priv->hdr.crc32 >> 16);
|
||||
*p++ = (uint8_t)(priv->hdr.crc32 >> 24);
|
||||
*p++ = (uint8_t)priv->hdr.uncomp_size;
|
||||
*p++ = (uint8_t)(priv->hdr.uncomp_size >> 8);
|
||||
*p++ = (uint8_t)(priv->hdr.uncomp_size >> 16);
|
||||
*p = (uint8_t)(priv->hdr.uncomp_size >> 24);
|
||||
} else {
|
||||
priv->u.trailer32[0] = priv->hdr.crc32;
|
||||
priv->u.trailer32[1] = priv->hdr.uncomp_size;
|
||||
}
|
||||
|
||||
*flags |= LWS_FOP_FLAG_COMPR_IS_GZIP;
|
||||
priv->fop_fd.flags = *flags;
|
||||
priv->add_gzip_container = 1;
|
||||
|
||||
return &priv->fop_fd;
|
||||
}
|
||||
|
||||
if (priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
|
||||
|
||||
/* we must decompress it to serve it */
|
||||
|
||||
lwsl_info("decompressed zip serving\n");
|
||||
|
||||
priv->fop_fd.len = priv->hdr.uncomp_size;
|
||||
|
||||
if (lws_fops_zip_reset_inflate(priv)) {
|
||||
lwsl_err("inflate init failed\n");
|
||||
goto bail2;
|
||||
}
|
||||
|
||||
priv->decompress = 1;
|
||||
|
||||
return &priv->fop_fd;
|
||||
}
|
||||
|
||||
/* we can't handle it ... */
|
||||
|
||||
lwsl_err("zipped file %s compressed in unknown way (%d)\n", vfs_path,
|
||||
priv->hdr.method);
|
||||
|
||||
bail2:
|
||||
lws_vfs_file_close(&priv->zip_fop_fd);
|
||||
bail1:
|
||||
free(priv);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ie, we are closing the fop_fd for the file inside the gzip */
|
||||
|
||||
static int
|
||||
lws_fops_zip_close(lws_fop_fd_t *fd)
|
||||
{
|
||||
lws_fops_zip_t priv = fop_fd_to_priv(*fd);
|
||||
|
||||
if (priv->decompress)
|
||||
inflateEnd(&priv->inflate);
|
||||
|
||||
lws_vfs_file_close(&priv->zip_fop_fd); /* close the gzip fop_fd */
|
||||
|
||||
free(priv);
|
||||
*fd = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static lws_fileofs_t
|
||||
lws_fops_zip_seek_cur(lws_fop_fd_t fd, lws_fileofs_t offset_from_cur_pos)
|
||||
{
|
||||
fd->pos += offset_from_cur_pos;
|
||||
|
||||
return fd->pos;
|
||||
}
|
||||
|
||||
static int
|
||||
lws_fops_zip_read(lws_fop_fd_t fd, lws_filepos_t *amount, uint8_t *buf,
|
||||
lws_filepos_t len)
|
||||
{
|
||||
lws_fops_zip_t priv = fop_fd_to_priv(fd);
|
||||
lws_filepos_t ramount, rlen, cur = lws_vfs_tell(fd);
|
||||
int ret;
|
||||
|
||||
if (priv->decompress) {
|
||||
|
||||
if (priv->exp_uncomp_pos != fd->pos) {
|
||||
/*
|
||||
* there has been a seek in the uncompressed fop_fd
|
||||
* we have to restart the decompression and loop eating
|
||||
* the decompressed data up to the seek point
|
||||
*/
|
||||
lwsl_info("seek in decompressed\n");
|
||||
|
||||
lws_fops_zip_reset_inflate(priv);
|
||||
|
||||
while (priv->exp_uncomp_pos != fd->pos) {
|
||||
rlen = len;
|
||||
if (rlen > fd->pos - priv->exp_uncomp_pos)
|
||||
rlen = fd->pos - priv->exp_uncomp_pos;
|
||||
if (lws_fops_zip_read(fd, amount, buf, rlen))
|
||||
return LWS_FZ_ERR_SEEK_COMPRESSED;
|
||||
}
|
||||
*amount = 0;
|
||||
}
|
||||
|
||||
priv->inflate.avail_out = (unsigned int)len;
|
||||
priv->inflate.next_out = buf;
|
||||
|
||||
spin:
|
||||
if (!priv->inflate.avail_in) {
|
||||
rlen = sizeof(priv->rbuf);
|
||||
if (rlen > priv->hdr.comp_size -
|
||||
(cur - priv->content_start))
|
||||
rlen = priv->hdr.comp_size -
|
||||
(priv->hdr.comp_size -
|
||||
priv->content_start);
|
||||
|
||||
if (priv->zip_fop_fd->fops->LWS_FOP_READ(
|
||||
priv->zip_fop_fd, &ramount, priv->rbuf,
|
||||
rlen))
|
||||
return LWS_FZ_ERR_READ_CONTENT;
|
||||
|
||||
cur += ramount;
|
||||
|
||||
priv->inflate.avail_in = (unsigned int)ramount;
|
||||
priv->inflate.next_in = priv->rbuf;
|
||||
}
|
||||
|
||||
ret = inflate(&priv->inflate, Z_NO_FLUSH);
|
||||
if (ret == Z_STREAM_ERROR)
|
||||
return ret;
|
||||
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
ret = Z_DATA_ERROR;
|
||||
/* and fall through */
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!priv->inflate.avail_in && priv->inflate.avail_out &&
|
||||
cur != priv->content_start + priv->hdr.comp_size)
|
||||
goto spin;
|
||||
|
||||
*amount = len - priv->inflate.avail_out;
|
||||
|
||||
priv->exp_uncomp_pos += *amount;
|
||||
fd->pos += *amount;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (priv->add_gzip_container) {
|
||||
|
||||
lwsl_info("%s: gzip + container\n", __func__);
|
||||
*amount = 0;
|
||||
|
||||
/* place the canned header at the start */
|
||||
|
||||
if (len && fd->pos < sizeof(hd)) {
|
||||
rlen = sizeof(hd) - fd->pos;
|
||||
if (rlen > len)
|
||||
rlen = len;
|
||||
/* provide stuff from canned header */
|
||||
memcpy(buf, hd + fd->pos, (size_t)rlen);
|
||||
fd->pos += rlen;
|
||||
buf += rlen;
|
||||
len -= rlen;
|
||||
*amount += rlen;
|
||||
}
|
||||
|
||||
/* serve gzipped data direct from zipfile */
|
||||
|
||||
if (len && fd->pos >= sizeof(hd) &&
|
||||
fd->pos < priv->hdr.comp_size + sizeof(hd)) {
|
||||
|
||||
rlen = priv->hdr.comp_size - (priv->zip_fop_fd->pos -
|
||||
priv->content_start);
|
||||
if (rlen > len)
|
||||
rlen = len;
|
||||
|
||||
if (rlen &&
|
||||
priv->zip_fop_fd->pos < (priv->hdr.comp_size +
|
||||
priv->content_start)) {
|
||||
if (lws_vfs_file_read(priv->zip_fop_fd,
|
||||
&ramount, buf, rlen))
|
||||
return LWS_FZ_ERR_READ_CONTENT;
|
||||
*amount += ramount;
|
||||
fd->pos += ramount; // virtual pos
|
||||
buf += ramount;
|
||||
len -= ramount;
|
||||
}
|
||||
}
|
||||
|
||||
/* place the prepared trailer at the end */
|
||||
|
||||
if (len && fd->pos >= priv->hdr.comp_size + sizeof(hd) &&
|
||||
fd->pos < priv->hdr.comp_size + sizeof(hd) +
|
||||
sizeof(priv->u)) {
|
||||
cur = fd->pos - priv->hdr.comp_size - sizeof(hd);
|
||||
rlen = sizeof(priv->u) - cur;
|
||||
if (rlen > len)
|
||||
rlen = len;
|
||||
|
||||
memcpy(buf, priv->u.trailer8 + cur, (size_t)rlen);
|
||||
|
||||
*amount += rlen;
|
||||
fd->pos += rlen;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
lwsl_info("%s: store\n", __func__);
|
||||
|
||||
if (len > priv->hdr.uncomp_size - (cur - priv->content_start))
|
||||
len = priv->hdr.comp_size - (priv->hdr.comp_size -
|
||||
priv->content_start);
|
||||
|
||||
if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
|
||||
amount, buf, len))
|
||||
return LWS_FZ_ERR_READ_CONTENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct lws_plat_file_ops fops_zip = {
|
||||
lws_fops_zip_open,
|
||||
lws_fops_zip_close,
|
||||
lws_fops_zip_seek_cur,
|
||||
lws_fops_zip_read,
|
||||
NULL,
|
||||
{ { ".zip/", 5 }, { ".jar/", 5 }, { ".war/", 5 } },
|
||||
NULL,
|
||||
};
|
|
@ -61,8 +61,7 @@
|
|||
#include "getifaddrs.h"
|
||||
|
||||
static int
|
||||
getifaddrs2(struct ifaddrs **ifap,
|
||||
int af, int siocgifconf, int siocgifflags,
|
||||
getifaddrs2(struct ifaddrs **ifap, int af, int siocgifconf, int siocgifflags,
|
||||
size_t ifreq_sz)
|
||||
{
|
||||
int ret;
|
||||
|
@ -74,7 +73,6 @@ getifaddrs2(struct ifaddrs **ifap,
|
|||
size_t sz;
|
||||
struct sockaddr sa_zero;
|
||||
struct ifreq *ifr;
|
||||
|
||||
struct ifaddrs *start, **end = &start;
|
||||
|
||||
buf = NULL;
|
||||
|
@ -231,7 +229,7 @@ print_addr(const char *s, struct sockaddr *sa)
|
|||
printf(" %s=%d/", s, sa->sa_family);
|
||||
#ifdef LWS_HAVE_STRUCT_SOCKADDR_SA_LEN
|
||||
for (i = 0;
|
||||
i < sa->sa_len - ((long)sa->sa_data - (long)&sa->sa_family); i++)
|
||||
i < sa->sa_len - ((lws_intptr_t)sa->sa_data - (lws_intptr_t)&sa->sa_family); i++)
|
||||
printf("%02x", ((unsigned char *)sa->sa_data)[i]);
|
||||
#else
|
||||
for (i = 0; i < sizeof(sa->sa_data); i++)
|
||||
|
@ -262,8 +260,8 @@ int
|
|||
main()
|
||||
{
|
||||
struct ifaddrs *a = NULL, *b;
|
||||
getifaddrs2(&a, AF_INET, SIOCGIFCONF,
|
||||
SIOCGIFFLAGS, sizeof(struct ifreq));
|
||||
getifaddrs2(&a, AF_INET, SIOCGIFCONF, SIOCGIFFLAGS,
|
||||
sizeof(struct ifreq));
|
||||
print_ifaddrs(a);
|
||||
printf("---\n");
|
||||
getifaddrs(&b);
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
#ifndef LWS_HAVE_GETIFADDRS
|
||||
#define LWS_HAVE_GETIFADDRS 0
|
||||
#endif
|
||||
|
||||
#if LWS_HAVE_GETIFADDRS
|
||||
#include <sys/types.h>
|
||||
#include <ifaddrs.h>
|
||||
|
|
198
lib/handshake.c
198
lib/handshake.c
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010-2013 Andy Green <andy@warmcat.com>
|
||||
* Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
|
@ -49,26 +49,30 @@
|
|||
#ifndef min
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We have to take care about parsing because the headers may be split
|
||||
* into multiple fragments. They may contain unknown headers with arbitrary
|
||||
* argument lengths. So, we parse using a single-character at a time state
|
||||
* machine that is completely independent of packet size.
|
||||
*
|
||||
* Returns <0 for error or length of chars consumed from buf (up to len)
|
||||
*/
|
||||
|
||||
LWS_VISIBLE int
|
||||
libwebsocket_read(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, unsigned char *buf, size_t len)
|
||||
lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
|
||||
{
|
||||
unsigned char *last_char, *oldbuf = buf;
|
||||
lws_filepos_t body_chunk_len;
|
||||
size_t n;
|
||||
int body_chunk_len;
|
||||
unsigned char *last_char;
|
||||
|
||||
lwsl_debug("%s: incoming len %d state %d\n", __func__, (int)len, wsi->state);
|
||||
|
||||
switch (wsi->state) {
|
||||
#ifdef LWS_USE_HTTP2
|
||||
case WSI_STATE_HTTP2_AWAIT_CLIENT_PREFACE:
|
||||
case WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS:
|
||||
case WSI_STATE_HTTP2_ESTABLISHED:
|
||||
case LWSS_HTTP2_AWAIT_CLIENT_PREFACE:
|
||||
case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS:
|
||||
case LWSS_HTTP2_ESTABLISHED:
|
||||
n = 0;
|
||||
while (n < len) {
|
||||
/*
|
||||
|
@ -83,55 +87,78 @@ libwebsocket_read(struct libwebsocket_context *context,
|
|||
/* account for what we're using in rxflow buffer */
|
||||
if (wsi->rxflow_buffer)
|
||||
wsi->rxflow_pos++;
|
||||
if (lws_http2_parser(context, wsi, buf[n++]))
|
||||
if (lws_http2_parser(wsi, buf[n++])) {
|
||||
lwsl_debug("%s: http2_parser bailed\n", __func__);
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
http_new:
|
||||
case WSI_STATE_HTTP:
|
||||
|
||||
case LWSS_HTTP_ISSUING_FILE:
|
||||
return 0;
|
||||
|
||||
case LWSS_CLIENT_HTTP_ESTABLISHED:
|
||||
break;
|
||||
|
||||
case LWSS_HTTP:
|
||||
wsi->hdr_parsing_completed = 0;
|
||||
/* fallthru */
|
||||
case WSI_STATE_HTTP_ISSUING_FILE:
|
||||
wsi->state = WSI_STATE_HTTP_HEADERS;
|
||||
wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART;
|
||||
wsi->u.hdr.lextable_pos = 0;
|
||||
/* fallthru */
|
||||
case WSI_STATE_HTTP_HEADERS:
|
||||
|
||||
case LWSS_HTTP_HEADERS:
|
||||
if (!wsi->u.hdr.ah) {
|
||||
lwsl_err("%s: LWSS_HTTP_HEADERS: NULL ah\n", __func__);
|
||||
assert(0);
|
||||
}
|
||||
lwsl_parser("issuing %d bytes to parser\n", (int)len);
|
||||
|
||||
if (lws_handshake_client(wsi, &buf, len))
|
||||
if (lws_handshake_client(wsi, &buf, (size_t)len))
|
||||
goto bail;
|
||||
|
||||
last_char = buf;
|
||||
if (lws_handshake_server(context, wsi, &buf, len))
|
||||
if (lws_handshake_server(wsi, &buf, (size_t)len))
|
||||
/* Handshake indicates this session is done. */
|
||||
goto bail;
|
||||
|
||||
/* It's possible that we've exhausted our data already, but
|
||||
* lws_handshake_server doesn't update len for us. Figure out how
|
||||
* much was read, so that we can proceed appropriately: */
|
||||
/* we might have transitioned to RAW */
|
||||
if (wsi->mode == LWSCM_RAW)
|
||||
/* we gave the read buffer to RAW handler already */
|
||||
goto read_ok;
|
||||
|
||||
/*
|
||||
* It's possible that we've exhausted our data already, or
|
||||
* rx flow control has stopped us dealing with this early,
|
||||
* but lws_handshake_server doesn't update len for us.
|
||||
* Figure out how much was read, so that we can proceed
|
||||
* appropriately:
|
||||
*/
|
||||
len -= (buf - last_char);
|
||||
lwsl_debug("%s: thinks we have used %ld\n", __func__, (long)len);
|
||||
|
||||
if (!wsi->hdr_parsing_completed)
|
||||
/* More header content on the way */
|
||||
goto read_ok;
|
||||
|
||||
switch (wsi->state) {
|
||||
case WSI_STATE_HTTP:
|
||||
case WSI_STATE_HTTP_HEADERS:
|
||||
goto http_complete;
|
||||
case WSI_STATE_HTTP_ISSUING_FILE:
|
||||
case LWSS_HTTP:
|
||||
case LWSS_HTTP_HEADERS:
|
||||
goto read_ok;
|
||||
case WSI_STATE_HTTP_BODY:
|
||||
wsi->u.http.content_remain = wsi->u.http.content_length;
|
||||
goto http_postbody;
|
||||
case LWSS_HTTP_ISSUING_FILE:
|
||||
goto read_ok;
|
||||
case LWSS_HTTP_BODY:
|
||||
wsi->u.http.content_remain =
|
||||
wsi->u.http.content_length;
|
||||
if (wsi->u.http.content_remain)
|
||||
goto http_postbody;
|
||||
|
||||
/* there is no POST content */
|
||||
goto postbody_completion;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case WSI_STATE_HTTP_BODY:
|
||||
case LWSS_HTTP_BODY:
|
||||
http_postbody:
|
||||
while (len && wsi->u.http.content_remain) {
|
||||
/* Copy as much as possible, up to the limit of:
|
||||
|
@ -141,44 +168,75 @@ http_postbody:
|
|||
body_chunk_len = min(wsi->u.http.content_remain,len);
|
||||
wsi->u.http.content_remain -= body_chunk_len;
|
||||
len -= body_chunk_len;
|
||||
#ifdef LWS_WITH_CGI
|
||||
if (wsi->cgi) {
|
||||
struct lws_cgi_args args;
|
||||
|
||||
if (wsi->protocol->callback) {
|
||||
n = wsi->protocol->callback(
|
||||
wsi->protocol->owning_server, wsi,
|
||||
args.ch = LWS_STDIN;
|
||||
args.stdwsi = &wsi->cgi->stdwsi[0];
|
||||
args.data = buf;
|
||||
args.len = body_chunk_len;
|
||||
|
||||
/* returns how much used */
|
||||
n = user_callback_handle_rxflow(
|
||||
wsi->protocol->callback,
|
||||
wsi, LWS_CALLBACK_CGI_STDIN_DATA,
|
||||
wsi->user_space,
|
||||
(void *)&args, 0);
|
||||
if ((int)n < 0)
|
||||
goto bail;
|
||||
} else {
|
||||
#endif
|
||||
n = wsi->protocol->callback(wsi,
|
||||
LWS_CALLBACK_HTTP_BODY, wsi->user_space,
|
||||
buf, body_chunk_len);
|
||||
buf, (size_t)body_chunk_len);
|
||||
if (n)
|
||||
goto bail;
|
||||
n = (size_t)body_chunk_len;
|
||||
#ifdef LWS_WITH_CGI
|
||||
}
|
||||
#endif
|
||||
buf += n;
|
||||
|
||||
if (wsi->u.http.content_remain) {
|
||||
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
|
||||
wsi->context->timeout_secs);
|
||||
break;
|
||||
}
|
||||
/* he sent all the content in time */
|
||||
postbody_completion:
|
||||
#ifdef LWS_WITH_CGI
|
||||
/* if we're running a cgi, we can't let him off the hook just because he sent his POST data */
|
||||
if (wsi->cgi)
|
||||
lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, wsi->context->timeout_secs);
|
||||
else
|
||||
#endif
|
||||
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
||||
#ifdef LWS_WITH_CGI
|
||||
if (!wsi->cgi)
|
||||
#endif
|
||||
{
|
||||
n = wsi->protocol->callback(wsi,
|
||||
LWS_CALLBACK_HTTP_BODY_COMPLETION,
|
||||
wsi->user_space, NULL, 0);
|
||||
if (n)
|
||||
goto bail;
|
||||
}
|
||||
buf += body_chunk_len;
|
||||
|
||||
if (!wsi->u.http.content_remain) {
|
||||
/* he sent the content in time */
|
||||
libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
||||
if (wsi->protocol->callback) {
|
||||
n = wsi->protocol->callback(
|
||||
wsi->protocol->owning_server, wsi,
|
||||
LWS_CALLBACK_HTTP_BODY_COMPLETION,
|
||||
wsi->user_space, NULL, 0);
|
||||
if (n)
|
||||
goto bail;
|
||||
}
|
||||
goto http_complete;
|
||||
} else
|
||||
libwebsocket_set_timeout(wsi,
|
||||
PENDING_TIMEOUT_HTTP_CONTENT,
|
||||
AWAITING_TIMEOUT);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case WSI_STATE_ESTABLISHED:
|
||||
case WSI_STATE_AWAITING_CLOSE_ACK:
|
||||
if (lws_handshake_client(wsi, &buf, len))
|
||||
case LWSS_ESTABLISHED:
|
||||
case LWSS_AWAITING_CLOSE_ACK:
|
||||
case LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION:
|
||||
case LWSS_SHUTDOWN:
|
||||
if (lws_handshake_client(wsi, &buf, (size_t)len))
|
||||
goto bail;
|
||||
switch (wsi->mode) {
|
||||
case LWS_CONNMODE_WS_SERVING:
|
||||
case LWSCM_WS_SERVING:
|
||||
|
||||
if (libwebsocket_interpret_incoming_packet(wsi, buf, len) < 0) {
|
||||
if (lws_interpret_incoming_packet(wsi, &buf, (size_t)len) < 0) {
|
||||
lwsl_info("interpret_incoming_packet has bailed\n");
|
||||
goto bail;
|
||||
}
|
||||
|
@ -186,35 +244,19 @@ http_postbody:
|
|||
}
|
||||
break;
|
||||
default:
|
||||
lwsl_err("libwebsocket_read: Unhandled state\n");
|
||||
lwsl_err("%s: Unhandled state %d\n", __func__, wsi->state);
|
||||
break;
|
||||
}
|
||||
|
||||
read_ok:
|
||||
/* Nothing more to do for now. */
|
||||
lwsl_debug("libwebsocket_read: read_ok\n");
|
||||
/* Nothing more to do for now */
|
||||
lwsl_info("%s: read_ok, used %ld\n", __func__, (long)(buf - oldbuf));
|
||||
|
||||
return 0;
|
||||
|
||||
http_complete:
|
||||
lwsl_debug("libwebsocket_read: http_complete\n");
|
||||
|
||||
#ifndef LWS_NO_SERVER
|
||||
/* Did the client want to keep the HTTP connection going? */
|
||||
if (lws_http_transaction_completed(wsi))
|
||||
goto bail;
|
||||
#endif
|
||||
/* If we have more data, loop back around: */
|
||||
if (len)
|
||||
goto http_new;
|
||||
|
||||
return 0;
|
||||
return buf - oldbuf;
|
||||
|
||||
bail:
|
||||
lwsl_debug("closing connection at libwebsocket_read bail:\n");
|
||||
|
||||
libwebsocket_close_and_free_session(context, wsi,
|
||||
LWS_CLOSE_STATUS_NOSTATUS);
|
||||
//lwsl_notice("closing connection at lws_read bail:\n");
|
||||
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
|
254
lib/header.c
254
lib/header.c
|
@ -20,26 +20,29 @@
|
|||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
|
||||
#include "lextable-strings.h"
|
||||
|
||||
|
||||
const unsigned char *lws_token_to_string(enum lws_token_indexes token)
|
||||
{
|
||||
if ((unsigned int)token >= ARRAY_SIZE(set))
|
||||
return NULL;
|
||||
|
||||
return (unsigned char *)set[token];
|
||||
}
|
||||
|
||||
int lws_add_http_header_by_name(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi,
|
||||
const unsigned char *name,
|
||||
const unsigned char *value,
|
||||
int length,
|
||||
unsigned char **p,
|
||||
unsigned char *end)
|
||||
int
|
||||
lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name,
|
||||
const unsigned char *value, int length,
|
||||
unsigned char **p, unsigned char *end)
|
||||
{
|
||||
#ifdef LWS_USE_HTTP2
|
||||
if (wsi->mode == LWS_CONNMODE_HTTP2_SERVING)
|
||||
return lws_add_http2_header_by_name(context, wsi, name, value, length, p, end);
|
||||
if (wsi->mode == LWSCM_HTTP2_SERVING)
|
||||
return lws_add_http2_header_by_name(wsi, name,
|
||||
value, length, p, end);
|
||||
#else
|
||||
(void)wsi;
|
||||
#endif
|
||||
if (name) {
|
||||
while (*p < end && *name)
|
||||
|
@ -55,55 +58,53 @@ int lws_add_http_header_by_name(struct libwebsocket_context *context,
|
|||
*p += length;
|
||||
*((*p)++) = '\x0d';
|
||||
*((*p)++) = '\x0a';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lws_finalize_http_header(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi,
|
||||
unsigned char **p,
|
||||
unsigned char *end)
|
||||
int lws_finalize_http_header(struct lws *wsi, unsigned char **p,
|
||||
unsigned char *end)
|
||||
{
|
||||
#ifdef LWS_USE_HTTP2
|
||||
if (wsi->mode == LWS_CONNMODE_HTTP2_SERVING)
|
||||
if (wsi->mode == LWSCM_HTTP2_SERVING)
|
||||
return 0;
|
||||
#else
|
||||
(void)wsi;
|
||||
#endif
|
||||
if ((long)(end - *p) < 3)
|
||||
if ((lws_intptr_t)(end - *p) < 3)
|
||||
return 1;
|
||||
*((*p)++) = '\x0d';
|
||||
*((*p)++) = '\x0a';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lws_add_http_header_by_token(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi,
|
||||
enum lws_token_indexes token,
|
||||
const unsigned char *value,
|
||||
int length,
|
||||
unsigned char **p,
|
||||
unsigned char *end)
|
||||
int
|
||||
lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token,
|
||||
const unsigned char *value, int length,
|
||||
unsigned char **p, unsigned char *end)
|
||||
{
|
||||
const unsigned char *name;
|
||||
#ifdef LWS_USE_HTTP2
|
||||
if (wsi->mode == LWS_CONNMODE_HTTP2_SERVING)
|
||||
return lws_add_http2_header_by_token(context, wsi, token, value, length, p, end);
|
||||
if (wsi->mode == LWSCM_HTTP2_SERVING)
|
||||
return lws_add_http2_header_by_token(wsi, token, value, length, p, end);
|
||||
#endif
|
||||
name = lws_token_to_string(token);
|
||||
if (!name)
|
||||
return 1;
|
||||
return lws_add_http_header_by_name(context, wsi, name, value, length, p, end);
|
||||
return lws_add_http_header_by_name(wsi, name, value, length, p, end);
|
||||
}
|
||||
|
||||
int lws_add_http_header_content_length(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi,
|
||||
unsigned long content_length,
|
||||
unsigned char **p,
|
||||
unsigned char *end)
|
||||
int lws_add_http_header_content_length(struct lws *wsi,
|
||||
lws_filepos_t content_length,
|
||||
unsigned char **p, unsigned char *end)
|
||||
{
|
||||
char b[24];
|
||||
int n;
|
||||
|
||||
n = sprintf(b, "%lu", content_length);
|
||||
if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)b, n, p, end))
|
||||
n = sprintf(b, "%llu", (unsigned long long)content_length);
|
||||
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH,
|
||||
(unsigned char *)b, n, p, end))
|
||||
return 1;
|
||||
wsi->u.http.content_length = content_length;
|
||||
wsi->u.http.content_remain = content_length;
|
||||
|
@ -111,7 +112,7 @@ int lws_add_http_header_content_length(struct libwebsocket_context *context,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const char *err400[] = {
|
||||
STORE_IN_ROM static const char * const err400[] = {
|
||||
"Bad Request",
|
||||
"Unauthorized",
|
||||
"Payment Required",
|
||||
|
@ -132,7 +133,7 @@ static const char *err400[] = {
|
|||
"Expectation Failed"
|
||||
};
|
||||
|
||||
static const char *err500[] = {
|
||||
STORE_IN_ROM static const char * const err500[] = {
|
||||
"Internal Server Error",
|
||||
"Not Implemented",
|
||||
"Bad Gateway",
|
||||
|
@ -141,69 +142,176 @@ static const char *err500[] = {
|
|||
"HTTP Version Not Supported"
|
||||
};
|
||||
|
||||
int lws_add_http_header_status(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi,
|
||||
unsigned int code,
|
||||
unsigned char **p,
|
||||
unsigned char *end)
|
||||
int
|
||||
lws_add_http_header_status(struct lws *wsi, unsigned int _code,
|
||||
unsigned char **p, unsigned char *end)
|
||||
{
|
||||
STORE_IN_ROM static const char * const hver[] = {
|
||||
"HTTP/1.0", "HTTP/1.1", "HTTP/2"
|
||||
};
|
||||
const struct lws_protocol_vhost_options *headers;
|
||||
unsigned int code = _code & LWSAHH_CODE_MASK;
|
||||
const char *description = "", *p1;
|
||||
unsigned char code_and_desc[60];
|
||||
const char *description = "";
|
||||
int n;
|
||||
|
||||
#ifdef LWS_WITH_ACCESS_LOG
|
||||
wsi->access_log.response = code;
|
||||
#endif
|
||||
|
||||
#ifdef LWS_USE_HTTP2
|
||||
if (wsi->mode == LWS_CONNMODE_HTTP2_SERVING)
|
||||
return lws_add_http2_header_status(context, wsi, code, p, end);
|
||||
if (wsi->mode == LWSCM_HTTP2_SERVING)
|
||||
return lws_add_http2_header_status(wsi, code, p, end);
|
||||
#endif
|
||||
if (code >= 400 && code < (400 + ARRAY_SIZE(err400)))
|
||||
description = err400[code - 400];
|
||||
if (code >= 500 && code < (500 + ARRAY_SIZE(err500)))
|
||||
description = err500[code - 500];
|
||||
|
||||
n = sprintf((char *)code_and_desc, "HTTP/1.0 %u %s", code, description);
|
||||
if (code == 200)
|
||||
description = "OK";
|
||||
|
||||
return lws_add_http_header_by_name(context, wsi, NULL, code_and_desc, n, p, end);
|
||||
if (code == 304)
|
||||
description = "Not Modified";
|
||||
else
|
||||
if (code >= 300 && code < 400)
|
||||
description = "Redirect";
|
||||
|
||||
if (wsi->u.http.request_version < ARRAY_SIZE(hver))
|
||||
p1 = hver[wsi->u.http.request_version];
|
||||
else
|
||||
p1 = hver[0];
|
||||
|
||||
n = sprintf((char *)code_and_desc, "%s %u %s", p1, code, description);
|
||||
|
||||
if (lws_add_http_header_by_name(wsi, NULL, code_and_desc, n, p, end))
|
||||
return 1;
|
||||
|
||||
headers = wsi->vhost->headers;
|
||||
while (headers) {
|
||||
if (lws_add_http_header_by_name(wsi,
|
||||
(const unsigned char *)headers->name,
|
||||
(unsigned char *)headers->value,
|
||||
strlen(headers->value), p, end))
|
||||
return 1;
|
||||
|
||||
headers = headers->next;
|
||||
}
|
||||
|
||||
if (wsi->context->server_string &&
|
||||
!(_code & LWSAHH_FLAG_NO_SERVER_NAME))
|
||||
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER,
|
||||
(unsigned char *)wsi->context->server_string,
|
||||
wsi->context->server_string_len, p, end))
|
||||
return 1;
|
||||
|
||||
if (wsi->vhost->options & LWS_SERVER_OPTION_STS)
|
||||
if (lws_add_http_header_by_name(wsi, (unsigned char *)
|
||||
"Strict-Transport-Security:",
|
||||
(unsigned char *)"max-age=15768000 ; "
|
||||
"includeSubDomains", 36, p, end))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* libwebsockets_return_http_status() - Return simple http status
|
||||
* @context: libwebsockets context
|
||||
* @wsi: Websocket instance (available from user callback)
|
||||
* @code: Status index, eg, 404
|
||||
* @html_body: User-readable HTML description < 1KB, or NULL
|
||||
*
|
||||
* Helper to report HTTP errors back to the client cleanly and
|
||||
* consistently
|
||||
*/
|
||||
LWS_VISIBLE int libwebsockets_return_http_status(
|
||||
struct libwebsocket_context *context, struct libwebsocket *wsi,
|
||||
unsigned int code, const char *html_body)
|
||||
LWS_VISIBLE int
|
||||
lws_return_http_status(struct lws *wsi, unsigned int code,
|
||||
const char *html_body)
|
||||
{
|
||||
int n, m;
|
||||
|
||||
unsigned char *p = context->service_buffer + LWS_SEND_BUFFER_PRE_PADDING;
|
||||
struct lws_context *context = lws_get_context(wsi);
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
unsigned char *p = pt->serv_buf + LWS_PRE;
|
||||
unsigned char *start = p;
|
||||
unsigned char *end = p + sizeof(context->service_buffer) -
|
||||
LWS_SEND_BUFFER_PRE_PADDING;
|
||||
unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE;
|
||||
int n = 0, m, len;
|
||||
char slen[20];
|
||||
|
||||
if (!html_body)
|
||||
html_body = "";
|
||||
|
||||
if (lws_add_http_header_status(context, wsi, code, &p, end))
|
||||
return 1;
|
||||
if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end))
|
||||
return 1;
|
||||
if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end))
|
||||
return 1;
|
||||
if (lws_finalize_http_header(context, wsi, &p, end))
|
||||
if (lws_add_http_header_status(wsi, code, &p, end))
|
||||
return 1;
|
||||
|
||||
m = libwebsocket_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
|
||||
if (m != (int)(p - start))
|
||||
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
|
||||
(unsigned char *)"text/html", 9,
|
||||
&p, end))
|
||||
return 1;
|
||||
|
||||
n = sprintf((char *)start, "<html><body><h1>%u</h1>%s</body></html>", code, html_body);
|
||||
m = libwebsocket_write(wsi, start, n, LWS_WRITE_HTTP);
|
||||
len = 35 + strlen(html_body) + sprintf(slen, "%d", code);
|
||||
n = sprintf(slen, "%d", len);
|
||||
|
||||
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH,
|
||||
(unsigned char *)slen, n,
|
||||
&p, end))
|
||||
return 1;
|
||||
|
||||
if (lws_finalize_http_header(wsi, &p, end))
|
||||
return 1;
|
||||
|
||||
#if defined(LWS_USE_HTTP2)
|
||||
{
|
||||
unsigned char *body = p + 512;
|
||||
|
||||
m = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
|
||||
if (m != (int)(p - start))
|
||||
return 1;
|
||||
|
||||
len = sprintf((char *)body, "<html><body><h1>%u</h1>%s</body></html>",
|
||||
code, html_body);
|
||||
|
||||
n = len;
|
||||
m = lws_write(wsi, body, len, LWS_WRITE_HTTP);
|
||||
}
|
||||
#else
|
||||
p += lws_snprintf((char *)p, end - p - 1,
|
||||
"<html><body><h1>%u</h1>%s</body></html>",
|
||||
code, html_body);
|
||||
|
||||
n = (int)(p - start);
|
||||
m = lws_write(wsi, start, n, LWS_WRITE_HTTP);
|
||||
if (m != n)
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
return m != n;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len,
|
||||
unsigned char **p, unsigned char *end)
|
||||
{
|
||||
unsigned char *start = *p;
|
||||
int n;
|
||||
|
||||
if (lws_add_http_header_status(wsi, code, p, end))
|
||||
return -1;
|
||||
|
||||
if (lws_add_http_header_by_token(wsi,
|
||||
WSI_TOKEN_HTTP_LOCATION,
|
||||
loc, len, p, end))
|
||||
return -1;
|
||||
/*
|
||||
* if we're going with http/1.1 and keepalive,
|
||||
* we have to give fake content metadata so the
|
||||
* client knows we completed the transaction and
|
||||
* it can do the redirect...
|
||||
*/
|
||||
if (lws_add_http_header_by_token(wsi,
|
||||
WSI_TOKEN_HTTP_CONTENT_TYPE,
|
||||
(unsigned char *)"text/html", 9,
|
||||
p, end))
|
||||
return -1;
|
||||
if (lws_add_http_header_by_token(wsi,
|
||||
WSI_TOKEN_HTTP_CONTENT_LENGTH,
|
||||
(unsigned char *)"0", 1, p, end))
|
||||
return -1;
|
||||
|
||||
if (lws_finalize_http_header(wsi, p, end))
|
||||
return -1;
|
||||
|
||||
n = lws_write(wsi, start, *p - start,
|
||||
LWS_WRITE_HTTP_HEADERS);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
|
219
lib/hpack.c
219
lib/hpack.c
|
@ -189,51 +189,55 @@ static int huftable_decode(int pos, char c)
|
|||
return pos + (lextable[q] << 1);
|
||||
}
|
||||
|
||||
static int lws_hpack_update_table_size(struct libwebsocket *wsi, int idx)
|
||||
static int lws_hpack_update_table_size(struct lws *wsi, int idx)
|
||||
{
|
||||
lwsl_info("hpack set table size %d\n", idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lws_frag_start(struct libwebsocket *wsi, int hdr_token_idx)
|
||||
static int lws_frag_start(struct lws *wsi, int hdr_token_idx)
|
||||
{
|
||||
struct allocated_headers * ah = wsi->u.http2.http.ah;
|
||||
|
||||
if (!hdr_token_idx)
|
||||
if (!hdr_token_idx) {
|
||||
lwsl_err("%s: zero hdr_token_idx\n", __func__);
|
||||
return 1;
|
||||
|
||||
if (ah->next_frag_index >= ARRAY_SIZE(ah->frag_index))
|
||||
return 1;
|
||||
|
||||
ah->frags[ah->next_frag_index].offset = ah->pos;
|
||||
ah->frags[ah->next_frag_index].len = 0;
|
||||
ah->frags[ah->next_frag_index].next_frag_index = 0;
|
||||
}
|
||||
|
||||
if (ah->nfrag >= ARRAY_SIZE(ah->frag_index)) {
|
||||
lwsl_err("%s: frag index %d too big\n", __func__, ah->nfrag);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ah->frags[ah->nfrag].offset = ah->pos;
|
||||
ah->frags[ah->nfrag].len = 0;
|
||||
ah->frags[ah->nfrag].nfrag = 0;
|
||||
|
||||
ah->frag_index[hdr_token_idx] = ah->nfrag;
|
||||
|
||||
ah->frag_index[hdr_token_idx] = ah->next_frag_index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lws_frag_append(struct libwebsocket *wsi, unsigned char c)
|
||||
static int lws_frag_append(struct lws *wsi, unsigned char c)
|
||||
{
|
||||
struct allocated_headers * ah = wsi->u.http2.http.ah;
|
||||
|
||||
ah->data[ah->pos++] = c;
|
||||
ah->frags[ah->next_frag_index].len++;
|
||||
|
||||
return ah->pos >= sizeof(ah->data);
|
||||
ah->frags[ah->nfrag].len++;
|
||||
|
||||
return ah->pos >= wsi->context->max_http_header_data;
|
||||
}
|
||||
|
||||
static int lws_frag_end(struct libwebsocket *wsi)
|
||||
static int lws_frag_end(struct lws *wsi)
|
||||
{
|
||||
if (lws_frag_append(wsi, 0))
|
||||
return 1;
|
||||
|
||||
wsi->u.http2.http.ah->next_frag_index++;
|
||||
wsi->u.http2.http.ah->nfrag++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lws_dump_header(struct libwebsocket *wsi, int hdr)
|
||||
static void lws_dump_header(struct lws *wsi, int hdr)
|
||||
{
|
||||
char s[200];
|
||||
int len = lws_hdr_copy(wsi, s, sizeof(s) - 1, hdr);
|
||||
|
@ -241,14 +245,15 @@ static void lws_dump_header(struct libwebsocket *wsi, int hdr)
|
|||
lwsl_info(" hdr tok %d (%s) = '%s'\n", hdr, lws_token_to_string(hdr), s);
|
||||
}
|
||||
|
||||
static int lws_token_from_index(struct libwebsocket *wsi, int index, char **arg, int *len)
|
||||
static int
|
||||
lws_token_from_index(struct lws *wsi, int index, char **arg, int *len)
|
||||
{
|
||||
struct hpack_dynamic_table *dyn;
|
||||
|
||||
|
||||
/* dynamic table only belongs to network wsi */
|
||||
|
||||
|
||||
wsi = lws_http2_get_network_wsi(wsi);
|
||||
|
||||
|
||||
dyn = wsi->u.http2.hpack_dyn_table;
|
||||
|
||||
if (index < ARRAY_SIZE(static_token))
|
||||
|
@ -256,24 +261,25 @@ static int lws_token_from_index(struct libwebsocket *wsi, int index, char **arg,
|
|||
|
||||
if (!dyn)
|
||||
return 0;
|
||||
|
||||
|
||||
index -= ARRAY_SIZE(static_token);
|
||||
if (index >= dyn->num_entries)
|
||||
return 0;
|
||||
|
||||
|
||||
if (arg && len) {
|
||||
*arg = dyn->args + dyn->entries[index].arg_offset;
|
||||
*len = dyn->entries[index].arg_len;
|
||||
}
|
||||
|
||||
|
||||
return dyn->entries[index].token;
|
||||
}
|
||||
|
||||
static int lws_hpack_add_dynamic_header(struct libwebsocket *wsi, int token, char *arg, int len)
|
||||
static int
|
||||
lws_hpack_add_dynamic_header(struct lws *wsi, int token, char *arg, int len)
|
||||
{
|
||||
struct hpack_dynamic_table *dyn;
|
||||
int ret = 1;
|
||||
|
||||
|
||||
wsi = lws_http2_get_network_wsi(wsi);
|
||||
dyn = wsi->u.http2.hpack_dyn_table;
|
||||
|
||||
|
@ -282,7 +288,7 @@ static int lws_hpack_add_dynamic_header(struct libwebsocket *wsi, int token, cha
|
|||
if (!dyn)
|
||||
return 1;
|
||||
wsi->u.http2.hpack_dyn_table = dyn;
|
||||
|
||||
|
||||
dyn->args = lws_malloc(1024);
|
||||
if (!dyn->args)
|
||||
goto bail1;
|
||||
|
@ -292,24 +298,25 @@ static int lws_hpack_add_dynamic_header(struct libwebsocket *wsi, int token, cha
|
|||
goto bail2;
|
||||
dyn->num_entries = 20;
|
||||
}
|
||||
|
||||
|
||||
if (dyn->next == dyn->num_entries)
|
||||
return 1;
|
||||
|
||||
|
||||
if (dyn->args_length - dyn->pos < len)
|
||||
return 1;
|
||||
|
||||
|
||||
dyn->entries[dyn->next].token = token;
|
||||
dyn->entries[dyn->next].arg_offset = dyn->pos;
|
||||
if (len)
|
||||
memcpy(dyn->args + dyn->pos, arg, len);
|
||||
dyn->entries[dyn->next].arg_len = len;
|
||||
|
||||
lwsl_info("%s: added dynamic hdr %d, token %d (%s), len %d\n", __func__, dyn->next, token, lws_token_to_string(token), len);
|
||||
|
||||
|
||||
lwsl_info("%s: added dynamic hdr %d, token %d (%s), len %d\n",
|
||||
__func__, dyn->next, token, lws_token_to_string(token), len);
|
||||
|
||||
dyn->pos += len;
|
||||
dyn->next++;
|
||||
|
||||
|
||||
return 0;
|
||||
|
||||
bail2:
|
||||
|
@ -321,12 +328,13 @@ bail1:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int lws_write_indexed_hdr(struct libwebsocket *wsi, int idx)
|
||||
static int lws_write_indexed_hdr(struct lws *wsi, int idx)
|
||||
{
|
||||
const char *p;
|
||||
int tok = lws_token_from_index(wsi, idx, NULL, 0);
|
||||
|
||||
lwsl_info("writing indexed hdr %d (tok %d '%s')\n", idx, tok, lws_token_to_string(tok));
|
||||
lwsl_info("writing indexed hdr %d (tok %d '%s')\n", idx, tok,
|
||||
lws_token_to_string(tok));
|
||||
|
||||
if (lws_frag_start(wsi, tok))
|
||||
return 1;
|
||||
|
@ -339,19 +347,20 @@ static int lws_write_indexed_hdr(struct libwebsocket *wsi, int idx)
|
|||
}
|
||||
if (lws_frag_end(wsi))
|
||||
return 1;
|
||||
|
||||
|
||||
lws_dump_header(wsi, tok);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lws_hpack_interpret(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, unsigned char c)
|
||||
int lws_hpack_interpret(struct lws *wsi, unsigned char c)
|
||||
{
|
||||
unsigned int prev;
|
||||
unsigned char c1;
|
||||
int n;
|
||||
|
||||
lwsl_debug(" state %d\n", wsi->u.http2.hpack);
|
||||
|
||||
switch (wsi->u.http2.hpack) {
|
||||
case HPKS_OPT_PADDING:
|
||||
wsi->u.http2.padding = c;
|
||||
|
@ -374,17 +383,18 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
|
|||
/* weight */
|
||||
wsi->u.http2.hpack = HPKS_TYPE;
|
||||
break;
|
||||
|
||||
|
||||
case HPKS_TYPE:
|
||||
|
||||
|
||||
if (wsi->u.http2.count > (wsi->u.http2.length - wsi->u.http2.padding)) {
|
||||
lwsl_info("padding eat\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (c & 0x80) { /* indexed header field only */
|
||||
/* just a possibly-extended integer */
|
||||
wsi->u.http2.hpack_type = HPKT_INDEXED_HDR_7;
|
||||
lwsl_debug("HKPS_TYPE setting header_index %d\n", c & 0x7f);
|
||||
wsi->u.http2.header_index = c & 0x7f;
|
||||
if ((c & 0x7f) == 0x7f) {
|
||||
wsi->u.http2.hpack_len = c & 0x7f;
|
||||
|
@ -392,6 +402,7 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
|
|||
wsi->u.http2.hpack = HPKS_IDX_EXT;
|
||||
break;
|
||||
}
|
||||
lwsl_debug("HKPS_TYPE: %d\n", c & 0x7f);
|
||||
if (lws_write_indexed_hdr(wsi, c & 0x7f))
|
||||
return 1;
|
||||
/* stay at same state */
|
||||
|
@ -403,6 +414,7 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
|
|||
* H + possibly-extended value length
|
||||
* literal value
|
||||
*/
|
||||
lwsl_debug("HKPS_TYPE 2 setting header_index %d\n", 0);
|
||||
wsi->u.http2.header_index = 0;
|
||||
if (c == 0x40) { /* literal name */
|
||||
wsi->u.http2.hpack_type = HPKT_LITERAL_HDR_VALUE_INCR;
|
||||
|
@ -418,6 +430,7 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
|
|||
wsi->u.http2.hpack = HPKS_IDX_EXT;
|
||||
break;
|
||||
}
|
||||
lwsl_debug("HKPS_TYPE 3 setting header_index %d\n", c & 0x3f);
|
||||
wsi->u.http2.header_index = c & 0x3f;
|
||||
wsi->u.http2.value = 1;
|
||||
wsi->u.http2.hpack = HPKS_HLEN;
|
||||
|
@ -426,7 +439,7 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
|
|||
switch(c & 0xf0) {
|
||||
case 0x10: /* literal header never index */
|
||||
case 0: /* literal header without indexing */
|
||||
/*
|
||||
/*
|
||||
* follows 0x40 except 4-bit hdr idx
|
||||
* and don't add to index
|
||||
*/
|
||||
|
@ -436,6 +449,7 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
|
|||
wsi->u.http2.value = 0;
|
||||
break;
|
||||
}
|
||||
//lwsl_debug("indexed\n");
|
||||
/* indexed name */
|
||||
wsi->u.http2.hpack_type = HPKT_INDEXED_HDR_4_VALUE;
|
||||
wsi->u.http2.header_index = 0;
|
||||
|
@ -445,6 +459,7 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
|
|||
wsi->u.http2.hpack = HPKS_IDX_EXT;
|
||||
break;
|
||||
}
|
||||
//lwsl_err("HKPS_TYPE 5 setting header_index %d\n", c & 0xf);
|
||||
wsi->u.http2.header_index = c & 0xf;
|
||||
wsi->u.http2.value = 1;
|
||||
wsi->u.http2.hpack = HPKS_HLEN;
|
||||
|
@ -465,18 +480,21 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
|
|||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case HPKS_IDX_EXT:
|
||||
wsi->u.http2.hpack_len += (c & 0x7f) << wsi->u.http2.hpack_m;
|
||||
wsi->u.http2.hpack_m += 7;
|
||||
if (!(c & 0x80)) {
|
||||
switch (wsi->u.http2.hpack_type) {
|
||||
case HPKT_INDEXED_HDR_7:
|
||||
//lwsl_err("HKPS_IDX_EXT hdr idx %d\n", wsi->u.http2.hpack_len);
|
||||
if (lws_write_indexed_hdr(wsi, wsi->u.http2.hpack_len))
|
||||
return 1;
|
||||
wsi->u.http2.hpack = HPKS_TYPE;
|
||||
break;
|
||||
default:
|
||||
// lwsl_err("HKPS_IDX_EXT setting header_index %d\n",
|
||||
// wsi->u.http2.hpack_len);
|
||||
wsi->u.http2.header_index = wsi->u.http2.hpack_len;
|
||||
wsi->u.http2.value = 1;
|
||||
wsi->u.http2.hpack = HPKS_HLEN;
|
||||
|
@ -492,10 +510,13 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
|
|||
if (wsi->u.http2.hpack_len < 0x7f) {
|
||||
pre_data:
|
||||
if (wsi->u.http2.value) {
|
||||
if (lws_frag_start(wsi,
|
||||
lws_token_from_index(wsi,
|
||||
wsi->u.http2.header_index, NULL, NULL)))
|
||||
if (wsi->u.http2.header_index)
|
||||
if (lws_frag_start(wsi, lws_token_from_index(wsi,
|
||||
wsi->u.http2.header_index,
|
||||
NULL, NULL))) {
|
||||
// lwsl_notice("%s: hlen failed\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
} else
|
||||
wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART;
|
||||
wsi->u.http2.hpack = HPKS_DATA;
|
||||
|
@ -504,7 +525,7 @@ pre_data:
|
|||
wsi->u.http2.hpack_m = 0;
|
||||
wsi->u.http2.hpack = HPKS_HLEN_EXT;
|
||||
break;
|
||||
|
||||
|
||||
case HPKS_HLEN_EXT:
|
||||
wsi->u.http2.hpack_len += (c & 0x7f) <<
|
||||
wsi->u.http2.hpack_m;
|
||||
|
@ -518,10 +539,9 @@ pre_data:
|
|||
for (n = 0; n < 8; n++) {
|
||||
if (wsi->u.http2.huff) {
|
||||
prev = wsi->u.http2.hpack_pos;
|
||||
wsi->u.http2.hpack_pos =
|
||||
huftable_decode(
|
||||
wsi->u.http2.hpack_pos = huftable_decode(
|
||||
wsi->u.http2.hpack_pos,
|
||||
(c >> 7) & 1);
|
||||
(c >> 7) & 1);
|
||||
c <<= 1;
|
||||
if (wsi->u.http2.hpack_pos == 0xffff)
|
||||
return 1;
|
||||
|
@ -529,7 +549,7 @@ pre_data:
|
|||
continue;
|
||||
c1 = wsi->u.http2.hpack_pos & 0x7fff;
|
||||
wsi->u.http2.hpack_pos = 0;
|
||||
|
||||
|
||||
if (!c1 && prev == HUFTABLE_0x100_PREV)
|
||||
; /* EOT */
|
||||
} else {
|
||||
|
@ -537,39 +557,46 @@ pre_data:
|
|||
c1 = c;
|
||||
}
|
||||
if (wsi->u.http2.value) { /* value */
|
||||
if (lws_frag_append(wsi, c1))
|
||||
return 1;
|
||||
if (wsi->u.http2.header_index)
|
||||
if (lws_frag_append(wsi, c1))
|
||||
return 1;
|
||||
} else { /* name */
|
||||
if (libwebsocket_parse(context, wsi, c1))
|
||||
if (lws_parse(wsi, c1))
|
||||
return 1;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
if (--wsi->u.http2.hpack_len == 0) {
|
||||
|
||||
|
||||
switch (wsi->u.http2.hpack_type) {
|
||||
case HPKT_LITERAL_HDR_VALUE_INCR:
|
||||
case HPKT_INDEXED_HDR_6_VALUE_INCR: // !!!
|
||||
if (lws_hpack_add_dynamic_header(wsi, lws_token_from_index(wsi, wsi->u.http2.header_index, NULL, NULL), NULL, 0))
|
||||
if (lws_hpack_add_dynamic_header(wsi,
|
||||
lws_token_from_index(wsi,
|
||||
wsi->u.http2.header_index,
|
||||
NULL, NULL), NULL, 0))
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
n = 8;
|
||||
if (wsi->u.http2.value) {
|
||||
if (lws_frag_end(wsi))
|
||||
return 1;
|
||||
|
||||
lws_dump_header(wsi, lws_token_from_index(wsi, wsi->u.http2.header_index, NULL, NULL));
|
||||
if (wsi->u.http2.count + wsi->u.http2.padding == wsi->u.http2.length)
|
||||
// lwsl_err("data\n");
|
||||
lws_dump_header(wsi, lws_token_from_index(
|
||||
wsi, wsi->u.http2.header_index,
|
||||
NULL, NULL));
|
||||
if (wsi->u.http2.count + wsi->u.http2.padding ==
|
||||
wsi->u.http2.length)
|
||||
wsi->u.http2.hpack = HKPS_OPT_DISCARD_PADDING;
|
||||
else
|
||||
wsi->u.http2.hpack = HPKS_TYPE;
|
||||
} else { /* name */
|
||||
if (wsi->u.hdr.parser_state < WSI_TOKEN_COUNT)
|
||||
|
||||
//if (wsi->u.hdr.parser_state < WSI_TOKEN_COUNT)
|
||||
|
||||
wsi->u.http2.value = 1;
|
||||
wsi->u.http2.hpack = HPKS_HLEN;
|
||||
}
|
||||
|
@ -581,11 +608,12 @@ pre_data:
|
|||
wsi->u.http2.hpack = HPKS_TYPE;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lws_http2_num(int starting_bits, unsigned long num, unsigned char **p, unsigned char *end)
|
||||
static int lws_http2_num(int starting_bits, unsigned long num,
|
||||
unsigned char **p, unsigned char *end)
|
||||
{
|
||||
int mask = (1 << starting_bits) - 1;
|
||||
|
||||
|
@ -593,11 +621,11 @@ static int lws_http2_num(int starting_bits, unsigned long num, unsigned char **p
|
|||
*((*p)++) |= num;
|
||||
return *p >= end;
|
||||
}
|
||||
|
||||
|
||||
*((*p)++) |= mask;
|
||||
if (*p >= end)
|
||||
return 1;
|
||||
|
||||
|
||||
num -= mask;
|
||||
while (num >= 128) {
|
||||
*((*p)++) = 0x80 | (num & 0x7f);
|
||||
|
@ -605,22 +633,19 @@ static int lws_http2_num(int starting_bits, unsigned long num, unsigned char **p
|
|||
return 1;
|
||||
num >>= 7;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lws_add_http2_header_by_name(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi,
|
||||
const unsigned char *name,
|
||||
const unsigned char *value,
|
||||
int length,
|
||||
unsigned char **p,
|
||||
unsigned char *end)
|
||||
int lws_add_http2_header_by_name(struct lws *wsi,
|
||||
const unsigned char *name,
|
||||
const unsigned char *value, int length,
|
||||
unsigned char **p, unsigned char *end)
|
||||
{
|
||||
int len;
|
||||
|
||||
|
||||
lwsl_info("%s: %p %s:%s\n", __func__, *p, name, value);
|
||||
|
||||
|
||||
len = strlen((char *)name);
|
||||
if (len)
|
||||
if (name[len - 1] == ':')
|
||||
|
@ -640,43 +665,39 @@ int lws_add_http2_header_by_name(struct libwebsocket_context *context,
|
|||
*(*p) = 0; /* non-HUF */
|
||||
if (lws_http2_num(7, length, p, end))
|
||||
return 1;
|
||||
|
||||
|
||||
memcpy(*p, value, length);
|
||||
*p += length;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lws_add_http2_header_by_token(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi,
|
||||
enum lws_token_indexes token,
|
||||
const unsigned char *value,
|
||||
int length,
|
||||
unsigned char **p,
|
||||
unsigned char *end)
|
||||
int lws_add_http2_header_by_token(struct lws *wsi, enum lws_token_indexes token,
|
||||
const unsigned char *value, int length,
|
||||
unsigned char **p, unsigned char *end)
|
||||
{
|
||||
const unsigned char *name;
|
||||
|
||||
name = lws_token_to_string(token);
|
||||
if (!name)
|
||||
return 1;
|
||||
|
||||
return lws_add_http2_header_by_name(context, wsi, name, value, length, p, end);
|
||||
|
||||
return lws_add_http2_header_by_name(wsi, name, value, length, p, end);
|
||||
}
|
||||
|
||||
int lws_add_http2_header_status(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi,
|
||||
unsigned int code,
|
||||
unsigned char **p,
|
||||
unsigned char *end)
|
||||
int lws_add_http2_header_status(struct lws *wsi,
|
||||
unsigned int code, unsigned char **p,
|
||||
unsigned char *end)
|
||||
{
|
||||
unsigned char status[10];
|
||||
int n;
|
||||
|
||||
|
||||
wsi->u.http2.send_END_STREAM = !!(code >= 400);
|
||||
|
||||
|
||||
n = sprintf((char *)status, "%u", code);
|
||||
if (lws_add_http2_header_by_token(context, wsi, WSI_TOKEN_HTTP_COLON_STATUS, status, n, p, end))
|
||||
if (lws_add_http2_header_by_token(wsi, WSI_TOKEN_HTTP_COLON_STATUS,
|
||||
status, n, p, end))
|
||||
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
|
|
219
lib/http2.c
219
lib/http2.c
|
@ -38,31 +38,33 @@ void lws_http2_init(struct http2_settings *settings)
|
|||
memcpy(settings, lws_http2_default_settings.setting, sizeof(*settings));
|
||||
}
|
||||
|
||||
struct libwebsocket *
|
||||
lws_http2_wsi_from_id(struct libwebsocket *wsi, unsigned int sid)
|
||||
struct lws *
|
||||
lws_http2_wsi_from_id(struct lws *wsi, unsigned int sid)
|
||||
{
|
||||
do {
|
||||
if (wsi->u.http2.my_stream_id == sid)
|
||||
return wsi;
|
||||
|
||||
|
||||
wsi = wsi->u.http2.next_child_wsi;
|
||||
} while (wsi);
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct libwebsocket *
|
||||
lws_create_server_child_wsi(struct libwebsocket_context *context, struct libwebsocket *parent_wsi, unsigned int sid)
|
||||
struct lws *
|
||||
lws_create_server_child_wsi(struct lws_vhost *vhost, struct lws *parent_wsi,
|
||||
unsigned int sid)
|
||||
{
|
||||
struct libwebsocket *wsi = libwebsocket_create_new_server_wsi(context);
|
||||
|
||||
struct lws *wsi = lws_create_new_server_wsi(vhost);
|
||||
|
||||
if (!wsi)
|
||||
return NULL;
|
||||
|
||||
|
||||
/* no more children allowed by parent */
|
||||
if (parent_wsi->u.http2.child_count + 1 == parent_wsi->u.http2.peer_settings.setting[LWS_HTTP2_SETTINGS__MAX_CONCURRENT_STREAMS])
|
||||
return NULL;
|
||||
|
||||
if (parent_wsi->u.http2.child_count + 1 ==
|
||||
parent_wsi->u.http2.peer_settings.setting[
|
||||
LWS_HTTP2_SETTINGS__MAX_CONCURRENT_STREAMS])
|
||||
goto bail;
|
||||
lws_http2_init(&wsi->u.http2.peer_settings);
|
||||
lws_http2_init(&wsi->u.http2.my_settings);
|
||||
wsi->u.http2.stream_id = sid;
|
||||
|
@ -72,49 +74,59 @@ lws_create_server_child_wsi(struct libwebsocket_context *context, struct libwebs
|
|||
wsi->u.http2.next_child_wsi = parent_wsi->u.http2.next_child_wsi;
|
||||
parent_wsi->u.http2.next_child_wsi = wsi;
|
||||
parent_wsi->u.http2.child_count++;
|
||||
|
||||
|
||||
wsi->u.http2.my_priority = 16;
|
||||
wsi->u.http2.tx_credit = 65535;
|
||||
|
||||
wsi->state = WSI_STATE_HTTP2_ESTABLISHED;
|
||||
wsi->mode = parent_wsi->mode;
|
||||
|
||||
wsi->protocol = &context->protocols[0];
|
||||
libwebsocket_ensure_user_space(wsi);
|
||||
|
||||
lwsl_info("%s: %p new child %p, sid %d, user_space=%p\n", __func__, parent_wsi, wsi, sid, wsi->user_space);
|
||||
|
||||
wsi->state = LWSS_HTTP2_ESTABLISHED;
|
||||
wsi->mode = parent_wsi->mode;
|
||||
|
||||
wsi->protocol = &vhost->protocols[0];
|
||||
if (lws_ensure_user_space(wsi))
|
||||
goto bail;
|
||||
|
||||
lwsl_info("%s: %p new child %p, sid %d, user_space=%p\n", __func__,
|
||||
parent_wsi, wsi, sid, wsi->user_space);
|
||||
|
||||
return wsi;
|
||||
|
||||
bail:
|
||||
vhost->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY,
|
||||
NULL, NULL, 0);
|
||||
lws_free(wsi);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int lws_remove_server_child_wsi(struct libwebsocket_context *context, struct libwebsocket *wsi)
|
||||
int lws_remove_server_child_wsi(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
struct libwebsocket **w = &wsi->u.http2.parent_wsi;
|
||||
struct lws **w = &wsi->u.http2.parent_wsi;
|
||||
do {
|
||||
if (*w == wsi) {
|
||||
*w = wsi->u.http2.next_child_wsi;
|
||||
(wsi->u.http2.parent_wsi)->u.http2.child_count--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
w = &((*w)->u.http2.next_child_wsi);
|
||||
} while (*w);
|
||||
|
||||
|
||||
lwsl_err("%s: can't find %p\n", __func__, wsi);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
lws_http2_interpret_settings_payload(struct http2_settings *settings, unsigned char *buf, int len)
|
||||
lws_http2_interpret_settings_payload(struct http2_settings *settings,
|
||||
unsigned char *buf, int len)
|
||||
{
|
||||
unsigned int a, b;
|
||||
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
|
||||
if (len < LWS_HTTP2_SETTINGS_LENGTH)
|
||||
return 1;
|
||||
|
||||
|
||||
while (len >= LWS_HTTP2_SETTINGS_LENGTH) {
|
||||
a = (buf[0] << 8) | buf[1];
|
||||
if (a < LWS_HTTP2_SETTINGS__COUNT) {
|
||||
|
@ -125,24 +137,25 @@ lws_http2_interpret_settings_payload(struct http2_settings *settings, unsigned c
|
|||
len -= LWS_HTTP2_SETTINGS_LENGTH;
|
||||
buf += LWS_HTTP2_SETTINGS_LENGTH;
|
||||
}
|
||||
|
||||
|
||||
if (len)
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct libwebsocket *lws_http2_get_network_wsi(struct libwebsocket *wsi)
|
||||
struct lws *lws_http2_get_network_wsi(struct lws *wsi)
|
||||
{
|
||||
while (wsi->u.http2.parent_wsi)
|
||||
wsi = wsi->u.http2.parent_wsi;
|
||||
|
||||
|
||||
return wsi;
|
||||
}
|
||||
|
||||
int lws_http2_frame_write(struct libwebsocket *wsi, int type, int flags, unsigned int sid, unsigned int len, unsigned char *buf)
|
||||
int lws_http2_frame_write(struct lws *wsi, int type, int flags,
|
||||
unsigned int sid, unsigned int len, unsigned char *buf)
|
||||
{
|
||||
struct libwebsocket *wsi_eff = lws_http2_get_network_wsi(wsi);
|
||||
struct lws *wsi_eff = lws_http2_get_network_wsi(wsi);
|
||||
unsigned char *p = &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH];
|
||||
int n;
|
||||
|
||||
|
@ -155,24 +168,28 @@ int lws_http2_frame_write(struct libwebsocket *wsi, int type, int flags, unsigne
|
|||
*p++ = sid >> 16;
|
||||
*p++ = sid >> 8;
|
||||
*p++ = sid;
|
||||
|
||||
lwsl_info("%s: %p (eff %p). type %d, flags 0x%x, sid=%d, len=%d\n",
|
||||
__func__, wsi, wsi_eff, type, flags, sid, len, wsi->u.http2.tx_credit);
|
||||
|
||||
|
||||
lwsl_info("%s: %p (eff %p). type %d, flags 0x%x, sid=%d, len=%d, tx_credit=%d\n",
|
||||
__func__, wsi, wsi_eff, type, flags, sid, len,
|
||||
wsi->u.http2.tx_credit);
|
||||
|
||||
if (type == LWS_HTTP2_FRAME_TYPE_DATA) {
|
||||
if (wsi->u.http2.tx_credit < len)
|
||||
lwsl_err("%s: %p: sending payload len %d but tx_credit only %d!\n", len, wsi->u.http2.tx_credit);
|
||||
lwsl_err("%s: %p: sending payload len %d"
|
||||
" but tx_credit only %d!\n", __func__, wsi, len,
|
||||
wsi->u.http2.tx_credit);
|
||||
wsi->u.http2.tx_credit -= len;
|
||||
}
|
||||
|
||||
n = lws_issue_raw(wsi_eff, &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH], len + LWS_HTTP2_FRAME_HEADER_LENGTH);
|
||||
n = lws_issue_raw(wsi_eff, &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH],
|
||||
len + LWS_HTTP2_FRAME_HEADER_LENGTH);
|
||||
if (n >= LWS_HTTP2_FRAME_HEADER_LENGTH)
|
||||
return n - LWS_HTTP2_FRAME_HEADER_LENGTH;
|
||||
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static void lws_http2_settings_write(struct libwebsocket *wsi, int n, unsigned char *buf)
|
||||
static void lws_http2_settings_write(struct lws *wsi, int n, unsigned char *buf)
|
||||
{
|
||||
*buf++ = n >> 8;
|
||||
*buf++ = n;
|
||||
|
@ -186,36 +203,35 @@ static const char * https_client_preface =
|
|||
"PRI * HTTP/2.0\x0d\x0a\x0d\x0aSM\x0d\x0a\x0d\x0a";
|
||||
|
||||
int
|
||||
lws_http2_parser(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, unsigned char c)
|
||||
lws_http2_parser(struct lws *wsi, unsigned char c)
|
||||
{
|
||||
struct libwebsocket *swsi;
|
||||
struct lws *swsi;
|
||||
int n;
|
||||
//dstruct libwebsocket *wsi_new;
|
||||
|
||||
switch (wsi->state) {
|
||||
case WSI_STATE_HTTP2_AWAIT_CLIENT_PREFACE:
|
||||
case LWSS_HTTP2_AWAIT_CLIENT_PREFACE:
|
||||
if (https_client_preface[wsi->u.http2.count++] != c)
|
||||
return 1;
|
||||
|
||||
if (!https_client_preface[wsi->u.http2.count]) {
|
||||
lwsl_info("http2: %p: established\n", wsi);
|
||||
wsi->state = WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS;
|
||||
wsi->state = LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS;
|
||||
wsi->u.http2.count = 0;
|
||||
wsi->u.http2.tx_credit = 65535;
|
||||
|
||||
/*
|
||||
|
||||
/*
|
||||
* we must send a settings frame -- empty one is OK...
|
||||
* that must be the first thing sent by server
|
||||
* and the peer must send a SETTINGS with ACK flag...
|
||||
*/
|
||||
|
||||
lws_set_protocol_write_pending(context, wsi, LWS_PPS_HTTP2_MY_SETTINGS);
|
||||
|
||||
lws_set_protocol_write_pending(wsi,
|
||||
LWS_PPS_HTTP2_MY_SETTINGS);
|
||||
}
|
||||
break;
|
||||
|
||||
case WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS:
|
||||
case WSI_STATE_HTTP2_ESTABLISHED:
|
||||
case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS:
|
||||
case LWSS_HTTP2_ESTABLISHED:
|
||||
if (wsi->u.http2.frame_state == LWS_HTTP2_FRAME_HEADER_LENGTH) { // payload
|
||||
wsi->u.http2.count++;
|
||||
wsi->u.http2.stream_wsi->u.http2.count = wsi->u.http2.count;
|
||||
|
@ -233,8 +249,15 @@ lws_http2_parser(struct libwebsocket_context *context,
|
|||
case LWS_HTTP2_FRAME_TYPE_CONTINUATION:
|
||||
case LWS_HTTP2_FRAME_TYPE_HEADERS:
|
||||
lwsl_info(" %02X\n", c);
|
||||
if (lws_hpack_interpret(context, wsi->u.http2.stream_wsi, c))
|
||||
if (!wsi->u.http2.stream_wsi->u.hdr.ah)
|
||||
if (lws_header_table_attach(wsi->u.http2.stream_wsi, 0)) {
|
||||
lwsl_err("%s: Failed to get ah\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
if (lws_hpack_interpret(wsi->u.http2.stream_wsi, c)) {
|
||||
lwsl_notice("%s: lws_hpack_interpret failed\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case LWS_HTTP2_FRAME_TYPE_GOAWAY:
|
||||
if (wsi->u.http2.count >= 5 && wsi->u.http2.count <= 8) {
|
||||
|
@ -269,7 +292,7 @@ lws_http2_parser(struct libwebsocket_context *context,
|
|||
}
|
||||
if (wsi->u.http2.count != wsi->u.http2.length)
|
||||
break;
|
||||
|
||||
|
||||
/* end of frame */
|
||||
|
||||
wsi->u.http2.frame_state = 0;
|
||||
|
@ -285,26 +308,26 @@ lws_http2_parser(struct libwebsocket_context *context,
|
|||
case LWS_HTTP2_FRAME_TYPE_HEADERS:
|
||||
/* service the http request itself */
|
||||
lwsl_info("servicing initial http request, wsi=%p, stream wsi=%p\n", wsi, wsi->u.http2.stream_wsi);
|
||||
n = lws_http_action(context, swsi);
|
||||
n = lws_http_action(swsi);
|
||||
(void)n;
|
||||
lwsl_info(" action result %d\n", n);
|
||||
break;
|
||||
case LWS_HTTP2_FRAME_TYPE_PING:
|
||||
if (wsi->u.http2.flags & LWS_HTTP2_FLAG_SETTINGS_ACK) { // ack
|
||||
} else { /* they're sending us a ping request */
|
||||
lws_set_protocol_write_pending(context, wsi, LWS_PPS_HTTP2_PONG);
|
||||
lws_set_protocol_write_pending(wsi, LWS_PPS_HTTP2_PONG);
|
||||
}
|
||||
break;
|
||||
case LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE:
|
||||
wsi->u.http2.hpack_e_dep &= ~(1 << 31);
|
||||
if ((long long)swsi->u.http2.tx_credit + (unsigned long long)wsi->u.http2.hpack_e_dep > (~(1 << 31)))
|
||||
if ((lws_intptr_t)swsi->u.http2.tx_credit + (lws_intptr_t)wsi->u.http2.hpack_e_dep > (~(1 << 31)))
|
||||
return 1; /* actually need to close swsi not the whole show */
|
||||
swsi->u.http2.tx_credit += wsi->u.http2.hpack_e_dep;
|
||||
if (swsi->u.http2.waiting_tx_credit && swsi->u.http2.tx_credit > 0) {
|
||||
lwsl_info("%s: %p: waiting_tx_credit -> wait on writeable\n", __func__, wsi);
|
||||
swsi->u.http2.waiting_tx_credit = 0;
|
||||
libwebsocket_callback_on_writable(context, swsi);
|
||||
}
|
||||
lws_callback_on_writable(swsi);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -336,21 +359,21 @@ lws_http2_parser(struct libwebsocket_context *context,
|
|||
lwsl_info("frame: type 0x%x, flags 0x%x, sid 0x%x, len 0x%x\n",
|
||||
wsi->u.http2.type, wsi->u.http2.flags, wsi->u.http2.stream_id, wsi->u.http2.length);
|
||||
wsi->u.http2.count = 0;
|
||||
|
||||
|
||||
wsi->u.http2.stream_wsi = wsi;
|
||||
if (wsi->u.http2.stream_id)
|
||||
wsi->u.http2.stream_wsi = lws_http2_wsi_from_id(wsi, wsi->u.http2.stream_id);
|
||||
|
||||
|
||||
switch (wsi->u.http2.type) {
|
||||
case LWS_HTTP2_FRAME_TYPE_SETTINGS:
|
||||
/* nonzero sid on settings is illegal */
|
||||
if (wsi->u.http2.stream_id)
|
||||
return 1;
|
||||
|
||||
|
||||
if (wsi->u.http2.flags & LWS_HTTP2_FLAG_SETTINGS_ACK) { // ack
|
||||
} else
|
||||
/* non-ACK coming in means we must ACK it */
|
||||
lws_set_protocol_write_pending(context, wsi, LWS_PPS_HTTP2_ACK_SETTINGS);
|
||||
lws_set_protocol_write_pending(wsi, LWS_PPS_HTTP2_ACK_SETTINGS);
|
||||
break;
|
||||
case LWS_HTTP2_FRAME_TYPE_PING:
|
||||
if (wsi->u.http2.stream_id)
|
||||
|
@ -367,23 +390,26 @@ lws_http2_parser(struct libwebsocket_context *context,
|
|||
lwsl_info("LWS_HTTP2_FRAME_TYPE_HEADERS: stream_id = %d\n", wsi->u.http2.stream_id);
|
||||
if (!wsi->u.http2.stream_id)
|
||||
return 1;
|
||||
if (!wsi->u.http2.stream_wsi)
|
||||
wsi->u.http2.stream_wsi = lws_create_server_child_wsi(context, wsi, wsi->u.http2.stream_id);
|
||||
|
||||
if (!wsi->u.http2.stream_wsi) {
|
||||
wsi->u.http2.stream_wsi =
|
||||
lws_create_server_child_wsi(wsi->vhost, wsi, wsi->u.http2.stream_id);
|
||||
wsi->u.http2.stream_wsi->http2_substream = 1;
|
||||
}
|
||||
|
||||
/* END_STREAM means after servicing this, close the stream */
|
||||
wsi->u.http2.END_STREAM = !!(wsi->u.http2.flags & LWS_HTTP2_FLAG_END_STREAM);
|
||||
lwsl_info("%s: headers END_STREAM = %d\n",__func__, wsi->u.http2.END_STREAM);
|
||||
update_end_headers:
|
||||
/* no END_HEADERS means CONTINUATION must come */
|
||||
wsi->u.http2.END_HEADERS = !!(wsi->u.http2.flags & LWS_HTTP2_FLAG_END_HEADERS);
|
||||
|
||||
|
||||
swsi = wsi->u.http2.stream_wsi;
|
||||
if (!swsi)
|
||||
return 1;
|
||||
|
||||
|
||||
|
||||
/* prepare the hpack parser at the right start */
|
||||
|
||||
|
||||
swsi->u.http2.flags = wsi->u.http2.flags;
|
||||
swsi->u.http2.length = wsi->u.http2.length;
|
||||
swsi->u.http2.END_STREAM = wsi->u.http2.END_STREAM;
|
||||
|
@ -409,29 +435,29 @@ update_end_headers:
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lws_http2_do_pps_send(struct libwebsocket_context *context, struct libwebsocket *wsi)
|
||||
int lws_http2_do_pps_send(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
unsigned char settings[LWS_SEND_BUFFER_PRE_PADDING + 6 * LWS_HTTP2_SETTINGS__COUNT];
|
||||
struct libwebsocket *swsi;
|
||||
unsigned char settings[LWS_PRE + 6 * LWS_HTTP2_SETTINGS__COUNT];
|
||||
struct lws *swsi;
|
||||
int n, m = 0;
|
||||
|
||||
lwsl_debug("%s: %p: %d\n", __func__, wsi, wsi->pps);
|
||||
|
||||
|
||||
switch (wsi->pps) {
|
||||
case LWS_PPS_HTTP2_MY_SETTINGS:
|
||||
for (n = 1; n < LWS_HTTP2_SETTINGS__COUNT; n++)
|
||||
if (wsi->u.http2.my_settings.setting[n] != lws_http2_default_settings.setting[n]) {
|
||||
lws_http2_settings_write(wsi, n,
|
||||
&settings[LWS_SEND_BUFFER_PRE_PADDING + m]);
|
||||
&settings[LWS_PRE + m]);
|
||||
m += sizeof(wsi->u.http2.one_setting);
|
||||
}
|
||||
n = lws_http2_frame_write(wsi, LWS_HTTP2_FRAME_TYPE_SETTINGS,
|
||||
0, LWS_HTTP2_STREAM_ID_MASTER, m,
|
||||
&settings[LWS_SEND_BUFFER_PRE_PADDING]);
|
||||
&settings[LWS_PRE]);
|
||||
if (n != m) {
|
||||
lwsl_info("send %d %d\n", n, m);
|
||||
return 1;
|
||||
|
@ -441,34 +467,35 @@ int lws_http2_do_pps_send(struct libwebsocket_context *context, struct libwebsoc
|
|||
/* send ack ... always empty */
|
||||
n = lws_http2_frame_write(wsi, LWS_HTTP2_FRAME_TYPE_SETTINGS,
|
||||
1, LWS_HTTP2_STREAM_ID_MASTER, 0,
|
||||
&settings[LWS_SEND_BUFFER_PRE_PADDING]);
|
||||
&settings[LWS_PRE]);
|
||||
if (n) {
|
||||
lwsl_err("ack tells %d\n", n);
|
||||
return 1;
|
||||
}
|
||||
/* this is the end of the preface dance then? */
|
||||
if (wsi->state == WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS) {
|
||||
wsi->state = WSI_STATE_HTTP2_ESTABLISHED;
|
||||
|
||||
wsi->u.http.fd = LWS_INVALID_FILE;
|
||||
|
||||
if (wsi->state == LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS) {
|
||||
wsi->state = LWSS_HTTP2_ESTABLISHED;
|
||||
|
||||
wsi->u.http.fop_fd = NULL;
|
||||
|
||||
if (lws_is_ssl(lws_http2_get_network_wsi(wsi))) {
|
||||
lwsl_info("skipping nonexistant ssl upgrade headers\n");
|
||||
lwsl_info("skipping nonexistent ssl upgrade headers\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
/*
|
||||
* we need to treat the headers from this upgrade
|
||||
* as the first job. These need to get
|
||||
* shifted to stream ID 1
|
||||
*/
|
||||
lwsl_info("%s: setting up sid 1\n", __func__);
|
||||
|
||||
swsi = wsi->u.http2.stream_wsi = lws_create_server_child_wsi(context, wsi, 1);
|
||||
|
||||
swsi = wsi->u.http2.stream_wsi =
|
||||
lws_create_server_child_wsi(wsi->vhost, wsi, 1);
|
||||
/* pass on the initial headers to SID 1 */
|
||||
swsi->u.http.ah = wsi->u.http.ah;
|
||||
wsi->u.http.ah = NULL;
|
||||
|
||||
|
||||
lwsl_info("%s: inherited headers %p\n", __func__, swsi->u.http.ah);
|
||||
swsi->u.http2.tx_credit = wsi->u.http2.peer_settings.setting[LWS_HTTP2_SETTINGS__INITIAL_WINDOW_SIZE];
|
||||
lwsl_info("initial tx credit on conn %p: %d\n", swsi, swsi->u.http2.tx_credit);
|
||||
|
@ -476,15 +503,15 @@ int lws_http2_do_pps_send(struct libwebsocket_context *context, struct libwebsoc
|
|||
/* demanded by HTTP2 */
|
||||
swsi->u.http2.END_STREAM = 1;
|
||||
lwsl_info("servicing initial http request\n");
|
||||
return lws_http_action(context, swsi);
|
||||
return lws_http_action(swsi);
|
||||
}
|
||||
break;
|
||||
case LWS_PPS_HTTP2_PONG:
|
||||
memcpy(&settings[LWS_SEND_BUFFER_PRE_PADDING], wsi->u.http2.ping_payload, 8);
|
||||
memcpy(&settings[LWS_PRE], wsi->u.http2.ping_payload, 8);
|
||||
n = lws_http2_frame_write(wsi, LWS_HTTP2_FRAME_TYPE_PING,
|
||||
LWS_HTTP2_FLAG_SETTINGS_ACK,
|
||||
LWS_HTTP2_STREAM_ID_MASTER, 8,
|
||||
&settings[LWS_SEND_BUFFER_PRE_PADDING]);
|
||||
&settings[LWS_PRE]);
|
||||
if (n != 8) {
|
||||
lwsl_info("send %d %d\n", n, m);
|
||||
return 1;
|
||||
|
@ -493,11 +520,11 @@ int lws_http2_do_pps_send(struct libwebsocket_context *context, struct libwebsoc
|
|||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct libwebsocket * lws_http2_get_nth_child(struct libwebsocket *wsi, int n)
|
||||
struct lws * lws_http2_get_nth_child(struct lws *wsi, int n)
|
||||
{
|
||||
do {
|
||||
wsi = wsi->u.http2.next_child_wsi;
|
||||
|
|
916
lib/lejp-conf.c
Normal file
916
lib/lejp-conf.c
Normal file
|
@ -0,0 +1,916 @@
|
|||
/*
|
||||
* libwebsockets web server application
|
||||
*
|
||||
* Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation:
|
||||
* version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
#include "lejp.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
/* this is needed for Travis CI */
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
#define ESC_INSTALL_DATADIR "_lws_ddir_"
|
||||
|
||||
static const char * const paths_global[] = {
|
||||
"global.uid",
|
||||
"global.gid",
|
||||
"global.count-threads",
|
||||
"global.init-ssl",
|
||||
"global.server-string",
|
||||
"global.plugin-dir",
|
||||
"global.ws-pingpong-secs",
|
||||
"global.timeout-secs",
|
||||
"global.reject-service-keywords[].*",
|
||||
"global.reject-service-keywords[]",
|
||||
};
|
||||
|
||||
enum lejp_global_paths {
|
||||
LEJPGP_UID,
|
||||
LEJPGP_GID,
|
||||
LEJPGP_COUNT_THREADS,
|
||||
LWJPGP_INIT_SSL,
|
||||
LEJPGP_SERVER_STRING,
|
||||
LEJPGP_PLUGIN_DIR,
|
||||
LWJPGP_PINGPONG_SECS,
|
||||
LWJPGP_TIMEOUT_SECS,
|
||||
LWJPGP_REJECT_SERVICE_KEYWORDS_NAME,
|
||||
LWJPGP_REJECT_SERVICE_KEYWORDS
|
||||
};
|
||||
|
||||
static const char * const paths_vhosts[] = {
|
||||
"vhosts[]",
|
||||
"vhosts[].mounts[]",
|
||||
"vhosts[].name",
|
||||
"vhosts[].port",
|
||||
"vhosts[].interface",
|
||||
"vhosts[].unix-socket",
|
||||
"vhosts[].sts",
|
||||
"vhosts[].host-ssl-key",
|
||||
"vhosts[].host-ssl-cert",
|
||||
"vhosts[].host-ssl-ca",
|
||||
"vhosts[].access-log",
|
||||
"vhosts[].mounts[].mountpoint",
|
||||
"vhosts[].mounts[].origin",
|
||||
"vhosts[].mounts[].protocol",
|
||||
"vhosts[].mounts[].default",
|
||||
"vhosts[].mounts[].auth-mask",
|
||||
"vhosts[].mounts[].cgi-timeout",
|
||||
"vhosts[].mounts[].cgi-env[].*",
|
||||
"vhosts[].mounts[].cache-max-age",
|
||||
"vhosts[].mounts[].cache-reuse",
|
||||
"vhosts[].mounts[].cache-revalidate",
|
||||
"vhosts[].mounts[].basic-auth",
|
||||
"vhosts[].mounts[].cache-intermediaries",
|
||||
"vhosts[].mounts[].extra-mimetypes.*",
|
||||
"vhosts[].mounts[].interpret.*",
|
||||
"vhosts[].ws-protocols[].*.*",
|
||||
"vhosts[].ws-protocols[].*",
|
||||
"vhosts[].ws-protocols[]",
|
||||
"vhosts[].keepalive_timeout",
|
||||
"vhosts[].enable-client-ssl",
|
||||
"vhosts[].ciphers",
|
||||
"vhosts[].ecdh-curve",
|
||||
"vhosts[].noipv6",
|
||||
"vhosts[].ipv6only",
|
||||
"vhosts[].ssl-option-set",
|
||||
"vhosts[].ssl-option-clear",
|
||||
"vhosts[].mounts[].pmo[].*",
|
||||
"vhosts[].headers[].*",
|
||||
"vhosts[].headers[]",
|
||||
"vhosts[].client-ssl-key",
|
||||
"vhosts[].client-ssl-cert",
|
||||
"vhosts[].client-ssl-ca",
|
||||
"vhosts[].client-ssl-ciphers",
|
||||
};
|
||||
|
||||
enum lejp_vhost_paths {
|
||||
LEJPVP,
|
||||
LEJPVP_MOUNTS,
|
||||
LEJPVP_NAME,
|
||||
LEJPVP_PORT,
|
||||
LEJPVP_INTERFACE,
|
||||
LEJPVP_UNIXSKT,
|
||||
LEJPVP_STS,
|
||||
LEJPVP_HOST_SSL_KEY,
|
||||
LEJPVP_HOST_SSL_CERT,
|
||||
LEJPVP_HOST_SSL_CA,
|
||||
LEJPVP_ACCESS_LOG,
|
||||
LEJPVP_MOUNTPOINT,
|
||||
LEJPVP_ORIGIN,
|
||||
LEJPVP_MOUNT_PROTOCOL,
|
||||
LEJPVP_DEFAULT,
|
||||
LEJPVP_DEFAULT_AUTH_MASK,
|
||||
LEJPVP_CGI_TIMEOUT,
|
||||
LEJPVP_CGI_ENV,
|
||||
LEJPVP_MOUNT_CACHE_MAX_AGE,
|
||||
LEJPVP_MOUNT_CACHE_REUSE,
|
||||
LEJPVP_MOUNT_CACHE_REVALIDATE,
|
||||
LEJPVP_MOUNT_BASIC_AUTH,
|
||||
LEJPVP_MOUNT_CACHE_INTERMEDIARIES,
|
||||
LEJPVP_MOUNT_EXTRA_MIMETYPES,
|
||||
LEJPVP_MOUNT_INTERPRET,
|
||||
LEJPVP_PROTOCOL_NAME_OPT,
|
||||
LEJPVP_PROTOCOL_NAME,
|
||||
LEJPVP_PROTOCOL,
|
||||
LEJPVP_KEEPALIVE_TIMEOUT,
|
||||
LEJPVP_ENABLE_CLIENT_SSL,
|
||||
LEJPVP_CIPHERS,
|
||||
LEJPVP_ECDH_CURVE,
|
||||
LEJPVP_NOIPV6,
|
||||
LEJPVP_IPV6ONLY,
|
||||
LEJPVP_SSL_OPTION_SET,
|
||||
LEJPVP_SSL_OPTION_CLEAR,
|
||||
LEJPVP_PMO,
|
||||
LEJPVP_HEADERS_NAME,
|
||||
LEJPVP_HEADERS,
|
||||
LEJPVP_CLIENT_SSL_KEY,
|
||||
LEJPVP_CLIENT_SSL_CERT,
|
||||
LEJPVP_CLIENT_SSL_CA,
|
||||
LEJPVP_CLIENT_CIPHERS,
|
||||
};
|
||||
|
||||
static const char * const parser_errs[] = {
|
||||
"",
|
||||
"",
|
||||
"No opening '{'",
|
||||
"Expected closing '}'",
|
||||
"Expected '\"'",
|
||||
"String underrun",
|
||||
"Illegal unescaped control char",
|
||||
"Illegal escape format",
|
||||
"Illegal hex number",
|
||||
"Expected ':'",
|
||||
"Illegal value start",
|
||||
"Digit required after decimal point",
|
||||
"Bad number format",
|
||||
"Bad exponent format",
|
||||
"Unknown token",
|
||||
"Too many ']'",
|
||||
"Mismatched ']'",
|
||||
"Expected ']'",
|
||||
"JSON nesting limit exceeded",
|
||||
"Nesting tracking used up",
|
||||
"Number too long",
|
||||
"Comma or block end expected",
|
||||
"Unknown",
|
||||
"Parser callback errored (see earlier error)",
|
||||
};
|
||||
|
||||
#define MAX_PLUGIN_DIRS 10
|
||||
|
||||
struct jpargs {
|
||||
struct lws_context_creation_info *info;
|
||||
struct lws_context *context;
|
||||
const struct lws_protocols *protocols;
|
||||
const struct lws_extension *extensions;
|
||||
char *p, *end, valid;
|
||||
struct lws_http_mount *head, *last;
|
||||
|
||||
struct lws_protocol_vhost_options *pvo;
|
||||
struct lws_protocol_vhost_options *pvo_em;
|
||||
struct lws_protocol_vhost_options *pvo_int;
|
||||
struct lws_http_mount m;
|
||||
const char **plugin_dirs;
|
||||
int count_plugin_dirs;
|
||||
|
||||
unsigned int enable_client_ssl:1;
|
||||
unsigned int fresh_mount:1;
|
||||
unsigned int any_vhosts:1;
|
||||
};
|
||||
|
||||
static void *
|
||||
lwsws_align(struct jpargs *a)
|
||||
{
|
||||
if ((lws_intptr_t)(a->p) & 15)
|
||||
a->p += 16 - ((lws_intptr_t)(a->p) & 15);
|
||||
|
||||
return a->p;
|
||||
}
|
||||
|
||||
static int
|
||||
arg_to_bool(const char *s)
|
||||
{
|
||||
static const char * const on[] = { "on", "yes", "true" };
|
||||
int n = atoi(s);
|
||||
|
||||
if (n)
|
||||
return 1;
|
||||
|
||||
for (n = 0; n < ARRAY_SIZE(on); n++)
|
||||
if (!strcasecmp(s, on[n]))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char
|
||||
lejp_globals_cb(struct lejp_ctx *ctx, char reason)
|
||||
{
|
||||
struct jpargs *a = (struct jpargs *)ctx->user;
|
||||
struct lws_protocol_vhost_options *rej;
|
||||
int n;
|
||||
|
||||
/* we only match on the prepared path strings */
|
||||
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
|
||||
return 0;
|
||||
|
||||
/* this catches, eg, vhosts[].headers[].xxx */
|
||||
if (reason == LEJPCB_VAL_STR_END &&
|
||||
ctx->path_match == LWJPGP_REJECT_SERVICE_KEYWORDS_NAME + 1) {
|
||||
rej = lwsws_align(a);
|
||||
a->p += sizeof(*rej);
|
||||
|
||||
n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
|
||||
rej->next = a->info->reject_service_keywords;
|
||||
a->info->reject_service_keywords = rej;
|
||||
rej->name = a->p;
|
||||
lwsl_notice(" adding rej %s=%s\n", a->p, ctx->buf);
|
||||
a->p += n - 1;
|
||||
*(a->p++) = '\0';
|
||||
rej->value = a->p;
|
||||
rej->options = NULL;
|
||||
goto dostring;
|
||||
}
|
||||
|
||||
switch (ctx->path_match - 1) {
|
||||
case LEJPGP_UID:
|
||||
a->info->uid = atoi(ctx->buf);
|
||||
return 0;
|
||||
case LEJPGP_GID:
|
||||
a->info->gid = atoi(ctx->buf);
|
||||
return 0;
|
||||
case LEJPGP_COUNT_THREADS:
|
||||
a->info->count_threads = atoi(ctx->buf);
|
||||
return 0;
|
||||
case LWJPGP_INIT_SSL:
|
||||
if (arg_to_bool(ctx->buf))
|
||||
a->info->options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
|
||||
return 0;
|
||||
case LEJPGP_SERVER_STRING:
|
||||
a->info->server_string = a->p;
|
||||
break;
|
||||
case LEJPGP_PLUGIN_DIR:
|
||||
if (a->count_plugin_dirs == MAX_PLUGIN_DIRS - 1) {
|
||||
lwsl_err("Too many plugin dirs\n");
|
||||
return -1;
|
||||
}
|
||||
a->plugin_dirs[a->count_plugin_dirs++] = a->p;
|
||||
break;
|
||||
|
||||
case LWJPGP_PINGPONG_SECS:
|
||||
a->info->ws_ping_pong_interval = atoi(ctx->buf);
|
||||
return 0;
|
||||
|
||||
case LWJPGP_TIMEOUT_SECS:
|
||||
a->info->timeout_secs = atoi(ctx->buf);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
dostring:
|
||||
a->p += lws_snprintf(a->p, a->end - a->p, "%s", ctx->buf);
|
||||
*(a->p)++ = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char
|
||||
lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
|
||||
{
|
||||
struct jpargs *a = (struct jpargs *)ctx->user;
|
||||
struct lws_protocol_vhost_options *pvo, *mp_cgienv, *headers;
|
||||
struct lws_http_mount *m;
|
||||
char *p, *p1;
|
||||
int n;
|
||||
|
||||
#if 0
|
||||
lwsl_notice(" %d: %s (%d)\n", reason, ctx->path, ctx->path_match);
|
||||
for (n = 0; n < ctx->wildcount; n++)
|
||||
lwsl_notice(" %d\n", ctx->wild[n]);
|
||||
#endif
|
||||
|
||||
if (reason == LEJPCB_OBJECT_START && ctx->path_match == LEJPVP + 1) {
|
||||
/* set the defaults for this vhost */
|
||||
a->valid = 1;
|
||||
a->head = NULL;
|
||||
a->last = NULL;
|
||||
a->info->port = 0;
|
||||
a->info->iface = NULL;
|
||||
a->info->protocols = a->protocols;
|
||||
a->info->extensions = a->extensions;
|
||||
a->info->ssl_cert_filepath = NULL;
|
||||
a->info->ssl_private_key_filepath = NULL;
|
||||
a->info->ssl_ca_filepath = NULL;
|
||||
a->info->client_ssl_cert_filepath = NULL;
|
||||
a->info->client_ssl_private_key_filepath = NULL;
|
||||
a->info->client_ssl_ca_filepath = NULL;
|
||||
a->info->client_ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:"
|
||||
"ECDHE-RSA-AES256-GCM-SHA384:"
|
||||
"DHE-RSA-AES256-GCM-SHA384:"
|
||||
"ECDHE-RSA-AES256-SHA384:"
|
||||
"HIGH:!aNULL:!eNULL:!EXPORT:"
|
||||
"!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:"
|
||||
"!SHA1:!DHE-RSA-AES128-GCM-SHA256:"
|
||||
"!DHE-RSA-AES128-SHA256:"
|
||||
"!AES128-GCM-SHA256:"
|
||||
"!AES128-SHA256:"
|
||||
"!DHE-RSA-AES256-SHA256:"
|
||||
"!AES256-GCM-SHA384:"
|
||||
"!AES256-SHA256";
|
||||
a->info->timeout_secs = 5;
|
||||
a->info->ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:"
|
||||
"ECDHE-RSA-AES256-GCM-SHA384:"
|
||||
"DHE-RSA-AES256-GCM-SHA384:"
|
||||
"ECDHE-RSA-AES256-SHA384:"
|
||||
"HIGH:!aNULL:!eNULL:!EXPORT:"
|
||||
"!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:"
|
||||
"!SHA1:!DHE-RSA-AES128-GCM-SHA256:"
|
||||
"!DHE-RSA-AES128-SHA256:"
|
||||
"!AES128-GCM-SHA256:"
|
||||
"!AES128-SHA256:"
|
||||
"!DHE-RSA-AES256-SHA256:"
|
||||
"!AES256-GCM-SHA384:"
|
||||
"!AES256-SHA256";
|
||||
a->info->pvo = NULL;
|
||||
a->info->headers = NULL;
|
||||
a->info->keepalive_timeout = 5;
|
||||
a->info->log_filepath = NULL;
|
||||
a->info->options &= ~(LWS_SERVER_OPTION_UNIX_SOCK |
|
||||
LWS_SERVER_OPTION_STS);
|
||||
a->enable_client_ssl = 0;
|
||||
}
|
||||
|
||||
if (reason == LEJPCB_OBJECT_START &&
|
||||
ctx->path_match == LEJPVP_MOUNTS + 1) {
|
||||
a->fresh_mount = 1;
|
||||
memset(&a->m, 0, sizeof(a->m));
|
||||
}
|
||||
|
||||
/* this catches, eg, vhosts[].ws-protocols[].xxx-protocol */
|
||||
if (reason == LEJPCB_OBJECT_START &&
|
||||
ctx->path_match == LEJPVP_PROTOCOL_NAME + 1) {
|
||||
a->pvo = lwsws_align(a);
|
||||
a->p += sizeof(*a->pvo);
|
||||
|
||||
n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
|
||||
/* ie, enable this protocol, no options yet */
|
||||
a->pvo->next = a->info->pvo;
|
||||
a->info->pvo = a->pvo;
|
||||
a->pvo->name = a->p;
|
||||
lwsl_notice(" adding protocol %s\n", a->p);
|
||||
a->p += n;
|
||||
a->pvo->value = a->p;
|
||||
a->pvo->options = NULL;
|
||||
goto dostring;
|
||||
}
|
||||
|
||||
/* this catches, eg, vhosts[].headers[].xxx */
|
||||
if (reason == LEJPCB_VAL_STR_END &&
|
||||
ctx->path_match == LEJPVP_HEADERS_NAME + 1) {
|
||||
headers = lwsws_align(a);
|
||||
a->p += sizeof(*headers);
|
||||
|
||||
n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
|
||||
/* ie, enable this protocol, no options yet */
|
||||
headers->next = a->info->headers;
|
||||
a->info->headers = headers;
|
||||
headers->name = a->p;
|
||||
// lwsl_notice(" adding header %s=%s\n", a->p, ctx->buf);
|
||||
a->p += n - 1;
|
||||
*(a->p++) = ':';
|
||||
if (a->p < a->end)
|
||||
*(a->p++) = '\0';
|
||||
else
|
||||
*(a->p - 1) = '\0';
|
||||
headers->value = a->p;
|
||||
headers->options = NULL;
|
||||
goto dostring;
|
||||
}
|
||||
|
||||
if (reason == LEJPCB_OBJECT_END &&
|
||||
(ctx->path_match == LEJPVP + 1 || !ctx->path[0]) &&
|
||||
a->valid) {
|
||||
|
||||
struct lws_vhost *vhost;
|
||||
|
||||
//lwsl_notice("%s\n", ctx->path);
|
||||
if (!a->info->port) {
|
||||
lwsl_err("Port required (eg, 443)");
|
||||
return 1;
|
||||
}
|
||||
a->valid = 0;
|
||||
a->info->mounts = a->head;
|
||||
|
||||
vhost = lws_create_vhost(a->context, a->info);
|
||||
if (!vhost) {
|
||||
lwsl_err("Failed to create vhost %s\n",
|
||||
a->info->vhost_name);
|
||||
return 1;
|
||||
}
|
||||
a->any_vhosts = 1;
|
||||
|
||||
if (a->enable_client_ssl) {
|
||||
const char *cert_filepath = a->info->client_ssl_cert_filepath;
|
||||
const char *private_key_filepath = a->info->client_ssl_private_key_filepath;
|
||||
const char *ca_filepath = a->info->client_ssl_ca_filepath;
|
||||
const char *cipher_list = a->info->client_ssl_cipher_list;
|
||||
memset(a->info, 0, sizeof(*a->info));
|
||||
a->info->client_ssl_cert_filepath = cert_filepath;
|
||||
a->info->client_ssl_private_key_filepath = private_key_filepath;
|
||||
a->info->client_ssl_ca_filepath = ca_filepath;
|
||||
a->info->client_ssl_cipher_list = cipher_list;
|
||||
a->info->options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
|
||||
lws_init_vhost_client_ssl(a->info, vhost);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (reason == LEJPCB_OBJECT_END &&
|
||||
ctx->path_match == LEJPVP_MOUNTS + 1) {
|
||||
static const char * const mount_protocols[] = {
|
||||
"http://",
|
||||
"https://",
|
||||
"file://",
|
||||
"cgi://",
|
||||
">http://",
|
||||
">https://",
|
||||
"callback://",
|
||||
"gzip://",
|
||||
};
|
||||
|
||||
if (!a->fresh_mount)
|
||||
return 0;
|
||||
|
||||
if (!a->m.mountpoint || !a->m.origin) {
|
||||
lwsl_err("mountpoint and origin required\n");
|
||||
return 1;
|
||||
}
|
||||
lwsl_debug("adding mount %s\n", a->m.mountpoint);
|
||||
m = lwsws_align(a);
|
||||
memcpy(m, &a->m, sizeof(*m));
|
||||
if (a->last)
|
||||
a->last->mount_next = m;
|
||||
|
||||
for (n = 0; n < ARRAY_SIZE(mount_protocols); n++)
|
||||
if (!strncmp(a->m.origin, mount_protocols[n],
|
||||
strlen(mount_protocols[n]))) {
|
||||
lwsl_err("----%s\n", a->m.origin);
|
||||
m->origin_protocol = n;
|
||||
m->origin = a->m.origin +
|
||||
strlen(mount_protocols[n]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (n == ARRAY_SIZE(mount_protocols)) {
|
||||
lwsl_err("unsupported protocol:// %s\n", a->m.origin);
|
||||
return 1;
|
||||
}
|
||||
|
||||
a->p += sizeof(*m);
|
||||
if (!a->head)
|
||||
a->head = m;
|
||||
|
||||
a->last = m;
|
||||
a->fresh_mount = 0;
|
||||
}
|
||||
|
||||
/* we only match on the prepared path strings */
|
||||
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
|
||||
return 0;
|
||||
|
||||
switch (ctx->path_match - 1) {
|
||||
case LEJPVP_NAME:
|
||||
a->info->vhost_name = a->p;
|
||||
break;
|
||||
case LEJPVP_PORT:
|
||||
a->info->port = atoi(ctx->buf);
|
||||
return 0;
|
||||
case LEJPVP_INTERFACE:
|
||||
a->info->iface = a->p;
|
||||
break;
|
||||
case LEJPVP_UNIXSKT:
|
||||
if (arg_to_bool(ctx->buf))
|
||||
a->info->options |= LWS_SERVER_OPTION_UNIX_SOCK;
|
||||
else
|
||||
a->info->options &= ~(LWS_SERVER_OPTION_UNIX_SOCK);
|
||||
return 0;
|
||||
case LEJPVP_STS:
|
||||
if (arg_to_bool(ctx->buf))
|
||||
a->info->options |= LWS_SERVER_OPTION_STS;
|
||||
else
|
||||
a->info->options &= ~(LWS_SERVER_OPTION_STS);
|
||||
return 0;
|
||||
case LEJPVP_HOST_SSL_KEY:
|
||||
a->info->ssl_private_key_filepath = a->p;
|
||||
break;
|
||||
case LEJPVP_HOST_SSL_CERT:
|
||||
a->info->ssl_cert_filepath = a->p;
|
||||
break;
|
||||
case LEJPVP_HOST_SSL_CA:
|
||||
a->info->ssl_ca_filepath = a->p;
|
||||
break;
|
||||
case LEJPVP_ACCESS_LOG:
|
||||
a->info->log_filepath = a->p;
|
||||
break;
|
||||
case LEJPVP_MOUNTPOINT:
|
||||
a->m.mountpoint = a->p;
|
||||
a->m.mountpoint_len = (unsigned char)strlen(ctx->buf);
|
||||
break;
|
||||
case LEJPVP_ORIGIN:
|
||||
if (!strncmp(ctx->buf, "callback://", 11))
|
||||
a->m.protocol = a->p + 11;
|
||||
|
||||
if (!a->m.origin)
|
||||
a->m.origin = a->p;
|
||||
break;
|
||||
case LEJPVP_DEFAULT:
|
||||
a->m.def = a->p;
|
||||
break;
|
||||
case LEJPVP_DEFAULT_AUTH_MASK:
|
||||
a->m.auth_mask = atoi(ctx->buf);
|
||||
return 0;
|
||||
case LEJPVP_MOUNT_CACHE_MAX_AGE:
|
||||
a->m.cache_max_age = atoi(ctx->buf);
|
||||
return 0;
|
||||
case LEJPVP_MOUNT_CACHE_REUSE:
|
||||
a->m.cache_reusable = arg_to_bool(ctx->buf);
|
||||
return 0;
|
||||
case LEJPVP_MOUNT_CACHE_REVALIDATE:
|
||||
a->m.cache_revalidate = arg_to_bool(ctx->buf);
|
||||
return 0;
|
||||
case LEJPVP_MOUNT_CACHE_INTERMEDIARIES:
|
||||
a->m.cache_intermediaries = arg_to_bool(ctx->buf);;
|
||||
return 0;
|
||||
case LEJPVP_MOUNT_BASIC_AUTH:
|
||||
a->m.basic_auth_login_file = a->p;
|
||||
break;
|
||||
case LEJPVP_CGI_TIMEOUT:
|
||||
a->m.cgi_timeout = atoi(ctx->buf);
|
||||
return 0;
|
||||
case LEJPVP_KEEPALIVE_TIMEOUT:
|
||||
a->info->keepalive_timeout = atoi(ctx->buf);
|
||||
return 0;
|
||||
case LEJPVP_CLIENT_CIPHERS:
|
||||
a->info->client_ssl_cipher_list = a->p;
|
||||
break;
|
||||
case LEJPVP_CIPHERS:
|
||||
a->info->ssl_cipher_list = a->p;
|
||||
break;
|
||||
case LEJPVP_ECDH_CURVE:
|
||||
a->info->ecdh_curve = a->p;
|
||||
break;
|
||||
case LEJPVP_PMO:
|
||||
case LEJPVP_CGI_ENV:
|
||||
mp_cgienv = lwsws_align(a);
|
||||
a->p += sizeof(*a->m.cgienv);
|
||||
|
||||
mp_cgienv->next = a->m.cgienv;
|
||||
a->m.cgienv = mp_cgienv;
|
||||
|
||||
n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
|
||||
mp_cgienv->name = a->p;
|
||||
a->p += n;
|
||||
mp_cgienv->value = a->p;
|
||||
mp_cgienv->options = NULL;
|
||||
//lwsl_notice(" adding pmo / cgi-env '%s' = '%s'\n", mp_cgienv->name,
|
||||
// mp_cgienv->value);
|
||||
goto dostring;
|
||||
|
||||
case LEJPVP_PROTOCOL_NAME_OPT:
|
||||
/* this catches, eg,
|
||||
* vhosts[].ws-protocols[].xxx-protocol.yyy-option
|
||||
* ie, these are options attached to a protocol with { }
|
||||
*/
|
||||
pvo = lwsws_align(a);
|
||||
a->p += sizeof(*a->pvo);
|
||||
|
||||
n = lejp_get_wildcard(ctx, 1, a->p, a->end - a->p);
|
||||
/* ie, enable this protocol, no options yet */
|
||||
pvo->next = a->pvo->options;
|
||||
a->pvo->options = pvo;
|
||||
pvo->name = a->p;
|
||||
a->p += n;
|
||||
pvo->value = a->p;
|
||||
pvo->options = NULL;
|
||||
break;
|
||||
|
||||
case LEJPVP_MOUNT_EXTRA_MIMETYPES:
|
||||
a->pvo_em = lwsws_align(a);
|
||||
a->p += sizeof(*a->pvo_em);
|
||||
|
||||
n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
|
||||
/* ie, enable this protocol, no options yet */
|
||||
a->pvo_em->next = a->m.extra_mimetypes;
|
||||
a->m.extra_mimetypes = a->pvo_em;
|
||||
a->pvo_em->name = a->p;
|
||||
lwsl_notice(" adding extra-mimetypes %s -> %s\n", a->p, ctx->buf);
|
||||
a->p += n;
|
||||
a->pvo_em->value = a->p;
|
||||
a->pvo_em->options = NULL;
|
||||
break;
|
||||
|
||||
case LEJPVP_MOUNT_INTERPRET:
|
||||
a->pvo_int = lwsws_align(a);
|
||||
a->p += sizeof(*a->pvo_int);
|
||||
|
||||
n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
|
||||
/* ie, enable this protocol, no options yet */
|
||||
a->pvo_int->next = a->m.interpret;
|
||||
a->m.interpret = a->pvo_int;
|
||||
a->pvo_int->name = a->p;
|
||||
lwsl_notice(" adding interpret %s -> %s\n", a->p,
|
||||
ctx->buf);
|
||||
a->p += n;
|
||||
a->pvo_int->value = a->p;
|
||||
a->pvo_int->options = NULL;
|
||||
break;
|
||||
|
||||
case LEJPVP_ENABLE_CLIENT_SSL:
|
||||
a->enable_client_ssl = arg_to_bool(ctx->buf);
|
||||
return 0;
|
||||
case LEJPVP_CLIENT_SSL_KEY:
|
||||
a->info->client_ssl_private_key_filepath = a->p;
|
||||
break;
|
||||
case LEJPVP_CLIENT_SSL_CERT:
|
||||
a->info->client_ssl_cert_filepath = a->p;
|
||||
break;
|
||||
case LEJPVP_CLIENT_SSL_CA:
|
||||
a->info->client_ssl_ca_filepath = a->p;
|
||||
break;
|
||||
|
||||
case LEJPVP_NOIPV6:
|
||||
if (arg_to_bool(ctx->buf))
|
||||
a->info->options |= LWS_SERVER_OPTION_DISABLE_IPV6;
|
||||
else
|
||||
a->info->options &= ~(LWS_SERVER_OPTION_DISABLE_IPV6);
|
||||
return 0;
|
||||
|
||||
case LEJPVP_IPV6ONLY:
|
||||
a->info->options |= LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY;
|
||||
if (arg_to_bool(ctx->buf))
|
||||
a->info->options |= LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE;
|
||||
else
|
||||
a->info->options &= ~(LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE);
|
||||
return 0;
|
||||
|
||||
case LEJPVP_SSL_OPTION_SET:
|
||||
a->info->ssl_options_set |= atol(ctx->buf);
|
||||
return 0;
|
||||
case LEJPVP_SSL_OPTION_CLEAR:
|
||||
a->info->ssl_options_clear |= atol(ctx->buf);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
dostring:
|
||||
p = ctx->buf;
|
||||
p1 = strstr(p, ESC_INSTALL_DATADIR);
|
||||
if (p1) {
|
||||
n = p1 - p;
|
||||
if (n > a->end - a->p)
|
||||
n = a->end - a->p;
|
||||
strncpy(a->p, p, n);
|
||||
a->p += n;
|
||||
a->p += lws_snprintf(a->p, a->end - a->p, "%s", LWS_INSTALL_DATADIR);
|
||||
p += n + strlen(ESC_INSTALL_DATADIR);
|
||||
}
|
||||
|
||||
a->p += lws_snprintf(a->p, a->end - a->p, "%s", p);
|
||||
*(a->p)++ = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* returns 0 = OK, 1 = can't open, 2 = parsing error
|
||||
*/
|
||||
|
||||
static int
|
||||
lwsws_get_config(void *user, const char *f, const char * const *paths,
|
||||
int count_paths, lejp_callback cb)
|
||||
{
|
||||
unsigned char buf[128];
|
||||
struct lejp_ctx ctx;
|
||||
int n, m, fd;
|
||||
|
||||
fd = open(f, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
lwsl_err("Cannot open %s\n", f);
|
||||
return 2;
|
||||
}
|
||||
lwsl_info("%s: %s\n", __func__, f);
|
||||
lejp_construct(&ctx, cb, user, paths, count_paths);
|
||||
|
||||
do {
|
||||
n = read(fd, buf, sizeof(buf));
|
||||
if (!n)
|
||||
break;
|
||||
|
||||
m = (int)(signed char)lejp_parse(&ctx, buf, n);
|
||||
} while (m == LEJP_CONTINUE);
|
||||
|
||||
close(fd);
|
||||
n = ctx.line;
|
||||
lejp_destruct(&ctx);
|
||||
|
||||
if (m < 0) {
|
||||
lwsl_err("%s(%u): parsing error %d: %s\n", f, n, m,
|
||||
parser_errs[-m]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(LWS_USE_LIBUV) && UV_VERSION_MAJOR > 0
|
||||
|
||||
static int
|
||||
lwsws_get_config_d(void *user, const char *d, const char * const *paths,
|
||||
int count_paths, lejp_callback cb)
|
||||
{
|
||||
uv_dirent_t dent;
|
||||
uv_fs_t req;
|
||||
char path[256];
|
||||
int ret = 0, ir;
|
||||
uv_loop_t loop;
|
||||
|
||||
ir = uv_loop_init(&loop);
|
||||
if (ir) {
|
||||
lwsl_err("%s: loop init failed %d\n", __func__, ir);
|
||||
}
|
||||
|
||||
if (!uv_fs_scandir(&loop, &req, d, 0, NULL)) {
|
||||
lwsl_err("Scandir on %s failed\n", d);
|
||||
return 2;
|
||||
}
|
||||
|
||||
while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
|
||||
lws_snprintf(path, sizeof(path) - 1, "%s/%s", d, dent.name);
|
||||
ret = lwsws_get_config(user, path, paths, count_paths, cb);
|
||||
if (ret)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
bail:
|
||||
uv_fs_req_cleanup(&req);
|
||||
while (uv_loop_close(&loop))
|
||||
;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#ifndef _WIN32
|
||||
static int filter(const struct dirent *ent)
|
||||
{
|
||||
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
lwsws_get_config_d(void *user, const char *d, const char * const *paths,
|
||||
int count_paths, lejp_callback cb)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
struct dirent **namelist;
|
||||
char path[256];
|
||||
int n, i, ret = 0;
|
||||
|
||||
n = scandir(d, &namelist, filter, alphasort);
|
||||
if (n < 0) {
|
||||
lwsl_err("Scandir on %s failed\n", d);
|
||||
}
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
lws_snprintf(path, sizeof(path) - 1, "%s/%s", d,
|
||||
namelist[i]->d_name);
|
||||
ret = lwsws_get_config(user, path, paths, count_paths, cb);
|
||||
if (ret) {
|
||||
while (i++ < n)
|
||||
free(namelist[i]);
|
||||
goto bail;
|
||||
}
|
||||
free(namelist[i]);
|
||||
}
|
||||
|
||||
bail:
|
||||
free(namelist);
|
||||
|
||||
return ret;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int
|
||||
lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d,
|
||||
char **cs, int *len)
|
||||
{
|
||||
struct jpargs a;
|
||||
const char * const *old = info->plugin_dirs;
|
||||
char dd[128];
|
||||
|
||||
memset(&a, 0, sizeof(a));
|
||||
|
||||
a.info = info;
|
||||
a.p = *cs;
|
||||
a.end = (a.p + *len) - 1;
|
||||
a.valid = 0;
|
||||
|
||||
lwsws_align(&a);
|
||||
info->plugin_dirs = (void *)a.p;
|
||||
a.plugin_dirs = (void *)a.p; /* writeable version */
|
||||
a.p += MAX_PLUGIN_DIRS * sizeof(void *);
|
||||
|
||||
/* copy any default paths */
|
||||
|
||||
while (old && *old) {
|
||||
a.plugin_dirs[a.count_plugin_dirs++] = *old;
|
||||
old++;
|
||||
}
|
||||
|
||||
lws_snprintf(dd, sizeof(dd) - 1, "%s/conf", d);
|
||||
if (lwsws_get_config(&a, dd, paths_global,
|
||||
ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
|
||||
return 1;
|
||||
lws_snprintf(dd, sizeof(dd) - 1, "%s/conf.d", d);
|
||||
if (lwsws_get_config_d(&a, dd, paths_global,
|
||||
ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
|
||||
return 1;
|
||||
|
||||
a.plugin_dirs[a.count_plugin_dirs] = NULL;
|
||||
|
||||
*cs = a.p;
|
||||
*len = a.end - a.p;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
lwsws_get_config_vhosts(struct lws_context *context,
|
||||
struct lws_context_creation_info *info, const char *d,
|
||||
char **cs, int *len)
|
||||
{
|
||||
struct jpargs a;
|
||||
char dd[128];
|
||||
|
||||
memset(&a, 0, sizeof(a));
|
||||
|
||||
a.info = info;
|
||||
a.p = *cs;
|
||||
a.end = a.p + *len;
|
||||
a.valid = 0;
|
||||
a.context = context;
|
||||
a.protocols = info->protocols;
|
||||
a.extensions = info->extensions;
|
||||
|
||||
lws_snprintf(dd, sizeof(dd) - 1, "%s/conf", d);
|
||||
if (lwsws_get_config(&a, dd, paths_vhosts,
|
||||
ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1)
|
||||
return 1;
|
||||
lws_snprintf(dd, sizeof(dd) - 1, "%s/conf.d", d);
|
||||
if (lwsws_get_config_d(&a, dd, paths_vhosts,
|
||||
ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1)
|
||||
return 1;
|
||||
|
||||
*cs = a.p;
|
||||
*len = a.end - a.p;
|
||||
|
||||
if (!a.any_vhosts) {
|
||||
lwsl_err("Need at least one vhost\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// lws_finalize_startup(context);
|
||||
|
||||
return 0;
|
||||
}
|
709
lib/lejp.c
Normal file
709
lib/lejp.c
Normal file
|
@ -0,0 +1,709 @@
|
|||
/*
|
||||
* Lightweight Embedded JSON Parser
|
||||
*
|
||||
* Copyright (C) 2013 Andy Green <andy@warmcat.com>
|
||||
* This code is licensed under LGPL 2.1
|
||||
* http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "lejp.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* lejp_construct - prepare a struct lejp_ctx for use
|
||||
*
|
||||
* \param ctx: pointer to your struct lejp_ctx
|
||||
* \param callback: your user callback which will received parsed tokens
|
||||
* \param user: optional user data pointer untouched by lejp
|
||||
* \param paths: your array of name elements you are interested in
|
||||
* \param count_paths: ARRAY_SIZE() of @paths
|
||||
*
|
||||
* Prepares your context struct for use with lejp
|
||||
*/
|
||||
|
||||
void
|
||||
lejp_construct(struct lejp_ctx *ctx,
|
||||
char (*callback)(struct lejp_ctx *ctx, char reason), void *user,
|
||||
const char * const *paths, unsigned char count_paths)
|
||||
{
|
||||
ctx->st[0].s = 0;
|
||||
ctx->st[0].p = 0;
|
||||
ctx->st[0].i = 0;
|
||||
ctx->st[0].b = 0;
|
||||
ctx->sp = 0;
|
||||
ctx->ipos = 0;
|
||||
ctx->ppos = 0;
|
||||
ctx->path_match = 0;
|
||||
ctx->path[0] = '\0';
|
||||
ctx->callback = callback;
|
||||
ctx->user = user;
|
||||
ctx->paths = paths;
|
||||
ctx->count_paths = count_paths;
|
||||
ctx->line = 1;
|
||||
ctx->callback(ctx, LEJPCB_CONSTRUCTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* lejp_destruct - retire a previously constructed struct lejp_ctx
|
||||
*
|
||||
* \param ctx: pointer to your struct lejp_ctx
|
||||
*
|
||||
* lejp does not perform any allocations, but since your user code might, this
|
||||
* provides a one-time LEJPCB_DESTRUCTED callback at destruction time where
|
||||
* you can clean up in your callback.
|
||||
*/
|
||||
|
||||
void
|
||||
lejp_destruct(struct lejp_ctx *ctx)
|
||||
{
|
||||
/* no allocations... just let callback know what it happening */
|
||||
ctx->callback(ctx, LEJPCB_DESTRUCTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* lejp_change_callback - switch to a different callback from now on
|
||||
*
|
||||
* \param ctx: pointer to your struct lejp_ctx
|
||||
* \param callback: your user callback which will received parsed tokens
|
||||
*
|
||||
* This tells the old callback it was destroyed, in case you want to take any
|
||||
* action because that callback "lost focus", then changes to the new
|
||||
* callback and tells it first that it was constructed, and then started.
|
||||
*
|
||||
* Changing callback is a cheap and powerful trick to split out handlers
|
||||
* according to information earlier in the parse. For example you may have
|
||||
* a JSON pair "schema" whose value defines what can be expected for the rest
|
||||
* of the JSON. Rather than having one huge callback for all cases, you can
|
||||
* have an initial one looking for "schema" which then calls
|
||||
* lejp_change_callback() to a handler specific for the schema.
|
||||
*
|
||||
* Notice that afterwards, you need to construct the context again anyway to
|
||||
* parse another JSON object, and the callback is reset then to the main,
|
||||
* schema-interpreting one. The construction action is very lightweight.
|
||||
*/
|
||||
|
||||
void
|
||||
lejp_change_callback(struct lejp_ctx *ctx,
|
||||
char (*callback)(struct lejp_ctx *ctx, char reason))
|
||||
{
|
||||
ctx->callback(ctx, LEJPCB_DESTRUCTED);
|
||||
ctx->callback = callback;
|
||||
ctx->callback(ctx, LEJPCB_CONSTRUCTED);
|
||||
ctx->callback(ctx, LEJPCB_START);
|
||||
}
|
||||
|
||||
static void
|
||||
lejp_check_path_match(struct lejp_ctx *ctx)
|
||||
{
|
||||
const char *p, *q;
|
||||
int n;
|
||||
|
||||
/* we only need to check if a match is not active */
|
||||
for (n = 0; !ctx->path_match && n < ctx->count_paths; n++) {
|
||||
ctx->wildcount = 0;
|
||||
p = ctx->path;
|
||||
q = ctx->paths[n];
|
||||
while (*p && *q) {
|
||||
if (*q != '*') {
|
||||
if (*p != *q)
|
||||
break;
|
||||
p++;
|
||||
q++;
|
||||
continue;
|
||||
}
|
||||
ctx->wild[ctx->wildcount++] = p - ctx->path;
|
||||
q++;
|
||||
/*
|
||||
* if * has something after it, match to .
|
||||
* if ends with *, eat everything.
|
||||
* This implies match sequences must be ordered like
|
||||
* x.*.*
|
||||
* x.*
|
||||
* if both options are possible
|
||||
*/
|
||||
while (*p && (*p != '.' || !*q))
|
||||
p++;
|
||||
}
|
||||
if (*p || *q)
|
||||
continue;
|
||||
|
||||
ctx->path_match = n + 1;
|
||||
ctx->path_match_len = ctx->ppos;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx->path_match)
|
||||
ctx->wildcount = 0;
|
||||
}
|
||||
|
||||
int
|
||||
lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (wildcard >= ctx->wildcount || !len)
|
||||
return 0;
|
||||
|
||||
n = ctx->wild[wildcard];
|
||||
|
||||
while (--len && n < ctx->ppos && (n == ctx->wild[wildcard] || ctx->path[n] != '.'))
|
||||
*dest++ = ctx->path[n++];
|
||||
|
||||
*dest = '\0';
|
||||
n++;
|
||||
|
||||
return n - ctx->wild[wildcard];
|
||||
}
|
||||
|
||||
/**
|
||||
* lejp_parse - interpret some more incoming data incrementally
|
||||
*
|
||||
* \param ctx: previously constructed parsing context
|
||||
* \param json: char buffer with the new data to interpret
|
||||
* \param len: amount of data in the buffer
|
||||
*
|
||||
* Because lejp is a stream parser, it incrementally parses as new data
|
||||
* becomes available, maintaining all state in the context struct. So an
|
||||
* incomplete JSON is a normal situation, getting you a LEJP_CONTINUE
|
||||
* return, signalling there's no error but to call again with more data when
|
||||
* it comes to complete the parsing. Successful parsing completes with a
|
||||
* 0 or positive integer indicating how much of the last input buffer was
|
||||
* unused.
|
||||
*/
|
||||
|
||||
int
|
||||
lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
|
||||
{
|
||||
unsigned char c, n, s, ret = LEJP_REJECT_UNKNOWN;
|
||||
static const char esc_char[] = "\"\\/bfnrt";
|
||||
static const char esc_tran[] = "\"\\/\b\f\n\r\t";
|
||||
static const char tokens[] = "rue alse ull ";
|
||||
|
||||
if (!ctx->sp && !ctx->ppos)
|
||||
ctx->callback(ctx, LEJPCB_START);
|
||||
|
||||
while (len--) {
|
||||
c = *json++;
|
||||
|
||||
s = ctx->st[ctx->sp].s;
|
||||
|
||||
/* skip whitespace unless we should care */
|
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '#') {
|
||||
if (c == '\n') {
|
||||
ctx->line++;
|
||||
ctx->st[ctx->sp].s &= ~LEJP_FLAG_WS_COMMENTLINE;
|
||||
}
|
||||
if (!(s & LEJP_FLAG_WS_KEEP)) {
|
||||
if (c == '#')
|
||||
ctx->st[ctx->sp].s |=
|
||||
LEJP_FLAG_WS_COMMENTLINE;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->st[ctx->sp].s & LEJP_FLAG_WS_COMMENTLINE)
|
||||
continue;
|
||||
|
||||
switch (s) {
|
||||
case LEJP_IDLE:
|
||||
if (c != '{') {
|
||||
ret = LEJP_REJECT_IDLE_NO_BRACE;
|
||||
goto reject;
|
||||
}
|
||||
if (ctx->callback(ctx, LEJPCB_OBJECT_START)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
ctx->st[ctx->sp].s = LEJP_MEMBERS;
|
||||
break;
|
||||
case LEJP_MEMBERS:
|
||||
if (c == '}') {
|
||||
ctx->st[ctx->sp].s = LEJP_IDLE;
|
||||
ret = LEJP_REJECT_MEMBERS_NO_CLOSE;
|
||||
goto reject;
|
||||
}
|
||||
ctx->st[ctx->sp].s = LEJP_M_P;
|
||||
goto redo_character;
|
||||
case LEJP_M_P:
|
||||
if (c != '\"') {
|
||||
ret = LEJP_REJECT_MP_NO_OPEN_QUOTE;
|
||||
goto reject;
|
||||
}
|
||||
/* push */
|
||||
ctx->st[ctx->sp].s = LEJP_MP_DELIM;
|
||||
c = LEJP_MP_STRING;
|
||||
goto add_stack_level;
|
||||
|
||||
case LEJP_MP_STRING:
|
||||
if (c == '\"') {
|
||||
if (!ctx->sp) {
|
||||
ret = LEJP_REJECT_MP_STRING_UNDERRUN;
|
||||
goto reject;
|
||||
}
|
||||
if (ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) {
|
||||
ctx->buf[ctx->npos] = '\0';
|
||||
if (ctx->callback(ctx,
|
||||
LEJPCB_VAL_STR_END) < 0) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
}
|
||||
/* pop */
|
||||
ctx->sp--;
|
||||
break;
|
||||
}
|
||||
if (c == '\\') {
|
||||
ctx->st[ctx->sp].s = LEJP_MP_STRING_ESC;
|
||||
break;
|
||||
}
|
||||
if (c < ' ') {/* "control characters" not allowed */
|
||||
ret = LEJP_REJECT_MP_ILLEGAL_CTRL;
|
||||
goto reject;
|
||||
}
|
||||
goto emit_string_char;
|
||||
|
||||
case LEJP_MP_STRING_ESC:
|
||||
if (c == 'u') {
|
||||
ctx->st[ctx->sp].s = LEJP_MP_STRING_ESC_U1;
|
||||
ctx->uni = 0;
|
||||
break;
|
||||
}
|
||||
for (n = 0; n < sizeof(esc_char); n++) {
|
||||
if (c != esc_char[n])
|
||||
continue;
|
||||
/* found it */
|
||||
c = esc_tran[n];
|
||||
ctx->st[ctx->sp].s = LEJP_MP_STRING;
|
||||
goto emit_string_char;
|
||||
}
|
||||
ret = LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC;
|
||||
/* illegal escape char */
|
||||
goto reject;
|
||||
|
||||
case LEJP_MP_STRING_ESC_U1:
|
||||
case LEJP_MP_STRING_ESC_U2:
|
||||
case LEJP_MP_STRING_ESC_U3:
|
||||
case LEJP_MP_STRING_ESC_U4:
|
||||
ctx->uni <<= 4;
|
||||
if (c >= '0' && c <= '9')
|
||||
ctx->uni |= c - '0';
|
||||
else
|
||||
if (c >= 'a' && c <= 'f')
|
||||
ctx->uni = c - 'a' + 10;
|
||||
else
|
||||
if (c >= 'A' && c <= 'F')
|
||||
ctx->uni = c - 'A' + 10;
|
||||
else {
|
||||
ret = LEJP_REJECT_ILLEGAL_HEX;
|
||||
goto reject;
|
||||
}
|
||||
ctx->st[ctx->sp].s++;
|
||||
switch (s) {
|
||||
case LEJP_MP_STRING_ESC_U2:
|
||||
if (ctx->uni < 0x08)
|
||||
break;
|
||||
/*
|
||||
* 0x08-0xff (0x0800 - 0xffff)
|
||||
* emit 3-byte UTF-8
|
||||
*/
|
||||
c = 0xe0 | ((ctx->uni >> 4) & 0xf);
|
||||
goto emit_string_char;
|
||||
|
||||
case LEJP_MP_STRING_ESC_U3:
|
||||
if (ctx->uni >= 0x080) {
|
||||
/*
|
||||
* 0x080 - 0xfff (0x0800 - 0xffff)
|
||||
* middle 3-byte seq
|
||||
* send ....XXXXXX..
|
||||
*/
|
||||
c = 0x80 | ((ctx->uni >> 2) & 0x3f);
|
||||
goto emit_string_char;
|
||||
}
|
||||
if (ctx->uni < 0x008)
|
||||
break;
|
||||
/*
|
||||
* 0x008 - 0x7f (0x0080 - 0x07ff)
|
||||
* start 2-byte seq
|
||||
*/
|
||||
c = 0xc0 | (ctx->uni >> 2);
|
||||
goto emit_string_char;
|
||||
|
||||
case LEJP_MP_STRING_ESC_U4:
|
||||
if (ctx->uni >= 0x0080)
|
||||
/* end of 2 or 3-byte seq */
|
||||
c = 0x80 | (ctx->uni & 0x3f);
|
||||
else
|
||||
/* literal */
|
||||
c = (unsigned char)ctx->uni;
|
||||
|
||||
ctx->st[ctx->sp].s = LEJP_MP_STRING;
|
||||
goto emit_string_char;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case LEJP_MP_DELIM:
|
||||
if (c != ':') {
|
||||
ret = LEJP_REJECT_MP_DELIM_MISSING_COLON;
|
||||
goto reject;
|
||||
}
|
||||
ctx->st[ctx->sp].s = LEJP_MP_VALUE;
|
||||
ctx->path[ctx->ppos] = '\0';
|
||||
|
||||
lejp_check_path_match(ctx);
|
||||
if (ctx->callback(ctx, LEJPCB_PAIR_NAME)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
break;
|
||||
|
||||
case LEJP_MP_VALUE:
|
||||
if (c >= '0' && c <= '9') {
|
||||
ctx->npos = 0;
|
||||
ctx->dcount = 0;
|
||||
ctx->f = 0;
|
||||
ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_INT;
|
||||
goto redo_character;
|
||||
}
|
||||
switch (c) {
|
||||
case'\"':
|
||||
/* push */
|
||||
ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
|
||||
c = LEJP_MP_STRING;
|
||||
ctx->npos = 0;
|
||||
ctx->buf[0] = '\0';
|
||||
if (ctx->callback(ctx, LEJPCB_VAL_STR_START)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
goto add_stack_level;
|
||||
|
||||
case '{':
|
||||
/* push */
|
||||
ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
|
||||
c = LEJP_MEMBERS;
|
||||
lejp_check_path_match(ctx);
|
||||
if (ctx->callback(ctx, LEJPCB_OBJECT_START)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
ctx->path_match = 0;
|
||||
goto add_stack_level;
|
||||
|
||||
case '[':
|
||||
/* push */
|
||||
ctx->st[ctx->sp].s = LEJP_MP_ARRAY_END;
|
||||
c = LEJP_MP_VALUE;
|
||||
ctx->path[ctx->ppos++] = '[';
|
||||
ctx->path[ctx->ppos++] = ']';
|
||||
ctx->path[ctx->ppos] = '\0';
|
||||
if (ctx->callback(ctx, LEJPCB_ARRAY_START)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
ctx->i[ctx->ipos++] = 0;
|
||||
if (ctx->ipos > ARRAY_SIZE(ctx->i)) {
|
||||
ret = LEJP_REJECT_MP_DELIM_ISTACK;
|
||||
goto reject;
|
||||
}
|
||||
goto add_stack_level;
|
||||
|
||||
case 't': /* true */
|
||||
ctx->uni = 0;
|
||||
ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
ctx->uni = 4;
|
||||
ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
ctx->uni = 4 + 5;
|
||||
ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK;
|
||||
break;
|
||||
default:
|
||||
ret = LEJP_REJECT_MP_DELIM_BAD_VALUE_START;
|
||||
goto reject;
|
||||
}
|
||||
break;
|
||||
|
||||
case LEJP_MP_VALUE_NUM_INT:
|
||||
if (!ctx->npos && c == '-') {
|
||||
ctx->f |= LEJP_SEEN_MINUS;
|
||||
goto append_npos;
|
||||
}
|
||||
|
||||
if (ctx->dcount < 10 && c >= '0' && c <= '9') {
|
||||
if (ctx->f & LEJP_SEEN_POINT)
|
||||
ctx->f |= LEJP_SEEN_POST_POINT;
|
||||
ctx->dcount++;
|
||||
goto append_npos;
|
||||
}
|
||||
if (c == '.') {
|
||||
if (ctx->dcount || (ctx->f & LEJP_SEEN_POINT)) {
|
||||
ret = LEJP_REJECT_MP_VAL_NUM_FORMAT;
|
||||
goto reject;
|
||||
}
|
||||
ctx->f |= LEJP_SEEN_POINT;
|
||||
goto append_npos;
|
||||
}
|
||||
/*
|
||||
* before exponent, if we had . we must have had at
|
||||
* least one more digit
|
||||
*/
|
||||
if ((ctx->f &
|
||||
(LEJP_SEEN_POINT | LEJP_SEEN_POST_POINT)) ==
|
||||
LEJP_SEEN_POINT) {
|
||||
ret = LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC;
|
||||
goto reject;
|
||||
}
|
||||
if (c == 'e' || c == 'E') {
|
||||
if (ctx->f & LEJP_SEEN_EXP) {
|
||||
ret = LEJP_REJECT_MP_VAL_NUM_FORMAT;
|
||||
goto reject;
|
||||
}
|
||||
ctx->f |= LEJP_SEEN_EXP;
|
||||
ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_EXP;
|
||||
goto append_npos;
|
||||
}
|
||||
/* if none of the above, did we even have a number? */
|
||||
if (!ctx->dcount) {
|
||||
ret = LEJP_REJECT_MP_VAL_NUM_FORMAT;
|
||||
goto reject;
|
||||
}
|
||||
|
||||
ctx->buf[ctx->npos] = '\0';
|
||||
if (ctx->f & LEJP_SEEN_POINT) {
|
||||
if (ctx->callback(ctx, LEJPCB_VAL_NUM_FLOAT)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
} else {
|
||||
if (ctx->callback(ctx, LEJPCB_VAL_NUM_INT)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
}
|
||||
|
||||
/* then this is the post-number character, loop */
|
||||
ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
|
||||
goto redo_character;
|
||||
|
||||
case LEJP_MP_VALUE_NUM_EXP:
|
||||
ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_INT;
|
||||
if (c >= '0' && c <= '9')
|
||||
goto redo_character;
|
||||
if (c == '+' || c == '-')
|
||||
goto append_npos;
|
||||
ret = LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP;
|
||||
goto reject;
|
||||
|
||||
case LEJP_MP_VALUE_TOK: /* true, false, null */
|
||||
if (c != tokens[ctx->uni]) {
|
||||
ret = LEJP_REJECT_MP_VAL_TOK_UNKNOWN;
|
||||
goto reject;
|
||||
}
|
||||
ctx->uni++;
|
||||
if (tokens[ctx->uni] != ' ')
|
||||
break;
|
||||
switch (ctx->uni) {
|
||||
case 3:
|
||||
ctx->buf[0] = '1';
|
||||
ctx->buf[1] = '\0';
|
||||
if (ctx->callback(ctx, LEJPCB_VAL_TRUE)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
ctx->buf[0] = '0';
|
||||
ctx->buf[1] = '\0';
|
||||
if (ctx->callback(ctx, LEJPCB_VAL_FALSE)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
ctx->buf[0] = '\0';
|
||||
if (ctx->callback(ctx, LEJPCB_VAL_NULL)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
break;
|
||||
}
|
||||
ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
|
||||
break;
|
||||
|
||||
case LEJP_MP_COMMA_OR_END:
|
||||
ctx->path[ctx->ppos] = '\0';
|
||||
if (c == ',') {
|
||||
/* increment this stack level's index */
|
||||
ctx->st[ctx->sp].s = LEJP_M_P;
|
||||
if (!ctx->sp) {
|
||||
ctx->ppos = 0;
|
||||
/*
|
||||
* since we came back to root level,
|
||||
* no path can still match
|
||||
*/
|
||||
ctx->path_match = 0;
|
||||
break;
|
||||
}
|
||||
ctx->ppos = ctx->st[ctx->sp - 1].p;
|
||||
ctx->path[ctx->ppos] = '\0';
|
||||
if (ctx->path_match &&
|
||||
ctx->ppos <= ctx->path_match_len)
|
||||
/*
|
||||
* we shrank the path to be
|
||||
* smaller than the matching point
|
||||
*/
|
||||
ctx->path_match = 0;
|
||||
|
||||
if (ctx->st[ctx->sp - 1].s != LEJP_MP_ARRAY_END)
|
||||
break;
|
||||
/* top level is definitely an array... */
|
||||
if (ctx->ipos)
|
||||
ctx->i[ctx->ipos - 1]++;
|
||||
ctx->st[ctx->sp].s = LEJP_MP_VALUE;
|
||||
break;
|
||||
}
|
||||
if (c == ']') {
|
||||
if (!ctx->sp) {
|
||||
ret = LEJP_REJECT_MP_C_OR_E_UNDERF;
|
||||
goto reject;
|
||||
}
|
||||
/* pop */
|
||||
ctx->sp--;
|
||||
if (ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) {
|
||||
ret = LEJP_REJECT_MP_C_OR_E_NOTARRAY;
|
||||
goto reject;
|
||||
}
|
||||
/* drop the path [n] bit */
|
||||
ctx->ppos = ctx->st[ctx->sp - 1].p;
|
||||
ctx->ipos = ctx->st[ctx->sp - 1].i;
|
||||
ctx->path[ctx->ppos] = '\0';
|
||||
if (ctx->path_match &&
|
||||
ctx->ppos <= ctx->path_match_len)
|
||||
/*
|
||||
* we shrank the path to be
|
||||
* smaller than the matching point
|
||||
*/
|
||||
ctx->path_match = 0;
|
||||
|
||||
/* do LEJP_MP_ARRAY_END processing */
|
||||
goto redo_character;
|
||||
}
|
||||
if (c == '}') {
|
||||
if (ctx->sp == 0) {
|
||||
lejp_check_path_match(ctx);
|
||||
if (ctx->callback(ctx, LEJPCB_OBJECT_END)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
ctx->callback(ctx, LEJPCB_COMPLETE);
|
||||
/* done, return unused amount */
|
||||
return len;
|
||||
}
|
||||
/* pop */
|
||||
ctx->sp--;
|
||||
ctx->ppos = ctx->st[ctx->sp - 1].p;
|
||||
ctx->ipos = ctx->st[ctx->sp - 1].i;
|
||||
ctx->path[ctx->ppos] = '\0';
|
||||
if (ctx->path_match &&
|
||||
ctx->ppos <= ctx->path_match_len)
|
||||
/*
|
||||
* we shrank the path to be
|
||||
* smaller than the matching point
|
||||
*/
|
||||
ctx->path_match = 0;
|
||||
lejp_check_path_match(ctx);
|
||||
if (ctx->callback(ctx, LEJPCB_OBJECT_END)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ret = LEJP_REJECT_MP_C_OR_E_NEITHER;
|
||||
goto reject;
|
||||
|
||||
case LEJP_MP_ARRAY_END:
|
||||
ctx->path[ctx->ppos] = '\0';
|
||||
if (c == ',') {
|
||||
/* increment this stack level's index */
|
||||
if (ctx->ipos)
|
||||
ctx->i[ctx->ipos - 1]++;
|
||||
ctx->st[ctx->sp].s = LEJP_MP_VALUE;
|
||||
if (ctx->sp)
|
||||
ctx->ppos = ctx->st[ctx->sp - 1].p;
|
||||
ctx->path[ctx->ppos] = '\0';
|
||||
break;
|
||||
}
|
||||
if (c != ']') {
|
||||
ret = LEJP_REJECT_MP_ARRAY_END_MISSING;
|
||||
goto reject;
|
||||
}
|
||||
|
||||
ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
|
||||
ctx->callback(ctx, LEJPCB_ARRAY_END);
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
emit_string_char:
|
||||
if (!ctx->sp || ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) {
|
||||
/* assemble the string value into chunks */
|
||||
ctx->buf[ctx->npos++] = c;
|
||||
if (ctx->npos == sizeof(ctx->buf) - 1) {
|
||||
if (ctx->callback(ctx, LEJPCB_VAL_STR_CHUNK)) {
|
||||
ret = LEJP_REJECT_CALLBACK;
|
||||
goto reject;
|
||||
}
|
||||
ctx->npos = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* name part of name:value pair */
|
||||
ctx->path[ctx->ppos++] = c;
|
||||
continue;
|
||||
|
||||
add_stack_level:
|
||||
/* push on to the object stack */
|
||||
if (ctx->ppos && ctx->st[ctx->sp].s != LEJP_MP_COMMA_OR_END &&
|
||||
ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END)
|
||||
ctx->path[ctx->ppos++] = '.';
|
||||
|
||||
ctx->st[ctx->sp].p = ctx->ppos;
|
||||
ctx->st[ctx->sp].i = ctx->ipos;
|
||||
if (++ctx->sp == ARRAY_SIZE(ctx->st)) {
|
||||
ret = LEJP_REJECT_STACK_OVERFLOW;
|
||||
goto reject;
|
||||
}
|
||||
ctx->path[ctx->ppos] = '\0';
|
||||
ctx->st[ctx->sp].s = c;
|
||||
ctx->st[ctx->sp].b = 0;
|
||||
continue;
|
||||
|
||||
append_npos:
|
||||
if (ctx->npos >= sizeof(ctx->buf)) {
|
||||
ret = LEJP_REJECT_NUM_TOO_LONG;
|
||||
goto reject;
|
||||
}
|
||||
ctx->buf[ctx->npos++] = c;
|
||||
continue;
|
||||
|
||||
redo_character:
|
||||
json--;
|
||||
len++;
|
||||
}
|
||||
|
||||
return LEJP_CONTINUE;
|
||||
|
||||
reject:
|
||||
ctx->callback(ctx, LEJPCB_FAILED);
|
||||
return ret;
|
||||
}
|
232
lib/lejp.h
Normal file
232
lib/lejp.h
Normal file
|
@ -0,0 +1,232 @@
|
|||
#include "libwebsockets.h"
|
||||
struct lejp_ctx;
|
||||
|
||||
#ifndef ARRAY_SIZE
|
||||
#define ARRAY_SIZE(_x) (sizeof(_x) / sizeof(_x[0]))
|
||||
#endif
|
||||
#define LEJP_FLAG_WS_KEEP 64
|
||||
#define LEJP_FLAG_WS_COMMENTLINE 32
|
||||
|
||||
enum lejp_states {
|
||||
LEJP_IDLE = 0,
|
||||
LEJP_MEMBERS = 1,
|
||||
LEJP_M_P = 2,
|
||||
LEJP_MP_STRING = LEJP_FLAG_WS_KEEP | 3,
|
||||
LEJP_MP_STRING_ESC = LEJP_FLAG_WS_KEEP | 4,
|
||||
LEJP_MP_STRING_ESC_U1 = LEJP_FLAG_WS_KEEP | 5,
|
||||
LEJP_MP_STRING_ESC_U2 = LEJP_FLAG_WS_KEEP | 6,
|
||||
LEJP_MP_STRING_ESC_U3 = LEJP_FLAG_WS_KEEP | 7,
|
||||
LEJP_MP_STRING_ESC_U4 = LEJP_FLAG_WS_KEEP | 8,
|
||||
LEJP_MP_DELIM = 9,
|
||||
LEJP_MP_VALUE = 10,
|
||||
LEJP_MP_VALUE_NUM_INT = LEJP_FLAG_WS_KEEP | 11,
|
||||
LEJP_MP_VALUE_NUM_EXP = LEJP_FLAG_WS_KEEP | 12,
|
||||
LEJP_MP_VALUE_TOK = LEJP_FLAG_WS_KEEP | 13,
|
||||
LEJP_MP_COMMA_OR_END = 14,
|
||||
LEJP_MP_ARRAY_END = 15,
|
||||
};
|
||||
|
||||
enum lejp_reasons {
|
||||
LEJP_CONTINUE = -1,
|
||||
LEJP_REJECT_IDLE_NO_BRACE = -2,
|
||||
LEJP_REJECT_MEMBERS_NO_CLOSE = -3,
|
||||
LEJP_REJECT_MP_NO_OPEN_QUOTE = -4,
|
||||
LEJP_REJECT_MP_STRING_UNDERRUN = -5,
|
||||
LEJP_REJECT_MP_ILLEGAL_CTRL = -6,
|
||||
LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC = -7,
|
||||
LEJP_REJECT_ILLEGAL_HEX = -8,
|
||||
LEJP_REJECT_MP_DELIM_MISSING_COLON = -9,
|
||||
LEJP_REJECT_MP_DELIM_BAD_VALUE_START = -10,
|
||||
LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC = -11,
|
||||
LEJP_REJECT_MP_VAL_NUM_FORMAT = -12,
|
||||
LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP = -13,
|
||||
LEJP_REJECT_MP_VAL_TOK_UNKNOWN = -14,
|
||||
LEJP_REJECT_MP_C_OR_E_UNDERF = -15,
|
||||
LEJP_REJECT_MP_C_OR_E_NOTARRAY = -16,
|
||||
LEJP_REJECT_MP_ARRAY_END_MISSING = -17,
|
||||
LEJP_REJECT_STACK_OVERFLOW = -18,
|
||||
LEJP_REJECT_MP_DELIM_ISTACK = -19,
|
||||
LEJP_REJECT_NUM_TOO_LONG = -20,
|
||||
LEJP_REJECT_MP_C_OR_E_NEITHER = -21,
|
||||
LEJP_REJECT_UNKNOWN = -22,
|
||||
LEJP_REJECT_CALLBACK = -23
|
||||
};
|
||||
|
||||
#define LEJP_FLAG_CB_IS_VALUE 64
|
||||
|
||||
enum lejp_callbacks {
|
||||
LEJPCB_CONSTRUCTED = 0,
|
||||
LEJPCB_DESTRUCTED = 1,
|
||||
|
||||
LEJPCB_START = 2,
|
||||
LEJPCB_COMPLETE = 3,
|
||||
LEJPCB_FAILED = 4,
|
||||
|
||||
LEJPCB_PAIR_NAME = 5,
|
||||
|
||||
LEJPCB_VAL_TRUE = LEJP_FLAG_CB_IS_VALUE | 6,
|
||||
LEJPCB_VAL_FALSE = LEJP_FLAG_CB_IS_VALUE | 7,
|
||||
LEJPCB_VAL_NULL = LEJP_FLAG_CB_IS_VALUE | 8,
|
||||
LEJPCB_VAL_NUM_INT = LEJP_FLAG_CB_IS_VALUE | 9,
|
||||
LEJPCB_VAL_NUM_FLOAT = LEJP_FLAG_CB_IS_VALUE | 10,
|
||||
LEJPCB_VAL_STR_START = 11, /* notice handle separately */
|
||||
LEJPCB_VAL_STR_CHUNK = LEJP_FLAG_CB_IS_VALUE | 12,
|
||||
LEJPCB_VAL_STR_END = LEJP_FLAG_CB_IS_VALUE | 13,
|
||||
|
||||
LEJPCB_ARRAY_START = 14,
|
||||
LEJPCB_ARRAY_END = 15,
|
||||
|
||||
LEJPCB_OBJECT_START = 16,
|
||||
LEJPCB_OBJECT_END = 17
|
||||
};
|
||||
|
||||
/**
|
||||
* _lejp_callback() - User parser actions
|
||||
* \param ctx: LEJP context
|
||||
* \param reason: Callback reason
|
||||
*
|
||||
* Your user callback is associated with the context at construction time,
|
||||
* and receives calls as the parsing progresses.
|
||||
*
|
||||
* All of the callbacks may be ignored and just return 0.
|
||||
*
|
||||
* The reasons it might get called, found in @reason, are:
|
||||
*
|
||||
* LEJPCB_CONSTRUCTED: The context was just constructed... you might want to
|
||||
* perform one-time allocation for the life of the context.
|
||||
*
|
||||
* LEJPCB_DESTRUCTED: The context is being destructed... if you made any
|
||||
* allocations at construction-time, you can free them now
|
||||
*
|
||||
* LEJPCB_START: Parsing is beginning at the first byte of input
|
||||
*
|
||||
* LEJPCB_COMPLETE: Parsing has completed successfully. You'll get a 0 or
|
||||
* positive return code from lejp_parse indicating the
|
||||
* amount of unused bytes left in the input buffer
|
||||
*
|
||||
* LEJPCB_FAILED: Parsing failed. You'll get a negative error code
|
||||
* returned from lejp_parse
|
||||
*
|
||||
* LEJPCB_PAIR_NAME: When a "name":"value" pair has had the name parsed,
|
||||
* this callback occurs. You can find the new name at
|
||||
* the end of ctx->path[]
|
||||
*
|
||||
* LEJPCB_VAL_TRUE: The "true" value appeared
|
||||
*
|
||||
* LEJPCB_VAL_FALSE: The "false" value appeared
|
||||
*
|
||||
* LEJPCB_VAL_NULL: The "null" value appeared
|
||||
*
|
||||
* LEJPCB_VAL_NUM_INT: A string representing an integer is in ctx->buf
|
||||
*
|
||||
* LEJPCB_VAL_NUM_FLOAT: A string representing a float is in ctx->buf
|
||||
*
|
||||
* LEJPCB_VAL_STR_START: We are starting to parse a string, no data yet
|
||||
*
|
||||
* LEJPCB_VAL_STR_CHUNK: We parsed LEJP_STRING_CHUNK -1 bytes of string data in
|
||||
* ctx->buf, which is as much as we can buffer, so we are
|
||||
* spilling it. If all your strings are less than
|
||||
* LEJP_STRING_CHUNK - 1 bytes, you will never see this
|
||||
* callback.
|
||||
*
|
||||
* LEJPCB_VAL_STR_END: String parsing has completed, the last chunk of the
|
||||
* string is in ctx->buf.
|
||||
*
|
||||
* LEJPCB_ARRAY_START: An array started
|
||||
*
|
||||
* LEJPCB_ARRAY_END: An array ended
|
||||
*
|
||||
* LEJPCB_OBJECT_START: An object started
|
||||
*
|
||||
* LEJPCB_OBJECT_END: An object ended
|
||||
*/
|
||||
LWS_EXTERN char _lejp_callback(struct lejp_ctx *ctx, char reason);
|
||||
|
||||
typedef char (*lejp_callback)(struct lejp_ctx *ctx, char reason);
|
||||
|
||||
#ifndef LEJP_MAX_DEPTH
|
||||
#define LEJP_MAX_DEPTH 12
|
||||
#endif
|
||||
#ifndef LEJP_MAX_INDEX_DEPTH
|
||||
#define LEJP_MAX_INDEX_DEPTH 5
|
||||
#endif
|
||||
#ifndef LEJP_MAX_PATH
|
||||
#define LEJP_MAX_PATH 128
|
||||
#endif
|
||||
#ifndef LEJP_STRING_CHUNK
|
||||
/* must be >= 30 to assemble floats */
|
||||
#define LEJP_STRING_CHUNK 255
|
||||
#endif
|
||||
|
||||
enum num_flags {
|
||||
LEJP_SEEN_MINUS = (1 << 0),
|
||||
LEJP_SEEN_POINT = (1 << 1),
|
||||
LEJP_SEEN_POST_POINT = (1 << 2),
|
||||
LEJP_SEEN_EXP = (1 << 3)
|
||||
};
|
||||
|
||||
struct _lejp_stack {
|
||||
char s; /* lejp_state stack*/
|
||||
char p; /* path length */
|
||||
char i; /* index array length */
|
||||
char b; /* user bitfield */
|
||||
};
|
||||
|
||||
struct lejp_ctx {
|
||||
|
||||
/* sorted by type for most compact alignment
|
||||
*
|
||||
* pointers
|
||||
*/
|
||||
|
||||
char (*callback)(struct lejp_ctx *ctx, char reason);
|
||||
void *user;
|
||||
const char * const *paths;
|
||||
|
||||
/* arrays */
|
||||
|
||||
struct _lejp_stack st[LEJP_MAX_DEPTH];
|
||||
unsigned short i[LEJP_MAX_INDEX_DEPTH]; /* index array */
|
||||
unsigned short wild[LEJP_MAX_INDEX_DEPTH]; /* index array */
|
||||
char path[LEJP_MAX_PATH];
|
||||
char buf[LEJP_STRING_CHUNK];
|
||||
|
||||
/* int */
|
||||
|
||||
unsigned int line;
|
||||
|
||||
/* short */
|
||||
|
||||
unsigned short uni;
|
||||
|
||||
/* char */
|
||||
|
||||
unsigned char npos;
|
||||
unsigned char dcount;
|
||||
unsigned char f;
|
||||
unsigned char sp; /* stack head */
|
||||
unsigned char ipos; /* index stack depth */
|
||||
unsigned char ppos;
|
||||
unsigned char count_paths;
|
||||
unsigned char path_match;
|
||||
unsigned char path_match_len;
|
||||
unsigned char wildcount;
|
||||
};
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN void
|
||||
lejp_construct(struct lejp_ctx *ctx,
|
||||
char (*callback)(struct lejp_ctx *ctx, char reason), void *user,
|
||||
const char * const *paths, unsigned char paths_count);
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN void
|
||||
lejp_destruct(struct lejp_ctx *ctx);
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len);
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN void
|
||||
lejp_change_callback(struct lejp_ctx *ctx,
|
||||
char (*callback)(struct lejp_ctx *ctx, char reason));
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len);
|
|
@ -1,6 +1,10 @@
|
|||
/* set of parsable strings -- ALL LOWER CASE */
|
||||
|
||||
static const char *set[] = {
|
||||
#if !defined(STORE_IN_ROM)
|
||||
#define STORE_IN_ROM
|
||||
#endif
|
||||
|
||||
STORE_IN_ROM static const char * const set[] = {
|
||||
"get ",
|
||||
"post ",
|
||||
"options ",
|
||||
|
@ -39,13 +43,13 @@ static const char *set[] = {
|
|||
"sec-websocket-key:",
|
||||
"sec-websocket-version:",
|
||||
"sec-websocket-origin:",
|
||||
|
||||
":authority:",
|
||||
":method:",
|
||||
":path:",
|
||||
":scheme:",
|
||||
":status:",
|
||||
|
||||
|
||||
":authority",
|
||||
":method",
|
||||
":path",
|
||||
":scheme",
|
||||
":status",
|
||||
|
||||
"accept-charset:",
|
||||
"accept-ranges:",
|
||||
"access-control-allow-origin:",
|
||||
|
@ -79,12 +83,20 @@ static const char *set[] = {
|
|||
"vary:",
|
||||
"via:",
|
||||
"www-authenticate:",
|
||||
"proxy ",
|
||||
|
||||
|
||||
"patch",
|
||||
"put",
|
||||
"delete",
|
||||
|
||||
"uri-args", /* fake header used for uri-only storage */
|
||||
|
||||
"proxy ",
|
||||
"x-real-ip:",
|
||||
"http/1.0 ",
|
||||
|
||||
"x-forwarded-for",
|
||||
"connect ",
|
||||
|
||||
"", /* not matchable */
|
||||
|
||||
};
|
||||
|
|
1486
lib/lextable.h
1486
lib/lextable.h
File diff suppressed because it is too large
Load diff
171
lib/libev.c
171
lib/libev.c
|
@ -23,84 +23,92 @@
|
|||
|
||||
void lws_feature_status_libev(struct lws_context_creation_info *info)
|
||||
{
|
||||
if (info->options & LWS_SERVER_OPTION_LIBEV)
|
||||
if (lws_check_opt(info->options, LWS_SERVER_OPTION_LIBEV))
|
||||
lwsl_notice("libev support compiled in and enabled\n");
|
||||
else
|
||||
lwsl_notice("libev support compiled in but disabled\n");
|
||||
}
|
||||
|
||||
static void
|
||||
libwebsocket_accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents)
|
||||
static void
|
||||
lws_accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents)
|
||||
{
|
||||
struct libwebsocket_pollfd eventfd;
|
||||
struct lws_io_watcher *lws_io = container_of(watcher, struct lws_io_watcher, watcher);
|
||||
struct libwebsocket_context *context = lws_io->context;
|
||||
struct lws_io_watcher *lws_io = lws_container_of(watcher,
|
||||
struct lws_io_watcher, ev_watcher);
|
||||
struct lws_context *context = lws_io->context;
|
||||
struct lws_pollfd eventfd;
|
||||
|
||||
if (revents & EV_ERROR)
|
||||
return;
|
||||
|
||||
eventfd.fd = watcher->fd;
|
||||
eventfd.events = 0;
|
||||
eventfd.revents = EV_NONE;
|
||||
if (revents & EV_READ)
|
||||
if (revents & EV_READ) {
|
||||
eventfd.events |= LWS_POLLIN;
|
||||
eventfd.revents |= LWS_POLLIN;
|
||||
|
||||
if (revents & EV_WRITE)
|
||||
}
|
||||
if (revents & EV_WRITE) {
|
||||
eventfd.events |= LWS_POLLOUT;
|
||||
eventfd.revents |= LWS_POLLOUT;
|
||||
|
||||
libwebsocket_service_fd(context, &eventfd);
|
||||
}
|
||||
lws_service_fd(context, &eventfd);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
libwebsocket_sigint_cb(struct ev_loop *loop,
|
||||
struct ev_signal *watcher, int revents)
|
||||
lws_ev_sigint_cb(struct ev_loop *loop, struct ev_signal *watcher, int revents)
|
||||
{
|
||||
ev_break(loop, EVBREAK_ALL);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int libwebsocket_sigint_cfg(
|
||||
struct libwebsocket_context *context,
|
||||
int use_ev_sigint,
|
||||
lws_ev_signal_cb* cb)
|
||||
LWS_VISIBLE int
|
||||
lws_ev_sigint_cfg(struct lws_context *context, int use_ev_sigint,
|
||||
lws_ev_signal_cb_t *cb)
|
||||
{
|
||||
context->use_ev_sigint = use_ev_sigint;
|
||||
if( cb ) {
|
||||
if (cb)
|
||||
context->lws_ev_sigint_cb = cb;
|
||||
}
|
||||
else {
|
||||
context->lws_ev_sigint_cb = &libwebsocket_sigint_cb;
|
||||
};
|
||||
else
|
||||
context->lws_ev_sigint_cb = &lws_ev_sigint_cb;
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
libwebsocket_initloop(
|
||||
struct libwebsocket_context *context,
|
||||
struct ev_loop *loop)
|
||||
lws_ev_initloop(struct lws_context *context, struct ev_loop *loop, int tsi)
|
||||
{
|
||||
struct ev_signal *w_sigint = &context->pt[tsi].w_sigint.ev_watcher;
|
||||
struct ev_io *w_accept = &context->pt[tsi].w_accept.ev_watcher;
|
||||
struct lws_vhost *vh = context->vhost_list;
|
||||
const char * backend_name;
|
||||
int status = 0;
|
||||
int backend;
|
||||
const char * backend_name;
|
||||
struct ev_io *w_accept = &context->w_accept.watcher;
|
||||
struct ev_signal *w_sigint = &context->w_sigint.watcher;
|
||||
|
||||
if (!loop)
|
||||
loop = ev_default_loop(0);
|
||||
loop = ev_loop_new(0);
|
||||
else
|
||||
context->pt[tsi].ev_loop_foreign = 1;
|
||||
|
||||
context->pt[tsi].io_loop_ev = loop;
|
||||
|
||||
context->io_loop = loop;
|
||||
|
||||
/*
|
||||
* Initialize the accept w_accept with the listening socket
|
||||
* and register a callback for read operations:
|
||||
* Initialize the accept w_accept with all the listening sockets
|
||||
* and register a callback for read operations
|
||||
*/
|
||||
ev_io_init(w_accept, libwebsocket_accept_cb,
|
||||
context->listen_service_fd, EV_READ);
|
||||
ev_io_start(context->io_loop,w_accept);
|
||||
while (vh) {
|
||||
if (vh->lserv_wsi) {
|
||||
vh->lserv_wsi->w_read.context = context;
|
||||
ev_io_init(w_accept, lws_accept_cb, vh->lserv_wsi->desc.sockfd,
|
||||
EV_READ);
|
||||
}
|
||||
vh = vh->vhost_next;
|
||||
}
|
||||
ev_io_start(context->pt[tsi].io_loop_ev, w_accept);
|
||||
|
||||
/* Register the signal watcher unless the user has indicated otherwise: */
|
||||
if( context->use_ev_sigint ) {
|
||||
/* Register the signal watcher unless the user says not to */
|
||||
if (context->use_ev_sigint) {
|
||||
ev_signal_init(w_sigint, context->lws_ev_sigint_cb, SIGINT);
|
||||
ev_signal_start(context->io_loop,w_sigint);
|
||||
};
|
||||
ev_signal_start(context->pt[tsi].io_loop_ev, w_sigint);
|
||||
}
|
||||
backend = ev_backend(loop);
|
||||
|
||||
switch (backend) {
|
||||
|
@ -125,70 +133,101 @@ libwebsocket_initloop(
|
|||
default:
|
||||
backend_name = "Unknown libev backend";
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
lwsl_notice(" libev backend: %s\n", backend_name);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libev_accept(struct libwebsocket_context *context,
|
||||
struct libwebsocket *new_wsi, int accept_fd)
|
||||
void
|
||||
lws_libev_destroyloop(struct lws_context *context, int tsi)
|
||||
{
|
||||
struct ev_io *r = &new_wsi->w_read.watcher;
|
||||
struct ev_io *w = &new_wsi->w_write.watcher;
|
||||
struct lws_context_per_thread *pt = &context->pt[tsi];
|
||||
|
||||
if (!LWS_LIBEV_ENABLED(context))
|
||||
if (!lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEV))
|
||||
return;
|
||||
|
||||
new_wsi->w_read.context = context;
|
||||
new_wsi->w_write.context = context;
|
||||
ev_io_init(r, libwebsocket_accept_cb, accept_fd, EV_READ);
|
||||
ev_io_init(w, libwebsocket_accept_cb, accept_fd, EV_WRITE);
|
||||
if (!pt->io_loop_ev)
|
||||
return;
|
||||
|
||||
ev_io_stop(pt->io_loop_ev, &pt->w_accept.ev_watcher);
|
||||
if (context->use_ev_sigint)
|
||||
ev_signal_stop(pt->io_loop_ev,
|
||||
&pt->w_sigint.ev_watcher);
|
||||
if (!pt->ev_loop_foreign)
|
||||
ev_loop_destroy(pt->io_loop_ev);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libev_io(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, int flags)
|
||||
lws_libev_accept(struct lws *new_wsi, lws_sock_file_fd_type desc)
|
||||
{
|
||||
struct lws_context *context = lws_get_context(new_wsi);
|
||||
struct ev_io *r = &new_wsi->w_read.ev_watcher;
|
||||
struct ev_io *w = &new_wsi->w_write.ev_watcher;
|
||||
int fd;
|
||||
|
||||
if (!LWS_LIBEV_ENABLED(context))
|
||||
return;
|
||||
|
||||
if (!context->io_loop)
|
||||
if (new_wsi->mode == LWSCM_RAW_FILEDESC)
|
||||
fd = desc.filefd;
|
||||
else
|
||||
fd = desc.sockfd;
|
||||
|
||||
new_wsi->w_read.context = context;
|
||||
new_wsi->w_write.context = context;
|
||||
ev_io_init(r, lws_accept_cb, fd, EV_READ);
|
||||
ev_io_init(w, lws_accept_cb, fd, EV_WRITE);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libev_io(struct lws *wsi, int flags)
|
||||
{
|
||||
struct lws_context *context = lws_get_context(wsi);
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
|
||||
if (!LWS_LIBEV_ENABLED(context))
|
||||
return;
|
||||
|
||||
if (!pt->io_loop_ev)
|
||||
return;
|
||||
|
||||
assert((flags & (LWS_EV_START | LWS_EV_STOP)) &&
|
||||
(flags & (LWS_EV_READ | LWS_EV_WRITE)));
|
||||
(flags & (LWS_EV_READ | LWS_EV_WRITE)));
|
||||
|
||||
if (flags & LWS_EV_START) {
|
||||
if (flags & LWS_EV_WRITE)
|
||||
ev_io_start(context->io_loop, &wsi->w_write.watcher);
|
||||
ev_io_start(pt->io_loop_ev, &wsi->w_write.ev_watcher);
|
||||
if (flags & LWS_EV_READ)
|
||||
ev_io_start(context->io_loop, &wsi->w_read.watcher);
|
||||
ev_io_start(pt->io_loop_ev, &wsi->w_read.ev_watcher);
|
||||
} else {
|
||||
if (flags & LWS_EV_WRITE)
|
||||
ev_io_stop(context->io_loop, &wsi->w_write.watcher);
|
||||
ev_io_stop(pt->io_loop_ev, &wsi->w_write.ev_watcher);
|
||||
if (flags & LWS_EV_READ)
|
||||
ev_io_stop(context->io_loop, &wsi->w_read.watcher);
|
||||
ev_io_stop(pt->io_loop_ev, &wsi->w_read.ev_watcher);
|
||||
}
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_libev_init_fd_table(struct libwebsocket_context *context)
|
||||
lws_libev_init_fd_table(struct lws_context *context)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (!LWS_LIBEV_ENABLED(context))
|
||||
return 0;
|
||||
|
||||
context->w_accept.context = context;
|
||||
context->w_sigint.context = context;
|
||||
for (n = 0; n < context->count_threads; n++) {
|
||||
context->pt[n].w_accept.context = context;
|
||||
context->pt[n].w_sigint.context = context;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libev_run(struct libwebsocket_context *context)
|
||||
lws_libev_run(const struct lws_context *context, int tsi)
|
||||
{
|
||||
if (context->io_loop && LWS_LIBEV_ENABLED(context))
|
||||
ev_run(context->io_loop, 0);
|
||||
if (context->pt[tsi].io_loop_ev && LWS_LIBEV_ENABLED(context))
|
||||
ev_run(context->pt[tsi].io_loop_ev, 0);
|
||||
}
|
||||
|
|
249
lib/libevent.c
Normal file
249
lib/libevent.c
Normal file
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010-2014 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation:
|
||||
* version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
|
||||
void lws_feature_status_libevent(struct lws_context_creation_info *info)
|
||||
{
|
||||
if (lws_check_opt(info->options, LWS_SERVER_OPTION_LIBEVENT))
|
||||
lwsl_notice("libevent support compiled in and enabled\n");
|
||||
else
|
||||
lwsl_notice("libevent support compiled in but disabled\n");
|
||||
}
|
||||
|
||||
static void
|
||||
lws_event_cb(evutil_socket_t sock_fd, short revents, void *ctx)
|
||||
{
|
||||
struct lws_io_watcher *lws_io = (struct lws_io_watcher *)ctx;
|
||||
struct lws_context *context = lws_io->context;
|
||||
struct lws_pollfd eventfd;
|
||||
|
||||
if (revents & EV_TIMEOUT)
|
||||
return;
|
||||
|
||||
/* !!! EV_CLOSED doesn't exist in libevent2 */
|
||||
#if LIBEVENT_VERSION_NUMBER < 0x02000000
|
||||
if (revents & EV_CLOSED)
|
||||
{
|
||||
event_del(lws_io->event_watcher);
|
||||
event_free(lws_io->event_watcher);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
eventfd.fd = sock_fd;
|
||||
eventfd.events = 0;
|
||||
eventfd.revents = 0;
|
||||
if (revents & EV_READ)
|
||||
{
|
||||
eventfd.events |= LWS_POLLIN;
|
||||
eventfd.revents |= LWS_POLLIN;
|
||||
}
|
||||
if (revents & EV_WRITE)
|
||||
{
|
||||
eventfd.events |= LWS_POLLOUT;
|
||||
eventfd.revents |= LWS_POLLOUT;
|
||||
}
|
||||
lws_service_fd(context, &eventfd);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_event_sigint_cb(evutil_socket_t sock_fd, short revents, void *ctx)
|
||||
{
|
||||
struct lws_context_per_thread *pt = ctx;
|
||||
if (!pt->ev_loop_foreign)
|
||||
event_base_loopbreak(pt->io_loop_event_base);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_event_sigint_cfg(struct lws_context *context, int use_event_sigint,
|
||||
lws_event_signal_cb_t *cb)
|
||||
{
|
||||
context->use_ev_sigint = use_event_sigint;
|
||||
if (cb)
|
||||
context->lws_event_sigint_cb = cb;
|
||||
else
|
||||
context->lws_event_sigint_cb = &lws_event_sigint_cb;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_event_initloop(struct lws_context *context, struct event_base *loop,
|
||||
int tsi)
|
||||
{
|
||||
if (!loop)
|
||||
{
|
||||
context->pt[tsi].io_loop_event_base = event_base_new();
|
||||
}
|
||||
else
|
||||
{
|
||||
context->pt[tsi].ev_loop_foreign = 1;
|
||||
context->pt[tsi].io_loop_event_base = loop;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize all events with the listening sockets
|
||||
* and register a callback for read operations
|
||||
*/
|
||||
struct lws_vhost *vh = context->vhost_list;
|
||||
while (vh)
|
||||
{
|
||||
if (vh->lserv_wsi)
|
||||
{
|
||||
vh->lserv_wsi->w_read.context = context;
|
||||
vh->lserv_wsi->w_read.event_watcher = event_new(
|
||||
loop,
|
||||
vh->lserv_wsi->desc.sockfd,
|
||||
(EV_READ | EV_PERSIST),
|
||||
lws_event_cb,
|
||||
&vh->lserv_wsi->w_read);
|
||||
event_add(vh->lserv_wsi->w_read.event_watcher, NULL);
|
||||
}
|
||||
vh = vh->vhost_next;
|
||||
}
|
||||
|
||||
/* Register the signal watcher unless the user says not to */
|
||||
if (context->use_ev_sigint)
|
||||
{
|
||||
struct event *w_sigint = evsignal_new(loop, SIGINT,
|
||||
context->lws_event_sigint_cb, &context->pt[tsi]);
|
||||
context->pt[tsi].w_sigint.event_watcher = w_sigint;
|
||||
event_add(w_sigint, NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
lws_libevent_destroyloop(struct lws_context *context, int tsi)
|
||||
{
|
||||
if (!lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEVENT))
|
||||
return;
|
||||
|
||||
struct lws_context_per_thread *pt = &context->pt[tsi];
|
||||
if (!pt->io_loop_event_base)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Free all events with the listening sockets
|
||||
*/
|
||||
struct lws_vhost *vh = context->vhost_list;
|
||||
while (vh)
|
||||
{
|
||||
if (vh->lserv_wsi)
|
||||
{
|
||||
event_free(vh->lserv_wsi->w_read.event_watcher);
|
||||
vh->lserv_wsi->w_read.event_watcher = NULL;
|
||||
}
|
||||
vh = vh->vhost_next;
|
||||
}
|
||||
|
||||
if (context->use_ev_sigint)
|
||||
event_free(pt->w_sigint.event_watcher);
|
||||
if (!pt->ev_loop_foreign)
|
||||
event_base_free(pt->io_loop_event_base);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libevent_accept(struct lws *new_wsi, lws_sock_file_fd_type desc)
|
||||
{
|
||||
struct lws_context *context = lws_get_context(new_wsi);
|
||||
if (!LWS_LIBEVENT_ENABLED(context))
|
||||
return;
|
||||
|
||||
new_wsi->w_read.context = context;
|
||||
new_wsi->w_write.context = context;
|
||||
|
||||
// Initialize the event
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)new_wsi->tsi];
|
||||
int fd;
|
||||
if (new_wsi->mode == LWSCM_RAW_FILEDESC)
|
||||
fd = desc.filefd;
|
||||
else
|
||||
fd = desc.sockfd;
|
||||
new_wsi->w_read.event_watcher = event_new(pt->io_loop_event_base, fd,
|
||||
(EV_READ | EV_PERSIST), lws_event_cb, &new_wsi->w_read);
|
||||
new_wsi->w_write.event_watcher = event_new(pt->io_loop_event_base, fd,
|
||||
(EV_WRITE | EV_PERSIST), lws_event_cb, &new_wsi->w_write);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libevent_io(struct lws *wsi, int flags)
|
||||
{
|
||||
struct lws_context *context = lws_get_context(wsi);
|
||||
|
||||
if (!LWS_LIBEVENT_ENABLED(context))
|
||||
return;
|
||||
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
if (!pt->io_loop_event_base || context->being_destroyed)
|
||||
return;
|
||||
|
||||
assert((flags & (LWS_EV_START | LWS_EV_STOP)) &&
|
||||
(flags & (LWS_EV_READ | LWS_EV_WRITE)));
|
||||
|
||||
if (flags & LWS_EV_START)
|
||||
{
|
||||
if (flags & LWS_EV_WRITE)
|
||||
{
|
||||
event_add(wsi->w_write.event_watcher, NULL);
|
||||
}
|
||||
if (flags & LWS_EV_READ)
|
||||
{
|
||||
event_add(wsi->w_read.event_watcher, NULL);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (flags & LWS_EV_WRITE)
|
||||
{
|
||||
event_del(wsi->w_write.event_watcher);
|
||||
}
|
||||
if (flags & LWS_EV_READ)
|
||||
{
|
||||
event_del(wsi->w_read.event_watcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_libevent_init_fd_table(struct lws_context *context)
|
||||
{
|
||||
if (!LWS_LIBEVENT_ENABLED(context))
|
||||
return 0;
|
||||
|
||||
int n;
|
||||
for (n = 0; n < context->count_threads; n++)
|
||||
{
|
||||
context->pt[n].w_sigint.context = context;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libevent_run(const struct lws_context *context, int tsi)
|
||||
{
|
||||
// Run/Dispatch the event_base loop
|
||||
if (context->pt[tsi].io_loop_event_base && LWS_LIBEVENT_ENABLED(context))
|
||||
event_base_dispatch(context->pt[tsi].io_loop_event_base);
|
||||
}
|
723
lib/libuv.c
Normal file
723
lib/libuv.c
Normal file
|
@ -0,0 +1,723 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation:
|
||||
* version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
|
||||
void
|
||||
lws_feature_status_libuv(struct lws_context_creation_info *info)
|
||||
{
|
||||
if (lws_check_opt(info->options, LWS_SERVER_OPTION_LIBUV))
|
||||
lwsl_notice("libuv support compiled in and enabled\n");
|
||||
else
|
||||
lwsl_notice("libuv support compiled in but disabled\n");
|
||||
}
|
||||
|
||||
static void
|
||||
lws_uv_idle(uv_idle_t *handle
|
||||
#if UV_VERSION_MAJOR == 0
|
||||
, int status
|
||||
#endif
|
||||
)
|
||||
{
|
||||
struct lws_context_per_thread *pt = lws_container_of(handle,
|
||||
struct lws_context_per_thread, uv_idle);
|
||||
|
||||
// lwsl_debug("%s\n", __func__);
|
||||
|
||||
/*
|
||||
* is there anybody with pending stuff that needs service forcing?
|
||||
*/
|
||||
if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) {
|
||||
/* -1 timeout means just do forced service */
|
||||
_lws_plat_service_tsi(pt->context, -1, pt->tid);
|
||||
/* still somebody left who wants forced service? */
|
||||
if (!lws_service_adjust_timeout(pt->context, 1, pt->tid))
|
||||
/* yes... come back again later */
|
||||
// lwsl_debug("%s: done again\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* there is nobody who needs service forcing, shut down idle */
|
||||
uv_idle_stop(handle);
|
||||
|
||||
//lwsl_debug("%s: done stop\n", __func__);
|
||||
}
|
||||
|
||||
static void
|
||||
lws_io_cb(uv_poll_t *watcher, int status, int revents)
|
||||
{
|
||||
struct lws_io_watcher *lws_io = lws_container_of(watcher,
|
||||
struct lws_io_watcher, uv_watcher);
|
||||
struct lws *wsi = lws_container_of(lws_io, struct lws, w_read);
|
||||
struct lws_context *context = wsi->context;
|
||||
struct lws_pollfd eventfd;
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32)
|
||||
eventfd.fd = watcher->socket;
|
||||
#else
|
||||
eventfd.fd = watcher->io_watcher.fd;
|
||||
#endif
|
||||
eventfd.events = 0;
|
||||
eventfd.revents = 0;
|
||||
|
||||
if (status < 0) {
|
||||
/* at this point status will be an UV error, like UV_EBADF,
|
||||
we treat all errors as LWS_POLLHUP */
|
||||
|
||||
/* you might want to return; instead of servicing the fd in some cases */
|
||||
if (status == UV_EAGAIN)
|
||||
return;
|
||||
|
||||
eventfd.events |= LWS_POLLHUP;
|
||||
eventfd.revents |= LWS_POLLHUP;
|
||||
} else {
|
||||
if (revents & UV_READABLE) {
|
||||
eventfd.events |= LWS_POLLIN;
|
||||
eventfd.revents |= LWS_POLLIN;
|
||||
}
|
||||
if (revents & UV_WRITABLE) {
|
||||
eventfd.events |= LWS_POLLOUT;
|
||||
eventfd.revents |= LWS_POLLOUT;
|
||||
}
|
||||
}
|
||||
lws_service_fd(context, &eventfd);
|
||||
|
||||
uv_idle_start(&context->pt[(int)wsi->tsi].uv_idle, lws_uv_idle);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_uv_sigint_cb(uv_signal_t *watcher, int signum)
|
||||
{
|
||||
lwsl_err("internal signal handler caught signal %d\n", signum);
|
||||
lws_libuv_stop(watcher->data);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_uv_sigint_cfg(struct lws_context *context, int use_uv_sigint,
|
||||
uv_signal_cb cb)
|
||||
{
|
||||
context->use_ev_sigint = use_uv_sigint;
|
||||
if (cb)
|
||||
context->lws_uv_sigint_cb = cb;
|
||||
else
|
||||
context->lws_uv_sigint_cb = &lws_uv_sigint_cb;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
lws_uv_timeout_cb(uv_timer_t *timer
|
||||
#if UV_VERSION_MAJOR == 0
|
||||
, int status
|
||||
#endif
|
||||
)
|
||||
{
|
||||
struct lws_context_per_thread *pt = lws_container_of(timer,
|
||||
struct lws_context_per_thread, uv_timeout_watcher);
|
||||
|
||||
if (pt->context->requested_kill)
|
||||
return;
|
||||
|
||||
lwsl_debug("%s\n", __func__);
|
||||
|
||||
lws_service_fd_tsi(pt->context, NULL, pt->tid);
|
||||
}
|
||||
|
||||
static const int sigs[] = { SIGINT, SIGTERM, SIGSEGV, SIGFPE, SIGHUP };
|
||||
|
||||
int
|
||||
lws_uv_initvhost(struct lws_vhost* vh, struct lws* wsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt;
|
||||
int n;
|
||||
|
||||
if (!LWS_LIBUV_ENABLED(vh->context))
|
||||
return 0;
|
||||
if (!wsi)
|
||||
wsi = vh->lserv_wsi;
|
||||
if (!wsi)
|
||||
return 0;
|
||||
if (wsi->w_read.context)
|
||||
return 0;
|
||||
|
||||
pt = &vh->context->pt[(int)wsi->tsi];
|
||||
if (!pt->io_loop_uv)
|
||||
return 0;
|
||||
|
||||
wsi->w_read.context = vh->context;
|
||||
n = uv_poll_init_socket(pt->io_loop_uv,
|
||||
&wsi->w_read.uv_watcher, wsi->desc.sockfd);
|
||||
if (n) {
|
||||
lwsl_err("uv_poll_init failed %d, sockfd=%p\n",
|
||||
n, (void *)(lws_intptr_t)wsi->desc.sockfd);
|
||||
|
||||
return -1;
|
||||
}
|
||||
lws_libuv_io(wsi, LWS_EV_START | LWS_EV_READ);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This needs to be called after vhosts have been defined.
|
||||
*
|
||||
* If later, after server start, another vhost is added, this must be
|
||||
* called again to bind the vhost
|
||||
*/
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_uv_initloop(struct lws_context *context, uv_loop_t *loop, int tsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[tsi];
|
||||
struct lws_vhost *vh = context->vhost_list;
|
||||
int status = 0, n, ns, first = 1;
|
||||
|
||||
if (!pt->io_loop_uv) {
|
||||
if (!loop) {
|
||||
loop = lws_malloc(sizeof(*loop));
|
||||
if (!loop) {
|
||||
lwsl_err("OOM\n");
|
||||
return -1;
|
||||
}
|
||||
#if UV_VERSION_MAJOR > 0
|
||||
uv_loop_init(loop);
|
||||
#else
|
||||
lwsl_err("This libuv is too old to work...\n");
|
||||
return 1;
|
||||
#endif
|
||||
pt->ev_loop_foreign = 0;
|
||||
} else {
|
||||
lwsl_notice(" Using foreign event loop...\n");
|
||||
pt->ev_loop_foreign = 1;
|
||||
}
|
||||
|
||||
pt->io_loop_uv = loop;
|
||||
uv_idle_init(loop, &pt->uv_idle);
|
||||
|
||||
ns = ARRAY_SIZE(sigs);
|
||||
if (lws_check_opt(context->options,
|
||||
LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN))
|
||||
ns = 2;
|
||||
|
||||
if (pt->context->use_ev_sigint) {
|
||||
assert(ns <= ARRAY_SIZE(pt->signals));
|
||||
for (n = 0; n < ns; n++) {
|
||||
uv_signal_init(loop, &pt->signals[n]);
|
||||
pt->signals[n].data = pt->context;
|
||||
uv_signal_start(&pt->signals[n],
|
||||
context->lws_uv_sigint_cb, sigs[n]);
|
||||
}
|
||||
}
|
||||
} else
|
||||
first = 0;
|
||||
|
||||
/*
|
||||
* Initialize the accept wsi read watcher with all the listening sockets
|
||||
* and register a callback for read operations
|
||||
*
|
||||
* We have to do it here because the uv loop(s) are not
|
||||
* initialized until after context creation.
|
||||
*/
|
||||
while (vh) {
|
||||
if (lws_uv_initvhost(vh, vh->lserv_wsi) == -1)
|
||||
return -1;
|
||||
vh = vh->vhost_next;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
uv_timer_init(pt->io_loop_uv, &pt->uv_timeout_watcher);
|
||||
uv_timer_start(&pt->uv_timeout_watcher, lws_uv_timeout_cb,
|
||||
10, 1000);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void lws_uv_close_cb(uv_handle_t *handle)
|
||||
{
|
||||
//lwsl_err("%s: handle %p\n", __func__, handle);
|
||||
}
|
||||
|
||||
static void lws_uv_walk_cb(uv_handle_t *handle, void *arg)
|
||||
{
|
||||
if (!uv_is_closing(handle))
|
||||
uv_close(handle, lws_uv_close_cb);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_close_all_handles_in_loop(uv_loop_t *loop)
|
||||
{
|
||||
uv_walk(loop, lws_uv_walk_cb, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
lws_libuv_destroyloop(struct lws_context *context, int tsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[tsi];
|
||||
// struct lws_context *ctx;
|
||||
int m, budget = 100, ns;
|
||||
|
||||
if (!lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV))
|
||||
return;
|
||||
|
||||
if (!pt->io_loop_uv)
|
||||
return;
|
||||
|
||||
lwsl_notice("%s: closing signals + timers context %p\n", __func__, context);
|
||||
|
||||
if (context->use_ev_sigint) {
|
||||
uv_signal_stop(&pt->w_sigint.uv_watcher);
|
||||
|
||||
ns = ARRAY_SIZE(sigs);
|
||||
if (lws_check_opt(context->options, LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN))
|
||||
ns = 2;
|
||||
|
||||
for (m = 0; m < ns; m++) {
|
||||
uv_signal_stop(&pt->signals[m]);
|
||||
uv_close((uv_handle_t *)&pt->signals[m], lws_uv_close_cb);
|
||||
}
|
||||
}
|
||||
|
||||
uv_timer_stop(&pt->uv_timeout_watcher);
|
||||
uv_close((uv_handle_t *)&pt->uv_timeout_watcher, lws_uv_close_cb);
|
||||
|
||||
uv_idle_stop(&pt->uv_idle);
|
||||
uv_close((uv_handle_t *)&pt->uv_idle, lws_uv_close_cb);
|
||||
|
||||
if (pt->ev_loop_foreign)
|
||||
return;
|
||||
|
||||
while (budget-- && uv_run(pt->io_loop_uv, UV_RUN_NOWAIT))
|
||||
;
|
||||
|
||||
lwsl_notice("%s: closing all loop handles context %p\n", __func__, context);
|
||||
|
||||
uv_stop(pt->io_loop_uv);
|
||||
|
||||
uv_walk(pt->io_loop_uv, lws_uv_walk_cb, NULL);
|
||||
|
||||
while (uv_run(pt->io_loop_uv, UV_RUN_NOWAIT))
|
||||
;
|
||||
#if UV_VERSION_MAJOR > 0
|
||||
m = uv_loop_close(pt->io_loop_uv);
|
||||
if (m == UV_EBUSY)
|
||||
lwsl_err("%s: uv_loop_close: UV_EBUSY\n", __func__);
|
||||
#endif
|
||||
lws_free(pt->io_loop_uv);
|
||||
}
|
||||
|
||||
void
|
||||
lws_libuv_accept(struct lws *wsi, lws_sock_file_fd_type desc)
|
||||
{
|
||||
struct lws_context *context = lws_get_context(wsi);
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
|
||||
if (!LWS_LIBUV_ENABLED(context))
|
||||
return;
|
||||
|
||||
lwsl_debug("%s: new wsi %p\n", __func__, wsi);
|
||||
|
||||
wsi->w_read.context = context;
|
||||
if (wsi->mode == LWSCM_RAW_FILEDESC)
|
||||
uv_poll_init(pt->io_loop_uv, &wsi->w_read.uv_watcher,
|
||||
(int)desc.filefd);
|
||||
else
|
||||
uv_poll_init_socket(pt->io_loop_uv, &wsi->w_read.uv_watcher,
|
||||
desc.sockfd);
|
||||
}
|
||||
|
||||
void
|
||||
lws_libuv_io(struct lws *wsi, int flags)
|
||||
{
|
||||
struct lws_context *context = lws_get_context(wsi);
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
#if defined(WIN32) || defined(_WIN32)
|
||||
int current_events = wsi->w_read.uv_watcher.events &
|
||||
(UV_READABLE | UV_WRITABLE);
|
||||
#else
|
||||
int current_events = wsi->w_read.uv_watcher.io_watcher.pevents &
|
||||
(UV_READABLE | UV_WRITABLE);
|
||||
#endif
|
||||
struct lws_io_watcher *w = &wsi->w_read;
|
||||
|
||||
if (!LWS_LIBUV_ENABLED(context))
|
||||
return;
|
||||
|
||||
// lwsl_notice("%s: wsi: %p, flags:0x%x\n", __func__, wsi, flags);
|
||||
|
||||
// w->context is set after the loop is initialized
|
||||
|
||||
if (!pt->io_loop_uv || !w->context) {
|
||||
lwsl_info("%s: no io loop yet\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!((flags & (LWS_EV_START | LWS_EV_STOP)) &&
|
||||
(flags & (LWS_EV_READ | LWS_EV_WRITE)))) {
|
||||
lwsl_err("%s: assert: flags %d", __func__, flags);
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if (flags & LWS_EV_START) {
|
||||
if (flags & LWS_EV_WRITE)
|
||||
current_events |= UV_WRITABLE;
|
||||
|
||||
if (flags & LWS_EV_READ)
|
||||
current_events |= UV_READABLE;
|
||||
|
||||
uv_poll_start(&w->uv_watcher, current_events, lws_io_cb);
|
||||
} else {
|
||||
if (flags & LWS_EV_WRITE)
|
||||
current_events &= ~UV_WRITABLE;
|
||||
|
||||
if (flags & LWS_EV_READ)
|
||||
current_events &= ~UV_READABLE;
|
||||
|
||||
if (!(current_events & (UV_READABLE | UV_WRITABLE)))
|
||||
uv_poll_stop(&w->uv_watcher);
|
||||
else
|
||||
uv_poll_start(&w->uv_watcher, current_events,
|
||||
lws_io_cb);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
lws_libuv_init_fd_table(struct lws_context *context)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (!LWS_LIBUV_ENABLED(context))
|
||||
return 0;
|
||||
|
||||
for (n = 0; n < context->count_threads; n++)
|
||||
context->pt[n].w_sigint.context = context;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libuv_run(const struct lws_context *context, int tsi)
|
||||
{
|
||||
if (context->pt[tsi].io_loop_uv && LWS_LIBUV_ENABLED(context))
|
||||
uv_run(context->pt[tsi].io_loop_uv, 0);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libuv_stop_without_kill(const struct lws_context *context, int tsi)
|
||||
{
|
||||
if (context->pt[tsi].io_loop_uv && LWS_LIBUV_ENABLED(context))
|
||||
uv_stop(context->pt[tsi].io_loop_uv);
|
||||
}
|
||||
|
||||
static void
|
||||
lws_libuv_kill(const struct lws_context *context)
|
||||
{
|
||||
int n;
|
||||
|
||||
lwsl_notice("%s\n", __func__);
|
||||
|
||||
for (n = 0; n < context->count_threads; n++)
|
||||
if (context->pt[n].io_loop_uv &&
|
||||
LWS_LIBUV_ENABLED(context) )//&&
|
||||
//!context->pt[n].ev_loop_foreign)
|
||||
uv_stop(context->pt[n].io_loop_uv);
|
||||
}
|
||||
|
||||
/*
|
||||
* This does not actually stop the event loop. The reason is we have to pass
|
||||
* libuv handle closures through its event loop. So this tries to close all
|
||||
* wsi, and set a flag; when all the wsi closures are finalized then we
|
||||
* actually stop the libuv event loops.
|
||||
*/
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libuv_stop(struct lws_context *context)
|
||||
{
|
||||
struct lws_context_per_thread *pt;
|
||||
int n, m;
|
||||
|
||||
if (context->requested_kill)
|
||||
return;
|
||||
|
||||
context->requested_kill = 1;
|
||||
|
||||
m = context->count_threads;
|
||||
context->being_destroyed = 1;
|
||||
|
||||
while (m--) {
|
||||
pt = &context->pt[m];
|
||||
|
||||
for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) {
|
||||
struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd);
|
||||
|
||||
if (!wsi)
|
||||
continue;
|
||||
lws_close_free_wsi(wsi,
|
||||
LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY
|
||||
/* no protocol close */);
|
||||
n--;
|
||||
}
|
||||
}
|
||||
|
||||
lwsl_info("%s: feels everything closed\n", __func__);
|
||||
if (context->count_wsi_allocated == 0)
|
||||
lws_libuv_kill(context);
|
||||
}
|
||||
|
||||
LWS_VISIBLE uv_loop_t *
|
||||
lws_uv_getloop(struct lws_context *context, int tsi)
|
||||
{
|
||||
if (context->pt[tsi].io_loop_uv && LWS_LIBUV_ENABLED(context))
|
||||
return context->pt[tsi].io_loop_uv;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
lws_libuv_closewsi(uv_handle_t* handle)
|
||||
{
|
||||
struct lws *n = NULL, *wsi = (struct lws *)(((char *)handle) -
|
||||
(char *)(&n->w_read.uv_watcher));
|
||||
struct lws_context *context = lws_get_context(wsi);
|
||||
int lspd = 0;
|
||||
|
||||
if (wsi->mode == LWSCM_SERVER_LISTENER &&
|
||||
wsi->context->deprecated) {
|
||||
lspd = 1;
|
||||
context->deprecation_pending_listen_close_count--;
|
||||
if (!context->deprecation_pending_listen_close_count)
|
||||
lspd = 2;
|
||||
}
|
||||
|
||||
lws_close_free_wsi_final(wsi);
|
||||
|
||||
if (lspd == 2 && context->deprecation_cb) {
|
||||
lwsl_notice("calling deprecation callback\n");
|
||||
context->deprecation_cb();
|
||||
}
|
||||
|
||||
//lwsl_notice("%s: ctx %p: wsi left %d\n", __func__, context, context->count_wsi_allocated);
|
||||
|
||||
if (context->requested_kill && context->count_wsi_allocated == 0)
|
||||
lws_libuv_kill(context);
|
||||
}
|
||||
|
||||
void
|
||||
lws_libuv_closehandle(struct lws *wsi)
|
||||
{
|
||||
struct lws_context *context = lws_get_context(wsi);
|
||||
|
||||
/* required to defer actual deletion until libuv has processed it */
|
||||
uv_close((uv_handle_t*)&wsi->w_read.uv_watcher, lws_libuv_closewsi);
|
||||
|
||||
if (context->requested_kill && context->count_wsi_allocated == 0)
|
||||
lws_libuv_kill(context);
|
||||
}
|
||||
|
||||
static void
|
||||
lws_libuv_closewsi_m(uv_handle_t* handle)
|
||||
{
|
||||
lws_sockfd_type sockfd = (lws_sockfd_type)(lws_intptr_t)handle->data;
|
||||
|
||||
compatible_close(sockfd);
|
||||
}
|
||||
|
||||
void
|
||||
lws_libuv_closehandle_manually(struct lws *wsi)
|
||||
{
|
||||
uv_handle_t *h = (void *)&wsi->w_read.uv_watcher;
|
||||
|
||||
h->data = (void *)(lws_intptr_t)wsi->desc.sockfd;
|
||||
/* required to defer actual deletion until libuv has processed it */
|
||||
uv_close((uv_handle_t*)&wsi->w_read.uv_watcher, lws_libuv_closewsi_m);
|
||||
}
|
||||
|
||||
int
|
||||
lws_libuv_check_watcher_active(struct lws *wsi)
|
||||
{
|
||||
uv_handle_t *h = (void *)&wsi->w_read.uv_watcher;
|
||||
|
||||
return uv_is_active(h);
|
||||
}
|
||||
|
||||
|
||||
#if defined(LWS_WITH_PLUGINS) && (UV_VERSION_MAJOR > 0)
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_plugins_init(struct lws_context *context, const char * const *d)
|
||||
{
|
||||
struct lws_plugin_capability lcaps;
|
||||
struct lws_plugin *plugin;
|
||||
lws_plugin_init_func initfunc;
|
||||
int m, ret = 0;
|
||||
void *v;
|
||||
uv_dirent_t dent;
|
||||
uv_fs_t req;
|
||||
char path[256];
|
||||
uv_lib_t lib;
|
||||
int pofs = 0;
|
||||
|
||||
#if defined(__MINGW32__) || !defined(WIN32)
|
||||
pofs = 3;
|
||||
#endif
|
||||
|
||||
lib.errmsg = NULL;
|
||||
lib.handle = NULL;
|
||||
|
||||
uv_loop_init(&context->pu_loop);
|
||||
|
||||
lwsl_notice(" Plugins:\n");
|
||||
|
||||
while (d && *d) {
|
||||
|
||||
lwsl_notice(" Scanning %s\n", *d);
|
||||
m =uv_fs_scandir(&context->pu_loop, &req, *d, 0, NULL);
|
||||
if (m < 1) {
|
||||
lwsl_err("Scandir on %s failed\n", *d);
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
|
||||
if (strlen(dent.name) < 7)
|
||||
continue;
|
||||
|
||||
lwsl_notice(" %s\n", dent.name);
|
||||
|
||||
lws_snprintf(path, sizeof(path) - 1, "%s/%s", *d, dent.name);
|
||||
if (uv_dlopen(path, &lib)) {
|
||||
uv_dlerror(&lib);
|
||||
lwsl_err("Error loading DSO: %s\n", lib.errmsg);
|
||||
uv_dlclose(&lib);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* we could open it, can we get his init function? */
|
||||
|
||||
#if !defined(WIN32) && !defined(__MINGW32__)
|
||||
m = lws_snprintf(path, sizeof(path) - 1, "init_%s",
|
||||
dent.name + pofs /* snip lib... */);
|
||||
path[m - 3] = '\0'; /* snip the .so */
|
||||
#else
|
||||
m = lws_snprintf(path, sizeof(path) - 1, "init_%s",
|
||||
dent.name + pofs);
|
||||
path[m - 4] = '\0'; /* snip the .dll */
|
||||
#endif
|
||||
if (uv_dlsym(&lib, path, &v)) {
|
||||
uv_dlerror(&lib);
|
||||
lwsl_err("Failed to get %s on %s: %s", path,
|
||||
dent.name, lib.errmsg);
|
||||
uv_dlclose(&lib);
|
||||
goto bail;
|
||||
}
|
||||
initfunc = (lws_plugin_init_func)v;
|
||||
lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
|
||||
m = initfunc(context, &lcaps);
|
||||
if (m) {
|
||||
lwsl_err("Initializing %s failed %d\n", dent.name, m);
|
||||
goto skip;
|
||||
}
|
||||
|
||||
plugin = lws_malloc(sizeof(*plugin));
|
||||
if (!plugin) {
|
||||
uv_dlclose(&lib);
|
||||
lwsl_err("OOM\n");
|
||||
goto bail;
|
||||
}
|
||||
plugin->list = context->plugin_list;
|
||||
context->plugin_list = plugin;
|
||||
strncpy(plugin->name, dent.name, sizeof(plugin->name) - 1);
|
||||
plugin->name[sizeof(plugin->name) - 1] = '\0';
|
||||
plugin->lib = lib;
|
||||
plugin->caps = lcaps;
|
||||
context->plugin_protocol_count += lcaps.count_protocols;
|
||||
context->plugin_extension_count += lcaps.count_extensions;
|
||||
|
||||
continue;
|
||||
|
||||
skip:
|
||||
uv_dlclose(&lib);
|
||||
}
|
||||
bail:
|
||||
uv_fs_req_cleanup(&req);
|
||||
d++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_plugins_destroy(struct lws_context *context)
|
||||
{
|
||||
struct lws_plugin *plugin = context->plugin_list, *p;
|
||||
lws_plugin_destroy_func func;
|
||||
char path[256];
|
||||
void *v;
|
||||
int m;
|
||||
int pofs = 0;
|
||||
|
||||
#if defined(__MINGW32__) || !defined(WIN32)
|
||||
pofs = 3;
|
||||
#endif
|
||||
|
||||
if (!plugin)
|
||||
return 0;
|
||||
|
||||
// lwsl_notice("%s\n", __func__);
|
||||
|
||||
while (plugin) {
|
||||
p = plugin;
|
||||
|
||||
#if !defined(WIN32) && !defined(__MINGW32__)
|
||||
m = lws_snprintf(path, sizeof(path) - 1, "destroy_%s", plugin->name + pofs);
|
||||
path[m - 3] = '\0';
|
||||
#else
|
||||
m = lws_snprintf(path, sizeof(path) - 1, "destroy_%s", plugin->name + pofs);
|
||||
path[m - 4] = '\0';
|
||||
#endif
|
||||
|
||||
if (uv_dlsym(&plugin->lib, path, &v)) {
|
||||
uv_dlerror(&plugin->lib);
|
||||
lwsl_err("Failed to get %s on %s: %s", path,
|
||||
plugin->name, plugin->lib.errmsg);
|
||||
} else {
|
||||
func = (lws_plugin_destroy_func)v;
|
||||
m = func(context);
|
||||
if (m)
|
||||
lwsl_err("Destroying %s failed %d\n",
|
||||
plugin->name, m);
|
||||
}
|
||||
|
||||
uv_dlclose(&p->lib);
|
||||
plugin = p->list;
|
||||
p->list = NULL;
|
||||
free(p);
|
||||
}
|
||||
|
||||
context->plugin_list = NULL;
|
||||
|
||||
while (uv_loop_close(&context->pu_loop))
|
||||
;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
3604
lib/libwebsockets.c
Normal file → Executable file
3604
lib/libwebsockets.c
Normal file → Executable file
File diff suppressed because it is too large
Load diff
6030
lib/libwebsockets.h
6030
lib/libwebsockets.h
File diff suppressed because it is too large
Load diff
1739
lib/lws-plat-esp32.c
Normal file
1739
lib/lws-plat-esp32.c
Normal file
File diff suppressed because it is too large
Load diff
700
lib/lws-plat-esp8266.c
Normal file
700
lib/lws-plat-esp8266.c
Normal file
|
@ -0,0 +1,700 @@
|
|||
#include "private-libwebsockets.h"
|
||||
|
||||
#include "ip_addr.h"
|
||||
|
||||
/* forced into this because new espconn accepted callbacks carry no context ptr */
|
||||
static struct lws_context *hacky_context;
|
||||
static unsigned int time_high, ot;
|
||||
|
||||
/*
|
||||
* included from libwebsockets.c for esp8266 builds
|
||||
*/
|
||||
|
||||
unsigned long long time_in_microseconds(void)
|
||||
{
|
||||
unsigned int t = system_get_time();
|
||||
|
||||
if (ot > t)
|
||||
time_high++;
|
||||
ot = t;
|
||||
|
||||
return (((long long)time_high) << 32) | t;
|
||||
}
|
||||
|
||||
int gettimeofday(struct timeval *tv, void *tz)
|
||||
{
|
||||
unsigned long long t = time_in_microseconds();
|
||||
|
||||
tv->tv_sec = t / 1000000;
|
||||
tv->tv_usec = t % 1000000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
time_t time(time_t *tloc)
|
||||
{
|
||||
unsigned long long t = time_in_microseconds();
|
||||
|
||||
if (tloc)
|
||||
*tloc = t / 1000000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_get_random(struct lws_context *context, void *buf, int len)
|
||||
{
|
||||
// return read(context->fd_random, (char *)buf, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_send_pipe_choked(struct lws *wsi)
|
||||
{
|
||||
return wsi->pending_send_completion;
|
||||
}
|
||||
|
||||
LWS_VISIBLE struct lws *
|
||||
wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd)
|
||||
{
|
||||
int n;
|
||||
|
||||
for (n = 0; n < context->max_fds; n++)
|
||||
if (context->connpool[n] == fd)
|
||||
return (struct lws *)context->connpool[n + context->max_fds];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len)
|
||||
{
|
||||
//lwsl_notice("%s: wsi %p: len %d\n", __func__, wsi, len);
|
||||
|
||||
wsi->pending_send_completion++;
|
||||
espconn_send(wsi->desc.sockfd, buf, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void abort(void)
|
||||
{
|
||||
while(1) ;
|
||||
}
|
||||
|
||||
void exit(int n)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
void _sint(void *s)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
insert_wsi(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
(void)context;
|
||||
(void)wsi;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
delete_from_fd(struct lws_context *context, lws_sockfd_type fd)
|
||||
{
|
||||
(void)context;
|
||||
(void)fd;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct tm *localtime(const time_t *timep)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
struct tm *localtime_r(const time_t *timep, struct tm *t)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int atoi(const char *s)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
while (*s && (*s >= '0' && *s <= '9'))
|
||||
n = (n * 10) + ((*s++) - '0');
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
#undef isxdigit
|
||||
int isxdigit(int c)
|
||||
{
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return 1;
|
||||
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return 1;
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int strcasecmp(const char *s1, const char *s2)
|
||||
{
|
||||
char a, b;
|
||||
while (*s1 && *s2) {
|
||||
a = *s1++;
|
||||
b = *s2++;
|
||||
|
||||
if (a == b)
|
||||
continue;
|
||||
|
||||
if (a >= 'a' && a <= 'z')
|
||||
a -= 'a' - 'A';
|
||||
if (b >= 'a' && b <= 'z')
|
||||
b -= 'a' - 'A';
|
||||
|
||||
if (a != b)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_poll_listen_fd(struct lws_pollfd *fd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_cancel_service_pt(struct lws *wsi)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_cancel_service(struct lws_context *context)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
|
||||
{
|
||||
extern void output_redirect(const char *str);
|
||||
output_redirect(line);
|
||||
}
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_check_connection_error(struct lws *wsi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_service(struct lws_context *context, int timeout_ms)
|
||||
{
|
||||
// return _lws_plat_service_tsi(context, timeout_ms, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
esp8266_find_free_conn(struct lws_context *context)
|
||||
{
|
||||
int n;
|
||||
|
||||
for (n = 0; n < context->max_fds; n++)
|
||||
if (!context->connpool[n]) {
|
||||
lwsl_info(" using connpool %d\n", n);
|
||||
return n;
|
||||
}
|
||||
|
||||
lwsl_err("%s: no free conns\n", __func__);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
lws_sockfd_type
|
||||
esp8266_create_tcp_listen_socket(struct lws_vhost *vh)
|
||||
{
|
||||
int n = esp8266_find_free_conn(vh->context);
|
||||
struct espconn *conn;
|
||||
|
||||
if (n < 0)
|
||||
return NULL;
|
||||
|
||||
conn = lws_zalloc(sizeof *conn);
|
||||
if (!conn)
|
||||
return NULL;
|
||||
|
||||
vh->context->connpool[n] = conn;
|
||||
|
||||
conn->type = ESPCONN_TCP;
|
||||
conn->state = ESPCONN_NONE;
|
||||
conn->proto.tcp = &vh->tcp;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
const char *
|
||||
lws_plat_get_peer_simple(struct lws *wsi, char *name, int namelen)
|
||||
{
|
||||
unsigned char *p = wsi->desc.sockfd->proto.tcp->remote_ip;
|
||||
|
||||
lws_snprintf(name, namelen, "%u.%u.%u.%u", p[0], p[1], p[2], p[3]);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len)
|
||||
{
|
||||
//lwsl_notice("%s\n", __func__);
|
||||
|
||||
if (!wsi->context->rxd)
|
||||
return 0;
|
||||
|
||||
if (len < wsi->context->rxd_len)
|
||||
lwsl_err("trunc read (%d vs %d)\n", len, wsi->context->rxd_len);
|
||||
else
|
||||
len = wsi->context->rxd_len;
|
||||
|
||||
ets_memcpy(buf, wsi->context->rxd, len);
|
||||
|
||||
wsi->context->rxd = NULL;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static void
|
||||
cb_1Hz(void *arg)
|
||||
{
|
||||
struct lws_context *context = arg;
|
||||
struct lws_context_per_thread *pt = &context->pt[0];
|
||||
struct lws *wsi;
|
||||
struct lws_pollfd *pollfd;
|
||||
int n;
|
||||
|
||||
/* Service any ah that has pending rx */
|
||||
for (n = 0; n < context->max_http_header_pool; n++)
|
||||
if (pt->ah_pool[n].rxpos != pt->ah_pool[n].rxlen) {
|
||||
wsi = pt->ah_pool[n].wsi;
|
||||
pollfd = &pt->fds[wsi->position_in_fds_table];
|
||||
if (pollfd->events & LWS_POLLIN) {
|
||||
pollfd->revents |= LWS_POLLIN;
|
||||
lws_service_fd(context, pollfd);
|
||||
}
|
||||
}
|
||||
|
||||
/* handle timeouts */
|
||||
|
||||
lws_service_fd(context, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
esp8266_cb_rx(void *arg, char *data, unsigned short len)
|
||||
{
|
||||
struct espconn *conn = arg;
|
||||
struct lws *wsi = conn->reverse;
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[0];
|
||||
struct lws_pollfd pollfd;
|
||||
int n = 0;
|
||||
|
||||
/*
|
||||
* if we're doing HTTP headers, and we have no ah, check if there is
|
||||
* a free ah, if not, have to buffer it
|
||||
*/
|
||||
if (!wsi->hdr_parsing_completed && !wsi->u.hdr.ah) {
|
||||
for (n = 0; n < wsi->context->max_http_header_pool; n++)
|
||||
if (!pt->ah_pool[n].in_use)
|
||||
break;
|
||||
|
||||
n = n == wsi->context->max_http_header_pool;
|
||||
}
|
||||
|
||||
if (!(pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN) || n) {
|
||||
wsi->premature_rx = realloc(wsi->premature_rx,
|
||||
wsi->prem_rx_size + len);
|
||||
if (!wsi->premature_rx)
|
||||
return;
|
||||
os_memcpy((char *)wsi->premature_rx + wsi->prem_rx_size, data, len);
|
||||
wsi->prem_rx_size += len;
|
||||
// lwsl_notice("%s: wsi %p: len %d BUFFERING\n", __func__, wsi, len);
|
||||
|
||||
if (n) /* we know it will fail, but we will get on the wait list */
|
||||
n = lws_header_table_attach(wsi, 0);
|
||||
|
||||
(void)n;
|
||||
return;
|
||||
}
|
||||
|
||||
//lwsl_err("%s: wsi %p. len %d\n", __func__, wsi, len);
|
||||
|
||||
pollfd.fd = arg;
|
||||
pollfd.events = LWS_POLLIN;
|
||||
pollfd.revents = LWS_POLLIN;
|
||||
|
||||
wsi->context->rxd = data;
|
||||
wsi->context->rxd_len = len;
|
||||
|
||||
lws_service_fd(lws_get_context(wsi), &pollfd);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
esp8266_cb_sent(void *arg)
|
||||
{
|
||||
struct espconn *conn = arg;
|
||||
struct lws *wsi = conn->reverse;
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
|
||||
// lwsl_err("%s: wsi %p (psc %d) wsi->position_in_fds_table=%d\n", __func__, wsi, wsi->pending_send_completion, wsi->position_in_fds_table);
|
||||
|
||||
wsi->pending_send_completion--;
|
||||
if (wsi->close_is_pending_send_completion &&
|
||||
!wsi->pending_send_completion &&
|
||||
!lws_partial_buffered(wsi)) {
|
||||
lwsl_notice("doing delayed close\n");
|
||||
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
|
||||
}
|
||||
|
||||
if (pt->fds[wsi->position_in_fds_table].events & LWS_POLLOUT) {
|
||||
struct lws_pollfd pollfd;
|
||||
|
||||
pollfd.fd = arg;
|
||||
pollfd.events = LWS_POLLOUT;
|
||||
pollfd.revents = LWS_POLLOUT;
|
||||
|
||||
// lwsl_notice("informing POLLOUT\n");
|
||||
|
||||
lws_service_fd(lws_get_context(wsi), &pollfd);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
esp8266_cb_disconnected(void *arg)
|
||||
{
|
||||
struct espconn *conn = arg;
|
||||
struct lws *wsi = conn->reverse;
|
||||
int n;
|
||||
|
||||
lwsl_notice("%s: %p\n", __func__, wsi);
|
||||
|
||||
for (n = 0; n < hacky_context->max_fds; n++)
|
||||
if (hacky_context->connpool[n] == arg) {
|
||||
hacky_context->connpool[n] = NULL;
|
||||
lwsl_info(" freed connpool %d\n", n);
|
||||
}
|
||||
|
||||
if (wsi) {
|
||||
conn->reverse = NULL;
|
||||
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
|
||||
lwsl_notice("closed ok\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
esp8266_cb_recon(void *arg, signed char err)
|
||||
{
|
||||
struct espconn *conn = arg;
|
||||
|
||||
lwsl_err("%s: wsi %p. err %d\n", __func__, conn->reverse, err);
|
||||
|
||||
conn->state = ESPCONN_CLOSE;
|
||||
|
||||
esp8266_cb_disconnected(arg);
|
||||
}
|
||||
|
||||
/*
|
||||
* there is no reliable indication of which listen socket we were accepted on.
|
||||
*/
|
||||
|
||||
static void
|
||||
esp8266_cb_connect(void *arg)
|
||||
{
|
||||
struct espconn *cs = arg;
|
||||
// struct ip_addr *ipa = (struct ip_addr *)cs->proto.tcp->remote_ip;
|
||||
struct lws_vhost *vh = hacky_context->vhost_list;
|
||||
// struct ip_info info;
|
||||
struct lws *wsi;
|
||||
int n;
|
||||
|
||||
lwsl_notice("%s: (wsi coming): %p\n", __func__, cs->reverse);
|
||||
#if 0
|
||||
wifi_get_ip_info(0, &info);
|
||||
if (ip_addr_netcmp(ipa, &info.ip, &info.netmask)) {
|
||||
/* we are on the same subnet as the AP, ie, connected to AP */
|
||||
while (vh && strcmp(vh->name, "ap"))
|
||||
vh = vh->vhost_next;
|
||||
} else
|
||||
while (vh && !strcmp(vh->name, "ap"))
|
||||
vh = vh->vhost_next;
|
||||
|
||||
if (!vh)
|
||||
goto bail;
|
||||
#endif
|
||||
n = esp8266_find_free_conn(hacky_context);
|
||||
if (n < 0)
|
||||
goto bail;
|
||||
|
||||
hacky_context->connpool[n] = cs;
|
||||
|
||||
espconn_recv_hold(cs);
|
||||
|
||||
wsi = lws_adopt_socket_vhost(vh, cs);
|
||||
if (!wsi)
|
||||
goto bail;
|
||||
|
||||
lwsl_err("%s: wsi %p (using free_conn %d): vh %s\n", __func__, wsi, n, vh->name);
|
||||
|
||||
espconn_regist_recvcb(cs, esp8266_cb_rx);
|
||||
espconn_regist_reconcb(cs, esp8266_cb_recon);
|
||||
espconn_regist_disconcb(cs, esp8266_cb_disconnected);
|
||||
espconn_regist_sentcb(cs, esp8266_cb_sent);
|
||||
|
||||
espconn_set_opt(cs, ESPCONN_NODELAY | ESPCONN_REUSEADDR);
|
||||
espconn_regist_time(cs, 7200, 1);
|
||||
|
||||
return;
|
||||
|
||||
bail:
|
||||
lwsl_err("%s: bailed]n", __func__);
|
||||
espconn_disconnect(cs);
|
||||
}
|
||||
|
||||
void
|
||||
esp8266_tcp_stream_bind(lws_sockfd_type fd, int port, struct lws *wsi)
|
||||
{
|
||||
fd->proto.tcp->local_port = port;
|
||||
fd->reverse = wsi;
|
||||
|
||||
hacky_context = wsi->context;
|
||||
|
||||
espconn_regist_connectcb(fd, esp8266_cb_connect);
|
||||
/* hmmm it means, listen() + accept() */
|
||||
espconn_accept(fd);
|
||||
|
||||
espconn_tcp_set_max_con_allow(fd, 10);
|
||||
}
|
||||
|
||||
void
|
||||
esp8266_tcp_stream_accept(lws_sockfd_type fd, struct lws *wsi)
|
||||
{
|
||||
int n;
|
||||
|
||||
fd->reverse = wsi;
|
||||
|
||||
for (n = 0; n < wsi->context->max_fds ; n++)
|
||||
if (wsi->context->connpool[n] == wsi->desc.sockfd)
|
||||
wsi->position_in_fds_table = n;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_drop_app_privileges(struct lws_context_creation_info *info)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_context_early_init(void)
|
||||
{
|
||||
espconn_tcp_set_max_con(12);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_context_early_destroy(struct lws_context *context)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_context_late_destroy(struct lws_context *context)
|
||||
{
|
||||
#if 0
|
||||
struct lws_context_per_thread *pt = &context->pt[0];
|
||||
int m = context->count_threads;
|
||||
|
||||
if (context->lws_lookup)
|
||||
lws_free(context->lws_lookup);
|
||||
|
||||
while (m--) {
|
||||
close(pt->dummy_pipe_fds[0]);
|
||||
close(pt->dummy_pipe_fds[1]);
|
||||
pt++;
|
||||
}
|
||||
#endif
|
||||
// close(context->fd_random);
|
||||
}
|
||||
|
||||
/* cast a struct sockaddr_in6 * into addr for ipv6 */
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
|
||||
size_t addrlen)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
|
||||
context->connpool[wsi->position_in_fds_table + context->max_fds] = (lws_sockfd_type)wsi;
|
||||
wsi->desc.sockfd->reverse = wsi;
|
||||
pt->fds_count++;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_delete_socket_from_fds(struct lws_context *context,
|
||||
struct lws *wsi, int m)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
int n;
|
||||
|
||||
for (n = 0; n < wsi->context->max_fds; n++)
|
||||
if (wsi->context->connpool[n] == wsi->desc.sockfd) {
|
||||
wsi->context->connpool[n] = NULL;
|
||||
wsi->context->connpool[n + wsi->context->max_fds] = NULL;
|
||||
lwsl_notice(" freed connpool %d\n", n);
|
||||
}
|
||||
|
||||
wsi->desc.sockfd->reverse = NULL;
|
||||
pt->fds_count--;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_service_periodic(struct lws_context *context)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_change_pollfd(struct lws_context *context,
|
||||
struct lws *wsi, struct lws_pollfd *pfd)
|
||||
{
|
||||
void *p;
|
||||
|
||||
//lwsl_notice("%s: %p: wsi->pift=%d, events %d\n",
|
||||
// __func__, wsi, wsi->position_in_fds_table, pfd->events);
|
||||
|
||||
if (pfd->events & LWS_POLLIN) {
|
||||
if (wsi->premature_rx) {
|
||||
lwsl_notice("replaying buffered rx: wsi %p\n", wsi);
|
||||
p = wsi->premature_rx;
|
||||
wsi->premature_rx = NULL;
|
||||
esp8266_cb_rx(wsi->desc.sockfd,
|
||||
(char *)p + wsi->prem_rx_pos,
|
||||
wsi->prem_rx_size - wsi->prem_rx_pos);
|
||||
wsi->prem_rx_size = 0;
|
||||
wsi->prem_rx_pos = 0;
|
||||
lws_free(p);
|
||||
}
|
||||
if (espconn_recv_unhold(wsi->desc.sockfd) < 0)
|
||||
return -1;
|
||||
} else
|
||||
if (espconn_recv_hold(wsi->desc.sockfd) < 0)
|
||||
return -1;
|
||||
|
||||
if (!(pfd->events & LWS_POLLOUT))
|
||||
return 0;
|
||||
|
||||
if (!wsi->pending_send_completion) {
|
||||
pfd->revents |= LWS_POLLOUT;
|
||||
|
||||
// lwsl_notice("doing POLLOUT\n");
|
||||
lws_service_fd(lws_get_context(wsi), pfd);
|
||||
} //else
|
||||
//lwsl_notice("pending sc\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE const char *
|
||||
lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
|
||||
{
|
||||
// return inet_ntop(af, src, dst, cnt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_inet_pton(int af, const char *src, void *dst)
|
||||
{
|
||||
//return inet_pton(af, src, dst);
|
||||
return 1;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_init(struct lws_context *context,
|
||||
struct lws_context_creation_info *info)
|
||||
{
|
||||
// struct lws_context_per_thread *pt = &context->pt[0];
|
||||
// int n = context->count_threads, fd;
|
||||
|
||||
/* master context has the global fd lookup array */
|
||||
context->connpool = lws_zalloc(sizeof(struct espconn *) *
|
||||
context->max_fds * 2);
|
||||
if (context->connpool == NULL) {
|
||||
lwsl_err("OOM on lws_lookup array for %d connections\n",
|
||||
context->max_fds);
|
||||
return 1;
|
||||
}
|
||||
|
||||
lwsl_notice(" mem: platform fd map: %5u bytes\n",
|
||||
sizeof(struct espconn *) * context->max_fds);
|
||||
// fd = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY);
|
||||
|
||||
// context->fd_random = fd;
|
||||
// if (context->fd_random < 0) {
|
||||
// lwsl_err("Unable to open random device %s %d\n",
|
||||
// SYSTEM_RANDOM_FILEPATH, context->fd_random);
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
os_memset(&context->to_timer, 0, sizeof(os_timer_t));
|
||||
os_timer_disarm(&context->to_timer);
|
||||
os_timer_setfn(&context->to_timer, (os_timer_func_t *)cb_1Hz, context);
|
||||
os_timer_arm(&context->to_timer, 1000, 1);
|
||||
|
||||
if (!lws_libev_init_fd_table(context) &&
|
||||
!lws_libuv_init_fd_table(context) &&
|
||||
!lws_libevent_init_fd_table(context)) {
|
||||
/* otherwise libev handled it instead */
|
||||
#if 0
|
||||
while (n--) {
|
||||
if (pipe(pt->dummy_pipe_fds)) {
|
||||
lwsl_err("Unable to create pipe\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* use the read end of pipe as first item */
|
||||
pt->fds[0].fd = pt->dummy_pipe_fds[0];
|
||||
pt->fds[0].events = LWS_POLLIN;
|
||||
pt->fds[0].revents = 0;
|
||||
pt->fds_count = 1;
|
||||
pt++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef LWS_WITH_PLUGINS
|
||||
if (info->plugin_dirs)
|
||||
lws_plat_plugins_init(context, info->plugin_dirs);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
329
lib/lws-plat-optee.c
Normal file
329
lib/lws-plat-optee.c
Normal file
|
@ -0,0 +1,329 @@
|
|||
#include "private-libwebsockets.h"
|
||||
|
||||
/*
|
||||
* included from libwebsockets.c for OPTEE builds
|
||||
*/
|
||||
|
||||
void TEE_GenerateRandom(void *randomBuffer, uint32_t randomBufferLen);
|
||||
|
||||
unsigned long long time_in_microseconds(void)
|
||||
{
|
||||
return ((unsigned long long)time(NULL)) * 1000000;
|
||||
}
|
||||
#if 0
|
||||
LWS_VISIBLE int
|
||||
lws_get_random(struct lws_context *context, void *buf, int len)
|
||||
{
|
||||
TEE_GenerateRandom(buf, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
LWS_VISIBLE int
|
||||
lws_send_pipe_choked(struct lws *wsi)
|
||||
{
|
||||
#if 0
|
||||
struct lws_pollfd fds;
|
||||
|
||||
/* treat the fact we got a truncated send pending as if we're choked */
|
||||
if (wsi->trunc_len)
|
||||
return 1;
|
||||
|
||||
fds.fd = wsi->desc.sockfd;
|
||||
fds.events = POLLOUT;
|
||||
fds.revents = 0;
|
||||
|
||||
if (poll(&fds, 1, 0) != 1)
|
||||
return 1;
|
||||
|
||||
if ((fds.revents & POLLOUT) == 0)
|
||||
return 1;
|
||||
#endif
|
||||
/* okay to send another packet without blocking */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_poll_listen_fd(struct lws_pollfd *fd)
|
||||
{
|
||||
// return poll(fd, 1, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_cancel_service_pt(struct lws *wsi)
|
||||
{
|
||||
#if 0
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
char buf = 0;
|
||||
|
||||
if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1)
|
||||
lwsl_err("Cannot write to dummy pipe");
|
||||
#endif
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_cancel_service(struct lws_context *context)
|
||||
{
|
||||
#if 0
|
||||
struct lws_context_per_thread *pt = &context->pt[0];
|
||||
char buf = 0, m = context->count_threads;
|
||||
|
||||
while (m--) {
|
||||
if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1)
|
||||
lwsl_err("Cannot write to dummy pipe");
|
||||
pt++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if 0
|
||||
LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
|
||||
{
|
||||
IMSG("%d: %s\n", level, line);
|
||||
}
|
||||
#endif
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt;
|
||||
int n = -1, m, c;
|
||||
//char buf;
|
||||
|
||||
/* stay dead once we are dead */
|
||||
|
||||
if (!context || !context->vhost_list)
|
||||
return 1;
|
||||
|
||||
pt = &context->pt[tsi];
|
||||
|
||||
if (timeout_ms < 0)
|
||||
goto faked_service;
|
||||
|
||||
if (!context->service_tid_detected) {
|
||||
struct lws _lws;
|
||||
|
||||
memset(&_lws, 0, sizeof(_lws));
|
||||
_lws.context = context;
|
||||
|
||||
context->service_tid_detected =
|
||||
context->vhost_list->protocols[0].callback(
|
||||
&_lws, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
|
||||
}
|
||||
context->service_tid = context->service_tid_detected;
|
||||
|
||||
/*
|
||||
* is there anybody with pending stuff that needs service forcing?
|
||||
*/
|
||||
if (!lws_service_adjust_timeout(context, 1, tsi)) {
|
||||
lwsl_notice("%s: doing forced service\n", __func__);
|
||||
/* -1 timeout means just do forced service */
|
||||
_lws_plat_service_tsi(context, -1, pt->tid);
|
||||
/* still somebody left who wants forced service? */
|
||||
if (!lws_service_adjust_timeout(context, 1, pt->tid))
|
||||
/* yes... come back again quickly */
|
||||
timeout_ms = 0;
|
||||
}
|
||||
#if 1
|
||||
n = poll(pt->fds, pt->fds_count, timeout_ms);
|
||||
|
||||
#ifdef LWS_OPENSSL_SUPPORT
|
||||
if (!pt->rx_draining_ext_list &&
|
||||
!lws_ssl_anybody_has_buffered_read_tsi(context, tsi) && !n) {
|
||||
#else
|
||||
if (!pt->rx_draining_ext_list && !n) /* poll timeout */ {
|
||||
#endif
|
||||
lws_service_fd_tsi(context, NULL, tsi);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
faked_service:
|
||||
m = lws_service_flag_pending(context, tsi);
|
||||
if (m)
|
||||
c = -1; /* unknown limit */
|
||||
else
|
||||
if (n < 0) {
|
||||
if (LWS_ERRNO != LWS_EINTR)
|
||||
return -1;
|
||||
return 0;
|
||||
} else
|
||||
c = n;
|
||||
|
||||
/* any socket with events to service? */
|
||||
for (n = 0; n < pt->fds_count && c; n++) {
|
||||
if (!pt->fds[n].revents)
|
||||
continue;
|
||||
|
||||
c--;
|
||||
#if 0
|
||||
if (pt->fds[n].fd == pt->dummy_pipe_fds[0]) {
|
||||
if (read(pt->fds[n].fd, &buf, 1) != 1)
|
||||
lwsl_err("Cannot read from dummy pipe.");
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
|
||||
if (m < 0)
|
||||
return -1;
|
||||
/* if something closed, retry this slot */
|
||||
if (m)
|
||||
n--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_check_connection_error(struct lws *wsi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_service(struct lws_context *context, int timeout_ms)
|
||||
{
|
||||
return _lws_plat_service_tsi(context, timeout_ms, 0);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_set_socket_options(struct lws_vhost *vhost, int fd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_drop_app_privileges(struct lws_context_creation_info *info)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_context_early_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_context_early_destroy(struct lws_context *context)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_context_late_destroy(struct lws_context *context)
|
||||
{
|
||||
if (context->lws_lookup)
|
||||
lws_free(context->lws_lookup);
|
||||
}
|
||||
|
||||
/* cast a struct sockaddr_in6 * into addr for ipv6 */
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
|
||||
size_t addrlen)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
|
||||
pt->fds[pt->fds_count++].revents = 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_delete_socket_from_fds(struct lws_context *context,
|
||||
struct lws *wsi, int m)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
|
||||
pt->fds_count--;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_service_periodic(struct lws_context *context)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_change_pollfd(struct lws_context *context,
|
||||
struct lws *wsi, struct lws_pollfd *pfd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE const char *
|
||||
lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
|
||||
{
|
||||
//return inet_ntop(af, src, dst, cnt);
|
||||
return "lws_plat_inet_ntop";
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_inet_pton(int af, const char *src, void *dst)
|
||||
{
|
||||
//return inet_pton(af, src, dst);
|
||||
return 1;
|
||||
}
|
||||
|
||||
LWS_VISIBLE lws_fop_fd_t
|
||||
_lws_plat_file_open(lws_plat_file_open(struct lws_plat_file_ops *fops,
|
||||
const char *filename, lws_fop_flags_t *flags)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
_lws_plat_file_close(lws_fop_fd_t *fop_fd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE lws_fileofs_t
|
||||
_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
|
||||
uint8_t *buf, lws_filepos_t len)
|
||||
{
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
|
||||
uint8_t *buf, lws_filepos_t len)
|
||||
{
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_init(struct lws_context *context,
|
||||
struct lws_context_creation_info *info)
|
||||
{
|
||||
/* master context has the global fd lookup array */
|
||||
context->lws_lookup = lws_zalloc(sizeof(struct lws *) *
|
||||
context->max_fds);
|
||||
if (context->lws_lookup == NULL) {
|
||||
lwsl_err("OOM on lws_lookup array for %d connections\n",
|
||||
context->max_fds);
|
||||
return 1;
|
||||
}
|
||||
|
||||
lwsl_notice(" mem: platform fd map: %5lu bytes\n",
|
||||
(long)sizeof(struct lws *) * context->max_fds);
|
||||
|
||||
#ifdef LWS_WITH_PLUGINS
|
||||
if (info->plugin_dirs)
|
||||
lws_plat_plugins_init(context, info->plugin_dirs);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -3,6 +3,12 @@
|
|||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
|
||||
#ifdef LWS_WITH_PLUGINS
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#include <dirent.h>
|
||||
|
||||
|
||||
/*
|
||||
* included from libwebsockets.c for unix builds
|
||||
*/
|
||||
|
@ -14,21 +20,22 @@ unsigned long long time_in_microseconds(void)
|
|||
return ((unsigned long long)tv.tv_sec * 1000000LL) + tv.tv_usec;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int libwebsockets_get_random(struct libwebsocket_context *context,
|
||||
void *buf, int len)
|
||||
LWS_VISIBLE int
|
||||
lws_get_random(struct lws_context *context, void *buf, int len)
|
||||
{
|
||||
return read(context->fd_random, (char *)buf, len);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int lws_send_pipe_choked(struct libwebsocket *wsi)
|
||||
LWS_VISIBLE int
|
||||
lws_send_pipe_choked(struct lws *wsi)
|
||||
{
|
||||
struct libwebsocket_pollfd fds;
|
||||
struct lws_pollfd fds;
|
||||
|
||||
/* treat the fact we got a truncated send pending as if we're choked */
|
||||
if (wsi->truncated_send_len)
|
||||
if (wsi->trunc_len)
|
||||
return 1;
|
||||
|
||||
fds.fd = wsi->sock;
|
||||
fds.fd = wsi->desc.sockfd;
|
||||
fds.events = POLLOUT;
|
||||
fds.revents = 0;
|
||||
|
||||
|
@ -44,35 +51,34 @@ LWS_VISIBLE int lws_send_pipe_choked(struct libwebsocket *wsi)
|
|||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_poll_listen_fd(struct libwebsocket_pollfd *fd)
|
||||
lws_poll_listen_fd(struct lws_pollfd *fd)
|
||||
{
|
||||
return poll(fd, 1, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* This is just used to interrupt poll waiting
|
||||
* we don't have to do anything with it.
|
||||
*/
|
||||
static void lws_sigusr2(int sig)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* libwebsocket_cancel_service() - Cancel servicing of pending websocket activity
|
||||
* @context: Websocket context
|
||||
*
|
||||
* This function let a call to libwebsocket_service() waiting for a timeout
|
||||
* immediately return.
|
||||
*/
|
||||
LWS_VISIBLE void
|
||||
libwebsocket_cancel_service(struct libwebsocket_context *context)
|
||||
lws_cancel_service_pt(struct lws *wsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
char buf = 0;
|
||||
|
||||
if (write(context->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1)
|
||||
if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1)
|
||||
lwsl_err("Cannot write to dummy pipe");
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_cancel_service(struct lws_context *context)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[0];
|
||||
char buf = 0, m = context->count_threads;
|
||||
|
||||
while (m--) {
|
||||
if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1)
|
||||
lwsl_err("Cannot write to dummy pipe");
|
||||
pt++;
|
||||
}
|
||||
}
|
||||
|
||||
LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
|
||||
{
|
||||
int syslog_level = LOG_DEBUG;
|
||||
|
@ -94,89 +100,91 @@ LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
|
|||
syslog(syslog_level, "%s", line);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_service(struct libwebsocket_context *context, int timeout_ms)
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
|
||||
{
|
||||
int n;
|
||||
int m;
|
||||
struct lws_context_per_thread *pt;
|
||||
int n = -1, m, c;
|
||||
char buf;
|
||||
#ifdef LWS_OPENSSL_SUPPORT
|
||||
struct libwebsocket *wsi, *wsi_next;
|
||||
#endif
|
||||
|
||||
/* stay dead once we are dead */
|
||||
|
||||
if (!context)
|
||||
if (!context || !context->vhost_list)
|
||||
return 1;
|
||||
|
||||
lws_libev_run(context);
|
||||
pt = &context->pt[tsi];
|
||||
|
||||
context->service_tid = context->protocols[0].callback(context, NULL,
|
||||
LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
|
||||
lws_stats_atomic_bump(context, pt, LWSSTATS_C_SERVICE_ENTRY, 1);
|
||||
|
||||
#ifdef LWS_OPENSSL_SUPPORT
|
||||
/* if we know we have non-network pending data, do not wait in poll */
|
||||
if (lws_ssl_anybody_has_buffered_read(context))
|
||||
timeout_ms = 0;
|
||||
#endif
|
||||
n = poll(context->fds, context->fds_count, timeout_ms);
|
||||
context->service_tid = 0;
|
||||
if (timeout_ms < 0)
|
||||
goto faked_service;
|
||||
|
||||
#ifdef LWS_OPENSSL_SUPPORT
|
||||
if (!lws_ssl_anybody_has_buffered_read(context) && n == 0) {
|
||||
#else
|
||||
if (n == 0) /* poll timeout */ {
|
||||
#endif
|
||||
libwebsocket_service_fd(context, NULL);
|
||||
return 0;
|
||||
lws_libev_run(context, tsi);
|
||||
lws_libuv_run(context, tsi);
|
||||
lws_libevent_run(context, tsi);
|
||||
|
||||
if (!context->service_tid_detected) {
|
||||
struct lws _lws;
|
||||
|
||||
memset(&_lws, 0, sizeof(_lws));
|
||||
_lws.context = context;
|
||||
|
||||
context->service_tid_detected =
|
||||
context->vhost_list->protocols[0].callback(
|
||||
&_lws, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
|
||||
}
|
||||
context->service_tid = context->service_tid_detected;
|
||||
|
||||
if (n < 0) {
|
||||
if (LWS_ERRNO != LWS_EINTR)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef LWS_OPENSSL_SUPPORT
|
||||
/*
|
||||
* For all guys with buffered SSL read data already saved up, if they
|
||||
* are not flowcontrolled, fake their POLLIN status so they'll get
|
||||
* service to use up the buffered incoming data, even though their
|
||||
* network socket may have nothing
|
||||
* is there anybody with pending stuff that needs service forcing?
|
||||
*/
|
||||
|
||||
wsi = context->pending_read_list;
|
||||
while (wsi) {
|
||||
wsi_next = wsi->pending_read_list_next;
|
||||
context->fds[wsi->position_in_fds_table].revents |=
|
||||
context->fds[wsi->position_in_fds_table].events & POLLIN;
|
||||
if (context->fds[wsi->position_in_fds_table].revents & POLLIN) {
|
||||
/*
|
||||
* he's going to get serviced now, take him off the
|
||||
* list of guys with buffered SSL. If he still has some
|
||||
* at the end of the service, he'll get put back on the
|
||||
* list then.
|
||||
*/
|
||||
lws_ssl_remove_wsi_from_buffered_list(context, wsi);
|
||||
}
|
||||
wsi = wsi_next;
|
||||
if (!lws_service_adjust_timeout(context, 1, tsi)) {
|
||||
/* -1 timeout means just do forced service */
|
||||
_lws_plat_service_tsi(context, -1, pt->tid);
|
||||
/* still somebody left who wants forced service? */
|
||||
if (!lws_service_adjust_timeout(context, 1, pt->tid))
|
||||
/* yes... come back again quickly */
|
||||
timeout_ms = 0;
|
||||
}
|
||||
|
||||
n = poll(pt->fds, pt->fds_count, timeout_ms);
|
||||
|
||||
#ifdef LWS_OPENSSL_SUPPORT
|
||||
if (!pt->rx_draining_ext_list &&
|
||||
!lws_ssl_anybody_has_buffered_read_tsi(context, tsi) && !n) {
|
||||
#else
|
||||
if (!pt->rx_draining_ext_list && !n) /* poll timeout */ {
|
||||
#endif
|
||||
lws_service_fd_tsi(context, NULL, tsi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
faked_service:
|
||||
m = lws_service_flag_pending(context, tsi);
|
||||
if (m)
|
||||
c = -1; /* unknown limit */
|
||||
else
|
||||
if (n < 0) {
|
||||
if (LWS_ERRNO != LWS_EINTR)
|
||||
return -1;
|
||||
return 0;
|
||||
} else
|
||||
c = n;
|
||||
|
||||
/* any socket with events to service? */
|
||||
|
||||
for (n = 0; n < context->fds_count; n++) {
|
||||
|
||||
if (!context->fds[n].revents)
|
||||
for (n = 0; n < pt->fds_count && c; n++) {
|
||||
if (!pt->fds[n].revents)
|
||||
continue;
|
||||
|
||||
if (context->fds[n].fd == context->dummy_pipe_fds[0]) {
|
||||
if (read(context->fds[n].fd, &buf, 1) != 1)
|
||||
c--;
|
||||
|
||||
if (pt->fds[n].fd == pt->dummy_pipe_fds[0]) {
|
||||
if (read(pt->fds[n].fd, &buf, 1) != 1)
|
||||
lwsl_err("Cannot read from dummy pipe.");
|
||||
continue;
|
||||
}
|
||||
|
||||
m = libwebsocket_service_fd(context, &context->fds[n]);
|
||||
m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
|
||||
if (m < 0)
|
||||
return -1;
|
||||
/* if something closed, retry this slot */
|
||||
|
@ -188,25 +196,41 @@ lws_plat_service(struct libwebsocket_context *context, int timeout_ms)
|
|||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_set_socket_options(struct libwebsocket_context *context, int fd)
|
||||
lws_plat_check_connection_error(struct lws *wsi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_service(struct lws_context *context, int timeout_ms)
|
||||
{
|
||||
return _lws_plat_service_tsi(context, timeout_ms, 0);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_set_socket_options(struct lws_vhost *vhost, int fd)
|
||||
{
|
||||
int optval = 1;
|
||||
socklen_t optlen = sizeof(optval);
|
||||
|
||||
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
|
||||
#if defined(__APPLE__) || \
|
||||
defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
|
||||
defined(__NetBSD__) || \
|
||||
defined(__OpenBSD__)
|
||||
struct protoent *tcp_proto;
|
||||
#endif
|
||||
|
||||
if (context->ka_time) {
|
||||
if (vhost->ka_time) {
|
||||
/* enable keepalive on this socket */
|
||||
optval = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
|
||||
(const void *)&optval, optlen) < 0)
|
||||
(const void *)&optval, optlen) < 0)
|
||||
return 1;
|
||||
|
||||
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
|
||||
defined(__CYGWIN__) || defined(__OpenBSD__)
|
||||
#if defined(__APPLE__) || \
|
||||
defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
|
||||
defined(__NetBSD__) || \
|
||||
defined(__CYGWIN__) || defined(__OpenBSD__) || defined (__sun)
|
||||
|
||||
/*
|
||||
* didn't find a way to set these per-socket, need to
|
||||
|
@ -214,27 +238,43 @@ lws_plat_set_socket_options(struct libwebsocket_context *context, int fd)
|
|||
*/
|
||||
#else
|
||||
/* set the keepalive conditions we want on it too */
|
||||
optval = context->ka_time;
|
||||
optval = vhost->ka_time;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE,
|
||||
(const void *)&optval, optlen) < 0)
|
||||
(const void *)&optval, optlen) < 0)
|
||||
return 1;
|
||||
|
||||
optval = context->ka_interval;
|
||||
optval = vhost->ka_interval;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL,
|
||||
(const void *)&optval, optlen) < 0)
|
||||
(const void *)&optval, optlen) < 0)
|
||||
return 1;
|
||||
|
||||
optval = context->ka_probes;
|
||||
optval = vhost->ka_probes;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT,
|
||||
(const void *)&optval, optlen) < 0)
|
||||
(const void *)&optval, optlen) < 0)
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(SO_BINDTODEVICE)
|
||||
if (vhost->bind_iface && vhost->iface) {
|
||||
lwsl_info("binding listen skt to %s using SO_BINDTODEVICE\n", vhost->iface);
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, vhost->iface,
|
||||
strlen(vhost->iface)) < 0) {
|
||||
lwsl_warn("Failed to bind to device %s\n", vhost->iface);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Disable Nagle */
|
||||
optval = 1;
|
||||
#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && \
|
||||
!defined(__OpenBSD__)
|
||||
#if defined (__sun)
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0)
|
||||
return 1;
|
||||
#elif !defined(__APPLE__) && \
|
||||
!defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && \
|
||||
!defined(__NetBSD__) && \
|
||||
!defined(__OpenBSD__)
|
||||
if (setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0)
|
||||
return 1;
|
||||
#else
|
||||
|
@ -250,111 +290,266 @@ lws_plat_set_socket_options(struct libwebsocket_context *context, int fd)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
|
||||
static void
|
||||
_lws_plat_apply_caps(int mode, cap_value_t *cv, int count)
|
||||
{
|
||||
cap_t caps;
|
||||
|
||||
if (!count)
|
||||
return;
|
||||
|
||||
caps = cap_get_proc();
|
||||
|
||||
cap_set_flag(caps, mode, count, cv, CAP_SET);
|
||||
cap_set_proc(caps);
|
||||
prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
|
||||
cap_free(caps);
|
||||
}
|
||||
#endif
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_drop_app_privileges(struct lws_context_creation_info *info)
|
||||
{
|
||||
if (info->uid != -1) {
|
||||
struct passwd *p = getpwuid(info->uid);
|
||||
#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
|
||||
int n;
|
||||
#endif
|
||||
|
||||
if (p) {
|
||||
initgroups(p->pw_name, info->gid);
|
||||
if (setuid(info->uid))
|
||||
lwsl_warn("setuid: %s\n", strerror(LWS_ERRNO));
|
||||
else
|
||||
lwsl_notice(" Set privs to user '%s'\n", p->pw_name);
|
||||
} else
|
||||
lwsl_warn("getpwuid: unable to find uid %d", info->uid);
|
||||
}
|
||||
if (info->gid != -1)
|
||||
if (setgid(info->gid))
|
||||
lwsl_warn("setgid: %s\n", strerror(LWS_ERRNO));
|
||||
|
||||
if (info->uid != -1) {
|
||||
struct passwd *p = getpwuid(info->uid);
|
||||
|
||||
if (p) {
|
||||
|
||||
#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
|
||||
_lws_plat_apply_caps(CAP_PERMITTED, info->caps, info->count_caps);
|
||||
#endif
|
||||
|
||||
initgroups(p->pw_name, info->gid);
|
||||
if (setuid(info->uid))
|
||||
lwsl_warn("setuid: %s\n", strerror(LWS_ERRNO));
|
||||
else
|
||||
lwsl_notice("Set privs to user '%s'\n", p->pw_name);
|
||||
|
||||
#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
|
||||
_lws_plat_apply_caps(CAP_EFFECTIVE, info->caps, info->count_caps);
|
||||
|
||||
if (info->count_caps)
|
||||
for (n = 0; n < info->count_caps; n++)
|
||||
lwsl_notice(" RETAINING CAPABILITY %d\n", (int)info->caps[n]);
|
||||
#endif
|
||||
|
||||
} else
|
||||
lwsl_warn("getpwuid: unable to find uid %d", info->uid);
|
||||
}
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_init_lookup(struct libwebsocket_context *context)
|
||||
#ifdef LWS_WITH_PLUGINS
|
||||
|
||||
#if defined(LWS_USE_LIBUV) && UV_VERSION_MAJOR > 0
|
||||
|
||||
/* libuv.c implements these in a cross-platform way */
|
||||
|
||||
#else
|
||||
|
||||
static int filter(const struct dirent *ent)
|
||||
{
|
||||
context->lws_lookup = lws_zalloc(sizeof(struct libwebsocket *) * context->max_fds);
|
||||
if (context->lws_lookup == NULL) {
|
||||
lwsl_err(
|
||||
"Unable to allocate lws_lookup array for %d connections\n",
|
||||
context->max_fds);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_init_fd_tables(struct libwebsocket_context *context)
|
||||
{
|
||||
context->fd_random = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY);
|
||||
if (context->fd_random < 0) {
|
||||
lwsl_err("Unable to open random device %s %d\n",
|
||||
SYSTEM_RANDOM_FILEPATH, context->fd_random);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (lws_libev_init_fd_table(context))
|
||||
/* libev handled it instead */
|
||||
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
|
||||
return 0;
|
||||
|
||||
if (pipe(context->dummy_pipe_fds)) {
|
||||
lwsl_err("Unable to create pipe\n");
|
||||
return 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_plugins_init(struct lws_context * context, const char * const *d)
|
||||
{
|
||||
struct lws_plugin_capability lcaps;
|
||||
struct lws_plugin *plugin;
|
||||
lws_plugin_init_func initfunc;
|
||||
struct dirent **namelist;
|
||||
int n, i, m, ret = 0;
|
||||
char path[256];
|
||||
void *l;
|
||||
|
||||
lwsl_notice(" Plugins:\n");
|
||||
|
||||
while (d && *d) {
|
||||
n = scandir(*d, &namelist, filter, alphasort);
|
||||
if (n < 0) {
|
||||
lwsl_err("Scandir on %s failed\n", *d);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (strlen(namelist[i]->d_name) < 7)
|
||||
goto inval;
|
||||
|
||||
lwsl_notice(" %s\n", namelist[i]->d_name);
|
||||
|
||||
lws_snprintf(path, sizeof(path) - 1, "%s/%s", *d,
|
||||
namelist[i]->d_name);
|
||||
l = dlopen(path, RTLD_NOW);
|
||||
if (!l) {
|
||||
lwsl_err("Error loading DSO: %s\n", dlerror());
|
||||
while (i++ < n)
|
||||
free(namelist[i]);
|
||||
goto bail;
|
||||
}
|
||||
/* we could open it, can we get his init function? */
|
||||
m = lws_snprintf(path, sizeof(path) - 1, "init_%s",
|
||||
namelist[i]->d_name + 3 /* snip lib... */);
|
||||
path[m - 3] = '\0'; /* snip the .so */
|
||||
initfunc = dlsym(l, path);
|
||||
if (!initfunc) {
|
||||
lwsl_err("Failed to get init on %s: %s",
|
||||
namelist[i]->d_name, dlerror());
|
||||
dlclose(l);
|
||||
}
|
||||
lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
|
||||
m = initfunc(context, &lcaps);
|
||||
if (m) {
|
||||
lwsl_err("Initializing %s failed %d\n",
|
||||
namelist[i]->d_name, m);
|
||||
dlclose(l);
|
||||
goto skip;
|
||||
}
|
||||
|
||||
plugin = lws_malloc(sizeof(*plugin));
|
||||
if (!plugin) {
|
||||
lwsl_err("OOM\n");
|
||||
goto bail;
|
||||
}
|
||||
plugin->list = context->plugin_list;
|
||||
context->plugin_list = plugin;
|
||||
strncpy(plugin->name, namelist[i]->d_name, sizeof(plugin->name) - 1);
|
||||
plugin->name[sizeof(plugin->name) - 1] = '\0';
|
||||
plugin->l = l;
|
||||
plugin->caps = lcaps;
|
||||
context->plugin_protocol_count += lcaps.count_protocols;
|
||||
context->plugin_extension_count += lcaps.count_extensions;
|
||||
|
||||
free(namelist[i]);
|
||||
continue;
|
||||
|
||||
skip:
|
||||
dlclose(l);
|
||||
inval:
|
||||
free(namelist[i]);
|
||||
}
|
||||
free(namelist);
|
||||
d++;
|
||||
}
|
||||
|
||||
/* use the read end of pipe as first item */
|
||||
context->fds[0].fd = context->dummy_pipe_fds[0];
|
||||
context->fds[0].events = LWS_POLLIN;
|
||||
context->fds[0].revents = 0;
|
||||
context->fds_count = 1;
|
||||
bail:
|
||||
free(namelist);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_plugins_destroy(struct lws_context * context)
|
||||
{
|
||||
struct lws_plugin *plugin = context->plugin_list, *p;
|
||||
lws_plugin_destroy_func func;
|
||||
char path[256];
|
||||
int m;
|
||||
|
||||
if (!plugin)
|
||||
return 0;
|
||||
|
||||
lwsl_notice("%s\n", __func__);
|
||||
|
||||
while (plugin) {
|
||||
p = plugin;
|
||||
m = lws_snprintf(path, sizeof(path) - 1, "destroy_%s", plugin->name + 3);
|
||||
path[m - 3] = '\0';
|
||||
func = dlsym(plugin->l, path);
|
||||
if (!func) {
|
||||
lwsl_err("Failed to get destroy on %s: %s",
|
||||
plugin->name, dlerror());
|
||||
goto next;
|
||||
}
|
||||
m = func(context);
|
||||
if (m)
|
||||
lwsl_err("Initializing %s failed %d\n",
|
||||
plugin->name, m);
|
||||
next:
|
||||
dlclose(p->l);
|
||||
plugin = p->list;
|
||||
p->list = NULL;
|
||||
free(p);
|
||||
}
|
||||
|
||||
context->plugin_list = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sigpipe_handler(int x)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#if 0
|
||||
static void
|
||||
sigabrt_handler(int x)
|
||||
{
|
||||
printf("%s\n", __func__);
|
||||
//*(char *)0 = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_context_early_init(void)
|
||||
{
|
||||
sigset_t mask;
|
||||
#if !defined(LWS_AVOID_SIGPIPE_IGN)
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
signal(SIGUSR2, lws_sigusr2);
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGUSR2);
|
||||
|
||||
sigprocmask(SIG_BLOCK, &mask, NULL);
|
||||
|
||||
signal(SIGPIPE, sigpipe_handler);
|
||||
// signal(SIGABRT, sigabrt_handler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_context_early_destroy(struct libwebsocket_context *context)
|
||||
lws_plat_context_early_destroy(struct lws_context *context)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_context_late_destroy(struct libwebsocket_context *context)
|
||||
lws_plat_context_late_destroy(struct lws_context *context)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[0];
|
||||
int m = context->count_threads;
|
||||
|
||||
#ifdef LWS_WITH_PLUGINS
|
||||
if (context->plugin_list)
|
||||
lws_plat_plugins_destroy(context);
|
||||
#endif
|
||||
|
||||
if (context->lws_lookup)
|
||||
lws_free(context->lws_lookup);
|
||||
|
||||
close(context->dummy_pipe_fds[0]);
|
||||
close(context->dummy_pipe_fds[1]);
|
||||
close(context->fd_random);
|
||||
while (m--) {
|
||||
if (pt->dummy_pipe_fds[0])
|
||||
close(pt->dummy_pipe_fds[0]);
|
||||
if (pt->dummy_pipe_fds[1])
|
||||
close(pt->dummy_pipe_fds[1]);
|
||||
pt++;
|
||||
}
|
||||
if (!context->fd_random)
|
||||
lwsl_err("ZERO RANDOM FD\n");
|
||||
if (context->fd_random != LWS_INVALID_FILE)
|
||||
close(context->fd_random);
|
||||
}
|
||||
|
||||
/* cast a struct sockaddr_in6 * into addr for ipv6 */
|
||||
|
||||
LWS_VISIBLE int
|
||||
interface_to_sa(struct libwebsocket_context *context,
|
||||
const char *ifname, struct sockaddr_in *addr, size_t addrlen)
|
||||
lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
|
||||
size_t addrlen)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
|
@ -377,7 +572,7 @@ interface_to_sa(struct libwebsocket_context *context,
|
|||
switch (ifc->ifa_addr->sa_family) {
|
||||
case AF_INET:
|
||||
#ifdef LWS_USE_IPV6
|
||||
if (LWS_IPV6_ENABLED(context)) {
|
||||
if (ipv6) {
|
||||
/* map IPv4 to IPv6 */
|
||||
bzero((char *)&addr6->sin6_addr,
|
||||
sizeof(struct in6_addr));
|
||||
|
@ -408,7 +603,7 @@ interface_to_sa(struct libwebsocket_context *context,
|
|||
freeifaddrs(ifr);
|
||||
|
||||
if (rc == -1) {
|
||||
/* check if bind to IP adddress */
|
||||
/* check if bind to IP address */
|
||||
#ifdef LWS_USE_IPV6
|
||||
if (inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1)
|
||||
rc = 0;
|
||||
|
@ -422,54 +617,212 @@ interface_to_sa(struct libwebsocket_context *context,
|
|||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_insert_socket_into_fds(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi)
|
||||
lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
lws_libev_io(context, wsi, LWS_EV_START | LWS_EV_READ);
|
||||
context->fds[context->fds_count++].revents = 0;
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
|
||||
lws_libev_io(wsi, LWS_EV_START | LWS_EV_READ);
|
||||
lws_libuv_io(wsi, LWS_EV_START | LWS_EV_READ);
|
||||
lws_libevent_io(wsi, LWS_EV_START | LWS_EV_READ);
|
||||
|
||||
pt->fds[pt->fds_count++].revents = 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_delete_socket_from_fds(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, int m)
|
||||
lws_plat_delete_socket_from_fds(struct lws_context *context,
|
||||
struct lws *wsi, int m)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
|
||||
lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE);
|
||||
lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE);
|
||||
lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE);
|
||||
|
||||
pt->fds_count--;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_service_periodic(struct libwebsocket_context *context)
|
||||
lws_plat_service_periodic(struct lws_context *context)
|
||||
{
|
||||
/* if our parent went down, don't linger around */
|
||||
if (context->started_with_parent &&
|
||||
kill(context->started_with_parent, 0) < 0)
|
||||
kill(context->started_with_parent, 0) < 0)
|
||||
kill(getpid(), SIGTERM);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_change_pollfd(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, struct libwebsocket_pollfd *pfd)
|
||||
lws_plat_change_pollfd(struct lws_context *context,
|
||||
struct lws *wsi, struct lws_pollfd *pfd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_open_file(const char* filename, unsigned long* filelen)
|
||||
{
|
||||
struct stat stat_buf;
|
||||
int ret = open(filename, O_RDONLY);
|
||||
|
||||
if (ret < 0)
|
||||
return LWS_INVALID_FILE;
|
||||
|
||||
if (fstat(ret, &stat_buf) < 0) {
|
||||
close(ret);
|
||||
return LWS_INVALID_FILE;
|
||||
}
|
||||
*filelen = stat_buf.st_size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LWS_VISIBLE const char *
|
||||
lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
|
||||
{
|
||||
return inet_ntop(af, src, dst, cnt);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_inet_pton(int af, const char *src, void *dst)
|
||||
{
|
||||
return inet_pton(af, src, dst);
|
||||
}
|
||||
|
||||
LWS_VISIBLE lws_fop_fd_t
|
||||
_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
|
||||
const char *vpath, lws_fop_flags_t *flags)
|
||||
{
|
||||
struct stat stat_buf;
|
||||
int ret = open(filename, (*flags) & LWS_FOP_FLAGS_MASK, 0664);
|
||||
lws_fop_fd_t fop_fd;
|
||||
|
||||
if (ret < 0)
|
||||
return NULL;
|
||||
|
||||
if (fstat(ret, &stat_buf) < 0)
|
||||
goto bail;
|
||||
|
||||
fop_fd = malloc(sizeof(*fop_fd));
|
||||
if (!fop_fd)
|
||||
goto bail;
|
||||
|
||||
fop_fd->fops = fops;
|
||||
fop_fd->flags = *flags;
|
||||
fop_fd->fd = ret;
|
||||
fop_fd->filesystem_priv = NULL; /* we don't use it */
|
||||
fop_fd->len = stat_buf.st_size;
|
||||
fop_fd->pos = 0;
|
||||
|
||||
return fop_fd;
|
||||
|
||||
bail:
|
||||
close(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
_lws_plat_file_close(lws_fop_fd_t *fop_fd)
|
||||
{
|
||||
int fd = (*fop_fd)->fd;
|
||||
|
||||
free(*fop_fd);
|
||||
*fop_fd = NULL;
|
||||
|
||||
return close(fd);
|
||||
}
|
||||
|
||||
LWS_VISIBLE lws_fileofs_t
|
||||
_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
|
||||
{
|
||||
lws_fileofs_t r;
|
||||
|
||||
if (offset > 0 && offset > fop_fd->len - fop_fd->pos)
|
||||
offset = fop_fd->len - fop_fd->pos;
|
||||
|
||||
if ((lws_fileofs_t)fop_fd->pos + offset < 0)
|
||||
offset = -fop_fd->pos;
|
||||
|
||||
r = lseek(fop_fd->fd, offset, SEEK_CUR);
|
||||
|
||||
if (r >= 0)
|
||||
fop_fd->pos = r;
|
||||
else
|
||||
lwsl_err("error seeking from cur %ld, offset %ld\n",
|
||||
(long)fop_fd->pos, (long)offset);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
|
||||
uint8_t *buf, lws_filepos_t len)
|
||||
{
|
||||
long n;
|
||||
|
||||
n = read((int)fop_fd->fd, buf, len);
|
||||
if (n == -1) {
|
||||
*amount = 0;
|
||||
return -1;
|
||||
}
|
||||
fop_fd->pos += n;
|
||||
lwsl_debug("%s: read %ld of req %ld, pos %ld, len %ld\n", __func__, n,
|
||||
(long)len, (long)fop_fd->pos, (long)fop_fd->len);
|
||||
*amount = n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
|
||||
uint8_t *buf, lws_filepos_t len)
|
||||
{
|
||||
long n;
|
||||
|
||||
n = write((int)fop_fd->fd, buf, len);
|
||||
if (n == -1) {
|
||||
*amount = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
fop_fd->pos += n;
|
||||
*amount = n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_init(struct lws_context *context,
|
||||
struct lws_context_creation_info *info)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[0];
|
||||
int n = context->count_threads, fd;
|
||||
|
||||
/* master context has the global fd lookup array */
|
||||
context->lws_lookup = lws_zalloc(sizeof(struct lws *) *
|
||||
context->max_fds);
|
||||
if (context->lws_lookup == NULL) {
|
||||
lwsl_err("OOM on lws_lookup array for %d connections\n",
|
||||
context->max_fds);
|
||||
return 1;
|
||||
}
|
||||
|
||||
lwsl_notice(" mem: platform fd map: %5lu bytes\n",
|
||||
(unsigned long)(sizeof(struct lws *) * context->max_fds));
|
||||
fd = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY);
|
||||
|
||||
context->fd_random = fd;
|
||||
if (context->fd_random < 0) {
|
||||
lwsl_err("Unable to open random device %s %d\n",
|
||||
SYSTEM_RANDOM_FILEPATH, context->fd_random);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!lws_libev_init_fd_table(context) &&
|
||||
!lws_libuv_init_fd_table(context) &&
|
||||
!lws_libevent_init_fd_table(context)) {
|
||||
/* otherwise libev handled it instead */
|
||||
|
||||
while (n--) {
|
||||
if (pipe(pt->dummy_pipe_fds)) {
|
||||
lwsl_err("Unable to create pipe\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* use the read end of pipe as first item */
|
||||
pt->fds[0].fd = pt->dummy_pipe_fds[0];
|
||||
pt->fds[0].events = LWS_POLLIN;
|
||||
pt->fds[0].revents = 0;
|
||||
pt->fds_count = 1;
|
||||
pt++;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef LWS_WITH_PLUGINS
|
||||
if (info->plugin_dirs)
|
||||
lws_plat_plugins_init(context, info->plugin_dirs);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#endif
|
||||
#include "private-libwebsockets.h"
|
||||
|
||||
unsigned long long
|
||||
time_in_microseconds()
|
||||
{
|
||||
#ifndef DELTA_EPOCH_IN_MICROSECS
|
||||
#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL
|
||||
#endif
|
||||
FILETIME filetime;
|
||||
ULARGE_INTEGER datetime;
|
||||
|
||||
|
@ -28,30 +33,33 @@ time_in_microseconds()
|
|||
time_t time(time_t *t)
|
||||
{
|
||||
time_t ret = time_in_microseconds() / 1000000;
|
||||
*t = ret;
|
||||
|
||||
if(t != NULL)
|
||||
*t = ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* file descriptor hash management */
|
||||
|
||||
struct libwebsocket *
|
||||
wsi_from_fd(struct libwebsocket_context *context, int fd)
|
||||
struct lws *
|
||||
wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd)
|
||||
{
|
||||
int h = LWS_FD_HASH(fd);
|
||||
int n = 0;
|
||||
|
||||
for (n = 0; n < context->fd_hashtable[h].length; n++)
|
||||
if (context->fd_hashtable[h].wsi[n]->sock == fd)
|
||||
if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd)
|
||||
return context->fd_hashtable[h].wsi[n];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
insert_wsi(struct libwebsocket_context *context, struct libwebsocket *wsi)
|
||||
insert_wsi(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
int h = LWS_FD_HASH(wsi->sock);
|
||||
int h = LWS_FD_HASH(wsi->desc.sockfd);
|
||||
|
||||
if (context->fd_hashtable[h].length == (getdtablesize() - 1)) {
|
||||
lwsl_err("hash table overflow\n");
|
||||
|
@ -64,16 +72,16 @@ insert_wsi(struct libwebsocket_context *context, struct libwebsocket *wsi)
|
|||
}
|
||||
|
||||
int
|
||||
delete_from_fd(struct libwebsocket_context *context, int fd)
|
||||
delete_from_fd(struct lws_context *context, lws_sockfd_type fd)
|
||||
{
|
||||
int h = LWS_FD_HASH(fd);
|
||||
int n = 0;
|
||||
|
||||
for (n = 0; n < context->fd_hashtable[h].length; n++)
|
||||
if (context->fd_hashtable[h].wsi[n]->sock == fd) {
|
||||
if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd) {
|
||||
while (n < context->fd_hashtable[h].length) {
|
||||
context->fd_hashtable[h].wsi[n] =
|
||||
context->fd_hashtable[h].wsi[n + 1];
|
||||
context->fd_hashtable[h].wsi[n + 1];
|
||||
n++;
|
||||
}
|
||||
context->fd_hashtable[h].length--;
|
||||
|
@ -86,8 +94,8 @@ delete_from_fd(struct libwebsocket_context *context, int fd)
|
|||
return 1;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int libwebsockets_get_random(struct libwebsocket_context *context,
|
||||
void *buf, int len)
|
||||
LWS_VISIBLE int lws_get_random(struct lws_context *context,
|
||||
void *buf, int len)
|
||||
{
|
||||
int n;
|
||||
char *p = (char *)buf;
|
||||
|
@ -98,17 +106,21 @@ LWS_VISIBLE int libwebsockets_get_random(struct libwebsocket_context *context,
|
|||
return n;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int lws_send_pipe_choked(struct libwebsocket *wsi)
|
||||
LWS_VISIBLE int lws_send_pipe_choked(struct lws *wsi)
|
||||
{
|
||||
return wsi->sock_send_blocking;
|
||||
/* treat the fact we got a truncated send pending as if we're choked */
|
||||
if (wsi->trunc_len)
|
||||
return 1;
|
||||
|
||||
return (int)wsi->sock_send_blocking;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int lws_poll_listen_fd(struct libwebsocket_pollfd *fd)
|
||||
LWS_VISIBLE int lws_poll_listen_fd(struct lws_pollfd *fd)
|
||||
{
|
||||
fd_set readfds;
|
||||
struct timeval tv = { 0, 0 };
|
||||
|
||||
assert(fd->events == LWS_POLLIN);
|
||||
assert((fd->events & LWS_POLLIN) == LWS_POLLIN);
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(fd->fd, &readfds);
|
||||
|
@ -116,17 +128,23 @@ LWS_VISIBLE int lws_poll_listen_fd(struct libwebsocket_pollfd *fd)
|
|||
return select(fd->fd + 1, &readfds, NULL, NULL, &tv);
|
||||
}
|
||||
|
||||
/**
|
||||
* libwebsocket_cancel_service() - Cancel servicing of pending websocket activity
|
||||
* @context: Websocket context
|
||||
*
|
||||
* This function let a call to libwebsocket_service() waiting for a timeout
|
||||
* immediately return.
|
||||
*/
|
||||
LWS_VISIBLE void
|
||||
libwebsocket_cancel_service(struct libwebsocket_context *context)
|
||||
lws_cancel_service(struct lws_context *context)
|
||||
{
|
||||
WSASetEvent(context->events[0]);
|
||||
struct lws_context_per_thread *pt = &context->pt[0];
|
||||
int n = context->count_threads;
|
||||
|
||||
while (n--) {
|
||||
WSASetEvent(pt->events[0]);
|
||||
pt++;
|
||||
}
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_cancel_service_pt(struct lws *wsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
WSASetEvent(pt->events[0]);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
|
||||
|
@ -134,108 +152,189 @@ LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
|
|||
lwsl_emit_stderr(level, line);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_service(struct libwebsocket_context *context, int timeout_ms)
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
|
||||
{
|
||||
int n;
|
||||
int i;
|
||||
DWORD ev;
|
||||
struct lws_context_per_thread *pt;
|
||||
WSANETWORKEVENTS networkevents;
|
||||
struct libwebsocket_pollfd *pfd;
|
||||
struct lws_pollfd *pfd;
|
||||
struct lws *wsi;
|
||||
unsigned int i;
|
||||
DWORD ev;
|
||||
int n, m;
|
||||
|
||||
/* stay dead once we are dead */
|
||||
|
||||
if (context == NULL)
|
||||
return 1;
|
||||
|
||||
context->service_tid = context->protocols[0].callback(context, NULL,
|
||||
LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
|
||||
pt = &context->pt[tsi];
|
||||
|
||||
for (i = 0; i < context->fds_count; ++i) {
|
||||
pfd = &context->fds[i];
|
||||
if (pfd->fd == context->listen_service_fd)
|
||||
if (!context->service_tid_detected) {
|
||||
struct lws _lws;
|
||||
|
||||
memset(&_lws, 0, sizeof(_lws));
|
||||
_lws.context = context;
|
||||
|
||||
context->service_tid_detected = context->vhost_list->
|
||||
protocols[0].callback(&_lws, LWS_CALLBACK_GET_THREAD_ID,
|
||||
NULL, NULL, 0);
|
||||
}
|
||||
context->service_tid = context->service_tid_detected;
|
||||
|
||||
if (timeout_ms < 0)
|
||||
{
|
||||
if (lws_service_flag_pending(context, tsi)) {
|
||||
/* any socket with events to service? */
|
||||
for (n = 0; n < (int)pt->fds_count; n++) {
|
||||
if (!pt->fds[n].revents)
|
||||
continue;
|
||||
|
||||
m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
|
||||
if (m < 0)
|
||||
return -1;
|
||||
/* if something closed, retry this slot */
|
||||
if (m)
|
||||
n--;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < pt->fds_count; ++i) {
|
||||
pfd = &pt->fds[i];
|
||||
|
||||
if (!(pfd->events & LWS_POLLOUT))
|
||||
continue;
|
||||
|
||||
if (pfd->events & LWS_POLLOUT) {
|
||||
if (wsi_from_fd(context,pfd->fd)->sock_send_blocking)
|
||||
continue;
|
||||
pfd->revents = LWS_POLLOUT;
|
||||
n = libwebsocket_service_fd(context, pfd);
|
||||
if (n < 0)
|
||||
return n;
|
||||
wsi = wsi_from_fd(context, pfd->fd);
|
||||
if (wsi->listener)
|
||||
continue;
|
||||
if (!wsi || wsi->sock_send_blocking)
|
||||
continue;
|
||||
pfd->revents = LWS_POLLOUT;
|
||||
n = lws_service_fd(context, pfd);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
/* if something closed, retry this slot */
|
||||
if (n)
|
||||
i--;
|
||||
|
||||
if (wsi->trunc_len)
|
||||
WSASetEvent(pt->events[0]);
|
||||
}
|
||||
|
||||
/*
|
||||
* is there anybody with pending stuff that needs service forcing?
|
||||
*/
|
||||
if (!lws_service_adjust_timeout(context, 1, tsi)) {
|
||||
/* -1 timeout means just do forced service */
|
||||
_lws_plat_service_tsi(context, -1, pt->tid);
|
||||
/* still somebody left who wants forced service? */
|
||||
if (!lws_service_adjust_timeout(context, 1, pt->tid))
|
||||
/* yes... come back again quickly */
|
||||
timeout_ms = 0;
|
||||
}
|
||||
|
||||
ev = WSAWaitForMultipleEvents( 1, pt->events , FALSE, timeout_ms, FALSE);
|
||||
if (ev == WSA_WAIT_EVENT_0) {
|
||||
unsigned int eIdx;
|
||||
|
||||
WSAResetEvent(pt->events[0]);
|
||||
|
||||
for (eIdx = 0; eIdx < pt->fds_count; ++eIdx) {
|
||||
if (WSAEnumNetworkEvents(pt->fds[eIdx].fd, 0, &networkevents) == SOCKET_ERROR) {
|
||||
lwsl_err("WSAEnumNetworkEvents() failed with error %d\n", LWS_ERRNO);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pfd = &pt->fds[eIdx];
|
||||
pfd->revents = (short)networkevents.lNetworkEvents;
|
||||
|
||||
if ((networkevents.lNetworkEvents & FD_CONNECT) &&
|
||||
networkevents.iErrorCode[FD_CONNECT_BIT] &&
|
||||
networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EALREADY &&
|
||||
networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EINPROGRESS &&
|
||||
networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EWOULDBLOCK &&
|
||||
networkevents.iErrorCode[FD_CONNECT_BIT] != WSAEINVAL) {
|
||||
lwsl_debug("Unable to connect errno=%d\n",
|
||||
networkevents.iErrorCode[FD_CONNECT_BIT]);
|
||||
pfd->revents = LWS_POLLHUP;
|
||||
} else
|
||||
pfd->revents = (short)networkevents.lNetworkEvents;
|
||||
|
||||
if (pfd->revents & LWS_POLLOUT) {
|
||||
wsi = wsi_from_fd(context, pfd->fd);
|
||||
if (wsi)
|
||||
wsi->sock_send_blocking = 0;
|
||||
}
|
||||
/* if something closed, retry this slot */
|
||||
if (pfd->revents & LWS_POLLHUP)
|
||||
--eIdx;
|
||||
|
||||
if( pfd->revents != 0 ) {
|
||||
lws_service_fd_tsi(context, pfd, tsi);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ev = WSAWaitForMultipleEvents(context->fds_count + 1,
|
||||
context->events, FALSE, timeout_ms, FALSE);
|
||||
context->service_tid = 0;
|
||||
|
||||
if (ev == WSA_WAIT_TIMEOUT) {
|
||||
libwebsocket_service_fd(context, NULL);
|
||||
return 0;
|
||||
lws_service_fd(context, NULL);
|
||||
}
|
||||
|
||||
if (ev == WSA_WAIT_EVENT_0) {
|
||||
WSAResetEvent(context->events[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ev < WSA_WAIT_EVENT_0 || ev > WSA_WAIT_EVENT_0 + context->fds_count)
|
||||
return -1;
|
||||
|
||||
pfd = &context->fds[ev - WSA_WAIT_EVENT_0 - 1];
|
||||
|
||||
if (WSAEnumNetworkEvents(pfd->fd,
|
||||
context->events[ev - WSA_WAIT_EVENT_0],
|
||||
&networkevents) == SOCKET_ERROR) {
|
||||
lwsl_err("WSAEnumNetworkEvents() failed with error %d\n",
|
||||
LWS_ERRNO);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pfd->revents = networkevents.lNetworkEvents;
|
||||
|
||||
if (pfd->revents & LWS_POLLOUT)
|
||||
wsi_from_fd(context,pfd->fd)->sock_send_blocking = FALSE;
|
||||
|
||||
return libwebsocket_service_fd(context, pfd);
|
||||
return 0;;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_set_socket_options(struct libwebsocket_context *context, int fd)
|
||||
lws_plat_service(struct lws_context *context, int timeout_ms)
|
||||
{
|
||||
return _lws_plat_service_tsi(context, timeout_ms, 0);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd)
|
||||
{
|
||||
int optval = 1;
|
||||
int optlen = sizeof(optval);
|
||||
u_long optl = 1;
|
||||
DWORD dwBytesRet;
|
||||
struct tcp_keepalive alive;
|
||||
int protonbr;
|
||||
#ifndef _WIN32_WCE
|
||||
struct protoent *tcp_proto;
|
||||
|
||||
if (context->ka_time) {
|
||||
#endif
|
||||
|
||||
if (vhost->ka_time) {
|
||||
/* enable keepalive on this socket */
|
||||
optval = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
|
||||
(const char *)&optval, optlen) < 0)
|
||||
(const char *)&optval, optlen) < 0)
|
||||
return 1;
|
||||
|
||||
alive.onoff = TRUE;
|
||||
alive.keepalivetime = context->ka_time;
|
||||
alive.keepaliveinterval = context->ka_interval;
|
||||
alive.keepalivetime = vhost->ka_time;
|
||||
alive.keepaliveinterval = vhost->ka_interval;
|
||||
|
||||
if (WSAIoctl(fd, SIO_KEEPALIVE_VALS, &alive, sizeof(alive),
|
||||
NULL, 0, &dwBytesRet, NULL, NULL))
|
||||
if (WSAIoctl(fd, SIO_KEEPALIVE_VALS, &alive, sizeof(alive),
|
||||
NULL, 0, &dwBytesRet, NULL, NULL))
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Disable Nagle */
|
||||
optval = 1;
|
||||
#ifndef _WIN32_WCE
|
||||
tcp_proto = getprotobyname("TCP");
|
||||
if (!tcp_proto) {
|
||||
lwsl_err("getprotobyname() failed with error %d\n", LWS_ERRNO);
|
||||
return 1;
|
||||
}
|
||||
protonbr = tcp_proto->p_proto;
|
||||
#else
|
||||
protonbr = 6;
|
||||
#endif
|
||||
|
||||
setsockopt(fd, tcp_proto->p_proto, TCP_NODELAY, (const char *)&optval, optlen);
|
||||
setsockopt(fd, protonbr, TCP_NODELAY, (const char *)&optval, optlen);
|
||||
|
||||
/* We are nonblocking... */
|
||||
ioctlsocket(fd, FIONBIO, &optl);
|
||||
|
@ -248,40 +347,6 @@ lws_plat_drop_app_privileges(struct lws_context_creation_info *info)
|
|||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_init_lookup(struct libwebsocket_context *context)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FD_HASHTABLE_MODULUS; i++) {
|
||||
context->fd_hashtable[i].wsi = lws_zalloc(sizeof(struct libwebsocket*) * context->max_fds);
|
||||
|
||||
if (!context->fd_hashtable[i].wsi) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_init_fd_tables(struct libwebsocket_context *context)
|
||||
{
|
||||
context->events = lws_malloc(sizeof(WSAEVENT) * (context->max_fds + 1));
|
||||
if (context->events == NULL) {
|
||||
lwsl_err("Unable to allocate events array for %d connections\n",
|
||||
context->max_fds);
|
||||
return 1;
|
||||
}
|
||||
|
||||
context->fds_count = 0;
|
||||
context->events[0] = WSACreateEvent();
|
||||
|
||||
context->fd_random = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_context_early_init(void)
|
||||
{
|
||||
|
@ -305,16 +370,22 @@ lws_plat_context_early_init(void)
|
|||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_context_early_destroy(struct libwebsocket_context *context)
|
||||
lws_plat_context_early_destroy(struct lws_context *context)
|
||||
{
|
||||
if (context->events) {
|
||||
WSACloseEvent(context->events[0]);
|
||||
lws_free(context->events);
|
||||
struct lws_context_per_thread *pt = &context->pt[0];
|
||||
int n = context->count_threads;
|
||||
|
||||
while (n--) {
|
||||
if (pt->events) {
|
||||
WSACloseEvent(pt->events[0]);
|
||||
lws_free(pt->events);
|
||||
}
|
||||
pt++;
|
||||
}
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_context_late_destroy(struct libwebsocket_context *context)
|
||||
lws_plat_context_late_destroy(struct lws_context *context)
|
||||
{
|
||||
int n;
|
||||
|
||||
|
@ -326,10 +397,20 @@ lws_plat_context_late_destroy(struct libwebsocket_context *context)
|
|||
WSACleanup();
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
interface_to_sa(struct libwebsocket_context *context,
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
lws_interface_to_sa(int ipv6,
|
||||
const char *ifname, struct sockaddr_in *addr, size_t addrlen)
|
||||
{
|
||||
#ifdef LWS_USE_IPV6
|
||||
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
|
||||
|
||||
if (ipv6) {
|
||||
if (lws_plat_inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
long long address = inet_addr(ifname);
|
||||
|
||||
if (address == INADDR_NONE) {
|
||||
|
@ -341,45 +422,69 @@ interface_to_sa(struct libwebsocket_context *context,
|
|||
if (address == INADDR_NONE)
|
||||
return -1;
|
||||
|
||||
addr->sin_addr.s_addr = address;
|
||||
addr->sin_addr.s_addr = (lws_intptr_t)address;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_insert_socket_into_fds(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi)
|
||||
lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
context->fds[context->fds_count++].revents = 0;
|
||||
context->events[context->fds_count] = WSACreateEvent();
|
||||
WSAEventSelect(wsi->sock, context->events[context->fds_count], LWS_POLLIN);
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
|
||||
pt->fds[pt->fds_count++].revents = 0;
|
||||
pt->events[pt->fds_count] = pt->events[0];
|
||||
WSAEventSelect(wsi->desc.sockfd, pt->events[0],
|
||||
LWS_POLLIN | LWS_POLLHUP | FD_CONNECT);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_delete_socket_from_fds(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, int m)
|
||||
lws_plat_delete_socket_from_fds(struct lws_context *context,
|
||||
struct lws *wsi, int m)
|
||||
{
|
||||
WSACloseEvent(context->events[m + 1]);
|
||||
context->events[m + 1] = context->events[context->fds_count + 1];
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
|
||||
pt->events[m + 1] = pt->events[pt->fds_count--];
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_plat_service_periodic(struct libwebsocket_context *context)
|
||||
lws_plat_service_periodic(struct lws_context *context)
|
||||
{
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_change_pollfd(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, struct libwebsocket_pollfd *pfd)
|
||||
lws_plat_check_connection_error(struct lws *wsi)
|
||||
{
|
||||
long networkevents = LWS_POLLOUT | LWS_POLLHUP;
|
||||
|
||||
int optVal;
|
||||
int optLen = sizeof(int);
|
||||
|
||||
if (getsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_ERROR,
|
||||
(char*)&optVal, &optLen) != SOCKET_ERROR && optVal &&
|
||||
optVal != LWS_EALREADY && optVal != LWS_EINPROGRESS &&
|
||||
optVal != LWS_EWOULDBLOCK && optVal != WSAEINVAL) {
|
||||
lwsl_debug("Connect failed SO_ERROR=%d\n", optVal);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_change_pollfd(struct lws_context *context,
|
||||
struct lws *wsi, struct lws_pollfd *pfd)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
long networkevents = LWS_POLLHUP | FD_CONNECT;
|
||||
|
||||
if ((pfd->events & LWS_POLLIN))
|
||||
networkevents |= LWS_POLLIN;
|
||||
|
||||
if (WSAEventSelect(wsi->sock,
|
||||
context->events[wsi->position_in_fds_table + 1],
|
||||
networkevents) != SOCKET_ERROR)
|
||||
if ((pfd->events & LWS_POLLOUT))
|
||||
networkevents |= LWS_POLLOUT;
|
||||
|
||||
if (WSAEventSelect(wsi->desc.sockfd,
|
||||
pt->events[0],
|
||||
networkevents) != SOCKET_ERROR)
|
||||
return 0;
|
||||
|
||||
lwsl_err("WSAEventSelect() failed with error %d\n", LWS_ERRNO);
|
||||
|
@ -387,31 +492,14 @@ lws_plat_change_pollfd(struct libwebsocket_context *context,
|
|||
return 1;
|
||||
}
|
||||
|
||||
LWS_VISIBLE HANDLE
|
||||
lws_plat_open_file(const char* filename, unsigned long* filelen)
|
||||
{
|
||||
HANDLE ret;
|
||||
WCHAR buffer[MAX_PATH];
|
||||
|
||||
MultiByteToWideChar(CP_UTF8, 0, filename, -1, buffer,
|
||||
sizeof(buffer) / sizeof(buffer[0]));
|
||||
ret = CreateFileW(buffer, GENERIC_READ, FILE_SHARE_READ,
|
||||
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
|
||||
if (ret != LWS_INVALID_FILE)
|
||||
*filelen = GetFileSize(ret, NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
LWS_VISIBLE const char *
|
||||
lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
|
||||
{
|
||||
{
|
||||
WCHAR *buffer;
|
||||
DWORD bufferlen = cnt;
|
||||
BOOL ok = FALSE;
|
||||
|
||||
buffer = lws_malloc(bufferlen);
|
||||
buffer = lws_malloc(bufferlen * 2);
|
||||
if (!buffer) {
|
||||
lwsl_err("Out of memory\n");
|
||||
return NULL;
|
||||
|
@ -449,3 +537,209 @@ lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
|
|||
lws_free(buffer);
|
||||
return ok ? dst : NULL;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_inet_pton(int af, const char *src, void *dst)
|
||||
{
|
||||
WCHAR *buffer;
|
||||
DWORD bufferlen = strlen(src) + 1;
|
||||
BOOL ok = FALSE;
|
||||
|
||||
buffer = lws_malloc(bufferlen * 2);
|
||||
if (!buffer) {
|
||||
lwsl_err("Out of memory\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (MultiByteToWideChar(CP_ACP, 0, src, bufferlen, buffer, bufferlen) <= 0) {
|
||||
lwsl_err("Failed to convert multi byte to wide char\n");
|
||||
lws_free(buffer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (af == AF_INET) {
|
||||
struct sockaddr_in dstaddr;
|
||||
int dstaddrlen = sizeof(dstaddr);
|
||||
bzero(&dstaddr, sizeof(dstaddr));
|
||||
dstaddr.sin_family = AF_INET;
|
||||
|
||||
if (!WSAStringToAddressW(buffer, af, 0, (struct sockaddr *) &dstaddr, &dstaddrlen)) {
|
||||
ok = TRUE;
|
||||
memcpy(dst, &dstaddr.sin_addr, sizeof(dstaddr.sin_addr));
|
||||
}
|
||||
#ifdef LWS_USE_IPV6
|
||||
} else if (af == AF_INET6) {
|
||||
struct sockaddr_in6 dstaddr;
|
||||
int dstaddrlen = sizeof(dstaddr);
|
||||
bzero(&dstaddr, sizeof(dstaddr));
|
||||
dstaddr.sin6_family = AF_INET6;
|
||||
|
||||
if (!WSAStringToAddressW(buffer, af, 0, (struct sockaddr *) &dstaddr, &dstaddrlen)) {
|
||||
ok = TRUE;
|
||||
memcpy(dst, &dstaddr.sin6_addr, sizeof(dstaddr.sin6_addr));
|
||||
}
|
||||
#endif
|
||||
} else
|
||||
lwsl_err("Unsupported type\n");
|
||||
|
||||
if (!ok) {
|
||||
int rv = WSAGetLastError();
|
||||
lwsl_err("WSAAddressToString() : %d\n", rv);
|
||||
}
|
||||
|
||||
lws_free(buffer);
|
||||
return ok ? 1 : -1;
|
||||
}
|
||||
|
||||
LWS_VISIBLE lws_fop_fd_t
|
||||
_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
|
||||
const char *vpath, lws_fop_flags_t *flags)
|
||||
{
|
||||
HANDLE ret;
|
||||
WCHAR buf[MAX_PATH];
|
||||
lws_fop_fd_t fop_fd;
|
||||
LARGE_INTEGER llFileSize = {0};
|
||||
|
||||
MultiByteToWideChar(CP_UTF8, 0, filename, -1, buf, ARRAY_SIZE(buf));
|
||||
if (((*flags) & 7) == _O_RDONLY) {
|
||||
ret = CreateFileW(buf, GENERIC_READ, FILE_SHARE_READ,
|
||||
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
} else {
|
||||
ret = CreateFileW(buf, GENERIC_WRITE, 0, NULL,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
}
|
||||
|
||||
if (ret == LWS_INVALID_FILE)
|
||||
goto bail;
|
||||
|
||||
fop_fd = malloc(sizeof(*fop_fd));
|
||||
if (!fop_fd)
|
||||
goto bail;
|
||||
|
||||
fop_fd->fops = fops;
|
||||
fop_fd->fd = ret;
|
||||
fop_fd->filesystem_priv = NULL; /* we don't use it */
|
||||
fop_fd->flags = *flags;
|
||||
fop_fd->len = GetFileSize(ret, NULL);
|
||||
if(GetFileSizeEx(ret, &llFileSize))
|
||||
fop_fd->len = llFileSize.QuadPart;
|
||||
|
||||
fop_fd->pos = 0;
|
||||
|
||||
return fop_fd;
|
||||
|
||||
bail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
_lws_plat_file_close(lws_fop_fd_t *fop_fd)
|
||||
{
|
||||
HANDLE fd = (*fop_fd)->fd;
|
||||
|
||||
free(*fop_fd);
|
||||
*fop_fd = NULL;
|
||||
|
||||
CloseHandle((HANDLE)fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE lws_fileofs_t
|
||||
_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
|
||||
{
|
||||
LARGE_INTEGER l;
|
||||
|
||||
l.QuadPart = offset;
|
||||
return SetFilePointerEx((HANDLE)fop_fd->fd, l, NULL, FILE_CURRENT);
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
|
||||
uint8_t *buf, lws_filepos_t len)
|
||||
{
|
||||
DWORD _amount;
|
||||
|
||||
if (!ReadFile((HANDLE)fop_fd->fd, buf, (DWORD)len, &_amount, NULL)) {
|
||||
*amount = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
fop_fd->pos += _amount;
|
||||
*amount = (unsigned long)_amount;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
|
||||
uint8_t* buf, lws_filepos_t len)
|
||||
{
|
||||
DWORD _amount;
|
||||
|
||||
if (!WriteFile((HANDLE)fop_fd->fd, buf, (DWORD)len, &_amount, NULL)) {
|
||||
*amount = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
fop_fd->pos += _amount;
|
||||
*amount = (unsigned long)_amount;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_plat_init(struct lws_context *context,
|
||||
struct lws_context_creation_info *info)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[0];
|
||||
int i, n = context->count_threads;
|
||||
|
||||
for (i = 0; i < FD_HASHTABLE_MODULUS; i++) {
|
||||
context->fd_hashtable[i].wsi =
|
||||
lws_zalloc(sizeof(struct lws*) * context->max_fds);
|
||||
|
||||
if (!context->fd_hashtable[i].wsi)
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (n--) {
|
||||
pt->events = lws_malloc(sizeof(WSAEVENT) *
|
||||
(context->fd_limit_per_thread + 1));
|
||||
if (pt->events == NULL) {
|
||||
lwsl_err("Unable to allocate events array for %d connections\n",
|
||||
context->fd_limit_per_thread + 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pt->fds_count = 0;
|
||||
pt->events[0] = WSACreateEvent();
|
||||
|
||||
pt++;
|
||||
}
|
||||
|
||||
context->fd_random = 0;
|
||||
|
||||
#ifdef LWS_WITH_PLUGINS
|
||||
if (info->plugin_dirs)
|
||||
lws_plat_plugins_init(context, info->plugin_dirs);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int kill(int pid, int sig)
|
||||
{
|
||||
lwsl_err("Sorry Windows doesn't support kill().");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int fork(void)
|
||||
{
|
||||
lwsl_err("Sorry Windows doesn't support fork().");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ static struct huf huf_literal[] = {
|
|||
/* 0x09 */ { 0xffffea, 24 },
|
||||
/* 0x0a */ { 0x3ffffffc, 30 },
|
||||
/* 0x0b */ { 0xfffffe9, 28 },
|
||||
|
||||
|
||||
/* 0x0c */ { 0xfffffea, 28 },
|
||||
/* 0x0d */ { 0x3ffffffd, 30 },
|
||||
/* 0x0e */ { 0xfffffeb, 28 },
|
||||
|
@ -85,7 +85,7 @@ static struct huf huf_literal[] = {
|
|||
/* 0x39 */ { 0x1f, 6 },
|
||||
/* 0x3a */ { 0x5c, 7 },
|
||||
/* 0x3b */ { 0xfb, 8 },
|
||||
|
||||
|
||||
/* 0x3c */ { 0x7ffc, 15 },
|
||||
/* 0x3d */ { 0x20, 6 },
|
||||
/* 0x3e */ { 0xffb, 12 },
|
||||
|
@ -134,7 +134,7 @@ static struct huf huf_literal[] = {
|
|||
/* 0x69 */ { 0x6, 5 },
|
||||
/* 0x6a */ { 0x74, 7 },
|
||||
/* 0x6b */ { 0x75, 7 },
|
||||
|
||||
|
||||
|
||||
/* 0x6c */ { 0x28, 6 },
|
||||
/* 0x6d */ { 0x29, 6 },
|
||||
|
@ -184,7 +184,7 @@ static struct huf huf_literal[] = {
|
|||
/* 0x99 */ { 0x1fffdc, 21 },
|
||||
/* 0x9a */ { 0x3fffd8, 22 },
|
||||
/* 0x9b */ { 0x7fffe5, 23 },
|
||||
|
||||
|
||||
/* 0x9c */ { 0x3fffd9, 22 },
|
||||
/* 0x9d */ { 0x7fffe6, 23 },
|
||||
/* 0x9e */ { 0x7fffe7, 23 },
|
||||
|
@ -233,7 +233,7 @@ static struct huf huf_literal[] = {
|
|||
/* 0xc9 */ { 0x3ffffe3, 26 },
|
||||
/* 0xca */ { 0x3ffffe4, 26 },
|
||||
/* 0xcb */ { 0x7ffffde, 27 },
|
||||
|
||||
|
||||
/* 0xcc */ { 0x7ffffdf, 27 },
|
||||
/* 0xcd */ { 0x3ffffe5, 26 },
|
||||
/* 0xce */ { 0xfffff1, 24 },
|
||||
|
@ -282,7 +282,7 @@ static struct huf huf_literal[] = {
|
|||
/* 0xf9 */ { 0xffffffe, 28 },
|
||||
/* 0xfa */ { 0x7ffffec, 27 },
|
||||
/* 0xfb */ { 0x7ffffed, 27 },
|
||||
|
||||
|
||||
/* 0xfc */ { 0x7ffffee, 27 },
|
||||
/* 0xfd */ { 0x7ffffef, 27 },
|
||||
/* 0xfe */ { 0x7fffff0, 27 },
|
||||
|
@ -338,7 +338,7 @@ int main(void)
|
|||
int pos = 0;
|
||||
int biggest = 0;
|
||||
int fails = 0;
|
||||
|
||||
|
||||
m = 0;
|
||||
while (m < ARRAY_SIZE(state)) {
|
||||
for (j = 0; j < PARALLEL; j++) {
|
||||
|
@ -391,14 +391,14 @@ again:
|
|||
|
||||
if (state[n].state[0]) /* nonterminal */
|
||||
pos += 2;
|
||||
|
||||
|
||||
walk ++;
|
||||
}
|
||||
|
||||
|
||||
fprintf(stdout, "static unsigned char lextable[] = {\n");
|
||||
|
||||
#define TERMINAL_MASK 0x8000
|
||||
|
||||
|
||||
walk = 0;
|
||||
pos = 0;
|
||||
q = 0;
|
||||
|
@ -435,9 +435,9 @@ again:
|
|||
walk++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
j = (state[saw].real_pos - q) >> 1;
|
||||
|
||||
|
||||
if (j > biggest)
|
||||
biggest = j;
|
||||
|
||||
|
@ -447,7 +447,7 @@ again:
|
|||
state[n].real_pos, state[saw].real_pos);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
fprintf(stdout, " /* %d */ 0x%02X "
|
||||
"/* (to 0x%04X state %3d) */,\n",
|
||||
m,
|
||||
|
@ -469,7 +469,7 @@ again:
|
|||
fprintf(stdout, "0x%02x, ", terms[n]);
|
||||
}
|
||||
fprintf(stdout, "\n};\n");
|
||||
|
||||
|
||||
/*
|
||||
* Try to parse every legal input string
|
||||
*/
|
||||
|
@ -494,7 +494,7 @@ again:
|
|||
y = walk & 0x7fff;
|
||||
if (y == 0 && m == 29) {
|
||||
y |= 0x100;
|
||||
fprintf(stdout,
|
||||
fprintf(stdout,
|
||||
"\n/* state that points to "
|
||||
"0x100 for disambiguation with "
|
||||
"0x0 */\n"
|
||||
|
|
|
@ -22,11 +22,11 @@
|
|||
* b7 = 0 = 1-byte seq
|
||||
* 0x08 = fail
|
||||
* 2-byte seq
|
||||
* 0x00 - 0x07, then terminal as given in 2nd byte
|
||||
* 0x00 - 0x07, then terminal as given in 2nd byte
|
||||
3-byte seq
|
||||
* no match: go fwd 3 byte, match: jump fwd by amt in +1/+2 bytes
|
||||
* no match: go fwd 3 byte, match: jump fwd by amt in +1/+2 bytes
|
||||
* = 1 = 1-byte seq
|
||||
* no match: die, match go fwd 1 byte
|
||||
* no match: die, match go fwd 1 byte
|
||||
*/
|
||||
|
||||
unsigned char lextable[] = {
|
||||
|
@ -51,7 +51,6 @@ int next = 1;
|
|||
|
||||
int lextable_decode(int pos, char c)
|
||||
{
|
||||
|
||||
while (1) {
|
||||
if (lextable[pos] & (1 << 7)) { /* 1-byte, fail on mismatch */
|
||||
if ((lextable[pos] & 0x7f) != c)
|
||||
|
|
705
lib/output.c
705
lib/output.c
File diff suppressed because it is too large
Load diff
1170
lib/parsers.c
1170
lib/parsers.c
File diff suppressed because it is too large
Load diff
635
lib/pollfd.c
635
lib/pollfd.c
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010-2014 Andy Green <andy@warmcat.com>
|
||||
* Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
|
@ -22,143 +22,79 @@
|
|||
#include "private-libwebsockets.h"
|
||||
|
||||
int
|
||||
insert_wsi_socket_into_fds(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi)
|
||||
_lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa)
|
||||
{
|
||||
struct libwebsocket_pollargs pa = { wsi->sock, LWS_POLLIN, 0 };
|
||||
struct lws_context_per_thread *pt;
|
||||
struct lws_context *context;
|
||||
int ret = 0, pa_events = 1;
|
||||
struct lws_pollfd *pfd;
|
||||
int sampled_tid, tid;
|
||||
|
||||
if (context->fds_count >= context->max_fds) {
|
||||
lwsl_err("Too many fds (%d)\n", context->max_fds);
|
||||
return 1;
|
||||
if (!wsi || wsi->position_in_fds_table < 0)
|
||||
return 0;
|
||||
|
||||
if (wsi->handling_pollout && !_and && _or == LWS_POLLOUT) {
|
||||
/*
|
||||
* Happening alongside service thread handling POLLOUT.
|
||||
* The danger is when he is finished, he will disable POLLOUT,
|
||||
* countermanding what we changed here.
|
||||
*
|
||||
* Instead of changing the fds, inform the service thread
|
||||
* what happened, and ask it to leave POLLOUT active on exit
|
||||
*/
|
||||
wsi->leave_pollout_active = 1;
|
||||
/*
|
||||
* by definition service thread is not in poll wait, so no need
|
||||
* to cancel service
|
||||
*/
|
||||
|
||||
lwsl_debug("%s: using leave_pollout_active\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
if (wsi->sock >= context->max_fds) {
|
||||
lwsl_err("Socket fd %d is too high (%d)\n",
|
||||
wsi->sock, context->max_fds);
|
||||
return 1;
|
||||
context = wsi->context;
|
||||
pt = &context->pt[(int)wsi->tsi];
|
||||
assert(wsi->position_in_fds_table >= 0 &&
|
||||
wsi->position_in_fds_table < pt->fds_count);
|
||||
|
||||
pfd = &pt->fds[wsi->position_in_fds_table];
|
||||
pa->fd = wsi->desc.sockfd;
|
||||
pa->prev_events = pfd->events;
|
||||
pa->events = pfd->events = (pfd->events & ~_and) | _or;
|
||||
|
||||
//lwsl_notice("%s: wsi %p, posin %d. from %d -> %d\n", __func__, wsi, wsi->position_in_fds_table, pa->prev_events, pa->events);
|
||||
|
||||
|
||||
if (wsi->http2_substream)
|
||||
return 0;
|
||||
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CHANGE_MODE_POLL_FD,
|
||||
wsi->user_space, (void *)pa, 0)) {
|
||||
ret = -1;
|
||||
goto bail;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(wsi);
|
||||
assert(wsi->sock >= 0);
|
||||
|
||||
lwsl_info("insert_wsi_socket_into_fds: wsi=%p, sock=%d, fds pos=%d\n",
|
||||
wsi, wsi->sock, context->fds_count);
|
||||
|
||||
if (context->protocols[0].callback(context, wsi,
|
||||
LWS_CALLBACK_LOCK_POLL, wsi->user_space, (void *) &pa, 0))
|
||||
return -1;
|
||||
|
||||
insert_wsi(context, wsi);
|
||||
wsi->position_in_fds_table = context->fds_count;
|
||||
context->fds[context->fds_count].fd = wsi->sock;
|
||||
context->fds[context->fds_count].events = LWS_POLLIN;
|
||||
|
||||
lws_plat_insert_socket_into_fds(context, wsi);
|
||||
|
||||
/* external POLL support via protocol 0 */
|
||||
if (context->protocols[0].callback(context, wsi,
|
||||
LWS_CALLBACK_ADD_POLL_FD, wsi->user_space, (void *) &pa, 0))
|
||||
return -1;
|
||||
|
||||
if (context->protocols[0].callback(context, wsi,
|
||||
LWS_CALLBACK_UNLOCK_POLL, wsi->user_space, (void *)&pa, 0))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
remove_wsi_socket_from_fds(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi)
|
||||
{
|
||||
int m;
|
||||
struct libwebsocket_pollargs pa = { wsi->sock, 0, 0 };
|
||||
|
||||
lws_libev_io(context, wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE);
|
||||
|
||||
--context->fds_count;
|
||||
|
||||
#ifndef _WIN32
|
||||
if (wsi->sock > context->max_fds) {
|
||||
lwsl_err("Socket fd %d too high (%d)\n",
|
||||
wsi->sock, context->max_fds);
|
||||
return 1;
|
||||
if (_and & LWS_POLLIN) {
|
||||
lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ);
|
||||
lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ);
|
||||
lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_READ);
|
||||
}
|
||||
#endif
|
||||
|
||||
lwsl_info("%s: wsi=%p, sock=%d, fds pos=%d\n", __func__,
|
||||
wsi, wsi->sock, wsi->position_in_fds_table);
|
||||
|
||||
if (context->protocols[0].callback(context, wsi,
|
||||
LWS_CALLBACK_LOCK_POLL, wsi->user_space, (void *)&pa, 0))
|
||||
return -1;
|
||||
|
||||
m = wsi->position_in_fds_table; /* replace the contents for this */
|
||||
|
||||
/* have the last guy take up the vacant slot */
|
||||
context->fds[m] = context->fds[context->fds_count];
|
||||
|
||||
lws_plat_delete_socket_from_fds(context, wsi, m);
|
||||
|
||||
/*
|
||||
* end guy's fds_lookup entry remains unchanged
|
||||
* (still same fd pointing to same wsi)
|
||||
*/
|
||||
/* end guy's "position in fds table" changed */
|
||||
wsi_from_fd(context,context->fds[context->fds_count].fd)->
|
||||
position_in_fds_table = m;
|
||||
/* deletion guy's lws_lookup entry needs nuking */
|
||||
delete_from_fd(context,wsi->sock);
|
||||
/* removed wsi has no position any more */
|
||||
wsi->position_in_fds_table = -1;
|
||||
|
||||
/* remove also from external POLL support via protocol 0 */
|
||||
if (wsi->sock) {
|
||||
if (context->protocols[0].callback(context, wsi,
|
||||
LWS_CALLBACK_DEL_POLL_FD, wsi->user_space,
|
||||
(void *) &pa, 0))
|
||||
return -1;
|
||||
if (_or & LWS_POLLIN) {
|
||||
lws_libev_io(wsi, LWS_EV_START | LWS_EV_READ);
|
||||
lws_libuv_io(wsi, LWS_EV_START | LWS_EV_READ);
|
||||
lws_libevent_io(wsi, LWS_EV_START | LWS_EV_READ);
|
||||
}
|
||||
if (_and & LWS_POLLOUT) {
|
||||
lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_WRITE);
|
||||
lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_WRITE);
|
||||
lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_WRITE);
|
||||
}
|
||||
if (_or & LWS_POLLOUT) {
|
||||
lws_libev_io(wsi, LWS_EV_START | LWS_EV_WRITE);
|
||||
lws_libuv_io(wsi, LWS_EV_START | LWS_EV_WRITE);
|
||||
lws_libevent_io(wsi, LWS_EV_START | LWS_EV_WRITE);
|
||||
}
|
||||
if (context->protocols[0].callback(context, wsi,
|
||||
LWS_CALLBACK_UNLOCK_POLL,
|
||||
wsi->user_space, (void *) &pa, 0))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
lws_change_pollfd(struct libwebsocket *wsi, int _and, int _or)
|
||||
{
|
||||
struct libwebsocket_context *context;
|
||||
int tid;
|
||||
int sampled_tid;
|
||||
struct libwebsocket_pollfd *pfd;
|
||||
struct libwebsocket_pollargs pa;
|
||||
|
||||
if (!wsi || !wsi->protocol || wsi->position_in_fds_table < 0)
|
||||
return 1;
|
||||
|
||||
context = wsi->protocol->owning_server;
|
||||
if (!context)
|
||||
return 1;
|
||||
|
||||
pfd = &context->fds[wsi->position_in_fds_table];
|
||||
pa.fd = wsi->sock;
|
||||
|
||||
if (context->protocols[0].callback(context, wsi,
|
||||
LWS_CALLBACK_LOCK_POLL, wsi->user_space, (void *) &pa, 0))
|
||||
return -1;
|
||||
|
||||
pa.prev_events = pfd->events;
|
||||
pa.events = pfd->events = (pfd->events & ~_and) | _or;
|
||||
|
||||
if (context->protocols[0].callback(context, wsi,
|
||||
LWS_CALLBACK_CHANGE_MODE_POLL_FD,
|
||||
wsi->user_space, (void *) &pa, 0))
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* if we changed something in this pollfd...
|
||||
|
@ -167,132 +103,457 @@ lws_change_pollfd(struct libwebsocket *wsi, int _and, int _or)
|
|||
* ... and the service thread is waiting ...
|
||||
* then cancel it to force a restart with our changed events
|
||||
*/
|
||||
if (pa.prev_events != pa.events) {
|
||||
|
||||
#if LWS_POSIX
|
||||
pa_events = pa->prev_events != pa->events;
|
||||
#endif
|
||||
|
||||
if (pa_events) {
|
||||
|
||||
if (lws_plat_change_pollfd(context, wsi, pfd)) {
|
||||
lwsl_info("%s failed\n", __func__);
|
||||
return 1;
|
||||
ret = -1;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
sampled_tid = context->service_tid;
|
||||
if (sampled_tid) {
|
||||
tid = context->protocols[0].callback(context, NULL,
|
||||
tid = wsi->vhost->protocols[0].callback(wsi,
|
||||
LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
|
||||
if (tid == -1)
|
||||
return -1;
|
||||
if (tid == -1) {
|
||||
ret = -1;
|
||||
goto bail;
|
||||
}
|
||||
if (tid != sampled_tid)
|
||||
libwebsocket_cancel_service(context);
|
||||
lws_cancel_service_pt(wsi);
|
||||
}
|
||||
}
|
||||
|
||||
if (context->protocols[0].callback(context, wsi,
|
||||
LWS_CALLBACK_UNLOCK_POLL, wsi->user_space, (void *) &pa, 0))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
bail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef LWS_NO_SERVER
|
||||
static void
|
||||
lws_accept_modulation(struct lws_context_per_thread *pt, int allow)
|
||||
{
|
||||
// multithread listen seems broken
|
||||
#if 0
|
||||
struct lws_vhost *vh = context->vhost_list;
|
||||
struct lws_pollargs pa1;
|
||||
|
||||
/**
|
||||
* libwebsocket_callback_on_writable() - Request a callback when this socket
|
||||
* becomes able to be written to without
|
||||
* blocking
|
||||
*
|
||||
* @context: libwebsockets context
|
||||
* @wsi: Websocket connection instance to get callback for
|
||||
*/
|
||||
while (vh) {
|
||||
if (allow)
|
||||
_lws_change_pollfd(pt->wsi_listening,
|
||||
0, LWS_POLLIN, &pa1);
|
||||
else
|
||||
_lws_change_pollfd(pt->wsi_listening,
|
||||
LWS_POLLIN, 0, &pa1);
|
||||
vh = vh->vhost_next;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
struct lws_pollargs pa = { wsi->desc.sockfd, LWS_POLLIN, 0 };
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
int ret = 0;
|
||||
|
||||
|
||||
lwsl_debug("%s: %p: tsi=%d, sock=%d, pos-in-fds=%d\n",
|
||||
__func__, wsi, wsi->tsi, wsi->desc.sockfd, pt->fds_count);
|
||||
|
||||
if ((unsigned int)pt->fds_count >= context->fd_limit_per_thread) {
|
||||
lwsl_err("Too many fds (%d vs %d)\n", context->max_fds,
|
||||
context->fd_limit_per_thread );
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) && !defined(LWS_WITH_ESP8266)
|
||||
if (wsi->desc.sockfd >= context->max_fds) {
|
||||
lwsl_err("Socket fd %d is too high (%d)\n",
|
||||
wsi->desc.sockfd, context->max_fds);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(wsi);
|
||||
assert(wsi->vhost);
|
||||
assert(lws_socket_is_valid(wsi->desc.sockfd));
|
||||
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL,
|
||||
wsi->user_space, (void *) &pa, 1))
|
||||
return -1;
|
||||
|
||||
lws_pt_lock(pt);
|
||||
pt->count_conns++;
|
||||
insert_wsi(context, wsi);
|
||||
#if defined(LWS_WITH_ESP8266)
|
||||
if (wsi->position_in_fds_table == -1)
|
||||
#endif
|
||||
wsi->position_in_fds_table = pt->fds_count;
|
||||
|
||||
// lwsl_notice("%s: %p: setting posinfds %d\n", __func__, wsi, wsi->position_in_fds_table);
|
||||
|
||||
pt->fds[wsi->position_in_fds_table].fd = wsi->desc.sockfd;
|
||||
#if LWS_POSIX
|
||||
pt->fds[wsi->position_in_fds_table].events = LWS_POLLIN;
|
||||
#else
|
||||
pt->fds[wsi->position_in_fds_table].events = 0; // LWS_POLLIN;
|
||||
#endif
|
||||
pa.events = pt->fds[pt->fds_count].events;
|
||||
|
||||
lws_plat_insert_socket_into_fds(context, wsi);
|
||||
|
||||
/* external POLL support via protocol 0 */
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_ADD_POLL_FD,
|
||||
wsi->user_space, (void *) &pa, 0))
|
||||
ret = -1;
|
||||
#ifndef LWS_NO_SERVER
|
||||
/* if no more room, defeat accepts on this thread */
|
||||
if ((unsigned int)pt->fds_count == context->fd_limit_per_thread - 1)
|
||||
lws_accept_modulation(pt, 0);
|
||||
#endif
|
||||
lws_pt_unlock(pt);
|
||||
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL,
|
||||
wsi->user_space, (void *)&pa, 1))
|
||||
ret = -1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
remove_wsi_socket_from_fds(struct lws *wsi)
|
||||
{
|
||||
struct lws_context *context = wsi->context;
|
||||
struct lws_pollargs pa = { wsi->desc.sockfd, 0, 0 };
|
||||
#if !defined(LWS_WITH_ESP8266)
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
struct lws *end_wsi;
|
||||
int v;
|
||||
#endif
|
||||
int m, ret = 0;
|
||||
|
||||
if (wsi->parent_carries_io) {
|
||||
lws_same_vh_protocol_remove(wsi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) && !defined(LWS_WITH_ESP8266)
|
||||
if (wsi->desc.sockfd > context->max_fds) {
|
||||
lwsl_err("fd %d too high (%d)\n", wsi->desc.sockfd, context->max_fds);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL,
|
||||
wsi->user_space, (void *)&pa, 1))
|
||||
return -1;
|
||||
|
||||
lws_same_vh_protocol_remove(wsi);
|
||||
|
||||
/* the guy who is to be deleted's slot index in pt->fds */
|
||||
m = wsi->position_in_fds_table;
|
||||
|
||||
#if !defined(LWS_WITH_ESP8266)
|
||||
lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE | LWS_EV_PREPARE_DELETION);
|
||||
lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE | LWS_EV_PREPARE_DELETION);
|
||||
|
||||
lws_pt_lock(pt);
|
||||
|
||||
lwsl_debug("%s: wsi=%p, sock=%d, fds pos=%d, end guy pos=%d, endfd=%d\n",
|
||||
__func__, wsi, wsi->desc.sockfd, wsi->position_in_fds_table,
|
||||
pt->fds_count, pt->fds[pt->fds_count].fd);
|
||||
|
||||
/* have the last guy take up the now vacant slot */
|
||||
pt->fds[m] = pt->fds[pt->fds_count - 1];
|
||||
#endif
|
||||
/* this decrements pt->fds_count */
|
||||
lws_plat_delete_socket_from_fds(context, wsi, m);
|
||||
#if !defined(LWS_WITH_ESP8266)
|
||||
v = (int) pt->fds[m].fd;
|
||||
/* end guy's "position in fds table" is now the deletion guy's old one */
|
||||
end_wsi = wsi_from_fd(context, v);
|
||||
if (!end_wsi) {
|
||||
lwsl_err("no wsi found for sock fd %d at pos %d, pt->fds_count=%d\n", (int)pt->fds[m].fd, m, pt->fds_count);
|
||||
assert(0);
|
||||
} else
|
||||
end_wsi->position_in_fds_table = m;
|
||||
|
||||
/* deletion guy's lws_lookup entry needs nuking */
|
||||
delete_from_fd(context, wsi->desc.sockfd);
|
||||
/* removed wsi has no position any more */
|
||||
wsi->position_in_fds_table = -1;
|
||||
|
||||
/* remove also from external POLL support via protocol 0 */
|
||||
if (lws_socket_is_valid(wsi->desc.sockfd))
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_DEL_POLL_FD,
|
||||
wsi->user_space, (void *) &pa, 0))
|
||||
ret = -1;
|
||||
#ifndef LWS_NO_SERVER
|
||||
if (!context->being_destroyed)
|
||||
/* if this made some room, accept connects on this thread */
|
||||
if ((unsigned int)pt->fds_count < context->fd_limit_per_thread - 1)
|
||||
lws_accept_modulation(pt, 1);
|
||||
#endif
|
||||
lws_pt_unlock(pt);
|
||||
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL,
|
||||
wsi->user_space, (void *) &pa, 1))
|
||||
ret = -1;
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
lws_change_pollfd(struct lws *wsi, int _and, int _or)
|
||||
{
|
||||
struct lws_context_per_thread *pt;
|
||||
struct lws_context *context;
|
||||
struct lws_pollargs pa;
|
||||
int ret = 0;
|
||||
|
||||
if (!wsi || !wsi->protocol || wsi->position_in_fds_table < 0)
|
||||
return 1;
|
||||
|
||||
context = lws_get_context(wsi);
|
||||
if (!context)
|
||||
return 1;
|
||||
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL,
|
||||
wsi->user_space, (void *) &pa, 0))
|
||||
return -1;
|
||||
|
||||
pt = &context->pt[(int)wsi->tsi];
|
||||
|
||||
lws_pt_lock(pt);
|
||||
ret = _lws_change_pollfd(wsi, _and, _or, &pa);
|
||||
lws_pt_unlock(pt);
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL,
|
||||
wsi->user_space, (void *) &pa, 0))
|
||||
ret = -1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
libwebsocket_callback_on_writable(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi)
|
||||
lws_callback_on_writable(struct lws *wsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt;
|
||||
#ifdef LWS_USE_HTTP2
|
||||
struct libwebsocket *network_wsi, *wsi2;
|
||||
struct lws *network_wsi, *wsi2;
|
||||
int already;
|
||||
#endif
|
||||
|
||||
if (wsi->state == LWSS_SHUTDOWN)
|
||||
return 0;
|
||||
|
||||
if (wsi->socket_is_permanently_unusable)
|
||||
return 0;
|
||||
|
||||
if (wsi->parent_carries_io) {
|
||||
int n = lws_callback_on_writable(wsi->parent);
|
||||
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
wsi->parent_pending_cb_on_writable = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB_REQ, 1);
|
||||
#if defined(LWS_WITH_STATS)
|
||||
if (!wsi->active_writable_req_us) {
|
||||
wsi->active_writable_req_us = time_in_microseconds();
|
||||
lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB_EFF_REQ, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef LWS_USE_HTTP2
|
||||
lwsl_info("%s: %p\n", __func__, wsi);
|
||||
|
||||
if (wsi->mode != LWS_CONNMODE_HTTP2_SERVING)
|
||||
|
||||
if (wsi->mode != LWSCM_HTTP2_SERVING)
|
||||
goto network_sock;
|
||||
|
||||
|
||||
if (wsi->u.http2.requested_POLLOUT) {
|
||||
lwsl_info("already pending writable\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
if (wsi->u.http2.tx_credit <= 0) {
|
||||
/*
|
||||
* other side is not able to cope with us sending
|
||||
* anything so no matter if we have POLLOUT on our side.
|
||||
*
|
||||
*
|
||||
* Delay waiting for our POLLOUT until peer indicates he has
|
||||
* space for more using tx window command in http2 layer
|
||||
*/
|
||||
lwsl_info("%s: %p: waiting_tx_credit (%d)\n", __func__, wsi, wsi->u.http2.tx_credit);
|
||||
lwsl_info("%s: %p: waiting_tx_credit (%d)\n", __func__, wsi,
|
||||
wsi->u.http2.tx_credit);
|
||||
wsi->u.http2.waiting_tx_credit = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
network_wsi = lws_http2_get_network_wsi(wsi);
|
||||
already = network_wsi->u.http2.requested_POLLOUT;
|
||||
|
||||
|
||||
/* mark everybody above him as requesting pollout */
|
||||
|
||||
|
||||
wsi2 = wsi;
|
||||
while (wsi2) {
|
||||
wsi2->u.http2.requested_POLLOUT = 1;
|
||||
lwsl_info("mark %p pending writable\n", wsi2);
|
||||
wsi2 = wsi2->u.http2.parent_wsi;
|
||||
}
|
||||
|
||||
|
||||
/* for network action, act only on the network wsi */
|
||||
|
||||
|
||||
wsi = network_wsi;
|
||||
if (already)
|
||||
return 1;
|
||||
network_sock:
|
||||
#endif
|
||||
|
||||
if (lws_ext_callback_for_each_active(wsi,
|
||||
LWS_EXT_CALLBACK_REQUEST_ON_WRITEABLE, NULL, 0))
|
||||
if (lws_ext_cb_active(wsi, LWS_EXT_CB_REQUEST_ON_WRITEABLE, NULL, 0))
|
||||
return 1;
|
||||
|
||||
if (wsi->position_in_fds_table < 0) {
|
||||
lwsl_err("%s: failed to find socket %d\n", __func__, wsi->sock);
|
||||
lwsl_err("%s: failed to find socket %d\n", __func__, wsi->desc.sockfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lws_change_pollfd(wsi, 0, LWS_POLLOUT))
|
||||
return -1;
|
||||
|
||||
lws_libev_io(context, wsi, LWS_EV_START | LWS_EV_WRITE);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* libwebsocket_callback_on_writable_all_protocol() - Request a callback for
|
||||
* all connections using the given protocol when it
|
||||
* becomes possible to write to each socket without
|
||||
* blocking in turn.
|
||||
/*
|
||||
* stitch protocol choice into the vh protocol linked list
|
||||
* We always insert ourselves at the start of the list
|
||||
*
|
||||
* @protocol: Protocol whose connections will get callbacks
|
||||
* X <-> B
|
||||
* X <-> pAn <-> pB
|
||||
*
|
||||
* Illegal to attach more than once without detach inbetween
|
||||
*/
|
||||
void
|
||||
lws_same_vh_protocol_insert(struct lws *wsi, int n)
|
||||
{
|
||||
//lwsl_err("%s: pre insert vhost start wsi %p, that wsi prev == %p\n",
|
||||
// __func__,
|
||||
// wsi->vhost->same_vh_protocol_list[n],
|
||||
// wsi->same_vh_protocol_prev);
|
||||
|
||||
if (wsi->same_vh_protocol_prev || wsi->same_vh_protocol_next) {
|
||||
lws_same_vh_protocol_remove(wsi);
|
||||
lwsl_notice("Attempted to attach wsi twice to same vh prot\n");
|
||||
}
|
||||
|
||||
wsi->same_vh_protocol_prev = /* guy who points to us */
|
||||
&wsi->vhost->same_vh_protocol_list[n];
|
||||
wsi->same_vh_protocol_next = /* old first guy is our next */
|
||||
wsi->vhost->same_vh_protocol_list[n];
|
||||
/* we become the new first guy */
|
||||
wsi->vhost->same_vh_protocol_list[n] = wsi;
|
||||
|
||||
if (wsi->same_vh_protocol_next)
|
||||
/* old first guy points back to us now */
|
||||
wsi->same_vh_protocol_next->same_vh_protocol_prev =
|
||||
&wsi->same_vh_protocol_next;
|
||||
}
|
||||
|
||||
void
|
||||
lws_same_vh_protocol_remove(struct lws *wsi)
|
||||
{
|
||||
/*
|
||||
* detach ourselves from vh protocol list if we're on one
|
||||
* A -> B -> C
|
||||
* A -> C , or, B -> C, or A -> B
|
||||
*
|
||||
* OK to call on already-detached wsi
|
||||
*/
|
||||
lwsl_info("%s: removing same prot wsi %p\n", __func__, wsi);
|
||||
|
||||
if (wsi->same_vh_protocol_prev) {
|
||||
assert (*(wsi->same_vh_protocol_prev) == wsi);
|
||||
lwsl_info("have prev %p, setting him to our next %p\n",
|
||||
wsi->same_vh_protocol_prev,
|
||||
wsi->same_vh_protocol_next);
|
||||
|
||||
/* guy who pointed to us should point to our next */
|
||||
*(wsi->same_vh_protocol_prev) = wsi->same_vh_protocol_next;
|
||||
}
|
||||
|
||||
/* our next should point back to our prev */
|
||||
if (wsi->same_vh_protocol_next) {
|
||||
wsi->same_vh_protocol_next->same_vh_protocol_prev =
|
||||
wsi->same_vh_protocol_prev;
|
||||
}
|
||||
|
||||
wsi->same_vh_protocol_prev = NULL;
|
||||
wsi->same_vh_protocol_next = NULL;
|
||||
}
|
||||
|
||||
|
||||
LWS_VISIBLE int
|
||||
libwebsocket_callback_on_writable_all_protocol(
|
||||
const struct libwebsocket_protocols *protocol)
|
||||
lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost,
|
||||
const struct lws_protocols *protocol)
|
||||
{
|
||||
struct libwebsocket_context *context = protocol->owning_server;
|
||||
int n;
|
||||
struct libwebsocket *wsi;
|
||||
struct lws *wsi;
|
||||
|
||||
for (n = 0; n < context->fds_count; n++) {
|
||||
wsi = wsi_from_fd(context,context->fds[n].fd);
|
||||
if (!wsi)
|
||||
continue;
|
||||
if (wsi->protocol == protocol)
|
||||
libwebsocket_callback_on_writable(context, wsi);
|
||||
if (protocol < vhost->protocols ||
|
||||
protocol >= (vhost->protocols + vhost->count_protocols)) {
|
||||
|
||||
lwsl_err("%s: protocol %p is not from vhost %p (%p - %p)\n",
|
||||
__func__, protocol, vhost->protocols, vhost,
|
||||
(vhost->protocols + vhost->count_protocols));
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
wsi = vhost->same_vh_protocol_list[protocol - vhost->protocols];
|
||||
//lwsl_notice("%s: protocol %p, start wsi %p\n", __func__, protocol, wsi);
|
||||
while (wsi) {
|
||||
//lwsl_notice("%s: protocol %p, this wsi %p (wsi->protocol=%p)\n",
|
||||
// __func__, protocol, wsi, wsi->protocol);
|
||||
assert(wsi->protocol == protocol);
|
||||
assert(*wsi->same_vh_protocol_prev == wsi);
|
||||
if (wsi->same_vh_protocol_next) {
|
||||
// lwsl_err("my next says %p\n", wsi->same_vh_protocol_next);
|
||||
// lwsl_err("my next's prev says %p\n",
|
||||
// wsi->same_vh_protocol_next->same_vh_protocol_prev);
|
||||
assert(wsi->same_vh_protocol_next->same_vh_protocol_prev == &wsi->same_vh_protocol_next);
|
||||
}
|
||||
//lwsl_notice(" apv: %p\n", wsi);
|
||||
lws_callback_on_writable(wsi);
|
||||
wsi = wsi->same_vh_protocol_next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_callback_on_writable_all_protocol(const struct lws_context *context,
|
||||
const struct lws_protocols *protocol)
|
||||
{
|
||||
struct lws_vhost *vhost = context->vhost_list;
|
||||
int n;
|
||||
|
||||
while (vhost) {
|
||||
for (n = 0; n < vhost->count_protocols; n++)
|
||||
if (protocol->callback ==
|
||||
vhost->protocols[n].callback &&
|
||||
!strcmp(protocol->name, vhost->protocols[n].name))
|
||||
break;
|
||||
if (n != vhost->count_protocols)
|
||||
lws_callback_on_writable_all_protocol_vhost(
|
||||
vhost, &vhost->protocols[n]);
|
||||
|
||||
vhost = vhost->vhost_next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
File diff suppressed because it is too large
Load diff
211
lib/ranges.c
Normal file
211
lib/ranges.c
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* RFC7233 ranges parser
|
||||
*
|
||||
* Copyright (C) 2016 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation:
|
||||
* version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
|
||||
/*
|
||||
* RFC7233 examples
|
||||
*
|
||||
* o The first 500 bytes (byte offsets 0-499, inclusive):
|
||||
*
|
||||
* bytes=0-499
|
||||
*
|
||||
* o The second 500 bytes (byte offsets 500-999, inclusive):
|
||||
*
|
||||
* bytes=500-999
|
||||
*
|
||||
* o The final 500 bytes (byte offsets 9500-9999, inclusive):
|
||||
*
|
||||
* bytes=-500
|
||||
*
|
||||
* Or:
|
||||
*
|
||||
* bytes=9500-
|
||||
*
|
||||
* o The first and last bytes only (bytes 0 and 9999):
|
||||
*
|
||||
* bytes=0-0,-1
|
||||
*
|
||||
* o Other valid (but not canonical) specifications of the second 500
|
||||
* bytes (byte offsets 500-999, inclusive):
|
||||
*
|
||||
* bytes=500-600,601-999
|
||||
* bytes=500-700,601-999
|
||||
*/
|
||||
|
||||
/*
|
||||
* returns 1 if the range struct represents a usable range
|
||||
* if no ranges header, you get one of these for the whole
|
||||
* file. Otherwise you get one for each valid range in the
|
||||
* header.
|
||||
*
|
||||
* returns 0 if no further valid range forthcoming; rp->state
|
||||
* may be LWSRS_SYNTAX or LWSRS_COMPLETED
|
||||
*/
|
||||
|
||||
int
|
||||
lws_ranges_next(struct lws_range_parsing *rp)
|
||||
{
|
||||
static const char * const beq = "bytes=";
|
||||
char c;
|
||||
|
||||
while (1) {
|
||||
|
||||
c = rp->buf[rp->pos];
|
||||
|
||||
switch (rp->state) {
|
||||
case LWSRS_SYNTAX:
|
||||
case LWSRS_COMPLETED:
|
||||
return 0;
|
||||
|
||||
case LWSRS_NO_ACTIVE_RANGE:
|
||||
rp->state = LWSRS_COMPLETED;
|
||||
return 0;
|
||||
|
||||
case LWSRS_BYTES_EQ: // looking for "bytes="
|
||||
if (c != beq[rp->pos]) {
|
||||
rp->state = LWSRS_SYNTAX;
|
||||
return -1;
|
||||
}
|
||||
if (rp->pos == 5)
|
||||
rp->state = LWSRS_FIRST;
|
||||
break;
|
||||
|
||||
case LWSRS_FIRST:
|
||||
rp->start = 0;
|
||||
rp->end = 0;
|
||||
rp->start_valid = 0;
|
||||
rp->end_valid = 0;
|
||||
|
||||
rp->state = LWSRS_STARTING;
|
||||
|
||||
// fallthru
|
||||
|
||||
case LWSRS_STARTING:
|
||||
if (c == '-') {
|
||||
rp->state = LWSRS_ENDING;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(c >= '0' && c <= '9')) {
|
||||
rp->state = LWSRS_SYNTAX;
|
||||
return 0;
|
||||
}
|
||||
rp->start = (rp->start * 10) + (c - '0');
|
||||
rp->start_valid = 1;
|
||||
break;
|
||||
|
||||
case LWSRS_ENDING:
|
||||
if (c == ',' || c == '\0') {
|
||||
rp->state = LWSRS_FIRST;
|
||||
if (c == ',')
|
||||
rp->pos++;
|
||||
|
||||
/* by the end of this, start and end are always valid if the range still is */
|
||||
|
||||
if (!rp->start_valid) { /* eg, -500 */
|
||||
if (rp->end > rp->extent)
|
||||
rp->end = rp->extent;
|
||||
|
||||
rp->start = rp->extent - rp->end;
|
||||
rp->end = rp->extent - 1;
|
||||
} else
|
||||
if (!rp->end_valid)
|
||||
rp->end = rp->extent - 1;
|
||||
|
||||
rp->did_try = 1;
|
||||
|
||||
/* end must be >= start or ignore it */
|
||||
if (rp->end < rp->start) {
|
||||
if (c == ',')
|
||||
break;
|
||||
rp->state = LWSRS_COMPLETED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1; /* issue range */
|
||||
}
|
||||
|
||||
if (!(c >= '0' && c <= '9')) {
|
||||
rp->state = LWSRS_SYNTAX;
|
||||
return 0;
|
||||
}
|
||||
rp->end = (rp->end * 10) + (c - '0');
|
||||
rp->end_valid = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
rp->pos++;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
lws_ranges_reset(struct lws_range_parsing *rp)
|
||||
{
|
||||
rp->pos = 0;
|
||||
rp->ctr = 0;
|
||||
rp->start = 0;
|
||||
rp->end = 0;
|
||||
rp->start_valid = 0;
|
||||
rp->end_valid = 0;
|
||||
rp->state = LWSRS_BYTES_EQ;
|
||||
}
|
||||
|
||||
/*
|
||||
* returns count of valid ranges
|
||||
*/
|
||||
int
|
||||
lws_ranges_init(struct lws *wsi, struct lws_range_parsing *rp,
|
||||
unsigned long long extent)
|
||||
{
|
||||
rp->agg = 0;
|
||||
rp->send_ctr = 0;
|
||||
rp->inside = 0;
|
||||
rp->count_ranges = 0;
|
||||
rp->did_try = 0;
|
||||
lws_ranges_reset(rp);
|
||||
rp->state = LWSRS_COMPLETED;
|
||||
|
||||
rp->extent = extent;
|
||||
|
||||
if (lws_hdr_copy(wsi, (char *)rp->buf, sizeof(rp->buf),
|
||||
WSI_TOKEN_HTTP_RANGE) <= 0)
|
||||
return 0;
|
||||
|
||||
rp->state = LWSRS_BYTES_EQ;
|
||||
|
||||
while (lws_ranges_next(rp)) {
|
||||
rp->count_ranges++;
|
||||
rp->agg += rp->end - rp->start + 1;
|
||||
}
|
||||
|
||||
lwsl_debug("%s: count %d\n", __func__, rp->count_ranges);
|
||||
lws_ranges_reset(rp);
|
||||
|
||||
if (rp->did_try && !rp->count_ranges)
|
||||
return -1; /* "not satisfiable */
|
||||
|
||||
lws_ranges_next(rp);
|
||||
|
||||
return rp->count_ranges;
|
||||
}
|
52
lib/rewrite.c
Normal file
52
lib/rewrite.c
Normal file
|
@ -0,0 +1,52 @@
|
|||
#include "private-libwebsockets.h"
|
||||
|
||||
|
||||
LWS_EXTERN struct lws_rewrite *
|
||||
lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to)
|
||||
{
|
||||
struct lws_rewrite *r = lws_malloc(sizeof(*r));
|
||||
|
||||
if (!r) {
|
||||
lwsl_err("OOM\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (hubbub_parser_create("UTF-8", false, &r->parser) != HUBBUB_OK) {
|
||||
lws_free(r);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
r->from = from;
|
||||
r->from_len = strlen(from);
|
||||
r->to = to;
|
||||
r->to_len = strlen(to);
|
||||
r->params.token_handler.handler = cb;
|
||||
r->wsi = wsi;
|
||||
r->params.token_handler.pw = (void *)r;
|
||||
if (hubbub_parser_setopt(r->parser, HUBBUB_PARSER_TOKEN_HANDLER,
|
||||
&r->params) != HUBBUB_OK) {
|
||||
lws_free(r);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
LWS_EXTERN int
|
||||
lws_rewrite_parse(struct lws_rewrite *r,
|
||||
const unsigned char *in, int in_len)
|
||||
{
|
||||
if (hubbub_parser_parse_chunk(r->parser, in, in_len) != HUBBUB_OK)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_EXTERN void
|
||||
lws_rewrite_destroy(struct lws_rewrite *r)
|
||||
{
|
||||
hubbub_parser_destroy(r->parser);
|
||||
lws_free(r);
|
||||
}
|
||||
|
224
lib/romfs.c
Normal file
224
lib/romfs.c
Normal file
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Copyright (C) 2017 National Institute of Advanced Industrial Science
|
||||
* and Technology (AIST)
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of AIST nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "romfs.h"
|
||||
#include "esp_spi_flash.h"
|
||||
|
||||
#define RFS_STRING_MAX 96
|
||||
|
||||
static u32_be_t cache[(RFS_STRING_MAX + 32) / 4];
|
||||
static romfs_inode_t ci = (romfs_inode_t)cache;
|
||||
static romfs_t cr = (romfs_t)cache;
|
||||
|
||||
static void
|
||||
set_cache(romfs_inode_t inode, size_t len)
|
||||
{
|
||||
spi_flash_read((uint32_t)inode, cache, len);
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
ntohl(const u32_be_t be)
|
||||
{
|
||||
return ((be >> 24) & 0xff) |
|
||||
((be >> 16) & 0xff) << 8 |
|
||||
((be >> 8) & 0xff) << 16 |
|
||||
(be & 0xff) << 24;
|
||||
}
|
||||
static romfs_inode_t
|
||||
romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path);
|
||||
|
||||
static int
|
||||
plus_padding(const uint8_t *s)
|
||||
{
|
||||
int n;
|
||||
|
||||
set_cache((romfs_inode_t)s, RFS_STRING_MAX);
|
||||
n = strlen((const char *)cache);
|
||||
|
||||
if (!(n & 15))
|
||||
n += 0x10;
|
||||
|
||||
return (n + 15) & ~15;
|
||||
}
|
||||
|
||||
static romfs_inode_t
|
||||
skip_and_pad(romfs_inode_t ri)
|
||||
{
|
||||
const uint8_t *p = ((const uint8_t *)ri) + sizeof(*ri);
|
||||
|
||||
return (romfs_inode_t)(p + plus_padding(p));
|
||||
}
|
||||
|
||||
size_t
|
||||
romfs_mount_check(romfs_t romfs)
|
||||
{
|
||||
set_cache((romfs_inode_t)romfs, sizeof(*romfs));
|
||||
|
||||
if (cr->magic1 != 0x6d6f722d ||
|
||||
cr->magic2 != 0x2d736631)
|
||||
return 0;
|
||||
|
||||
return ntohl(cr->size);
|
||||
}
|
||||
|
||||
static romfs_inode_t
|
||||
romfs_symlink(romfs_t romfs, romfs_inode_t level, romfs_inode_t i)
|
||||
{
|
||||
const char *p = (const char *)skip_and_pad(i);
|
||||
|
||||
if (*p == '/') {
|
||||
level = skip_and_pad((romfs_inode_t)romfs);
|
||||
p++;
|
||||
}
|
||||
|
||||
return romfs_lookup(romfs, level, p);
|
||||
}
|
||||
|
||||
static romfs_inode_t
|
||||
dir_link(romfs_t romfs, romfs_inode_t i)
|
||||
{
|
||||
set_cache(i, sizeof(*i));
|
||||
return (romfs_inode_t)((const uint8_t *)romfs +
|
||||
ntohl(ci->dir_start));
|
||||
}
|
||||
|
||||
static romfs_inode_t
|
||||
romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path)
|
||||
{
|
||||
romfs_inode_t level, i = start, i_in;
|
||||
const char *p, *n, *cp;
|
||||
uint32_t next_be;
|
||||
|
||||
if (start == (romfs_inode_t)romfs)
|
||||
i = skip_and_pad((romfs_inode_t)romfs);
|
||||
level = i;
|
||||
while (i != (romfs_inode_t)romfs) {
|
||||
p = path;
|
||||
n = ((const char *)i) + sizeof(*i);
|
||||
i_in = i;
|
||||
|
||||
set_cache(i, sizeof(*i));
|
||||
next_be = ci->next;
|
||||
|
||||
cp = (const char *)cache;
|
||||
set_cache((romfs_inode_t)n, RFS_STRING_MAX);
|
||||
|
||||
while (*p && *p != '/' && *cp && *p == *cp && (p - path) < RFS_STRING_MAX) {
|
||||
p++;
|
||||
n++;
|
||||
cp++;
|
||||
}
|
||||
|
||||
if (!*cp && (!*p || *p == '/') &&
|
||||
(ntohl(next_be) & 7) == RFST_HARDLINK) {
|
||||
set_cache(i, sizeof(*i));
|
||||
return (romfs_inode_t)
|
||||
((const uint8_t *)romfs +
|
||||
(ntohl(ci->dir_start) & ~15));
|
||||
}
|
||||
|
||||
if (!*p && !*cp) {
|
||||
set_cache(i, sizeof(*i));
|
||||
if ((ntohl(ci->next) & 7) == RFST_SYMLINK) {
|
||||
i = romfs_symlink(romfs, level, i);
|
||||
continue;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
if (!*p && *cp == '/')
|
||||
return NULL;
|
||||
|
||||
if (*p == '/' && !*cp) {
|
||||
set_cache(i, sizeof(*i));
|
||||
switch (ntohl(ci->next) & 7) {
|
||||
case RFST_SYMLINK:
|
||||
i = romfs_symlink(romfs, level, i);
|
||||
if (!i)
|
||||
return NULL;
|
||||
i = dir_link(romfs, i);
|
||||
while (*path != '/' && *path)
|
||||
path++;
|
||||
if (!*path)
|
||||
return NULL;
|
||||
path++;
|
||||
continue;
|
||||
case RFST_DIR:
|
||||
path = p + 1;
|
||||
i = dir_link(romfs, i);
|
||||
break;
|
||||
default:
|
||||
path = p + 1;
|
||||
i = skip_and_pad(i);
|
||||
break;
|
||||
}
|
||||
level = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
set_cache(i, sizeof(*i));
|
||||
if (!(ntohl(ci->next) & ~15))
|
||||
return NULL;
|
||||
|
||||
i = (romfs_inode_t)((const uint8_t *)romfs +
|
||||
(ntohl(ci->next) & ~15));
|
||||
if (i == i_in)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const void *
|
||||
romfs_get_info(romfs_t romfs, const char *path, size_t *len, size_t *csum)
|
||||
{
|
||||
romfs_inode_t i;
|
||||
|
||||
if (*path == '/')
|
||||
path++;
|
||||
|
||||
i = romfs_lookup(romfs, (romfs_inode_t)romfs, path);
|
||||
|
||||
if (!i)
|
||||
return NULL;
|
||||
|
||||
set_cache(i, sizeof(*i));
|
||||
*len = ntohl(ci->size);
|
||||
if (csum)
|
||||
*csum = ntohl(ci->checksum);
|
||||
|
||||
return (void *)skip_and_pad(i);
|
||||
}
|
63
lib/romfs.h
Normal file
63
lib/romfs.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (C) 2017 National Institute of Advanced Industrial Science
|
||||
* and Technology (AIST)
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of AIST nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
typedef uint32_t u32_be_t;
|
||||
|
||||
struct romfs_superblock {
|
||||
u32_be_t magic1;
|
||||
u32_be_t magic2;
|
||||
u32_be_t size;
|
||||
u32_be_t checksum;
|
||||
};
|
||||
|
||||
struct romfs_i {
|
||||
u32_be_t next;
|
||||
u32_be_t dir_start;
|
||||
u32_be_t size;
|
||||
u32_be_t checksum;
|
||||
};
|
||||
|
||||
enum {
|
||||
RFST_HARDLINK = 0,
|
||||
RFST_DIR = 1,
|
||||
RFST_SYMLINK = 3,
|
||||
};
|
||||
|
||||
typedef const struct romfs_i *romfs_inode_t;
|
||||
typedef const struct romfs_superblock *romfs_t;
|
||||
|
||||
const void *
|
||||
romfs_get_info(romfs_t romfs, const char *path, size_t *len, size_t *csum);
|
||||
size_t
|
||||
romfs_mount_check(romfs_t romfs);
|
||||
|
|
@ -22,23 +22,26 @@
|
|||
#include "private-libwebsockets.h"
|
||||
|
||||
#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); }
|
||||
|
||||
#ifndef LWS_NO_EXTENSIONS
|
||||
LWS_VISIBLE int
|
||||
lws_extension_server_handshake(struct libwebsocket_context *context,
|
||||
struct libwebsocket *wsi, char **p)
|
||||
static int
|
||||
lws_extension_server_handshake(struct lws *wsi, char **p, int budget)
|
||||
{
|
||||
int n;
|
||||
char *c;
|
||||
char ext_name[128];
|
||||
struct libwebsocket_extension *ext;
|
||||
struct lws_context *context = wsi->context;
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
char ext_name[64], *args, *end = (*p) + budget - 1;
|
||||
const struct lws_ext_options *opts, *po;
|
||||
const struct lws_extension *ext;
|
||||
struct lws_ext_option_arg oa;
|
||||
int n, m, more = 1;
|
||||
int ext_count = 0;
|
||||
int more = 1;
|
||||
char ignore;
|
||||
char *c;
|
||||
|
||||
/*
|
||||
* Figure out which extensions the client has that we want to
|
||||
* enable on this connection, and give him back the list
|
||||
*/
|
||||
|
||||
if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS))
|
||||
return 0;
|
||||
|
||||
|
@ -47,24 +50,48 @@ lws_extension_server_handshake(struct libwebsocket_context *context,
|
|||
* and go through them
|
||||
*/
|
||||
|
||||
if (lws_hdr_copy(wsi, (char *)context->service_buffer,
|
||||
sizeof(context->service_buffer),
|
||||
WSI_TOKEN_EXTENSIONS) < 0)
|
||||
if (lws_hdr_copy(wsi, (char *)pt->serv_buf, context->pt_serv_buf_size,
|
||||
WSI_TOKEN_EXTENSIONS) < 0)
|
||||
return 1;
|
||||
|
||||
c = (char *)context->service_buffer;
|
||||
c = (char *)pt->serv_buf;
|
||||
lwsl_parser("WSI_TOKEN_EXTENSIONS = '%s'\n", c);
|
||||
wsi->count_active_extensions = 0;
|
||||
wsi->count_act_ext = 0;
|
||||
ignore = 0;
|
||||
n = 0;
|
||||
args = NULL;
|
||||
|
||||
/*
|
||||
* We may get a simple request
|
||||
*
|
||||
* Sec-WebSocket-Extensions: permessage-deflate
|
||||
*
|
||||
* or an elaborated one with requested options
|
||||
*
|
||||
* Sec-WebSocket-Extensions: permessage-deflate; \
|
||||
* server_no_context_takeover; \
|
||||
* client_no_context_takeover
|
||||
*/
|
||||
|
||||
while (more) {
|
||||
|
||||
if (*c && (*c != ',' && *c != ' ' && *c != '\t')) {
|
||||
if (*c && (*c != ',' && *c != '\t')) {
|
||||
if (*c == ';') {
|
||||
ignore = 1;
|
||||
args = c + 1;
|
||||
}
|
||||
if (ignore || *c == ' ') {
|
||||
c++;
|
||||
continue;
|
||||
}
|
||||
ext_name[n] = *c++;
|
||||
if (n < sizeof(ext_name) - 1)
|
||||
n++;
|
||||
continue;
|
||||
}
|
||||
ext_name[n] = '\0';
|
||||
|
||||
ignore = 0;
|
||||
if (!*c)
|
||||
more = 0;
|
||||
else {
|
||||
|
@ -73,9 +100,12 @@ lws_extension_server_handshake(struct libwebsocket_context *context,
|
|||
continue;
|
||||
}
|
||||
|
||||
while (args && *args && *args == ' ')
|
||||
args++;
|
||||
|
||||
/* check a client's extension against our support */
|
||||
|
||||
ext = wsi->protocol->owning_server->extensions;
|
||||
ext = wsi->vhost->extensions;
|
||||
|
||||
while (ext && ext->callback) {
|
||||
|
||||
|
@ -85,94 +115,132 @@ lws_extension_server_handshake(struct libwebsocket_context *context,
|
|||
}
|
||||
|
||||
/*
|
||||
* oh, we do support this one he
|
||||
* asked for... but let's ask user
|
||||
* code if it's OK to apply it on this
|
||||
* particular connection + protocol
|
||||
* oh, we do support this one he asked for... but let's
|
||||
* confirm he only gave it once
|
||||
*/
|
||||
|
||||
n = wsi->protocol->owning_server->
|
||||
protocols[0].callback(
|
||||
wsi->protocol->owning_server,
|
||||
wsi,
|
||||
LWS_CALLBACK_CONFIRM_EXTENSION_OKAY,
|
||||
wsi->user_space, ext_name, 0);
|
||||
for (m = 0; m < wsi->count_act_ext; m++)
|
||||
if (wsi->active_extensions[m] == ext) {
|
||||
lwsl_info("extension mentioned twice\n");
|
||||
return 1; /* shenanigans */
|
||||
}
|
||||
|
||||
/*
|
||||
* zero return from callback means
|
||||
* go ahead and allow the extension,
|
||||
* it's what we get if the callback is
|
||||
* ask user code if it's OK to apply it on this
|
||||
* particular connection + protocol
|
||||
*/
|
||||
m = (wsi->protocol->callback)(wsi,
|
||||
LWS_CALLBACK_CONFIRM_EXTENSION_OKAY,
|
||||
wsi->user_space, ext_name, 0);
|
||||
|
||||
/*
|
||||
* zero return from callback means go ahead and allow
|
||||
* the extension, it's what we get if the callback is
|
||||
* unhandled
|
||||
*/
|
||||
|
||||
if (n) {
|
||||
if (m) {
|
||||
ext++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* apply it */
|
||||
|
||||
if (ext_count)
|
||||
*(*p)++ = ',';
|
||||
else
|
||||
LWS_CPYAPP(*p,
|
||||
"\x0d\x0aSec-WebSocket-Extensions: ");
|
||||
*p += sprintf(*p, "%s", ext_name);
|
||||
ext_count++;
|
||||
|
||||
/* instantiate the extension on this conn */
|
||||
|
||||
wsi->active_extensions_user[
|
||||
wsi->count_active_extensions] =
|
||||
lws_zalloc(ext->per_session_data_size);
|
||||
if (wsi->active_extensions_user[
|
||||
wsi->count_active_extensions] == NULL) {
|
||||
lwsl_err("Out of mem\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
wsi->active_extensions[
|
||||
wsi->count_active_extensions] = ext;
|
||||
wsi->active_extensions[wsi->count_act_ext] = ext;
|
||||
|
||||
/* allow him to construct his context */
|
||||
|
||||
ext->callback(wsi->protocol->owning_server,
|
||||
ext, wsi,
|
||||
LWS_EXT_CALLBACK_CONSTRUCT,
|
||||
wsi->active_extensions_user[
|
||||
wsi->count_active_extensions], NULL, 0);
|
||||
if (ext->callback(lws_get_context(wsi), ext, wsi,
|
||||
LWS_EXT_CB_CONSTRUCT,
|
||||
(void *)&wsi->act_ext_user[
|
||||
wsi->count_act_ext],
|
||||
(void *)&opts, 0)) {
|
||||
lwsl_notice("ext %s failed construction\n",
|
||||
ext_name);
|
||||
ext_count--;
|
||||
ext++;
|
||||
|
||||
wsi->count_active_extensions++;
|
||||
lwsl_parser("count_active_extensions <- %d\n",
|
||||
wsi->count_active_extensions);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ext_count > 1)
|
||||
*(*p)++ = ',';
|
||||
else
|
||||
LWS_CPYAPP(*p,
|
||||
"\x0d\x0aSec-WebSocket-Extensions: ");
|
||||
*p += lws_snprintf(*p, (end - *p), "%s", ext_name);
|
||||
|
||||
/*
|
||||
* go through the options trying to apply the
|
||||
* recognized ones
|
||||
*/
|
||||
|
||||
lwsl_debug("ext args %s", args);
|
||||
|
||||
while (args && *args && *args != ',') {
|
||||
while (*args == ' ')
|
||||
args++;
|
||||
po = opts;
|
||||
while (po->name) {
|
||||
lwsl_debug("'%s' '%s'\n", po->name, args);
|
||||
/* only support arg-less options... */
|
||||
if (po->type == EXTARG_NONE &&
|
||||
!strncmp(args, po->name,
|
||||
strlen(po->name))) {
|
||||
oa.option_name = NULL;
|
||||
oa.option_index = po - opts;
|
||||
oa.start = NULL;
|
||||
lwsl_debug("setting %s\n", po->name);
|
||||
if (!ext->callback(
|
||||
lws_get_context(wsi), ext, wsi,
|
||||
LWS_EXT_CB_OPTION_SET,
|
||||
wsi->act_ext_user[
|
||||
wsi->count_act_ext],
|
||||
&oa, (end - *p))) {
|
||||
|
||||
*p += lws_snprintf(*p, (end - *p), "; %s", po->name);
|
||||
lwsl_debug("adding option %s\n", po->name);
|
||||
}
|
||||
}
|
||||
po++;
|
||||
}
|
||||
while (*args && *args != ',' && *args != ';')
|
||||
args++;
|
||||
}
|
||||
|
||||
wsi->count_act_ext++;
|
||||
lwsl_parser("count_act_ext <- %d\n",
|
||||
wsi->count_act_ext);
|
||||
|
||||
ext++;
|
||||
}
|
||||
|
||||
n = 0;
|
||||
args = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
int
|
||||
handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi)
|
||||
handshake_0405(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
unsigned char hash[20];
|
||||
int n;
|
||||
int n, accept_len;
|
||||
char *response;
|
||||
char *p;
|
||||
int accept_len;
|
||||
|
||||
if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST) ||
|
||||
!lws_hdr_total_length(wsi, WSI_TOKEN_KEY)) {
|
||||
!lws_hdr_total_length(wsi, WSI_TOKEN_KEY)) {
|
||||
lwsl_parser("handshake_04 missing pieces\n");
|
||||
/* completed header processing, but missing some bits */
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (lws_hdr_total_length(wsi, WSI_TOKEN_KEY) >=
|
||||
MAX_WEBSOCKET_04_KEY_LEN) {
|
||||
if (lws_hdr_total_length(wsi, WSI_TOKEN_KEY) >= MAX_WEBSOCKET_04_KEY_LEN) {
|
||||
lwsl_warn("Client key too long %d\n", MAX_WEBSOCKET_04_KEY_LEN);
|
||||
goto bail;
|
||||
}
|
||||
|
@ -181,51 +249,54 @@ handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi)
|
|||
* since key length is restricted above (currently 128), cannot
|
||||
* overflow
|
||||
*/
|
||||
n = sprintf((char *)context->service_buffer,
|
||||
"%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
|
||||
lws_hdr_simple_ptr(wsi, WSI_TOKEN_KEY));
|
||||
n = sprintf((char *)pt->serv_buf,
|
||||
"%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
|
||||
lws_hdr_simple_ptr(wsi, WSI_TOKEN_KEY));
|
||||
|
||||
libwebsockets_SHA1(context->service_buffer, n, hash);
|
||||
lws_SHA1(pt->serv_buf, n, hash);
|
||||
|
||||
accept_len = lws_b64_encode_string((char *)hash, 20,
|
||||
(char *)context->service_buffer,
|
||||
sizeof(context->service_buffer));
|
||||
(char *)pt->serv_buf, context->pt_serv_buf_size);
|
||||
if (accept_len < 0) {
|
||||
lwsl_warn("Base64 encoded hash too long\n");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* allocate the per-connection user memory (if any) */
|
||||
if (libwebsocket_ensure_user_space(wsi))
|
||||
if (lws_ensure_user_space(wsi))
|
||||
goto bail;
|
||||
|
||||
/* create the response packet */
|
||||
|
||||
/* make a buffer big enough for everything */
|
||||
|
||||
response = (char *)context->service_buffer + MAX_WEBSOCKET_04_KEY_LEN + LWS_SEND_BUFFER_PRE_PADDING;
|
||||
response = (char *)pt->serv_buf + MAX_WEBSOCKET_04_KEY_LEN + LWS_PRE;
|
||||
p = response;
|
||||
LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a"
|
||||
"Upgrade: WebSocket\x0d\x0a"
|
||||
"Connection: Upgrade\x0d\x0a"
|
||||
"Sec-WebSocket-Accept: ");
|
||||
strcpy(p, (char *)context->service_buffer);
|
||||
strcpy(p, (char *)pt->serv_buf);
|
||||
p += accept_len;
|
||||
|
||||
if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL)) {
|
||||
/* we can only return the protocol header if:
|
||||
* - one came in, and ... */
|
||||
if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) &&
|
||||
/* - it is not an empty string */
|
||||
wsi->protocol->name &&
|
||||
wsi->protocol->name[0]) {
|
||||
LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: ");
|
||||
n = lws_hdr_copy(wsi, p, 128, WSI_TOKEN_PROTOCOL);
|
||||
if (n < 0)
|
||||
goto bail;
|
||||
p += n;
|
||||
p += lws_snprintf(p, 128, "%s", wsi->protocol->name);
|
||||
}
|
||||
|
||||
#ifndef LWS_NO_EXTENSIONS
|
||||
/*
|
||||
* Figure out which extensions the client has that we want to
|
||||
* enable on this connection, and give him back the list
|
||||
* enable on this connection, and give him back the list.
|
||||
*
|
||||
* Give him a limited write bugdet
|
||||
*/
|
||||
if (lws_extension_server_handshake(context, wsi, &p))
|
||||
if (lws_extension_server_handshake(wsi, &p, 192))
|
||||
goto bail;
|
||||
#endif
|
||||
|
||||
|
@ -234,19 +305,18 @@ handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi)
|
|||
/* end of response packet */
|
||||
|
||||
LWS_CPYAPP(p, "\x0d\x0a\x0d\x0a");
|
||||
|
||||
if (!lws_any_extension_handled(context, wsi,
|
||||
LWS_EXT_CALLBACK_HANDSHAKE_REPLY_TX,
|
||||
response, p - response)) {
|
||||
|
||||
if (!lws_any_extension_handled(wsi, LWS_EXT_CB_HANDSHAKE_REPLY_TX,
|
||||
response, p - response)) {
|
||||
|
||||
/* okay send the handshake response accepting the connection */
|
||||
|
||||
lwsl_parser("issuing resp pkt %d len\n", (int)(p - response));
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && ! defined(LWS_WITH_ESP8266)
|
||||
fwrite(response, 1, p - response, stderr);
|
||||
#endif
|
||||
n = libwebsocket_write(wsi, (unsigned char *)response,
|
||||
p - response, LWS_WRITE_HTTP_HEADERS);
|
||||
n = lws_write(wsi, (unsigned char *)response,
|
||||
p - response, LWS_WRITE_HTTP_HEADERS);
|
||||
if (n != (p - response)) {
|
||||
lwsl_debug("handshake_0405: ERROR writing to socket\n");
|
||||
goto bail;
|
||||
|
@ -256,22 +326,26 @@ handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi)
|
|||
|
||||
/* alright clean up and set ourselves into established state */
|
||||
|
||||
wsi->state = WSI_STATE_ESTABLISHED;
|
||||
wsi->state = LWSS_ESTABLISHED;
|
||||
wsi->lws_rx_parse_state = LWS_RXPS_NEW;
|
||||
|
||||
/* notify user code that we're ready to roll */
|
||||
|
||||
if (wsi->protocol->callback)
|
||||
wsi->protocol->callback(wsi->protocol->owning_server,
|
||||
wsi, LWS_CALLBACK_ESTABLISHED,
|
||||
wsi->user_space, NULL, 0);
|
||||
{
|
||||
const char * uri_ptr =
|
||||
lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI);
|
||||
int uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
|
||||
const struct lws_http_mount *hit =
|
||||
lws_find_mount(wsi, uri_ptr, uri_len);
|
||||
if (hit && hit->cgienv &&
|
||||
wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO,
|
||||
wsi->user_space, (void *)hit->cgienv, 0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
bail:
|
||||
/* free up his parsing allocations */
|
||||
lws_free_header_table(wsi);
|
||||
/* caller will free up his parsing allocations */
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
3556
lib/server.c
3556
lib/server.c
File diff suppressed because it is too large
Load diff
1366
lib/service.c
1366
lib/service.c
File diff suppressed because it is too large
Load diff
13
lib/sha-1.c
13
lib/sha-1.c
|
@ -99,7 +99,6 @@ static const unsigned int _K[] =
|
|||
sha1_step(ctxt); \
|
||||
}
|
||||
|
||||
static void sha1_step __P((struct sha1_ctxt *));
|
||||
|
||||
static void
|
||||
sha1_step(struct sha1_ctxt *ctxt)
|
||||
|
@ -190,7 +189,7 @@ sha1_step(struct sha1_ctxt *ctxt)
|
|||
/*------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
sha1_init(struct sha1_ctxt *ctxt)
|
||||
_sha1_init(struct sha1_ctxt *ctxt)
|
||||
{
|
||||
bzero(ctxt, sizeof(struct sha1_ctxt));
|
||||
H(0) = 0x67452301;
|
||||
|
@ -212,14 +211,14 @@ sha1_pad(struct sha1_ctxt *ctxt)
|
|||
padlen = 64 - padstart;
|
||||
if (padlen < 8) {
|
||||
bzero(&ctxt->m.b8[padstart], padlen);
|
||||
COUNT += padlen;
|
||||
COUNT += (unsigned char)padlen;
|
||||
COUNT %= 64;
|
||||
sha1_step(ctxt);
|
||||
padstart = COUNT % 64; /* should be 0 */
|
||||
padlen = 64 - padstart; /* should be 64 */
|
||||
}
|
||||
bzero(&ctxt->m.b8[padstart], padlen - 8);
|
||||
COUNT += (padlen - 8);
|
||||
COUNT += ((unsigned char)padlen - 8);
|
||||
COUNT %= 64;
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
PUTPAD(ctxt->c.b8[0]); PUTPAD(ctxt->c.b8[1]);
|
||||
|
@ -250,7 +249,7 @@ sha1_loop(struct sha1_ctxt *ctxt, const unsigned char *input, size_t len)
|
|||
|
||||
copysiz = (gaplen < len - off) ? gaplen : len - off;
|
||||
memcpy(&ctxt->m.b8[gapstart], &input[off], copysiz);
|
||||
COUNT += copysiz;
|
||||
COUNT += (unsigned char)copysiz;
|
||||
COUNT %= 64;
|
||||
ctxt->c.b64[0] += copysiz * 8;
|
||||
if (COUNT % 64 == 0)
|
||||
|
@ -287,11 +286,11 @@ sha1_result(struct sha1_ctxt *ctxt, void *digest0)
|
|||
*/
|
||||
|
||||
LWS_VISIBLE unsigned char *
|
||||
libwebsockets_SHA1(const unsigned char *d, size_t n, unsigned char *md)
|
||||
lws_SHA1(const unsigned char *d, size_t n, unsigned char *md)
|
||||
{
|
||||
struct sha1_ctxt ctx;
|
||||
|
||||
sha1_init(&ctx);
|
||||
_sha1_init(&ctx);
|
||||
sha1_loop(&ctx, d, n);
|
||||
sha1_result(&ctx, (void *)md);
|
||||
|
||||
|
|
241
lib/smtp.c
Normal file
241
lib/smtp.c
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* SMTP support for libwebsockets
|
||||
*
|
||||
* Copyright (C) 2016 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation:
|
||||
* version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
|
||||
static unsigned int
|
||||
lwsgs_now_secs(void)
|
||||
{
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
|
||||
return tv.tv_sec;
|
||||
}
|
||||
|
||||
static void
|
||||
ccb(uv_handle_t* handle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
|
||||
{
|
||||
struct lws_email *email = (struct lws_email *)handle->data;
|
||||
|
||||
*buf = uv_buf_init(email->email_buf, sizeof(email->email_buf) - 1);
|
||||
}
|
||||
|
||||
static void
|
||||
on_write_end(uv_write_t *req, int status) {
|
||||
lwsl_notice("%s\n", __func__);
|
||||
if (status == -1) {
|
||||
fprintf(stderr, "error on_write_end");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
lwsgs_email_read(struct uv_stream_s *s, ssize_t nread, const uv_buf_t *buf)
|
||||
{
|
||||
struct lws_email *email = (struct lws_email *)s->data;
|
||||
static const short retcodes[] = {
|
||||
0, /* idle */
|
||||
0, /* connecting */
|
||||
220, /* connected */
|
||||
250, /* helo */
|
||||
250, /* from */
|
||||
250, /* to */
|
||||
354, /* data */
|
||||
250, /* body */
|
||||
221, /* quit */
|
||||
};
|
||||
uv_write_t write_req;
|
||||
uv_buf_t wbuf;
|
||||
int n;
|
||||
|
||||
if (nread >= 0)
|
||||
email->email_buf[nread] = '\0';
|
||||
lwsl_notice("%s: %s\n", __func__, buf->base);
|
||||
if (nread == -1) {
|
||||
lwsl_err("%s: failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
n = atoi(buf->base);
|
||||
if (n != retcodes[email->estate]) {
|
||||
lwsl_err("%s: bad response from server\n", __func__);
|
||||
goto close_conn;
|
||||
}
|
||||
|
||||
switch (email->estate) {
|
||||
case LGSSMTP_CONNECTED:
|
||||
n = sprintf(email->content, "HELO %s\n", email->email_helo);
|
||||
email->estate = LGSSMTP_SENT_HELO;
|
||||
break;
|
||||
case LGSSMTP_SENT_HELO:
|
||||
n = sprintf(email->content, "MAIL FROM: <%s>\n", email->email_from);
|
||||
email->estate = LGSSMTP_SENT_FROM;
|
||||
break;
|
||||
case LGSSMTP_SENT_FROM:
|
||||
n = sprintf(email->content, "RCPT TO: <%s>\n", email->email_to);
|
||||
email->estate = LGSSMTP_SENT_TO;
|
||||
break;
|
||||
case LGSSMTP_SENT_TO:
|
||||
n = sprintf(email->content, "DATA\n");
|
||||
email->estate = LGSSMTP_SENT_DATA;
|
||||
break;
|
||||
case LGSSMTP_SENT_DATA:
|
||||
if (email->on_get_body(email, email->content, email->max_content_size))
|
||||
return;
|
||||
n = strlen(email->content);
|
||||
email->estate = LGSSMTP_SENT_BODY;
|
||||
break;
|
||||
case LGSSMTP_SENT_BODY:
|
||||
n = sprintf(email->content, "quit\n");
|
||||
email->estate = LGSSMTP_SENT_QUIT;
|
||||
break;
|
||||
case LGSSMTP_SENT_QUIT:
|
||||
lwsl_notice("%s: done\n", __func__);
|
||||
email->on_sent(email);
|
||||
email->estate = LGSSMTP_IDLE;
|
||||
goto close_conn;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
puts(email->content);
|
||||
wbuf = uv_buf_init(email->content, n);
|
||||
uv_write(&write_req, s, &wbuf, 1, on_write_end);
|
||||
|
||||
return;
|
||||
|
||||
close_conn:
|
||||
|
||||
uv_close((uv_handle_t *)s, ccb);
|
||||
}
|
||||
|
||||
static void
|
||||
lwsgs_email_on_connect(uv_connect_t *req, int status)
|
||||
{
|
||||
struct lws_email *email = (struct lws_email *)req->data;
|
||||
|
||||
lwsl_notice("%s\n", __func__);
|
||||
|
||||
if (status == -1) {
|
||||
lwsl_err("%s: failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
uv_read_start(req->handle, alloc_buffer, lwsgs_email_read);
|
||||
email->estate = LGSSMTP_CONNECTED;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
uv_timeout_cb_email(uv_timer_t *w
|
||||
#if UV_VERSION_MAJOR == 0
|
||||
, int status
|
||||
#endif
|
||||
)
|
||||
{
|
||||
struct lws_email *email = lws_container_of(w, struct lws_email,
|
||||
timeout_email);
|
||||
time_t now = lwsgs_now_secs();
|
||||
struct sockaddr_in req_addr;
|
||||
|
||||
switch (email->estate) {
|
||||
case LGSSMTP_IDLE:
|
||||
|
||||
if (email->on_next(email))
|
||||
break;
|
||||
|
||||
email->estate = LGSSMTP_CONNECTING;
|
||||
|
||||
uv_tcp_init(email->loop, &email->email_client);
|
||||
if (uv_ip4_addr(email->email_smtp_ip, 25, &req_addr)) {
|
||||
lwsl_err("Unable to convert mailserver ads\n");
|
||||
return;
|
||||
}
|
||||
|
||||
lwsl_notice("LGSSMTP_IDLE: connecting\n");
|
||||
|
||||
email->email_connect_started = now;
|
||||
email->email_connect_req.data = email;
|
||||
email->email_client.data = email;
|
||||
uv_tcp_connect(&email->email_connect_req, &email->email_client,
|
||||
(struct sockaddr *)&req_addr,
|
||||
lwsgs_email_on_connect);
|
||||
|
||||
uv_timer_start(&email->timeout_email,
|
||||
uv_timeout_cb_email, 5000, 0);
|
||||
|
||||
break;
|
||||
|
||||
case LGSSMTP_CONNECTING:
|
||||
if (email->email_connect_started - now > 5) {
|
||||
lwsl_err("mail session timed out\n");
|
||||
/* !!! kill the connection */
|
||||
uv_close((uv_handle_t *) &email->email_connect_req, ccb);
|
||||
email->estate = LGSSMTP_IDLE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
lws_email_init(struct lws_email *email, uv_loop_t *loop, int max_content)
|
||||
{
|
||||
email->content = lws_malloc(max_content);
|
||||
if (!email->content)
|
||||
return 1;
|
||||
|
||||
email->max_content_size = max_content;
|
||||
uv_timer_init(loop, &email->timeout_email);
|
||||
|
||||
email->loop = loop;
|
||||
|
||||
/* trigger him one time in a bit */
|
||||
uv_timer_start(&email->timeout_email, uv_timeout_cb_email, 2000, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN void
|
||||
lws_email_check(struct lws_email *email)
|
||||
{
|
||||
uv_timer_start(&email->timeout_email, uv_timeout_cb_email, 1000, 0);
|
||||
}
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN void
|
||||
lws_email_destroy(struct lws_email *email)
|
||||
{
|
||||
if (email->content)
|
||||
lws_free_set_NULL(email->content);
|
||||
|
||||
uv_timer_stop(&email->timeout_email);
|
||||
uv_close((uv_handle_t *)&email->timeout_email, NULL);
|
||||
}
|
||||
|
593
lib/ssl-client.c
Normal file
593
lib/ssl-client.c
Normal file
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation:
|
||||
* version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
|
||||
extern int openssl_websocket_private_data_index,
|
||||
openssl_SSL_CTX_private_data_index;
|
||||
|
||||
extern void
|
||||
lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info);
|
||||
|
||||
extern int lws_ssl_get_error(struct lws *wsi, int n);
|
||||
|
||||
#if defined(USE_WOLFSSL)
|
||||
#else
|
||||
|
||||
static int
|
||||
OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
|
||||
{
|
||||
#if defined(LWS_WITH_ESP32)
|
||||
// long gvr = ssl_pm_get_verify_result(
|
||||
lwsl_notice("%s\n", __func__);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
SSL *ssl;
|
||||
int n;
|
||||
struct lws *wsi;
|
||||
|
||||
/* keep old behaviour accepting self-signed server certs */
|
||||
if (!preverify_ok) {
|
||||
int err = X509_STORE_CTX_get_error(x509_ctx);
|
||||
|
||||
if (err != X509_V_OK) {
|
||||
ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
|
||||
wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index);
|
||||
|
||||
if ((err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
|
||||
err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) &&
|
||||
wsi->use_ssl & LCCSCF_ALLOW_SELFSIGNED) {
|
||||
lwsl_notice("accepting self-signed certificate (verify_callback)\n");
|
||||
X509_STORE_CTX_set_error(x509_ctx, X509_V_OK);
|
||||
return 1; // ok
|
||||
} else if ((err == X509_V_ERR_CERT_NOT_YET_VALID ||
|
||||
err == X509_V_ERR_CERT_HAS_EXPIRED) &&
|
||||
wsi->use_ssl & LCCSCF_ALLOW_EXPIRED) {
|
||||
if (err == X509_V_ERR_CERT_NOT_YET_VALID)
|
||||
lwsl_notice("accepting not yet valid certificate (verify_callback)\n");
|
||||
else if (err == X509_V_ERR_CERT_HAS_EXPIRED)
|
||||
lwsl_notice("accepting expired certificate (verify_callback)\n");
|
||||
X509_STORE_CTX_set_error(x509_ctx, X509_V_OK);
|
||||
return 1; // ok
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
|
||||
wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index);
|
||||
|
||||
n = lws_get_context_protocol(wsi->context, 0).callback(wsi, LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION, x509_ctx, ssl, preverify_ok);
|
||||
|
||||
/* keep old behaviour if something wrong with server certs */
|
||||
/* if ssl error is overruled in callback and cert is ok,
|
||||
* X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); must be set and
|
||||
* return value is 0 from callback */
|
||||
if (!preverify_ok) {
|
||||
int err = X509_STORE_CTX_get_error(x509_ctx);
|
||||
|
||||
if (err != X509_V_OK) { /* cert validation error was not handled in callback */
|
||||
int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
|
||||
const char* msg = X509_verify_cert_error_string(err);
|
||||
lwsl_err("SSL error: %s (preverify_ok=%d;err=%d;depth=%d)\n", msg, preverify_ok, err, depth);
|
||||
return preverify_ok; // not ok
|
||||
}
|
||||
}
|
||||
/* convert callback return code from 0 = OK to verify callback return value 1 = OK */
|
||||
return !n;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
lws_ssl_client_bio_create(struct lws *wsi)
|
||||
{
|
||||
char hostname[128], *p;
|
||||
|
||||
if (lws_hdr_copy(wsi, hostname, sizeof(hostname),
|
||||
_WSI_TOKEN_CLIENT_HOST) <= 0) {
|
||||
lwsl_err("%s: Unable to get hostname\n", __func__);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* remove any :port part on the hostname... necessary for network
|
||||
* connection but typical certificates do not contain it
|
||||
*/
|
||||
p = hostname;
|
||||
while (*p) {
|
||||
if (*p == ':') {
|
||||
*p = '\0';
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
wsi->ssl = SSL_new(wsi->vhost->ssl_client_ctx);
|
||||
if (!wsi->ssl) {
|
||||
lwsl_err("SSL_new failed: %s\n",
|
||||
ERR_error_string(lws_ssl_get_error(wsi, 0), NULL));
|
||||
lws_ssl_elaborate_error();
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK)
|
||||
if (wsi->vhost->ssl_info_event_mask)
|
||||
SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback);
|
||||
#endif
|
||||
|
||||
#if defined LWS_HAVE_X509_VERIFY_PARAM_set1_host
|
||||
X509_VERIFY_PARAM *param;
|
||||
(void)param;
|
||||
|
||||
if (!(wsi->use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) {
|
||||
param = SSL_get0_param(wsi->ssl);
|
||||
/* Enable automatic hostname checks */
|
||||
X509_VERIFY_PARAM_set_hostflags(param,
|
||||
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
|
||||
X509_VERIFY_PARAM_set1_host(param, hostname, 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_ESP32)
|
||||
#ifndef USE_OLD_CYASSL
|
||||
/* OpenSSL_client_verify_callback will be called @ SSL_connect() */
|
||||
SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_ESP32)
|
||||
SSL_set_mode(wsi->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
#endif
|
||||
/*
|
||||
* use server name indication (SNI), if supported,
|
||||
* when establishing connection
|
||||
*/
|
||||
#ifdef USE_WOLFSSL
|
||||
#ifdef USE_OLD_CYASSL
|
||||
#ifdef CYASSL_SNI_HOST_NAME
|
||||
CyaSSL_UseSNI(wsi->ssl, CYASSL_SNI_HOST_NAME, hostname, strlen(hostname));
|
||||
#endif
|
||||
#else
|
||||
#ifdef WOLFSSL_SNI_HOST_NAME
|
||||
wolfSSL_UseSNI(wsi->ssl, WOLFSSL_SNI_HOST_NAME, hostname, strlen(hostname));
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#if defined(LWS_WITH_ESP32)
|
||||
// esp-idf openssl shim does not seem ready for this
|
||||
// SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback);
|
||||
SSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, OpenSSL_client_verify_callback);
|
||||
|
||||
#else
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
SSL_set_tlsext_host_name(wsi->ssl, hostname);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_WOLFSSL
|
||||
/*
|
||||
* wolfSSL/CyaSSL does certificate verification differently
|
||||
* from OpenSSL.
|
||||
* If we should ignore the certificate, we need to set
|
||||
* this before SSL_new and SSL_connect is called.
|
||||
* Otherwise the connect will simply fail with error code -155
|
||||
*/
|
||||
#ifdef USE_OLD_CYASSL
|
||||
if (wsi->use_ssl == 2)
|
||||
CyaSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL);
|
||||
#else
|
||||
if (wsi->use_ssl == 2)
|
||||
wolfSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL);
|
||||
#endif
|
||||
#endif /* USE_WOLFSSL */
|
||||
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
wsi->client_bio = BIO_new_socket(wsi->desc.sockfd, BIO_NOCLOSE);
|
||||
SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio);
|
||||
#else
|
||||
SSL_set_fd(wsi->ssl, wsi->desc.sockfd);
|
||||
#endif
|
||||
|
||||
#ifdef USE_WOLFSSL
|
||||
#ifdef USE_OLD_CYASSL
|
||||
CyaSSL_set_using_nonblock(wsi->ssl, 1);
|
||||
#else
|
||||
wolfSSL_set_using_nonblock(wsi->ssl, 1);
|
||||
#endif
|
||||
#else
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
BIO_set_nbio(wsi->client_bio, 1); /* nonblocking */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
SSL_set_ex_data(wsi->ssl, openssl_websocket_private_data_index,
|
||||
wsi);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(LWS_WITH_ESP32)
|
||||
int ERR_get_error(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
lws_ssl_client_connect1(struct lws *wsi)
|
||||
{
|
||||
struct lws_context *context = wsi->context;
|
||||
int n = 0;
|
||||
|
||||
lws_latency_pre(context, wsi);
|
||||
|
||||
n = SSL_connect(wsi->ssl);
|
||||
|
||||
lws_latency(context, wsi,
|
||||
"SSL_connect LWSCM_WSCL_ISSUE_HANDSHAKE", n, n > 0);
|
||||
|
||||
if (n < 0) {
|
||||
n = lws_ssl_get_error(wsi, n);
|
||||
|
||||
if (n == SSL_ERROR_WANT_READ)
|
||||
goto some_wait;
|
||||
|
||||
if (n == SSL_ERROR_WANT_WRITE) {
|
||||
/*
|
||||
* wants us to retry connect due to
|
||||
* state of the underlying ssl layer...
|
||||
* but since it may be stalled on
|
||||
* blocked write, no incoming data may
|
||||
* arrive to trigger the retry.
|
||||
* Force (possibly many times if the SSL
|
||||
* state persists in returning the
|
||||
* condition code, but other sockets
|
||||
* are getting serviced inbetweentimes)
|
||||
* us to get called back when writable.
|
||||
*/
|
||||
lwsl_info("%s: WANT_WRITE... retrying\n", __func__);
|
||||
lws_callback_on_writable(wsi);
|
||||
some_wait:
|
||||
wsi->mode = LWSCM_WSCL_WAITING_SSL;
|
||||
|
||||
return 0; /* no error */
|
||||
}
|
||||
|
||||
{
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
char *p = (char *)&pt->serv_buf[0];
|
||||
char *sb = p;
|
||||
|
||||
lwsl_err("ssl hs1 error, X509_V_ERR = %d: %s\n",
|
||||
n, ERR_error_string(n, sb));
|
||||
lws_ssl_elaborate_error();
|
||||
}
|
||||
|
||||
n = -1;
|
||||
}
|
||||
|
||||
if (n <= 0) {
|
||||
/*
|
||||
* retry if new data comes until we
|
||||
* run into the connection timeout or win
|
||||
*/
|
||||
|
||||
unsigned long error = ERR_get_error();
|
||||
|
||||
if (error != SSL_ERROR_NONE) {
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
char *p = (char *)&pt->serv_buf[0];
|
||||
char *sb = p;
|
||||
lwsl_err("SSL connect error %lu: %s\n",
|
||||
error, ERR_error_string(error, sb));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
lws_ssl_client_connect2(struct lws *wsi)
|
||||
{
|
||||
struct lws_context *context = wsi->context;
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
char *p = (char *)&pt->serv_buf[0];
|
||||
char *sb = p;
|
||||
int n = 0;
|
||||
|
||||
if (wsi->mode == LWSCM_WSCL_WAITING_SSL) {
|
||||
lws_latency_pre(context, wsi);
|
||||
n = SSL_connect(wsi->ssl);
|
||||
lwsl_debug("%s: SSL_connect says %d\n", __func__, n);
|
||||
|
||||
lws_latency(context, wsi,
|
||||
"SSL_connect LWSCM_WSCL_WAITING_SSL", n, n > 0);
|
||||
|
||||
if (n < 0) {
|
||||
n = lws_ssl_get_error(wsi, n);
|
||||
|
||||
if (n == SSL_ERROR_WANT_READ) {
|
||||
lwsl_info("SSL_connect WANT_READ... retrying\n");
|
||||
|
||||
wsi->mode = LWSCM_WSCL_WAITING_SSL;
|
||||
|
||||
return 0; /* no error */
|
||||
}
|
||||
|
||||
if (n == SSL_ERROR_WANT_WRITE) {
|
||||
/*
|
||||
* wants us to retry connect due to
|
||||
* state of the underlying ssl layer...
|
||||
* but since it may be stalled on
|
||||
* blocked write, no incoming data may
|
||||
* arrive to trigger the retry.
|
||||
* Force (possibly many times if the SSL
|
||||
* state persists in returning the
|
||||
* condition code, but other sockets
|
||||
* are getting serviced inbetweentimes)
|
||||
* us to get called back when writable.
|
||||
*/
|
||||
lwsl_info("SSL_connect WANT_WRITE... retrying\n");
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
wsi->mode = LWSCM_WSCL_WAITING_SSL;
|
||||
|
||||
return 0; /* no error */
|
||||
}
|
||||
|
||||
n = -1;
|
||||
}
|
||||
|
||||
if (n <= 0) {
|
||||
/*
|
||||
* retry if new data comes until we
|
||||
* run into the connection timeout or win
|
||||
*/
|
||||
unsigned long error = ERR_get_error();
|
||||
if (error != SSL_ERROR_NONE) {
|
||||
lwsl_err("SSL connect error %lu: %s\n",
|
||||
error, ERR_error_string(error, sb));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(LWS_WITH_ESP32)
|
||||
{
|
||||
X509 *peer = SSL_get_peer_certificate(wsi->ssl);
|
||||
|
||||
if (!peer) {
|
||||
lwsl_notice("peer did not provide cert\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
lwsl_notice("peer provided cert\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef USE_WOLFSSL
|
||||
/*
|
||||
* See comment above about wolfSSL certificate
|
||||
* verification
|
||||
*/
|
||||
lws_latency_pre(context, wsi);
|
||||
n = SSL_get_verify_result(wsi->ssl);
|
||||
lws_latency(context, wsi,
|
||||
"SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0);
|
||||
|
||||
lwsl_debug("get_verify says %d\n", n);
|
||||
|
||||
if (n != X509_V_OK) {
|
||||
if ((n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
|
||||
n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) &&
|
||||
(wsi->use_ssl & LCCSCF_ALLOW_SELFSIGNED)) {
|
||||
lwsl_notice("accepting self-signed certificate\n");
|
||||
} else if ((n == X509_V_ERR_CERT_NOT_YET_VALID ||
|
||||
n == X509_V_ERR_CERT_HAS_EXPIRED) &&
|
||||
(wsi->use_ssl & LCCSCF_ALLOW_EXPIRED)) {
|
||||
lwsl_notice("accepting expired certificate\n");
|
||||
} else if (n == X509_V_ERR_CERT_NOT_YET_VALID) {
|
||||
lwsl_notice("Cert is from the future... "
|
||||
"probably our clock... accepting...\n");
|
||||
} else {
|
||||
lwsl_err("server's cert didn't look good, X509_V_ERR = %d: %s\n",
|
||||
n, ERR_error_string(n, sb));
|
||||
lws_ssl_elaborate_error();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* USE_WOLFSSL */
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int lws_context_init_client_ssl(struct lws_context_creation_info *info,
|
||||
struct lws_vhost *vhost)
|
||||
{
|
||||
SSL_METHOD *method = NULL;
|
||||
struct lws wsi;
|
||||
unsigned long error;
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
const char *cipher_list = info->ssl_cipher_list;
|
||||
const char *ca_filepath = info->ssl_ca_filepath;
|
||||
const char *private_key_filepath = info->ssl_private_key_filepath;
|
||||
const char *cert_filepath = info->ssl_cert_filepath;
|
||||
|
||||
int n;
|
||||
|
||||
/*
|
||||
* for backwards-compatibility default to using ssl_... members, but
|
||||
* if the newer client-specific ones are given, use those
|
||||
*/
|
||||
if (info->client_ssl_cipher_list)
|
||||
cipher_list = info->client_ssl_cipher_list;
|
||||
if (info->client_ssl_ca_filepath)
|
||||
ca_filepath = info->client_ssl_ca_filepath;
|
||||
if (info->client_ssl_cert_filepath)
|
||||
cert_filepath = info->client_ssl_cert_filepath;
|
||||
if (info->client_ssl_private_key_filepath)
|
||||
private_key_filepath = info->client_ssl_private_key_filepath;
|
||||
#endif
|
||||
|
||||
if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT))
|
||||
return 0;
|
||||
|
||||
if (vhost->ssl_client_ctx)
|
||||
return 0;
|
||||
|
||||
if (info->provided_client_ssl_ctx) {
|
||||
/* use the provided OpenSSL context if given one */
|
||||
vhost->ssl_client_ctx = info->provided_client_ssl_ctx;
|
||||
/* nothing for lib to delete */
|
||||
vhost->user_supplied_ssl_ctx = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* basic openssl init already happened in context init */
|
||||
|
||||
/* choose the most recent spin of the api */
|
||||
#if defined(LWS_HAVE_TLS_CLIENT_METHOD)
|
||||
method = (SSL_METHOD *)TLS_client_method();
|
||||
#elif defined(LWS_HAVE_TLSV1_2_CLIENT_METHOD)
|
||||
method = (SSL_METHOD *)TLSv1_2_client_method();
|
||||
#else
|
||||
method = (SSL_METHOD *)SSLv23_client_method();
|
||||
#endif
|
||||
if (!method) {
|
||||
error = ERR_get_error();
|
||||
lwsl_err("problem creating ssl method %lu: %s\n",
|
||||
error, ERR_error_string(error,
|
||||
(char *)vhost->context->pt[0].serv_buf));
|
||||
return 1;
|
||||
}
|
||||
/* create context */
|
||||
vhost->ssl_client_ctx = SSL_CTX_new(method);
|
||||
if (!vhost->ssl_client_ctx) {
|
||||
error = ERR_get_error();
|
||||
lwsl_err("problem creating ssl context %lu: %s\n",
|
||||
error, ERR_error_string(error,
|
||||
(char *)vhost->context->pt[0].serv_buf));
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef SSL_OP_NO_COMPRESSION
|
||||
SSL_CTX_set_options(vhost->ssl_client_ctx, SSL_OP_NO_COMPRESSION);
|
||||
#endif
|
||||
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
SSL_CTX_set_options(vhost->ssl_client_ctx,
|
||||
SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||
|
||||
if (cipher_list)
|
||||
SSL_CTX_set_cipher_list(vhost->ssl_client_ctx, cipher_list);
|
||||
|
||||
#ifdef LWS_SSL_CLIENT_USE_OS_CA_CERTS
|
||||
if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS))
|
||||
/* loads OS default CA certs */
|
||||
SSL_CTX_set_default_verify_paths(vhost->ssl_client_ctx);
|
||||
#endif
|
||||
|
||||
/* openssl init for cert verification (for client sockets) */
|
||||
if (!ca_filepath) {
|
||||
if (!SSL_CTX_load_verify_locations(
|
||||
vhost->ssl_client_ctx, NULL,
|
||||
LWS_OPENSSL_CLIENT_CERTS))
|
||||
lwsl_err(
|
||||
"Unable to load SSL Client certs from %s "
|
||||
"(set by --with-client-cert-dir= "
|
||||
"in configure) -- client ssl isn't "
|
||||
"going to work\n", LWS_OPENSSL_CLIENT_CERTS);
|
||||
} else
|
||||
if (!SSL_CTX_load_verify_locations(
|
||||
vhost->ssl_client_ctx, ca_filepath, NULL)) {
|
||||
lwsl_err(
|
||||
"Unable to load SSL Client certs "
|
||||
"file from %s -- client ssl isn't "
|
||||
"going to work\n", info->client_ssl_ca_filepath);
|
||||
lws_ssl_elaborate_error();
|
||||
}
|
||||
else
|
||||
lwsl_info("loaded ssl_ca_filepath\n");
|
||||
#endif
|
||||
/*
|
||||
* callback allowing user code to load extra verification certs
|
||||
* helping the client to verify server identity
|
||||
*/
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
|
||||
/* support for client-side certificate authentication */
|
||||
if (cert_filepath) {
|
||||
lwsl_notice("%s: doing cert filepath\n", __func__);
|
||||
n = SSL_CTX_use_certificate_chain_file(vhost->ssl_client_ctx,
|
||||
cert_filepath);
|
||||
if (n < 1) {
|
||||
lwsl_err("problem %d getting cert '%s'\n", n,
|
||||
cert_filepath);
|
||||
lws_ssl_elaborate_error();
|
||||
return 1;
|
||||
}
|
||||
lwsl_notice("Loaded client cert %s\n", cert_filepath);
|
||||
}
|
||||
if (private_key_filepath) {
|
||||
lwsl_notice("%s: doing private key filepath\n", __func__);
|
||||
lws_ssl_bind_passphrase(vhost->ssl_client_ctx, info);
|
||||
/* set the private key from KeyFile */
|
||||
if (SSL_CTX_use_PrivateKey_file(vhost->ssl_client_ctx,
|
||||
private_key_filepath, SSL_FILETYPE_PEM) != 1) {
|
||||
lwsl_err("use_PrivateKey_file '%s'\n",
|
||||
private_key_filepath);
|
||||
lws_ssl_elaborate_error();
|
||||
return 1;
|
||||
}
|
||||
lwsl_notice("Loaded client cert private key %s\n",
|
||||
private_key_filepath);
|
||||
|
||||
/* verify private key */
|
||||
if (!SSL_CTX_check_private_key(vhost->ssl_client_ctx)) {
|
||||
lwsl_err("Private SSL key doesn't match cert\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* give him a fake wsi with context set, so he can use
|
||||
* lws_get_context() in the callback
|
||||
*/
|
||||
memset(&wsi, 0, sizeof(wsi));
|
||||
wsi.vhost = vhost;
|
||||
wsi.context = vhost->context;
|
||||
|
||||
vhost->protocols[0].callback(&wsi,
|
||||
LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS,
|
||||
vhost->ssl_client_ctx, NULL, 0);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -17,12 +17,12 @@
|
|||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*
|
||||
*
|
||||
* Some or all of this file is based on code from nghttp2, which has the
|
||||
* following license. Since it's more liberal than lws license, you're also
|
||||
* at liberty to get the original code from
|
||||
* https://github.com/tatsuhiro-t/nghttp2 under his liberal terms alone.
|
||||
*
|
||||
*
|
||||
* nghttp2 - HTTP/2.0 C Library
|
||||
*
|
||||
* Copyright (c) 2012 Tatsuhiro Tsujikawa
|
||||
|
@ -59,7 +59,8 @@ struct alpn_ctx {
|
|||
unsigned short len;
|
||||
};
|
||||
|
||||
static int npn_cb(SSL *s, const unsigned char **data, unsigned int *len, void *arg)
|
||||
static int
|
||||
npn_cb(SSL *s, const unsigned char **data, unsigned int *len, void *arg)
|
||||
{
|
||||
struct alpn_ctx *alpn_ctx = arg;
|
||||
|
||||
|
@ -70,15 +71,15 @@ static int npn_cb(SSL *s, const unsigned char **data, unsigned int *len, void *a
|
|||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
|
||||
static int alpn_cb(SSL *s, const unsigned char **out,
|
||||
unsigned char *outlen, const unsigned char *in,
|
||||
unsigned int inlen, void *arg)
|
||||
static int
|
||||
alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen,
|
||||
const unsigned char *in, unsigned int inlen, void *arg)
|
||||
{
|
||||
struct alpn_ctx *alpn_ctx = arg;
|
||||
|
||||
if (SSL_select_next_proto((unsigned char **)out, outlen,
|
||||
alpn_ctx->data, alpn_ctx->len, in, inlen) !=
|
||||
OPENSSL_NPN_NEGOTIATED)
|
||||
if (SSL_select_next_proto((unsigned char **)out, outlen, alpn_ctx->data,
|
||||
alpn_ctx->len, in, inlen) !=
|
||||
OPENSSL_NPN_NEGOTIATED)
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
|
@ -86,63 +87,62 @@ static int alpn_cb(SSL *s, const unsigned char **out,
|
|||
#endif
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_context_init_http2_ssl(struct libwebsocket_context *context)
|
||||
lws_context_init_http2_ssl(struct lws_vhost *vhost)
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
static struct alpn_ctx protos = { (unsigned char *)
|
||||
"\x05h2-14"
|
||||
"\x08http/1.1",
|
||||
6 + 9 };
|
||||
static struct alpn_ctx protos = { (unsigned char *)"\x02h2"
|
||||
"\x08http/1.1", 6 + 9 };
|
||||
|
||||
SSL_CTX_set_next_protos_advertised_cb(vhost->ssl_ctx, npn_cb, &protos);
|
||||
|
||||
SSL_CTX_set_next_protos_advertised_cb(context->ssl_ctx, npn_cb, &protos);
|
||||
|
||||
// ALPN selection callback
|
||||
SSL_CTX_set_alpn_select_cb(context->ssl_ctx, alpn_cb, &protos);
|
||||
SSL_CTX_set_alpn_select_cb(vhost->ssl_ctx, alpn_cb, &protos);
|
||||
lwsl_notice(" HTTP2 / ALPN enabled\n");
|
||||
#else
|
||||
lwsl_notice(
|
||||
" HTTP2 / ALPN configured but not supported by OpenSSL 0x%x\n",
|
||||
" HTTP2 / ALPN configured but not supported by OpenSSL 0x%lx\n",
|
||||
OPENSSL_VERSION_NUMBER);
|
||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
}
|
||||
|
||||
void lws_http2_configure_if_upgraded(struct libwebsocket *wsi)
|
||||
void lws_http2_configure_if_upgraded(struct lws *wsi)
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
struct allocated_headers *ah;
|
||||
const char *method = "alpn";
|
||||
const unsigned char *name;
|
||||
unsigned len;
|
||||
const char *method = "alpn";
|
||||
|
||||
SSL_get0_alpn_selected(wsi->ssl, &name, &len);
|
||||
|
||||
|
||||
if (!len) {
|
||||
SSL_get0_next_proto_negotiated(wsi->ssl, &name, &len);
|
||||
method = "npn";
|
||||
}
|
||||
|
||||
|
||||
if (!len) {
|
||||
lwsl_info("no npn/alpn upgrade\n");
|
||||
return;
|
||||
}
|
||||
|
||||
(void)method;
|
||||
lwsl_info("negotiated %s using %s\n", name, method);
|
||||
wsi->use_ssl = 1;
|
||||
if (strncmp((char *)name, "http/1.1", 8) == 0)
|
||||
return;
|
||||
|
||||
|
||||
/* http2 */
|
||||
|
||||
/* adopt the header info */
|
||||
|
||||
ah = wsi->u.hdr.ah;
|
||||
|
||||
lws_union_transition(wsi, LWS_CONNMODE_HTTP2_SERVING);
|
||||
wsi->state = WSI_STATE_HTTP2_AWAIT_CLIENT_PREFACE;
|
||||
|
||||
lws_union_transition(wsi, LWSCM_HTTP2_SERVING);
|
||||
wsi->state = LWSS_HTTP2_AWAIT_CLIENT_PREFACE;
|
||||
|
||||
/* http2 union member has http union struct at start */
|
||||
wsi->u.http.ah = ah;
|
||||
|
||||
|
||||
lws_http2_init(&wsi->u.http2.peer_settings);
|
||||
lws_http2_init(&wsi->u.http2.my_settings);
|
||||
|
||||
|
@ -151,4 +151,4 @@ void lws_http2_configure_if_upgraded(struct libwebsocket *wsi)
|
|||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
|
439
lib/ssl-server.c
Normal file
439
lib/ssl-server.c
Normal file
|
@ -0,0 +1,439 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation:
|
||||
* version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
|
||||
extern int openssl_websocket_private_data_index,
|
||||
openssl_SSL_CTX_private_data_index;
|
||||
|
||||
extern void
|
||||
lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info);
|
||||
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
static int
|
||||
OpenSSL_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
|
||||
{
|
||||
SSL *ssl;
|
||||
int n;
|
||||
struct lws *wsi;
|
||||
|
||||
ssl = X509_STORE_CTX_get_ex_data(x509_ctx,
|
||||
SSL_get_ex_data_X509_STORE_CTX_idx());
|
||||
|
||||
/*
|
||||
* !!! nasty openssl requires the index to come as a library-scope
|
||||
* static
|
||||
*/
|
||||
wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index);
|
||||
|
||||
n = wsi->vhost->protocols[0].callback(wsi,
|
||||
LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION,
|
||||
x509_ctx, ssl, preverify_ok);
|
||||
|
||||
/* convert return code from 0 = OK to 1 = OK */
|
||||
return !n;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
lws_context_ssl_init_ecdh(struct lws_vhost *vhost)
|
||||
{
|
||||
#ifdef LWS_SSL_SERVER_WITH_ECDH_CERT
|
||||
EC_KEY *EC_key = NULL;
|
||||
EVP_PKEY *pkey;
|
||||
int KeyType;
|
||||
X509 *x;
|
||||
|
||||
if (!lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_SSL_ECDH))
|
||||
return 0;
|
||||
|
||||
lwsl_notice(" Using ECDH certificate support\n");
|
||||
|
||||
/* Get X509 certificate from ssl context */
|
||||
x = sk_X509_value(vhost->ssl_ctx->extra_certs, 0);
|
||||
if (!x) {
|
||||
lwsl_err("%s: x is NULL\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
/* Get the public key from certificate */
|
||||
pkey = X509_get_pubkey(x);
|
||||
if (!pkey) {
|
||||
lwsl_err("%s: pkey is NULL\n", __func__);
|
||||
|
||||
return 1;
|
||||
}
|
||||
/* Get the key type */
|
||||
KeyType = EVP_PKEY_type(pkey->type);
|
||||
|
||||
if (EVP_PKEY_EC != KeyType) {
|
||||
lwsl_notice("Key type is not EC\n");
|
||||
return 0;
|
||||
}
|
||||
/* Get the key */
|
||||
EC_key = EVP_PKEY_get1_EC_KEY(pkey);
|
||||
/* Set ECDH parameter */
|
||||
if (!EC_key) {
|
||||
lwsl_err("%s: ECDH key is NULL \n", __func__);
|
||||
return 1;
|
||||
}
|
||||
SSL_CTX_set_tmp_ecdh(vhost->ssl_ctx, EC_key);
|
||||
EC_KEY_free(EC_key);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
lws_context_ssl_init_ecdh_curve(struct lws_context_creation_info *info,
|
||||
struct lws_vhost *vhost)
|
||||
{
|
||||
#ifdef LWS_HAVE_OPENSSL_ECDH_H
|
||||
EC_KEY *ecdh;
|
||||
int ecdh_nid;
|
||||
const char *ecdh_curve = "prime256v1";
|
||||
|
||||
if (info->ecdh_curve)
|
||||
ecdh_curve = info->ecdh_curve;
|
||||
|
||||
ecdh_nid = OBJ_sn2nid(ecdh_curve);
|
||||
if (NID_undef == ecdh_nid) {
|
||||
lwsl_err("SSL: Unknown curve name '%s'", ecdh_curve);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ecdh = EC_KEY_new_by_curve_name(ecdh_nid);
|
||||
if (NULL == ecdh) {
|
||||
lwsl_err("SSL: Unable to create curve '%s'", ecdh_curve);
|
||||
return 1;
|
||||
}
|
||||
SSL_CTX_set_tmp_ecdh(vhost->ssl_ctx, ecdh);
|
||||
EC_KEY_free(ecdh);
|
||||
|
||||
SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_SINGLE_ECDH_USE);
|
||||
|
||||
lwsl_notice(" SSL ECDH curve '%s'\n", ecdh_curve);
|
||||
#else
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
lwsl_notice(" OpenSSL doesn't support ECDH\n");
|
||||
#endif
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef OPENSSL_NO_TLSEXT
|
||||
static int
|
||||
lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg)
|
||||
{
|
||||
struct lws_context *context;
|
||||
struct lws_vhost *vhost, *vh;
|
||||
const char *servername;
|
||||
int port;
|
||||
|
||||
if (!ssl)
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
|
||||
context = (struct lws_context *)SSL_CTX_get_ex_data(
|
||||
SSL_get_SSL_CTX(ssl),
|
||||
openssl_SSL_CTX_private_data_index);
|
||||
|
||||
/*
|
||||
* We can only get ssl accepted connections by using a vhost's ssl_ctx
|
||||
* find out which listening one took us and only match vhosts on the
|
||||
* same port.
|
||||
*/
|
||||
vh = context->vhost_list;
|
||||
while (vh) {
|
||||
if (!vh->being_destroyed && vh->ssl_ctx == SSL_get_SSL_CTX(ssl))
|
||||
break;
|
||||
vh = vh->vhost_next;
|
||||
}
|
||||
|
||||
assert(vh); /* we cannot get an ssl without using a vhost ssl_ctx */
|
||||
port = vh->listen_port;
|
||||
|
||||
servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
||||
|
||||
if (servername) {
|
||||
vhost = lws_select_vhost(context, port, servername);
|
||||
if (vhost) {
|
||||
lwsl_debug("SNI: Found: %s (port %d)\n",
|
||||
servername, port);
|
||||
SSL_set_SSL_CTX(ssl, vhost->ssl_ctx);
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
lwsl_err("SNI: Unknown ServerName: %s\n", servername);
|
||||
}
|
||||
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
LWS_VISIBLE int
|
||||
lws_context_init_server_ssl(struct lws_context_creation_info *info,
|
||||
struct lws_vhost *vhost)
|
||||
{
|
||||
struct lws_context *context = vhost->context;
|
||||
struct lws wsi;
|
||||
unsigned long error;
|
||||
int n;
|
||||
|
||||
if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) {
|
||||
vhost->use_ssl = 0;
|
||||
return 0;
|
||||
}
|
||||
if (info->port != CONTEXT_PORT_NO_LISTEN) {
|
||||
|
||||
vhost->use_ssl = info->ssl_cert_filepath != NULL;
|
||||
|
||||
if (vhost->use_ssl && info->ssl_cipher_list)
|
||||
lwsl_notice(" SSL ciphers: '%s'\n", info->ssl_cipher_list);
|
||||
|
||||
if (vhost->use_ssl)
|
||||
lwsl_notice(" Using SSL mode\n");
|
||||
else
|
||||
lwsl_notice(" Using non-SSL mode\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* give him a fake wsi with context + vhost set, so he can use
|
||||
* lws_get_context() in the callback
|
||||
*/
|
||||
memset(&wsi, 0, sizeof(wsi));
|
||||
wsi.vhost = vhost;
|
||||
wsi.context = context;
|
||||
|
||||
(void)n;
|
||||
(void)error;
|
||||
|
||||
/*
|
||||
* Firefox insists on SSLv23 not SSLv3
|
||||
* Konq disables SSLv2 by default now, SSLv23 works
|
||||
*
|
||||
* SSLv23_server_method() is the openssl method for "allow all TLS
|
||||
* versions", compared to e.g. TLSv1_2_server_method() which only allows
|
||||
* tlsv1.2. Unwanted versions must be disabled using SSL_CTX_set_options()
|
||||
*/
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
{
|
||||
SSL_METHOD *method;
|
||||
|
||||
method = (SSL_METHOD *)SSLv23_server_method();
|
||||
if (!method) {
|
||||
error = ERR_get_error();
|
||||
lwsl_err("problem creating ssl method %lu: %s\n",
|
||||
error, ERR_error_string(error,
|
||||
(char *)context->pt[0].serv_buf));
|
||||
return 1;
|
||||
}
|
||||
vhost->ssl_ctx = SSL_CTX_new(method); /* create context */
|
||||
if (!vhost->ssl_ctx) {
|
||||
error = ERR_get_error();
|
||||
lwsl_err("problem creating ssl context %lu: %s\n",
|
||||
error, ERR_error_string(error,
|
||||
(char *)context->pt[0].serv_buf));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
const SSL_METHOD *method = TLSv1_2_server_method();
|
||||
|
||||
vhost->ssl_ctx = SSL_CTX_new(method); /* create context */
|
||||
if (!vhost->ssl_ctx) {
|
||||
lwsl_err("problem creating ssl context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
|
||||
/* associate the lws context with the SSL_CTX */
|
||||
|
||||
SSL_CTX_set_ex_data(vhost->ssl_ctx,
|
||||
openssl_SSL_CTX_private_data_index, (char *)vhost->context);
|
||||
/* Disable SSLv2 and SSLv3 */
|
||||
SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||
#ifdef SSL_OP_NO_COMPRESSION
|
||||
SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_NO_COMPRESSION);
|
||||
#endif
|
||||
SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_SINGLE_DH_USE);
|
||||
SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||
|
||||
if (info->ssl_cipher_list)
|
||||
SSL_CTX_set_cipher_list(vhost->ssl_ctx,
|
||||
info->ssl_cipher_list);
|
||||
#endif
|
||||
|
||||
/* as a server, are we requiring clients to identify themselves? */
|
||||
|
||||
if (lws_check_opt(info->options,
|
||||
LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT)) {
|
||||
int verify_options = SSL_VERIFY_PEER;
|
||||
|
||||
if (!lws_check_opt(info->options,
|
||||
LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED))
|
||||
verify_options |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
||||
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
SSL_CTX_set_session_id_context(vhost->ssl_ctx,
|
||||
(unsigned char *)context, sizeof(void *));
|
||||
|
||||
/* absolutely require the client cert */
|
||||
|
||||
SSL_CTX_set_verify(vhost->ssl_ctx,
|
||||
verify_options, OpenSSL_verify_callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef OPENSSL_NO_TLSEXT
|
||||
SSL_CTX_set_tlsext_servername_callback(vhost->ssl_ctx,
|
||||
lws_ssl_server_name_cb);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* give user code a chance to load certs into the server
|
||||
* allowing it to verify incoming client certs
|
||||
*/
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
if (info->ssl_ca_filepath &&
|
||||
!SSL_CTX_load_verify_locations(vhost->ssl_ctx,
|
||||
info->ssl_ca_filepath, NULL)) {
|
||||
lwsl_err("%s: SSL_CTX_load_verify_locations unhappy\n", __func__);
|
||||
}
|
||||
#endif
|
||||
if (vhost->use_ssl) {
|
||||
if (lws_context_ssl_init_ecdh_curve(info, vhost))
|
||||
return -1;
|
||||
|
||||
vhost->protocols[0].callback(&wsi,
|
||||
LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS,
|
||||
vhost->ssl_ctx, NULL, 0);
|
||||
}
|
||||
|
||||
if (lws_check_opt(info->options, LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT))
|
||||
/* Normally SSL listener rejects non-ssl, optionally allow */
|
||||
vhost->allow_non_ssl_on_ssl_port = 1;
|
||||
|
||||
if (info->ssl_options_set)
|
||||
SSL_CTX_set_options(vhost->ssl_ctx, info->ssl_options_set);
|
||||
|
||||
/* SSL_clear_options introduced in 0.9.8m */
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x009080df) && !defined(USE_WOLFSSL)
|
||||
if (info->ssl_options_clear)
|
||||
SSL_CTX_clear_options(vhost->ssl_ctx, info->ssl_options_clear);
|
||||
#endif
|
||||
|
||||
lwsl_info(" SSL options 0x%lX\n",
|
||||
SSL_CTX_get_options(vhost->ssl_ctx));
|
||||
|
||||
if (vhost->use_ssl) {
|
||||
/* openssl init for server sockets */
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
/* set the local certificate from CertFile */
|
||||
n = SSL_CTX_use_certificate_chain_file(vhost->ssl_ctx,
|
||||
info->ssl_cert_filepath);
|
||||
if (n != 1) {
|
||||
error = ERR_get_error();
|
||||
lwsl_err("problem getting cert '%s' %lu: %s\n",
|
||||
info->ssl_cert_filepath,
|
||||
error,
|
||||
ERR_error_string(error,
|
||||
(char *)context->pt[0].serv_buf));
|
||||
return 1;
|
||||
}
|
||||
lws_ssl_bind_passphrase(vhost->ssl_ctx, info);
|
||||
#else
|
||||
uint8_t *p;
|
||||
lws_filepos_t flen;
|
||||
int err;
|
||||
|
||||
if (alloc_pem_to_der_file(vhost->context, info->ssl_cert_filepath, &p,
|
||||
&flen)) {
|
||||
lwsl_err("couldn't find cert file %s\n",
|
||||
info->ssl_cert_filepath);
|
||||
|
||||
return 1;
|
||||
}
|
||||
err = SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx, flen, p);
|
||||
if (!err) {
|
||||
lwsl_err("Problem loading cert\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (alloc_pem_to_der_file(vhost->context,
|
||||
info->ssl_private_key_filepath, &p, &flen)) {
|
||||
lwsl_err("couldn't find cert file %s\n",
|
||||
info->ssl_cert_filepath);
|
||||
|
||||
return 1;
|
||||
}
|
||||
err = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, p, flen);
|
||||
if (!err) {
|
||||
lwsl_err("Problem loading key\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// free(p);
|
||||
#endif
|
||||
if (info->ssl_private_key_filepath != NULL) {
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
/* set the private key from KeyFile */
|
||||
if (SSL_CTX_use_PrivateKey_file(vhost->ssl_ctx,
|
||||
info->ssl_private_key_filepath,
|
||||
SSL_FILETYPE_PEM) != 1) {
|
||||
error = ERR_get_error();
|
||||
lwsl_err("ssl problem getting key '%s' %lu: %s\n",
|
||||
info->ssl_private_key_filepath, error,
|
||||
ERR_error_string(error,
|
||||
(char *)context->pt[0].serv_buf));
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
} else
|
||||
if (vhost->protocols[0].callback(&wsi,
|
||||
LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY,
|
||||
vhost->ssl_ctx, NULL, 0)) {
|
||||
lwsl_err("ssl private key not set\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
#if !defined(LWS_WITH_ESP32)
|
||||
/* verify private key */
|
||||
if (!SSL_CTX_check_private_key(vhost->ssl_ctx)) {
|
||||
lwsl_err("Private SSL key doesn't match cert\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
if (lws_context_ssl_init_ecdh(vhost))
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* SSL is happy and has a cert it's content with
|
||||
* If we're supporting HTTP2, initialize that
|
||||
*/
|
||||
|
||||
lws_context_init_http2_ssl(vhost);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load diff
317
libwebsockets.dox
Normal file
317
libwebsockets.dox
Normal file
|
@ -0,0 +1,317 @@
|
|||
# Doxyfile 1.8.11
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Project related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
DOXYFILE_ENCODING = UTF-8
|
||||
PROJECT_NAME = "libwebsockets"
|
||||
PROJECT_NUMBER =
|
||||
PROJECT_BRIEF = "Lightweight C library for HTML5 websockets"
|
||||
PROJECT_LOGO = "./test-server/libwebsockets.org-logo.png"
|
||||
OUTPUT_DIRECTORY = "doc"
|
||||
CREATE_SUBDIRS = NO
|
||||
ALLOW_UNICODE_NAMES = NO
|
||||
OUTPUT_LANGUAGE = English
|
||||
BRIEF_MEMBER_DESC = YES
|
||||
REPEAT_BRIEF = YES
|
||||
ABBREVIATE_BRIEF =
|
||||
ALWAYS_DETAILED_SEC = NO
|
||||
INLINE_INHERITED_MEMB = NO
|
||||
FULL_PATH_NAMES = YES
|
||||
STRIP_FROM_PATH =
|
||||
STRIP_FROM_INC_PATH =
|
||||
SHORT_NAMES = NO
|
||||
JAVADOC_AUTOBRIEF = NO
|
||||
QT_AUTOBRIEF = NO
|
||||
MULTILINE_CPP_IS_BRIEF = NO
|
||||
INHERIT_DOCS = YES
|
||||
SEPARATE_MEMBER_PAGES = NO
|
||||
TAB_SIZE = 8
|
||||
ALIASES =
|
||||
TCL_SUBST =
|
||||
OPTIMIZE_OUTPUT_FOR_C = YES
|
||||
OPTIMIZE_OUTPUT_JAVA = NO
|
||||
OPTIMIZE_FOR_FORTRAN = NO
|
||||
OPTIMIZE_OUTPUT_VHDL = NO
|
||||
EXTENSION_MAPPING =
|
||||
MARKDOWN_SUPPORT = YES
|
||||
AUTOLINK_SUPPORT = YES
|
||||
BUILTIN_STL_SUPPORT = NO
|
||||
CPP_CLI_SUPPORT = NO
|
||||
SIP_SUPPORT = NO
|
||||
IDL_PROPERTY_SUPPORT = YES
|
||||
DISTRIBUTE_GROUP_DOC = NO
|
||||
GROUP_NESTED_COMPOUNDS = NO
|
||||
SUBGROUPING = YES
|
||||
INLINE_GROUPED_CLASSES = NO
|
||||
INLINE_SIMPLE_STRUCTS = NO
|
||||
TYPEDEF_HIDES_STRUCT = NO
|
||||
LOOKUP_CACHE_SIZE = 0
|
||||
#---------------------------------------------------------------------------
|
||||
# Build related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
EXTRACT_ALL = NO
|
||||
EXTRACT_PRIVATE = NO
|
||||
EXTRACT_PACKAGE = NO
|
||||
EXTRACT_STATIC = NO
|
||||
EXTRACT_LOCAL_CLASSES = YES
|
||||
EXTRACT_LOCAL_METHODS = NO
|
||||
EXTRACT_ANON_NSPACES = NO
|
||||
HIDE_UNDOC_MEMBERS = NO
|
||||
HIDE_UNDOC_CLASSES = NO
|
||||
HIDE_FRIEND_COMPOUNDS = NO
|
||||
HIDE_IN_BODY_DOCS = NO
|
||||
INTERNAL_DOCS = NO
|
||||
CASE_SENSE_NAMES = YES
|
||||
HIDE_SCOPE_NAMES = NO
|
||||
HIDE_COMPOUND_REFERENCE= NO
|
||||
SHOW_INCLUDE_FILES = YES
|
||||
SHOW_GROUPED_MEMB_INC = YES
|
||||
FORCE_LOCAL_INCLUDES = NO
|
||||
INLINE_INFO = YES
|
||||
SORT_MEMBER_DOCS = YES
|
||||
SORT_BRIEF_DOCS = NO
|
||||
SORT_MEMBERS_CTORS_1ST = NO
|
||||
SORT_GROUP_NAMES = YES
|
||||
SORT_BY_SCOPE_NAME = NO
|
||||
STRICT_PROTO_MATCHING = YES
|
||||
GENERATE_TODOLIST = YES
|
||||
GENERATE_TESTLIST = YES
|
||||
GENERATE_BUGLIST = YES
|
||||
GENERATE_DEPRECATEDLIST= YES
|
||||
ENABLED_SECTIONS =
|
||||
MAX_INITIALIZER_LINES = 30
|
||||
SHOW_USED_FILES = YES
|
||||
SHOW_FILES = YES
|
||||
SHOW_NAMESPACES = YES
|
||||
FILE_VERSION_FILTER =
|
||||
LAYOUT_FILE =
|
||||
CITE_BIB_FILES =
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to warning and progress messages
|
||||
#---------------------------------------------------------------------------
|
||||
QUIET = NO
|
||||
WARNINGS = YES
|
||||
WARN_IF_UNDOCUMENTED = NO
|
||||
WARN_IF_DOC_ERROR = YES
|
||||
WARN_NO_PARAMDOC = NO
|
||||
WARN_AS_ERROR = NO
|
||||
WARN_FORMAT = "$file:$line: $text"
|
||||
WARN_LOGFILE =
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the input files
|
||||
#---------------------------------------------------------------------------
|
||||
INPUT = lib/libwebsockets.h mainpage.md README.build.md README.problems.md README.lwsws.md README.coding.md README.generic-sessions.md README.generic-table.md README.test-apps.md doc-assets
|
||||
INPUT_ENCODING = UTF-8
|
||||
FILE_PATTERNS = lib/*.c *.md *.png
|
||||
RECURSIVE = NO
|
||||
EXCLUDE =
|
||||
EXCLUDE_SYMLINKS = NO
|
||||
EXCLUDE_PATTERNS =
|
||||
EXCLUDE_SYMBOLS =
|
||||
EXAMPLE_PATH =
|
||||
EXAMPLE_PATTERNS =
|
||||
EXAMPLE_RECURSIVE = NO
|
||||
IMAGE_PATH = doc-assets
|
||||
INPUT_FILTER =
|
||||
FILTER_PATTERNS =
|
||||
FILTER_SOURCE_FILES = NO
|
||||
FILTER_SOURCE_PATTERNS =
|
||||
USE_MDFILE_AS_MAINPAGE = mainpage.md
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to source browsing
|
||||
#---------------------------------------------------------------------------
|
||||
SOURCE_BROWSER = NO
|
||||
INLINE_SOURCES = YES
|
||||
STRIP_CODE_COMMENTS = YES
|
||||
REFERENCED_BY_RELATION = NO
|
||||
REFERENCES_RELATION = NO
|
||||
REFERENCES_LINK_SOURCE = YES
|
||||
SOURCE_TOOLTIPS = YES
|
||||
USE_HTAGS = NO
|
||||
VERBATIM_HEADERS = YES
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the alphabetical class index
|
||||
#---------------------------------------------------------------------------
|
||||
ALPHABETICAL_INDEX = YES
|
||||
COLS_IN_ALPHA_INDEX = 5
|
||||
IGNORE_PREFIX =
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the HTML output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_HTML = YES
|
||||
HTML_OUTPUT = html
|
||||
HTML_FILE_EXTENSION = .html
|
||||
HTML_HEADER =
|
||||
HTML_FOOTER =
|
||||
HTML_STYLESHEET =
|
||||
HTML_EXTRA_STYLESHEET =
|
||||
HTML_EXTRA_FILES =
|
||||
HTML_COLORSTYLE_HUE = 220
|
||||
HTML_COLORSTYLE_SAT = 100
|
||||
HTML_COLORSTYLE_GAMMA = 80
|
||||
HTML_TIMESTAMP = NO
|
||||
HTML_DYNAMIC_SECTIONS = NO
|
||||
HTML_INDEX_NUM_ENTRIES = 100
|
||||
GENERATE_DOCSET = NO
|
||||
DOCSET_FEEDNAME = "Doxygen generated docs"
|
||||
DOCSET_BUNDLE_ID = org.doxygen.Project
|
||||
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
|
||||
DOCSET_PUBLISHER_NAME = Publisher
|
||||
GENERATE_HTMLHELP = NO
|
||||
CHM_FILE =
|
||||
HHC_LOCATION =
|
||||
GENERATE_CHI = NO
|
||||
CHM_INDEX_ENCODING =
|
||||
BINARY_TOC = NO
|
||||
TOC_EXPAND = NO
|
||||
GENERATE_QHP = NO
|
||||
QCH_FILE =
|
||||
QHP_NAMESPACE = org.doxygen.Project
|
||||
QHP_VIRTUAL_FOLDER = doc
|
||||
QHP_CUST_FILTER_NAME =
|
||||
QHP_CUST_FILTER_ATTRS =
|
||||
QHP_SECT_FILTER_ATTRS =
|
||||
QHG_LOCATION =
|
||||
GENERATE_ECLIPSEHELP = NO
|
||||
ECLIPSE_DOC_ID = org.doxygen.Project
|
||||
DISABLE_INDEX = NO
|
||||
GENERATE_TREEVIEW = YES
|
||||
ENUM_VALUES_PER_LINE = 4
|
||||
TREEVIEW_WIDTH = 250
|
||||
EXT_LINKS_IN_WINDOW = NO
|
||||
FORMULA_FONTSIZE = 10
|
||||
FORMULA_TRANSPARENT = YES
|
||||
USE_MATHJAX = NO
|
||||
MATHJAX_FORMAT = HTML-CSS
|
||||
MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
|
||||
MATHJAX_EXTENSIONS =
|
||||
MATHJAX_CODEFILE =
|
||||
SEARCHENGINE = NO
|
||||
SERVER_BASED_SEARCH = NO
|
||||
EXTERNAL_SEARCH = NO
|
||||
SEARCHENGINE_URL =
|
||||
SEARCHDATA_FILE = searchdata.xml
|
||||
EXTERNAL_SEARCH_ID =
|
||||
EXTRA_SEARCH_MAPPINGS =
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the LaTeX output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_LATEX = NO
|
||||
LATEX_OUTPUT = latex
|
||||
LATEX_CMD_NAME = latex
|
||||
MAKEINDEX_CMD_NAME = makeindex
|
||||
COMPACT_LATEX = NO
|
||||
PAPER_TYPE = a4
|
||||
EXTRA_PACKAGES =
|
||||
LATEX_HEADER =
|
||||
LATEX_FOOTER =
|
||||
LATEX_EXTRA_STYLESHEET =
|
||||
LATEX_EXTRA_FILES =
|
||||
PDF_HYPERLINKS = YES
|
||||
USE_PDFLATEX = YES
|
||||
LATEX_BATCHMODE = NO
|
||||
LATEX_HIDE_INDICES = NO
|
||||
LATEX_SOURCE_CODE = NO
|
||||
LATEX_BIB_STYLE = plain
|
||||
LATEX_TIMESTAMP = NO
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the RTF output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_RTF = NO
|
||||
RTF_OUTPUT = rtf
|
||||
COMPACT_RTF = NO
|
||||
RTF_HYPERLINKS = NO
|
||||
RTF_STYLESHEET_FILE =
|
||||
RTF_EXTENSIONS_FILE =
|
||||
RTF_SOURCE_CODE = NO
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the man page output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_MAN = NO
|
||||
MAN_OUTPUT = man
|
||||
MAN_EXTENSION = .3
|
||||
MAN_SUBDIR =
|
||||
MAN_LINKS = NO
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the XML output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_XML = NO
|
||||
XML_OUTPUT = xml
|
||||
XML_PROGRAMLISTING = YES
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the DOCBOOK output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_DOCBOOK = NO
|
||||
DOCBOOK_OUTPUT = docbook
|
||||
DOCBOOK_PROGRAMLISTING = NO
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options for the AutoGen Definitions output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_AUTOGEN_DEF = NO
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the Perl module output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_PERLMOD = NO
|
||||
PERLMOD_LATEX = NO
|
||||
PERLMOD_PRETTY = YES
|
||||
PERLMOD_MAKEVAR_PREFIX =
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the preprocessor
|
||||
#---------------------------------------------------------------------------
|
||||
ENABLE_PREPROCESSING = NO
|
||||
MACRO_EXPANSION = NO
|
||||
EXPAND_ONLY_PREDEF = NO
|
||||
SEARCH_INCLUDES = YES
|
||||
INCLUDE_PATH =
|
||||
INCLUDE_FILE_PATTERNS =
|
||||
PREDEFINED =
|
||||
EXPAND_AS_DEFINED =
|
||||
SKIP_FUNCTION_MACROS = YES
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to external references
|
||||
#---------------------------------------------------------------------------
|
||||
TAGFILES =
|
||||
GENERATE_TAGFILE =
|
||||
ALLEXTERNALS = NO
|
||||
EXTERNAL_GROUPS = YES
|
||||
EXTERNAL_PAGES = YES
|
||||
PERL_PATH = /usr/bin/perl
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the dot tool
|
||||
#---------------------------------------------------------------------------
|
||||
CLASS_DIAGRAMS = YES
|
||||
MSCGEN_PATH =
|
||||
DIA_PATH =
|
||||
HIDE_UNDOC_RELATIONS = YES
|
||||
HAVE_DOT = NO
|
||||
DOT_NUM_THREADS = 0
|
||||
DOT_FONTNAME = Helvetica
|
||||
DOT_FONTSIZE = 10
|
||||
DOT_FONTPATH =
|
||||
CLASS_GRAPH = YES
|
||||
COLLABORATION_GRAPH = YES
|
||||
GROUP_GRAPHS = YES
|
||||
UML_LOOK = NO
|
||||
UML_LIMIT_NUM_FIELDS = 10
|
||||
TEMPLATE_RELATIONS = NO
|
||||
INCLUDE_GRAPH = YES
|
||||
INCLUDED_BY_GRAPH = YES
|
||||
CALL_GRAPH = NO
|
||||
CALLER_GRAPH = NO
|
||||
GRAPHICAL_HIERARCHY = YES
|
||||
DIRECTORY_GRAPH = YES
|
||||
DOT_IMAGE_FORMAT = png
|
||||
INTERACTIVE_SVG = NO
|
||||
DOT_PATH =
|
||||
DOTFILE_DIRS =
|
||||
MSCFILE_DIRS =
|
||||
DIAFILE_DIRS =
|
||||
PLANTUML_JAR_PATH =
|
||||
PLANTUML_INCLUDE_PATH =
|
||||
DOT_GRAPH_MAX_NODES = 50
|
||||
MAX_DOT_GRAPH_DEPTH = 0
|
||||
DOT_TRANSPARENT = NO
|
||||
DOT_MULTI_TARGETS = NO
|
||||
GENERATE_LEGEND = YES
|
||||
DOT_CLEANUP = YES
|
|
@ -1,19 +1,19 @@
|
|||
Name: libwebsockets
|
||||
Version: 1.4
|
||||
Release: 48.gmaster_16fb0132%{?dist}
|
||||
Summary: Websocket Server Library
|
||||
Version: 2.3.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Websocket Server and Client Library
|
||||
|
||||
Group: System
|
||||
License: GPL
|
||||
URL: http://warmcat.com
|
||||
Group: System Environment/Libraries
|
||||
License: LGPLv2 with exceptions
|
||||
URL: https://libwebsockets.org
|
||||
Source0: %{name}-%{version}.tar.gz
|
||||
BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
|
||||
|
||||
BuildRequires: openssl-devel
|
||||
BuildRequires: openssl-devel cmake
|
||||
Requires: openssl
|
||||
|
||||
%description
|
||||
Webserver server library
|
||||
Webserver server and client library
|
||||
|
||||
%package devel
|
||||
Summary: Development files for libwebsockets
|
||||
|
@ -38,22 +38,30 @@ rm -rf $RPM_BUILD_ROOT
|
|||
cd build
|
||||
make install DESTDIR=$RPM_BUILD_ROOT
|
||||
|
||||
%post -p /sbin/ldconfig
|
||||
%postun -p /sbin/ldconfig
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
%files
|
||||
%defattr(-,root,root,-)
|
||||
%attr(755,root,root) /usr/bin/libwebsockets-test-server
|
||||
%attr(755,root,root) /usr/bin/libwebsockets-test-server-extpoll
|
||||
%attr(755,root,root) /usr/bin/libwebsockets-test-client
|
||||
%attr(755,root,root) /usr/bin/libwebsockets-test-ping
|
||||
%attr(755,root,root) /usr/bin/libwebsockets-test-echo
|
||||
%attr(755,root,root) /usr/bin/libwebsockets-test-fraggle
|
||||
%attr(755,root,root)
|
||||
/%{_libdir}/libwebsockets.so.5
|
||||
%attr(755,root,root)
|
||||
/usr/bin/libwebsockets-test-server
|
||||
/usr/bin/libwebsockets-test-server-extpoll
|
||||
/usr/bin/libwebsockets-test-client
|
||||
/usr/bin/libwebsockets-test-ping
|
||||
/usr/bin/libwebsockets-test-echo
|
||||
/usr/bin/libwebsockets-test-fraggle
|
||||
/usr/bin/libwebsockets-test-fuzxy
|
||||
/%{_libdir}/libwebsockets.so.11
|
||||
/%{_libdir}/libwebsockets.so
|
||||
%attr(755,root,root) /usr/share/libwebsockets-test-server
|
||||
/%{_libdir}/cmake/libwebsockets/LibwebsocketsConfig.cmake
|
||||
/%{_libdir}/cmake/libwebsockets/LibwebsocketsConfigVersion.cmake
|
||||
/%{_libdir}/cmake/libwebsockets/LibwebsocketsTargets.cmake
|
||||
/%{_libdir}/cmake/libwebsockets/LibwebsocketsTargets-release.cmake
|
||||
|
||||
/usr/share/libwebsockets-test-server
|
||||
%doc
|
||||
%files devel
|
||||
%defattr(-,root,root,-)
|
||||
|
@ -61,6 +69,23 @@ rm -rf $RPM_BUILD_ROOT
|
|||
%attr(755,root,root)
|
||||
/%{_libdir}/libwebsockets.a
|
||||
/%{_libdir}/pkgconfig/libwebsockets.pc
|
||||
/%{_libdir}/pkgconfig/libwebsockets_static.pc
|
||||
|
||||
%changelog
|
||||
* Fri Jul 28 2017 Andy Green <andy@warmcat.com> 2.3.0-1
|
||||
- MAJOR SONAMEBUMP APICHANGES Upstream 2.3.0 release
|
||||
|
||||
* Mon Mar 06 2017 Andy Green <andy@warmcat.com> 2.2.0-1
|
||||
- MAJOR SONAMEBUMP APICHANGES Upstream 2.2.0 release
|
||||
|
||||
* Thu Oct 06 2016 Andy Green <andy@warmcat.com> 2.1.0-1
|
||||
- MAJOR SONAMEBUMP APICHANGES Upstream 2.1.0 release
|
||||
|
||||
* Thu May 05 2016 Andy Green <andy@warmcat.com> 2.0.0-1
|
||||
- MAJOR SONAMEBUMP APICHANGES Upstream 2.0.0 release
|
||||
|
||||
* Tue Feb 16 2016 Andy Green <andy@warmcat.com> 1.7.0-1
|
||||
- MAJOR SONAMEBUMP APICHANGES Upstream 1.7.0 release
|
||||
|
||||
* Sun Jan 17 2016 Andrew Cooks <acooks@linux.com> 1.6.0-1
|
||||
- Bump version to 1.6.0
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#define LWS_INSTALL_DATADIR "${CMAKE_INSTALL_PREFIX}/share"
|
||||
|
||||
/* Define to 1 to use wolfSSL/CyaSSL as a replacement for OpenSSL.
|
||||
* LWS_OPENSSL_SUPPORT needs to be set also for this to work. */
|
||||
#cmakedefine USE_WOLFSSL
|
||||
|
@ -13,10 +15,25 @@
|
|||
/* Also define to 1 (in addition to USE_WOLFSSL) when using the
|
||||
(older) CyaSSL library */
|
||||
#cmakedefine USE_OLD_CYASSL
|
||||
#cmakedefine LWS_USE_BORINGSSL
|
||||
|
||||
#cmakedefine LWS_USE_MBEDTLS
|
||||
#cmakedefine LWS_USE_POLARSSL
|
||||
#cmakedefine LWS_WITH_ESP8266
|
||||
#cmakedefine LWS_WITH_ESP32
|
||||
|
||||
#cmakedefine LWS_WITH_PLUGINS
|
||||
#cmakedefine LWS_WITH_NO_LOGS
|
||||
|
||||
/* The Libwebsocket version */
|
||||
#cmakedefine LWS_LIBRARY_VERSION "${LWS_LIBRARY_VERSION}"
|
||||
|
||||
#define LWS_LIBRARY_VERSION_MAJOR ${LWS_LIBRARY_VERSION_MAJOR}
|
||||
#define LWS_LIBRARY_VERSION_MINOR ${LWS_LIBRARY_VERSION_MINOR}
|
||||
#define LWS_LIBRARY_VERSION_PATCH ${LWS_LIBRARY_VERSION_PATCH}
|
||||
/* LWS_LIBRARY_VERSION_NUMBER looks like 1005001 for e.g. version 1.5.1 */
|
||||
#define LWS_LIBRARY_VERSION_NUMBER (LWS_LIBRARY_VERSION_MAJOR*1000000)+(LWS_LIBRARY_VERSION_MINOR*1000)+LWS_LIBRARY_VERSION_PATCH
|
||||
|
||||
/* The current git commit hash that we're building from */
|
||||
#cmakedefine LWS_BUILD_HASH "${LWS_BUILD_HASH}"
|
||||
|
||||
|
@ -35,9 +52,18 @@
|
|||
/* Enable libev io loop */
|
||||
#cmakedefine LWS_USE_LIBEV
|
||||
|
||||
/* Enable libuv io loop */
|
||||
#cmakedefine LWS_USE_LIBUV
|
||||
|
||||
/* Enable libevent io loop */
|
||||
#cmakedefine LWS_USE_LIBEVENT
|
||||
|
||||
/* Build with support for ipv6 */
|
||||
#cmakedefine LWS_USE_IPV6
|
||||
|
||||
/* Build with support for UNIX domain socket */
|
||||
#cmakedefine LWS_USE_UNIX_SOCK
|
||||
|
||||
/* Build with support for HTTP2 */
|
||||
#cmakedefine LWS_USE_HTTP2
|
||||
|
||||
|
@ -62,4 +88,67 @@
|
|||
/* use SHA1() not internal libwebsockets_SHA1 */
|
||||
#cmakedefine LWS_SHA1_USE_OPENSSL_NAME
|
||||
|
||||
/* SSL server using ECDH certificate */
|
||||
#cmakedefine LWS_SSL_SERVER_WITH_ECDH_CERT
|
||||
#cmakedefine LWS_HAVE_SSL_CTX_set1_param
|
||||
#cmakedefine LWS_HAVE_X509_VERIFY_PARAM_set1_host
|
||||
|
||||
#cmakedefine LWS_HAVE_UV_VERSION_H
|
||||
|
||||
/* CGI apis */
|
||||
#cmakedefine LWS_WITH_CGI
|
||||
|
||||
/* whether the Openssl is recent enough, and / or built with, ecdh */
|
||||
#cmakedefine LWS_HAVE_OPENSSL_ECDH_H
|
||||
|
||||
/* HTTP Proxy support */
|
||||
#cmakedefine LWS_WITH_HTTP_PROXY
|
||||
|
||||
/* HTTP Ranges support */
|
||||
#cmakedefine LWS_WITH_RANGES
|
||||
|
||||
/* Http access log support */
|
||||
#cmakedefine LWS_WITH_ACCESS_LOG
|
||||
#cmakedefine LWS_WITH_SERVER_STATUS
|
||||
|
||||
#cmakedefine LWS_WITH_STATEFUL_URLDECODE
|
||||
|
||||
/* Maximum supported service threads */
|
||||
#define LWS_MAX_SMP ${LWS_MAX_SMP}
|
||||
|
||||
/* Lightweight JSON Parser */
|
||||
#cmakedefine LWS_WITH_LEJP
|
||||
|
||||
/* SMTP */
|
||||
#cmakedefine LWS_WITH_SMTP
|
||||
|
||||
/* OPTEE */
|
||||
#cmakedefine LWS_PLAT_OPTEE
|
||||
|
||||
/* ZIP FOPS */
|
||||
#cmakedefine LWS_WITH_ZIP_FOPS
|
||||
#cmakedefine LWS_HAVE_STDINT_H
|
||||
|
||||
#cmakedefine LWS_AVOID_SIGPIPE_IGN
|
||||
|
||||
#cmakedefine LWS_FALLBACK_GETHOSTBYNAME
|
||||
|
||||
#cmakedefine LWS_WITH_STATS
|
||||
#cmakedefine LWS_WITH_SOCKS5
|
||||
|
||||
#cmakedefine LWS_HAVE_SYS_CAPABILITY_H
|
||||
#cmakedefine LWS_HAVE_LIBCAP
|
||||
|
||||
#cmakedefine LWS_HAVE_ATOLL
|
||||
#cmakedefine LWS_HAVE__ATOI64
|
||||
#cmakedefine LWS_HAVE__STAT32I64
|
||||
|
||||
/* OpenSSL various APIs */
|
||||
|
||||
#cmakedefine LWS_HAVE_TLS_CLIENT_METHOD
|
||||
#cmakedefine LWS_HAVE_TLSV1_2_CLIENT_METHOD
|
||||
#cmakedefine LWS_HAVE_SSL_SET_INFO_CALLBACK
|
||||
|
||||
#cmakedefine LWS_HAS_INTPTR_T
|
||||
|
||||
${LWS_SIZEOFPTR_CODE}
|
||||
|
|
|
@ -75,6 +75,9 @@
|
|||
/* Define to 1 if you have the <sys/socket.h> header file. */
|
||||
#cmakedefine LWS_HAVE_SYS_SOCKET_H
|
||||
|
||||
/* Define to 1 if you have the <sys/sockio.h> header file. */
|
||||
#cmakedefine LWS_HAVE_SYS_SOCKIO_H
|
||||
|
||||
/* Define to 1 if you have the <sys/stat.h> header file. */
|
||||
#cmakedefine LWS_HAVE_SYS_STAT_H
|
||||
|
||||
|
@ -96,9 +99,14 @@
|
|||
/* Define to 1 if `vfork' works. */
|
||||
#cmakedefine LWS_HAVE_WORKING_VFORK
|
||||
|
||||
/* Define to 1 if execvpe() exists */
|
||||
#cmakedefine LWS_HAVE_EXECVPE
|
||||
|
||||
/* Define to 1 if you have the <zlib.h> header file. */
|
||||
#cmakedefine LWS_HAVE_ZLIB_H
|
||||
|
||||
#cmakedefine LWS_HAVE_GETLOADAVG
|
||||
|
||||
/* Define to the sub-directory in which libtool stores uninstalled libraries.
|
||||
*/
|
||||
#undef LT_OBJDIR // We're not using libtool
|
||||
|
|
10
lwsws/etc-logrotate.d-lwsws
Normal file
10
lwsws/etc-logrotate.d-lwsws
Normal file
|
@ -0,0 +1,10 @@
|
|||
/var/log/lwsws/*log {
|
||||
copytruncate
|
||||
missingok
|
||||
notifempty
|
||||
delaycompress
|
||||
postrotate
|
||||
/bin/systemctl reload lwsws.service > /dev/null 2>/dev/null || true
|
||||
endscript
|
||||
}
|
||||
|
16
lwsws/etc-lwsws-conf-EXAMPLE
Normal file
16
lwsws/etc-lwsws-conf-EXAMPLE
Normal file
|
@ -0,0 +1,16 @@
|
|||
# these are the server global settings
|
||||
# stuff related to vhosts should go in one
|
||||
# file per vhost in ../conf.d/
|
||||
|
||||
{
|
||||
"global": {
|
||||
"uid": "48",
|
||||
"gid": "48",
|
||||
"interface": "eth0",
|
||||
"count-threads": "1",
|
||||
"server-string": "lwsws",
|
||||
"ws-pingpong-secs": "200",
|
||||
"init-ssl": "yes"
|
||||
}
|
||||
}
|
||||
|
58
lwsws/etc-lwsws-conf.d-localhost-EXAMPLE
Normal file
58
lwsws/etc-lwsws-conf.d-localhost-EXAMPLE
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"vhosts": [ {
|
||||
"name": "localhost",
|
||||
"port": "7681",
|
||||
"interface": "lo",
|
||||
# "host-ssl-key": "/etc/pki/tls/private/libwebsockets.org.key",
|
||||
# "host-ssl-cert": "/etc/pki/tls/certs/libwebsockets.org.crt",
|
||||
# "host-ssl-ca": "/etc/pki/tls/certs/libwebsockets.org.cer",
|
||||
"access-log": "/var/log/lwsws/test-access-log",
|
||||
# "sts": "on",
|
||||
"mounts": [{
|
||||
"mountpoint": "/",
|
||||
"origin": "file://_lws_ddir_/libwebsockets-test-server",
|
||||
"default": "test.html",
|
||||
"cache-max-age": "60",
|
||||
"cache-reuse": "1",
|
||||
"cache-revalidate": "1",
|
||||
"cache-intermediaries": "0"
|
||||
}, {
|
||||
"mountpoint": "/server-status",
|
||||
"origin": "file://_lws_ddir_/libwebsockets-test-server/server-status",
|
||||
"default": "server-status.html"
|
||||
}, {
|
||||
"mountpoint": "/testcgi",
|
||||
"origin": "cgi://_lws_ddir_/libwebsockets-test-server/lws-cgi-test.sh"
|
||||
|
||||
}, {
|
||||
"mountpoint": "/formtest",
|
||||
"origin": "callback://protocol-post-demo"
|
||||
}],
|
||||
# which protocols are enabled for this vhost, and optional
|
||||
# vhost-specific config options for the protocol
|
||||
#
|
||||
"ws-protocols": [{
|
||||
"lws-meta": {
|
||||
"status": "ok"
|
||||
},
|
||||
"dumb-increment-protocol": {
|
||||
"status": "ok"
|
||||
},
|
||||
"lws-mirror-protocol": {
|
||||
"status": "ok"
|
||||
},
|
||||
"lws-status": {
|
||||
"status": "ok"
|
||||
},
|
||||
"protocol-post-demo": {
|
||||
"status": "ok"
|
||||
},
|
||||
"lws-server-status": {
|
||||
"status": "ok",
|
||||
"update-ms": "5000"
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
325
lwsws/main.c
Normal file
325
lwsws/main.c
Normal file
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* libwebsockets web server application
|
||||
*
|
||||
* Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* The person who associated a work with this deed has dedicated
|
||||
* the work to the public domain by waiving all of his or her rights
|
||||
* to the work worldwide under copyright law, including all related
|
||||
* and neighboring rights, to the extent allowed by law. You can copy,
|
||||
* modify, distribute and perform the work, even for commercial purposes,
|
||||
* all without asking permission.
|
||||
*
|
||||
* The test apps are intended to be adapted for use in your code, which
|
||||
* may be proprietary. So unlike the library itself, they are licensed
|
||||
* Public Domain.
|
||||
*/
|
||||
#include "lws_config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
#ifndef _WIN32
|
||||
#include <dirent.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#else
|
||||
#include <io.h>
|
||||
#include "gettimeofday.h"
|
||||
|
||||
int fork(void)
|
||||
{
|
||||
fprintf(stderr, "Sorry Windows doesn't support fork().\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "../lib/libwebsockets.h"
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
static struct lws_context *context;
|
||||
static char config_dir[128];
|
||||
static int opts = 0, do_reload = 1;
|
||||
static uv_loop_t loop;
|
||||
static uv_signal_t signal_outer;
|
||||
static int pids[32];
|
||||
|
||||
#define LWSWS_CONFIG_STRING_SIZE (32 * 1024)
|
||||
|
||||
static const struct lws_extension exts[] = {
|
||||
#if !defined(LWS_NO_EXTENSIONS)
|
||||
{
|
||||
"permessage-deflate",
|
||||
lws_extension_callback_pm_deflate,
|
||||
"permessage-deflate"
|
||||
},
|
||||
#endif
|
||||
{ NULL, NULL, NULL /* terminator */ }
|
||||
};
|
||||
|
||||
static const char * const plugin_dirs[] = {
|
||||
INSTALL_DATADIR"/libwebsockets-test-server/plugins/",
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "debug", required_argument, NULL, 'd' },
|
||||
{ "configdir", required_argument, NULL, 'c' },
|
||||
{ NULL, 0, 0, 0 }
|
||||
};
|
||||
|
||||
void signal_cb(uv_signal_t *watcher, int signum)
|
||||
{
|
||||
switch (watcher->signum) {
|
||||
case SIGTERM:
|
||||
case SIGINT:
|
||||
break;
|
||||
|
||||
case SIGHUP:
|
||||
if (lws_context_is_deprecated(context))
|
||||
return;
|
||||
lwsl_notice("Dropping listen sockets\n");
|
||||
lws_context_deprecate(context, NULL);
|
||||
return;
|
||||
|
||||
default:
|
||||
signal(SIGABRT, SIG_DFL);
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
lwsl_err("Signal %d caught\n", watcher->signum);
|
||||
lws_libuv_stop(context);
|
||||
}
|
||||
|
||||
static int
|
||||
context_creation(void)
|
||||
{
|
||||
int cs_len = LWSWS_CONFIG_STRING_SIZE - 1;
|
||||
struct lws_context_creation_info info;
|
||||
char *cs, *config_strings;
|
||||
|
||||
cs = config_strings = malloc(LWSWS_CONFIG_STRING_SIZE);
|
||||
if (!config_strings) {
|
||||
lwsl_err("Unable to allocate config strings heap\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
|
||||
info.external_baggage_free_on_destroy = config_strings;
|
||||
info.max_http_header_pool = 16;
|
||||
info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 |
|
||||
LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
|
||||
LWS_SERVER_OPTION_LIBUV;
|
||||
|
||||
info.plugin_dirs = plugin_dirs;
|
||||
lwsl_notice("Using config dir: \"%s\"\n", config_dir);
|
||||
|
||||
/*
|
||||
* first go through the config for creating the outer context
|
||||
*/
|
||||
if (lwsws_get_config_globals(&info, config_dir, &cs, &cs_len))
|
||||
goto init_failed;
|
||||
|
||||
context = lws_create_context(&info);
|
||||
if (context == NULL) {
|
||||
lwsl_err("libwebsocket init failed\n");
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
lws_uv_sigint_cfg(context, 1, signal_cb);
|
||||
lws_uv_initloop(context, &loop, 0);
|
||||
|
||||
/*
|
||||
* then create the vhosts... protocols are entirely coming from
|
||||
* plugins, so we leave it NULL
|
||||
*/
|
||||
|
||||
info.extensions = exts;
|
||||
|
||||
if (lwsws_get_config_vhosts(context, &info, config_dir,
|
||||
&cs, &cs_len))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
|
||||
init_failed:
|
||||
free(config_strings);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* root-level sighup handler
|
||||
*/
|
||||
|
||||
static void
|
||||
reload_handler(int signum)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
int m;
|
||||
|
||||
switch (signum) {
|
||||
|
||||
case SIGHUP: /* reload */
|
||||
fprintf(stderr, "root process receives reload\n");
|
||||
if (!do_reload) {
|
||||
fprintf(stderr, "passing HUP to child processes\n");
|
||||
for (m = 0; m < ARRAY_SIZE(pids); m++)
|
||||
if (pids[m])
|
||||
kill(pids[m], SIGHUP);
|
||||
sleep(1);
|
||||
}
|
||||
do_reload = 1;
|
||||
break;
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
case SIGKILL:
|
||||
fprintf(stderr, "killing service processes\n");
|
||||
for (m = 0; m < ARRAY_SIZE(pids); m++)
|
||||
if (pids[m])
|
||||
kill(pids[m], SIGTERM);
|
||||
exit(0);
|
||||
}
|
||||
#else
|
||||
// kill() implementation needed for WIN32
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int n = 0, debug_level = 7;
|
||||
#ifndef _WIN32
|
||||
int m;
|
||||
int status, syslog_options = LOG_PID | LOG_PERROR;
|
||||
#endif
|
||||
|
||||
strcpy(config_dir, "/etc/lwsws");
|
||||
while (n >= 0) {
|
||||
n = getopt_long(argc, argv, "hd:c:", options, NULL);
|
||||
if (n < 0)
|
||||
continue;
|
||||
switch (n) {
|
||||
case 'd':
|
||||
debug_level = atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
strncpy(config_dir, optarg, sizeof(config_dir) - 1);
|
||||
config_dir[sizeof(config_dir) - 1] = '\0';
|
||||
break;
|
||||
case 'h':
|
||||
fprintf(stderr, "Usage: lwsws [-c <config dir>] "
|
||||
"[-d <log bitfield>] [--help]\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
#ifndef _WIN32
|
||||
/*
|
||||
* We leave our original process up permanently, because that
|
||||
* suits systemd.
|
||||
*
|
||||
* Otherwise we get into problems when reload spawns new processes and
|
||||
* the original one dies randomly.
|
||||
*/
|
||||
|
||||
signal(SIGHUP, reload_handler);
|
||||
signal(SIGINT, reload_handler);
|
||||
|
||||
fprintf(stderr, "Root process is %u\n", getpid());
|
||||
|
||||
while (1) {
|
||||
if (do_reload) {
|
||||
do_reload = 0;
|
||||
n = fork();
|
||||
if (n == 0) /* new */
|
||||
break;
|
||||
/* old */
|
||||
if (n > 0)
|
||||
for (m = 0; m < ARRAY_SIZE(pids); m++)
|
||||
if (!pids[m]) {
|
||||
// fprintf(stderr, "added child pid %d\n", n);
|
||||
pids[m] = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#ifndef _WIN32
|
||||
sleep(2);
|
||||
|
||||
n = waitpid(-1, &status, WNOHANG);
|
||||
if (n > 0)
|
||||
for (m = 0; m < ARRAY_SIZE(pids); m++)
|
||||
if (pids[m] == n) {
|
||||
// fprintf(stderr, "reaped child pid %d\n", pids[m]);
|
||||
pids[m] = 0;
|
||||
break;
|
||||
}
|
||||
#else
|
||||
// !!! implemenation needed
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
/* child process */
|
||||
|
||||
#ifndef _WIN32
|
||||
/* we will only try to log things according to our debug_level */
|
||||
setlogmask(LOG_UPTO (LOG_DEBUG));
|
||||
openlog("lwsws", syslog_options, LOG_DAEMON);
|
||||
#endif
|
||||
|
||||
lws_set_log_level(debug_level, lwsl_emit_syslog);
|
||||
|
||||
lwsl_notice("lwsws libwebsockets web server - license CC0 + LGPL2.1\n");
|
||||
lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
|
||||
|
||||
#if (UV_VERSION_MAJOR > 0) // Travis...
|
||||
uv_loop_init(&loop);
|
||||
#else
|
||||
fprintf(stderr, "Your libuv is too old!\n");
|
||||
return 0;
|
||||
#endif
|
||||
uv_signal_init(&loop, &signal_outer);
|
||||
uv_signal_start(&signal_outer, signal_cb, SIGINT);
|
||||
uv_signal_start(&signal_outer, signal_cb, SIGHUP);
|
||||
|
||||
if (context_creation()) {
|
||||
lwsl_err("Context creation failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
lws_libuv_run(context, 0);
|
||||
|
||||
uv_signal_stop(&signal_outer);
|
||||
lws_context_destroy(context);
|
||||
|
||||
#if (UV_VERSION_MAJOR > 0) // Travis...
|
||||
lws_close_all_handles_in_loop(&loop);
|
||||
n = 0;
|
||||
while (n++ < 4096 && uv_loop_close(&loop))
|
||||
uv_run(&loop, UV_RUN_NOWAIT);
|
||||
#endif
|
||||
|
||||
lws_context_destroy2(context);
|
||||
|
||||
fprintf(stderr, "lwsws exited cleanly\n");
|
||||
|
||||
#ifndef _WIN32
|
||||
closelog();
|
||||
#endif
|
||||
|
||||
context = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
13
lwsws/usr-lib-systemd-system-lwsws.service
Normal file
13
lwsws/usr-lib-systemd-system-lwsws.service
Normal file
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=Libwebsockets Web Server
|
||||
After=syslog.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/lwsws
|
||||
ExecReload=/usr/bin/kill -HUP $MAINPID
|
||||
ExecStop=/usr/bin/killall lwsws
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
16
mainpage.md
Normal file
16
mainpage.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
##Libwebsockets API introduction
|
||||
|
||||
Libwebsockets covers a lot of interesting features for people making embedded servers or clients
|
||||
|
||||
- http(s) serving and client operation
|
||||
- ws(s) serving and client operation
|
||||
- http(s) apis for file transfer and upload
|
||||
- http POST form handling (including multipart)
|
||||
- cookie-based sessions
|
||||
- account management (including registration, email verification, lost pw etc)
|
||||
- strong ssl PFS support (A+ on SSLlabs test)
|
||||
|
||||
You can browse by api category <a href="modules.html">here</a>
|
||||
|
||||
A collection of READMEs for build, coding, lwsws etc are <a href="pages.html">here</a>
|
||||
|
21
module.json
Normal file
21
module.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "websockets",
|
||||
"version": "1.6.0",
|
||||
"description": "Libwebsockets",
|
||||
"keywords": [
|
||||
"lws",
|
||||
"libwebsockets",
|
||||
"websockets",
|
||||
"ws"
|
||||
],
|
||||
"author": "Andy Green <andy@warmcat.com>",
|
||||
"homepage": "https://libwebsockets.org",
|
||||
"license": "LGPL2.1-SLE",
|
||||
|
||||
"extraIncludes": [ "build/frdm-k64f-gcc/generated/include" ],
|
||||
"dependencies": {
|
||||
"mbed-drivers": "",
|
||||
"sal-stack-lwip": "",
|
||||
"sockets": ""
|
||||
}
|
||||
}
|
73
plugin-standalone/CMakeLists.txt
Normal file
73
plugin-standalone/CMakeLists.txt
Normal file
|
@ -0,0 +1,73 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if(NOT DEFINED CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type")
|
||||
endif()
|
||||
|
||||
# This shows one way to build a standalone plugin
|
||||
# outside of lws itself
|
||||
|
||||
project(lws-protocol-plugin-example C)
|
||||
|
||||
set(PACKAGE "lws-protocol-plugin-example")
|
||||
set(CPACK_PACKAGE_NAME "${PACKAGE}")
|
||||
set(CPACK_PACKAGE_VERSION "0.1")
|
||||
set(CPACK_PACKAGE_VENDOR "andy@warmcat.com")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PACKAGE} ${PACKAGE_VERSION}")
|
||||
set(SOVERSION "1")
|
||||
set(VERSION "0.1")
|
||||
|
||||
set(PLUGIN_NAME "protocol_example_standalone")
|
||||
# space-separated list of sources
|
||||
set(PLUGIN_SRCS protocol_example_standalone.c)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
|
||||
message(STATUS "CMAKE_TOOLCHAIN_FILE='${CMAKE_TOOLCHAIN_FILE}'")
|
||||
|
||||
# Try to find the current Git hash.
|
||||
find_package(Git)
|
||||
if(GIT_EXECUTABLE)
|
||||
execute_process(
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMAND "${GIT_EXECUTABLE}" describe
|
||||
OUTPUT_VARIABLE GIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
execute_process(
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMAND "whoami"
|
||||
OUTPUT_VARIABLE GIT_USER
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
execute_process(
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMAND "hostname"
|
||||
OUTPUT_VARIABLE GIT_HOST
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
string(REGEX REPLACE "([^\\])[\\]([^\\])" "\\1\\\\\\\\\\2" GIT_USER ${GIT_USER})
|
||||
set(LWS_BUILD_HASH ${GIT_USER}@${GIT_HOST}-${GIT_HASH})
|
||||
message("Git commit hash: ${LWS_BUILD_HASH}")
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}")
|
||||
|
||||
source_group("Headers Private" FILES ${PLUGIN_HDR})
|
||||
source_group("Sources" FILES ${PLUGIN_SRCS})
|
||||
add_library(${PLUGIN_NAME} SHARED ${PLUGIN_SRCS} ${PLUGIN_HDR})
|
||||
|
||||
target_link_libraries(${PLUGIN_NAME} -lwebsockets)
|
||||
|
||||
# Set test app specific defines.
|
||||
set_property(TARGET ${PLUGIN_NAME}
|
||||
PROPERTY COMPILE_DEFINITIONS
|
||||
INSTALL_DATADIR="${CMAKE_INSTALL_PREFIX}/plugins"
|
||||
)
|
||||
|
||||
list(APPEND PLUGINS_LIST ${PLUGIN_NAME})
|
||||
|
||||
install(TARGETS ${PLUGINS_LIST}
|
||||
PERMISSIONS OWNER_WRITE OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_READ GROUP_READ WORLD_READ
|
||||
DESTINATION share/libwebsockets-test-server/plugins
|
||||
COMPONENT plugins)
|
152
plugin-standalone/protocol_example_standalone.c
Normal file
152
plugin-standalone/protocol_example_standalone.c
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* ws protocol handler plugin for "dumb increment"
|
||||
*
|
||||
* Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* The person who associated a work with this deed has dedicated
|
||||
* the work to the public domain by waiving all of his or her rights
|
||||
* to the work worldwide under copyright law, including all related
|
||||
* and neighboring rights, to the extent allowed by law. You can copy,
|
||||
* modify, distribute and perform the work, even for commercial purposes,
|
||||
* all without asking permission.
|
||||
*
|
||||
* These test plugins are intended to be adapted for use in your code, which
|
||||
* may be proprietary. So unlike the library itself, they are licensed
|
||||
* Public Domain.
|
||||
*
|
||||
* This is a copy of dumb_increment adapted slightly to serve as the
|
||||
* "example-standalone-protocol", to show how to build protocol plugins
|
||||
* outside the library easily.
|
||||
*/
|
||||
|
||||
#define LWS_DLL
|
||||
#define LWS_INTERNAL
|
||||
#include "../lib/libwebsockets.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
struct per_vhost_data__dumb_increment {
|
||||
uv_timer_t timeout_watcher;
|
||||
struct lws_context *context;
|
||||
struct lws_vhost *vhost;
|
||||
const struct lws_protocols *protocol;
|
||||
};
|
||||
|
||||
struct per_session_data__dumb_increment {
|
||||
int number;
|
||||
};
|
||||
|
||||
static void
|
||||
uv_timeout_cb_dumb_increment(uv_timer_t *w
|
||||
#if UV_VERSION_MAJOR == 0
|
||||
, int status
|
||||
#endif
|
||||
)
|
||||
{
|
||||
struct per_vhost_data__dumb_increment *vhd = lws_container_of(w,
|
||||
struct per_vhost_data__dumb_increment, timeout_watcher);
|
||||
lws_callback_on_writable_all_protocol_vhost(vhd->vhost, vhd->protocol);
|
||||
}
|
||||
|
||||
static int
|
||||
callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
|
||||
void *user, void *in, size_t len)
|
||||
{
|
||||
struct per_session_data__dumb_increment *pss =
|
||||
(struct per_session_data__dumb_increment *)user;
|
||||
struct per_vhost_data__dumb_increment *vhd =
|
||||
(struct per_vhost_data__dumb_increment *)
|
||||
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
|
||||
lws_get_protocol(wsi));
|
||||
unsigned char buf[LWS_PRE + 512];
|
||||
unsigned char *p = &buf[LWS_PRE];
|
||||
int n, m;
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_PROTOCOL_INIT:
|
||||
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
|
||||
lws_get_protocol(wsi),
|
||||
sizeof(struct per_vhost_data__dumb_increment));
|
||||
vhd->context = lws_get_context(wsi);
|
||||
vhd->protocol = lws_get_protocol(wsi);
|
||||
vhd->vhost = lws_get_vhost(wsi);
|
||||
uv_timer_init(lws_uv_getloop(vhd->context, 0),
|
||||
&vhd->timeout_watcher);
|
||||
uv_timer_start(&vhd->timeout_watcher,
|
||||
uv_timeout_cb_dumb_increment, 50, 50);
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_PROTOCOL_DESTROY:
|
||||
if (!vhd)
|
||||
break;
|
||||
uv_timer_stop(&vhd->timeout_watcher);
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_ESTABLISHED:
|
||||
pss->number = 0;
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_SERVER_WRITEABLE:
|
||||
n = sprintf((char *)p, "%d", pss->number++);
|
||||
m = lws_write(wsi, p, n, LWS_WRITE_TEXT);
|
||||
if (m < n) {
|
||||
lwsl_err("ERROR %d writing to di socket\n", n);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_RECEIVE:
|
||||
if (len < 6)
|
||||
break;
|
||||
if (strcmp((const char *)in, "reset\n") == 0)
|
||||
pss->number = 0;
|
||||
if (strcmp((const char *)in, "closeme\n") == 0) {
|
||||
lwsl_notice("dumb_inc: closing as requested\n");
|
||||
lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY,
|
||||
(unsigned char *)"seeya", 5);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct lws_protocols protocols[] = {
|
||||
{
|
||||
"example-standalone-protocol",
|
||||
callback_dumb_increment,
|
||||
sizeof(struct per_session_data__dumb_increment),
|
||||
10, /* rx buf size must be >= permessage-deflate rx size */
|
||||
},
|
||||
};
|
||||
|
||||
LWS_VISIBLE int
|
||||
init_protocol_example_standalone(struct lws_context *context,
|
||||
struct lws_plugin_capability *c)
|
||||
{
|
||||
if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
|
||||
lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
|
||||
c->api_magic);
|
||||
return 1;
|
||||
}
|
||||
|
||||
c->protocols = protocols;
|
||||
c->count_protocols = ARRAY_SIZE(protocols);
|
||||
c->extensions = NULL;
|
||||
c->count_extensions = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_VISIBLE int
|
||||
destroy_protocol_example_standalone(struct lws_context *context)
|
||||
{
|
||||
return 0;
|
||||
}
|
5
plugins/generic-sessions/assets/admin-login.html
Normal file
5
plugins/generic-sessions/assets/admin-login.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
This is an example destination that will appear after successful Admin login.
|
||||
|
||||
This URL cannot be served if you're not logged in as admin.
|
||||
</html>
|
3
plugins/generic-sessions/assets/failed-login.html
Normal file
3
plugins/generic-sessions/assets/failed-login.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<html>
|
||||
This is an example destination that will appear after a failed login
|
||||
</html>
|
164
plugins/generic-sessions/assets/index.html
Normal file
164
plugins/generic-sessions/assets/index.html
Normal file
|
@ -0,0 +1,164 @@
|
|||
<html>
|
||||
<head>
|
||||
<script src="/lws-common.js" nonce="lwscaro"></script>
|
||||
<script src="lwsgs.js" nonce="lwscaro"></script>
|
||||
<style>
|
||||
.body { font-size: 12 }
|
||||
.gstitle { font-size: 18 }
|
||||
.group1 { vertical-align:middle;text-align:center;background:#f0f0e0;
|
||||
padding:12px; -webkit-border-radius:10px;
|
||||
-moz-border-radius:10px;border-radius:10px; }
|
||||
.group2 { vertical-align:middle; font-size: 22;text-align:center;
|
||||
margin:auto; align:center;
|
||||
background-color: rgba(255, 255, 255, 0.8); padding:12px;
|
||||
display:inline-block; -webkit-border-radius:10px;
|
||||
-moz-border-radius:10px; border-radius:10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-image:url(seats.jpg)">
|
||||
<table style="width:100%;height:100%;transition: max-height 2s;">
|
||||
<tr>
|
||||
<td style="vertical-align:top;text-align:left;width=200px">
|
||||
<img src="lwsgs-logo.png">
|
||||
</td>
|
||||
<td style="vertical-align:top;float:right">
|
||||
<div id=lwsgs style="zIndex: 1000;text-align:right;background-color: rgba(255, 255, 255, 0.8);"></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr><td colspan=2 style="height:99%;vertical-align:middle;">
|
||||
<table style="text-align:center;width:100%"><tr>
|
||||
<td style="margin:auto;align:center">
|
||||
<span id="nolog" class="group2" style="display:none;">
|
||||
This is a demo application for lws generic-sessions.<br><br>
|
||||
It's a simple messageboard.<br><br>
|
||||
What's interesting about it is there is <b>no serverside scripting</b>,<br>
|
||||
instead client js makes a wss:// connection back to the server<br>
|
||||
and then reacts to JSON from the ws protocol. Sessions stuff is <br>
|
||||
handled by lws generic sessions, making the <a href="https://github.com/warmcat/libwebsockets/blob/master/plugins/generic-sessions/protocol_lws_messageboard.c">actual<br>
|
||||
test application</a> <a href="https://github.com/warmcat/libwebsockets/blob/master/plugins/generic-sessions/index.html">very small</a>.<br><br>
|
||||
And because it's natively websocket, it's naturally connected<br>
|
||||
for dynamic events and easy to maintain.
|
||||
<br><br>
|
||||
Register / Login at the top right to see and create new messages.
|
||||
</span>
|
||||
<span id="logged" class="group2" style="display:none">
|
||||
<div id="newmsg">
|
||||
<form action="msg" method="post" target="hidden">
|
||||
New message<br>
|
||||
<textarea id="msg" placeholder="type your message here" cols="40" rows="5" name="msg"></textarea><br>
|
||||
<input type="submit" id="send" name="send" disabled=1>
|
||||
</form>
|
||||
</div>
|
||||
</span>
|
||||
<div id="dmessages">
|
||||
<span id="messages" ></span>
|
||||
</div>
|
||||
<span id="debug" class="group2"></span>
|
||||
</td></tr></table>
|
||||
</td></tr>
|
||||
</table>
|
||||
</form>
|
||||
<iframe name="hidden" style="display:none"></iframe>
|
||||
<script nonce="lwscaro">lwsgs_initial();
|
||||
document.getElementById("nolog").style.display = !!lwsgs_user ? "none" : "inline-block";
|
||||
document.getElementById("logged").style.display = !lwsgs_user ? "none" : "inline-block";
|
||||
|
||||
document.getElementById("msg").onkeyup = mupd;
|
||||
document.getElementById("msg").onchange = mupd;
|
||||
|
||||
var ws;
|
||||
|
||||
function mb_format(s)
|
||||
{
|
||||
var r = "", n, wos = 0;
|
||||
|
||||
for (n = 0; n < s.length; n++) {
|
||||
if (s[n] == ' ')
|
||||
wos = 0;
|
||||
else {
|
||||
wos++;
|
||||
if (wos == 40) {
|
||||
wos = 0;
|
||||
r = r + ' ';
|
||||
}
|
||||
}
|
||||
if (s[n] == '<') {
|
||||
r = r + "<";
|
||||
continue;
|
||||
}
|
||||
if (s[n] == '\n') {
|
||||
r = r + "<br>";
|
||||
continue;
|
||||
}
|
||||
|
||||
r = r + s[n];
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
function add_div(n, m)
|
||||
{
|
||||
var q = document.getElementById(n);
|
||||
var d = new Date(m.time * 1000);
|
||||
|
||||
q.innerHTML = "<br><div style=\"margin:2px\" class=\"group2\"><table style=\"table-layout: fixed;\"><tr><td>" +
|
||||
"<img src=\"https://www.gravatar.com/avatar/" + md5(m.email) +
|
||||
"?d=identicon\"><br>" +
|
||||
"<b>" + lwsgs_san(m.username) + "</b><br>" +
|
||||
"<span style=\"font-size:8pt\">" + d.toDateString() +
|
||||
"<br>" + d.toTimeString() + "</span><br>" +
|
||||
"IP: " + lwsgs_san(m.ip) +
|
||||
"</td><td style=\"display:inline-block;vertical-align:top;word-wrap:break-word;\"><span>" +
|
||||
mb_format(m.content) +
|
||||
"</span></td></tr></table></div><br>" + q.innerHTML;
|
||||
}
|
||||
|
||||
function get_appropriate_ws_url()
|
||||
{
|
||||
var pcol;
|
||||
var u = document.URL;
|
||||
|
||||
if (u.substring(0, 5) == "https") {
|
||||
pcol = "wss://";
|
||||
u = u.substr(8);
|
||||
} else {
|
||||
pcol = "ws://";
|
||||
if (u.substring(0, 4) == "http")
|
||||
u = u.substr(7);
|
||||
}
|
||||
u = u.split('/');
|
||||
|
||||
return pcol + u[0] + "/xxx";
|
||||
}
|
||||
|
||||
if (lwsgs_user) {
|
||||
if (typeof MozWebSocket != "undefined")
|
||||
ws = new MozWebSocket(get_appropriate_ws_url(),
|
||||
"protocol-lws-messageboard");
|
||||
else
|
||||
ws = new WebSocket(get_appropriate_ws_url(),
|
||||
"protocol-lws-messageboard");
|
||||
|
||||
try {
|
||||
ws.onopen = function() {
|
||||
document.getElementById("debug").textContent = "ws opened";
|
||||
}
|
||||
ws.onmessage =function got_packet(msg) {
|
||||
add_div("messages", JSON.parse(msg.data));
|
||||
}
|
||||
ws.onclose = function(){
|
||||
}
|
||||
} catch(exception) {
|
||||
alert('<p>Error' + exception);
|
||||
}
|
||||
}
|
||||
|
||||
function mupd()
|
||||
{
|
||||
document.getElementById("send").disabled = !document.getElementById("msg").value;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
BIN
plugins/generic-sessions/assets/lwsgs-logo.png
Normal file
BIN
plugins/generic-sessions/assets/lwsgs-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue