Compare commits
727 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c528e4db03 | ||
|
|
848ead7683 | ||
|
|
1b8419b9b5 | ||
|
|
900420719e | ||
|
|
ef7d1ba964 | ||
|
|
ab1654c784 | ||
|
|
24bad8e4bb | ||
|
|
f6d8200bc8 | ||
|
|
aef33548b3 | ||
|
|
56ecdcce82 | ||
|
|
b9b59ff95f | ||
|
|
aa9dd3e5c6 | ||
|
|
30bd659149 | ||
|
|
3286ca49c8 | ||
|
|
7700d12178 | ||
|
|
557b7e3f2e | ||
|
|
3e1e9146e1 | ||
|
|
40d8d1aac1 | ||
|
|
1457b8efba | ||
|
|
3821e80ac8 | ||
|
|
d40bb459ea | ||
|
|
edf04f07c9 | ||
|
|
2bdea23986 | ||
|
|
6be0c4b694 | ||
|
|
2a272f465c | ||
|
|
a8ecd32b45 | ||
|
|
20c42a175d | ||
|
|
d6c3ffde87 | ||
|
|
177e0ae6a3 | ||
|
|
dab1b4d256 | ||
|
|
3aba12631b | ||
|
|
cfee6430d4 | ||
|
|
6b96df1cec | ||
|
|
deecfa5047 | ||
|
|
6ca3f01038 | ||
|
|
d89f589f2b | ||
|
|
8c1895e04f | ||
|
|
c3d0132a98 | ||
|
|
d955279d17 | ||
|
|
76266dce0d | ||
|
|
5694206b35 | ||
|
|
4293a18726 | ||
|
|
9865af4174 | ||
|
|
c8c109093d | ||
|
|
b5784d6025 | ||
|
|
31bdb8909a | ||
|
|
ee921c22ae | ||
|
|
172ffd0434 | ||
|
|
d9b4406c09 | ||
|
|
8ac0e85df4 | ||
|
|
249ccd5e3c | ||
|
|
727d76d43e | ||
|
|
44770b7c07 | ||
|
|
b5d571c75f | ||
|
|
da56d83716 | ||
|
|
f777e213ce | ||
|
|
79906f5a7d | ||
|
|
54074e76b5 | ||
|
|
c2ea8db683 | ||
|
|
744671a518 | ||
|
|
7f5b127bbc | ||
|
|
89eff0c628 | ||
|
|
177aeebb1c | ||
|
|
e0e6bde4a5 | ||
|
|
e6b145716f | ||
|
|
f958342704 | ||
|
|
5a1f738505 | ||
|
|
8147cdf40c | ||
|
|
6afc64ac39 | ||
|
|
61b8e3598b | ||
|
|
3bb445ff40 | ||
|
|
59a8037599 | ||
|
|
09cb5fad59 | ||
|
|
ee8e45ecf7 | ||
|
|
339868838d | ||
|
|
c5579fa349 | ||
|
|
d4a859547c | ||
|
|
779aa240d2 | ||
|
|
9aaad00383 | ||
|
|
6422f7ef78 | ||
|
|
3e51584b3c | ||
|
|
4ff8a53084 | ||
|
|
ccb73437c4 | ||
|
|
b43114f61b | ||
|
|
9bd87ab511 | ||
|
|
b6ea55f7ef | ||
|
|
5f18cec97d | ||
|
|
3ba921ec0e | ||
|
|
e313da254c | ||
|
|
8660cf0d74 | ||
|
|
e0bfdff152 | ||
|
|
c27aed3603 | ||
|
|
cdc6002f0e | ||
|
|
4cf3d9c1a2 | ||
|
|
0473e08e21 | ||
|
|
346faa3de2 | ||
|
|
5ec6dca64d | ||
|
|
1a6b49cfb8 | ||
|
|
c7b75df390 | ||
|
|
f97c09291b | ||
|
|
8f2a5b403c | ||
|
|
ea41a90eae | ||
|
|
b1dd29e64d | ||
|
|
46e47c47ac | ||
|
|
b7bf431f0d | ||
|
|
7f432122d9 | ||
|
|
e7be8c6097 | ||
|
|
adf56a16e1 | ||
|
|
11204b8a2b | ||
|
|
cade27dceb | ||
|
|
5597d28e59 | ||
|
|
0502fec432 | ||
|
|
a6714fc2de | ||
|
|
1219dadbd5 | ||
|
|
77b995f5ed | ||
|
|
2f53b9ff68 | ||
|
|
d84d045849 | ||
|
|
d8e72dfe48 | ||
|
|
88b9f7a74c | ||
|
|
6a19501214 | ||
|
|
ba30b56c5f | ||
|
|
a375e4b2ce | ||
|
|
44fd71e17f | ||
|
|
b41c378de9 | ||
|
|
4ce6b308b3 | ||
|
|
72c6e7d1c5 | ||
|
|
7ca3f22e77 | ||
|
|
59790dafef | ||
|
|
7227cae6ef | ||
|
|
03bba51c1e | ||
|
|
0637050cbc | ||
|
|
7f58a68c5a | ||
|
|
8120b0397c | ||
|
|
fbe42b7dce | ||
|
|
def24e34ad | ||
|
|
593253e6a3 | ||
|
|
56dd4cb8b5 | ||
|
|
10c6f67500 | ||
|
|
5c1f968afa | ||
|
|
aaaf437d62 | ||
|
|
a8a866b5f6 | ||
|
|
aeb8f02269 | ||
|
|
45767360ab | ||
|
|
3563aa2e4d | ||
|
|
843d5b625a | ||
|
|
0f8ede85ca | ||
|
|
9267d7cae2 | ||
|
|
dade5a981b | ||
|
|
f873cba27e | ||
|
|
108dec53a5 | ||
|
|
e9592ed400 | ||
|
|
4c230522a2 | ||
|
|
16bd19c6dc | ||
|
|
b4b6d49d34 | ||
|
|
9d5f16a889 | ||
|
|
51c492b842 | ||
|
|
d3380228ac | ||
|
|
ad47bf5da3 | ||
|
|
76e8631e31 | ||
|
|
f688b9112d | ||
|
|
18d3f5d25d | ||
|
|
440cf2c983 | ||
|
|
adeb09576a | ||
|
|
fd52bcf8cd | ||
|
|
c2696bba00 | ||
|
|
fef9d57483 | ||
|
|
c250692ef0 | ||
|
|
2a6024e196 | ||
|
|
835662b3c9 | ||
|
|
6b5b880ab6 | ||
|
|
3de23d4206 | ||
|
|
ea82f2a8cc | ||
|
|
b5ad7642bc | ||
|
|
0fbe84e9f0 | ||
|
|
8063889d23 | ||
|
|
6e1ac8e173 | ||
|
|
1b0bb5ea19 | ||
|
|
4ce51261f1 | ||
|
|
6e34d9b73e | ||
|
|
6e01637400 | ||
|
|
9a96798f9f | ||
|
|
c9db6f87d1 | ||
|
|
567ce84e1e | ||
|
|
cde7ac7e52 | ||
|
|
b2f0d17e94 | ||
|
|
dae6be14b7 | ||
|
|
20cec857d2 | ||
|
|
739f694cfe | ||
|
|
84779002fb | ||
|
|
df926fb60d | ||
|
|
a2c7c5a516 | ||
|
|
1861379d43 | ||
|
|
408a344aae | ||
|
|
e9c1ed70d0 | ||
|
|
916dcca2b0 | ||
|
|
31e81bab17 | ||
|
|
6a5170c341 | ||
|
|
42bff8093c | ||
|
|
d1df94f284 | ||
|
|
9cf1744291 | ||
|
|
f0545c8c9a | ||
|
|
ef9ed87d30 | ||
|
|
58ffd92a34 | ||
|
|
418facc7e0 | ||
|
|
7f3c953e14 | ||
|
|
addab1fa2a | ||
|
|
39343ce957 | ||
|
|
7389cec78f | ||
|
|
9e5fe7d2b6 | ||
|
|
a7dfaee5ac | ||
|
|
7beb92b1cc | ||
|
|
5b56d22da9 | ||
|
|
e6b343070a | ||
|
|
8496b88518 | ||
|
|
aa542b7876 | ||
|
|
3f7d46378b | ||
|
|
b36def1f72 | ||
|
|
5a6a3a5e8d | ||
|
|
c3bd26d9b4 | ||
|
|
967e106ee7 | ||
|
|
7c73e59482 | ||
|
|
03dfa913d1 | ||
|
|
348e845793 | ||
|
|
80827c3aad | ||
|
|
3b4cf0a024 | ||
|
|
941252fd80 | ||
|
|
bc626ece2d | ||
|
|
400feb3ba7 | ||
|
|
756a4e3995 | ||
|
|
578301240e | ||
|
|
9b8f91c7f9 | ||
|
|
c7c389d4fb | ||
|
|
68f7adfa0b | ||
|
|
c4ac5170c7 | ||
|
|
32e0c8ac71 | ||
|
|
c9c1eae32f | ||
|
|
33366cc291 | ||
|
|
083ac1f125 | ||
|
|
80c6290b89 | ||
|
|
6492ad165a | ||
|
|
f0d1b1a40e | ||
|
|
17f044e6cd | ||
|
|
63f3a9cd7b | ||
|
|
b8ddbf13b4 | ||
|
|
be9bdbfe0f | ||
|
|
bc74bf1402 | ||
|
|
5c2a57f95a | ||
|
|
9cd8cc692e | ||
|
|
ebac4df2d3 | ||
|
|
0626f714c6 | ||
|
|
21842291e9 | ||
|
|
d461c295f6 | ||
|
|
85b3450525 | ||
|
|
75d7c81b7c | ||
|
|
1433e20f73 | ||
|
|
e614a2f726 | ||
|
|
32be7f1fd8 | ||
|
|
66e1d95a2a | ||
|
|
ff03be217e | ||
|
|
a52f8b89dc | ||
|
|
21c7c043b3 | ||
|
|
45e6608ad3 | ||
|
|
28978e3680 | ||
|
|
904f755c8c | ||
|
|
a2c309ddd3 | ||
|
|
677eeac8f7 | ||
|
|
b232cc0f24 | ||
|
|
18bbae1d1e | ||
|
|
08ee62b52c | ||
|
|
5b83aed719 | ||
|
|
b5875ca07b | ||
|
|
3f9bf28382 | ||
|
|
a2bd71c75b | ||
|
|
89509490c5 | ||
|
|
a997a46040 | ||
|
|
538dd07e7b | ||
|
|
046778404e | ||
|
|
766f83d653 | ||
|
|
b20aee6753 | ||
|
|
f9aa3aef18 | ||
|
|
d74aa6e53d | ||
|
|
ea6fbc7835 | ||
|
|
536e604b8e | ||
|
|
49d6f5018f | ||
|
|
4a830e422a | ||
|
|
df6c52fe75 | ||
|
|
01979c08b3 | ||
|
|
7928ef83cc | ||
|
|
10afe0a060 | ||
|
|
ef230cfa3d | ||
|
|
2d15a42137 | ||
|
|
57d6a2f645 | ||
|
|
469a0852d7 | ||
|
|
7983bb1490 | ||
|
|
2e7eac02ed | ||
|
|
238389fc7f | ||
|
|
4cec9da0a6 | ||
|
|
00f27d4400 | ||
|
|
f61618e4a4 | ||
|
|
265d5e306e | ||
|
|
10ed058c2e | ||
|
|
8a21f0efec | ||
|
|
b8291ac5a4 | ||
|
|
c17433cb93 | ||
|
|
35fe7f8203 | ||
|
|
21f13dba43 | ||
|
|
0db239a3fe | ||
|
|
4745d61f9b | ||
|
|
b9a4c3ba52 | ||
|
|
cbf6a70a1a | ||
|
|
7d4e093b58 | ||
|
|
d27feabcb3 | ||
|
|
0d9efa17de | ||
|
|
b125b04c8d | ||
|
|
0539f1d794 | ||
|
|
a4b6709f02 | ||
|
|
f2db143a6f | ||
|
|
b615f54d41 | ||
|
|
db2328e03e | ||
|
|
e2071935ad | ||
|
|
1c40ff773f | ||
|
|
37a3645663 | ||
|
|
eb8accd1e0 | ||
|
|
3af97894b4 | ||
|
|
15a0dcf4d8 | ||
|
|
aa3a44c42a | ||
|
|
40f4b47b87 | ||
|
|
dca09d96b3 | ||
|
|
dd10747460 | ||
|
|
77176162af | ||
|
|
8b9ee4dfd7 | ||
|
|
6e8e77b9ec | ||
|
|
7ef9ce8cc6 | ||
|
|
9646ac2911 | ||
|
|
566a713baa | ||
|
|
36f7e98e90 | ||
|
|
4da755816a | ||
|
|
3ef0258faf | ||
|
|
293ceaee93 | ||
|
|
7b58d0fa0f | ||
|
|
bd68c1e056 | ||
|
|
200162d973 | ||
|
|
4b37a53f1c | ||
|
|
2d41de7ae0 | ||
|
|
bc5bc62c18 | ||
|
|
a0d680024e | ||
|
|
45013feea7 | ||
|
|
2abbe992dd | ||
|
|
d7081b3eeb | ||
|
|
a2f5289bd9 | ||
|
|
b366a4b942 | ||
|
|
27e92653fe | ||
|
|
80120b7b7d | ||
|
|
af8a618826 | ||
|
|
af0e554562 | ||
|
|
382cbb18db | ||
|
|
252b523155 | ||
|
|
4303b3b716 | ||
|
|
87715dc21a | ||
|
|
8552494fc1 | ||
|
|
1c2290b613 | ||
|
|
5f6060b801 | ||
|
|
b39958604d | ||
|
|
22ca1491bc | ||
|
|
690d7e30cf | ||
|
|
4fac2f1094 | ||
|
|
eb0935d577 | ||
|
|
425fedf55b | ||
|
|
a33de09ae6 | ||
|
|
5018e56103 | ||
|
|
de2c975ac2 | ||
|
|
d86e2c0f59 | ||
|
|
14023163b3 | ||
|
|
d85dc8213e | ||
|
|
f6a34055ac | ||
|
|
3beb0d9c12 | ||
|
|
60968fe6f1 | ||
|
|
517c3101c7 | ||
|
|
998f03ba68 | ||
|
|
f3b0e0cfc7 | ||
|
|
a65e6c888c | ||
|
|
cd939d3379 | ||
|
|
29e5aeff5c | ||
|
|
0e5cf81fca | ||
|
|
61f217c610 | ||
|
|
ccb1f491c7 | ||
|
|
49fdac9b4e | ||
|
|
6b6a9490f6 | ||
|
|
2c783e9876 | ||
|
|
ecd971c0e5 | ||
|
|
c48875a6fb | ||
|
|
a245ccb8b7 | ||
|
|
75a18debcb | ||
|
|
1a15d3bb55 | ||
|
|
ffdcae47e3 | ||
|
|
34a2bc8590 | ||
|
|
8ae4d00ccb | ||
|
|
9ed6d5e7fb | ||
|
|
c9243b241c | ||
|
|
9c364a64e3 | ||
|
|
b34306205c | ||
|
|
77f778dbf9 | ||
|
|
23fa4e4161 | ||
|
|
5b6fd0b829 | ||
|
|
8fa8d7739f | ||
|
|
dc552c7a29 | ||
|
|
cf84f453d3 | ||
|
|
daf32a24bc | ||
|
|
34ecfd9857 | ||
|
|
4838564460 | ||
|
|
3dd2f67ff3 | ||
|
|
0ccec3dde0 | ||
|
|
ad77879caa | ||
|
|
27999983cc | ||
|
|
48b039940d | ||
|
|
84dc7d9d82 | ||
|
|
70d6332bad | ||
|
|
aec0b0c86a | ||
|
|
099f6a88c2 | ||
|
|
00a5d42d3d | ||
|
|
28f4a2cb67 | ||
|
|
1ac18ba10e | ||
|
|
1503ee2dfa | ||
|
|
484b528d4f | ||
|
|
758293dc02 | ||
|
|
6cb316b33e | ||
|
|
1d31831e7d | ||
|
|
ee32b07995 | ||
|
|
81f04ca692 | ||
|
|
1756997608 | ||
|
|
ec11ea4ca7 | ||
|
|
a1ebf85e1b | ||
|
|
4b2a734cda | ||
|
|
97ae8f78a0 | ||
|
|
834665c3ba | ||
|
|
d969dd2d8d | ||
|
|
3cb727561c | ||
|
|
cbc89376d3 | ||
|
|
66aa29e3d4 | ||
|
|
5c72aca5ee | ||
|
|
8824815d6d | ||
|
|
ad72e5cbdf | ||
|
|
b2f183fc17 | ||
|
|
9e16bc203a | ||
|
|
83c6eac017 | ||
|
|
d5b277a536 | ||
|
|
77ce6c33e3 | ||
|
|
60f8d349cf | ||
|
|
f539c3df70 | ||
|
|
fe1cf1758c | ||
|
|
8c71c26487 | ||
|
|
2ce39d1fd4 | ||
|
|
88a994b712 | ||
|
|
24c8cc1a0c | ||
|
|
caf684394c | ||
|
|
4b74510450 | ||
|
|
0ddcfc423e | ||
|
|
b4ac56e204 | ||
|
|
3b19ac3942 | ||
|
|
af52cad4ea | ||
|
|
d88396c1e2 | ||
|
|
4c5f7b9451 | ||
|
|
e601cedbb8 | ||
|
|
fa12dc867f | ||
|
|
acfb6c4578 | ||
|
|
e52d637092 | ||
|
|
2fba055696 | ||
|
|
88bb176f3b | ||
|
|
499eeb275b | ||
|
|
61a8d679bb | ||
|
|
2bbde91625 | ||
|
|
9ce098ee06 | ||
|
|
927ae57d24 | ||
|
|
d65c2d3539 | ||
|
|
2d9d8f32dc | ||
|
|
1a30cd5f4a | ||
|
|
f54687f398 | ||
|
|
9f4b4c5de1 | ||
|
|
33bfa94229 | ||
|
|
61e5f190a6 | ||
|
|
a4946272d0 | ||
|
|
8abd99d134 | ||
|
|
97e8ab7c5b | ||
|
|
cb930a7ab5 | ||
|
|
610f1f7dd4 | ||
|
|
516d0b4db8 | ||
|
|
df61ae5c6f | ||
|
|
9f6584b55e | ||
|
|
e4b41f5bb0 | ||
|
|
8d0cea93e9 | ||
|
|
d294d07919 | ||
|
|
153eea2bd5 | ||
|
|
571e3c5c13 | ||
|
|
a2d268f3d6 | ||
|
|
02d456d7a0 | ||
|
|
b3626ad86f | ||
|
|
9b6eab451f | ||
|
|
72be9b5f04 | ||
|
|
35e94a7f65 | ||
|
|
8e02586cc9 | ||
|
|
0b5a640896 | ||
|
|
64b5089909 | ||
|
|
e0e09bfa45 | ||
|
|
8c7f9f2c65 | ||
|
|
aff5cea949 | ||
|
|
8bd99f699f | ||
|
|
b0513fff8a | ||
|
|
f10db9d91a | ||
|
|
9b5fb17068 | ||
|
|
608d713dca | ||
|
|
6d5fc8ca21 | ||
|
|
8c3145b70f | ||
|
|
12a6f3e938 | ||
|
|
2c59435762 | ||
|
|
7336bf5c55 | ||
|
|
2b21b97277 | ||
|
|
603bc99a2f | ||
|
|
426ede1c99 | ||
|
|
21506c1d1b | ||
|
|
0b48b18e7e | ||
|
|
c8f6d9c7a1 | ||
|
|
ed8eced9dd | ||
|
|
4a029b7853 | ||
|
|
6bd9e58ece | ||
|
|
cd075fc8a6 | ||
|
|
e733427920 | ||
|
|
107da0ae22 | ||
|
|
3629d7a32c | ||
|
|
71e1152cde | ||
|
|
d01ab7fd23 | ||
|
|
f46d0a720c | ||
|
|
d943b4993a | ||
|
|
4a4f57d6ac | ||
|
|
085da16268 | ||
|
|
306f6b0037 | ||
|
|
3780d290fa | ||
|
|
ad7dc66f90 | ||
|
|
258244fed8 | ||
|
|
5f7982540f | ||
|
|
b1c0de4b77 | ||
|
|
45b3fecfff | ||
|
|
9774ded4dd | ||
|
|
7ec027854e | ||
|
|
b457001b42 | ||
|
|
6fc6866eb4 | ||
|
|
299e5528bc | ||
|
|
881524a5e4 | ||
|
|
ffc405138d | ||
|
|
f6ff74af16 | ||
|
|
73a12d47de | ||
|
|
83165df7e5 | ||
|
|
30074be5a5 | ||
|
|
16715aa309 | ||
|
|
53f3802a81 | ||
|
|
07626b5cc2 | ||
|
|
9ea603caf2 | ||
|
|
18ab9b80d2 | ||
|
|
71c1cb4e12 | ||
|
|
0c049f39d9 | ||
|
|
096b807c37 | ||
|
|
9018af4765 | ||
|
|
323f28e306 | ||
|
|
e9dbd00f42 | ||
|
|
98b5252a2d | ||
|
|
8abf42023a | ||
|
|
b792238f3f | ||
|
|
2486e5e75f | ||
|
|
5f113f1610 | ||
|
|
592e9dc3ff | ||
|
|
314316cee0 | ||
|
|
9cf663063e | ||
|
|
9ea9210a4b | ||
|
|
2bf1a94608 | ||
|
|
7a0236ad29 | ||
|
|
4341a0c198 | ||
|
|
8071612c12 | ||
|
|
ea9cc3da04 | ||
|
|
0df588fffb | ||
|
|
a622311dbc | ||
|
|
1c95ac2470 | ||
|
|
6ef22166f9 | ||
|
|
1107e50108 | ||
|
|
c19355a7b2 | ||
|
|
65d415fade | ||
|
|
b37313d3f9 | ||
|
|
58fcbad20a | ||
|
|
b571f331ec | ||
|
|
2547d4b3e7 | ||
|
|
892786bf0c | ||
|
|
0c51f2b583 | ||
|
|
d5607ca195 | ||
|
|
48cdf17a4f | ||
|
|
669a8fae15 | ||
|
|
d592936873 | ||
|
|
87898dd2f1 | ||
|
|
15c227f568 | ||
|
|
8de41acfe1 | ||
|
|
9680effb9f | ||
|
|
40df846e3f | ||
|
|
eee94fbf84 | ||
|
|
813679ba25 | ||
|
|
2b9e16c9c9 | ||
|
|
f9bb7f7cff | ||
|
|
41e9569094 | ||
|
|
336ee07191 | ||
|
|
81e0a145f1 | ||
|
|
a95a968fa8 | ||
|
|
6c08941d8b | ||
|
|
e13a5b3865 | ||
|
|
9011d5604a | ||
|
|
78aa4ca137 | ||
|
|
93111842df | ||
|
|
88693f8adc | ||
|
|
f904b012e9 | ||
|
|
cebe0a151f | ||
|
|
8fffad0891 | ||
|
|
fb1263dad7 | ||
|
|
28c5d6c10d | ||
|
|
e7a6f6836e | ||
|
|
f8ffe016d1 | ||
|
|
ec322f0966 | ||
|
|
0674709fc6 | ||
|
|
d91ac7ef21 | ||
|
|
2f0c3c7baf | ||
|
|
be68407707 | ||
|
|
f799a25aeb | ||
|
|
8329d045f1 | ||
|
|
503b289240 | ||
|
|
35e3bbb862 | ||
|
|
8dc261da79 | ||
|
|
65676f8988 | ||
|
|
730da3aa27 | ||
|
|
ff8674bca9 | ||
|
|
c7cfef3b61 | ||
|
|
32188b1cc0 | ||
|
|
37479d8c0d | ||
|
|
f5c7d6576d | ||
|
|
38c422bbc0 | ||
|
|
39d4e5ea81 | ||
|
|
4191ad1cbf | ||
|
|
29287da37c | ||
|
|
98c780415b | ||
|
|
756af9c699 | ||
|
|
7f9076bb1d | ||
|
|
2404f1ab9a | ||
|
|
64bcfd09ec | ||
|
|
6251d22c6e | ||
|
|
de1b4937a1 | ||
|
|
d092dd79ba | ||
|
|
a3ba968386 | ||
|
|
5ca2b97128 | ||
|
|
c9daad1cfd | ||
|
|
0526d5b53a | ||
|
|
34163fb8e4 | ||
|
|
98d2d661a8 | ||
|
|
d9f0a0fd74 | ||
|
|
d20d043e0f | ||
|
|
ce186e1872 | ||
|
|
a14da92e1d | ||
|
|
2b54d850ea | ||
|
|
a63c1b19fc | ||
|
|
34f74cd6ef | ||
|
|
c0b8e62135 | ||
|
|
ea24166b8e | ||
|
|
178b229d66 | ||
|
|
276493da65 | ||
|
|
e85dadfef2 | ||
|
|
1c70efb350 | ||
|
|
6804a0c564 | ||
|
|
6eb7ecc2d4 | ||
|
|
f4956b06b6 | ||
|
|
9a2f7c2660 | ||
|
|
b6017c558a | ||
|
|
62ed6c6a58 | ||
|
|
24966c812f | ||
|
|
ea8dc2e0af | ||
|
|
010b285e67 | ||
|
|
774231f347 | ||
|
|
ff1dfd8f25 | ||
|
|
2741ac195d | ||
|
|
cf780e296e | ||
|
|
0361246db0 | ||
|
|
63355ecd5b | ||
|
|
0f54ba59fb | ||
|
|
b67b243ebd | ||
|
|
4c29c20613 | ||
|
|
4c2619d948 | ||
|
|
ea5662a4a2 | ||
|
|
7c1ce777a1 | ||
|
|
3bb85f2d61 | ||
|
|
93e36b3113 | ||
|
|
932fb670e3 | ||
|
|
54dce61a95 | ||
|
|
e686c0e0a2 | ||
|
|
05acf32f39 | ||
|
|
67064012c9 | ||
|
|
10e93c3b1b | ||
|
|
5b2ec1cbb5 | ||
|
|
972f701c5c | ||
|
|
51285efbc3 | ||
|
|
e2635705f9 | ||
|
|
7762107543 | ||
|
|
9123ad89c8 | ||
|
|
39f3d6cc80 | ||
|
|
af5665f67c | ||
|
|
c2175cc692 | ||
|
|
1ef371a467 | ||
|
|
58461ff5bb | ||
|
|
665959da90 | ||
|
|
8e63974f94 | ||
|
|
eae86f54ba | ||
|
|
30128a122d | ||
|
|
56e22a39ac | ||
|
|
6ceb330baa | ||
|
|
f30509c7ba | ||
|
|
9489136bd6 | ||
|
|
05cd3d736b | ||
|
|
29b17772e5 | ||
|
|
6151d63eac | ||
|
|
e57aa68854 | ||
|
|
73fa6d9bd0 | ||
|
|
da3c4b87c1 | ||
|
|
0eedc1b128 | ||
|
|
db339d40de | ||
|
|
f72aa7884d | ||
|
|
1434f1bb40 | ||
|
|
6bc92b26a7 |
43
apps/addressbook/README.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
addressbook v2.0.2 - A simple name resolution mechanism for I2P
|
||||
|
||||
addressbook is a simple implementation of subscribable address books for I2P.
|
||||
Addresses are stored in userhosts.txt and a second copy of the address book is
|
||||
placed on your eepsite as hosts.txt.
|
||||
|
||||
subscriptions.txt contains a list of urls to check for new addresses.
|
||||
Since the urls are checked in order, and conflicting addresses are not added,
|
||||
addressbook.subscriptions can be considered to be ranked in order of trust.
|
||||
|
||||
The system created by addressbook is similar to the early days of DNS,
|
||||
when everyone ran a local name server. The major difference is the lack of
|
||||
authority. Name cannot be guaranteed to be globally unique, but in practise
|
||||
they probably will be, for a variety of social reasons.
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
i2p with a running http proxy
|
||||
|
||||
Installation and Usage
|
||||
**********************
|
||||
|
||||
1. Unzip addressbook-%ver.zip into your i2p directory.
|
||||
2. Restart your router.
|
||||
|
||||
The addressbook daemon will automatically run while the router is up.
|
||||
|
||||
Aside from the daemon itself, the other elements of the addressbook interface
|
||||
are the config.txt, myhosts.txt, and subscriptions.txt files found in the addressbook
|
||||
directory.
|
||||
|
||||
config.txt is the configuration file for addressbook.
|
||||
|
||||
myhosts.txt is the addressbook master address book. Addresses placed in this file
|
||||
take precidence over those in the router address book and in remote address books.
|
||||
If changes are made to this file, they will be reflected in the router address book
|
||||
and published address book after the next update. Do not make changes directly to the
|
||||
router address book, as they could be lost during an update.
|
||||
|
||||
subscriptions.txt is the subscription list for addressbook. Each entry is an absolute
|
||||
url to a file in hosts.txt format. Since the list is checked in order, url's should be
|
||||
listed in order of trust.
|
||||
51
apps/addressbook/build.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="addressbook" default="war" basedir=".">
|
||||
|
||||
<property name="src" value="java/src/addressbook"/>
|
||||
<property name="build" value="build"/>
|
||||
<property name="dist" location="dist"/>
|
||||
<property name="jar" value="addressbook.jar"/>
|
||||
<property name="war" value="addressbook.war"/>
|
||||
|
||||
<target name="init">
|
||||
<mkdir dir="${build}"/>
|
||||
<mkdir dir="${dist}"/>
|
||||
</target>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="${build}"/>
|
||||
<delete dir="${dist}"/>
|
||||
</target>
|
||||
|
||||
<target name="distclean" depends="clean" />
|
||||
|
||||
<target name="compile" depends="init">
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
srcdir="${src}" destdir="${build}">
|
||||
<classpath>
|
||||
<pathelement location="../../core/java/build/i2p.jar" />
|
||||
<pathelement location="../jetty/jettylib/javax.servlet.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
<jar basedir="${build}" destfile="${dist}/${jar}">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="addressbook.Daemon"/>
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target name="war" depends="compile">
|
||||
<mkdir dir="${dist}/tmp"/>
|
||||
<mkdir dir="${dist}/tmp/WEB-INF"/>
|
||||
<mkdir dir="${dist}/tmp/WEB-INF/classes"/>
|
||||
<copy todir="${dist}/tmp/WEB-INF/classes">
|
||||
<fileset dir="${build}"/>
|
||||
</copy>
|
||||
<war basedir="${dist}/tmp" webxml="web.xml" destfile="${dist}/${war}"/>
|
||||
<delete dir="${dist}/tmp"/>
|
||||
</target>
|
||||
|
||||
</project>
|
||||
43
apps/addressbook/config.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
# This is the configuration file for addressbook.
|
||||
#
|
||||
# Options
|
||||
# *******
|
||||
# All paths are realitive to i2p/addressbook. Default value for
|
||||
# each option is given in parentheses.
|
||||
#
|
||||
# proxy_host The hostname of your I2P http proxy.
|
||||
# (localhost)
|
||||
#
|
||||
# proxy_port The port of your I2P http proxy. (4444)
|
||||
#
|
||||
# master_addressbook The path to your master address book, used for local
|
||||
# changes only. (myhosts.txt)
|
||||
#
|
||||
# router_addressbook The path to the address book used by the router.
|
||||
# Contains the addresses from your master address book
|
||||
# and your subscribed address books. (../userhosts.txt)
|
||||
#
|
||||
# published_addressbook The path to the copy of your address book made
|
||||
# available on i2p. (../eepsite/docroot/hosts.txt)
|
||||
#
|
||||
# log The path to your addressbook log. (log.txt)
|
||||
#
|
||||
# subscriptions The path to your subscription file. (subscriptions.txt)
|
||||
#
|
||||
# etags The path to the etags header storage file. (etags)
|
||||
#
|
||||
# last_modified The path to the last-modified header storage file.
|
||||
# (last_modified)
|
||||
#
|
||||
# update_delay The time (in hours) between each update. (1)
|
||||
|
||||
proxy_host=localhost
|
||||
proxy_port=4444
|
||||
master_addressbook=myhosts.txt
|
||||
router_addressbook=../userhosts.txt
|
||||
published_addressbook=../eepsite/docroot/hosts.txt
|
||||
log=log.txt
|
||||
subscriptions=subscriptions.txt
|
||||
etags=etags
|
||||
last_modified=last_modified
|
||||
update_delay=1
|
||||
218
apps/addressbook/java/src/addressbook/AddressBook.java
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* 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
|
||||
* COPYRIGHT HOLDER 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.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.EepGet;
|
||||
|
||||
/**
|
||||
* An address book for storing human readable names mapped to base64 i2p
|
||||
* destinations. AddressBooks can be created from local and remote files, merged
|
||||
* together, and written out to local files.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class AddressBook {
|
||||
|
||||
private String location;
|
||||
|
||||
private Map addresses;
|
||||
|
||||
private boolean modified;
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the Map addresses.
|
||||
*
|
||||
* @param addresses
|
||||
* A Map containing human readable addresses as keys, mapped to
|
||||
* base64 i2p destinations.
|
||||
*/
|
||||
public AddressBook(Map addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the file at url. If the
|
||||
* remote file cannot be read, construct an empty AddressBook
|
||||
*
|
||||
* @param url
|
||||
* A URL pointing at a file with lines in the format "key=value",
|
||||
* where key is a human readable name, and value is a base64 i2p
|
||||
* destination.
|
||||
*/
|
||||
public AddressBook(String url, String proxyHost, int proxyPort) {
|
||||
this.location = url;
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
proxyHost, proxyPort, 0, "addressbook.tmp", url, true,
|
||||
null);
|
||||
get.fetch();
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
new File("addressbook.tmp").delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the Subscription subscription. If the
|
||||
* address book at subscription has not changed since the last time it was
|
||||
* read or cannot be read, return an empty AddressBook.
|
||||
*
|
||||
* @param subscription
|
||||
* A Subscription instance pointing at a remote address book.
|
||||
*/
|
||||
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
|
||||
this.location = subscription.getLocation();
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
proxyHost, proxyPort, 0, "addressbook.tmp",
|
||||
subscription.getLocation(), true, subscription.getEtag());
|
||||
get.fetch();
|
||||
subscription.setEtag(get.getETag());
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
new File("addressbook.tmp").delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the file at file. If the
|
||||
* file cannot be read, construct an empty AddressBook
|
||||
*
|
||||
* @param file
|
||||
* A File pointing at a file with lines in the format
|
||||
* "key=value", where key is a human readable name, and value is
|
||||
* a base64 i2p destination.
|
||||
*/
|
||||
public AddressBook(File file) {
|
||||
this.location = file.toString();
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(file);
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map containing the addresses in the AddressBook.
|
||||
*
|
||||
* @return A Map containing the addresses in the AddressBook, where the key
|
||||
* is a human readable name, and the value is a base64 i2p
|
||||
* destination.
|
||||
*/
|
||||
public Map getAddresses() {
|
||||
return this.addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location of the file this AddressBook was constructed from.
|
||||
*
|
||||
* @return A String representing either an abstract path, or a url,
|
||||
* depending on how the instance was constructed.
|
||||
*/
|
||||
public String getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the contents of the AddressBook.
|
||||
*
|
||||
* @return A String representing the contents of the AddressBook.
|
||||
*/
|
||||
public String toString() {
|
||||
return this.addresses.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this AddressBook with AddressBook other, writing messages about new
|
||||
* addresses or conflicts to log. Addresses in AddressBook other that are
|
||||
* not in this AddressBook are added to this AddressBook. In case of a
|
||||
* conflict, addresses in this AddressBook take precedence
|
||||
*
|
||||
* @param other
|
||||
* An AddressBook to merge with.
|
||||
* @param log
|
||||
* The log to write messages about new addresses or conflicts to.
|
||||
*/
|
||||
public void merge(AddressBook other, boolean overwrite, Log log) {
|
||||
Iterator otherIter = other.addresses.keySet().iterator();
|
||||
|
||||
while (otherIter.hasNext()) {
|
||||
String otherKey = (String) otherIter.next();
|
||||
String otherValue = (String) other.addresses.get(otherKey);
|
||||
|
||||
if (otherKey.endsWith(".i2p") && otherValue.length() >= 516) {
|
||||
if (this.addresses.containsKey(otherKey) && !overwrite) {
|
||||
if (!this.addresses.get(otherKey).equals(otherValue)
|
||||
&& log != null) {
|
||||
log.append("Conflict for " + otherKey + " from "
|
||||
+ other.location
|
||||
+ ". Destination in remote address book is "
|
||||
+ otherValue);
|
||||
}
|
||||
} else if (!this.addresses.containsKey(otherKey)
|
||||
|| !this.addresses.get(otherKey).equals(otherValue)) {
|
||||
this.addresses.put(otherKey, otherValue);
|
||||
this.modified = true;
|
||||
if (log != null) {
|
||||
log.append("New address " + otherKey
|
||||
+ " added to address book.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the contents of this AddressBook out to the File file. If the file
|
||||
* cannot be writen to, this method will silently fail.
|
||||
*
|
||||
* @param file
|
||||
* The file to write the contents of this AddressBook too.
|
||||
*/
|
||||
public void write(File file) {
|
||||
if (this.modified) {
|
||||
try {
|
||||
ConfigParser.write(this.addresses, file);
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write this AddressBook out to the file it was read from. Requires that
|
||||
* AddressBook was constructed from a file on the local filesystem. If the
|
||||
* file cannot be writen to, this method will silently fail.
|
||||
*/
|
||||
public void write() {
|
||||
this.write(new File(this.location));
|
||||
}
|
||||
}
|
||||
304
apps/addressbook/java/src/addressbook/ConfigParser.java
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* 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
|
||||
* COPYRIGHT HOLDER 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.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Iterator;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Utility class providing methods to parse and write files in config file
|
||||
* format, and subscription file format.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*/
|
||||
public class ConfigParser {
|
||||
|
||||
/**
|
||||
* Strip the comments from a String. Lines that begin with '#' and ';' are
|
||||
* considered comments, as well as any part of a line after a '#'.
|
||||
*
|
||||
* @param inputLine
|
||||
* A String to strip comments from.
|
||||
* @return A String without comments, but otherwise identical to inputLine.
|
||||
*/
|
||||
public static String stripComments(String inputLine) {
|
||||
if (inputLine.startsWith(";")) {
|
||||
return "";
|
||||
}
|
||||
if (inputLine.split("#").length > 0) {
|
||||
return inputLine.split("#")[0];
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of BufferedReader input. input must have
|
||||
* a single key, value pair on each line, in the format: key=value. Lines
|
||||
* starting with '#' or ';' are considered comments, and ignored. Lines that
|
||||
* are obviously not in the format key=value are also ignored.
|
||||
*
|
||||
* @param input
|
||||
* A BufferedReader with lines in key=value format to parse into
|
||||
* a Map.
|
||||
* @return A Map containing the key, value pairs from input.
|
||||
* @throws IOException
|
||||
* if the BufferedReader cannot be read.
|
||||
*
|
||||
*/
|
||||
public static Map parse(BufferedReader input) throws IOException {
|
||||
Map result = new HashMap();
|
||||
String inputLine;
|
||||
inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
inputLine = ConfigParser.stripComments(inputLine);
|
||||
String[] splitLine = inputLine.split("=");
|
||||
if (splitLine.length == 2) {
|
||||
result.put(splitLine[0].trim(), splitLine[1].trim());
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
}
|
||||
input.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the File file. See parseBufferedReader
|
||||
* for details of the input format.
|
||||
*
|
||||
* @param file
|
||||
* A File to parse.
|
||||
* @return A Map containing the key, value pairs from file.
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static Map parse(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
return ConfigParser.parse(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the String string. See
|
||||
* parseBufferedReader for details of the input format.
|
||||
*
|
||||
* @param string
|
||||
* A String to parse.
|
||||
* @return A Map containing the key, value pairs from string.
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static Map parse(String string) throws IOException {
|
||||
StringReader stringReader = new StringReader(string);
|
||||
BufferedReader input = new BufferedReader(stringReader);
|
||||
return ConfigParser.parse(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the File file. If file cannot be read,
|
||||
* use map instead, and write the result to where file should have been.
|
||||
*
|
||||
* @param file
|
||||
* A File to attempt to parse.
|
||||
* @param map
|
||||
* A Map to use as the default, if file fails.
|
||||
* @return A Map containing the key, value pairs from file, or if file
|
||||
* cannot be read, map.
|
||||
*/
|
||||
public static Map parse(File file, Map map) {
|
||||
Map result = new HashMap();
|
||||
try {
|
||||
result = ConfigParser.parse(file);
|
||||
} catch (IOException exp) {
|
||||
result = map;
|
||||
try {
|
||||
ConfigParser.write(result, file);
|
||||
} catch (IOException exp2) {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List where each element is a line from the BufferedReader input.
|
||||
*
|
||||
* @param input
|
||||
* A BufferedReader to parse.
|
||||
* @return A List consisting of one element for each line in input.
|
||||
* @throws IOException
|
||||
* if input cannot be read.
|
||||
*/
|
||||
public static List parseSubscriptions(BufferedReader input)
|
||||
throws IOException {
|
||||
List result = new LinkedList();
|
||||
String inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
inputLine = ConfigParser.stripComments(inputLine).trim();
|
||||
if (inputLine.length() > 0) {
|
||||
result.add(inputLine);
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
}
|
||||
input.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List where each element is a line from the File file.
|
||||
*
|
||||
* @param file
|
||||
* A File to parse.
|
||||
* @return A List consisting of one element for each line in file.
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static List parseSubscriptions(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
return ConfigParser.parseSubscriptions(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List where each element is a line from the String string.
|
||||
*
|
||||
* @param string
|
||||
* A String to parse.
|
||||
* @return A List consisting of one element for each line in string.
|
||||
* @throws IOException
|
||||
* if string cannot be read.
|
||||
*/
|
||||
public static List parseSubscriptions(String string) throws IOException {
|
||||
StringReader stringReader = new StringReader(string);
|
||||
BufferedReader input = new BufferedReader(stringReader);
|
||||
return ConfigParser.parseSubscriptions(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List using the contents of the File file. If file cannot be
|
||||
* read, use list instead, and write the result to where file should have
|
||||
* been.
|
||||
*
|
||||
* @param file
|
||||
* A File to attempt to parse.
|
||||
* @param string
|
||||
* A List to use as the default, if file fails.
|
||||
* @return A List consisting of one element for each line in file, or if
|
||||
* file cannot be read, list.
|
||||
*/
|
||||
public static List parseSubscriptions(File file, List list) {
|
||||
List result = new LinkedList();
|
||||
try {
|
||||
result = ConfigParser.parseSubscriptions(file);
|
||||
} catch (IOException exp) {
|
||||
result = list;
|
||||
try {
|
||||
ConfigParser.writeSubscriptions(result, file);
|
||||
} catch (IOException exp2) {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of Map map to BufferedWriter output. Output is written
|
||||
* with one key, value pair on each line, in the format: key=value.
|
||||
*
|
||||
* @param map
|
||||
* A Map to write to output.
|
||||
* @param output
|
||||
* A BufferedWriter to write the Map to.
|
||||
* @throws IOException
|
||||
* if the BufferedWriter cannot be written to.
|
||||
*/
|
||||
public static void write(Map map, BufferedWriter output) throws IOException {
|
||||
Iterator keyIter = map.keySet().iterator();
|
||||
|
||||
while (keyIter.hasNext()) {
|
||||
String key = (String) keyIter.next();
|
||||
output.write(key + "=" + (String) map.get(key));
|
||||
output.newLine();
|
||||
}
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of Map map to the File file. Output is written
|
||||
* with one key, value pair on each line, in the format: key=value.
|
||||
*
|
||||
* @param map
|
||||
* A Map to write to file.
|
||||
* @param file
|
||||
* A File to write the Map to.
|
||||
* @throws IOException
|
||||
* if file cannot be written to.
|
||||
*/
|
||||
public static void write(Map map, File file) throws IOException {
|
||||
ConfigParser
|
||||
.write(map, new BufferedWriter(new FileWriter(file, false)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of List list to BufferedReader output. Output is written
|
||||
* with each element of list on a new line.
|
||||
*
|
||||
* @param list
|
||||
* A List to write to file.
|
||||
* @param output
|
||||
* A BufferedReader to write list to.
|
||||
* @throws IOException
|
||||
* if output cannot be written to.
|
||||
*/
|
||||
public static void writeSubscriptions(List list, BufferedWriter output)
|
||||
throws IOException {
|
||||
Iterator iter = list.iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
output.write((String) iter.next());
|
||||
output.newLine();
|
||||
}
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of List list to File file. Output is written with each
|
||||
* element of list on a new line.
|
||||
*
|
||||
* @param list
|
||||
* A List to write to file.
|
||||
* @param file
|
||||
* A File to write list to.
|
||||
* @throws IOException
|
||||
* if output cannot be written to.
|
||||
*/
|
||||
public static void writeSubscriptions(List list, File file)
|
||||
throws IOException {
|
||||
ConfigParser.writeSubscriptions(list, new BufferedWriter(
|
||||
new FileWriter(file, false)));
|
||||
}
|
||||
|
||||
}
|
||||
191
apps/addressbook/java/src/addressbook/Daemon.java
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* 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
|
||||
* COPYRIGHT HOLDER 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.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Main class of addressbook. Performs updates, and runs the main loop.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Daemon {
|
||||
public static final String VERSION = "2.0.3";
|
||||
private static final Daemon _instance = new Daemon();
|
||||
|
||||
/**
|
||||
* Update the router and published address books using remote data from the
|
||||
* subscribed address books listed in subscriptions.
|
||||
*
|
||||
* @param master
|
||||
* The master AddressBook. This address book is never
|
||||
* overwritten, so it is safe for the user to write to.
|
||||
* @param router
|
||||
* The router AddressBook. This is the address book read by
|
||||
* client applications.
|
||||
* @param published
|
||||
* The published AddressBook. This address book is published on
|
||||
* the user's eepsite so that others may subscribe to it.
|
||||
* @param subscriptions
|
||||
* A SubscriptionList listing the remote address books to update
|
||||
* from.
|
||||
* @param log
|
||||
* The log to write changes and conflicts to.
|
||||
*/
|
||||
public void update(AddressBook master, AddressBook router,
|
||||
File published, SubscriptionList subscriptions, Log log) {
|
||||
router.merge(master, true, null);
|
||||
Iterator iter = subscriptions.iterator();
|
||||
while (iter.hasNext()) {
|
||||
router.merge((AddressBook) iter.next(), false, log);
|
||||
}
|
||||
router.write();
|
||||
if (published != null)
|
||||
router.write(published);
|
||||
subscriptions.write();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an update, using the Map settings to provide the parameters.
|
||||
*
|
||||
* @param settings
|
||||
* A Map containg the parameters needed by update.
|
||||
* @param home
|
||||
* The directory containing addressbook's configuration files.
|
||||
*/
|
||||
public void update(Map settings, String home) {
|
||||
File masterFile = new File(home, (String) settings
|
||||
.get("master_addressbook"));
|
||||
File routerFile = new File(home, (String) settings
|
||||
.get("router_addressbook"));
|
||||
File published = null;
|
||||
if ("true".equals(settings.get("should_publish")))
|
||||
published = new File(home, (String) settings
|
||||
.get("published_addressbook"));
|
||||
File subscriptionFile = new File(home, (String) settings
|
||||
.get("subscriptions"));
|
||||
File logFile = new File(home, (String) settings.get("log"));
|
||||
File etagsFile = new File(home, (String) settings.get("etags"));
|
||||
File lastModifiedFile = new File(home, (String) settings
|
||||
.get("last_modified"));
|
||||
|
||||
AddressBook master = new AddressBook(masterFile);
|
||||
AddressBook router = new AddressBook(routerFile);
|
||||
|
||||
List defaultSubs = new LinkedList();
|
||||
defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
|
||||
|
||||
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
|
||||
etagsFile, lastModifiedFile, defaultSubs, (String) settings
|
||||
.get("proxy_host"), Integer.parseInt((String) settings.get("proxy_port")));
|
||||
Log log = new Log(logFile);
|
||||
|
||||
update(master, router, published, subscriptions, log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings, set the proxy, then enter into the main loop. The main
|
||||
* loop performs an immediate update, and then an update every number of
|
||||
* hours, as configured in the settings file.
|
||||
*
|
||||
* @param args
|
||||
* Command line arguments. If there are any arguments provided,
|
||||
* the first is taken as addressbook's home directory, and the
|
||||
* others are ignored.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
_instance.run(args);
|
||||
}
|
||||
|
||||
public void run(String[] args) {
|
||||
String settingsLocation = "config.txt";
|
||||
Map settings = new HashMap();
|
||||
String home;
|
||||
if (args.length > 0) {
|
||||
home = args[0];
|
||||
} else {
|
||||
home = ".";
|
||||
}
|
||||
|
||||
Map defaultSettings = new HashMap();
|
||||
defaultSettings.put("proxy_host", "localhost");
|
||||
defaultSettings.put("proxy_port", "4444");
|
||||
defaultSettings.put("master_addressbook", "../userhosts.txt");
|
||||
defaultSettings.put("router_addressbook", "../hosts.txt");
|
||||
defaultSettings.put("published_addressbook", "../eepsite/docroot/hosts.txt");
|
||||
defaultSettings.put("should_publish", "false");
|
||||
defaultSettings.put("log", "log.txt");
|
||||
defaultSettings.put("subscriptions", "subscriptions.txt");
|
||||
defaultSettings.put("etags", "etags");
|
||||
defaultSettings.put("last_modified", "last_modified");
|
||||
defaultSettings.put("update_delay", "1");
|
||||
|
||||
File homeFile = new File(home);
|
||||
if (!homeFile.exists()) {
|
||||
boolean created = homeFile.mkdirs();
|
||||
if (created)
|
||||
System.out.println("INFO: Addressbook directory " + homeFile.getName() + " created");
|
||||
else
|
||||
System.out.println("ERROR: Addressbook directory " + homeFile.getName() + " could not be created");
|
||||
}
|
||||
|
||||
File settingsFile = new File(homeFile, settingsLocation);
|
||||
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
// wait
|
||||
try {
|
||||
Thread.currentThread().sleep(5*60*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
while (true) {
|
||||
long delay = Long.parseLong((String) settings.get("update_delay"));
|
||||
if (delay < 1) {
|
||||
delay = 1;
|
||||
}
|
||||
|
||||
update(settings, home);
|
||||
try {
|
||||
synchronized (this) {
|
||||
wait(delay * 60 * 60 * 1000);
|
||||
}
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to get the addressbook to reread its config and
|
||||
* refetch its subscriptions.
|
||||
*/
|
||||
public static void wakeup() {
|
||||
synchronized (_instance) {
|
||||
_instance.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
apps/addressbook/java/src/addressbook/DaemonThread.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* 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
|
||||
* COPYRIGHT HOLDER 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.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
/**
|
||||
* A thread that waits five minutes, then runs the addressbook daemon.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class DaemonThread extends Thread {
|
||||
|
||||
private String[] args;
|
||||
|
||||
/**
|
||||
* Construct a DaemonThread with the command line arguments args.
|
||||
* @param args
|
||||
* A String array to pass to Daemon.main().
|
||||
*/
|
||||
public DaemonThread(String[] args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
//try {
|
||||
// Thread.sleep(5 * 60 * 1000);
|
||||
//} catch (InterruptedException exp) {
|
||||
//}
|
||||
Daemon.main(this.args);
|
||||
}
|
||||
}
|
||||
76
apps/addressbook/java/src/addressbook/Log.java
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* 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
|
||||
* COPYRIGHT HOLDER 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.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A simple log with automatic time stamping.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Log {
|
||||
|
||||
private File file;
|
||||
|
||||
/**
|
||||
* Construct a Log instance that writes to the File file.
|
||||
*
|
||||
* @param file
|
||||
* A File for the log to write to.
|
||||
*/
|
||||
public Log(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write entry to a new line in the log, with appropriate time stamp.
|
||||
*
|
||||
* @param entry
|
||||
* A String containing a message to append to the log.
|
||||
*/
|
||||
public void append(String entry) {
|
||||
try {
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(this.file,
|
||||
true));
|
||||
String timestamp = new Date().toString();
|
||||
bw.write(timestamp + " -- " + entry);
|
||||
bw.newLine();
|
||||
bw.close();
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the File that the Log is writing to.
|
||||
*
|
||||
* @return The File that the log is writing to.
|
||||
*/
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
}
|
||||
61
apps/addressbook/java/src/addressbook/Servlet.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* 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
|
||||
* COPYRIGHT HOLDER 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.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import javax.servlet.GenericServlet;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
/**
|
||||
* A wrapper for addressbook to allow it to be started as a web application.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Servlet extends GenericServlet {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
|
||||
*/
|
||||
public void service(ServletRequest request, ServletResponse response) {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
|
||||
*/
|
||||
public void init(ServletConfig config) {
|
||||
try {
|
||||
super.init(config);
|
||||
} catch (ServletException exp) {
|
||||
}
|
||||
String[] args = new String[1];
|
||||
args[0] = config.getInitParameter("home");
|
||||
DaemonThread thread = new DaemonThread(args);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
System.out.println("INFO: Starting Addressbook " + Daemon.VERSION);
|
||||
System.out.println("INFO: config root under " + args[0]);
|
||||
}
|
||||
|
||||
}
|
||||
105
apps/addressbook/java/src/addressbook/Subscription.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* 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
|
||||
* COPYRIGHT HOLDER 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.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
/**
|
||||
* A subscription to a remote address book.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Subscription {
|
||||
|
||||
private String location;
|
||||
|
||||
private String etag;
|
||||
|
||||
private String lastModified;
|
||||
|
||||
/**
|
||||
* Construct a Subscription pointing to the address book at location, that
|
||||
* was last read at the time represented by etag and lastModified.
|
||||
*
|
||||
* @param location
|
||||
* A String representing a url to a remote address book.
|
||||
* @param etag
|
||||
* The etag header that we recieved the last time we read this
|
||||
* subscription.
|
||||
* @param lastModified
|
||||
* the last-modified header we recieved the last time we read
|
||||
* this subscription.
|
||||
*/
|
||||
public Subscription(String location, String etag, String lastModified) {
|
||||
this.location = location;
|
||||
this.etag = etag;
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location this Subscription points at.
|
||||
*
|
||||
* @return A String representing a url to a remote address book.
|
||||
*/
|
||||
public String getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the etag header that we recieved the last time we read this
|
||||
* subscription.
|
||||
*
|
||||
* @return A String containing the etag header.
|
||||
*/
|
||||
public String getEtag() {
|
||||
return this.etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the etag header.
|
||||
*
|
||||
* @param etag
|
||||
* A String containing the etag header.
|
||||
*/
|
||||
public void setEtag(String etag) {
|
||||
this.etag = etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last-modified header that we recieved the last time we read
|
||||
* this subscription.
|
||||
*
|
||||
* @return A String containing the last-modified header.
|
||||
*/
|
||||
public String getLastModified() {
|
||||
return this.lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last-modified header.
|
||||
*
|
||||
* @param lastModified
|
||||
* A String containing the last-modified header.
|
||||
*/
|
||||
public void setLastModified(String lastModified) {
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* 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
|
||||
* COPYRIGHT HOLDER 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.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An iterator over the subscriptions in a SubscriptionList. Note that this iterator
|
||||
* returns AddressBook objects, and not Subscription objects.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*/
|
||||
public class SubscriptionIterator implements Iterator {
|
||||
|
||||
private Iterator subIterator;
|
||||
private String proxyHost;
|
||||
private int proxyPort;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionIterator using the Subscriprions in List subscriptions.
|
||||
*
|
||||
* @param subscriptions
|
||||
* List of Subscription objects that represent address books.
|
||||
*/
|
||||
public SubscriptionIterator(List subscriptions, String proxyHost, int proxyPort) {
|
||||
this.subIterator = subscriptions.iterator();
|
||||
this.proxyHost = proxyHost;
|
||||
this.proxyPort = proxyPort;
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#hasNext()
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return this.subIterator.hasNext();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#next()
|
||||
*/
|
||||
public Object next() {
|
||||
Subscription sub = (Subscription) this.subIterator.next();
|
||||
return new AddressBook(sub, this.proxyHost, this.proxyPort);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#remove()
|
||||
*/
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
129
apps/addressbook/java/src/addressbook/SubscriptionList.java
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* 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
|
||||
* COPYRIGHT HOLDER 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.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A list of Subscriptions loaded from a file.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class SubscriptionList {
|
||||
|
||||
private List subscriptions;
|
||||
|
||||
private File etagsFile;
|
||||
|
||||
private File lastModifiedFile;
|
||||
|
||||
private String proxyHost;
|
||||
|
||||
private int proxyPort;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionList using the urls from locationsFile and, if
|
||||
* available, the etags and last-modified headers loaded from etagsFile and
|
||||
* lastModifiedFile.
|
||||
*
|
||||
* @param locationsFile
|
||||
* A file containing one url on each line.
|
||||
* @param etagsFile
|
||||
* A file containg the etag headers used for conditional GET. The
|
||||
* file is in the format "url=etag".
|
||||
* @param lastModifiedFile
|
||||
* A file containg the last-modified headers used for conditional
|
||||
* GET. The file is in the format "url=leastmodified".
|
||||
*/
|
||||
public SubscriptionList(File locationsFile, File etagsFile,
|
||||
File lastModifiedFile, List defaultSubs, String proxyHost,
|
||||
int proxyPort) {
|
||||
this.subscriptions = new LinkedList();
|
||||
this.etagsFile = etagsFile;
|
||||
this.lastModifiedFile = lastModifiedFile;
|
||||
this.proxyHost = proxyHost;
|
||||
this.proxyPort = proxyPort;
|
||||
Map etags;
|
||||
Map lastModified;
|
||||
String location;
|
||||
List locations = ConfigParser.parseSubscriptions(locationsFile,
|
||||
defaultSubs);
|
||||
try {
|
||||
etags = ConfigParser.parse(etagsFile);
|
||||
} catch (IOException exp) {
|
||||
etags = new HashMap();
|
||||
}
|
||||
try {
|
||||
lastModified = ConfigParser.parse(lastModifiedFile);
|
||||
} catch (IOException exp) {
|
||||
lastModified = new HashMap();
|
||||
}
|
||||
Iterator iter = locations.iterator();
|
||||
while (iter.hasNext()) {
|
||||
location = (String) iter.next();
|
||||
this.subscriptions.add(new Subscription(location, (String) etags
|
||||
.get(location), (String) lastModified.get(location)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an iterator over the AddressBooks represented by the Subscriptions
|
||||
* in this SubscriptionList.
|
||||
*
|
||||
* @return A SubscriptionIterator.
|
||||
*/
|
||||
public SubscriptionIterator iterator() {
|
||||
return new SubscriptionIterator(this.subscriptions, this.proxyHost,
|
||||
this.proxyPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the etag and last-modified headers for each Subscription to files.
|
||||
*/
|
||||
public void write() {
|
||||
Iterator iter = this.subscriptions.iterator();
|
||||
Subscription sub;
|
||||
Map etags = new HashMap();
|
||||
Map lastModified = new HashMap();
|
||||
while (iter.hasNext()) {
|
||||
sub = (Subscription) iter.next();
|
||||
if (sub.getEtag() != null) {
|
||||
etags.put(sub.getLocation(), sub.getEtag());
|
||||
}
|
||||
if (sub.getLastModified() != null) {
|
||||
lastModified.put(sub.getLocation(), sub.getLastModified());
|
||||
}
|
||||
}
|
||||
try {
|
||||
ConfigParser.write(etags, this.etagsFile);
|
||||
ConfigParser.write(lastModified, this.lastModifiedFile);
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
}
|
||||
}
|
||||
10
apps/addressbook/myhosts.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# addressbook master address book. Addresses placed in this file take precidence
|
||||
# over those in the router address book and in remote address books. If changes
|
||||
# are made to this file, they will be reflected in the router address book and
|
||||
# published address book after the next update.
|
||||
#
|
||||
# Do not make changes directly to the router address book, as they could be lost
|
||||
# during an update.
|
||||
#
|
||||
# This file takes addresses in the hosts.txt format, i.e.
|
||||
# example.i2p=somereallylongbase64thingAAAA
|
||||
7
apps/addressbook/subscriptions.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# Subscription list for addressbook
|
||||
#
|
||||
# Each entry is an absolute url to a file in hosts.txt format.
|
||||
# Since the list is checked in order, url's should be listed in order of trust.
|
||||
#
|
||||
http://dev.i2p/i2p/hosts.txt
|
||||
http://duck.i2p/hosts.txt
|
||||
16
apps/addressbook/web.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
|
||||
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
|
||||
|
||||
<web-app>
|
||||
<servlet>
|
||||
<servlet-name>addressbook</servlet-name>
|
||||
<servlet-class>addressbook.Servlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>home</param-name>
|
||||
<param-value>./addressbook</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
</web-app>
|
||||
@@ -111,12 +111,12 @@ public class Bogobot extends PircBot {
|
||||
_botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh");
|
||||
|
||||
_ircChannel = config.getProperty("ircChannel", "#i2p-chat");
|
||||
_ircServer = config.getProperty("ircServer", "irc.duck.i2p");
|
||||
_ircServer = config.getProperty("ircServer", "irc.postman.i2p");
|
||||
_ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668"));
|
||||
|
||||
_isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue();
|
||||
_loggedHostnamePattern = config.getProperty("loggedHostnamePattern", "");
|
||||
_logFilePrefix = config.getProperty("logFilePrefix", "irc.duck.i2p.i2p-chat");
|
||||
_logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat");
|
||||
_logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY);
|
||||
|
||||
_isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue();
|
||||
|
||||
106
apps/fortuna/build.xml
Normal file
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="fortuna">
|
||||
|
||||
<property name="cvs.base.dir" value="java/gnu-crypto" />
|
||||
<property name="cvs.etc.dir" value="${cvs.base.dir}/etc" />
|
||||
<property name="cvs.lib.dir" value="${cvs.base.dir}/lib" />
|
||||
<property name="cvs.object.dir" value="${cvs.base.dir}/classes" />
|
||||
<property name="cvs.base.crypto.object.dir" value="${cvs.object.dir}/gnu/crypto" />
|
||||
<property name="cvs.cipher.object.dir" value="${cvs.base.crypto.object.dir}/cipher" />
|
||||
<property name="cvs.hash.object.dir" value="${cvs.base.crypto.object.dir}/hash" />
|
||||
<property name="cvs.prng.object.dir" value="${cvs.base.crypto.object.dir}/prng" />
|
||||
|
||||
<patternset id="fortuna.files">
|
||||
<include name="${cvs.base.crypto.object.dir}/Registry.class"/>
|
||||
<include name="${cvs.prng.object.dir}/Fortuna*.class"/>
|
||||
<include name="${cvs.prng.object.dir}/BasePRNG.class"/>
|
||||
<include name="${cvs.prng.object.dir}/RandomEventListener.class"/>
|
||||
<include name="${cvs.prng.object.dir}/IRandom.class"/>
|
||||
<include name="${cvs.cipher.object.dir}/CipherFactory.class"/>
|
||||
<include name="${cvs.cipher.object.dir}/IBlockCipher.class"/>
|
||||
<include name="${cvs.hash.object.dir}/HashFactory.class"/>
|
||||
<include name="${cvs.hash.object.dir}/IMessageDigest.class"/>
|
||||
</patternset>
|
||||
|
||||
<target name="all" depends="build,jar"
|
||||
description="Create and test the custom Fortuna library" />
|
||||
|
||||
<target name="build" depends="-init,checkout"
|
||||
description="Build the source and tests">
|
||||
<ant dir="${cvs.base.dir}" target="jar" />
|
||||
</target>
|
||||
|
||||
<target name="builddep" />
|
||||
|
||||
<target name="checkout" depends="-init" unless="cvs.source.available"
|
||||
description="Check out GNU Crypto sources from CVS HEAD">
|
||||
<cvs cvsRoot=":ext:anoncvs@savannah.gnu.org:/cvsroot/gnu-crypto"
|
||||
cvsRsh="ssh"
|
||||
dest="java"
|
||||
package="gnu-crypto" />
|
||||
</target>
|
||||
|
||||
<target name="clean"
|
||||
description="Remove generated tests and object files">
|
||||
<ant dir="${cvs.base.dir}" target="clean" />
|
||||
</target>
|
||||
|
||||
<target name="cleandep" />
|
||||
|
||||
<target name="compile" />
|
||||
|
||||
<target name="distclean" depends="clean"
|
||||
description="Remove all generated files">
|
||||
<delete dir="build" />
|
||||
<delete dir="jartemp" />
|
||||
<!--
|
||||
Annoyingly the GNU Crypto distclean task called here doesn't clean
|
||||
*all* derived files from java/gnu-crypto/lib like it should.....
|
||||
-->
|
||||
<ant dir="${cvs.base.dir}" target="distclean" />
|
||||
<!--
|
||||
.....and so we mop up the rest ourselves.
|
||||
-->
|
||||
<delete dir="${cvs.lib.dir}" />
|
||||
</target>
|
||||
|
||||
<target name="-init">
|
||||
<available property="cvs.source.available" file="${cvs.base.dir}" />
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="build"
|
||||
description="Create the custom Fortuna jar library">
|
||||
<delete dir="build" />
|
||||
<delete dir="jartemp" />
|
||||
<mkdir dir="build" />
|
||||
<mkdir dir="jartemp/${cvs.object.dir}" />
|
||||
<copy todir="jartemp">
|
||||
<fileset dir=".">
|
||||
<patternset refid="fortuna.files" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<jar basedir="jartemp/${cvs.object.dir}" jarfile="build/fortuna.jar">
|
||||
<manifest>
|
||||
<section name="fortuna">
|
||||
<attribute name="Implementation-Title" value="I2P Custom GNU Crypto Fortuna Library" />
|
||||
<attribute name="Implementation-Version" value="CVS HEAD" />
|
||||
<attribute name="Implementation-Vendor" value="Free Software Foundation" />
|
||||
<attribute name="Implementation-Vendor-Id" value="FSF" />
|
||||
<attribute name="Implementation-URL" value="http://www.gnu.org/software/gnu-crypto" />
|
||||
</section>
|
||||
</manifest>
|
||||
</jar>
|
||||
<delete dir="jartemp" />
|
||||
</target>
|
||||
|
||||
<target name="test" depends="jar"
|
||||
description="Perform crypto tests on custom Fortuna jar library" />
|
||||
<!--
|
||||
Add this when Fortuna tests are added to GNU Crypto, else write some
|
||||
-->
|
||||
|
||||
<target name="update" depends="checkout"
|
||||
description="Update GNU Crypto sources to latest CVS HEAD">
|
||||
<cvs command="update -d" cvsRsh="ssh" dest="java/gnu-crypto" />
|
||||
</target>
|
||||
</project>
|
||||
@@ -274,9 +274,9 @@ public class PeerData {
|
||||
|
||||
_lostRate.addData(numTimedOut, 0);
|
||||
|
||||
_receiveRate.coallesceStats();
|
||||
_sendRate.coallesceStats();
|
||||
_lostRate.coallesceStats();
|
||||
_receiveRate.coalesceStats();
|
||||
_sendRate.coalesceStats();
|
||||
_lostRate.coalesceStats();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer data cleaned up " + numTimedOut + " timed out pings and removed " + numDropped
|
||||
@@ -409,4 +409,4 @@ public class PeerData {
|
||||
_wasPonged = pong;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
</war>
|
||||
</target>
|
||||
<target name="precompilejsp">
|
||||
<delete dir="../jsp/WEB-INF/" />
|
||||
<delete file="../jsp/web-fragment.xml" />
|
||||
<delete file="../jsp/web-out.xml" />
|
||||
<mkdir dir="../jsp/WEB-INF/" />
|
||||
<mkdir dir="../jsp/WEB-INF/classes" />
|
||||
<!-- there are various jspc ant tasks, but they all seem a bit flakey -->
|
||||
@@ -39,12 +42,13 @@
|
||||
<pathelement location="../../jetty/jettylib/jasper-compiler.jar" />
|
||||
<pathelement location="../../jetty/jettylib/jasper-runtime.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-el.jar" />
|
||||
<pathelement location="../../jetty/jettylib/ant.jar" />
|
||||
<pathelement location="build/i2ptunnel.jar" />
|
||||
</classpath>
|
||||
<arg value="-d" />
|
||||
<arg value="../jsp/WEB-INF/classes" />
|
||||
<arg value="-v9" />
|
||||
<arg value="-p" />
|
||||
<arg value="net.i2p.i2ptunnel.jsp" />
|
||||
<arg value="-webinc" />
|
||||
@@ -52,10 +56,13 @@
|
||||
<arg value="-webapp" />
|
||||
<arg value="../jsp/" />
|
||||
</java>
|
||||
<javac destdir="../jsp/WEB-INF/classes/" srcdir="../jsp/WEB-INF/classes" includes="*.java">
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="../jsp/WEB-INF/classes/" srcdir="../jsp/WEB-INF/classes" includes="**/*.java">
|
||||
<classpath>
|
||||
<pathelement location="../../jetty/jettylib/jasper-runtime.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-el.jar" />
|
||||
<pathelement location="build/i2ptunnel.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2005 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple stream for delivering an HTTP response to
|
||||
* the client, trivially filtered to make sure "Connection: close"
|
||||
* is always in the response. Perhaps add transparent handling of the
|
||||
* Content-encoding: x-i2p-gzip, adjusting the headers to say Content-encoding: identity?
|
||||
* Content-encoding: gzip is trivial as well, but Transfer-encoding: chunked makes it
|
||||
* more work than is worthwhile at the moment.
|
||||
*
|
||||
*/
|
||||
class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private ByteCache _cache;
|
||||
protected ByteArray _headerBuffer;
|
||||
private boolean _headerWritten;
|
||||
private byte _buf1[];
|
||||
protected boolean _gzip;
|
||||
private long _dataWritten;
|
||||
private InternalGZIPInputStream _in;
|
||||
private static final int CACHE_SIZE = 8*1024;
|
||||
|
||||
public HTTPResponseOutputStream(OutputStream raw) {
|
||||
super(raw);
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_log = _context.logManager().getLog(getClass());
|
||||
_cache = ByteCache.getInstance(8, CACHE_SIZE);
|
||||
_headerBuffer = _cache.acquire();
|
||||
_headerWritten = false;
|
||||
_gzip = false;
|
||||
_dataWritten = 0;
|
||||
_buf1 = new byte[1];
|
||||
}
|
||||
|
||||
public void write(int c) throws IOException {
|
||||
_buf1[0] = (byte)c;
|
||||
write(_buf1, 0, 1);
|
||||
}
|
||||
public void write(byte buf[]) throws IOException {
|
||||
write(buf, 0, buf.length);
|
||||
}
|
||||
public void write(byte buf[], int off, int len) throws IOException {
|
||||
if (_headerWritten) {
|
||||
out.write(buf, off, len);
|
||||
_dataWritten += len;
|
||||
out.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
ensureCapacity();
|
||||
_headerBuffer.getData()[_headerBuffer.getValid()] = buf[off+i];
|
||||
_headerBuffer.setValid(_headerBuffer.getValid()+1);
|
||||
|
||||
if (headerReceived()) {
|
||||
writeHeader();
|
||||
_headerWritten = true;
|
||||
if (i + 1 < len) {
|
||||
// write out the remaining
|
||||
out.write(buf, off+i+1, len-i-1);
|
||||
_dataWritten += len-i-1;
|
||||
out.flush();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** grow (and free) the buffer as necessary */
|
||||
private void ensureCapacity() {
|
||||
if (_headerBuffer.getValid() + 1 >= _headerBuffer.getData().length) {
|
||||
int newSize = (int)(_headerBuffer.getData().length * 1.5);
|
||||
ByteArray newBuf = new ByteArray(new byte[newSize]);
|
||||
System.arraycopy(_headerBuffer.getData(), 0, newBuf.getData(), 0, _headerBuffer.getValid());
|
||||
newBuf.setValid(_headerBuffer.getValid());
|
||||
newBuf.setOffset(0);
|
||||
if (_headerBuffer.getData().length == CACHE_SIZE)
|
||||
_cache.release(_headerBuffer);
|
||||
_headerBuffer = newBuf;
|
||||
}
|
||||
}
|
||||
|
||||
/** are the headers finished? */
|
||||
private boolean headerReceived() {
|
||||
if (_headerBuffer.getValid() < 3) return false;
|
||||
byte first = _headerBuffer.getData()[_headerBuffer.getValid()-3];
|
||||
byte second = _headerBuffer.getData()[_headerBuffer.getValid()-2];
|
||||
byte third = _headerBuffer.getData()[_headerBuffer.getValid()-1];
|
||||
return (isNL(second) && isNL(third)) || // \n\n
|
||||
(isNL(first) && isNL(third)); // \n\r\n
|
||||
}
|
||||
|
||||
/**
|
||||
* Tweak that first HTTP response line (HTTP 200 OK, etc)
|
||||
*
|
||||
*/
|
||||
protected String filterResponseLine(String line) {
|
||||
return line;
|
||||
}
|
||||
|
||||
/** we ignore any potential \r, since we trim it on write anyway */
|
||||
private static final byte NL = '\n';
|
||||
private boolean isNL(byte b) { return (b == NL); }
|
||||
|
||||
/** ok, received, now munge & write it */
|
||||
private void writeHeader() throws IOException {
|
||||
String responseLine = null;
|
||||
|
||||
boolean connectionSent = false;
|
||||
boolean proxyConnectionSent = false;
|
||||
|
||||
int lastEnd = -1;
|
||||
for (int i = 0; i < _headerBuffer.getValid(); i++) {
|
||||
if (isNL(_headerBuffer.getData()[i])) {
|
||||
if (lastEnd == -1) {
|
||||
responseLine = new String(_headerBuffer.getData(), 0, i+1); // includes NL
|
||||
responseLine = filterResponseLine(responseLine);
|
||||
responseLine = (responseLine.trim() + "\n");
|
||||
out.write(responseLine.getBytes());
|
||||
} else {
|
||||
for (int j = lastEnd+1; j < i; j++) {
|
||||
if (_headerBuffer.getData()[j] == ':') {
|
||||
int keyLen = j-(lastEnd+1);
|
||||
int valLen = i-(j+2);
|
||||
if ( (keyLen <= 0) || (valLen <= 0) )
|
||||
throw new IOException("Invalid header @ " + j);
|
||||
String key = new String(_headerBuffer.getData(), lastEnd+1, keyLen);
|
||||
String val = new String(_headerBuffer.getData(), j+2, valLen).trim();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Response header [" + key + "] = [" + val + "]");
|
||||
|
||||
if ("Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Connection: close\n".getBytes());
|
||||
connectionSent = true;
|
||||
} else if ("Proxy-Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
proxyConnectionSent = true;
|
||||
} else if ( ("Content-encoding".equalsIgnoreCase(key)) && ("x-i2p-gzip".equalsIgnoreCase(val)) ) {
|
||||
_gzip = true;
|
||||
} else {
|
||||
out.write((key.trim() + ": " + val.trim() + "\n").getBytes());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastEnd = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (!connectionSent)
|
||||
out.write("Connection: close\n".getBytes());
|
||||
if (!proxyConnectionSent)
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
|
||||
finishHeaders();
|
||||
|
||||
boolean shouldCompress = shouldCompress();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After headers: gzip? " + _gzip + " compress? " + shouldCompress);
|
||||
|
||||
// done, shove off
|
||||
if (_headerBuffer.getData().length == CACHE_SIZE)
|
||||
_cache.release(_headerBuffer);
|
||||
else
|
||||
_headerBuffer = null;
|
||||
if (shouldCompress) {
|
||||
beginProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean shouldCompress() { return _gzip; }
|
||||
|
||||
protected void finishHeaders() throws IOException {
|
||||
out.write("\n".getBytes()); // end of the headers
|
||||
}
|
||||
|
||||
protected void beginProcessing() throws IOException {
|
||||
//out.flush();
|
||||
PipedInputStream pi = new PipedInputStream();
|
||||
PipedOutputStream po = new PipedOutputStream(pi);
|
||||
new I2PThread(new Pusher(pi, out), "HTTP decompresser").start();
|
||||
out = po;
|
||||
}
|
||||
|
||||
private class Pusher implements Runnable {
|
||||
private InputStream _inRaw;
|
||||
private OutputStream _out;
|
||||
public Pusher(InputStream in, OutputStream out) {
|
||||
_inRaw = in;
|
||||
_out = out;
|
||||
}
|
||||
public void run() {
|
||||
OutputStream to = null;
|
||||
_in = null;
|
||||
long start = System.currentTimeMillis();
|
||||
long written = 0;
|
||||
try {
|
||||
_in = new InternalGZIPInputStream(_inRaw);
|
||||
byte buf[] = new byte[8192];
|
||||
int read = -1;
|
||||
while ( (read = _in.read(buf)) != -1) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read " + read + " and writing it to the browser/streams");
|
||||
_out.write(buf, 0, read);
|
||||
_out.flush();
|
||||
written += read;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Decompressed: " + written + ", " + _in.getTotalRead() + "/" + _in.getTotalExpanded());
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error decompressing: " + written + ", " + (_in != null ? _in.getTotalRead() + "/" + _in.getTotalExpanded() : ""), ioe);
|
||||
} finally {
|
||||
if (_log.shouldLog(Log.WARN) && (_in != null))
|
||||
_log.warn("After decompression, written=" + written +
|
||||
(_in != null ?
|
||||
" read=" + _in.getTotalRead()
|
||||
+ ", expanded=" + _in.getTotalExpanded() + ", remaining=" + _in.getRemaining()
|
||||
+ ", finished=" + _in.getFinished()
|
||||
: ""));
|
||||
if (_out != null) try {
|
||||
_out.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
double compressed = (_in != null ? _in.getTotalRead() : 0);
|
||||
double expanded = (_in != null ? _in.getTotalExpanded() : 0);
|
||||
double ratio = 0;
|
||||
if (expanded > 0)
|
||||
ratio = compressed/expanded;
|
||||
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, end-start);
|
||||
}
|
||||
}
|
||||
private class InternalGZIPInputStream extends GZIPInputStream {
|
||||
public InternalGZIPInputStream(InputStream in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
public long getTotalRead() { return super.inf.getTotalIn(); }
|
||||
public long getTotalExpanded() { return super.inf.getTotalOut(); }
|
||||
public long getRemaining() { return super.inf.getRemaining(); }
|
||||
public boolean getFinished() { return super.inf.finished(); }
|
||||
public String toString() {
|
||||
return "Read: " + getTotalRead() + " expanded: " + getTotalExpanded() + " remaining: " + getRemaining() + " finished: " + getFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return super.toString() + ": " + _in;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
String simple = "HTTP/1.1 200 OK\n" +
|
||||
"foo: bar\n" +
|
||||
"baz: bat\n" +
|
||||
"\n" +
|
||||
"hi ho, this is the body";
|
||||
String filtered = "HTTP/1.1 200 OK\n" +
|
||||
"Connection: keep-alive\n" +
|
||||
"foo: bar\n" +
|
||||
"baz: bat\n" +
|
||||
"\n" +
|
||||
"hi ho, this is the body";
|
||||
String winfilter= "HTTP/1.1 200 OK\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"foo: bar\r\n" +
|
||||
"baz: bat\r\n" +
|
||||
"\r\n" +
|
||||
"hi ho, this is the body";
|
||||
String minimal = "HTTP/1.1 200 OK\n" +
|
||||
"\n" +
|
||||
"hi ho, this is the body";
|
||||
String winmin = "HTTP/1.1 200 OK\r\n" +
|
||||
"\r\n" +
|
||||
"hi ho, this is the body";
|
||||
String invalid1 = "HTTP/1.1 200 OK\n";
|
||||
String invalid2 = "HTTP/1.1 200 OK";
|
||||
String invalid3 = "HTTP 200 OK\r\n";
|
||||
String invalid4 = "HTTP 200 OK\r";
|
||||
String invalid5 = "HTTP/1.1 200 OK\r\n" +
|
||||
"I am broken, and I smell\r\n" +
|
||||
"\r\n";
|
||||
String invalid6 = "HTTP/1.1 200 OK\r\n" +
|
||||
":I am broken, and I smell\r\n" +
|
||||
"\r\n";
|
||||
String invalid7 = "HTTP/1.1 200 OK\n" +
|
||||
"I am broken, and I smell:\n" +
|
||||
":asdf\n" +
|
||||
":\n" +
|
||||
"\n";
|
||||
String large = "HTTP/1.1 200 OK\n" +
|
||||
"Last-modified: Tue, 25 Nov 2003 12:05:38 GMT\n" +
|
||||
"Expires: Tue, 25 Nov 2003 12:05:38 GMT\n" +
|
||||
"Content-length: 32\n" +
|
||||
"\n" +
|
||||
"hi ho, this is the body";
|
||||
/* */
|
||||
test("Simple", simple, true);
|
||||
test("Filtered", filtered, true);
|
||||
test("Filtered windows", winfilter, true);
|
||||
test("Minimal", minimal, true);
|
||||
test("Windows", winmin, true);
|
||||
test("Large", large, true);
|
||||
test("Invalid (short headers)", invalid1, true);
|
||||
test("Invalid (no headers)", invalid2, true);
|
||||
test("Invalid (windows with short headers)", invalid3, true);
|
||||
test("Invalid (windows no headers)", invalid4, true);
|
||||
test("Invalid (bad headers)", invalid5, true);
|
||||
test("Invalid (bad headers2)", invalid6, false);
|
||||
test("Invalid (bad headers3)", invalid7, false);
|
||||
/* */
|
||||
}
|
||||
|
||||
private static void test(String name, String orig, boolean shouldPass) {
|
||||
System.out.println("====Testing: " + name + "\n" + orig + "\n------------");
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
|
||||
HTTPResponseOutputStream resp = new HTTPResponseOutputStream(baos);
|
||||
resp.write(orig.getBytes());
|
||||
resp.flush();
|
||||
String received = new String(baos.toByteArray());
|
||||
System.out.println(received);
|
||||
} catch (Exception e) {
|
||||
if (shouldPass)
|
||||
e.printStackTrace();
|
||||
else
|
||||
System.out.println("Properly fails with " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
* not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -108,8 +109,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
_tunnelId = ++__tunnelId;
|
||||
_log = _context.logManager().getLog(I2PTunnel.class);
|
||||
_event = new EventDispatcherImpl();
|
||||
_clientOptions = new Properties();
|
||||
_clientOptions.putAll(System.getProperties());
|
||||
Properties p = new Properties();
|
||||
p.putAll(System.getProperties());
|
||||
_clientOptions = p;
|
||||
_sessions = new ArrayList(1);
|
||||
|
||||
addConnectionEventListener(lsnr);
|
||||
@@ -183,7 +185,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
void addSession(I2PSession session) {
|
||||
if (session == null) return;
|
||||
synchronized (_sessions) {
|
||||
_sessions.add(session);
|
||||
if (!_sessions.contains(session))
|
||||
_sessions.add(session);
|
||||
}
|
||||
}
|
||||
void removeSession(I2PSession session) {
|
||||
@@ -229,6 +232,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
runClientOptions(args, l);
|
||||
} else if ("server".equals(cmdname)) {
|
||||
runServer(args, l);
|
||||
} else if ("httpserver".equals(cmdname)) {
|
||||
runHttpServer(args, l);
|
||||
} else if ("textserver".equals(cmdname)) {
|
||||
runTextServer(args, l);
|
||||
} else if ("client".equals(cmdname)) {
|
||||
@@ -281,11 +286,12 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("owndest yes|no");
|
||||
l.log("ping <args>");
|
||||
l.log("server <host> <port> <privkeyfile>");
|
||||
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
|
||||
l.log("textserver <host> <port> <privkey>");
|
||||
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
|
||||
l.log("gentextkeys");
|
||||
l.log("client <port> <pubkey>|file:<pubkeyfile>");
|
||||
l.log("httpclient <port>");
|
||||
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
|
||||
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
|
||||
l.log("lookup <name>");
|
||||
l.log("quit");
|
||||
l.log("close [forced] <jobnumber>|all");
|
||||
@@ -370,6 +376,65 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the HTTP server pointing at the host and port specified using the private i2p
|
||||
* destination loaded from the specified file, replacing the HTTP headers
|
||||
* so that the Host: specified is the one spoofed. <p />
|
||||
*
|
||||
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
|
||||
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
|
||||
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
|
||||
*
|
||||
* @param args {hostname, portNumber, spoofedHost, privKeyFilename}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runHttpServer(String args[], Logging l) {
|
||||
if (args.length == 4) {
|
||||
InetAddress serverHost = null;
|
||||
int portNum = -1;
|
||||
File privKeyFile = null;
|
||||
try {
|
||||
serverHost = InetAddress.getByName(args[0]);
|
||||
} catch (UnknownHostException uhe) {
|
||||
l.log("unknown host");
|
||||
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
portNum = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
String spoofedHost = args[2];
|
||||
|
||||
privKeyFile = new File(args[3]);
|
||||
if (!privKeyFile.canRead()) {
|
||||
l.log("private key file does not exist");
|
||||
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
I2PTunnelHTTPServer serv = new I2PTunnelHTTPServer(serverHost, portNum, privKeyFile, args[3], spoofedHost, l, (EventDispatcher) this, this);
|
||||
serv.setReadTimeout(readTimeout);
|
||||
serv.startRunning();
|
||||
addtask(serv);
|
||||
notifyEvent("serverTaskId", new Integer(serv.getId()));
|
||||
return;
|
||||
} else {
|
||||
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
|
||||
l.log(" creates an HTTP server that sends all incoming data\n"
|
||||
+ " of its destination to host:port., filtering the HTTP\n"
|
||||
+ " headers so it looks like the request is to the spoofed host.");
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the server pointing at the host and port specified using the private i2p
|
||||
* destination loaded from the given base64 stream. <p />
|
||||
@@ -423,12 +488,16 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
* Also sets the event "openClientResult" = "error" or "ok" (before setting the value to "ok" it also
|
||||
* adds "Ready! Port #" to the logger as well). In addition, it will also set "clientLocalPort" =
|
||||
* Integer port number if the client is listening
|
||||
* sharedClient parameter is a String "true" or "false"
|
||||
*
|
||||
* @param args {portNumber, destinationBase64 or "file:filename"}
|
||||
* @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient]}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runClient(String args[], Logging l) {
|
||||
if (args.length == 2) {
|
||||
boolean isShared = true;
|
||||
if (args.length == 3)
|
||||
isShared = Boolean.valueOf(args[2].trim()).booleanValue();
|
||||
if ( (args.length == 2) || (args.length == 3) ) {
|
||||
int portNum = -1;
|
||||
try {
|
||||
portNum = Integer.parseInt(args[0]);
|
||||
@@ -439,6 +508,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
return;
|
||||
}
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
@@ -449,9 +519,12 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
notifyEvent("clientTaskId", new Integer(-1));
|
||||
}
|
||||
} else {
|
||||
l.log("client <port> <pubkey>|file:<pubkeyfile>");
|
||||
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>]");
|
||||
l.log(" creates a client that forwards port to the pubkey.\n"
|
||||
+ " use 0 as port to get a free port assigned.");
|
||||
+ " use 0 as port to get a free port assigned. If you specify\n"
|
||||
+ " a comma delimited list of pubkeys, it will rotate among them\n"
|
||||
+ " randomlyl. sharedClient indicates if this client shares \n"
|
||||
+ " with other clients (true of false)");
|
||||
notifyEvent("clientTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
@@ -461,12 +534,13 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* Sets the event "httpclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
|
||||
* Also sets "httpclientStatus" = "ok" or "error" after the client tunnel has started.
|
||||
* parameter sharedClient is a String, either "true" or "false"
|
||||
*
|
||||
* @param args {portNumber and (optionally) proxy to be used for the WWW}
|
||||
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runHttpClient(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 2) {
|
||||
if (args.length >= 1 && args.length <= 3) {
|
||||
int port = -1;
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
@@ -476,12 +550,32 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
notifyEvent("httpclientTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String proxy = "squid.i2p";
|
||||
if (args.length == 2) {
|
||||
proxy = args[1];
|
||||
boolean isShared = true;
|
||||
if (args.length > 1) {
|
||||
if ("true".equalsIgnoreCase(args[1].trim())) {
|
||||
isShared = true;
|
||||
if (args.length == 3)
|
||||
proxy = args[2];
|
||||
} else if ("false".equalsIgnoreCase(args[1].trim())) {
|
||||
_log.warn("args[1] == [" + args[1] + "] and rejected explicitly");
|
||||
isShared = false;
|
||||
if (args.length == 3)
|
||||
proxy = args[2];
|
||||
} else if (args.length == 3) {
|
||||
isShared = false; // not "true"
|
||||
proxy = args[2];
|
||||
_log.warn("args[1] == [" + args[1] + "] but rejected");
|
||||
} else {
|
||||
// isShared not specified, default to true
|
||||
isShared = true;
|
||||
proxy = args[1];
|
||||
}
|
||||
}
|
||||
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
task = new I2PTunnelHTTPClient(port, l, ownDest, proxy, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
@@ -492,8 +586,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
notifyEvent("httpclientTaskId", new Integer(-1));
|
||||
}
|
||||
} else {
|
||||
l.log("httpclient <port> [<proxy>]");
|
||||
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
|
||||
l.log(" creates a client that distributes HTTP requests.");
|
||||
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
|
||||
l.log(" <proxy> (optional) indicates a proxy server to be used");
|
||||
l.log(" when trying to access an address out of the .i2p domain");
|
||||
l.log(" (the default proxy is squid.i2p).");
|
||||
@@ -1052,6 +1147,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
|
||||
private String getPrefix() { return '[' + _tunnelId + "]: "; }
|
||||
|
||||
public I2PAppContext getContext() { return _context; }
|
||||
|
||||
/**
|
||||
* Call this whenever we lose touch with the router involuntarily (aka the router
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
@@ -15,15 +19,17 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
|
||||
private static final Log _log = new Log(I2PTunnelClient.class);
|
||||
|
||||
protected Destination dest;
|
||||
/** list of Destination objects that we point at */
|
||||
protected List dests;
|
||||
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
/**
|
||||
* @param destinations comma delimited list of peers we target
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
*/
|
||||
public I2PTunnelClient(int localPort, String destination, Logging l,
|
||||
public I2PTunnelClient(int localPort, String destinations, Logging l,
|
||||
boolean ownDest, EventDispatcher notifyThis,
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, "SynSender", tunnel);
|
||||
@@ -33,19 +39,28 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
dest = I2PTunnel.destFromName(destination);
|
||||
if (dest == null) {
|
||||
l.log("Could not resolve " + destination + ".");
|
||||
return;
|
||||
StringTokenizer tok = new StringTokenizer(destinations, ",");
|
||||
dests = new ArrayList(1);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String destination = tok.nextToken();
|
||||
try {
|
||||
Destination dest = I2PTunnel.destFromName(destination);
|
||||
if (dest == null)
|
||||
l.log("Could not resolve " + destination);
|
||||
else
|
||||
dests.add(dest);
|
||||
} catch (DataFormatException dfe) {
|
||||
l.log("Bad format parsing \"" + destination + "\"");
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
l.log("Bad format in destination \"" + destination + "\".");
|
||||
}
|
||||
|
||||
if (dests.size() <= 0) {
|
||||
l.log("No target destinations found");
|
||||
notifyEvent("openClientResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> " + destination);
|
||||
setName(getLocalPort() + " -> " + destinations);
|
||||
|
||||
startRunning();
|
||||
|
||||
@@ -56,14 +71,34 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
public long getReadTimeout() { return readTimeout; }
|
||||
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
Destination dest = pickDestination();
|
||||
I2PSocket i2ps = null;
|
||||
try {
|
||||
I2PSocket i2ps = createI2PSocket(dest);
|
||||
i2ps = createI2PSocket(dest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
new I2PTunnelRunner(s, i2ps, sockLock, null);
|
||||
new I2PTunnelRunner(s, i2ps, sockLock, null, mySockets);
|
||||
} catch (Exception ex) {
|
||||
_log.info("Error connecting", ex);
|
||||
l.log(ex.getMessage());
|
||||
closeSocket(s);
|
||||
if (i2ps != null) {
|
||||
synchronized (sockLock) {
|
||||
mySockets.remove(sockLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Destination pickDestination() {
|
||||
int size = dests.size();
|
||||
if (size <= 0) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("No client targets?!");
|
||||
return null;
|
||||
}
|
||||
if (size == 1) // skip the rand in the most common case
|
||||
return (Destination)dests.get(0);
|
||||
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
|
||||
return (Destination)dests.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
@@ -24,6 +25,7 @@ import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable {
|
||||
@@ -31,13 +33,13 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
private static final Log _log = new Log(I2PTunnelClientBase.class);
|
||||
protected Logging l;
|
||||
|
||||
private static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
|
||||
static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
|
||||
|
||||
private static volatile long __clientId = 0;
|
||||
protected long _clientId;
|
||||
protected Object sockLock = new Object(); // Guards sockMgr and mySockets
|
||||
private I2PSocketManager sockMgr;
|
||||
private List mySockets = new ArrayList();
|
||||
protected I2PSocketManager sockMgr;
|
||||
protected List mySockets = new ArrayList();
|
||||
|
||||
protected Destination dest = null;
|
||||
private int localPort;
|
||||
@@ -55,6 +57,32 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
private String handlerName;
|
||||
|
||||
private Object conLock = new Object();
|
||||
|
||||
/** List of Socket for those accept()ed but not yet started up */
|
||||
private List _waitingSockets;
|
||||
/** How many connections will we allow to be in the process of being built at once? */
|
||||
private int _numConnectionBuilders;
|
||||
/** How long will we allow sockets to sit in the _waitingSockets map before killing them? */
|
||||
private int _maxWaitTime;
|
||||
|
||||
/**
|
||||
* How many concurrent connections this I2PTunnel instance will allow to be
|
||||
* in the process of connecting (or if less than 1, there is no limit)?
|
||||
*/
|
||||
public static final String PROP_NUM_CONNECTION_BUILDERS = "i2ptunnel.numConnectionBuilders";
|
||||
/**
|
||||
* How long will we let a socket wait after being accept()ed without getting
|
||||
* pumped through a connection builder (in milliseconds). If this time is
|
||||
* reached, the socket is unceremoniously closed and discarded. If the max
|
||||
* wait time is less than 1, there is no limit.
|
||||
*
|
||||
*/
|
||||
public static final String PROP_MAX_WAIT_TIME = "i2ptunnel.maxWaitTime";
|
||||
|
||||
private static final int DEFAULT_NUM_CONNECTION_BUILDERS = 5;
|
||||
private static final int DEFAULT_MAX_WAIT_TIME = 30*1000;
|
||||
|
||||
//public I2PTunnelClientBase(int localPort, boolean ownDest,
|
||||
// Logging l) {
|
||||
// I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
|
||||
@@ -73,11 +101,21 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
this.l = l;
|
||||
this.handlerName = handlerName + _clientId;
|
||||
|
||||
synchronized (sockLock) {
|
||||
if (ownDest) {
|
||||
sockMgr = buildSocketManager();
|
||||
} else {
|
||||
sockMgr = getSocketManager();
|
||||
// no need to load the netDb with leaseSets for destinations that will never
|
||||
// be looked up
|
||||
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
|
||||
|
||||
while (sockMgr == null) {
|
||||
synchronized (sockLock) {
|
||||
if (ownDest) {
|
||||
sockMgr = buildSocketManager();
|
||||
} else {
|
||||
sockMgr = getSocketManager();
|
||||
}
|
||||
}
|
||||
if (sockMgr == null) {
|
||||
_log.log(Log.CRIT, "Unable to create socket manager (our own? " + ownDest + ")");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
if (sockMgr == null) {
|
||||
@@ -94,7 +132,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
t.start();
|
||||
open = true;
|
||||
synchronized (this) {
|
||||
while (!listenerReady) {
|
||||
while (!listenerReady && open) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
@@ -103,14 +141,47 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
configurePool(tunnel);
|
||||
|
||||
if (open && listenerReady) {
|
||||
l.log("Ready! Port " + getLocalPort());
|
||||
notifyEvent("openBaseClientResult", "ok");
|
||||
} else {
|
||||
l.log("Error!");
|
||||
l.log("Error listening - please see the logs!");
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* build and configure the pool handling accept()ed but not yet
|
||||
* established connections
|
||||
*
|
||||
*/
|
||||
private void configurePool(I2PTunnel tunnel) {
|
||||
_waitingSockets = new ArrayList(4);
|
||||
|
||||
Properties opts = tunnel.getClientOptions();
|
||||
String maxWait = opts.getProperty(PROP_MAX_WAIT_TIME, DEFAULT_MAX_WAIT_TIME+"");
|
||||
try {
|
||||
_maxWaitTime = Integer.parseInt(maxWait);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_maxWaitTime = DEFAULT_MAX_WAIT_TIME;
|
||||
}
|
||||
|
||||
String numBuild = opts.getProperty(PROP_NUM_CONNECTION_BUILDERS, DEFAULT_NUM_CONNECTION_BUILDERS+"");
|
||||
try {
|
||||
_numConnectionBuilders = Integer.parseInt(numBuild);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_numConnectionBuilders = DEFAULT_NUM_CONNECTION_BUILDERS;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _numConnectionBuilders; i++) {
|
||||
String name = "ClientBuilder" + _clientId + '.' + i;
|
||||
I2PThread b = new I2PThread(new TunnelConnectionBuilder(), name);
|
||||
b.setDaemon(true);
|
||||
b.start();
|
||||
}
|
||||
}
|
||||
|
||||
private static I2PSocketManager socketManager;
|
||||
|
||||
@@ -118,7 +189,16 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
return getSocketManager(getTunnel());
|
||||
}
|
||||
protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel) {
|
||||
if (socketManager == null) {
|
||||
if (socketManager != null) {
|
||||
I2PSession s = socketManager.getSession();
|
||||
if ( (s == null) || (s.isClosed()) ) {
|
||||
_log.info("Building a new socket manager since the old one closed [s=" + s + "]");
|
||||
socketManager = buildSocketManager(tunnel);
|
||||
} else {
|
||||
_log.info("Not building a new socket manager since the old one is open [s=" + s + "]");
|
||||
}
|
||||
} else {
|
||||
_log.info("Building a new socket manager since there is no other one");
|
||||
socketManager = buildSocketManager(tunnel);
|
||||
}
|
||||
return socketManager;
|
||||
@@ -133,8 +213,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
props.putAll(System.getProperties());
|
||||
else
|
||||
props.putAll(tunnel.getClientOptions());
|
||||
I2PSocketManager sockManager = I2PSocketManagerFactory.createManager(tunnel.host, Integer.parseInt(tunnel.port), props);
|
||||
if (sockManager == null) return null;
|
||||
int portNum = 7654;
|
||||
if (tunnel.port != null) {
|
||||
try {
|
||||
portNum = Integer.parseInt(tunnel.port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.log(Log.CRIT, "Invalid port specified [" + tunnel.port + "], reverting to " + portNum);
|
||||
}
|
||||
}
|
||||
|
||||
I2PSocketManager sockManager = null;
|
||||
while (sockManager == null) {
|
||||
sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
|
||||
|
||||
if (sockManager == null) {
|
||||
_log.log(Log.CRIT, "Unable to create socket manager");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
sockManager.setName("Client");
|
||||
return sockManager;
|
||||
}
|
||||
@@ -170,9 +266,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
*/
|
||||
private I2PSocketOptions getDefaultOptions() {
|
||||
I2PSocketOptions opts = new I2PSocketOptions();
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
protected I2PSocketOptions getDefaultOptions() {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
return opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
*/
|
||||
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
defaultOpts.putAll(overrides);
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
return opts;
|
||||
}
|
||||
|
||||
@@ -216,7 +327,14 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
public final void run() {
|
||||
try {
|
||||
InetAddress addr = getListenHost(l);
|
||||
if (addr == null) return;
|
||||
if (addr == null) {
|
||||
open = false;
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }
|
||||
return;
|
||||
}
|
||||
ss = new ServerSocket(localPort, 0, addr);
|
||||
|
||||
// If a free port was requested, find out what we got
|
||||
@@ -247,8 +365,18 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
manageConnection(s);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error listening for connections", ex);
|
||||
_log.error("Error listening for connections on " + localPort, ex);
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
synchronized (sockLock) {
|
||||
mySockets.clear();
|
||||
}
|
||||
open = false;
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
synchronized (_waitingSockets) {
|
||||
_waitingSockets.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +386,52 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
* @param s Socket to take care of
|
||||
*/
|
||||
protected void manageConnection(Socket s) {
|
||||
new ClientConnectionRunner(s, handlerName);
|
||||
if (s == null) return;
|
||||
if (_numConnectionBuilders <= 0) {
|
||||
new I2PThread(new BlockingRunner(s), "Clinet run").start();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_maxWaitTime > 0)
|
||||
SimpleTimer.getInstance().addEvent(new CloseEvent(s), _maxWaitTime);
|
||||
|
||||
synchronized (_waitingSockets) {
|
||||
_waitingSockets.add(s);
|
||||
_waitingSockets.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking runner, used during the connection establishment whenever we
|
||||
* are not using the queued builders.
|
||||
*
|
||||
*/
|
||||
private class BlockingRunner implements Runnable {
|
||||
private Socket _s;
|
||||
public BlockingRunner(Socket s) { _s = s; }
|
||||
public void run() {
|
||||
clientConnectionRun(_s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove and close the socket from the waiting list, if it is still there.
|
||||
*
|
||||
*/
|
||||
private class CloseEvent implements SimpleTimer.TimedEvent {
|
||||
private Socket _s;
|
||||
public CloseEvent(Socket s) { _s = s; }
|
||||
public void timeReached() {
|
||||
boolean stillWaiting = false;
|
||||
synchronized (_waitingSockets) {
|
||||
stillWaiting = _waitingSockets.remove(_s);
|
||||
}
|
||||
if (stillWaiting) {
|
||||
try { _s.close(); } catch (IOException ioe) {}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Closed a waiting socket because of backlog");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
@@ -277,7 +450,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
return false;
|
||||
}
|
||||
getTunnel().removeSession(sockMgr.getSession());
|
||||
I2PSession session = sockMgr.getSession();
|
||||
if (session != null) {
|
||||
getTunnel().removeSession(session);
|
||||
}
|
||||
l.log("Closing client " + toString());
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
@@ -287,8 +463,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
l.log("Client closed.");
|
||||
open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void closeSocket(Socket s) {
|
||||
@@ -298,20 +476,30 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
_log.error("Could not close socket", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile long __runnerId = 0;
|
||||
|
||||
public class ClientConnectionRunner extends I2PThread {
|
||||
private Socket s;
|
||||
|
||||
public ClientConnectionRunner(Socket s, String name) {
|
||||
this.s = s;
|
||||
setName(name + '.' + (++__runnerId));
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
clientConnectionRun(s);
|
||||
/**
|
||||
* Pool runner pulling sockets off the waiting list and pushing them
|
||||
* through clientConnectionRun. This dies when the I2PTunnel instance
|
||||
* is closed.
|
||||
*
|
||||
*/
|
||||
private class TunnelConnectionBuilder implements Runnable {
|
||||
public void run() {
|
||||
Socket s = null;
|
||||
while (open) {
|
||||
try {
|
||||
synchronized (_waitingSockets) {
|
||||
if (_waitingSockets.size() <= 0)
|
||||
_waitingSockets.wait();
|
||||
else
|
||||
s = (Socket)_waitingSockets.remove(0);
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
if (s != null)
|
||||
clientConnectionRun(s);
|
||||
s = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,15 +14,16 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -70,7 +71,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"wrong BASE64 I2P Destination or the link you are following is "+
|
||||
"bad. The host (or the WWW proxy, if you're using one) could also "+
|
||||
"be temporarily offline. You may want to <b>retry</b>. "+
|
||||
"Could not find the following Destination:<BR><BR>")
|
||||
"Could not find the following Destination:<BR><BR><div>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_TIMEOUT =
|
||||
@@ -95,6 +96,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_AHELPER_CONFLICT =
|
||||
("HTTP/1.1 409 Conflict\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: Destination key conflict</H1>"+
|
||||
"The addresshelper link you followed specifies a different destination key "+
|
||||
"than a host entry in your host database. "+
|
||||
"Someone could be trying to impersonate another eepsite, "+
|
||||
"or people have given two eepsites identical names.<P/>"+
|
||||
"You can resolve the conflict by considering which key you trust, "+
|
||||
"and either discarding the addresshelper link, "+
|
||||
"discarding the host entry from your host database, "+
|
||||
"or naming one of them differently.<P/>")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
@@ -142,6 +159,43 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int DEFAULT_READ_TIMEOUT = 60*1000;
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
*/
|
||||
protected I2PSocketOptions getDefaultOptions() {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
|
||||
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
|
||||
//if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
|
||||
// defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
return opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
*/
|
||||
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
defaultOpts.putAll(overrides);
|
||||
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
|
||||
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
|
||||
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
|
||||
defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
return opts;
|
||||
}
|
||||
|
||||
private static final boolean DEFAULT_GZIP = true;
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
@@ -149,20 +203,21 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
String targetRequest = null;
|
||||
boolean usingWWWProxy = false;
|
||||
String currentProxy = null;
|
||||
InactivityTimeoutThread timeoutThread = null;
|
||||
long requestId = ++__requestId;
|
||||
try {
|
||||
out = s.getOutputStream();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "ISO-8859-1"));
|
||||
String line, method = null, protocol = null, host = null, destination = null;
|
||||
StringBuffer newRequest = new StringBuffer();
|
||||
int ahelper = 0;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
|
||||
|
||||
if (line.startsWith("Connection: ") ||
|
||||
line.startsWith("Keep-Alive: ") ||
|
||||
line.startsWith("Proxy-Connection: "))
|
||||
String lowercaseLine = line.toLowerCase();
|
||||
if (lowercaseLine.startsWith("connection: ") ||
|
||||
lowercaseLine.startsWith("keep-alive: ") ||
|
||||
lowercaseLine.startsWith("proxy-connection: "))
|
||||
continue;
|
||||
|
||||
if (method == null) { // first line (GET /base64/realaddr)
|
||||
@@ -175,7 +230,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
String request = line.substring(pos + 1);
|
||||
if (request.startsWith("/") && getTunnel().getClientOptions().getProperty("i2ptunnel.noproxy") != null) {
|
||||
request = "http://i2p" + request;
|
||||
} else if (request.startsWith("/eepproxy/")) {
|
||||
// /eepproxy/foo.i2p/bar/baz.html HTTP/1.0
|
||||
String subRequest = request.substring("/eepproxy/".length());
|
||||
int protopos = subRequest.indexOf(" ");
|
||||
String uri = subRequest.substring(0, protopos);
|
||||
if (uri.indexOf("/") == -1) {
|
||||
uri = uri + "/";
|
||||
}
|
||||
// "http://" + "foo.i2p/bar/baz.html" + " HTTP/1.0"
|
||||
request = "http://" + uri + subRequest.substring(protopos);
|
||||
}
|
||||
|
||||
pos = request.indexOf("//");
|
||||
if (pos == -1) {
|
||||
method = null;
|
||||
@@ -195,36 +261,102 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
// Quick hack for foo.bar.i2p
|
||||
if (host.toLowerCase().endsWith(".i2p")) {
|
||||
// Destination gets the host name
|
||||
destination = host;
|
||||
// Host becomes the destination key
|
||||
host = getHostName(destination);
|
||||
if ( (host != null) && ("i2p".equals(host)) ) {
|
||||
int pos2;
|
||||
if ((pos2 = line.indexOf("?")) != -1) {
|
||||
// Try to find an address helper in the fragments
|
||||
String fragments = line.substring(pos2 + 1);
|
||||
pos2 = fragments.indexOf(" ");
|
||||
fragments = fragments.substring(0, pos2);
|
||||
fragments = fragments + "&";
|
||||
String fragment;
|
||||
while(fragments.length() > 0) {
|
||||
pos2 = fragments.indexOf("&");
|
||||
fragment = fragments.substring(0, pos2);
|
||||
fragments = fragments.substring(pos2 + 1);
|
||||
if (fragment.startsWith("i2paddresshelper")) {
|
||||
pos2 = fragment.indexOf("=");
|
||||
if (pos2 >= 0) {
|
||||
addressHelpers.put(destination,fragment.substring(pos2 + 1));
|
||||
|
||||
int pos2;
|
||||
if ((pos2 = request.indexOf("?")) != -1) {
|
||||
// Try to find an address helper in the fragments
|
||||
// and split the request into it's component parts for rebuilding later
|
||||
String ahelperKey = null;
|
||||
boolean ahelperConflict = false;
|
||||
|
||||
String fragments = request.substring(pos2 + 1);
|
||||
String uriPath = request.substring(0, pos2);
|
||||
pos2 = fragments.indexOf(" ");
|
||||
String protocolVersion = fragments.substring(pos2 + 1);
|
||||
String urlEncoding = "";
|
||||
fragments = fragments.substring(0, pos2);
|
||||
String initialFragments = fragments;
|
||||
fragments = fragments + "&";
|
||||
String fragment;
|
||||
while(fragments.length() > 0) {
|
||||
pos2 = fragments.indexOf("&");
|
||||
fragment = fragments.substring(0, pos2);
|
||||
fragments = fragments.substring(pos2 + 1);
|
||||
|
||||
// Fragment looks like addresshelper key
|
||||
if (fragment.startsWith("i2paddresshelper=")) {
|
||||
pos2 = fragment.indexOf("=");
|
||||
ahelperKey = fragment.substring(pos2 + 1);
|
||||
|
||||
// Key contains data, lets not ignore it
|
||||
if (ahelperKey != null) {
|
||||
|
||||
// Host resolvable only with addresshelper
|
||||
if ( (host == null) || ("i2p".equals(host)) )
|
||||
{
|
||||
// Cannot check, use addresshelper key
|
||||
addressHelpers.put(destination,ahelperKey);
|
||||
} else {
|
||||
// Host resolvable from database, verify addresshelper key
|
||||
// Silently bypass correct keys, otherwise alert
|
||||
if (!host.equals(ahelperKey))
|
||||
{
|
||||
// Conflict: handle when URL reconstruction done
|
||||
ahelperConflict = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + host + "], specified key [" + ahelperKey + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other fragments, just pass along
|
||||
// Append each fragment to urlEncoding
|
||||
if ("".equals(urlEncoding)) {
|
||||
urlEncoding = "?" + fragment;
|
||||
} else {
|
||||
urlEncoding = urlEncoding + "&" + fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reconstruct the request minus the i2paddresshelper GET var
|
||||
request = uriPath + urlEncoding + " " + protocolVersion;
|
||||
|
||||
// Did addresshelper key conflict?
|
||||
if (ahelperConflict)
|
||||
{
|
||||
String str;
|
||||
byte[] header;
|
||||
str = FileUtil.readTextFile("docs/ahelper-conflict-header.ht", 100, true);
|
||||
if (str != null) header = str.getBytes();
|
||||
else header = ERR_AHELPER_CONFLICT;
|
||||
|
||||
String addressHelper = (String) addressHelpers.get(destination);
|
||||
if (addressHelper != null) {
|
||||
destination = addressHelper;
|
||||
host = getHostName(destination);
|
||||
if (out != null) {
|
||||
long alias = I2PAppContext.getGlobalContext().random().nextLong();
|
||||
String trustedURL = protocol + uriPath + urlEncoding;
|
||||
String conflictURL = protocol + alias + ".i2p/?" + initialFragments;
|
||||
out.write(header);
|
||||
out.write(("To visit the destination in your host database, click <a href=\"" + trustedURL + "\">here</a>. To visit the conflicting addresshelper link by temporarily giving it a random alias, click <a href=\"" + conflictURL + "\">here</a>.<P/>").getBytes());
|
||||
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String addressHelper = (String) addressHelpers.get(destination);
|
||||
if (addressHelper != null) {
|
||||
destination = addressHelper;
|
||||
host = getHostName(destination);
|
||||
ahelper = 1;
|
||||
}
|
||||
|
||||
line = method + " " + request.substring(pos);
|
||||
} else if (host.indexOf(".") != -1) {
|
||||
// The request must be forwarded to a WWW proxy
|
||||
@@ -277,23 +409,44 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
} else {
|
||||
if (line.startsWith("Host: ") && !usingWWWProxy) {
|
||||
if (lowercaseLine.startsWith("host: ") && !usingWWWProxy) {
|
||||
line = "Host: " + host;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Setting host = " + host);
|
||||
} else if (line.startsWith("User-Agent: ")) {
|
||||
line = "User-Agent: MYOB/6.66 (AN/ON)";
|
||||
} else if (line.startsWith("Referer: ")) {
|
||||
} else if (lowercaseLine.startsWith("user-agent: ")) {
|
||||
// always stripped, added back at the end
|
||||
line = null;
|
||||
continue;
|
||||
} else if (lowercaseLine.startsWith("accept")) {
|
||||
// strip the accept-blah headers, as they vary dramatically from
|
||||
// browser to browser
|
||||
line = null;
|
||||
continue;
|
||||
} else if (lowercaseLine.startsWith("referer: ")) {
|
||||
// Shouldn't we be more specific, like accepting in-site referers ?
|
||||
line = "Referer: i2p";
|
||||
} else if (line.startsWith("Via: ")) {
|
||||
line = "Via: i2p";
|
||||
} else if (line.startsWith("From: ")) {
|
||||
line = "From: i2p";
|
||||
//line = "Referer: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (lowercaseLine.startsWith("via: ")) {
|
||||
//line = "Via: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (lowercaseLine.startsWith("from: ")) {
|
||||
//line = "From: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
}
|
||||
}
|
||||
|
||||
if (line.length() == 0) {
|
||||
|
||||
String ok = getTunnel().getContext().getProperty("i2ptunnel.gzip");
|
||||
boolean gzip = DEFAULT_GZIP;
|
||||
if (ok != null)
|
||||
gzip = Boolean.valueOf(ok).booleanValue();
|
||||
if (gzip)
|
||||
newRequest.append("Accept-Encoding: x-i2p-gzip\r\n");
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
} else {
|
||||
@@ -330,30 +483,44 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
l.log("Could not resolve " + destination + ".");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
|
||||
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest, usingWWWProxy, destination);
|
||||
String str;
|
||||
byte[] header;
|
||||
if (usingWWWProxy)
|
||||
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
|
||||
else if(ahelper != 0)
|
||||
str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
|
||||
else
|
||||
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
|
||||
if (str != null)
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
String remoteID;
|
||||
I2PSocket i2ps = createI2PSocket(dest);
|
||||
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", ""+120*1000);
|
||||
// 1 == disconnect. see ConnectionOptions in the new streaming lib, which i
|
||||
// dont want to hard link to here
|
||||
opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
|
||||
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions(opts));
|
||||
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
|
||||
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data);
|
||||
timeoutThread = new InactivityTimeoutThread(runner, out, targetRequest, usingWWWProxy, currentProxy, s, requestId);
|
||||
timeoutThread.start();
|
||||
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
I2PTunnelRunner runner = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
} catch (SocketException ex) {
|
||||
if (timeoutThread != null) timeoutThread.disable();
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (IOException ex) {
|
||||
if (timeoutThread != null) timeoutThread.disable();
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (I2PException ex) {
|
||||
if (timeoutThread != null) timeoutThread.disable();
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
@@ -361,91 +528,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
private static final long INACTIVITY_TIMEOUT = 120 * 1000;
|
||||
private static volatile long __timeoutId = 0;
|
||||
|
||||
private class InactivityTimeoutThread extends I2PThread {
|
||||
|
||||
private Socket s;
|
||||
private I2PTunnelRunner _runner;
|
||||
private OutputStream _out;
|
||||
private String _targetRequest;
|
||||
private boolean _useWWWProxy;
|
||||
private String _currentProxy;
|
||||
private long _requestId;
|
||||
private boolean _disabled;
|
||||
private Object _disableLock = new Object();
|
||||
|
||||
public InactivityTimeoutThread(I2PTunnelRunner runner, OutputStream out, String targetRequest,
|
||||
boolean useWWWProxy, String currentProxy, Socket s, long requestId) {
|
||||
this.s = s;
|
||||
_runner = runner;
|
||||
_out = out;
|
||||
_targetRequest = targetRequest;
|
||||
_useWWWProxy = useWWWProxy;
|
||||
_currentProxy = currentProxy;
|
||||
_disabled = false;
|
||||
_requestId = requestId;
|
||||
long timeoutId = ++__timeoutId;
|
||||
setName("InactivityThread " + getPrefix(requestId) + timeoutId);
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
_disabled = true;
|
||||
synchronized (_disableLock) {
|
||||
_disableLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (!_disabled) {
|
||||
if (_runner.isFinished()) {
|
||||
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix(_requestId) + "HTTP client request completed prior to timeout");
|
||||
return;
|
||||
}
|
||||
if (_runner.getLastActivityOn() < Clock.getInstance().now() - INACTIVITY_TIMEOUT) {
|
||||
if (_runner.getStartedOn() < Clock.getInstance().now() - INACTIVITY_TIMEOUT) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(_requestId) + "HTTP client request timed out (lastActivity: "
|
||||
+ new Date(_runner.getLastActivityOn()) + ", startedOn: "
|
||||
+ new Date(_runner.getStartedOn()) + ")");
|
||||
timeout();
|
||||
return;
|
||||
} else {
|
||||
// runner hasn't been going to long enough
|
||||
}
|
||||
} else {
|
||||
// there has been activity in the period
|
||||
}
|
||||
synchronized (_disableLock) {
|
||||
try {
|
||||
_disableLock.wait(INACTIVITY_TIMEOUT);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void timeout() {
|
||||
_log.info(getPrefix(_requestId) + "Inactivity timeout reached");
|
||||
l.log("Inactivity timeout reached");
|
||||
if (_out != null) {
|
||||
try {
|
||||
if (_runner.getLastActivityOn() > 0) {
|
||||
// some data has been sent, so don't 404 it
|
||||
} else {
|
||||
writeErrorMessage(ERR_TIMEOUT, _out, _targetRequest, _useWWWProxy, _currentProxy);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.warn(getPrefix(_requestId) + "Error writing out the 'timeout' message", ioe);
|
||||
}
|
||||
} else {
|
||||
_log.warn(getPrefix(_requestId) + "Client disconnected before we could say we timed out");
|
||||
}
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
|
||||
private final static String getHostName(String host) {
|
||||
if (host == null) return null;
|
||||
try {
|
||||
@@ -457,15 +539,45 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
private class OnTimeout implements Runnable {
|
||||
private Socket _socket;
|
||||
private OutputStream _out;
|
||||
private String _target;
|
||||
private boolean _usingProxy;
|
||||
private String _wwwProxy;
|
||||
private long _requestId;
|
||||
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
|
||||
_socket = s;
|
||||
_out = out;
|
||||
_target = target;
|
||||
_usingProxy = usingProxy;
|
||||
_wwwProxy = wwwProxy;
|
||||
_requestId = id;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Timeout occured requesting " + _target);
|
||||
handleHTTPClientException(new RuntimeException("Timeout"), _out,
|
||||
_target, _usingProxy, _wwwProxy, _requestId);
|
||||
closeSocket(_socket);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy) throws IOException {
|
||||
if (out != null) {
|
||||
out.write(errMessage);
|
||||
if (targetRequest != null) {
|
||||
out.write(targetRequest.getBytes());
|
||||
int protopos = targetRequest.indexOf(" ");
|
||||
String uri = targetRequest.substring(0, protopos);
|
||||
out.write("<a href=\"http://".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">http://".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
if (usingWWWProxy) out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
|
||||
}
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
@@ -479,7 +591,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
_log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex);
|
||||
if (out != null) {
|
||||
try {
|
||||
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest, usingWWWProxy, wwwProxy);
|
||||
String str;
|
||||
byte[] header;
|
||||
if (usingWWWProxy)
|
||||
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
|
||||
else
|
||||
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
|
||||
if (str != null)
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Override the response with a stream filtering the HTTP headers
|
||||
* received. Specifically, this makes sure we get Connection: close,
|
||||
* so the browser knows they really shouldn't try to use persistent
|
||||
* connections. The HTTP server *should* already be setting this,
|
||||
* since the HTTP headers sent by the browser specify Connection: close,
|
||||
* and the server should echo it. However, both broken and malicious
|
||||
* servers could ignore that, potentially confusing the user.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
|
||||
private Log _log;
|
||||
public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
|
||||
super(s, i2ps, slock, initialI2PData, sockList, onTimeout);
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(I2PTunnelHTTPClientRunner.class);
|
||||
}
|
||||
|
||||
protected OutputStream getSocketOut() throws IOException {
|
||||
OutputStream raw = super.getSocketOut();
|
||||
return new HTTPResponseOutputStream(raw);
|
||||
}
|
||||
|
||||
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
|
||||
try {
|
||||
i2pin.close();
|
||||
i2pout.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unable to close the i2p socket output stream: " + i2pout, ioe);
|
||||
}
|
||||
try {
|
||||
in.close();
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unable to close the browser output stream: " + out, ioe);
|
||||
}
|
||||
i2ps.close();
|
||||
s.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple extension to the I2PTunnelServer that filters the HTTP
|
||||
* headers sent from the client to the server, replacing the Host
|
||||
* header with whatever this instance has been configured with, and
|
||||
* if the browser set Accept-encoding: x-i2p-gzip, gzip the http
|
||||
* message body and set Content-encoding: x-i2p-gzip.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
private final static Log _log = new Log(I2PTunnelHTTPServer.class);
|
||||
/** what Host: should we seem to be to the webserver? */
|
||||
private String _spoofHost;
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, privkeyname, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the thread pool of I2PSocket handlers
|
||||
*
|
||||
*/
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
long afterAccept = getTunnel().getContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
// give them 5 seconds to send in the HTTP request
|
||||
socket.setReadTimeout(5*1000);
|
||||
|
||||
InputStream in = socket.getInputStream();
|
||||
|
||||
StringBuffer command = new StringBuffer(128);
|
||||
Properties headers = readHeaders(in, command);
|
||||
if ( (_spoofHost != null) && (_spoofHost.trim().length() > 0) )
|
||||
headers.setProperty("Host", _spoofHost);
|
||||
headers.setProperty("Connection", "close");
|
||||
// we keep the enc sent by the browser before clobbering it, since it may have
|
||||
// been x-i2p-gzip
|
||||
String enc = headers.getProperty("Accept-encoding");
|
||||
headers.setProperty("Accept-encoding", "identity;q=1, *;q=0");
|
||||
String modifiedHeader = formatHeaders(headers, command);
|
||||
|
||||
//String modifiedHeader = getModifiedHeader(socket);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Modified header: [" + modifiedHeader + "]");
|
||||
|
||||
socket.setReadTimeout(readTimeout);
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = getTunnel().getContext().clock().now();
|
||||
// instead of i2ptunnelrunner, use something that reads the HTTP
|
||||
// request from the socket, modifies the headers, sends the request to the
|
||||
// server, reads the response headers, rewriting to include Content-encoding: x-i2p-gzip
|
||||
// if it was one of the Accept-encoding: values, and gzip the payload
|
||||
Properties opts = getTunnel().getClientOptions();
|
||||
boolean allowGZIP = true;
|
||||
if (opts != null) {
|
||||
String val = opts.getProperty("i2ptunnel.gzip");
|
||||
if ( (val != null) && (!Boolean.valueOf(val).booleanValue()) )
|
||||
allowGZIP = false;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("HTTP server encoding header: " + enc);
|
||||
if ( allowGZIP && (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) ) {
|
||||
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), "http compressor");
|
||||
req.start();
|
||||
} else {
|
||||
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error while receiving the new HTTP request", ex);
|
||||
}
|
||||
|
||||
long afterHandle = getTunnel().getContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle, 0);
|
||||
if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
|
||||
}
|
||||
|
||||
private class CompressedRequestor implements Runnable {
|
||||
private Socket _webserver;
|
||||
private I2PSocket _browser;
|
||||
private String _headers;
|
||||
public CompressedRequestor(Socket webserver, I2PSocket browser, String headers) {
|
||||
_webserver = webserver;
|
||||
_browser = browser;
|
||||
_headers = headers;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Compressed requestor running");
|
||||
OutputStream serverout = null;
|
||||
OutputStream browserout = null;
|
||||
InputStream browserin = null;
|
||||
InputStream serverin = null;
|
||||
try {
|
||||
serverout = _webserver.getOutputStream();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("request headers: " + _headers);
|
||||
serverout.write(_headers.getBytes());
|
||||
browserin = _browser.getInputStream();
|
||||
I2PThread sender = new I2PThread(new Sender(serverout, browserin, "server: browser to server"), "http compressed sender");
|
||||
sender.start();
|
||||
|
||||
browserout = _browser.getOutputStream();
|
||||
serverin = _webserver.getInputStream();
|
||||
CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout);
|
||||
Sender s = new Sender(compressedOut, serverin, "server: server to browser");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Before pumping the compressed response");
|
||||
s.run(); // same thread
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After pumping the compressed response: " + compressedOut.getTotalRead() + "/" + compressedOut.getTotalCompressed());
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("error compressing", ioe);
|
||||
} finally {
|
||||
if (browserout != null) try { browserout.close(); } catch (IOException ioe) {}
|
||||
if (serverout != null) try { serverout.close(); } catch (IOException ioe) {}
|
||||
if (browserin != null) try { browserin.close(); } catch (IOException ioe) {}
|
||||
if (serverin != null) try { serverin.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
private class Sender implements Runnable {
|
||||
private OutputStream _out;
|
||||
private InputStream _in;
|
||||
private String _name;
|
||||
public Sender(OutputStream out, InputStream in, String name) {
|
||||
_out = out;
|
||||
_in = in;
|
||||
_name = name;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": Begin sending");
|
||||
try {
|
||||
byte buf[] = new byte[16*1024];
|
||||
int read = 0;
|
||||
int total = 0;
|
||||
while ( (read = _in.read(buf)) != -1) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": read " + read + " and sending through the stream");
|
||||
_out.write(buf, 0, read);
|
||||
total += read;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_name + ": Done sending: " + total);
|
||||
_out.flush();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Error sending", ioe);
|
||||
} finally {
|
||||
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
|
||||
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
private class CompressedResponseOutputStream extends HTTPResponseOutputStream {
|
||||
private InternalGZIPOutputStream _gzipOut;
|
||||
public CompressedResponseOutputStream(OutputStream o) {
|
||||
super(o);
|
||||
}
|
||||
|
||||
protected boolean shouldCompress() { return true; }
|
||||
protected void finishHeaders() throws IOException {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Including x-i2p-gzip as the content encoding in the response");
|
||||
out.write("Content-encoding: x-i2p-gzip\n".getBytes());
|
||||
super.finishHeaders();
|
||||
}
|
||||
|
||||
protected void beginProcessing() throws IOException {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Beginning compression processing");
|
||||
out.flush();
|
||||
_gzipOut = new InternalGZIPOutputStream(out);
|
||||
out = _gzipOut;
|
||||
}
|
||||
public long getTotalRead() { return _gzipOut.getTotalRead(); }
|
||||
public long getTotalCompressed() { return _gzipOut.getTotalCompressed(); }
|
||||
}
|
||||
private class InternalGZIPOutputStream extends GZIPOutputStream {
|
||||
public InternalGZIPOutputStream(OutputStream target) throws IOException {
|
||||
super(target);
|
||||
}
|
||||
public long getTotalRead() { return super.def.getTotalIn(); }
|
||||
public long getTotalCompressed() { return super.def.getTotalOut(); }
|
||||
}
|
||||
|
||||
private String formatHeaders(Properties headers, StringBuffer command) {
|
||||
StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64);
|
||||
buf.append(command.toString()).append('\n');
|
||||
for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String val = headers.getProperty(name);
|
||||
buf.append(name).append(": ").append(val).append('\n');
|
||||
}
|
||||
buf.append('\n');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private Properties readHeaders(InputStream in, StringBuffer command) throws IOException {
|
||||
Properties headers = new Properties();
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
|
||||
boolean ok = DataHelper.readLine(in, command);
|
||||
if (!ok) throw new IOException("EOF reached while reading the HTTP command [" + command.toString() + "]");
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the http command [" + command.toString() + "]");
|
||||
|
||||
while (true) {
|
||||
buf.setLength(0);
|
||||
ok = DataHelper.readLine(in, buf);
|
||||
if (!ok) throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
||||
if ( (buf.length() <= 1) && ( (buf.charAt(0) == '\n') || (buf.charAt(0) == '\r') ) ) {
|
||||
// end of headers reached
|
||||
return headers;
|
||||
} else {
|
||||
int split = buf.indexOf(": ");
|
||||
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
|
||||
String name = buf.substring(0, split);
|
||||
String value = buf.substring(split+2); // ": "
|
||||
if ("Accept-encoding".equalsIgnoreCase(name))
|
||||
name = "Accept-encoding";
|
||||
headers.setProperty(name, value);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the header [" + name + "] = [" + value + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
@@ -11,9 +10,11 @@ import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
@@ -29,7 +30,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
* Sun's impl of BufferedOutputStream), but that is the streaming
|
||||
* api's job...
|
||||
*/
|
||||
static int MAX_PACKET_SIZE = 1024 * 32;
|
||||
static int MAX_PACKET_SIZE = 1024 * 4;
|
||||
|
||||
static final int NETWORK_BUFFER_SIZE = MAX_PACKET_SIZE;
|
||||
|
||||
@@ -38,23 +39,43 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
Object slock, finishLock = new Object();
|
||||
boolean finished = false;
|
||||
HashMap ostreams, sockets;
|
||||
I2PSession session;
|
||||
byte[] initialData;
|
||||
byte[] initialI2PData;
|
||||
byte[] initialSocketData;
|
||||
/** when the last data was sent/received (or -1 if never) */
|
||||
private long lastActivityOn;
|
||||
/** when the runner started up */
|
||||
private long startedOn;
|
||||
private List sockList;
|
||||
/** if we die before receiving any data, run this job */
|
||||
private Runnable onTimeout;
|
||||
private long totalSent;
|
||||
private long totalReceived;
|
||||
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData) {
|
||||
private volatile long __forwarderId;
|
||||
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList) {
|
||||
this(s, i2ps, slock, initialI2PData, null, sockList, null);
|
||||
}
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList) {
|
||||
this(s, i2ps, slock, initialI2PData, initialSocketData, sockList, null);
|
||||
}
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
|
||||
this(s, i2ps, slock, initialI2PData, null, sockList, onTimeout);
|
||||
}
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList, Runnable onTimeout) {
|
||||
this.sockList = sockList;
|
||||
this.s = s;
|
||||
this.i2ps = i2ps;
|
||||
this.slock = slock;
|
||||
this.initialData = initialData;
|
||||
this.initialI2PData = initialI2PData;
|
||||
this.initialSocketData = initialSocketData;
|
||||
this.onTimeout = onTimeout;
|
||||
lastActivityOn = -1;
|
||||
startedOn = Clock.getInstance().now();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("I2PTunnelRunner started");
|
||||
_runnerId = ++__runnerId;
|
||||
__forwarderId = i2ps.hashCode();
|
||||
setName("I2PTunnelRunner " + _runnerId);
|
||||
start();
|
||||
}
|
||||
@@ -90,51 +111,101 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
return startedOn;
|
||||
}
|
||||
|
||||
protected InputStream getSocketIn() throws IOException { return s.getInputStream(); }
|
||||
protected OutputStream getSocketOut() throws IOException { return s.getOutputStream(); }
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
InputStream in = s.getInputStream();
|
||||
OutputStream out = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
|
||||
InputStream in = getSocketIn();
|
||||
OutputStream out = getSocketOut(); // = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
|
||||
i2ps.setSocketErrorListener(this);
|
||||
InputStream i2pin = i2ps.getInputStream();
|
||||
OutputStream i2pout = new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
|
||||
if (initialData != null) {
|
||||
OutputStream i2pout = i2ps.getOutputStream(); //new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
|
||||
if (initialI2PData != null) {
|
||||
synchronized (slock) {
|
||||
i2pout.write(initialData);
|
||||
i2pout.flush();
|
||||
i2pout.write(initialI2PData);
|
||||
//i2pout.flush();
|
||||
}
|
||||
}
|
||||
Thread t1 = new StreamForwarder(in, i2pout);
|
||||
Thread t2 = new StreamForwarder(i2pin, out);
|
||||
if (initialSocketData != null) {
|
||||
out.write(initialSocketData);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Initial data " + (initialI2PData != null ? initialI2PData.length : 0)
|
||||
+ " written to I2P, " + (initialSocketData != null ? initialSocketData.length : 0)
|
||||
+ " written to the socket, starting forwarders");
|
||||
Thread t1 = new StreamForwarder(in, i2pout, true);
|
||||
Thread t2 = new StreamForwarder(i2pin, out, false);
|
||||
synchronized (finishLock) {
|
||||
while (!finished) {
|
||||
finishLock.wait();
|
||||
}
|
||||
}
|
||||
// now one connection is dead - kill the other as well.
|
||||
s.close();
|
||||
s = null;
|
||||
i2ps.close();
|
||||
i2ps = null;
|
||||
t1.join();
|
||||
t2.join();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("At least one forwarder completed, closing and joining");
|
||||
|
||||
// this task is useful for the httpclient
|
||||
if (onTimeout != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("runner has a timeout job, totalReceived = " + totalReceived
|
||||
+ " totalSent = " + totalSent + " job = " + onTimeout);
|
||||
if ( (totalSent <= 0) && (totalReceived <= 0) )
|
||||
onTimeout.run();
|
||||
}
|
||||
|
||||
// now one connection is dead - kill the other as well, after making sure we flush
|
||||
close(out, in, i2pout, i2pin, s, i2ps, t1, t2);
|
||||
} catch (InterruptedException ex) {
|
||||
_log.error("Interrupted", ex);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Interrupted", ex);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
_log.debug("Error forwarding", ex);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Error forwarding", ex);
|
||||
} catch (Exception e) {
|
||||
_log.error("Internal error", e);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Internal error", e);
|
||||
} finally {
|
||||
removeRef();
|
||||
try {
|
||||
if (s != null) s.close();
|
||||
if (i2ps != null) i2ps.close();
|
||||
if (s != null)
|
||||
s.close();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
_log.error("Could not close socket", ex);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not close java socket", ex);
|
||||
}
|
||||
if (i2ps != null) {
|
||||
try {
|
||||
i2ps.close();
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not close I2PSocket", ex);
|
||||
}
|
||||
i2ps.setSocketErrorListener(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
i2pout.flush();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
in.close();
|
||||
i2pin.close();
|
||||
// ok, yeah, there's a race here in theory, if data comes in after flushing and before
|
||||
// closing, but its better than before...
|
||||
s.close();
|
||||
i2ps.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
}
|
||||
|
||||
public void errorOccurred() {
|
||||
synchronized (finishLock) {
|
||||
finished = true;
|
||||
@@ -142,63 +213,110 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
}
|
||||
}
|
||||
|
||||
private volatile long __forwarderId = 0;
|
||||
private void removeRef() {
|
||||
if (sockList != null) {
|
||||
synchronized (slock) {
|
||||
boolean removed = sockList.remove(i2ps);
|
||||
//System.out.println("Removal of i2psocket " + i2ps + " successful? "
|
||||
// + removed + " remaining: " + sockList.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class StreamForwarder extends I2PThread {
|
||||
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
String direction;
|
||||
private boolean _toI2P;
|
||||
private ByteCache _cache;
|
||||
|
||||
private StreamForwarder(InputStream in, OutputStream out) {
|
||||
private StreamForwarder(InputStream in, OutputStream out, boolean toI2P) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
_toI2P = toI2P;
|
||||
direction = (toI2P ? "toI2P" : "fromI2P");
|
||||
_cache = ByteCache.getInstance(32, NETWORK_BUFFER_SIZE);
|
||||
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
byte[] buffer = new byte[NETWORK_BUFFER_SIZE];
|
||||
String from = i2ps.getThisDestination().calculateHash().toBase64().substring(0,6);
|
||||
String to = i2ps.getPeerDestination().calculateHash().toBase64().substring(0,6);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(direction + ": Forwarding between "
|
||||
+ from + " and " + to);
|
||||
}
|
||||
|
||||
ByteArray ba = _cache.acquire();
|
||||
byte[] buffer = ba.getData(); // new byte[NETWORK_BUFFER_SIZE];
|
||||
try {
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
if (_toI2P)
|
||||
totalSent += len;
|
||||
else
|
||||
totalReceived += len;
|
||||
|
||||
if (len > 0) updateActivity();
|
||||
|
||||
if (in.available() == 0) {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Flushing after sending " + len + " bytes through");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(direction + ": " + len + " bytes flushed through to "
|
||||
+ i2ps.getPeerDestination().calculateHash().toBase64().substring(0,6));
|
||||
try {
|
||||
Thread.sleep(I2PTunnel.PACKET_DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (in.available() == 0) {
|
||||
out.flush(); // make sure the data get though
|
||||
|
||||
if (in.available() <= 0)
|
||||
out.flush(); // make sure the data get though
|
||||
}
|
||||
}
|
||||
//out.flush(); // close() flushes
|
||||
} catch (SocketException ex) {
|
||||
// this *will* occur when the other threads closes the socket
|
||||
synchronized (finishLock) {
|
||||
if (!finished) {
|
||||
_log.debug("Socket closed - error reading and writing",
|
||||
ex);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(direction + ": Socket closed - error reading and writing",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedIOException ex) {
|
||||
_log.warn("Closing connection due to timeout (error: \""
|
||||
+ ex.getMessage() + "\")");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(direction + ": Closing connection due to timeout (error: \""
|
||||
+ ex.getMessage() + "\")");
|
||||
} catch (IOException ex) {
|
||||
if (!finished)
|
||||
_log.error("Error forwarding", ex);
|
||||
else
|
||||
_log.warn("You may ignore this", ex);
|
||||
if (!finished) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(direction + ": Error forwarding", ex);
|
||||
}
|
||||
//else
|
||||
// _log.warn("You may ignore this", ex);
|
||||
} finally {
|
||||
_cache.release(ba);
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info(direction + ": done forwarding between "
|
||||
+ from + " and " + to);
|
||||
}
|
||||
try {
|
||||
out.close();
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error closing streams", ex);
|
||||
_log.warn(direction + ": Error closing input stream", ex);
|
||||
}
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(direction + ": Error flushing to close", ioe);
|
||||
}
|
||||
synchronized (finishLock) {
|
||||
finished = true;
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.ConnectException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
@@ -31,29 +32,43 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
private final static Log _log = new Log(I2PTunnelServer.class);
|
||||
|
||||
private I2PSocketManager sockMgr;
|
||||
private I2PServerSocket i2pss;
|
||||
protected I2PSocketManager sockMgr;
|
||||
protected I2PServerSocket i2pss;
|
||||
|
||||
private Object lock = new Object(), slock = new Object();
|
||||
private Object lock = new Object();
|
||||
protected Object slock = new Object();
|
||||
|
||||
private InetAddress remoteHost;
|
||||
private int remotePort;
|
||||
protected InetAddress remoteHost;
|
||||
protected int remotePort;
|
||||
private boolean _usePool;
|
||||
|
||||
private Logging l;
|
||||
|
||||
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
|
||||
/** default timeout to 3 minutes - override if desired */
|
||||
private long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
private static final boolean DEFAULT_USE_POOL = false;
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privData, notifyThis, tunnel);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
|
||||
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
|
||||
if (usePool != null)
|
||||
_usePool = "true".equalsIgnoreCase(usePool);
|
||||
else
|
||||
_usePool = DEFAULT_USE_POOL;
|
||||
init(host, port, bais, privData, l);
|
||||
}
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, File privkey, String privkeyname, Logging l,
|
||||
EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
|
||||
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
|
||||
if (usePool != null)
|
||||
_usePool = "true".equalsIgnoreCase(usePool);
|
||||
else
|
||||
_usePool = DEFAULT_USE_POOL;
|
||||
try {
|
||||
init(host, port, new FileInputStream(privkey), privkeyname, l);
|
||||
} catch (IOException ioe) {
|
||||
@@ -64,6 +79,11 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
|
||||
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
|
||||
if (usePool != null)
|
||||
_usePool = "true".equalsIgnoreCase(usePool);
|
||||
else
|
||||
_usePool = DEFAULT_USE_POOL;
|
||||
init(host, port, privData, privkeyname, l);
|
||||
}
|
||||
|
||||
@@ -74,10 +94,25 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Properties props = new Properties();
|
||||
props.putAll(getTunnel().getClientOptions());
|
||||
synchronized (slock) {
|
||||
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, Integer.parseInt(getTunnel().port),
|
||||
props);
|
||||
int portNum = 7654;
|
||||
if (getTunnel().port != null) {
|
||||
try {
|
||||
portNum = Integer.parseInt(getTunnel().port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
|
||||
}
|
||||
}
|
||||
|
||||
while (sockMgr == null) {
|
||||
synchronized (slock) {
|
||||
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, portNum,
|
||||
props);
|
||||
|
||||
}
|
||||
if (sockMgr == null) {
|
||||
_log.log(Log.CRIT, "Unable to create socket manager");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
sockMgr.setName("Server");
|
||||
getTunnel().addSession(sockMgr.getSession());
|
||||
@@ -143,57 +178,103 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String PROP_HANDLER_COUNT = "i2ptunnel.blockingHandlerCount";
|
||||
private static final int DEFAULT_HANDLER_COUNT = 10;
|
||||
|
||||
protected int getHandlerCount() {
|
||||
int rv = DEFAULT_HANDLER_COUNT;
|
||||
String cnt = getTunnel().getClientOptions().getProperty(PROP_HANDLER_COUNT);
|
||||
if (cnt != null) {
|
||||
try {
|
||||
rv = Integer.parseInt(cnt);
|
||||
if (rv <= 0)
|
||||
rv = DEFAULT_HANDLER_COUNT;
|
||||
} catch (NumberFormatException nfe) {
|
||||
rv = DEFAULT_HANDLER_COUNT;
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
if (shouldUsePool()) {
|
||||
I2PServerSocket i2pss = sockMgr.getServerSocket();
|
||||
int handlers = getHandlerCount();
|
||||
for (int i = 0; i < handlers; i++) {
|
||||
I2PThread handler = new I2PThread(new Handler(i2pss), "Handle Server " + i);
|
||||
handler.start();
|
||||
}
|
||||
} else {
|
||||
I2PServerSocket i2pss = sockMgr.getServerSocket();
|
||||
while (true) {
|
||||
I2PSocket i2ps = i2pss.accept();
|
||||
I2PThread t = new I2PThread(new Handler(i2ps));
|
||||
t.start();
|
||||
try {
|
||||
final I2PSocket i2ps = i2pss.accept();
|
||||
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
|
||||
new I2PThread(new Runnable() { public void run() { blockingHandle(i2ps); } }).start();
|
||||
} catch (I2PException ipe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error accepting - KILLING THE TUNNEL SERVER", ipe);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error accepting", ce);
|
||||
// not killing the server..
|
||||
}
|
||||
}
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldUsePool() { return _usePool; }
|
||||
|
||||
/**
|
||||
* Async handler to keep .accept() from blocking too long.
|
||||
* todo: replace with a thread pool so we dont get overrun by threads if/when
|
||||
* receiving a lot of connection requests concurrently.
|
||||
* minor thread pool to pull off the accept() concurrently. there are still lots
|
||||
* (and lots) of wasted threads within the I2PTunnelRunner, but its a start
|
||||
*
|
||||
*/
|
||||
private class Handler implements Runnable {
|
||||
private I2PSocket _handleSocket;
|
||||
public Handler(I2PSocket socket) {
|
||||
_handleSocket = socket;
|
||||
private I2PServerSocket _serverSocket;
|
||||
public Handler(I2PServerSocket serverSocket) {
|
||||
_serverSocket = serverSocket;
|
||||
}
|
||||
public void run() {
|
||||
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
_handleSocket.setReadTimeout(readTimeout);
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = I2PAppContext.getGlobalContext().clock().now();
|
||||
new I2PTunnelRunner(s, _handleSocket, slock, null);
|
||||
} catch (SocketException ex) {
|
||||
while (open) {
|
||||
try {
|
||||
_handleSocket.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
blockingHandle(_serverSocket.accept());
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
return;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
return;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
|
||||
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
if (timeToHandle > 1000)
|
||||
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
socket.setReadTimeout(readTimeout);
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = I2PAppContext.getGlobalContext().clock().now();
|
||||
new I2PTunnelRunner(s, socket, slock, null, null);
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
|
||||
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
if (timeToHandle > 1000)
|
||||
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@@ -15,6 +16,7 @@ import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -28,7 +30,9 @@ public class TunnelController implements Logging {
|
||||
private Properties _config;
|
||||
private I2PTunnel _tunnel;
|
||||
private List _messages;
|
||||
private List _sessions;
|
||||
private boolean _running;
|
||||
private boolean _starting;
|
||||
|
||||
/**
|
||||
* Create a new controller for a tunnel out of the specific config options.
|
||||
@@ -54,10 +58,9 @@ public class TunnelController implements Logging {
|
||||
setConfig(config, prefix);
|
||||
_messages = new ArrayList(4);
|
||||
_running = false;
|
||||
if (createKey && ("server".equals(getType())) )
|
||||
if (createKey && ("server".equals(getType()) || "httpserver".equals(getType())) )
|
||||
createPrivateKey();
|
||||
if (getStartOnLoad())
|
||||
startTunnel();
|
||||
_starting = getStartOnLoad();
|
||||
}
|
||||
|
||||
private void createPrivateKey() {
|
||||
@@ -97,17 +100,25 @@ public class TunnelController implements Logging {
|
||||
}
|
||||
}
|
||||
|
||||
public void startTunnelBackground() {
|
||||
if (_running) return;
|
||||
_starting = true;
|
||||
new I2PThread(new Runnable() { public void run() { startTunnel(); } }).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start up the tunnel (if it isn't already running)
|
||||
*
|
||||
*/
|
||||
public void startTunnel() {
|
||||
_starting = true;
|
||||
try {
|
||||
doStartTunnel();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error starting up the tunnel", e);
|
||||
log("Error starting up the tunnel - " + e.getMessage());
|
||||
}
|
||||
_starting = false;
|
||||
}
|
||||
private void doStartTunnel() {
|
||||
if (_running) {
|
||||
@@ -128,6 +139,8 @@ public class TunnelController implements Logging {
|
||||
startClient();
|
||||
} else if ("server".equals(type)) {
|
||||
startServer();
|
||||
} else if ("httpserver".equals(type)) {
|
||||
startHttpServer();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Cannot start tunnel - unknown type [" + type + "]");
|
||||
@@ -140,20 +153,56 @@ public class TunnelController implements Logging {
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String proxyList = getProxyList();
|
||||
String sharedClient = getSharedClient();
|
||||
if (proxyList == null)
|
||||
_tunnel.runHttpClient(new String[] { listenPort }, this);
|
||||
_tunnel.runHttpClient(new String[] { listenPort, sharedClient }, this);
|
||||
else
|
||||
_tunnel.runHttpClient(new String[] { listenPort, proxyList }, this);
|
||||
_tunnel.runHttpClient(new String[] { listenPort, sharedClient, proxyList }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note the fact that we are using some sessions, so that they dont get
|
||||
* closed by some other tunnels
|
||||
*/
|
||||
private void acquire() {
|
||||
List sessions = _tunnel.getSessions();
|
||||
if (sessions != null) {
|
||||
for (int i = 0; i < sessions.size(); i++) {
|
||||
I2PSession session = (I2PSession)sessions.get(i);
|
||||
TunnelControllerGroup.getInstance().acquire(this, session);
|
||||
}
|
||||
_sessions = sessions;
|
||||
} else {
|
||||
_log.error("No sessions to acquire?");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note the fact that we are no longer using some sessions, and if
|
||||
* no other tunnels are using them, close them.
|
||||
*/
|
||||
private void release() {
|
||||
if (_sessions != null) {
|
||||
for (int i = 0; i < _sessions.size(); i++) {
|
||||
I2PSession s = (I2PSession)_sessions.get(i);
|
||||
TunnelControllerGroup.getInstance().release(this, s);
|
||||
}
|
||||
} else {
|
||||
_log.error("No sessions to release?");
|
||||
}
|
||||
}
|
||||
|
||||
private void startClient() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String dest = getTargetDestination();
|
||||
_tunnel.runClient(new String[] { listenPort, dest }, this);
|
||||
String sharedClient = getSharedClient();
|
||||
_tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
@@ -164,6 +213,19 @@ public class TunnelController implements Logging {
|
||||
String targetPort = getTargetPort();
|
||||
String privKeyFile = getPrivKeyFile();
|
||||
_tunnel.runServer(new String[] { targetHost, targetPort, privKeyFile }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startHttpServer() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
String targetHost = getTargetHost();
|
||||
String targetPort = getTargetPort();
|
||||
String spoofedHost = getSpoofedHost();
|
||||
String privKeyFile = getPrivKeyFile();
|
||||
_tunnel.runHttpServer(new String[] { targetHost, targetPort, spoofedHost, privKeyFile }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
@@ -194,13 +256,25 @@ public class TunnelController implements Logging {
|
||||
String host = getI2CPHost();
|
||||
if ( (host != null) && (host.length() > 0) )
|
||||
_tunnel.host = host;
|
||||
// woohah, special casing for people with ipv6/etc
|
||||
if ("localhost".equals(_tunnel.host))
|
||||
_tunnel.host = "127.0.0.1";
|
||||
String port = getI2CPPort();
|
||||
if ( (port != null) && (port.length() > 0) )
|
||||
_tunnel.port = port;
|
||||
if ( (port != null) && (port.length() > 0) ) {
|
||||
try {
|
||||
int portNum = Integer.parseInt(port);
|
||||
_tunnel.port = String.valueOf(portNum);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_tunnel.port = "7654";
|
||||
}
|
||||
} else {
|
||||
_tunnel.port = "7654";
|
||||
}
|
||||
}
|
||||
|
||||
public void stopTunnel() {
|
||||
_tunnel.runClose(new String[] { "forced", "all" }, this);
|
||||
release();
|
||||
_running = false;
|
||||
}
|
||||
|
||||
@@ -254,13 +328,28 @@ public class TunnelController implements Logging {
|
||||
public String getListenOnInterface() { return _config.getProperty("interface"); }
|
||||
public String getTargetHost() { return _config.getProperty("targetHost"); }
|
||||
public String getTargetPort() { return _config.getProperty("targetPort"); }
|
||||
public String getSpoofedHost() { return _config.getProperty("spoofedHost"); }
|
||||
public String getPrivKeyFile() { return _config.getProperty("privKeyFile"); }
|
||||
public String getListenPort() { return _config.getProperty("listenPort"); }
|
||||
public String getTargetDestination() { return _config.getProperty("targetDestination"); }
|
||||
public String getProxyList() { return _config.getProperty("proxyList"); }
|
||||
public String getSharedClient() { return _config.getProperty("sharedClient", "true"); }
|
||||
public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
|
||||
public String getMyDestination() {
|
||||
if (_tunnel != null) {
|
||||
List sessions = _tunnel.getSessions();
|
||||
for (int i = 0; i < sessions.size(); i++) {
|
||||
I2PSession session = (I2PSession)sessions.get(i);
|
||||
Destination dest = session.getMyDestination();
|
||||
if (dest != null)
|
||||
return dest.toBase64();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean getIsRunning() { return _running; }
|
||||
public boolean getIsStarting() { return _starting; }
|
||||
|
||||
public void getSummary(StringBuffer buf) {
|
||||
String type = getType();
|
||||
@@ -270,6 +359,8 @@ public class TunnelController implements Logging {
|
||||
getClientSummary(buf);
|
||||
else if ("server".equals(type))
|
||||
getServerSummary(buf);
|
||||
else if ("httpserver".equals(type))
|
||||
getHttpServerSummary(buf);
|
||||
else
|
||||
buf.append("Unknown type ").append(type);
|
||||
}
|
||||
@@ -323,6 +414,18 @@ public class TunnelController implements Logging {
|
||||
getOptionSummary(buf);
|
||||
}
|
||||
|
||||
private void getHttpServerSummary(StringBuffer buf) {
|
||||
String description = getDescription();
|
||||
if ( (description != null) && (description.trim().length() > 0) )
|
||||
buf.append("<i>").append(description).append("</i><br />\n");
|
||||
buf.append("Server tunnel pointing at port ").append(getTargetPort());
|
||||
buf.append(" on ").append(getTargetHost());
|
||||
buf.append(" for the site ").append(getSpoofedHost());
|
||||
buf.append("<br />\n");
|
||||
buf.append("Private destination loaded from ").append(getPrivKeyFile()).append("<br />\n");
|
||||
getOptionSummary(buf);
|
||||
}
|
||||
|
||||
private void getOptionSummary(StringBuffer buf) {
|
||||
String opts = getClientOptions();
|
||||
if ( (opts != null) && (opts.length() > 0) )
|
||||
@@ -334,14 +437,21 @@ public class TunnelController implements Logging {
|
||||
Destination dest = session.getMyDestination();
|
||||
if (dest != null) {
|
||||
buf.append("Destination hash: ").append(dest.calculateHash().toBase64()).append("<br />\n");
|
||||
buf.append("Full destination: ");
|
||||
buf.append("<input type=\"text\" size=\"10\" onclick=\"this.select();\" ");
|
||||
buf.append("value=\"").append(dest.toBase64()).append("\" />\n");
|
||||
if ("server".equals(getType())) {
|
||||
buf.append(" Give that out to people so they can view your service.");
|
||||
buf.append(" If you are going to share it on irc, be sure to split it on two lines");
|
||||
if ( ("server".equals(getType())) || ("httpserver".equals(getType())) ) {
|
||||
buf.append("Full destination: ");
|
||||
buf.append("<input type=\"text\" size=\"10\" onclick=\"this.select();\" ");
|
||||
buf.append("value=\"").append(dest.toBase64()).append("\" />\n");
|
||||
long val = new Random().nextLong();
|
||||
if (val < 0) val = 0 - val;
|
||||
buf.append("<br />You can <a href=\"http://temp").append(val);
|
||||
buf.append(".i2p/?i2paddresshelper=").append(dest.toBase64()).append("\">view</a>");
|
||||
buf.append(" it in a browser (only when you're using the eepProxy)\n");
|
||||
buf.append("<br />If you are going to share this on IRC, you need to split it up:<br />\n");
|
||||
String str = dest.toBase64();
|
||||
buf.append(str.substring(0, str.length()/2)).append("<br />\n");
|
||||
buf.append(str.substring(str.length()/2)).append("<br />\n");
|
||||
buf.append("You can also post it to <a href=\"http://forum.i2p/viewforum.php?f=16\">Eepsite announcement forum</a><br />");
|
||||
}
|
||||
buf.append("<br />\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -30,26 +34,42 @@ public class TunnelControllerGroup {
|
||||
private List _controllers;
|
||||
private String _configFile = DEFAULT_CONFIG_FILE;
|
||||
|
||||
public static TunnelControllerGroup getInstance() { return _instance; }
|
||||
/**
|
||||
* Map of I2PSession to a Set of TunnelController objects
|
||||
* using the session (to prevent closing the session until
|
||||
* no more tunnels are using it)
|
||||
*
|
||||
*/
|
||||
private Map _sessions;
|
||||
|
||||
public static TunnelControllerGroup getInstance() {
|
||||
synchronized (TunnelControllerGroup.class) {
|
||||
if (_instance == null)
|
||||
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private TunnelControllerGroup(String configFile) {
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(TunnelControllerGroup.class);
|
||||
_controllers = new ArrayList();
|
||||
_controllers = Collections.synchronizedList(new ArrayList());
|
||||
_configFile = configFile;
|
||||
_sessions = new HashMap(4);
|
||||
loadControllers(_configFile);
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
if ( (args == null) || (args.length <= 0) ) {
|
||||
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
|
||||
} else if (args.length == 1) {
|
||||
if (DEFAULT_CONFIG_FILE.equals(args[0]))
|
||||
synchronized (TunnelControllerGroup.class) {
|
||||
if (_instance != null) return; // already loaded through the web
|
||||
|
||||
if ( (args == null) || (args.length <= 0) ) {
|
||||
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
|
||||
else
|
||||
} else if (args.length == 1) {
|
||||
_instance = new TunnelControllerGroup(args[0]);
|
||||
} else {
|
||||
System.err.println("Usage: TunnelControllerGroup [filename]");
|
||||
return;
|
||||
} else {
|
||||
System.err.println("Usage: TunnelControllerGroup [filename]");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +94,24 @@ public class TunnelControllerGroup {
|
||||
_controllers.add(controller);
|
||||
i++;
|
||||
}
|
||||
I2PThread startupThread = new I2PThread(new StartControllers(), "Startup tunnels");
|
||||
startupThread.start();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(i + " controllers loaded from " + configFile);
|
||||
}
|
||||
|
||||
private class StartControllers implements Runnable {
|
||||
public void run() {
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = (TunnelController)_controllers.get(i);
|
||||
if (controller.getStartOnLoad())
|
||||
controller.startTunnel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void reloadControllers() {
|
||||
unloadControllers();
|
||||
loadControllers(_configFile);
|
||||
@@ -128,7 +162,6 @@ public class TunnelControllerGroup {
|
||||
controller.stopTunnel();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_controllers.size() + " controllers stopped");
|
||||
return msgs;
|
||||
@@ -143,10 +176,10 @@ public class TunnelControllerGroup {
|
||||
List msgs = new ArrayList();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = (TunnelController)_controllers.get(i);
|
||||
controller.startTunnel();
|
||||
controller.startTunnelBackground();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(_controllers.size() + " controllers started");
|
||||
return msgs;
|
||||
@@ -244,33 +277,13 @@ public class TunnelControllerGroup {
|
||||
}
|
||||
|
||||
Properties props = new Properties();
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(cfgFile);
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(fis));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.length() <= 0) continue;
|
||||
if (line.startsWith("#") || line.startsWith(";"))
|
||||
continue;
|
||||
int eq = line.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= line.length() - 1) )
|
||||
continue;
|
||||
String key = line.substring(0, eq);
|
||||
String val = line.substring(eq+1);
|
||||
props.setProperty(key, val);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Props loaded with " + props.size() + " lines");
|
||||
DataHelper.loadProps(props, cfgFile);
|
||||
return props;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error reading the controllers from " + configFile, ioe);
|
||||
return null;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,4 +294,61 @@ public class TunnelControllerGroup {
|
||||
*/
|
||||
public List getControllers() { return _controllers; }
|
||||
|
||||
|
||||
/**
|
||||
* Note the fact that the controller is using the session so that
|
||||
* it isn't destroyed prematurely.
|
||||
*
|
||||
*/
|
||||
void acquire(TunnelController controller, I2PSession session) {
|
||||
synchronized (_sessions) {
|
||||
Set owners = (Set)_sessions.get(session);
|
||||
if (owners == null) {
|
||||
owners = new HashSet(1);
|
||||
_sessions.put(session, owners);
|
||||
}
|
||||
owners.add(controller);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Acquiring session " + session + " for " + controller);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Note the fact that the controller is no longer using the session, and if
|
||||
* no other controllers are using it, destroy the session.
|
||||
*
|
||||
*/
|
||||
void release(TunnelController controller, I2PSession session) {
|
||||
boolean shouldClose = false;
|
||||
synchronized (_sessions) {
|
||||
Set owners = (Set)_sessions.get(session);
|
||||
if (owners != null) {
|
||||
owners.remove(controller);
|
||||
if (owners.size() <= 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After releasing session " + session + " by " + controller + ", no more owners remain");
|
||||
shouldClose = true;
|
||||
_sessions.remove(session);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After releasing session " + session + " by " + controller + ", " + owners.size() + " owners remain");
|
||||
shouldClose = false;
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("After releasing session " + session + " by " + controller + ", no owners were even known?!");
|
||||
shouldClose = true;
|
||||
}
|
||||
}
|
||||
if (shouldClose) {
|
||||
try {
|
||||
session.destroySession();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Session destroyed: " + session);
|
||||
} catch (I2PSessionException ise) {
|
||||
_log.error("Error closing the client session", ise);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,435 +0,0 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Quick and dirty socket listener to control an I2PTunnel.
|
||||
* Basically run this class as TunnelManager [listenHost] [listenPort] and
|
||||
* then send it commands on that port. Commands are one shot deals -
|
||||
* Send a command + newline, get a response plus newline, then get disconnected.
|
||||
* <p />
|
||||
* <b>Implemented commands:</b>
|
||||
* <pre>
|
||||
* -------------------------------------------------
|
||||
* lookup <name>\n
|
||||
* --
|
||||
* <base64 of the destination>\n
|
||||
* or
|
||||
* <error message, usually 'Unknown host'>\n
|
||||
*
|
||||
* Lookup the public key of a named destination (i.e. listed in hosts.txt)
|
||||
* -------------------------------------------------
|
||||
* genkey\n
|
||||
* --
|
||||
* <base64 of the destination>\t<base64 of private data>\n
|
||||
*
|
||||
* Generates a new public and private key pair
|
||||
* -------------------------------------------------
|
||||
* convertprivate <base64 of privkey>
|
||||
* --
|
||||
* <base64 of destination>\n
|
||||
* or
|
||||
* <error message>\n
|
||||
*
|
||||
* Returns the destination (pubkey) of a given private key.
|
||||
* -------------------------------------------------
|
||||
* listen_on <ip>\n
|
||||
* --
|
||||
* ok\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Sets the ip address clients will listen on. By default this is the
|
||||
* localhost (127.0.0.1)
|
||||
* -------------------------------------------------
|
||||
* openclient <listenPort> <peer>\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* ok <listenPort> [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Open a tunnel on the given <listenport> to the destination specified
|
||||
* by <peer>. If <listenPort> is 0 a free port is picked and returned in
|
||||
* the reply message. Otherwise the short reply message is used.
|
||||
* Peer can be the base64 of the destination, a file with the public key
|
||||
* specified as 'file:<filename>' or the name of a destination listed in
|
||||
* hosts.txt. The <jobId> returned together with "ok" and <listenport> can
|
||||
* later be used as argument for the "close" command.
|
||||
* -------------------------------------------------
|
||||
* openhttpclient <listenPort> [<proxy>]\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* ok <listenPort> [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Open an HTTP proxy through the I2P on the given
|
||||
* <listenport>. <proxy> (optional) specifies a
|
||||
* destination to be used as an outbound proxy, to access normal WWW
|
||||
* sites out of the .i2p domain. If <listenPort> is 0 a free
|
||||
* port is picked and returned in the reply message. Otherwise the
|
||||
* short reply message is used. <proxy> can be the base64 of the
|
||||
* destination, a file with the public key specified as
|
||||
* 'file:<filename>' or the name of a destination listed in
|
||||
* hosts.txt. The <jobId> returned together with "ok" and
|
||||
* <listenport> can later be used as argument for the "close"
|
||||
* command.
|
||||
* -------------------------------------------------
|
||||
* opensockstunnel <listenPort>\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* ok <listenPort> [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Open an SOCKS tunnel through the I2P on the given
|
||||
* <listenport>. If <listenPort> is 0 a free port is
|
||||
* picked and returned in the reply message. Otherwise the short
|
||||
* reply message is used. The <jobId> returned together with
|
||||
* "ok" and <listenport> can later be used as argument for the
|
||||
* "close" command.
|
||||
* -------------------------------------------------
|
||||
* openserver <serverHost> <serverPort> <serverKeys>\n
|
||||
* --
|
||||
* ok [<jobId>]\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Starts receiving traffic for the destination specified by <serverKeys>
|
||||
* and forwards it to the <serverPort> of <serverHost>.
|
||||
* <serverKeys> is the base 64 encoded private key set of the local
|
||||
* destination. The <joId> returned together with "ok" can later be used
|
||||
* as argument for the "close" command.
|
||||
* -------------------------------------------------
|
||||
* close [forced] <jobId>\n
|
||||
* or
|
||||
* close [forced] all\n
|
||||
* --
|
||||
* ok\n
|
||||
* or
|
||||
* error\n
|
||||
*
|
||||
* Closes the job specified by <jobId> or all jobs. Use the list command
|
||||
* for a list of running jobs.
|
||||
* Normally a connection job is not closed when it still has an active
|
||||
* connection. Use the optional 'forced' keyword to close connections
|
||||
* regardless of their use.
|
||||
* -------------------------------------------------
|
||||
* list\n
|
||||
* --
|
||||
* Example output:
|
||||
*
|
||||
* [0] i2p.dnsalias.net/69.55.226.145:5555 <- C:\i2pKeys\squidPriv
|
||||
* [1] 8767 -> HTTPClient
|
||||
* [2] 7575 -> file:C:\i2pKeys\squidPub
|
||||
* [3] 5252 -> sCcSANIO~f4AQtCNI1BvDp3ZBS~9Ag5O0k0Msm7XBWWz5eOnZWL3MQ-2rxlesucb9XnpASGhWzyYNBpWAfaIB3pux1J1xujQLOwscMIhm7T8BP76Ly5jx6BLZCYrrPj0BI0uV90XJyT~4UyQgUlC1jzFQdZ9HDgBPJDf1UI4-YjIwEHuJgdZynYlQ1oUFhgno~HhcDByXO~PDaO~1JDMDbBEfIh~v6MgmHp-Xchod1OfKFrxFrzHgcJbn7E8edTFjZA6JCi~DtFxFelQz1lSBd-QB1qJnA0g-pVL5qngNUojXJCXs4qWcQ7ICLpvIc-Fpfj-0F1gkVlGDSGkb1yLH3~8p4czYgR3W5D7OpwXzezz6clpV8kmbd~x2SotdWsXBPRhqpewO38coU4dJG3OEUbuYmdN~nJMfWbmlcM1lXzz2vBsys4sZzW6dV3hZnbvbfxNTqbdqOh-KXi1iAzXv7CVTun0ubw~CfeGpcAqutC5loRUq7Mq62ngOukyv8Z9AAAA
|
||||
*
|
||||
* Lists descriptions of all running jobs. The exact format of the
|
||||
* description depends on the type of job.
|
||||
* -------------------------------------------------
|
||||
* </pre>
|
||||
*/
|
||||
public class TunnelManager implements Runnable {
|
||||
private final static Log _log = new Log(TunnelManager.class);
|
||||
private I2PTunnel _tunnel;
|
||||
private ServerSocket _socket;
|
||||
private boolean _keepAccepting;
|
||||
|
||||
public TunnelManager(int listenPort) {
|
||||
this(null, listenPort);
|
||||
}
|
||||
|
||||
public TunnelManager(String listenHost, int listenPort) {
|
||||
_tunnel = new I2PTunnel();
|
||||
_keepAccepting = true;
|
||||
try {
|
||||
if (listenHost != null) {
|
||||
_socket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
|
||||
_log.info("Listening for tunnel management clients on " + listenHost + ":" + listenPort);
|
||||
} else {
|
||||
_socket = new ServerSocket(listenPort);
|
||||
_log.info("Listening for tunnel management clients on localhost:" + listenPort);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error starting up tunnel management listener on " + listenPort, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
int port = 7676;
|
||||
String host = null;
|
||||
if (args.length == 1) {
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Usage: TunnelManager [host] [port]");
|
||||
return;
|
||||
}
|
||||
} else if (args.length == 2) {
|
||||
host = args[0];
|
||||
try {
|
||||
port = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Usage: TunnelManager [host] [port]");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TunnelManager mgr = new TunnelManager(host, port);
|
||||
Thread t = new I2PThread(mgr, "Listener");
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (_socket == null) {
|
||||
_log.error("Unable to start listening, since the socket was not bound. Already running?");
|
||||
return;
|
||||
}
|
||||
_log.debug("Running");
|
||||
try {
|
||||
while (_keepAccepting) {
|
||||
Socket socket = _socket.accept();
|
||||
_log.debug("Client accepted");
|
||||
if (socket != null) {
|
||||
Thread t = new I2PThread(new TunnelManagerClientRunner(this, socket));
|
||||
t.setName("TunnelManager Client");
|
||||
t.setPriority(I2PThread.MIN_PRIORITY);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error accepting connections", ioe);
|
||||
} catch (Exception e) {
|
||||
_log.error("Other error?!", e);
|
||||
} finally {
|
||||
if (_socket != null) try {
|
||||
_socket.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
|
||||
public void error(String msg, OutputStream out) throws IOException {
|
||||
out.write(msg.getBytes());
|
||||
out.write('\n');
|
||||
}
|
||||
|
||||
public void processQuit(OutputStream out) throws IOException {
|
||||
out.write("Nice try".getBytes());
|
||||
out.write('\n');
|
||||
}
|
||||
|
||||
public void processList(OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
long startCommand = Clock.getInstance().now();
|
||||
_tunnel.runCommand("list", buf);
|
||||
Object obj = _tunnel.waitEventValue("listDone");
|
||||
long endCommand = Clock.getInstance().now();
|
||||
String str = buf.getBuffer();
|
||||
_log.debug("ListDone complete after " + (endCommand - startCommand) + "ms: [" + str + "]");
|
||||
out.write(str.getBytes());
|
||||
out.write('\n');
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processListenOn(String ip, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("listen_on " + ip, buf);
|
||||
String status = (String) _tunnel.waitEventValue("listen_onResult");
|
||||
out.write((status + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* "lookup <name>" returns with the result in base64, else "Unknown host" [or something like that],
|
||||
* then a newline.
|
||||
*
|
||||
*/
|
||||
public void processLookup(String name, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("lookup " + name, buf);
|
||||
String rv = (String) _tunnel.waitEventValue("lookupResult");
|
||||
out.write(rv.getBytes());
|
||||
out.write('\n');
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processTestDestination(String destKey, OutputStream out) throws IOException {
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(destKey);
|
||||
out.write("valid\n".getBytes());
|
||||
} catch (DataFormatException dfe) {
|
||||
out.write("invalid\n".getBytes());
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void processConvertPrivate(String priv, OutputStream out) throws IOException {
|
||||
try {
|
||||
Destination dest = new Destination();
|
||||
dest.fromBase64(priv);
|
||||
String str = dest.toBase64();
|
||||
out.write(str.getBytes());
|
||||
out.write('\n');
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error converting private data", dfe);
|
||||
out.write("Error converting private key\n".getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
public void processClose(String which, boolean forced, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand((forced ? "close forced " : "close ") + which, buf);
|
||||
String str = (String) _tunnel.waitEventValue("closeResult");
|
||||
out.write((str + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* "genkey" returns with the base64 of the destination, followed by a tab, then the base64 of that
|
||||
* destination's private keys, then a newline.
|
||||
*
|
||||
*/
|
||||
public void processGenKey(OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("gentextkeys", buf);
|
||||
String priv = (String) _tunnel.waitEventValue("privateKey");
|
||||
String pub = (String) _tunnel.waitEventValue("publicDestination");
|
||||
out.write((pub + "\t" + priv).getBytes());
|
||||
out.write('\n');
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenClient(int listenPort, String peer, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("client " + listenPort + " " + peer, buf);
|
||||
Integer taskId = (Integer) _tunnel.waitEventValue("clientTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
String rv = (String) _tunnel.waitEventValue("openClientResult");
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenPort != 0) {
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
|
||||
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenHTTPClient(int listenPort, String proxy, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("httpclient " + listenPort + " " + proxy, buf);
|
||||
Integer taskId = (Integer) _tunnel.waitEventValue("httpclientTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
String rv = (String) _tunnel.waitEventValue("openHTTPClientResult");
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenPort != 0) {
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
|
||||
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenSOCKSTunnel(int listenPort, OutputStream out) throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("sockstunnel " + listenPort, buf);
|
||||
Integer taskId = (Integer) _tunnel.waitEventValue("sockstunnelTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
String rv = (String) _tunnel.waitEventValue("openSOCKSTunnelResult");
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenPort != 0) {
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
|
||||
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
public void processOpenServer(String serverHost, int serverPort, String privateKeys, OutputStream out)
|
||||
throws IOException {
|
||||
BufferLogger buf = new BufferLogger();
|
||||
_tunnel.runCommand("textserver " + serverHost + " " + serverPort + " " + privateKeys, buf);
|
||||
Integer taskId = (Integer) _tunnel.waitEventValue("serverTaskId");
|
||||
if (taskId.intValue() < 0) {
|
||||
out.write("error\n".getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
String rv = (String) _tunnel.waitEventValue("openServerResult");
|
||||
|
||||
if (rv.equals("error")) {
|
||||
out.write((rv + "\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
return;
|
||||
}
|
||||
|
||||
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
|
||||
buf.ignoreFurtherActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Frisbee.
|
||||
*
|
||||
*/
|
||||
public void unknownCommand(String command, OutputStream out) throws IOException {
|
||||
out.write("Unknown command: ".getBytes());
|
||||
out.write(command.getBytes());
|
||||
out.write("\n".getBytes());
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Runner thread that reads commands from the socket and fires off commands to
|
||||
* the TunnelManager
|
||||
*
|
||||
*/
|
||||
class TunnelManagerClientRunner implements Runnable {
|
||||
private final static Log _log = new Log(TunnelManagerClientRunner.class);
|
||||
private TunnelManager _mgr;
|
||||
private Socket _clientSocket;
|
||||
|
||||
public TunnelManagerClientRunner(TunnelManager mgr, Socket socket) {
|
||||
_clientSocket = socket;
|
||||
_mgr = mgr;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_log.debug("Client running");
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(_clientSocket.getInputStream()));
|
||||
OutputStream out = _clientSocket.getOutputStream();
|
||||
|
||||
String cmd = reader.readLine();
|
||||
if (cmd != null) processCommand(cmd, out);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error processing client commands", ioe);
|
||||
} finally {
|
||||
if (_clientSocket != null) try {
|
||||
_clientSocket.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
_log.debug("Client closed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the command string and fire off the appropriate tunnelManager method,
|
||||
* sending the results to the output stream
|
||||
*/
|
||||
private void processCommand(String command, OutputStream out) throws IOException {
|
||||
_log.debug("Processing [" + command + "]");
|
||||
StringTokenizer tok = new StringTokenizer(command);
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.unknownCommand(command, out);
|
||||
} else {
|
||||
String cmd = tok.nextToken();
|
||||
if ("quit".equalsIgnoreCase(cmd)) {
|
||||
_mgr.processQuit(out);
|
||||
} else if ("lookup".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens())
|
||||
_mgr.processLookup(tok.nextToken(), out);
|
||||
else
|
||||
_mgr.error("Usage: lookup <hostname>", out);
|
||||
} else if ("testdestination".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens())
|
||||
_mgr.processTestDestination(tok.nextToken(), out);
|
||||
else
|
||||
_mgr.error("Usage: testdestination <publicDestination>", out);
|
||||
} else if ("convertprivate".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens())
|
||||
_mgr.processConvertPrivate(tok.nextToken(), out);
|
||||
else
|
||||
_mgr.error("Usage: convertprivate <privateData>", out);
|
||||
} else if ("close".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens()) {
|
||||
String closeArg;
|
||||
if ((closeArg = tok.nextToken()).equals("forced")) {
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.processClose(tok.nextToken(), true, out);
|
||||
} else {
|
||||
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
|
||||
}
|
||||
} else {
|
||||
_mgr.processClose(closeArg, false, out);
|
||||
}
|
||||
} else {
|
||||
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
|
||||
}
|
||||
} else if ("genkey".equalsIgnoreCase(cmd)) {
|
||||
_mgr.processGenKey(out);
|
||||
} else if ("list".equalsIgnoreCase(cmd)) {
|
||||
_mgr.processList(out);
|
||||
} else if ("listen_on".equalsIgnoreCase(cmd)) {
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.processListenOn(tok.nextToken(), out);
|
||||
} else {
|
||||
_mgr.error("Usage: listen_on <ip>", out);
|
||||
}
|
||||
} else if ("openclient".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
String peer = null;
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openclient <listenPort> <peer>", out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openclient <listenport> <peer>", out);
|
||||
return;
|
||||
}
|
||||
peer = tok.nextToken();
|
||||
_mgr.processOpenClient(listenPort, peer, out);
|
||||
} else if ("openhttpclient".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
String proxy = "squid.i2p";
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openhttpclient <listenPort> [<proxy>]", out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
proxy = tok.nextToken();
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openclient <listenport> [<proxy>]", out);
|
||||
return;
|
||||
}
|
||||
_mgr.processOpenHTTPClient(listenPort, proxy, out);
|
||||
} else if ("opensockstunnel".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: opensockstunnel <listenPort>", out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: opensockstunnel <listenport>", out);
|
||||
return;
|
||||
}
|
||||
_mgr.processOpenSOCKSTunnel(listenPort, out);
|
||||
} else if ("openserver".equalsIgnoreCase(cmd)) {
|
||||
int listenPort = 0;
|
||||
String serverHost = null;
|
||||
String serverKeys = null;
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
|
||||
return;
|
||||
}
|
||||
serverHost = tok.nextToken();
|
||||
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String portStr = tok.nextToken();
|
||||
listenPort = Integer.parseInt(portStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_mgr.error("Bad listen port", out);
|
||||
return;
|
||||
}
|
||||
if (!tok.hasMoreTokens()) {
|
||||
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
|
||||
return;
|
||||
}
|
||||
serverKeys = tok.nextToken();
|
||||
_mgr.processOpenServer(serverHost, listenPort, serverKeys, out);
|
||||
} else {
|
||||
_mgr.unknownCommand(command, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Uuuugly... generate the edit/add forms for the various
|
||||
* I2PTunnel types (httpclient/client/server)
|
||||
*
|
||||
*/
|
||||
class WebEditPageFormGenerator {
|
||||
private static final String SELECT_TYPE_FORM =
|
||||
"<form action=\"edit.jsp\"> Type of tunnel: <select name=\"type\">" +
|
||||
"<option value=\"httpclient\">HTTP proxy</option>" +
|
||||
"<option value=\"client\">Client tunnel</option>" +
|
||||
"<option value=\"server\">Server tunnel</option>" +
|
||||
"</select> <input type=\"submit\" value=\"GO\" />" +
|
||||
"</form>\n";
|
||||
|
||||
/**
|
||||
* Retrieve the form requested
|
||||
*
|
||||
*/
|
||||
public static String getForm(WebEditPageHelper helper) {
|
||||
TunnelController controller = helper.getTunnelController();
|
||||
|
||||
if ( (helper.getType() == null) && (controller == null) )
|
||||
return SELECT_TYPE_FORM;
|
||||
|
||||
String id = helper.getNum();
|
||||
String type = helper.getType();
|
||||
if (controller != null)
|
||||
type = controller.getType();
|
||||
|
||||
if ("httpclient".equals(type))
|
||||
return getEditHttpClientForm(controller, id);
|
||||
else if ("client".equals(type))
|
||||
return getEditClientForm(controller, id);
|
||||
else if ("server".equals(type))
|
||||
return getEditServerForm(controller, id);
|
||||
else
|
||||
return "WTF, unknown type [" + type + "]";
|
||||
}
|
||||
|
||||
private static String getEditHttpClientForm(TunnelController controller, String id) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
addGeneral(buf, controller, id);
|
||||
buf.append("<b>Type:</b> <i>HTTP proxy</i><input type=\"hidden\" name=\"type\" value=\"httpclient\" /><br />\n");
|
||||
|
||||
addListeningOn(buf, controller, 4444);
|
||||
|
||||
buf.append("<b>Outproxies:</b> <input type=\"text\" name=\"proxyList\" size=\"20\" ");
|
||||
if ( (controller != null) && (controller.getProxyList() != null) )
|
||||
buf.append("value=\"").append(controller.getProxyList()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"squid.i2p\" ");
|
||||
buf.append("/><br />\n");
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String getEditClientForm(TunnelController controller, String id) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
addGeneral(buf, controller, id);
|
||||
buf.append("<b>Type:</b> <i>Client tunnel</i><input type=\"hidden\" name=\"type\" value=\"client\" /><br />\n");
|
||||
|
||||
addListeningOn(buf, controller, 2025 + new Random().nextInt(1000)); // 2025 since nextInt can be negative
|
||||
|
||||
buf.append("<b>Target:</b> <input type=\"text\" size=\"40\" name=\"targetDestination\" ");
|
||||
if ( (controller != null) && (controller.getTargetDestination() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetDestination()).append("\" ");
|
||||
buf.append(" /> (either the hosts.txt name or the full base64 destination)<br />\n");
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\"><br />\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String getEditServerForm(TunnelController controller, String id) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
addGeneral(buf, controller, id);
|
||||
buf.append("<b>Type:</b> <i>Server tunnel</i><input type=\"hidden\" name=\"type\" value=\"server\" /><br />\n");
|
||||
|
||||
buf.append("<b>Target host:</b> <input type=\"text\" size=\"40\" name=\"targetHost\" ");
|
||||
if ( (controller != null) && (controller.getTargetHost() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"localhost\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
|
||||
if ( (controller != null) && (controller.getTargetPort() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetPort()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"80\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Private key file:</b> <input type=\"text\" name=\"privKeyFile\" value=\"");
|
||||
if ( (controller != null) && (controller.getPrivKeyFile() != null) ) {
|
||||
buf.append(controller.getPrivKeyFile()).append("\" /><br />");
|
||||
} else {
|
||||
buf.append("myServer.privKey\" /><br />");
|
||||
buf.append("<input type=\"hidden\" name=\"privKeyGenerate\" value=\"true\" />");
|
||||
}
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start off the form and add some common fields (name, num, description)
|
||||
*
|
||||
* @param buf where to shove the form
|
||||
* @param controller tunnel in question, or null if we're creating a new tunnel
|
||||
* @param id index into the current list of tunnelControllerGroup.getControllers() list
|
||||
* (or null if we are generating an 'add' form)
|
||||
*/
|
||||
private static void addGeneral(StringBuffer buf, TunnelController controller, String id) {
|
||||
buf.append("<form action=\"edit.jsp\">");
|
||||
if (id != null)
|
||||
buf.append("<input type=\"hidden\" name=\"num\" value=\"").append(id).append("\" />");
|
||||
|
||||
buf.append("<b>Name:</b> <input type=\"text\" name=\"name\" size=\"20\" ");
|
||||
if ( (controller != null) && (controller.getName() != null) )
|
||||
buf.append("value=\"").append(controller.getName()).append("\" ");
|
||||
buf.append("/><br />\n");
|
||||
|
||||
buf.append("<b>Description:</b> <input type=\"text\" name=\"description\" size=\"60\" ");
|
||||
if ( (controller != null) && (controller.getDescription() != null) )
|
||||
buf.append("value=\"").append(controller.getDescription()).append("\" ");
|
||||
buf.append("/><br />\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the fields asking for what port and interface the tunnel should
|
||||
* listen on.
|
||||
*
|
||||
* @param buf where to shove the form
|
||||
* @param controller tunnel in question, or null if we're creating a new tunnel
|
||||
* @param defaultPort if we are creating a new tunnel, default the form to the given port
|
||||
*/
|
||||
private static void addListeningOn(StringBuffer buf, TunnelController controller, int defaultPort) {
|
||||
buf.append("<b>Listening on port:</b> <input type=\"text\" name=\"port\" size=\"20\" ");
|
||||
if ( (controller != null) && (controller.getListenPort() != null) )
|
||||
buf.append("value=\"").append(controller.getListenPort()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"").append(defaultPort).append("\" ");
|
||||
buf.append("/><br />\n");
|
||||
|
||||
String selectedOn = null;
|
||||
if ( (controller != null) && (controller.getListenOnInterface() != null) )
|
||||
selectedOn = controller.getListenOnInterface();
|
||||
|
||||
buf.append("<b>Reachable by:</b> ");
|
||||
buf.append("<select name=\"reachableBy\">");
|
||||
buf.append("<option value=\"127.0.0.1\" ");
|
||||
if ( (selectedOn != null) && ("127.0.0.1".equals(selectedOn)) )
|
||||
buf.append("selected=\"true\" ");
|
||||
buf.append(">Locally (127.0.0.1)</option>\n");
|
||||
buf.append("<option value=\"0.0.0.0\" ");
|
||||
if ( (selectedOn != null) && ("0.0.0.0".equals(selectedOn)) )
|
||||
buf.append("selected=\"true\" ");
|
||||
buf.append(">Everyone (0.0.0.0)</option>\n");
|
||||
buf.append("</select> ");
|
||||
buf.append("Other: <input type=\"text\" name=\"reachableByOther\" value=\"");
|
||||
if ( (selectedOn != null) && (!"127.0.0.1".equals(selectedOn)) && (!"0.0.0.0".equals(selectedOn)) )
|
||||
buf.append(selectedOn);
|
||||
buf.append("\"><br />\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fields for customizing the I2PSession options, including helpers for
|
||||
* tunnel depth and count, as well as I2CP host and port.
|
||||
*
|
||||
* @param buf where to shove the form
|
||||
* @param controller tunnel in question, or null if we're creating a new tunnel
|
||||
*/
|
||||
private static void addOptions(StringBuffer buf, TunnelController controller) {
|
||||
int tunnelDepth = 2;
|
||||
int numTunnels = 2;
|
||||
Properties opts = getOptions(controller);
|
||||
if (opts != null) {
|
||||
String depth = opts.getProperty("tunnels.depthInbound");
|
||||
if (depth != null) {
|
||||
try {
|
||||
tunnelDepth = Integer.parseInt(depth);
|
||||
} catch (NumberFormatException nfe) {
|
||||
tunnelDepth = 2;
|
||||
}
|
||||
}
|
||||
String num = opts.getProperty("tunnels.numInbound");
|
||||
if (num != null) {
|
||||
try {
|
||||
numTunnels = Integer.parseInt(num);
|
||||
} catch (NumberFormatException nfe) {
|
||||
numTunnels = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.append("<b>Tunnel depth:</b> ");
|
||||
buf.append("<select name=\"tunnelDepth\">");
|
||||
buf.append("<option value=\"0\" ");
|
||||
if (tunnelDepth == 0) buf.append(" selected=\"true\" ");
|
||||
buf.append(">0 hop tunnel (low anonymity, low latency)</option>");
|
||||
buf.append("<option value=\"1\" ");
|
||||
if (tunnelDepth == 1) buf.append(" selected=\"true\" ");
|
||||
buf.append(">1 hop tunnel (medium anonymity, medium latency)</option>");
|
||||
buf.append("<option value=\"2\" ");
|
||||
if (tunnelDepth == 2) buf.append(" selected=\"true\" ");
|
||||
buf.append(">2 hop tunnel (high anonymity, high latency)</option>");
|
||||
if (tunnelDepth > 2) {
|
||||
buf.append("<option value=\"").append(tunnelDepth).append("\" selected=\"true\" >");
|
||||
buf.append(tunnelDepth);
|
||||
buf.append(" hop tunnel (custom)</option>");
|
||||
}
|
||||
buf.append("</select><br />\n");
|
||||
|
||||
buf.append("<b>Tunnel count:</b> ");
|
||||
buf.append("<select name=\"tunnelCount\">");
|
||||
buf.append("<option value=\"1\" ");
|
||||
if (numTunnels == 1) buf.append(" selected=\"true\" ");
|
||||
buf.append(">1 inbound tunnel (low bandwidth usage, less reliability)</option>");
|
||||
buf.append("<option value=\"2\" ");
|
||||
if (numTunnels == 2) buf.append(" selected=\"true\" ");
|
||||
buf.append(">2 inbound tunnels (standard bandwidth usage, standard reliability)</option>");
|
||||
buf.append("<option value=\"3\" ");
|
||||
if (numTunnels == 3) buf.append(" selected=\"true\" ");
|
||||
buf.append(">3 inbound tunnels (higher bandwidth usage, higher reliability)</option>");
|
||||
|
||||
if (numTunnels > 3) {
|
||||
buf.append("<option value=\"").append(numTunnels).append("\" selected=\"true\" >");
|
||||
buf.append(numTunnels);
|
||||
buf.append(" inbound tunnels (custom)</option>");
|
||||
}
|
||||
buf.append("</select><br />\n");
|
||||
|
||||
buf.append("<b>I2CP host:</b> ");
|
||||
buf.append("<input type=\"text\" name=\"clientHost\" size=\"20\" value=\"");
|
||||
if ( (controller != null) && (controller.getI2CPHost() != null) )
|
||||
buf.append(controller.getI2CPHost());
|
||||
else
|
||||
buf.append("localhost");
|
||||
buf.append("\" /><br />\n");
|
||||
buf.append("<b>I2CP port:</b> ");
|
||||
buf.append("<input type=\"text\" name=\"clientPort\" size=\"20\" value=\"");
|
||||
if ( (controller != null) && (controller.getI2CPPort() != null) )
|
||||
buf.append(controller.getI2CPPort());
|
||||
else
|
||||
buf.append("7654");
|
||||
buf.append("\" /><br />\n");
|
||||
|
||||
buf.append("<b>Other custom options:</b> \n");
|
||||
buf.append("<input type=\"text\" name=\"customOptions\" size=\"60\" value=\"");
|
||||
if (opts != null) {
|
||||
int i = 0;
|
||||
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = opts.getProperty(key);
|
||||
if ("tunnels.depthInbound".equals(key)) continue;
|
||||
if ("tunnels.numInbound".equals(key)) continue;
|
||||
if (i != 0) buf.append(' ');
|
||||
buf.append(key).append('=').append(val);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
buf.append("\" /><br />\n");
|
||||
buf.append("<b>Start automatically?</b> \n");
|
||||
buf.append("<input type=\"checkbox\" name=\"startOnLoad\" value=\"true\" ");
|
||||
if ( (controller != null) && (controller.getStartOnLoad()) )
|
||||
buf.append(" checked=\"true\" />\n<br />\n");
|
||||
else
|
||||
buf.append(" />\n<br />\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the client options from the tunnel
|
||||
*
|
||||
* @return map of name=val to be used as I2P session options
|
||||
*/
|
||||
private static Properties getOptions(TunnelController controller) {
|
||||
if (controller == null) return null;
|
||||
String opts = controller.getClientOptions();
|
||||
StringTokenizer tok = new StringTokenizer(opts);
|
||||
Properties props = new Properties();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
||||
continue;
|
||||
String key = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
props.setProperty(key, val);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* UUUUuuuuuugly glue code to handle bean interaction from the web, process
|
||||
* that data, and spit out the results (or the form requested). The basic
|
||||
* usage is to set any of the fields with data then query the bean via
|
||||
* getActionResults() which triggers the request processing (taking all the
|
||||
* provided data, doing what needs to be done) and returns the results of those
|
||||
* activites. Then a subsequent call to getEditForm() generates the HTML form
|
||||
* to either edit the currently selected tunnel (if specified) or add a new one.
|
||||
* This functionality is delegated to the WebEditPageFormGenerator.
|
||||
*
|
||||
*/
|
||||
public class WebEditPageHelper {
|
||||
private Log _log;
|
||||
private String _action;
|
||||
private String _type;
|
||||
private String _id;
|
||||
private String _name;
|
||||
private String _description;
|
||||
private String _i2cpHost;
|
||||
private String _i2cpPort;
|
||||
private String _tunnelDepth;
|
||||
private String _tunnelCount;
|
||||
private String _customOptions;
|
||||
private String _proxyList;
|
||||
private String _port;
|
||||
private String _reachableBy;
|
||||
private String _reachableByOther;
|
||||
private String _targetDestination;
|
||||
private String _targetHost;
|
||||
private String _targetPort;
|
||||
private String _privKeyFile;
|
||||
private boolean _startOnLoad;
|
||||
private boolean _privKeyGenerate;
|
||||
private boolean _removeConfirmed;
|
||||
|
||||
public WebEditPageHelper() {
|
||||
_action = null;
|
||||
_type = null;
|
||||
_id = null;
|
||||
_removeConfirmed = false;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(WebEditPageHelper.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for form submit - either "Save" or Remove"
|
||||
*/
|
||||
public void setAction(String action) {
|
||||
_action = (action != null ? action.trim() : null);
|
||||
}
|
||||
/**
|
||||
* What type of tunnel (httpclient, client, or server). This is
|
||||
* required when adding a new tunnel.
|
||||
*
|
||||
*/
|
||||
public void setType(String type) {
|
||||
_type = (type != null ? type.trim() : null);
|
||||
}
|
||||
/**
|
||||
* Which particular tunnel should be edited (index into the current
|
||||
* TunnelControllerGroup's getControllers() list). This is required
|
||||
* when editing a tunnel, but not when adding a new one.
|
||||
*
|
||||
*/
|
||||
public void setNum(String id) {
|
||||
_id = (id != null ? id.trim() : null);
|
||||
}
|
||||
String getType() { return _type; }
|
||||
String getNum() { return _id; }
|
||||
|
||||
/** Short name of the tunnel */
|
||||
public void setName(String name) {
|
||||
_name = (name != null ? name.trim() : null);
|
||||
}
|
||||
/** one line description */
|
||||
public void setDescription(String description) {
|
||||
_description = (description != null ? description.trim() : null);
|
||||
}
|
||||
/** I2CP host the router is on */
|
||||
public void setClientHost(String host) {
|
||||
_i2cpHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** I2CP port the router is on */
|
||||
public void setClientPort(String port) {
|
||||
_i2cpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** how many hops to use for inbound tunnels */
|
||||
public void setTunnelDepth(String tunnelDepth) {
|
||||
_tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
|
||||
}
|
||||
/** how many parallel inbound tunnels to use */
|
||||
public void setTunnelCount(String tunnelCount) {
|
||||
_tunnelCount = (tunnelCount != null ? tunnelCount.trim() : null);
|
||||
}
|
||||
/** what I2P session overrides should be used */
|
||||
public void setCustomOptions(String customOptions) {
|
||||
_customOptions = (customOptions != null ? customOptions.trim() : null);
|
||||
}
|
||||
/** what HTTP outproxies should be used (httpclient specific) */
|
||||
public void setProxyList(String proxyList) {
|
||||
_proxyList = (proxyList != null ? proxyList.trim() : null);
|
||||
}
|
||||
/** what port should this client/httpclient listen on */
|
||||
public void setPort(String port) {
|
||||
_port = (port != null ? port.trim() : null);
|
||||
}
|
||||
/**
|
||||
* what interface should this client/httpclient listen on (unless
|
||||
* overridden by the setReachableByOther() field)
|
||||
*/
|
||||
public void setReachableBy(String reachableBy) {
|
||||
_reachableBy = (reachableBy != null ? reachableBy.trim() : null);
|
||||
}
|
||||
/**
|
||||
* If specified, defines the exact IP interface to listen for requests
|
||||
* on (in the case of client/httpclient tunnels)
|
||||
*/
|
||||
public void setReachableByOther(String reachableByOther) {
|
||||
_reachableByOther = (reachableByOther != null ? reachableByOther.trim() : null);
|
||||
}
|
||||
/** What peer does this client tunnel point at */
|
||||
public void setTargetDestination(String dest) {
|
||||
_targetDestination = (dest != null ? dest.trim() : null);
|
||||
}
|
||||
/** What host does this server tunnel point at */
|
||||
public void setTargetHost(String host) {
|
||||
_targetHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** What port does this server tunnel point at */
|
||||
public void setTargetPort(String port) {
|
||||
_targetPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** What filename is this server tunnel's private keys stored in */
|
||||
public void setPrivKeyFile(String file) {
|
||||
_privKeyFile = (file != null ? file.trim() : null);
|
||||
}
|
||||
/**
|
||||
* If called with any value, we want to generate a new destination
|
||||
* for this server tunnel. This won't cause any existing private keys
|
||||
* to be overwritten, however.
|
||||
*/
|
||||
public void setPrivKeyGenerate(String moo) {
|
||||
_privKeyGenerate = true;
|
||||
}
|
||||
/**
|
||||
* If called with any value (and the form submitted with action=Remove),
|
||||
* we really do want to stop and remove the tunnel.
|
||||
*/
|
||||
public void setRemoveConfirm(String moo) {
|
||||
_removeConfirmed = true;
|
||||
}
|
||||
/**
|
||||
* If called with any value, we want this tunnel to start whenever it is
|
||||
* loaded (aka right now and whenever the router is started up)
|
||||
*/
|
||||
public void setStartOnLoad(String moo) {
|
||||
_startOnLoad = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the form and display any resulting messages
|
||||
*
|
||||
*/
|
||||
public String getActionResults() {
|
||||
try {
|
||||
return processAction();
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Internal error processing request", t);
|
||||
return "Internal error - " + t.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an HTML form to edit / create a tunnel according to the
|
||||
* specified fields
|
||||
*/
|
||||
public String getEditForm() {
|
||||
try {
|
||||
return WebEditPageFormGenerator.getForm(this);
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Internal error retrieving edit form", t);
|
||||
return "Internal error - " + t.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the tunnel pointed to by the current id
|
||||
*
|
||||
*/
|
||||
TunnelController getTunnelController() {
|
||||
if (_id == null) return null;
|
||||
int id = -1;
|
||||
try {
|
||||
id = Integer.parseInt(_id);
|
||||
List controllers = TunnelControllerGroup.getInstance().getControllers();
|
||||
if ( (id < 0) || (id >= controllers.size()) )
|
||||
return null;
|
||||
else
|
||||
return (TunnelController)controllers.get(id);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid tunnel id [" + _id + "]", nfe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String processAction() {
|
||||
if ( (_action == null) || (_action.trim().length() <= 0) )
|
||||
return "";
|
||||
if ("Save".equals(_action))
|
||||
return save();
|
||||
else if ("Remove".equals(_action))
|
||||
return remove();
|
||||
else
|
||||
return "Action <i>" + _action + "</i> unknown";
|
||||
}
|
||||
|
||||
private String remove() {
|
||||
if (!_removeConfirmed)
|
||||
return "Please confirm removal";
|
||||
|
||||
TunnelController cur = getTunnelController();
|
||||
if (cur == null)
|
||||
return "Invalid tunnel number";
|
||||
|
||||
List msgs = TunnelControllerGroup.getInstance().removeController(cur);
|
||||
msgs.addAll(doSave());
|
||||
return getMessages(msgs);
|
||||
}
|
||||
|
||||
private String save() {
|
||||
if (_type == null)
|
||||
return "<b>Invalid form submission (no type?)</b>";
|
||||
Properties config = getConfig();
|
||||
if (config == null)
|
||||
return "<b>Invalid params</b>";
|
||||
|
||||
TunnelController cur = getTunnelController();
|
||||
if (cur == null) {
|
||||
// creating new
|
||||
cur = new TunnelController(config, "", _privKeyGenerate);
|
||||
TunnelControllerGroup.getInstance().addController(cur);
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
|
||||
return getMessages(doSave());
|
||||
}
|
||||
private List doSave() {
|
||||
TunnelControllerGroup.getInstance().saveConfig();
|
||||
return TunnelControllerGroup.getInstance().clearAllMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on all provided data, create a set of configuration parameters
|
||||
* suitable for use in a TunnelController. This will replace (not add to)
|
||||
* any existing parameters, so this should return a comprehensive mapping.
|
||||
*
|
||||
*/
|
||||
private Properties getConfig() {
|
||||
Properties config = new Properties();
|
||||
updateConfigGeneric(config);
|
||||
|
||||
if ("httpclient".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_proxyList != null)
|
||||
config.setProperty("proxyList", _proxyList);
|
||||
} else if ("client".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_targetDestination != null)
|
||||
config.setProperty("targetDestination", _targetDestination);
|
||||
} else if ("server".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private void updateConfigGeneric(Properties config) {
|
||||
config.setProperty("type", _type);
|
||||
if (_name != null)
|
||||
config.setProperty("name", _name);
|
||||
if (_description != null)
|
||||
config.setProperty("description", _description);
|
||||
if (_i2cpHost != null)
|
||||
config.setProperty("i2cpHost", _i2cpHost);
|
||||
if (_i2cpPort != null)
|
||||
config.setProperty("i2cpPort", _i2cpPort);
|
||||
|
||||
if (_customOptions != null) {
|
||||
StringTokenizer tok = new StringTokenizer(_customOptions);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
||||
continue;
|
||||
String key = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
if ("tunnels.numInbound".equals(key)) continue;
|
||||
if ("tunnels.depthInbound".equals(key)) continue;
|
||||
config.setProperty("option." + key, val);
|
||||
}
|
||||
}
|
||||
|
||||
config.setProperty("startOnLoad", _startOnLoad + "");
|
||||
|
||||
if (_tunnelCount != null)
|
||||
config.setProperty("option.tunnels.numInbound", _tunnelCount);
|
||||
if (_tunnelDepth != null)
|
||||
config.setProperty("option.tunnels.depthInbound", _tunnelDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty print the messages provided
|
||||
*
|
||||
*/
|
||||
private String getMessages(List msgs) {
|
||||
if (msgs == null) return "";
|
||||
int num = msgs.size();
|
||||
switch (num) {
|
||||
case 0: return "";
|
||||
case 1: return (String)msgs.get(0);
|
||||
default:
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("<ul>");
|
||||
for (int i = 0; i < num; i++)
|
||||
buf.append("<li>").append((String)msgs.get(i)).append("</li>\n");
|
||||
buf.append("</ul>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.util.List;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Ugly hack to let the web interface access the list of known tunnels and
|
||||
* control their operation. Any data submitted by setting properties are
|
||||
* acted upon by calling getActionResults() (which returns any messages
|
||||
* generated). In addition, the getSummaryList() generates the html for
|
||||
* summarizing all of the tunnels known, including both their status and the
|
||||
* links to edit, stop, or start them.
|
||||
*
|
||||
*/
|
||||
public class WebStatusPageHelper {
|
||||
private Log _log;
|
||||
private String _action;
|
||||
private int _controllerNum;
|
||||
|
||||
public WebStatusPageHelper() {
|
||||
_action = null;
|
||||
_controllerNum = -1;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(WebStatusPageHelper.class);
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
_action = action;
|
||||
}
|
||||
public void setNum(String num) {
|
||||
if (num != null) {
|
||||
try {
|
||||
_controllerNum = Integer.parseInt(num);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_controllerNum = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getActionResults() {
|
||||
try {
|
||||
return processAction();
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Internal error processing web status", t);
|
||||
return "Internal error processing request - " + t.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public String getSummaryList() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
StringBuffer buf = new StringBuffer(4*1024);
|
||||
buf.append("<ul>");
|
||||
List tunnels = group.getControllers();
|
||||
for (int i = 0; i < tunnels.size(); i++) {
|
||||
buf.append("<li>\n");
|
||||
getSummary(buf, i, (TunnelController)tunnels.get(i));
|
||||
buf.append("</li>\n");
|
||||
}
|
||||
buf.append("</ul>");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private void getSummary(StringBuffer buf, int num, TunnelController controller) {
|
||||
buf.append("<b>").append(controller.getName()).append("</b>: ");
|
||||
if (controller.getIsRunning()) {
|
||||
buf.append("<i>running</i> ");
|
||||
buf.append("<a href=\"index.jsp?num=").append(num).append("&action=stop\">stop</a> ");
|
||||
} else {
|
||||
buf.append("<i>not running</i> ");
|
||||
buf.append("<a href=\"index.jsp?num=").append(num).append("&action=start\">start</a> ");
|
||||
}
|
||||
buf.append("<a href=\"edit.jsp?num=").append(num).append("\">edit</a> ");
|
||||
buf.append("<br />\n");
|
||||
controller.getSummary(buf);
|
||||
}
|
||||
|
||||
private String processAction() {
|
||||
if ( (_action == null) || (_action.trim().length() <= 0) )
|
||||
return getMessages();
|
||||
if ("Stop all".equals(_action))
|
||||
return stopAll();
|
||||
else if ("Start all".equals(_action))
|
||||
return startAll();
|
||||
else if ("Restart all".equals(_action))
|
||||
return restartAll();
|
||||
else if ("Reload config".equals(_action))
|
||||
return reloadConfig();
|
||||
else if ("stop".equals(_action))
|
||||
return stop();
|
||||
else if ("start".equals(_action))
|
||||
return start();
|
||||
else
|
||||
return "Action <i>" + _action + "</i> unknown";
|
||||
}
|
||||
private String stopAll() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
List msgs = group.stopAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String startAll() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
List msgs = group.startAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String restartAll() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
List msgs = group.restartAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String reloadConfig() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
group.reloadControllers();
|
||||
return "Config reloaded";
|
||||
}
|
||||
private String start() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
if (_controllerNum < 0) return "Invalid tunnel";
|
||||
|
||||
List controllers = group.getControllers();
|
||||
if (_controllerNum >= controllers.size()) return "Invalid tunnel";
|
||||
TunnelController controller = (TunnelController)controllers.get(_controllerNum);
|
||||
controller.startTunnel();
|
||||
return getMessages(controller.clearMessages());
|
||||
}
|
||||
|
||||
private String stop() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
|
||||
|
||||
if (_controllerNum < 0) return "Invalid tunnel";
|
||||
|
||||
List controllers = group.getControllers();
|
||||
if (_controllerNum >= controllers.size()) return "Invalid tunnel";
|
||||
TunnelController controller = (TunnelController)controllers.get(_controllerNum);
|
||||
controller.stopTunnel();
|
||||
return getMessages(controller.clearMessages());
|
||||
}
|
||||
|
||||
private String getMessages() {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
if (group == null)
|
||||
return "";
|
||||
|
||||
return getMessages(group.clearAllMessages());
|
||||
}
|
||||
|
||||
private String getMessages(List msgs) {
|
||||
if (msgs == null) return "";
|
||||
int num = msgs.size();
|
||||
switch (num) {
|
||||
case 0: return "";
|
||||
case 1: return (String)msgs.get(0);
|
||||
default:
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("<ul>");
|
||||
for (int i = 0; i < num; i++)
|
||||
buf.append("<li>").append((String)msgs.get(i)).append("</li>\n");
|
||||
buf.append("</ul>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s);
|
||||
Socket clientSock = serv.getClientSocket();
|
||||
I2PSocket destSock = serv.getDestinationI2PSocket();
|
||||
new I2PTunnelRunner(clientSock, destSock, sockLock, null);
|
||||
new I2PTunnelRunner(clientSock, destSock, sockLock, null, mySockets);
|
||||
} catch (SOCKSException e) {
|
||||
_log.error("Error from SOCKS connection: " + e.getMessage());
|
||||
closeSocket(s);
|
||||
|
||||
@@ -14,7 +14,6 @@ import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.Log;
|
||||
@@ -81,7 +80,7 @@ public abstract class SOCKSServer {
|
||||
if (connHostName.toLowerCase().endsWith(".i2p")) {
|
||||
_log.debug("connecting to " + connHostName + "...");
|
||||
I2PSocketManager sm = I2PSocketManagerFactory.createManager();
|
||||
destSock = sm.connect(I2PTunnel.destFromName(connHostName), new I2PSocketOptions());
|
||||
destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
|
||||
confirmConnection();
|
||||
_log.debug("connection confirmed - exchanging data...");
|
||||
} else {
|
||||
|
||||
225
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
Normal file
@@ -0,0 +1,225 @@
|
||||
package net.i2p.i2ptunnel.web;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2005 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Ugly little accessor for the edit page
|
||||
*/
|
||||
public class EditBean extends IndexBean {
|
||||
public EditBean() { super(); }
|
||||
|
||||
public static boolean staticIsClient(int tunnel) {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
List controllers = group.getControllers();
|
||||
if (controllers.size() > tunnel) {
|
||||
TunnelController cur = (TunnelController)controllers.get(tunnel);
|
||||
if (cur == null) return false;
|
||||
return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getTargetHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getTargetHost();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
public String getTargetPort(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getTargetPort();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
public String getSpoofedHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getSpoofedHost();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
public String getPrivateKeyFile(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getPrivKeyFile();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean startAutomatically(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getStartOnLoad();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSharedClient(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return "true".equalsIgnoreCase(tun.getSharedClient());
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean shouldDelay(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String delay = opts.getProperty("i2p.streaming.connectDelay");
|
||||
if ( (delay == null) || ("0".equals(delay)) )
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInteractive(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String wsiz = opts.getProperty("i2p.streaming.maxWindowSize");
|
||||
if ( (wsiz == null) || (!"1".equals(wsiz)) )
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTunnelDepth(int tunnel, int defaultLength) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String len = opts.getProperty("inbound.length");
|
||||
if (len == null) return defaultLength;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultLength;
|
||||
}
|
||||
} else {
|
||||
return defaultLength;
|
||||
}
|
||||
} else {
|
||||
return defaultLength;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTunnelCount(int tunnel, int defaultCount) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String len = opts.getProperty("inbound.quantity");
|
||||
if (len == null) return defaultCount;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultCount;
|
||||
}
|
||||
} else {
|
||||
return defaultCount;
|
||||
}
|
||||
} else {
|
||||
return defaultCount;
|
||||
}
|
||||
}
|
||||
|
||||
public String getI2CPHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getI2CPHost();
|
||||
else
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
public String getI2CPPort(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getI2CPPort();
|
||||
else
|
||||
return "7654";
|
||||
}
|
||||
|
||||
public String getCustomOptions(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts == null) return "";
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
int i = 0;
|
||||
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = opts.getProperty(key);
|
||||
if ("inbound.length".equals(key)) continue;
|
||||
if ("outbound.length".equals(key)) continue;
|
||||
if ("inbound.quantity".equals(key)) continue;
|
||||
if ("outbound.quantity".equals(key)) continue;
|
||||
if ("inbound.nickname".equals(key)) continue;
|
||||
if ("outbound.nickname".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
|
||||
if (i != 0) buf.append(' ');
|
||||
buf.append(key).append('=').append(val);
|
||||
i++;
|
||||
}
|
||||
return buf.toString();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the client options from the tunnel
|
||||
*
|
||||
* @return map of name=val to be used as I2P session options
|
||||
*/
|
||||
private static Properties getOptions(TunnelController controller) {
|
||||
if (controller == null) return null;
|
||||
String opts = controller.getClientOptions();
|
||||
StringTokenizer tok = new StringTokenizer(opts);
|
||||
Properties props = new Properties();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
||||
continue;
|
||||
String key = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
props.setProperty(key, val);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
650
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
Normal file
@@ -0,0 +1,650 @@
|
||||
package net.i2p.i2ptunnel.web;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2005 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple accessor for exposing tunnel info, but also an ugly form handler
|
||||
*
|
||||
*/
|
||||
public class IndexBean {
|
||||
protected I2PAppContext _context;
|
||||
protected Log _log;
|
||||
protected TunnelControllerGroup _group;
|
||||
private String _action;
|
||||
private int _tunnel;
|
||||
private long _prevNonce;
|
||||
private long _curNonce;
|
||||
private long _nextNonce;
|
||||
private String _passphrase;
|
||||
|
||||
private String _type;
|
||||
private String _name;
|
||||
private String _description;
|
||||
private String _i2cpHost;
|
||||
private String _i2cpPort;
|
||||
private String _tunnelDepth;
|
||||
private String _tunnelCount;
|
||||
private boolean _connectDelay;
|
||||
private String _customOptions;
|
||||
private String _proxyList;
|
||||
private String _port;
|
||||
private String _reachableBy;
|
||||
private String _reachableByOther;
|
||||
private String _targetDestination;
|
||||
private String _targetHost;
|
||||
private String _targetPort;
|
||||
private String _spoofedHost;
|
||||
private String _privKeyFile;
|
||||
private String _profile;
|
||||
private boolean _startOnLoad;
|
||||
private boolean _sharedClient;
|
||||
private boolean _privKeyGenerate;
|
||||
private boolean _removeConfirmed;
|
||||
|
||||
public static final int RUNNING = 1;
|
||||
public static final int STARTING = 2;
|
||||
public static final int NOT_RUNNING = 3;
|
||||
|
||||
public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase";
|
||||
static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
|
||||
static final String CLIENT_NICKNAME = "shared clients";
|
||||
|
||||
public IndexBean() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(IndexBean.class);
|
||||
_group = TunnelControllerGroup.getInstance();
|
||||
_action = null;
|
||||
_tunnel = -1;
|
||||
_curNonce = -1;
|
||||
_prevNonce = -1;
|
||||
try {
|
||||
String nonce = System.getProperty(PROP_NONCE);
|
||||
if (nonce != null)
|
||||
_prevNonce = Long.parseLong(nonce);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
_nextNonce = _context.random().nextLong();
|
||||
System.setProperty(PROP_NONCE, Long.toString(_nextNonce));
|
||||
}
|
||||
|
||||
public long getNextNonce() { return _nextNonce; }
|
||||
public void setNonce(String nonce) {
|
||||
if ( (nonce == null) || (nonce.trim().length() <= 0) ) return;
|
||||
try {
|
||||
_curNonce = Long.parseLong(nonce);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_curNonce = -1;
|
||||
}
|
||||
}
|
||||
public void setPassphrase(String phrase) {
|
||||
_passphrase = phrase;
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
if ( (action == null) || (action.trim().length() <= 0) ) return;
|
||||
_action = action;
|
||||
}
|
||||
public void setTunnel(String tunnel) {
|
||||
if ( (tunnel == null) || (tunnel.trim().length() <= 0) ) return;
|
||||
try {
|
||||
_tunnel = Integer.parseInt(tunnel);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_tunnel = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validPassphrase(String proposed) {
|
||||
if (proposed == null) return false;
|
||||
String pass = _context.getProperty(PROP_TUNNEL_PASSPHRASE);
|
||||
if ( (pass != null) && (pass.trim().length() > 0) )
|
||||
return pass.trim().equals(proposed.trim());
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private String processAction() {
|
||||
if ( (_action == null) || (_action.trim().length() <= 0) )
|
||||
return "";
|
||||
if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) )
|
||||
return "Invalid nonce, are you being spoofed?";
|
||||
if ("Stop all tunnels".equals(_action))
|
||||
return stopAll();
|
||||
else if ("Start all tunnels".equals(_action))
|
||||
return startAll();
|
||||
else if ("Restart all".equals(_action))
|
||||
return restartAll();
|
||||
else if ("Reload config".equals(_action))
|
||||
return reloadConfig();
|
||||
else if ("stop".equals(_action))
|
||||
return stop();
|
||||
else if ("start".equals(_action))
|
||||
return start();
|
||||
else if ("Save changes".equals(_action))
|
||||
return saveChanges();
|
||||
else if ("Delete this proxy".equals(_action))
|
||||
return deleteTunnel();
|
||||
else
|
||||
return "Action " + _action + " unknown";
|
||||
}
|
||||
private String stopAll() {
|
||||
if (_group == null) return "";
|
||||
List msgs = _group.stopAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String startAll() {
|
||||
if (_group == null) return "";
|
||||
List msgs = _group.startAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String restartAll() {
|
||||
if (_group == null) return "";
|
||||
List msgs = _group.restartAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String reloadConfig() {
|
||||
if (_group == null) return "";
|
||||
|
||||
_group.reloadControllers();
|
||||
return "Config reloaded";
|
||||
}
|
||||
private String start() {
|
||||
if (_tunnel < 0) return "Invalid tunnel";
|
||||
|
||||
List controllers = _group.getControllers();
|
||||
if (_tunnel >= controllers.size()) return "Invalid tunnel";
|
||||
TunnelController controller = (TunnelController)controllers.get(_tunnel);
|
||||
controller.startTunnelBackground();
|
||||
return "";
|
||||
}
|
||||
|
||||
private String stop() {
|
||||
if (_tunnel < 0) return "Invalid tunnel";
|
||||
|
||||
List controllers = _group.getControllers();
|
||||
if (_tunnel >= controllers.size()) return "Invalid tunnel";
|
||||
TunnelController controller = (TunnelController)controllers.get(_tunnel);
|
||||
controller.stopTunnel();
|
||||
return "";
|
||||
}
|
||||
|
||||
private String saveChanges() {
|
||||
TunnelController cur = getController(_tunnel);
|
||||
|
||||
Properties config = getConfig();
|
||||
if (config == null)
|
||||
return "Invalid params";
|
||||
|
||||
if (cur == null) {
|
||||
// creating new
|
||||
cur = new TunnelController(config, "", true);
|
||||
_group.addController(cur);
|
||||
if (cur.getStartOnLoad())
|
||||
cur.startTunnelBackground();
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same
|
||||
// I2CP options
|
||||
List controllers = _group.getControllers();
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelController c = (TunnelController)controllers.get(i);
|
||||
if (c == cur) continue;
|
||||
//only change when they really are declared of beeing a sharedClient
|
||||
if (("httpclient".equals(c.getType()) || "client".equals(c.getType())) && "true".equalsIgnoreCase(c.getSharedClient())) {
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelCount != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
cOpt.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
}
|
||||
if (_tunnelDepth != null) {
|
||||
cOpt.setProperty("option.inbound.length", _tunnelDepth);
|
||||
cOpt.setProperty("option.outbound.length", _tunnelDepth);
|
||||
}
|
||||
cOpt.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
cOpt.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
|
||||
c.setConfig(cOpt, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List msgs = doSave();
|
||||
msgs.add(0, "Changes saved");
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private List doSave() {
|
||||
_group.saveConfig();
|
||||
return _group.clearAllMessages();
|
||||
}
|
||||
private String deleteTunnel() {
|
||||
if (!_removeConfirmed)
|
||||
return "Please confirm removal";
|
||||
|
||||
TunnelController cur = getController(_tunnel);
|
||||
if (cur == null)
|
||||
return "Invalid tunnel number";
|
||||
|
||||
List msgs = _group.removeController(cur);
|
||||
msgs.addAll(doSave());
|
||||
return getMessages(msgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes any action requested (start/stop/etc) and dump out the
|
||||
* messages.
|
||||
*
|
||||
*/
|
||||
public String getMessages() {
|
||||
if (_group == null)
|
||||
return "";
|
||||
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
if (_action != null) {
|
||||
try {
|
||||
buf.append(processAction()).append("\n");
|
||||
} catch (Exception e) {
|
||||
_log.log(Log.CRIT, "Error processing " + _action, e);
|
||||
}
|
||||
}
|
||||
getMessages(_group.clearAllMessages(), buf);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
////
|
||||
// The remaining methods are simple bean props for the jsp to query
|
||||
////
|
||||
|
||||
public int getTunnelCount() {
|
||||
if (_group == null) return 0;
|
||||
return _group.getControllers().size();
|
||||
}
|
||||
|
||||
public boolean isClient(int tunnelNum) {
|
||||
TunnelController cur = getController(tunnelNum);
|
||||
if (cur == null) return false;
|
||||
return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
|
||||
}
|
||||
|
||||
public String getTunnelName(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getName();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getClientPort(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getListenPort();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getTunnelType(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return getTypeName(tun.getType());
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getTypeName(String internalType) {
|
||||
if ("client".equals(internalType)) return "Client proxy";
|
||||
else if ("httpclient".equals(internalType)) return "HTTP proxy";
|
||||
else if ("server".equals(internalType)) return "Server";
|
||||
else if ("httpserver".equals(internalType)) return "HTTP server";
|
||||
else return internalType;
|
||||
}
|
||||
|
||||
public String getInternalType(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getType();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getClientInterface(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getListenOnInterface();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public int getTunnelStatus(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun == null) return NOT_RUNNING;
|
||||
if (tun.getIsRunning()) return RUNNING;
|
||||
else if (tun.getIsStarting()) return STARTING;
|
||||
else return NOT_RUNNING;
|
||||
}
|
||||
|
||||
public String getTunnelDescription(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getDescription();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getSharedClient(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getSharedClient();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getClientDestination(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun == null) return "";
|
||||
if ("client".equals(tun.getType())) return tun.getTargetDestination();
|
||||
else return tun.getProxyList();
|
||||
}
|
||||
|
||||
public String getServerTarget(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getTargetHost() + ':' + tun.getTargetPort();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getDestinationBase64(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
String rv = tun.getMyDestination();
|
||||
if (rv != null)
|
||||
return rv;
|
||||
else
|
||||
return "";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// bean props for form submission
|
||||
///
|
||||
|
||||
/**
|
||||
* What type of tunnel (httpclient, client, or server). This is
|
||||
* required when adding a new tunnel.
|
||||
*
|
||||
*/
|
||||
public void setType(String type) {
|
||||
_type = (type != null ? type.trim() : null);
|
||||
}
|
||||
String getType() { return _type; }
|
||||
|
||||
/** Short name of the tunnel */
|
||||
public void setName(String name) {
|
||||
_name = (name != null ? name.trim() : null);
|
||||
}
|
||||
/** one line description */
|
||||
public void setDescription(String description) {
|
||||
_description = (description != null ? description.trim() : null);
|
||||
}
|
||||
/** I2CP host the router is on */
|
||||
public void setClientHost(String host) {
|
||||
_i2cpHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** I2CP port the router is on */
|
||||
public void setClientPort(String port) {
|
||||
_i2cpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** how many hops to use for inbound tunnels */
|
||||
public void setTunnelDepth(String tunnelDepth) {
|
||||
_tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
|
||||
}
|
||||
/** how many parallel inbound tunnels to use */
|
||||
public void setTunnelCount(String tunnelCount) {
|
||||
_tunnelCount = (tunnelCount != null ? tunnelCount.trim() : null);
|
||||
}
|
||||
/** what I2P session overrides should be used */
|
||||
public void setCustomOptions(String customOptions) {
|
||||
_customOptions = (customOptions != null ? customOptions.trim() : null);
|
||||
}
|
||||
/** what HTTP outproxies should be used (httpclient specific) */
|
||||
public void setProxyList(String proxyList) {
|
||||
_proxyList = (proxyList != null ? proxyList.trim() : null);
|
||||
}
|
||||
/** what port should this client/httpclient listen on */
|
||||
public void setPort(String port) {
|
||||
_port = (port != null ? port.trim() : null);
|
||||
}
|
||||
/**
|
||||
* what interface should this client/httpclient listen on (unless
|
||||
* overridden by the setReachableByOther() field)
|
||||
*/
|
||||
public void setReachableBy(String reachableBy) {
|
||||
_reachableBy = (reachableBy != null ? reachableBy.trim() : null);
|
||||
}
|
||||
/**
|
||||
* If specified, defines the exact IP interface to listen for requests
|
||||
* on (in the case of client/httpclient tunnels)
|
||||
*/
|
||||
public void setReachableByOther(String reachableByOther) {
|
||||
_reachableByOther = (reachableByOther != null ? reachableByOther.trim() : null);
|
||||
}
|
||||
/** What peer does this client tunnel point at */
|
||||
public void setTargetDestination(String dest) {
|
||||
_targetDestination = (dest != null ? dest.trim() : null);
|
||||
}
|
||||
/** What host does this server tunnel point at */
|
||||
public void setTargetHost(String host) {
|
||||
_targetHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** What port does this server tunnel point at */
|
||||
public void setTargetPort(String port) {
|
||||
_targetPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** What host does this http server tunnel spoof */
|
||||
public void setSpoofedHost(String host) {
|
||||
_spoofedHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** What filename is this server tunnel's private keys stored in */
|
||||
public void setPrivKeyFile(String file) {
|
||||
_privKeyFile = (file != null ? file.trim() : null);
|
||||
}
|
||||
/**
|
||||
* If called with any value (and the form submitted with action=Remove),
|
||||
* we really do want to stop and remove the tunnel.
|
||||
*/
|
||||
public void setRemoveConfirm(String moo) {
|
||||
_removeConfirmed = true;
|
||||
}
|
||||
/**
|
||||
* If called with any value, we want this tunnel to start whenever it is
|
||||
* loaded (aka right now and whenever the router is started up)
|
||||
*/
|
||||
public void setStartOnLoad(String moo) {
|
||||
_startOnLoad = true;
|
||||
}
|
||||
public void setShared(String moo) {
|
||||
_sharedClient=true;
|
||||
}
|
||||
public void setShared(boolean val) {
|
||||
_sharedClient=val;
|
||||
}
|
||||
public void setConnectDelay(String moo) {
|
||||
_connectDelay = true;
|
||||
}
|
||||
public void setProfile(String profile) {
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on all provided data, create a set of configuration parameters
|
||||
* suitable for use in a TunnelController. This will replace (not add to)
|
||||
* any existing parameters, so this should return a comprehensive mapping.
|
||||
*
|
||||
*/
|
||||
private Properties getConfig() {
|
||||
Properties config = new Properties();
|
||||
updateConfigGeneric(config);
|
||||
|
||||
if ("httpclient".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_proxyList != null)
|
||||
config.setProperty("proxyList", _proxyList);
|
||||
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
if (_name != null && !_sharedClient) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
|
||||
config.setProperty("sharedClient", _sharedClient + "");
|
||||
} else if ("client".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
if (_reachableByOther != null)
|
||||
config.setProperty("interface", _reachableByOther);
|
||||
else
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_targetDestination != null)
|
||||
config.setProperty("targetDestination", _targetDestination);
|
||||
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
if (_name != null && !_sharedClient) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
config.setProperty("sharedClient", _sharedClient + "");
|
||||
} else if ("server".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
} else if ("httpserver".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
if (_spoofedHost != null)
|
||||
config.setProperty("spoofedHost", _spoofedHost);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private void updateConfigGeneric(Properties config) {
|
||||
config.setProperty("type", _type);
|
||||
if (_name != null)
|
||||
config.setProperty("name", _name);
|
||||
if (_description != null)
|
||||
config.setProperty("description", _description);
|
||||
if (_i2cpHost != null)
|
||||
config.setProperty("i2cpHost", _i2cpHost);
|
||||
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) )
|
||||
config.setProperty("i2cpPort", _i2cpPort);
|
||||
else
|
||||
config.setProperty("i2cpPort", "7654");
|
||||
|
||||
if (_customOptions != null) {
|
||||
StringTokenizer tok = new StringTokenizer(_customOptions);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
||||
continue;
|
||||
String key = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
if ("inbound.length".equals(key)) continue;
|
||||
if ("outbound.length".equals(key)) continue;
|
||||
if ("inbound.quantity".equals(key)) continue;
|
||||
if ("outbound.quantity".equals(key)) continue;
|
||||
if ("inbound.nickname".equals(key)) continue;
|
||||
if ("outbound.nickname".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
|
||||
config.setProperty("option." + key, val);
|
||||
}
|
||||
}
|
||||
|
||||
config.setProperty("startOnLoad", _startOnLoad + "");
|
||||
|
||||
if (_tunnelCount != null) {
|
||||
config.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
config.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
}
|
||||
if (_tunnelDepth != null) {
|
||||
config.setProperty("option.inbound.length", _tunnelDepth);
|
||||
config.setProperty("option.outbound.length", _tunnelDepth);
|
||||
}
|
||||
if (_connectDelay)
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "1000");
|
||||
else
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
if (_name != null) {
|
||||
if ( ((!"client".equals(_type)) && (!"httpclient".equals(_type))) || (!_sharedClient) ) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
} else {
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
}
|
||||
}
|
||||
if ("interactive".equals(_profile))
|
||||
config.setProperty("option.i2p.streaming.maxWindowSize", "1");
|
||||
else
|
||||
config.remove("option.i2p.streaming.maxWindowSize");
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
|
||||
protected TunnelController getController(int tunnel) {
|
||||
if (tunnel < 0) return null;
|
||||
if (_group == null) return null;
|
||||
List controllers = _group.getControllers();
|
||||
if (controllers.size() > tunnel)
|
||||
return (TunnelController)controllers.get(tunnel);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getMessages(List msgs) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
getMessages(msgs, buf);
|
||||
return buf.toString();
|
||||
}
|
||||
private void getMessages(List msgs, StringBuffer buf) {
|
||||
if (msgs == null) return;
|
||||
for (int i = 0; i < msgs.size(); i++) {
|
||||
buf.append((String)msgs.get(i)).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,26 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean" %>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<title>I2PTunnel edit</title>
|
||||
</head><body>
|
||||
|
||||
<a href="index.jsp">Back</a>
|
||||
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.WebEditPageHelper" id="helper" scope="request" />
|
||||
<jsp:setProperty name="helper" property="*" />
|
||||
<b><jsp:getProperty name="helper" property="actionResults" /></b>
|
||||
|
||||
<jsp:getProperty name="helper" property="editForm" />
|
||||
</body>
|
||||
</html>
|
||||
<% String tun = request.getParameter("tunnel");
|
||||
if (tun != null) {
|
||||
try {
|
||||
int curTunnel = Integer.parseInt(tun);
|
||||
if (EditBean.staticIsClient(curTunnel)) {
|
||||
%><jsp:include page="editClient.jsp" /><%
|
||||
} else {
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
%>Invalid tunnel parameter<%
|
||||
}
|
||||
} else {
|
||||
String type = request.getParameter("type");
|
||||
int curTunnel = -1;
|
||||
if ("client".equals(type) || "httpclient".equals(type)) {
|
||||
%><jsp:include page="editClient.jsp" /><%
|
||||
} else if ("server".equals(type) || "httpserver".equals(type)) {
|
||||
%><jsp:include page="editServer.jsp" /><%
|
||||
} else {
|
||||
%>Invalid tunnel type<%
|
||||
}
|
||||
}
|
||||
%>
|
||||
293
apps/i2ptunnel/jsp/editClient.jsp
Normal file
@@ -0,0 +1,293 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
|
||||
<% String tun = request.getParameter("tunnel");
|
||||
int curTunnel = -1;
|
||||
if (tun != null) {
|
||||
try {
|
||||
curTunnel = Integer.parseInt(tun);
|
||||
} catch (NumberFormatException nfe) {
|
||||
curTunnel = -1;
|
||||
}
|
||||
}
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="index.jsp">
|
||||
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
|
||||
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
|
||||
<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<% if (curTunnel >= 0) { %>
|
||||
<b>Edit proxy settings</b>
|
||||
<% } else { %>
|
||||
<b>New proxy settings</b>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Type: </b>
|
||||
<td><%
|
||||
if (curTunnel >= 0) {
|
||||
%><%=editBean.getTunnelType(curTunnel)%>
|
||||
<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
|
||||
<%
|
||||
} else {
|
||||
%><%=editBean.getTypeName(request.getParameter("type"))%>
|
||||
<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
|
||||
<%
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Description: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Start automatically?:</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.startAutomatically(curTunnel)) { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" checked="true" />
|
||||
<% } else { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" />
|
||||
<% } %>
|
||||
<i>(Check the Box for 'YES')</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <b>Listening Port:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="6" maxlength="5" name="port" value="<%=editBean.getClientPort(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b> Accessable by:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="reachableBy">
|
||||
<% String clientInterface = editBean.getClientInterface(curTunnel); %>
|
||||
<% if (("127.0.0.1".equals(clientInterface)) || (clientInterface == null) || (clientInterface.trim().length() <= 0)) { %>
|
||||
<option value="127.0.0.1" selected="true">Locally (127.0.0.1)</option>
|
||||
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
|
||||
<option value="other">LAN Hosts (Please specify your LAN address)</option>
|
||||
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachableByOther" size="20" />
|
||||
<% } else if ("0.0.0.0".equals(clientInterface)) { %>
|
||||
<option value="127.0.0.1">Locally (127.0.0.1)</option>
|
||||
<option value="0.0.0.0" selected="true">Everyone (0.0.0.0)</option>
|
||||
<option value="other">LAN Hosts (Please specify your LAN address)</option>
|
||||
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachableByOther" size="20" />
|
||||
<% } else { %>
|
||||
<option value="127.0.0.1">Locally (127.0.0.1)</option>
|
||||
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
|
||||
<option value="other" selected="true">LAN Hosts (Please specify your LAN address)</option>
|
||||
|
||||
</select>
|
||||
|
||||
<b>others:</b>
|
||||
<input type="text" name="reachableByOther" size="20" value="<%=clientInterface%>" />
|
||||
<% } %>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
|
||||
<td><b>Outproxies:</b>
|
||||
<% } else { %>
|
||||
<td><b>Target:</b>
|
||||
<% } %>
|
||||
</td>
|
||||
<td>
|
||||
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
|
||||
<input type="text" name="proxyList" value="<%=editBean.getClientDestination(curTunnel)%>" />
|
||||
<% } else { %>
|
||||
<input type="text" name="targetDestination" value="<%=editBean.getClientDestination(curTunnel)%>" />
|
||||
<% } %>
|
||||
<i>(name or destination)</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Delayed connect?</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.shouldDelay(curTunnel)) { %>
|
||||
<input type="checkbox" value="1000" name="connectDelay" checked="true" />
|
||||
<% } else { %>
|
||||
<input type="checkbox" value="1000" name="connectDelay" />
|
||||
<% } %>
|
||||
<i>(for request/response connections)</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Profile:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="profile">
|
||||
<% if (editBean.isInteractive(curTunnel)) { %>
|
||||
<option value="interactive" selected="true">interactive connection </option>
|
||||
<option value="bulk">bulk connection (downloads/websites/BT) </option>
|
||||
<% } else { %>
|
||||
<option value="interactive">interactive connection </option>
|
||||
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Shared Client</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.isSharedClient(curTunnel)) { %>
|
||||
<input type="checkbox" value="true" name="shared" checked="true" />
|
||||
<% } else { %>
|
||||
<input type="checkbox" value="true" name="shared" />
|
||||
<% } %>
|
||||
<i>(Share tunnels with other clients and httpclients? Change requires restart of client proxy)</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<b><hr size="1">
|
||||
Advanced networking options<br />
|
||||
<span style="color:#dd0000;">(NOTE: when this client proxy is configured to share tunnels, then these options are for all the shared proxy clients!)</span></b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Tunnel depth:</b>
|
||||
</td>
|
||||
<td><select name="tunnelDepth">
|
||||
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
|
||||
switch (tunnelDepth) {
|
||||
case 0: %>
|
||||
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 1: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Tunnel count:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="tunnelCount">
|
||||
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
|
||||
switch (tunnelCount) {
|
||||
case 1: %>
|
||||
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 3: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
<tr>
|
||||
<td><b>I2CP host:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>I2CP port:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Custom options:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<hr size="1">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Save:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Save changes" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Delete?</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Delete this proxy" />
|
||||
<b><span style="color:#dd0000;">confirm delete:</span></b>
|
||||
<input type="checkbox" value="true" name="removeConfirm" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
233
apps/i2ptunnel/jsp/editServer.jsp
Normal file
@@ -0,0 +1,233 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
|
||||
<% String tun = request.getParameter("tunnel");
|
||||
int curTunnel = -1;
|
||||
if (tun != null) {
|
||||
try {
|
||||
curTunnel = Integer.parseInt(tun);
|
||||
} catch (NumberFormatException nfe) {
|
||||
curTunnel = -1;
|
||||
}
|
||||
}
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="index.jsp">
|
||||
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
|
||||
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
|
||||
<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<% if (curTunnel >= 0) { %>
|
||||
<b>Edit server settings</b>
|
||||
<% } else { %>
|
||||
<b>New server settings</b>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Type: </b>
|
||||
<td><%
|
||||
if (curTunnel >= 0) {
|
||||
%><%=editBean.getTunnelType(curTunnel)%>
|
||||
<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
|
||||
<%
|
||||
} else {
|
||||
%><%=editBean.getTypeName(request.getParameter("type"))%>
|
||||
<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
|
||||
<%
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Description: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Start automatically?:</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.startAutomatically(curTunnel)) { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" checked="true" />
|
||||
<% } else { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" />
|
||||
<% } %>
|
||||
<i>(Check the Box for 'YES')</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <b>Target:</b>
|
||||
</td>
|
||||
<td>
|
||||
Host: <input type="text" size="20" name="targetHost" value="<%=editBean.getTargetHost(curTunnel)%>" />
|
||||
Port: <input type="text" size="6" maxlength="5" name="targetPort" value="<%=editBean.getTargetPort(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<% String curType = editBean.getInternalType(curTunnel);
|
||||
if ( (curType == null) || (curType.trim().length() <= 0) )
|
||||
curType = request.getParameter("type");
|
||||
if ("httpserver".equals(curType)) { %>
|
||||
<tr>
|
||||
<td><b>Website name:</b></td>
|
||||
<td><input type="text" size="20" name="spoofedHost" value="<%=editBean.getSpoofedHost(curTunnel)%>" />
|
||||
</td></tr>
|
||||
<% } %>
|
||||
<tr>
|
||||
<td><b>Private key file:</b>
|
||||
</td>
|
||||
<td><input type="text" size="30" name="privKeyFile" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Profile:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="profile">
|
||||
<% if (editBean.isInteractive(curTunnel)) { %>
|
||||
<option value="interactive" selected="true">interactive connection </option>
|
||||
<option value="bulk">bulk connection (downloads/websites/BT) </option>
|
||||
<% } else { %>
|
||||
<option value="interactive">interactive connection </option>
|
||||
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top" align="left"><b>Local destination:</b><br /><i>(if known)</i></td>
|
||||
<td valign="top" align="left"><input type="text" size="60" value="<%=editBean.getDestinationBase64(curTunnel)%>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<b><hr size="1">
|
||||
Advanced networking options<br />
|
||||
</b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Tunnel depth:</b>
|
||||
</td>
|
||||
<td><select name="tunnelDepth">
|
||||
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
|
||||
switch (tunnelDepth) {
|
||||
case 0: %>
|
||||
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 1: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Tunnel count:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="tunnelCount">
|
||||
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
|
||||
switch (tunnelCount) {
|
||||
case 1: %>
|
||||
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 3: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
<tr>
|
||||
<td><b>I2CP host:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>I2CP port:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Custom options:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<hr size="1">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Save:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Save changes" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Delete?</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Delete this proxy" />
|
||||
<b><span style="color:#dd0000;">confirm delete:</span></b>
|
||||
<input type="checkbox" value="true" name="removeConfirm" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,31 +1,184 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean" %>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.IndexBean" id="indexBean" scope="request" />
|
||||
<jsp:setProperty name="indexBean" property="*" />
|
||||
|
||||
<html><head>
|
||||
<title>I2PTunnel status</title>
|
||||
</head><body>
|
||||
<html>
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
</head>
|
||||
<body style="font-family: Verdana, Tahoma, Helvetica, sans-serif;font-size:12px;">
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td nowrap="true"><b>New Messages: </b><br />
|
||||
<a href="index.jsp">refresh</a>
|
||||
</td>
|
||||
<td>
|
||||
<textarea rows="3" cols="60" readonly="true"><jsp:getProperty name="indexBean" property="messages" /></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.WebStatusPageHelper" id="helper" scope="request" />
|
||||
<jsp:setProperty name="helper" property="*" />
|
||||
<b><jsp:getProperty name="helper" property="actionResults" /></b>
|
||||
|
||||
<jsp:getProperty name="helper" property="summaryList" />
|
||||
<hr />
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="7" align="center" valign="middle" style="font-size:14px;">
|
||||
<b>Your Client Tunnels:</b><br />
|
||||
<hr size="1" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="15%"><b>Name:</b></td>
|
||||
<td><b>Port:</b></td>
|
||||
<td><b>Type:</b></td>
|
||||
<td><b>Interface:</b></td>
|
||||
<td><b>Status:</b></td>
|
||||
</tr>
|
||||
<% for (int curClient = 0; curClient < indexBean.getTunnelCount(); curClient++) {
|
||||
if (!indexBean.isClient(curClient)) continue; %>
|
||||
<tr>
|
||||
<td valign="top" align="left">
|
||||
<b><a href="edit.jsp?tunnel=<%=curClient%>"><%=indexBean.getTunnelName(curClient) %></a></b></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientPort(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getTunnelType(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientInterface(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%
|
||||
switch (indexBean.getTunnelStatus(curClient)) {
|
||||
case IndexBean.STARTING:
|
||||
%><b><span style="color:#339933">Starting...</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
|
||||
break;
|
||||
case IndexBean.RUNNING:
|
||||
%><b><span style="color:#00dd00">Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
|
||||
break;
|
||||
case IndexBean.NOT_RUNNING:
|
||||
%><b><span style="color:#dd0000">Not Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curClient%>">[START]</a><%
|
||||
break;
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr><td align="right" valign="top">Destination:</td>
|
||||
<td colspan="4"><input align="left" size="40" valign="top" style="overflow: hidden" readonly="true" value="<%=indexBean.getClientDestination(curClient) %>" /></td></tr>
|
||||
<tr>
|
||||
<td valign="top" align="right">Description:</td>
|
||||
<td valign="top" align="left" colspan="4"><%=indexBean.getTunnelDescription(curClient) %></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="5" align="center" valign="middle" style="font-size:14px;">
|
||||
<b>Your Server Tunnels:</b><br />
|
||||
<hr size="1" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="15%"><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Points at:</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Status:</b>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<% for (int curServer = 0; curServer < indexBean.getTunnelCount(); curServer++) {
|
||||
if (indexBean.isClient(curServer)) continue; %>
|
||||
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<b><a href="edit.jsp?tunnel=<%=curServer%>"><%=indexBean.getTunnelName(curServer)%></a></b>
|
||||
</td>
|
||||
<td valign="top"><%=indexBean.getServerTarget(curServer)%></td>
|
||||
<td valign="top" nowrap="true"><%
|
||||
switch (indexBean.getTunnelStatus(curServer)) {
|
||||
case IndexBean.RUNNING:
|
||||
%><b><span style="color:#00dd00">Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
|
||||
if ("httpserver".equals(indexBean.getInternalType(curServer))) {
|
||||
%> (<a href="http://<%=(new java.util.Random()).nextLong()%>.i2p/?i2paddresshelper=<%=indexBean.getDestinationBase64(curServer)%>">preview</a>)<%
|
||||
}
|
||||
break;
|
||||
case IndexBean.NOT_RUNNING:
|
||||
%><b><span style="color:#dd0000">Not Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curServer%>">[START]</a><%
|
||||
break;
|
||||
case IndexBean.STARTING:
|
||||
%>
|
||||
<b><span style="color:#339933">Starting...</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
|
||||
break;
|
||||
}
|
||||
%>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td valign="top" align="right">Description:</td>
|
||||
<td valign="top" align="left" colspan="2"><%=indexBean.getTunnelDescription(curServer)%></td></tr>
|
||||
<% } %>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center" valign="middle">
|
||||
<b>Operations Menu - Please chose from below!</b><br /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<form action="index.jsp" method="GET">
|
||||
<input type="submit" name="action" value="Stop all" />
|
||||
<input type="submit" name="action" value="Start all" />
|
||||
<input type="submit" name="action" value="Restart all" />
|
||||
<input type="submit" name="action" value="Reload config" />
|
||||
<td >
|
||||
<input type="hidden" name="nonce" value="<%=indexBean.getNextNonce()%>" />
|
||||
<input type="submit" name="action" value="Stop all tunnels" />
|
||||
<input type="submit" name="action" value="Start all tunnels" />
|
||||
<input type="submit" name="action" value="Restart all" />
|
||||
<input type="submit" name="action" value="Reload config" />
|
||||
</td>
|
||||
</form>
|
||||
|
||||
<form action="edit.jsp">
|
||||
<td >
|
||||
<b>Add new:</b>
|
||||
<select name="type">
|
||||
<select name="type">
|
||||
<option value="httpclient">HTTP proxy</option>
|
||||
<option value="client">Client tunnel</option>
|
||||
<option value="server">Server tunnel</option>
|
||||
</select> <input type="submit" value="GO" />
|
||||
<option value="httpserver">HTTP server tunnel</option>
|
||||
</select> <input type="submit" value="Create" />
|
||||
</td>
|
||||
</form>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
|
||||
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
|
||||
@@ -14,4 +14,4 @@
|
||||
<welcome-file>index.html</welcome-file>
|
||||
<welcome-file>index.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
</web-app>
|
||||
</web-app>
|
||||
|
||||
@@ -3,18 +3,38 @@
|
||||
|
||||
<target name="all" depends="build" />
|
||||
<target name="fetchJettylib" >
|
||||
<available property="jetty.available" file="jettylib" />
|
||||
<available property="jetty.available" file="jetty-5.1.2.zip" />
|
||||
<ant target="doFetchJettylib" />
|
||||
</target>
|
||||
<target name="doFetchJettylib" unless="jetty.available" >
|
||||
<echo message="The libraries contained within the fetched file are from Jetty's 4.2.21 " />
|
||||
<echo message="distribution (http://jetty.mortbay.org/) which we have copied to our website since" />
|
||||
<echo message="theirs doesn't have direct HTTP access to the libs. These are not " />
|
||||
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.2" />
|
||||
<echo message="distribution (http://jetty.mortbay.org/). These are not " />
|
||||
<echo message="necessary for using I2P, but are used by some applications on top of I2P," />
|
||||
<echo message="such as the routerconsole." />
|
||||
<get src="http://dev.i2p.net/jettylib.tar.bz2" verbose="true" dest="jettylib.tar.bz2" />
|
||||
<untar src="jettylib.tar.bz2" compression="bzip2" dest="." />
|
||||
<delete file="jettylib.tar.bz2" />
|
||||
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.2.zip" verbose="true" dest="jetty-5.1.2.zip" />
|
||||
<ant target="doExtract" />
|
||||
</target>
|
||||
<target name="doExtract">
|
||||
<unzip src="jetty-5.1.2.zip" dest="." />
|
||||
<mkdir dir="jettylib" />
|
||||
<copy todir="jettylib">
|
||||
<fileset dir="jetty-5.1.2/lib">
|
||||
<include name="*.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="jettylib">
|
||||
<fileset dir="jetty-5.1.2/ext">
|
||||
<include name="ant.jar" />
|
||||
<include name="commons-el.jar" />
|
||||
<include name="commons-logging.jar" />
|
||||
<include name="jasper-compiler.jar" />
|
||||
<include name="jasper-runtime.jar" />
|
||||
<include name="javax.servlet.jar" />
|
||||
<include name="org.mortbay.jetty.jar" />
|
||||
<include name="xercesImpl.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<delete dir="jetty-5.1.2" />
|
||||
</target>
|
||||
<target name="build" depends="fetchJettylib" />
|
||||
<target name="builddep" />
|
||||
|
||||
@@ -5,7 +5,7 @@ package net.i2p.client.streaming;
|
||||
* so care should be taken when using in a multithreaded environment.
|
||||
*
|
||||
*/
|
||||
public class ByteCollector {
|
||||
class ByteCollector {
|
||||
byte[] contents;
|
||||
int size;
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ public interface I2PSocket {
|
||||
*/
|
||||
public void close() throws IOException;
|
||||
|
||||
public boolean isClosed();
|
||||
|
||||
public void setSocketErrorListener(SocketErrorListener lsnr);
|
||||
/**
|
||||
* Allow notification of underlying errors communicating across I2P without
|
||||
|
||||
@@ -24,7 +24,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
public static final int MAX_PACKET_SIZE = 1024 * 32;
|
||||
public static final int PACKET_DELAY = 100;
|
||||
|
||||
private I2PSocketManager manager;
|
||||
private I2PSocketManagerImpl manager;
|
||||
private Destination local;
|
||||
private Destination remote;
|
||||
private String localID;
|
||||
@@ -32,7 +32,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
private Object remoteIDWaiter = new Object();
|
||||
private I2PInputStream in;
|
||||
private I2POutputStream out;
|
||||
private SocketErrorListener _socketErrorListener;
|
||||
private I2PSocket.SocketErrorListener _socketErrorListener;
|
||||
private boolean outgoing;
|
||||
private long _socketId;
|
||||
private static long __socketId = 0;
|
||||
@@ -69,7 +69,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
* @param outgoing did we initiate the connection (true) or did we receive it (false)?
|
||||
* @param localID what is our half of the socket ID?
|
||||
*/
|
||||
public I2PSocketImpl(Destination peer, I2PSocketManager mgr, boolean outgoing, String localID) {
|
||||
public I2PSocketImpl(Destination peer, I2PSocketManagerImpl mgr, boolean outgoing, String localID) {
|
||||
this.outgoing = outgoing;
|
||||
manager = mgr;
|
||||
remote = peer;
|
||||
@@ -157,7 +157,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: RemoteID set to "
|
||||
+ I2PSocketManager.getReadableForm(remoteID) + " for "
|
||||
+ I2PSocketManagerImpl.getReadableForm(remoteID) + " for "
|
||||
+ this.hashCode());
|
||||
}
|
||||
return remoteID;
|
||||
@@ -233,8 +233,10 @@ class I2PSocketImpl implements I2PSocket {
|
||||
in.notifyClosed();
|
||||
}
|
||||
|
||||
public boolean isClosed() { return _closedOn > 0; }
|
||||
|
||||
/**
|
||||
* Close the socket from the I2P side, e. g. by a close packet.
|
||||
* Close the socket from the I2P side (by a close packet)
|
||||
*/
|
||||
protected void internalClose() {
|
||||
synchronized (flagLock) {
|
||||
@@ -249,9 +251,9 @@ class I2PSocketImpl implements I2PSocket {
|
||||
|
||||
private byte getMask(int add) {
|
||||
if (outgoing)
|
||||
return (byte)(I2PSocketManager.DATA_IN + (byte)add);
|
||||
return (byte)(I2PSocketManagerImpl.DATA_IN + (byte)add);
|
||||
else
|
||||
return (byte)(I2PSocketManager.DATA_OUT + (byte)add);
|
||||
return (byte)(I2PSocketManagerImpl.DATA_OUT + (byte)add);
|
||||
}
|
||||
|
||||
public void setOptions(I2PSocketOptions options) {
|
||||
@@ -284,7 +286,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
in.setReadTimeout(ms);
|
||||
}
|
||||
|
||||
public void setSocketErrorListener(SocketErrorListener lsnr) {
|
||||
public void setSocketErrorListener(I2PSocket.SocketErrorListener lsnr) {
|
||||
_socketErrorListener = lsnr;
|
||||
}
|
||||
|
||||
@@ -614,7 +616,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
_log.info(getPrefix() + ":" + Thread.currentThread().getName()
|
||||
+ "Sending close packet: (we started? " + outgoing
|
||||
+ ") after reading " + _bytesRead + " and writing " + _bytesWritten);
|
||||
byte[] packet = I2PSocketManager.makePacket(getMask(0x02), remoteID, new byte[0]);
|
||||
byte[] packet = I2PSocketManagerImpl.makePacket(getMask(0x02), remoteID, new byte[0]);
|
||||
boolean sent = manager.getSession().sendMessage(remote, packet);
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -645,7 +647,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
_log.error(getPrefix() + "NULL REMOTEID");
|
||||
return false;
|
||||
}
|
||||
byte[] packet = I2PSocketManager.makePacket(getMask(0x00), remoteID, data);
|
||||
byte[] packet = I2PSocketManagerImpl.makePacket(getMask(0x00), remoteID, data);
|
||||
boolean sent;
|
||||
synchronized (flagLock) {
|
||||
if (closed2) return false;
|
||||
|
||||
@@ -4,26 +4,16 @@
|
||||
*/
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
@@ -34,62 +24,8 @@ import net.i2p.util.Log;
|
||||
* or receive any messages with its .receiveMessage
|
||||
*
|
||||
*/
|
||||
public class I2PSocketManager implements I2PSessionListener {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private I2PServerSocketImpl _serverSocket = null;
|
||||
private Object lock = new Object(); // for locking socket lists
|
||||
private HashMap _outSockets;
|
||||
private HashMap _inSockets;
|
||||
private I2PSocketOptions _defaultOptions;
|
||||
private long _acceptTimeout;
|
||||
private String _name;
|
||||
private static int __managerId = 0;
|
||||
|
||||
public static final short ACK = 0x51;
|
||||
public static final short CLOSE_OUT = 0x52;
|
||||
public static final short DATA_OUT = 0x50;
|
||||
public static final short SYN = 0xA1;
|
||||
public static final short CLOSE_IN = 0xA2;
|
||||
public static final short DATA_IN = 0xA0;
|
||||
public static final short CHAFF = 0xFF;
|
||||
|
||||
/**
|
||||
* How long to wait for the client app to accept() before sending back CLOSE?
|
||||
* This includes the time waiting in the queue. Currently set to 5 seconds.
|
||||
*/
|
||||
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
|
||||
|
||||
public I2PSocketManager() {
|
||||
this("SocketManager " + (++__managerId));
|
||||
}
|
||||
public I2PSocketManager(String name) {
|
||||
_name = name;
|
||||
_session = null;
|
||||
_inSockets = new HashMap(16);
|
||||
_outSockets = new HashMap(16);
|
||||
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(I2PSocketManager.class);
|
||||
_context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.sent", "How many bytes are sent in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.received", "How many bytes are received in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.transferBalance", "How many streams send more than they receive (positive means more sent, negative means more received)?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.synNoAck", "How many times have we sent a SYN but not received an ACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.ackSendFailed", "How many times have we tried to send an ACK to a SYN and failed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackSent", "How many times have we refused a SYN with a NACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackReceived", "How many times have we received a NACK to our SYN?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
public I2PSession getSession() {
|
||||
return _session;
|
||||
}
|
||||
|
||||
public void setSession(I2PSession session) {
|
||||
_session = session;
|
||||
if (session != null) session.setSessionListener(this);
|
||||
}
|
||||
public interface I2PSocketManager {
|
||||
public I2PSession getSession();
|
||||
|
||||
/**
|
||||
* How long should we wait for the client to .accept() a socket before
|
||||
@@ -97,340 +33,14 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*
|
||||
* @param ms milliseconds to wait, maximum
|
||||
*/
|
||||
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
|
||||
public long getAcceptTimeout() { return _acceptTimeout; }
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
_log.info(getName() + ": Disconnected from the session");
|
||||
destroySocketManager();
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
_log.error(getName() + ": Error occurred: [" + message + "]", error);
|
||||
}
|
||||
public void setAcceptTimeout(long ms);
|
||||
public long getAcceptTimeout();
|
||||
public void setDefaultOptions(I2PSocketOptions options);
|
||||
public I2PSocketOptions getDefaultOptions();
|
||||
public I2PServerSocket getServerSocket();
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
I2PSocketImpl s;
|
||||
byte msg[] = session.receiveMessage(msgId);
|
||||
if (msg.length == 1 && msg[0] == -1) {
|
||||
_log.debug(getName() + ": Ping received");
|
||||
return;
|
||||
}
|
||||
if (msg.length < 4) {
|
||||
_log.error(getName() + ": ==== packet too short ====");
|
||||
return;
|
||||
}
|
||||
int type = msg[0] & 0xff;
|
||||
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
|
||||
byte[] payload = new byte[msg.length - 4];
|
||||
System.arraycopy(msg, 4, payload, 0, payload.length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Message read: type = [" + Integer.toHexString(type)
|
||||
+ "] id = [" + getReadableForm(id)
|
||||
+ "] payload length: [" + payload.length + "]");
|
||||
switch (type) {
|
||||
case ACK:
|
||||
ackAvailable(id, payload);
|
||||
return;
|
||||
case CLOSE_OUT:
|
||||
disconnectAvailable(id, payload);
|
||||
return;
|
||||
case DATA_OUT:
|
||||
sendOutgoingAvailable(id, payload);
|
||||
return;
|
||||
case SYN:
|
||||
synIncomingAvailable(id, payload, session);
|
||||
return;
|
||||
case CLOSE_IN:
|
||||
disconnectIncoming(id, payload);
|
||||
return;
|
||||
case DATA_IN:
|
||||
sendIncoming(id, payload);
|
||||
case CHAFF:
|
||||
// ignore
|
||||
return;
|
||||
default:
|
||||
handleUnknown(type, id, payload);
|
||||
return;
|
||||
}
|
||||
} catch (I2PException ise) {
|
||||
_log.error(getName() + ": Error processing", ise);
|
||||
} catch (IllegalStateException ise) {
|
||||
_log.debug(getName() + ": Error processing", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received an ACK packet (hopefully, in response to a SYN that we
|
||||
* recently sent out). Notify the associated I2PSocket that we now have
|
||||
* the remote stream ID (which should get things going, since the handshake
|
||||
* is complete).
|
||||
*
|
||||
*/
|
||||
private void ackAvailable(String id, byte payload[]) {
|
||||
long begin = _context.clock().now();
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
if (s == null) {
|
||||
_log.warn(getName() + ": No socket responsible for ACK packet");
|
||||
return;
|
||||
}
|
||||
|
||||
long socketRetrieved = _context.clock().now();
|
||||
|
||||
String remoteId = null;
|
||||
remoteId = s.getRemoteID(false);
|
||||
|
||||
if ( (payload.length == 3) && (remoteId == null) ) {
|
||||
String newID = toString(payload);
|
||||
long beforeSetRemId = _context.clock().now();
|
||||
s.setRemoteID(newID);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": ackAvailable - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, getRemoteId took "
|
||||
+ (beforeSetRemId-socketRetrieved) + "ms, setRemoteId took "
|
||||
+ (_context.clock().now()-beforeSetRemId) + "ms");
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// (payload.length != 3 || getRemoteId != null)
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (payload.length != 3)
|
||||
_log.warn(getName() + ": Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn(getName() + ": Remote ID already exists? " + remoteId);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": invalid ack - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, overall took "
|
||||
+ (_context.clock().now()-begin) + "ms");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a disconnect packet, telling us to tear down the specified
|
||||
* stream.
|
||||
*/
|
||||
private void disconnectAvailable(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect outgoing for socket " + s);
|
||||
try {
|
||||
if (s != null) {
|
||||
if (payload.length > 0) {
|
||||
_log.debug(getName() + ": Disconnect packet had "
|
||||
+ payload.length + " bytes");
|
||||
}
|
||||
if (s.getRemoteID(false) == null) {
|
||||
s.setRemoteID(null); // Just to wake up socket
|
||||
return;
|
||||
}
|
||||
s.internalClose();
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (Exception t) {
|
||||
_log.error(getName() + ": Ignoring error on disconnect for socket " + s, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we created - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
// packet send outgoing
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send outgoing [" + payload.length + "] for socket " + s);
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a SYN packet (a request for a new stream). If the client has
|
||||
* said they want incoming sockets (by retrieving the serverSocket), the stream
|
||||
* will be ACKed, but if they have not, they'll be NACKed)
|
||||
*
|
||||
* @throws DataFormatException if the destination in the SYN was invalid
|
||||
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
|
||||
*/
|
||||
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
|
||||
throws DataFormatException, I2PSessionException {
|
||||
Destination d = new Destination();
|
||||
d.fromByteArray(payload);
|
||||
|
||||
I2PSocketImpl s = null;
|
||||
boolean acceptConnections = (_serverSocket != null);
|
||||
String newLocalID = null;
|
||||
synchronized (lock) {
|
||||
newLocalID = makeID(_inSockets);
|
||||
if (acceptConnections) {
|
||||
s = new I2PSocketImpl(d, this, false, newLocalID);
|
||||
s.setRemoteID(id);
|
||||
}
|
||||
}
|
||||
_log.debug(getName() + ": *Syn! for socket " + s);
|
||||
|
||||
if (!acceptConnections) {
|
||||
// The app did not instantiate an I2PServerSocket
|
||||
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.error(getName() + ": Error sending close to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
}
|
||||
_context.statManager().addRateData("streaming.nackSent", 1, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
if (!replySentOk) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Error sending reply to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message for socket " + s,
|
||||
new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.ackSendFailed", 1, 1);
|
||||
}
|
||||
} else {
|
||||
// timed out or serverSocket closed
|
||||
byte[] packet = toBytes(" " + id);
|
||||
packet[0] = CLOSE_OUT;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.warn(getName() + ": Error sending NACK for session creation for socket " + s);
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming,nackSent", 1, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a disconnect for a socket we didn't initiate, so kill
|
||||
* the socket.
|
||||
*
|
||||
*/
|
||||
private void disconnectIncoming(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length == 0 && s != null) {
|
||||
_inSockets.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect incoming for socket " + s);
|
||||
|
||||
try {
|
||||
if (payload.length == 0 && s != null) {
|
||||
s.internalClose();
|
||||
return;
|
||||
} else {
|
||||
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.error(getName() + ": Disconnect packet had " + payload.length + " bytes");
|
||||
if (s != null)
|
||||
s.internalClose();
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.error(getName() + ": Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we received - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendIncoming(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send incoming [" + payload.length + "] for socket " + s);
|
||||
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.info(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown packet. moo.
|
||||
*
|
||||
*/
|
||||
private void handleUnknown(int type, String id, byte payload[]) {
|
||||
_log.error(getName() + ": \n\n=============== Unknown packet! " + "============"
|
||||
+ "\nType: " + (int) type
|
||||
+ "\nID: " + getReadableForm(id)
|
||||
+ "\nBase64'ed Data: " + Base64.encode(payload)
|
||||
+ "\n\n\n");
|
||||
if (id != null) {
|
||||
synchronized (lock) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
_log.error(getName() + ": Abuse reported [" + severity + "]");
|
||||
}
|
||||
|
||||
public void setDefaultOptions(I2PSocketOptions options) {
|
||||
_defaultOptions = options;
|
||||
}
|
||||
|
||||
public I2PSocketOptions getDefaultOptions() {
|
||||
return _defaultOptions;
|
||||
}
|
||||
|
||||
public I2PServerSocket getServerSocket() {
|
||||
if (_serverSocket == null) {
|
||||
_serverSocket = new I2PServerSocketImpl(this);
|
||||
}
|
||||
return _serverSocket;
|
||||
}
|
||||
public I2PSocketOptions buildOptions();
|
||||
public I2PSocketOptions buildOptions(Properties opts);
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
@@ -445,86 +55,7 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*/
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options)
|
||||
throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
String localID, lcID;
|
||||
I2PSocketImpl s;
|
||||
synchronized (lock) {
|
||||
localID = makeID(_outSockets);
|
||||
lcID = getReadableForm(localID);
|
||||
s = new I2PSocketImpl(peer, this, true, localID);
|
||||
_outSockets.put(localID, s);
|
||||
}
|
||||
try {
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
||||
_session.getMyDestination().writeBytes(pubkey);
|
||||
String remoteID;
|
||||
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
|
||||
boolean sent = false;
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
if (!sent) {
|
||||
_log.info(getName() + ": Unable to send & receive ack for SYN packet for socket " + s);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new I2PException("Error sending through I2P network");
|
||||
}
|
||||
if (options != null)
|
||||
remoteID = s.getRemoteID(true, options.getConnectTimeout());
|
||||
else
|
||||
remoteID = s.getRemoteID(true, getDefaultOptions().getConnectTimeout());
|
||||
|
||||
if (remoteID == null) {
|
||||
_context.statManager().addRateData("streaming.nackReceived", 1, 1);
|
||||
throw new ConnectException("Connection refused by peer for socket " + s);
|
||||
}
|
||||
if ("".equals(remoteID)) {
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new NoRouteToHostException("Unable to reach peer for socket " + s);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": TIMING: s given out for remoteID "
|
||||
+ getReadableForm(remoteID) + " for socket " + s);
|
||||
|
||||
return s;
|
||||
} catch (InterruptedIOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(getName() + ": Timeout waiting for ack from syn for id "
|
||||
+ getReadableForm(lcID) + " for socket " + s, ioe);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new InterruptedIOException("Timeout waiting for ack");
|
||||
} catch (ConnectException ex) {
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (NoRouteToHostException ex) {
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(getName() + ": Error sending syn on id " + getReadableForm(lcID) + " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw new I2PException("Unhandled IOException occurred");
|
||||
} catch (I2PException ex) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getName() + ": Error sending syn on id " + getReadableForm(lcID) + " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
s.internalClose();
|
||||
_log.error(getName() + ": Unhandled error connecting", e);
|
||||
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
NoRouteToHostException, InterruptedIOException;
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
@@ -537,179 +68,37 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
return connect(peer, null);
|
||||
}
|
||||
|
||||
NoRouteToHostException, InterruptedIOException;
|
||||
|
||||
/**
|
||||
* Destroy the socket manager, freeing all the associated resources. This
|
||||
* method will block untill all the managed sockets are closed.
|
||||
*
|
||||
*/
|
||||
public void destroySocketManager() {
|
||||
if (_serverSocket != null) {
|
||||
_serverSocket.close();
|
||||
_serverSocket = null;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
Iterator iter;
|
||||
String id = null;
|
||||
I2PSocketImpl sock;
|
||||
|
||||
iter = _inSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_inSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing inSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
|
||||
iter = _outSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_outSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing outSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": Waiting for all open sockets to really close...");
|
||||
synchronized (lock) {
|
||||
while ((_inSockets.size() != 0) || (_outSockets.size() != 0)) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
_log.debug(getName() + ": Destroying I2P session...");
|
||||
_session.destroySession();
|
||||
_log.debug(getName() + ": I2P session destroyed");
|
||||
} catch (I2PSessionException e) {
|
||||
_log.error(getName() + ": Error destroying I2P session", e);
|
||||
}
|
||||
}
|
||||
public void destroySocketManager();
|
||||
|
||||
/**
|
||||
* Retrieve a set of currently connected I2PSockets, either initiated locally or remotely.
|
||||
*
|
||||
*/
|
||||
public Set listSockets() {
|
||||
Set sockets = new HashSet(8);
|
||||
synchronized (lock) {
|
||||
sockets.addAll(_inSockets.values());
|
||||
sockets.addAll(_outSockets.values());
|
||||
}
|
||||
return sockets;
|
||||
}
|
||||
public Set listSockets();
|
||||
|
||||
/**
|
||||
* Ping the specified peer, returning true if they replied to the ping within
|
||||
* the timeout specified, false otherwise. This call blocks.
|
||||
*
|
||||
*/
|
||||
public boolean ping(Destination peer, long timeoutMs) {
|
||||
try {
|
||||
return _session.sendMessage(peer, new byte[] { (byte) CHAFF});
|
||||
} catch (I2PException ex) {
|
||||
_log.error(getName() + ": I2PException:", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public boolean ping(Destination peer, long timeoutMs);
|
||||
|
||||
public void removeSocket(I2PSocketImpl sock) {
|
||||
synchronized (lock) {
|
||||
_inSockets.remove(sock.getLocalID());
|
||||
_outSockets.remove(sock.getLocalID());
|
||||
lock.notify();
|
||||
}
|
||||
public String getName();
|
||||
public void setName(String name);
|
||||
|
||||
long now = _context.clock().now();
|
||||
long lifetime = now - sock.getCreatedOn();
|
||||
long timeSinceClose = now - sock.getClosedOn();
|
||||
long sent = sock.getBytesSent();
|
||||
long recv = sock.getBytesReceived();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": Removing socket \"" + getReadableForm(sock.getLocalID()) + "\" [" + sock
|
||||
+ ", send: " + sent + ", recv: " + recv
|
||||
+ ", lifetime: " + lifetime + "ms, time since close: " + timeSinceClose + ")]",
|
||||
new Exception("removeSocket called"));
|
||||
}
|
||||
|
||||
_context.statManager().addRateData("streaming.lifetime", lifetime, lifetime);
|
||||
_context.statManager().addRateData("streaming.sent", sent, lifetime);
|
||||
_context.statManager().addRateData("streaming.received", recv, lifetime);
|
||||
|
||||
if (sent > recv) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", 1, lifetime);
|
||||
} else if (recv > sent) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", -1, lifetime);
|
||||
} else {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() { return _name; }
|
||||
public void setName(String name) { _name = name; }
|
||||
public void init(I2PAppContext context, I2PSession session, Properties opts, String name);
|
||||
|
||||
public static String getReadableForm(String id) {
|
||||
if (id == null) return "(null)";
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(toBytes(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new part the connection ID that is locally unique
|
||||
*
|
||||
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
|
||||
*/
|
||||
private static String makeID(HashMap uniqueIn) {
|
||||
String newID;
|
||||
do {
|
||||
int id = (int) (Math.random() * 16777215 + 1);
|
||||
byte[] nid = new byte[3];
|
||||
nid[0] = (byte) (id / 65536);
|
||||
nid[1] = (byte) ((id / 256) % 256);
|
||||
nid[2] = (byte) (id % 256);
|
||||
newID = toString(nid);
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new packet of the given type for the specified connection containing
|
||||
* the given payload
|
||||
*/
|
||||
public static byte[] makePacket(byte type, String id, byte[] payload) {
|
||||
byte[] packet = new byte[payload.length + 4];
|
||||
packet[0] = type;
|
||||
byte[] temp = toBytes(id);
|
||||
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
|
||||
System.arraycopy(temp, 0, packet, 1, 3);
|
||||
System.arraycopy(payload, 0, packet, 4, payload.length);
|
||||
return packet;
|
||||
}
|
||||
public void addDisconnectListener(DisconnectListener lsnr);
|
||||
public void removeDisconnectListener(DisconnectListener lsnr);
|
||||
|
||||
private static final String toString(byte data[]) {
|
||||
try {
|
||||
return new String(data, "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte[] toBytes(String str) {
|
||||
try {
|
||||
return str.getBytes("ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
public static interface DisconnectListener {
|
||||
public void sessionDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
@@ -23,6 +25,10 @@ import net.i2p.util.Log;
|
||||
public class I2PSocketManagerFactory {
|
||||
private final static Log _log = new Log(I2PSocketManagerFactory.class);
|
||||
|
||||
public static final String PROP_MANAGER = "i2p.streaming.manager";
|
||||
//public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerImpl";
|
||||
public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerFull";
|
||||
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the local machine on the default port (7654).
|
||||
@@ -30,18 +36,27 @@ public class I2PSocketManagerFactory {
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager() {
|
||||
String i2cpHost = System.getProperty(I2PClient.PROP_TCP_HOST, "localhost");
|
||||
int i2cpPort = 7654;
|
||||
String i2cpPortStr = System.getProperty(I2PClient.PROP_TCP_PORT);
|
||||
if (i2cpPortStr != null) {
|
||||
try {
|
||||
i2cpPort = Integer.parseInt(i2cpPortStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
return createManager(getHost(), getPort(), System.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the local machine on the default port (7654).
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(Properties opts) {
|
||||
return createManager(getHost(), getPort(), opts);
|
||||
}
|
||||
|
||||
return createManager(i2cpHost, i2cpPort, System.getProperties());
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the specified host and port
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(String host, int port) {
|
||||
return createManager(host, port, System.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +81,26 @@ public class I2PSocketManagerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using the destination loaded from the given private key
|
||||
* stream and connected to the default I2CP host and port.
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(InputStream myPrivateKeyStream) {
|
||||
return createManager(myPrivateKeyStream, getHost(), getPort(), System.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using the destination loaded from the given private key
|
||||
* stream and connected to the default I2CP host and port.
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(InputStream myPrivateKeyStream, Properties opts) {
|
||||
return createManager(myPrivateKeyStream, getHost(), getPort(), opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using the destination loaded from the given private key
|
||||
* stream and connected to the I2CP router on the specified machine on the given
|
||||
@@ -76,23 +111,92 @@ public class I2PSocketManagerFactory {
|
||||
public static I2PSocketManager createManager(InputStream myPrivateKeyStream, String i2cpHost, int i2cpPort,
|
||||
Properties opts) {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
|
||||
if (opts == null)
|
||||
opts = new Properties();
|
||||
for (Iterator iter = System.getProperties().keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
if (!opts.containsKey(name))
|
||||
opts.setProperty(name, System.getProperty(name));
|
||||
}
|
||||
boolean oldLib = DEFAULT_MANAGER.equals(opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER));
|
||||
if (oldLib && false) {
|
||||
// for the old streaming lib
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
//opts.setProperty("tunnels.depthInbound", "0");
|
||||
} else {
|
||||
// for new streaming lib:
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT);
|
||||
//p.setProperty("tunnels.depthInbound", "0");
|
||||
}
|
||||
|
||||
if (i2cpHost != null)
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
|
||||
if (i2cpPort > 0)
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
|
||||
|
||||
try {
|
||||
I2PSession session = client.createSession(myPrivateKeyStream, opts);
|
||||
session.connect();
|
||||
return createManager(session);
|
||||
I2PSocketManager sockMgr = createManager(session, opts, "manager");
|
||||
if (sockMgr != null)
|
||||
sockMgr.setDefaultOptions(sockMgr.buildOptions(opts));
|
||||
return sockMgr;
|
||||
} catch (I2PSessionException ise) {
|
||||
_log.error("Error creating session for socket manager", ise);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static I2PSocketManager createManager(I2PSession session) {
|
||||
I2PSocketManager mgr = new I2PSocketManager();
|
||||
mgr.setSession(session);
|
||||
mgr.setDefaultOptions(new I2PSocketOptions());
|
||||
return mgr;
|
||||
private static I2PSocketManager createManager(I2PSession session, Properties opts, String name) {
|
||||
if (false) {
|
||||
I2PSocketManagerImpl mgr = new I2PSocketManagerImpl();
|
||||
mgr.setSession(session);
|
||||
//mgr.setDefaultOptions(new I2PSocketOptions());
|
||||
return mgr;
|
||||
} else {
|
||||
String classname = opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER);
|
||||
if (classname != null) {
|
||||
try {
|
||||
Class cls = Class.forName(classname);
|
||||
Object obj = cls.newInstance();
|
||||
if (obj instanceof I2PSocketManager) {
|
||||
I2PSocketManager mgr = (I2PSocketManager)obj;
|
||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
||||
mgr.init(context, session, opts, name);
|
||||
return mgr;
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "]");
|
||||
}
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
_log.error("Error loading " + classname, cnfe);
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "] - not found");
|
||||
} catch (InstantiationException ie) {
|
||||
_log.error("Error loading " + classname, ie);
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "] - unable to instantiate");
|
||||
} catch (IllegalAccessException iae) {
|
||||
_log.error("Error loading " + classname, iae);
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "] - illegal access");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("No manager class specified");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getHost() {
|
||||
return System.getProperty(I2PClient.PROP_TCP_HOST, "localhost");
|
||||
}
|
||||
private static int getPort() {
|
||||
int i2cpPort = 7654;
|
||||
String i2cpPortStr = System.getProperty(I2PClient.PROP_TCP_PORT);
|
||||
if (i2cpPortStr != null) {
|
||||
try {
|
||||
i2cpPort = Integer.parseInt(i2cpPortStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
return i2cpPort;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,787 @@
|
||||
/*
|
||||
* licensed under BSD license...
|
||||
* (if you know the proper clause for that, add it ...)
|
||||
*/
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Centralize the coordination and multiplexing of the local client's streaming.
|
||||
* There should be one I2PSocketManager for each I2PSession, and if an application
|
||||
* is sending and receiving data through the streaming library using an
|
||||
* I2PSocketManager, it should not attempt to call I2PSession's setSessionListener
|
||||
* or receive any messages with its .receiveMessage
|
||||
*
|
||||
*/
|
||||
class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private I2PServerSocketImpl _serverSocket = null;
|
||||
private Object lock = new Object(); // for locking socket lists
|
||||
private HashMap _outSockets;
|
||||
private HashMap _inSockets;
|
||||
private I2PSocketOptions _defaultOptions;
|
||||
private long _acceptTimeout;
|
||||
private String _name;
|
||||
private List _listeners;
|
||||
private static int __managerId = 0;
|
||||
|
||||
public static final short ACK = 0x51;
|
||||
public static final short CLOSE_OUT = 0x52;
|
||||
public static final short DATA_OUT = 0x50;
|
||||
public static final short SYN = 0xA1;
|
||||
public static final short CLOSE_IN = 0xA2;
|
||||
public static final short DATA_IN = 0xA0;
|
||||
public static final short CHAFF = 0xFF;
|
||||
|
||||
/**
|
||||
* How long to wait for the client app to accept() before sending back CLOSE?
|
||||
* This includes the time waiting in the queue. Currently set to 5 seconds.
|
||||
*/
|
||||
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
|
||||
|
||||
public I2PSocketManagerImpl() {
|
||||
this("SocketManager " + (++__managerId));
|
||||
}
|
||||
public I2PSocketManagerImpl(String name) {
|
||||
init(I2PAppContext.getGlobalContext(), null, null, name);
|
||||
}
|
||||
|
||||
public void init(I2PAppContext context, I2PSession session, Properties opts, String name) {
|
||||
_name = name;
|
||||
_context = context;
|
||||
_log = _context.logManager().getLog(I2PSocketManager.class);
|
||||
_inSockets = new HashMap(16);
|
||||
_outSockets = new HashMap(16);
|
||||
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
|
||||
_listeners = new ArrayList(1);
|
||||
setSession(session);
|
||||
setDefaultOptions(buildOptions(opts));
|
||||
_context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.sent", "How many bytes are sent in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.received", "How many bytes are received in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.transferBalance", "How many streams send more than they receive (positive means more sent, negative means more received)?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.synNoAck", "How many times have we sent a SYN but not received an ACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.ackSendFailed", "How many times have we tried to send an ACK to a SYN and failed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackSent", "How many times have we refused a SYN with a NACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackReceived", "How many times have we received a NACK to our SYN?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
public I2PSession getSession() {
|
||||
return _session;
|
||||
}
|
||||
|
||||
public void setSession(I2PSession session) {
|
||||
_session = session;
|
||||
if (session != null) session.setSessionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* How long should we wait for the client to .accept() a socket before
|
||||
* sending back a NACK/Close?
|
||||
*
|
||||
* @param ms milliseconds to wait, maximum
|
||||
*/
|
||||
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
|
||||
public long getAcceptTimeout() { return _acceptTimeout; }
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
_log.info(getName() + ": Disconnected from the session");
|
||||
destroySocketManager();
|
||||
List listeners = null;
|
||||
synchronized (_listeners) {
|
||||
listeners = new ArrayList(_listeners);
|
||||
_listeners.clear();
|
||||
}
|
||||
for (int i = 0; i < listeners.size(); i++) {
|
||||
I2PSocketManager.DisconnectListener lsnr = (I2PSocketManager.DisconnectListener)listeners.get(i);
|
||||
lsnr.sessionDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
_log.error(getName() + ": Error occurred: [" + message + "]", error);
|
||||
}
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
I2PSocketImpl s;
|
||||
byte msg[] = session.receiveMessage(msgId);
|
||||
if (msg.length == 1 && msg[0] == -1) {
|
||||
_log.debug(getName() + ": Ping received");
|
||||
return;
|
||||
}
|
||||
if (msg.length < 4) {
|
||||
_log.warn(getName() + ": ==== packet too short ====");
|
||||
return;
|
||||
}
|
||||
int type = msg[0] & 0xff;
|
||||
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
|
||||
byte[] payload = new byte[msg.length - 4];
|
||||
System.arraycopy(msg, 4, payload, 0, payload.length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Message read: type = [" + Integer.toHexString(type)
|
||||
+ "] id = [" + getReadableForm(id)
|
||||
+ "] payload length: [" + payload.length + "]");
|
||||
switch (type) {
|
||||
case ACK:
|
||||
ackAvailable(id, payload);
|
||||
return;
|
||||
case CLOSE_OUT:
|
||||
disconnectAvailable(id, payload);
|
||||
return;
|
||||
case DATA_OUT:
|
||||
sendOutgoingAvailable(id, payload);
|
||||
return;
|
||||
case SYN:
|
||||
synIncomingAvailable(id, payload, session);
|
||||
return;
|
||||
case CLOSE_IN:
|
||||
disconnectIncoming(id, payload);
|
||||
return;
|
||||
case DATA_IN:
|
||||
sendIncoming(id, payload);
|
||||
case CHAFF:
|
||||
// ignore
|
||||
return;
|
||||
default:
|
||||
handleUnknown(type, id, payload);
|
||||
return;
|
||||
}
|
||||
} catch (I2PException ise) {
|
||||
_log.warn(getName() + ": Error processing", ise);
|
||||
} catch (IllegalStateException ise) {
|
||||
_log.debug(getName() + ": Error processing", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received an ACK packet (hopefully, in response to a SYN that we
|
||||
* recently sent out). Notify the associated I2PSocket that we now have
|
||||
* the remote stream ID (which should get things going, since the handshake
|
||||
* is complete).
|
||||
*
|
||||
*/
|
||||
private void ackAvailable(String id, byte payload[]) {
|
||||
long begin = _context.clock().now();
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
if (s == null) {
|
||||
_log.warn(getName() + ": No socket responsible for ACK packet for id " + getReadableForm(id));
|
||||
return;
|
||||
}
|
||||
|
||||
long socketRetrieved = _context.clock().now();
|
||||
|
||||
String remoteId = null;
|
||||
remoteId = s.getRemoteID(false);
|
||||
|
||||
if ( (payload.length == 3) && (remoteId == null) ) {
|
||||
String newID = toString(payload);
|
||||
long beforeSetRemId = _context.clock().now();
|
||||
s.setRemoteID(newID);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": ackAvailable - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, getRemoteId took "
|
||||
+ (beforeSetRemId-socketRetrieved) + "ms, setRemoteId took "
|
||||
+ (_context.clock().now()-beforeSetRemId) + "ms");
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// (payload.length != 3 || getRemoteId != null)
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (payload.length != 3)
|
||||
_log.warn(getName() + ": Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn(getName() + ": Remote ID already exists? " + remoteId);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": invalid ack - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, overall took "
|
||||
+ (_context.clock().now()-begin) + "ms");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a disconnect packet, telling us to tear down the specified
|
||||
* stream.
|
||||
*/
|
||||
private void disconnectAvailable(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect outgoing for socket " + s + " on id "
|
||||
+ getReadableForm(id));
|
||||
try {
|
||||
if (s != null) {
|
||||
if (payload.length > 0) {
|
||||
_log.debug(getName() + ": Disconnect packet had "
|
||||
+ payload.length + " bytes");
|
||||
}
|
||||
if (s.getRemoteID(false) == null) {
|
||||
s.setRemoteID(null); // Just to wake up socket
|
||||
return;
|
||||
}
|
||||
s.internalClose();
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (Exception t) {
|
||||
_log.warn(getName() + ": Ignoring error on disconnect for socket " + s, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we created - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
// packet send outgoing
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send outgoing [" + payload.length + "] for socket "
|
||||
+ s + " on id " + getReadableForm(id));
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a SYN packet (a request for a new stream). If the client has
|
||||
* said they want incoming sockets (by retrieving the serverSocket), the stream
|
||||
* will be ACKed, but if they have not, they'll be NACKed)
|
||||
*
|
||||
* @throws DataFormatException if the destination in the SYN was invalid
|
||||
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
|
||||
*/
|
||||
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
|
||||
throws DataFormatException, I2PSessionException {
|
||||
Destination d = new Destination();
|
||||
d.fromByteArray(payload);
|
||||
|
||||
I2PSocketImpl s = null;
|
||||
boolean acceptConnections = (_serverSocket != null);
|
||||
String newLocalID = null;
|
||||
synchronized (lock) {
|
||||
newLocalID = makeID(_inSockets);
|
||||
if (acceptConnections) {
|
||||
s = new I2PSocketImpl(d, this, false, newLocalID);
|
||||
s.setRemoteID(id);
|
||||
}
|
||||
}
|
||||
_log.debug(getName() + ": *Syn! for socket " + s + " on id " + getReadableForm(newLocalID)
|
||||
+ " from " + d.calculateHash().toBase64().substring(0,6));
|
||||
|
||||
if (!acceptConnections) {
|
||||
// The app did not instantiate an I2PServerSocket
|
||||
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.warn(getName() + ": Error sending close to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
}
|
||||
_context.statManager().addRateData("streaming.nackSent", 1, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
if (!replySentOk) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Error sending reply to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message for socket " + s,
|
||||
new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.ackSendFailed", 1, 1);
|
||||
}
|
||||
} else {
|
||||
// timed out or serverSocket closed
|
||||
byte[] packet = toBytes(" " + id);
|
||||
packet[0] = CLOSE_OUT;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.warn(getName() + ": Error sending NACK for session creation for socket " + s);
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming,nackSent", 1, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a disconnect for a socket we didn't initiate, so kill
|
||||
* the socket.
|
||||
*
|
||||
*/
|
||||
private void disconnectIncoming(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length == 0 && s != null) {
|
||||
_inSockets.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect incoming for socket " + s);
|
||||
|
||||
try {
|
||||
if (payload.length == 0 && s != null) {
|
||||
s.internalClose();
|
||||
return;
|
||||
} else {
|
||||
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.warn(getName() + ": Disconnect packet had " + payload.length + " bytes");
|
||||
if (s != null)
|
||||
s.internalClose();
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.warn(getName() + ": Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we received - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendIncoming(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send incoming [" + payload.length + "] for socket " + s);
|
||||
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.info(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown packet. moo.
|
||||
*
|
||||
*/
|
||||
private void handleUnknown(int type, String id, byte payload[]) {
|
||||
_log.error(getName() + ": \n\n=============== Unknown packet! " + "============"
|
||||
+ "\nType: " + (int) type
|
||||
+ "\nID: " + getReadableForm(id)
|
||||
+ "\nBase64'ed Data: " + Base64.encode(payload)
|
||||
+ "\n\n\n");
|
||||
if (id != null) {
|
||||
synchronized (lock) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
_log.error(getName() + ": Abuse reported [" + severity + "]");
|
||||
}
|
||||
|
||||
public void setDefaultOptions(I2PSocketOptions options) {
|
||||
_defaultOptions = options;
|
||||
}
|
||||
|
||||
public I2PSocketOptions getDefaultOptions() {
|
||||
return _defaultOptions;
|
||||
}
|
||||
|
||||
public I2PSocketOptions buildOptions() { return buildOptions(null); }
|
||||
public I2PSocketOptions buildOptions(Properties opts) {
|
||||
return new I2PSocketOptionsImpl(opts);
|
||||
}
|
||||
|
||||
public I2PServerSocket getServerSocket() {
|
||||
if (_serverSocket == null) {
|
||||
_serverSocket = new I2PServerSocketImpl(this);
|
||||
}
|
||||
return _serverSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
*
|
||||
* @param peer Destination to connect to
|
||||
* @param options I2P socket options to be used for connecting
|
||||
*
|
||||
* @throws ConnectException if the peer refuses the connection
|
||||
* @throws NoRouteToHostException if the peer is not found or not reachable
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options)
|
||||
throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
String localID, lcID;
|
||||
I2PSocketImpl s;
|
||||
synchronized (lock) {
|
||||
localID = makeID(_outSockets);
|
||||
lcID = getReadableForm(localID);
|
||||
s = new I2PSocketImpl(peer, this, true, localID);
|
||||
_outSockets.put(localID, s);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": connect(" + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ ", ...): localID = " + lcID);
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
||||
_session.getMyDestination().writeBytes(pubkey);
|
||||
String remoteID;
|
||||
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
|
||||
boolean sent = false;
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
if (!sent) {
|
||||
_log.info(getName() + ": Unable to send & receive ack for SYN packet for socket "
|
||||
+ s + " with localID = " + lcID);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new I2PException("Error sending through I2P network");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": syn sent ok to "
|
||||
+ peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " with localID = " + lcID);
|
||||
}
|
||||
if (options != null)
|
||||
remoteID = s.getRemoteID(true, options.getConnectTimeout());
|
||||
else
|
||||
remoteID = s.getRemoteID(true, getDefaultOptions().getConnectTimeout());
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": remoteID received from "
|
||||
+ peer.calculateHash().toBase64().substring(0,6)
|
||||
+ ": " + getReadableForm(remoteID)
|
||||
+ " with localID = " + lcID);
|
||||
|
||||
if (remoteID == null) {
|
||||
_context.statManager().addRateData("streaming.nackReceived", 1, 1);
|
||||
throw new ConnectException("Connection refused by peer for socket " + s);
|
||||
}
|
||||
if ("".equals(remoteID)) {
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new NoRouteToHostException("Unable to reach peer for socket " + s);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": TIMING: s given out for remoteID "
|
||||
+ getReadableForm(remoteID) + " for socket " + s);
|
||||
|
||||
return s;
|
||||
} catch (InterruptedIOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Timeout waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ioe);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new InterruptedIOException("Timeout waiting for ack");
|
||||
} catch (ConnectException ex) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Connection error waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (NoRouteToHostException ex) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": No route to host waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Error sending syn on id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw new I2PException("Unhandled IOException occurred");
|
||||
} catch (I2PException ex) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getName() + ": Error sending syn on id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
s.internalClose();
|
||||
_log.warn(getName() + ": Unhandled error connecting on "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, e);
|
||||
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
*
|
||||
* @param peer Destination to connect to
|
||||
*
|
||||
* @throws ConnectException if the peer refuses the connection
|
||||
* @throws NoRouteToHostException if the peer is not found or not reachable
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
return connect(peer, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the socket manager, freeing all the associated resources. This
|
||||
* method will block untill all the managed sockets are closed.
|
||||
*
|
||||
*/
|
||||
public void destroySocketManager() {
|
||||
if (_serverSocket != null) {
|
||||
_serverSocket.close();
|
||||
_serverSocket = null;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
Iterator iter;
|
||||
String id = null;
|
||||
I2PSocketImpl sock;
|
||||
|
||||
iter = _inSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_inSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing inSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
|
||||
iter = _outSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_outSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing outSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": Waiting for all open sockets to really close...");
|
||||
synchronized (lock) {
|
||||
while ((_inSockets.size() != 0) || (_outSockets.size() != 0)) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
_log.debug(getName() + ": Destroying I2P session...");
|
||||
_session.destroySession();
|
||||
_log.debug(getName() + ": I2P session destroyed");
|
||||
} catch (I2PSessionException e) {
|
||||
_log.warn(getName() + ": Error destroying I2P session", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of currently connected I2PSockets, either initiated locally or remotely.
|
||||
*
|
||||
*/
|
||||
public Set listSockets() {
|
||||
Set sockets = new HashSet(8);
|
||||
synchronized (lock) {
|
||||
sockets.addAll(_inSockets.values());
|
||||
sockets.addAll(_outSockets.values());
|
||||
}
|
||||
return sockets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping the specified peer, returning true if they replied to the ping within
|
||||
* the timeout specified, false otherwise. This call blocks.
|
||||
*
|
||||
*/
|
||||
public boolean ping(Destination peer, long timeoutMs) {
|
||||
try {
|
||||
return _session.sendMessage(peer, new byte[] { (byte) CHAFF});
|
||||
} catch (I2PException ex) {
|
||||
_log.warn(getName() + ": I2PException:", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSocket(I2PSocketImpl sock) {
|
||||
String localId = sock.getLocalID();
|
||||
boolean removed = false;
|
||||
synchronized (lock) {
|
||||
removed = (null != _inSockets.remove(localId));
|
||||
removed = removed || (null != _outSockets.remove(localId));
|
||||
lock.notify();
|
||||
}
|
||||
|
||||
long now = _context.clock().now();
|
||||
long lifetime = now - sock.getCreatedOn();
|
||||
long timeSinceClose = now - sock.getClosedOn();
|
||||
long sent = sock.getBytesSent();
|
||||
long recv = sock.getBytesReceived();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": Removing socket \"" + getReadableForm(localId) + "\" [" + sock
|
||||
+ ", send: " + sent + ", recv: " + recv
|
||||
+ ", lifetime: " + lifetime + "ms, time since close: " + timeSinceClose
|
||||
+ " removed? " + removed + ")]",
|
||||
new Exception("removeSocket called"));
|
||||
}
|
||||
|
||||
_context.statManager().addRateData("streaming.lifetime", lifetime, lifetime);
|
||||
_context.statManager().addRateData("streaming.sent", sent, lifetime);
|
||||
_context.statManager().addRateData("streaming.received", recv, lifetime);
|
||||
|
||||
if (sent > recv) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", 1, lifetime);
|
||||
} else if (recv > sent) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", -1, lifetime);
|
||||
} else {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() { return _name; }
|
||||
public void setName(String name) { _name = name; }
|
||||
|
||||
public void addDisconnectListener(I2PSocketManager.DisconnectListener lsnr) {
|
||||
synchronized (_listeners) {
|
||||
_listeners.add(lsnr);
|
||||
}
|
||||
}
|
||||
public void removeDisconnectListener(I2PSocketManager.DisconnectListener lsnr) {
|
||||
synchronized (_listeners) {
|
||||
_listeners.remove(lsnr);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getReadableForm(String id) {
|
||||
if (id == null) return "(null)";
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(toBytes(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new part the connection ID that is locally unique
|
||||
*
|
||||
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
|
||||
*/
|
||||
private static String makeID(HashMap uniqueIn) {
|
||||
String newID;
|
||||
do {
|
||||
int id = (int) (Math.random() * 16777215 + 1);
|
||||
byte[] nid = new byte[3];
|
||||
nid[0] = (byte) (id / 65536);
|
||||
nid[1] = (byte) ((id / 256) % 256);
|
||||
nid[2] = (byte) (id % 256);
|
||||
newID = toString(nid);
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new packet of the given type for the specified connection containing
|
||||
* the given payload
|
||||
*/
|
||||
public static byte[] makePacket(byte type, String id, byte[] payload) {
|
||||
byte[] packet = new byte[payload.length + 4];
|
||||
packet[0] = type;
|
||||
byte[] temp = toBytes(id);
|
||||
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
|
||||
System.arraycopy(temp, 0, packet, 1, 3);
|
||||
System.arraycopy(payload, 0, packet, 4, payload.length);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static final String toString(byte data[]) {
|
||||
try {
|
||||
return new String(data, "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte[] toBytes(String str) {
|
||||
try {
|
||||
return str.getBytes("ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,56 +4,38 @@ package net.i2p.client.streaming;
|
||||
* Define the configuration for streaming and verifying data on the socket.
|
||||
*
|
||||
*/
|
||||
public class I2PSocketOptions {
|
||||
private long _connectTimeout;
|
||||
private long _readTimeout;
|
||||
private long _writeTimeout;
|
||||
private int _maxBufferSize;
|
||||
|
||||
public static final int DEFAULT_BUFFER_SIZE = 1024*64;
|
||||
public static final int DEFAULT_WRITE_TIMEOUT = 60*1000;
|
||||
public interface I2PSocketOptions {
|
||||
public static final String PROP_BUFFER_SIZE = "i2p.streaming.bufferSize";
|
||||
public static final String PROP_CONNECT_TIMEOUT = "i2p.streaming.connectTimeout";
|
||||
public static final String PROP_READ_TIMEOUT = "i2p.streaming.readTimeout";
|
||||
public static final String PROP_WRITE_TIMEOUT = "i2p.streaming.writeTimeout";
|
||||
|
||||
public I2PSocketOptions() {
|
||||
_connectTimeout = -1;
|
||||
_readTimeout = -1;
|
||||
_writeTimeout = DEFAULT_WRITE_TIMEOUT;
|
||||
_maxBufferSize = DEFAULT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* How long we will wait for the ACK from a SYN, in milliseconds.
|
||||
*
|
||||
* @return milliseconds to wait, or -1 if we will wait indefinitely
|
||||
*/
|
||||
public long getConnectTimeout() {
|
||||
return _connectTimeout;
|
||||
}
|
||||
public long getConnectTimeout();
|
||||
|
||||
/**
|
||||
* Define how long we will wait for the ACK from a SYN, in milliseconds.
|
||||
*
|
||||
*/
|
||||
public void setConnectTimeout(long ms) {
|
||||
_connectTimeout = ms;
|
||||
}
|
||||
public void setConnectTimeout(long ms);
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the input stream while waiting
|
||||
* for more data. If this value is exceeded, the read() throws
|
||||
* InterruptedIOException
|
||||
*/
|
||||
public long getReadTimeout() {
|
||||
return _readTimeout;
|
||||
}
|
||||
public long getReadTimeout();
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the input stream while waiting
|
||||
* for more data. If this value is exceeded, the read() throws
|
||||
* InterruptedIOException
|
||||
*/
|
||||
public void setReadTimeout(long ms) {
|
||||
_readTimeout = ms;
|
||||
}
|
||||
public void setReadTimeout(long ms);
|
||||
|
||||
/**
|
||||
* How much data will we accept that hasn't been written out yet. After
|
||||
@@ -63,9 +45,7 @@ public class I2PSocketOptions {
|
||||
*
|
||||
* @return buffer size limit, in bytes
|
||||
*/
|
||||
public int getMaxBufferSize() {
|
||||
return _maxBufferSize;
|
||||
}
|
||||
public int getMaxBufferSize();
|
||||
|
||||
/**
|
||||
* How much data will we accept that hasn't been written out yet. After
|
||||
@@ -74,9 +54,7 @@ public class I2PSocketOptions {
|
||||
* less than or equal to zero, there is no limit (warning: can eat ram)
|
||||
*
|
||||
*/
|
||||
public void setMaxBufferSize(int numBytes) {
|
||||
_maxBufferSize = numBytes;
|
||||
}
|
||||
public void setMaxBufferSize(int numBytes);
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the output stream while waiting
|
||||
@@ -84,9 +62,7 @@ public class I2PSocketOptions {
|
||||
* InterruptedIOException. If this is less than or equal to zero, there
|
||||
* is no timeout.
|
||||
*/
|
||||
public long getWriteTimeout() {
|
||||
return _writeTimeout;
|
||||
}
|
||||
public long getWriteTimeout();
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the output stream while waiting
|
||||
@@ -94,7 +70,5 @@ public class I2PSocketOptions {
|
||||
* InterruptedIOException. If this is less than or equal to zero, there
|
||||
* is no timeout.
|
||||
*/
|
||||
public void setWriteTimeout(long ms) {
|
||||
_writeTimeout = ms;
|
||||
}
|
||||
public void setWriteTimeout(long ms);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Define the configuration for streaming and verifying data on the socket.
|
||||
*
|
||||
*/
|
||||
class I2PSocketOptionsImpl implements I2PSocketOptions {
|
||||
private long _connectTimeout;
|
||||
private long _readTimeout;
|
||||
private long _writeTimeout;
|
||||
private int _maxBufferSize;
|
||||
|
||||
public static final int DEFAULT_BUFFER_SIZE = 1024*64;
|
||||
public static final int DEFAULT_WRITE_TIMEOUT = 60*1000;
|
||||
public static final int DEFAULT_CONNECT_TIMEOUT = 60*1000;
|
||||
|
||||
public I2PSocketOptionsImpl() {
|
||||
this(System.getProperties());
|
||||
}
|
||||
|
||||
public I2PSocketOptionsImpl(I2PSocketOptions opts) {
|
||||
this(System.getProperties());
|
||||
if (opts != null) {
|
||||
_connectTimeout = opts.getConnectTimeout();
|
||||
_readTimeout = opts.getReadTimeout();
|
||||
_writeTimeout = opts.getWriteTimeout();
|
||||
_maxBufferSize = opts.getMaxBufferSize();
|
||||
}
|
||||
}
|
||||
|
||||
public I2PSocketOptionsImpl(Properties opts) {
|
||||
init(opts);
|
||||
}
|
||||
|
||||
public void setProperties(Properties opts) {
|
||||
if (opts == null) return;
|
||||
if (opts.containsKey(PROP_BUFFER_SIZE))
|
||||
_maxBufferSize = getInt(opts, PROP_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
|
||||
if (opts.containsKey(PROP_CONNECT_TIMEOUT))
|
||||
_connectTimeout = getInt(opts, PROP_CONNECT_TIMEOUT, DEFAULT_CONNECT_TIMEOUT);
|
||||
if (opts.containsKey(PROP_READ_TIMEOUT))
|
||||
_readTimeout = getInt(opts, PROP_READ_TIMEOUT, -1);
|
||||
if (opts.containsKey(PROP_WRITE_TIMEOUT))
|
||||
_writeTimeout = getInt(opts, PROP_WRITE_TIMEOUT, DEFAULT_WRITE_TIMEOUT);
|
||||
}
|
||||
|
||||
protected void init(Properties opts) {
|
||||
_maxBufferSize = getInt(opts, PROP_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
|
||||
_connectTimeout = getInt(opts, PROP_CONNECT_TIMEOUT, DEFAULT_CONNECT_TIMEOUT);
|
||||
_readTimeout = getInt(opts, PROP_READ_TIMEOUT, -1);
|
||||
_writeTimeout = getInt(opts, PROP_WRITE_TIMEOUT, DEFAULT_WRITE_TIMEOUT);
|
||||
}
|
||||
|
||||
protected int getInt(Properties opts, String name, int defaultVal) {
|
||||
if (opts == null) return defaultVal;
|
||||
String val = opts.getProperty(name);
|
||||
if (val == null) {
|
||||
return defaultVal;
|
||||
} else {
|
||||
try {
|
||||
return Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How long we will wait for the ACK from a SYN, in milliseconds.
|
||||
*
|
||||
* @return milliseconds to wait, or -1 if we will wait indefinitely
|
||||
*/
|
||||
public long getConnectTimeout() {
|
||||
return _connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define how long we will wait for the ACK from a SYN, in milliseconds.
|
||||
*
|
||||
*/
|
||||
public void setConnectTimeout(long ms) {
|
||||
_connectTimeout = ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the input stream while waiting
|
||||
* for more data. If this value is exceeded, the read() throws
|
||||
* InterruptedIOException
|
||||
*/
|
||||
public long getReadTimeout() {
|
||||
return _readTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the input stream while waiting
|
||||
* for more data. If this value is exceeded, the read() throws
|
||||
* InterruptedIOException
|
||||
*/
|
||||
public void setReadTimeout(long ms) {
|
||||
_readTimeout = ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much data will we accept that hasn't been written out yet. After
|
||||
* this amount has been exceeded, subsequent .write calls will block until
|
||||
* either some data is removed or the connection is closed. If this is
|
||||
* less than or equal to zero, there is no limit (warning: can eat ram)
|
||||
*
|
||||
* @return buffer size limit, in bytes
|
||||
*/
|
||||
public int getMaxBufferSize() {
|
||||
return _maxBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much data will we accept that hasn't been written out yet. After
|
||||
* this amount has been exceeded, subsequent .write calls will block until
|
||||
* either some data is removed or the connection is closed. If this is
|
||||
* less than or equal to zero, there is no limit (warning: can eat ram)
|
||||
*
|
||||
*/
|
||||
public void setMaxBufferSize(int numBytes) {
|
||||
_maxBufferSize = numBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the output stream while waiting
|
||||
* for the data to flush. If this value is exceeded, the write() throws
|
||||
* InterruptedIOException. If this is less than or equal to zero, there
|
||||
* is no timeout.
|
||||
*/
|
||||
public long getWriteTimeout() {
|
||||
return _writeTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the output stream while waiting
|
||||
* for the data to flush. If this value is exceeded, the write() throws
|
||||
* InterruptedIOException. If this is less than or equal to zero, there
|
||||
* is no timeout.
|
||||
*/
|
||||
public void setWriteTimeout(long ms) {
|
||||
_writeTimeout = ms;
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,14 @@ import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -26,6 +28,8 @@ public class StreamSinkClient {
|
||||
private int _sendSize;
|
||||
private int _writeDelay;
|
||||
private String _peerDestFile;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
|
||||
|
||||
/**
|
||||
@@ -35,6 +39,11 @@ public class StreamSinkClient {
|
||||
* @param serverDestFile file containing the StreamSinkServer's binary Destination
|
||||
*/
|
||||
public StreamSinkClient(int sendSize, int writeDelayMs, String serverDestFile) {
|
||||
this(null, -1, sendSize, writeDelayMs, serverDestFile);
|
||||
}
|
||||
public StreamSinkClient(String i2cpHost, int i2cpPort, int sendSize, int writeDelayMs, String serverDestFile) {
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
_sendSize = sendSize;
|
||||
_writeDelay = writeDelayMs;
|
||||
_peerDestFile = serverDestFile;
|
||||
@@ -46,7 +55,11 @@ public class StreamSinkClient {
|
||||
*
|
||||
*/
|
||||
public void runClient() {
|
||||
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
|
||||
I2PSocketManager mgr = null;
|
||||
if (_i2cpHost != null)
|
||||
mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties());
|
||||
else
|
||||
mgr = I2PSocketManagerFactory.createManager();
|
||||
Destination peer = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
@@ -62,74 +75,114 @@ public class StreamSinkClient {
|
||||
} finally {
|
||||
if (fis == null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
|
||||
System.out.println("Send " + _sendSize + "KB to " + peer.calculateHash().toBase64());
|
||||
|
||||
try {
|
||||
I2PSocket sock = mgr.connect(peer);
|
||||
byte buf[] = new byte[32*1024];
|
||||
Random rand = new Random();
|
||||
OutputStream out = sock.getOutputStream();
|
||||
long beforeSending = System.currentTimeMillis();
|
||||
for (int i = 0; i < _sendSize; i+= 32) {
|
||||
rand.nextBytes(buf);
|
||||
out.write(buf);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + _sendSize + "KB to " + peer.calculateHash().toBase64());
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
I2PSocket sock = mgr.connect(peer);
|
||||
byte buf[] = new byte[Math.min(32*1024, _sendSize*1024)];
|
||||
Random rand = new Random();
|
||||
OutputStream out = sock.getOutputStream();
|
||||
long beforeSending = System.currentTimeMillis();
|
||||
for (int i = 0; (_sendSize < 0) || (i < _sendSize); i+= buf.length/1024) {
|
||||
rand.nextBytes(buf);
|
||||
out.write(buf);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Wrote " + ((1+i*buf.length)/1024) + "/" + _sendSize + "KB");
|
||||
if (_writeDelay > 0) {
|
||||
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
sock.close();
|
||||
long afterSending = System.currentTimeMillis();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Wrote " + (i+32) + "/" + _sendSize + "KB");
|
||||
if (_writeDelay > 0) {
|
||||
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
long afterSending = System.currentTimeMillis();
|
||||
System.out.println("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms");
|
||||
sock.close();
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("Timeout connecting to the peer", iie);
|
||||
return;
|
||||
} catch (NoRouteToHostException nrthe) {
|
||||
_log.error("Unable to connect to the peer", nrthe);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
return;
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error connecting to the peer", ie);
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("IO error sending", ioe);
|
||||
return;
|
||||
_log.debug("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms");
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("Timeout connecting to the peer", iie);
|
||||
//return;
|
||||
} catch (NoRouteToHostException nrthe) {
|
||||
_log.error("Unable to connect to the peer", nrthe);
|
||||
//return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
//return;
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error connecting to the peer", ie);
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("IO error sending", ioe);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the client. <code>Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile</code> <br />
|
||||
* Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile [concurrentSends]</code> <br />
|
||||
* <ul>
|
||||
* <li><b>sendSizeKB</b>: how many KB to send</li>
|
||||
* <li><b>sendSizeKB</b>: how many KB to send, or -1 for unlimited</li>
|
||||
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
|
||||
* <li><b>serverDestFile</b>: file containing the StreamSinkServer's binary Destination</li>
|
||||
* <li><b>concurrentSends</b>: how many concurrent threads should send to the server at once</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 3) {
|
||||
System.out.println("Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile");
|
||||
} else {
|
||||
int sendSizeKB = -1;
|
||||
int writeDelayMs = -1;
|
||||
try {
|
||||
sendSizeKB = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Send size invalid [" + args[0] + "]");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
writeDelayMs = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Write delay ms invalid [" + args[1] + "]");
|
||||
return;
|
||||
}
|
||||
StreamSinkClient client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
|
||||
client.runClient();
|
||||
StreamSinkClient client = null;
|
||||
int sendSizeKB = -1;
|
||||
int writeDelayMs = -1;
|
||||
int concurrent = 1;
|
||||
|
||||
switch (args.length) {
|
||||
case 3: // fall through
|
||||
case 4:
|
||||
try {
|
||||
sendSizeKB = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Send size invalid [" + args[0] + "]");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
writeDelayMs = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Write delay ms invalid [" + args[1] + "]");
|
||||
return;
|
||||
}
|
||||
if (args.length == 4) {
|
||||
try { concurrent = Integer.parseInt(args[3]); } catch (NumberFormatException nfe) {}
|
||||
}
|
||||
client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
|
||||
break;
|
||||
case 5: // fall through
|
||||
case 6:
|
||||
try {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
sendSizeKB = Integer.parseInt(args[2]);
|
||||
writeDelayMs = Integer.parseInt(args[3]);
|
||||
client = new StreamSinkClient(args[0], port, sendSizeKB, writeDelayMs, args[4]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("arg error");
|
||||
}
|
||||
if (args.length == 6) {
|
||||
try { concurrent = Integer.parseInt(args[5]); } catch (NumberFormatException nfe) {}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile [concurrentSends]");
|
||||
}
|
||||
if (client != null) {
|
||||
for (int i = 0; i < concurrent; i++)
|
||||
new I2PThread(new Runner(client), "Client " + i).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Runner implements Runnable {
|
||||
private StreamSinkClient _client;
|
||||
public Runner(StreamSinkClient client) {
|
||||
_client = client;
|
||||
}
|
||||
public void run() {
|
||||
_client.runClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple streaming lib test app that connects to a given destination and sends
|
||||
* the contents of a file, then disconnects. See the {@link #main}
|
||||
*
|
||||
*/
|
||||
public class StreamSinkSend {
|
||||
private Log _log;
|
||||
private String _sendFile;
|
||||
private int _writeDelay;
|
||||
private String _peerDestFile;
|
||||
|
||||
/**
|
||||
* Build the client but don't fire it up.
|
||||
* @param filename file to send
|
||||
* @param writeDelayMs how long to wait between each .write (0 for no delay)
|
||||
* @param serverDestFile file containing the StreamSinkServer's binary Destination
|
||||
*/
|
||||
public StreamSinkSend(String filename, int writeDelayMs, String serverDestFile) {
|
||||
_sendFile = filename;
|
||||
_writeDelay = writeDelayMs;
|
||||
_peerDestFile = serverDestFile;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkClient.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually connect and run the client - this call blocks until completion.
|
||||
*
|
||||
*/
|
||||
public void runClient() {
|
||||
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
|
||||
Destination peer = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(_peerDestFile);
|
||||
peer = new Destination();
|
||||
peer.readBytes(fis);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error finding the peer destination to contact in " + _peerDestFile, ioe);
|
||||
return;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Peer destination is not valid in " + _peerDestFile, dfe);
|
||||
return;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
|
||||
System.out.println("Send " + _sendFile + " to " + peer.calculateHash().toBase64());
|
||||
|
||||
try {
|
||||
I2PSocket sock = mgr.connect(peer);
|
||||
byte buf[] = new byte[32*1024];
|
||||
OutputStream out = sock.getOutputStream();
|
||||
long beforeSending = System.currentTimeMillis();
|
||||
fis = new FileInputStream(_sendFile);
|
||||
long size = 0;
|
||||
while (true) {
|
||||
int read = fis.read(buf);
|
||||
if (read < 0)
|
||||
break;
|
||||
out.write(buf, 0, read);
|
||||
size += read;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Wrote " + read);
|
||||
if (_writeDelay > 0) {
|
||||
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
fis.close();
|
||||
sock.close();
|
||||
long afterSending = System.currentTimeMillis();
|
||||
System.out.println("Sent " + (size / 1024) + "KB in " + (afterSending-beforeSending) + "ms");
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("Timeout connecting to the peer", iie);
|
||||
return;
|
||||
} catch (NoRouteToHostException nrthe) {
|
||||
_log.error("Unable to connect to the peer", nrthe);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
return;
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error connecting to the peer", ie);
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("IO error sending", ioe);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the client. <code>Usage: StreamSinkClient sendFile writeDelayMs serverDestFile</code> <br />
|
||||
* <ul>
|
||||
* <li><b>sendFile</b>: filename to send</li>
|
||||
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
|
||||
* <li><b>serverDestFile</b>: file containing the StreamSinkServer's binary Destination</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 3) {
|
||||
System.out.println("Usage: StreamSinkClient sendFile writeDelayMs serverDestFile");
|
||||
} else {
|
||||
int writeDelayMs = -1;
|
||||
try {
|
||||
writeDelayMs = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Write delay ms invalid [" + args[1] + "]");
|
||||
return;
|
||||
}
|
||||
StreamSinkSend client = new StreamSinkSend(args[0], writeDelayMs, args[2]);
|
||||
client.runClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,9 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@@ -23,6 +26,9 @@ public class StreamSinkServer {
|
||||
private Log _log;
|
||||
private String _sinkDir;
|
||||
private String _destFile;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
private int _handlers;
|
||||
|
||||
/**
|
||||
* Create but do not start the streaming server.
|
||||
@@ -31,8 +37,14 @@ public class StreamSinkServer {
|
||||
* @param ourDestFile filename to write our binary destination to
|
||||
*/
|
||||
public StreamSinkServer(String sinkDir, String ourDestFile) {
|
||||
this(sinkDir, ourDestFile, null, -1, 3);
|
||||
}
|
||||
public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort, int handlers) {
|
||||
_sinkDir = sinkDir;
|
||||
_destFile = ourDestFile;
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
_handlers = handlers;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkServer.class);
|
||||
}
|
||||
|
||||
@@ -42,9 +54,14 @@ public class StreamSinkServer {
|
||||
*
|
||||
*/
|
||||
public void runServer() {
|
||||
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
|
||||
I2PSocketManager mgr = null;
|
||||
if (_i2cpHost != null)
|
||||
mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties());
|
||||
else
|
||||
mgr = I2PSocketManagerFactory.createManager();
|
||||
Destination dest = mgr.getSession().getMyDestination();
|
||||
System.out.println("Listening for connections on: " + dest.calculateHash().toBase64());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Listening for connections on: " + dest.calculateHash().toBase64());
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(_destFile);
|
||||
@@ -60,24 +77,16 @@ public class StreamSinkServer {
|
||||
}
|
||||
|
||||
I2PServerSocket sock = mgr.getServerSocket();
|
||||
while (true) {
|
||||
try {
|
||||
I2PSocket curSock = sock.accept();
|
||||
handle(curSock);
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error accepting connection", ie);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
return;
|
||||
}
|
||||
}
|
||||
startup(sock);
|
||||
}
|
||||
|
||||
private void handle(I2PSocket socket) {
|
||||
I2PThread t = new I2PThread(new ClientRunner(socket));
|
||||
t.setName("Handle " + socket.getPeerDestination().calculateHash().toBase64().substring(0,4));
|
||||
t.start();
|
||||
public void startup(I2PServerSocket sock) {
|
||||
for (int i = 0; i < _handlers; i++) {
|
||||
I2PThread t = new I2PThread(new ClientRunner(sock));
|
||||
t.setName("Handler " + i);
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,51 +94,102 @@ public class StreamSinkServer {
|
||||
*
|
||||
*/
|
||||
private class ClientRunner implements Runnable {
|
||||
private I2PSocket _sock;
|
||||
private FileOutputStream _fos;
|
||||
public ClientRunner(I2PSocket socket) {
|
||||
_sock = socket;
|
||||
private I2PServerSocket _socket;
|
||||
public ClientRunner(I2PServerSocket socket) {
|
||||
_socket = socket;
|
||||
}
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
I2PSocket socket = _socket.accept();
|
||||
if (socket != null)
|
||||
handle(socket);
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error accepting connection", ie);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(I2PSocket sock) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
File sink = new File(_sinkDir);
|
||||
if (!sink.exists())
|
||||
sink.mkdirs();
|
||||
File cur = File.createTempFile("clientSink", ".dat", sink);
|
||||
_fos = new FileOutputStream(cur);
|
||||
fos = new FileOutputStream(cur);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Writing to " + cur.getAbsolutePath());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error creating sink", ioe);
|
||||
_fos = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
public void run() {
|
||||
if (_fos == null) return;
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
InputStream in = _sock.getInputStream();
|
||||
InputStream in = sock.getInputStream();
|
||||
byte buf[] = new byte[4096];
|
||||
long written = 0;
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1) {
|
||||
_fos.write(buf, 0, read);
|
||||
//_fos.write(buf, 0, read);
|
||||
written += read;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("read and wrote " + read + " (" + written + ")");
|
||||
}
|
||||
fos.write(("written: [" + written + "]\n").getBytes());
|
||||
long lifetime = System.currentTimeMillis() - start;
|
||||
_log.info("Got EOF from client socket [written=" + written + " lifetime=" + lifetime + "]");
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing the sink", ioe);
|
||||
} finally {
|
||||
if (_fos != null) try { _fos.close(); } catch (IOException ioe) {}
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
if (sock != null) try { sock.close(); } catch (IOException ioe) {}
|
||||
_log.debug("Client socket closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the streaming server. <code>Usage: StreamSinkServer sinkDir ourDestFile</code><br />
|
||||
* Fire up the streaming server. <code>Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [numHandlers]</code><br />
|
||||
* <ul>
|
||||
* <li><b>sinkDir</b>: Directory to store received files in</li>
|
||||
* <li><b>ourDestFile</b>: filename to write our binary destination to</li>
|
||||
* <li><b>numHandlers</b>: how many concurrent connections to handle</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 2) {
|
||||
System.out.println("Usage: StreamSinkServer sinkDir ourDestFile");
|
||||
} else {
|
||||
StreamSinkServer server = new StreamSinkServer(args[0], args[1]);
|
||||
server.runServer();
|
||||
StreamSinkServer server = null;
|
||||
switch (args.length) {
|
||||
case 0:
|
||||
server = new StreamSinkServer("dataDir", "server.key", "localhost", 7654, 3);
|
||||
break;
|
||||
case 2:
|
||||
server = new StreamSinkServer(args[0], args[1]);
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
int handlers = 3;
|
||||
if (args.length == 5) {
|
||||
try {
|
||||
handlers = Integer.parseInt(args[4]);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
try {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
server = new StreamSinkServer(args[2], args[3], args[0], port, handlers);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [handlers]");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [handlers]");
|
||||
}
|
||||
if (server != null)
|
||||
server.runServer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.OutputStream;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Sit around on a destination, receiving lots of data and sending lots of
|
||||
* data to whomever talks to us.
|
||||
*
|
||||
* Usage: TestSwarm myKeyFile [peerDestFile ]*
|
||||
*
|
||||
*/
|
||||
public class TestSwarm {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private String _destFile;
|
||||
private String _peerDestFiles[];
|
||||
private String _conOptions;
|
||||
private I2PSocketManager _manager;
|
||||
private boolean _dead;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: TestSwarm myDestFile [peerDestFile ]*");
|
||||
return;
|
||||
}
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
String files[] = new String[args.length - 1];
|
||||
System.arraycopy(args, 1, files, 0, files.length);
|
||||
TestSwarm swarm = new TestSwarm(ctx, args[0], files);
|
||||
swarm.startup();
|
||||
}
|
||||
|
||||
public TestSwarm(I2PAppContext ctx, String destFile, String peerDestFiles[]) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(TestSwarm.class);
|
||||
_dead = false;
|
||||
_destFile = destFile;
|
||||
_peerDestFiles = peerDestFiles;
|
||||
_conOptions = "";
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
_log.debug("Starting up");
|
||||
File keys = new File(_destFile);
|
||||
if (!keys.exists()) {
|
||||
try {
|
||||
I2PClientFactory.createClient().createDestination(new FileOutputStream(keys));
|
||||
} catch (Exception e) {
|
||||
_log.error("Error creating a new destination on " + keys, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
_manager = I2PSocketManagerFactory.createManager(new FileInputStream(_destFile), null, -1, null);
|
||||
} catch (Exception e) {
|
||||
_log.error("Error creatign the manager", e);
|
||||
return;
|
||||
}
|
||||
|
||||
I2PThread listener = new I2PThread(new Listener(), "Listener");
|
||||
listener.start();
|
||||
|
||||
connectWithPeers();
|
||||
}
|
||||
|
||||
|
||||
private void connectWithPeers() {
|
||||
if (_peerDestFiles != null) {
|
||||
for (int i = 0; i < _peerDestFiles.length; i++) {
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(_peerDestFiles[i]);
|
||||
Destination dest = new Destination();
|
||||
dest.readBytes(fin);
|
||||
|
||||
I2PThread flooder = new I2PThread(new Flooder(dest), "Flooder+" + dest.calculateHash().toBase64().substring(0,4));
|
||||
flooder.start();
|
||||
} catch (Exception e) {
|
||||
_log.error("Unable to read the peer from " + _peerDestFiles[i], e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Listener implements Runnable {
|
||||
public void run() {
|
||||
try {
|
||||
I2PServerSocket ss = _manager.getServerSocket();
|
||||
I2PSocket s = null;
|
||||
while ( (s = ss.accept()) != null) {
|
||||
I2PThread flooder = new I2PThread(new Flooder(s), "Flooder-" + s.getPeerDestination().calculateHash().toBase64().substring(0,4));
|
||||
flooder.start();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error listening", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile long __conId = 0;
|
||||
private class Flooder implements Runnable {
|
||||
private Destination _remoteDestination;
|
||||
private I2PSocket _socket;
|
||||
private boolean _closed;
|
||||
private long _started;
|
||||
private long _totalSent;
|
||||
private long _totalReceived;
|
||||
private long _lastReceived;
|
||||
private long _lastReceivedOn;
|
||||
private long _connectionId;
|
||||
|
||||
public Flooder(Destination dest) {
|
||||
_socket = null;
|
||||
_remoteDestination = dest;
|
||||
_connectionId = ++__conId;
|
||||
_closed = false;
|
||||
_lastReceived = -1;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
}
|
||||
|
||||
public Flooder(I2PSocket socket) {
|
||||
_socket = socket;
|
||||
_remoteDestination = socket.getPeerDestination();
|
||||
_connectionId = ++__conId;
|
||||
_closed = false;
|
||||
_lastReceived = -1;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
}
|
||||
|
||||
public long getConnectionId() { return _connectionId; }
|
||||
public Destination getDestination() { return _remoteDestination; }
|
||||
|
||||
public void run() {
|
||||
_started = _context.clock().now();
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".started", 1, 0);
|
||||
byte data[] = new byte[4*1024];
|
||||
_context.random().nextBytes(data);
|
||||
long value = 0;
|
||||
long lastSend = _context.clock().now();
|
||||
if (_socket == null) {
|
||||
try {
|
||||
_socket = _manager.connect(_remoteDestination);
|
||||
} catch (Exception e) {
|
||||
_log.error("Error connecting to " + _remoteDestination.calculateHash().toBase64().substring(0,4));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
I2PThread floodListener = new I2PThread(new FloodListener(), "FloodListener" + _connectionId);
|
||||
floodListener.start();
|
||||
|
||||
try {
|
||||
OutputStream out = _socket.getOutputStream();
|
||||
while (!_closed) {
|
||||
if (shouldSend()) {
|
||||
out.write(data);
|
||||
// out.flush();
|
||||
_totalSent += data.length;
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
|
||||
//try { Thread.sleep(100); } catch (InterruptedException ie) {}
|
||||
long now = _context.clock().now();
|
||||
//_log.debug("Sending " + _connectionId + " after " + (now-lastSend));
|
||||
lastSend = now;
|
||||
//try { Thread.sleep(20); } catch (InterruptedException ie) {}
|
||||
} else {
|
||||
try { Thread.sleep(5000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error sending", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class FloodListener implements Runnable {
|
||||
public void run() {
|
||||
long lastRead = System.currentTimeMillis();
|
||||
long now = lastRead;
|
||||
try {
|
||||
InputStream in = _socket.getInputStream();
|
||||
byte buf[] = new byte[8*1024];
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1) {
|
||||
now = System.currentTimeMillis();
|
||||
_totalReceived += read;
|
||||
_context.statManager().addRateData("swarm." + getConnectionId() + ".totalReceived", _totalReceived, 0);
|
||||
//_log.debug("Receiving " + _connectionId + " with " + read + " after " + (now-lastRead));
|
||||
lastRead = now;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error listening to the flood", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSend() {
|
||||
return Boolean.valueOf(_context.getProperty("shouldSend", "false")).booleanValue();
|
||||
}
|
||||
}
|
||||
@@ -206,11 +206,11 @@ public class NetMonitor {
|
||||
}
|
||||
|
||||
/** drop all the old summary data */
|
||||
public void coallesceData() {
|
||||
public void coalesceData() {
|
||||
synchronized (_peerSummaries) {
|
||||
for (Iterator iter = _peerSummaries.values().iterator(); iter.hasNext(); ) {
|
||||
PeerSummary summary = (PeerSummary)iter.next();
|
||||
summary.coallesceData(_summaryDurationHours * 60*60*1000);
|
||||
summary.coalesceData(_summaryDurationHours * 60*60*1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class NetMonitorRunner implements Runnable {
|
||||
long nextExport = now + _monitor.getExportDelay() * 1000;
|
||||
while (_monitor.isRunning()) {
|
||||
now = Clock.getInstance().now();
|
||||
_monitor.coallesceData();
|
||||
_monitor.coalesceData();
|
||||
if (now >= nextHarvest) {
|
||||
runHarvest();
|
||||
nextHarvest = now + _monitor.getHarvestDelay() * 1000;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class PeerSummary {
|
||||
/** statName to a List of PeerStat elements (sorted by sample date, earliest first) */
|
||||
private Map _stats;
|
||||
/** lock on this when accessing stat data */
|
||||
private Object _coallesceLock = new Object();
|
||||
private Object _coalesceLock = new Object();
|
||||
|
||||
public PeerSummary(String peer) {
|
||||
_peer = peer;
|
||||
@@ -38,7 +38,7 @@ public class PeerSummary {
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, double val[]) {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
@@ -53,7 +53,7 @@ public class PeerSummary {
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, long val[]) {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public class PeerSummary {
|
||||
*
|
||||
*/
|
||||
public List getData(String statName) {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
return new ArrayList(((TreeMap)_stats.get(statName)).values());
|
||||
}
|
||||
}
|
||||
@@ -78,21 +78,21 @@ public class PeerSummary {
|
||||
*
|
||||
*/
|
||||
public Set getStatNames() {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
return new HashSet(_stats.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/** drop old data points */
|
||||
public void coallesceData(long summaryDurationMs) {
|
||||
public void coalesceData(long summaryDurationMs) {
|
||||
long earliest = Clock.getInstance().now() - summaryDurationMs;
|
||||
synchronized (_coallesceLock) {
|
||||
locked_coallesce(earliest);
|
||||
synchronized (_coalesceLock) {
|
||||
locked_coalesce(earliest);
|
||||
}
|
||||
}
|
||||
|
||||
/** go through all the stats and remove ones from before the given date */
|
||||
private void locked_coallesce(long earliestSampleDate) {
|
||||
private void locked_coalesce(long earliestSampleDate) {
|
||||
if (true) return;
|
||||
for (Iterator iter = _stats.keySet().iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
@@ -116,4 +116,4 @@ public class PeerSummary {
|
||||
_stats.put(statName, new TreeMap());
|
||||
return (TreeMap)_stats.get(statName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class PeerSummaryReader {
|
||||
}
|
||||
if (summary == null)
|
||||
return;
|
||||
summary.coallesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
summary.coalesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
monitor.addSummary(summary);
|
||||
}
|
||||
|
||||
@@ -103,4 +103,4 @@ class PeerSummaryReader {
|
||||
return _fmt.parse(when).getTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
236
apps/pants/build.xml
Normal file
@@ -0,0 +1,236 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
Ports + Ant = Pants, a simple Ant-based package manager
|
||||
|
||||
free (adj.): unencumbered; not under the control of others
|
||||
|
||||
Written by smeghead in 2005 and released into the public domain with no
|
||||
warranty of any kind, either expressed or implied. It probably won't make
|
||||
your computer catch on fire, or eat your children, but it might. Use at your
|
||||
own risk.
|
||||
-->
|
||||
|
||||
<project basedir="." default="help" name="pants-interface">
|
||||
|
||||
<!-- .......................... Global Properties .......................... -->
|
||||
|
||||
|
||||
|
||||
<!-- ........................... Internal Tasks ............................ -->
|
||||
|
||||
<target name="-fetchCvs" unless="cvs.source.available" if="using.cvs">
|
||||
<cvs compressionlevel="${cvs.compression.level}"
|
||||
date="${cvs.date}"
|
||||
dest="./distfiles/cvs-src/${pbuild}"
|
||||
failonerror="true"
|
||||
package="${cvs.package}"
|
||||
passfile="${cvs.passfile}"
|
||||
port="${cvs.port}"
|
||||
cvsRoot="${cvs.root}"
|
||||
cvsRsh="${cvs.rsh}"
|
||||
tag="${cvs.tag}"
|
||||
/>
|
||||
</target>
|
||||
|
||||
<target name="-fetchPackage" unless="using.cvs">
|
||||
<get src="${package.url}"
|
||||
verbose="true"
|
||||
dest="./distfiles"
|
||||
/>
|
||||
</target>
|
||||
|
||||
<target name="-init">
|
||||
<!--
|
||||
TODO: Create dist/ and working/ folders for each pbuild subdir in case
|
||||
they've been wiped.
|
||||
-->
|
||||
<loadproperties srcfile="world" />
|
||||
<taskdef name="mergetypedproperties"
|
||||
classname="net.i2p.pants.MergeTypedPropertiesTask"
|
||||
classpath="./lib/pants.jar"
|
||||
/>
|
||||
<mergetypedproperties input="./pbuilds/${pbuild}/pbuild.properties"
|
||||
output="./pbuilds/${pbuild}/merged-properties.temp"
|
||||
booleanList="version.latest.find.regex.canonicaleq, version.latest.find.regex.caseinsensitive, version.latest.find.regex.comments, version.latest.find.regex.dotall, version.latest.find.regex.multiline, version.latest.find.regex.unicodecase, version.latest.find.regex.unixlines"
|
||||
stringList="cvs.compression.level, cvs.date, cvs.package, cvs.passfile, cvs.port, cvs.root, cvs.rsh, cvs.tag, package.url, version.latest, version.latest.find.url, version.latest.find.regex"
|
||||
/>
|
||||
<loadproperties srcfile="./pbuilds/${pbuild}/merged-properties.temp" />
|
||||
<delete file="./pbuilds/${pbuild}/merged-properties.temp" />
|
||||
<!--
|
||||
If '-Dpbuild={name}' isn't specified, the 'build', 'fetch', 'update'
|
||||
and 'version' commands should default to 'world' behavior.
|
||||
-->
|
||||
<antcall target="-setWorld" />
|
||||
<condition property="using.cvs">
|
||||
<or>
|
||||
<equals arg1="CVS" arg2="${version.using.${pbuild}}" />
|
||||
<equals arg1="cvs" arg2="${version.using.${pbuild}}" />
|
||||
</or>
|
||||
</condition>
|
||||
<!--
|
||||
If 'version.recommended' isn't defined in pbuild.properties, default
|
||||
to latest available version.
|
||||
-->
|
||||
</target>
|
||||
|
||||
<target name="-setWorld" unless="pbuild">
|
||||
<property name="pbuild" value="world" />
|
||||
</target>
|
||||
|
||||
<target name="-unpackTarBz2">
|
||||
<untar src="${pbuild.package}"
|
||||
compression="bzip2"
|
||||
dest="./${pbuild}/working"
|
||||
/>
|
||||
</target>
|
||||
|
||||
<target name="-unpackTarGzip">
|
||||
<untar src="${pbuild.package}"
|
||||
compression="gzip"
|
||||
dest="./${pbuild}/working"
|
||||
/>
|
||||
</target>
|
||||
|
||||
<target name="-unpackZip">
|
||||
<unzip src="${pbuild.package}" dest="./${pbuild}/working" />
|
||||
</target>
|
||||
|
||||
<target name="-updateCvs" if="using.cvs">
|
||||
<cvs command="update -d"
|
||||
compressionlevel="${compression.level}"
|
||||
date="${cvs.date}"
|
||||
dest="./distfiles/cvs-src"
|
||||
failonerror="true"
|
||||
package="${cvs.package}"
|
||||
passfile="${cvs.passfile}"
|
||||
port="${cvs.port}"
|
||||
cvsRoot="${cvs.root}"
|
||||
cvsRsh="${cvs.rsh}"
|
||||
tag="${cvs.tag}"
|
||||
/>
|
||||
</target>
|
||||
|
||||
<target name="-updateConfirm" if="confirm.update" unless="no.prompts">
|
||||
<input validargs="y,Y,n,N"
|
||||
defaultvalue="n"
|
||||
addproperty="confirm.update.answer">
|
||||
You currently have the recommended version installed. A newer
|
||||
version will be installed if you continue and this may break some
|
||||
applications which depend on this package. Are you sure you want
|
||||
to update? [y/N]
|
||||
</input>
|
||||
<condition property="abort.update">
|
||||
<or>
|
||||
<equals arg1="n" arg2="${confirm.update.answer}" />
|
||||
<equals arg1="N" arg2="${confirm.update.answer}" />
|
||||
</or>
|
||||
</condition>
|
||||
<fail if="abort.update">Update aborted.</fail>
|
||||
</target>
|
||||
|
||||
<target name="-versionLatest">
|
||||
<get src="${version.latest.find.url}"
|
||||
dest="version.latest.in.temp"
|
||||
verbose="true"
|
||||
/>
|
||||
<taskdef name="match"
|
||||
classname="net.i2p.pants.MatchTask"
|
||||
classpath="./lib/pants.jar"
|
||||
/>
|
||||
<match input="version.latest.in.temp"
|
||||
output="version.latest.parsed.temp"
|
||||
regex="${version.latest.find.regex}"
|
||||
canonicaleq="${version.latest.find.regex.canonicaleq}"
|
||||
caseinsensitive="${version.latest.find.regex.caseinsensitive}"
|
||||
comments="${version.latest.find.regex.comments}"
|
||||
dotall="${version.latest.find.regex.dotall}"
|
||||
multiline="${version.latest.find.regex.multiline}"
|
||||
unicodecase="${version.latest.find.regex.unicodecase}"
|
||||
unixlines="${version.latest.find.regex.unixlines}"
|
||||
/>
|
||||
<loadproperties srcFile="version.latest.parsed.temp" />
|
||||
<delete file="version.latest.in.temp" />
|
||||
<delete file="version.latest.parsed.temp" />
|
||||
<property name="version.latest" value="${group.1}" />
|
||||
</target>
|
||||
|
||||
<target name="-versionRecommended">
|
||||
<property name="version.recommended" value="x" />
|
||||
</target>
|
||||
|
||||
<target name="-versionUsing">
|
||||
<property name="version.using" value="x" />
|
||||
</target>
|
||||
|
||||
<!-- .......................... Public Interface ........................... -->
|
||||
|
||||
<target name="build" depends="-init,fetch"
|
||||
description="Build a pbuild and its dependencies">
|
||||
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}" target="clean" />
|
||||
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}" target="build" />
|
||||
<!--
|
||||
Perform a 'clean' on the target first (but not 'distclean')
|
||||
-->
|
||||
</target>
|
||||
|
||||
<target name="fetch" depends="-init"
|
||||
description="Get package only">
|
||||
<antcall target="-fetchPackage" />
|
||||
<antcall target="-fetchCvs" />
|
||||
<copydir dest="./pbuilds/${pbuild}/working"
|
||||
src="./distfiles/cvs-src/${pbuild}"
|
||||
/>
|
||||
</target>
|
||||
|
||||
<target name="help"
|
||||
description="Display usage synopsis">
|
||||
<echo>
|
||||
Pants usage:
|
||||
|
||||
ant [--usejikes] [-Dpbuild={name}] [-Dpbuild.version={version}]
|
||||
[-D{property}={value}] [-Dno.prompts=true] build | fetch |
|
||||
help | install | uninstall | update | version
|
||||
|
||||
build Build a pbuild and its dependencies
|
||||
fetch Get package only
|
||||
help Display usage synopsis
|
||||
install Fetch, build and install a pbuild
|
||||
uninstall Uninstall a pbuild
|
||||
update Update pbuild(s) to the latest version(s)
|
||||
version Display pbuild version information
|
||||
</echo>
|
||||
</target>
|
||||
|
||||
<!--
|
||||
Install recommended version by default unless 'version' property is set.
|
||||
Do not install if package is already installed.
|
||||
-->
|
||||
<target name="install" depends="-init, build"
|
||||
description="Install a pbuild">
|
||||
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}" target="dist" />
|
||||
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}"
|
||||
target="distclean"
|
||||
/>
|
||||
</target>
|
||||
|
||||
<target name="uninstall" depends="-init"
|
||||
description="Uninstall a pbuild" />
|
||||
|
||||
<target name="update" depends="-init"
|
||||
description="Update pbuild(s) to the latest version(s)">
|
||||
<condition property="${confirm.update}">
|
||||
<equals arg1="${version.using}" arg2="${version.recommended}" />
|
||||
</condition>
|
||||
<antcall target="-updateConfirm" />
|
||||
</target>
|
||||
|
||||
<target name="version"
|
||||
depends="-init, -versionRecommended, -versionUsing, -versionLatest"
|
||||
description="Display pbuild version information">
|
||||
<echo message="Latest version: ${version.recommended}" />
|
||||
<echo message="Latest version: ${version.using}" />
|
||||
<echo message="Latest version: ${version.latest}" />
|
||||
</target>
|
||||
|
||||
</project>
|
||||
45
apps/pants/pants/build.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="build" name="build-pants">
|
||||
|
||||
<target name="build"
|
||||
description="Build the source">
|
||||
<mkdir dir="./java/build"/>
|
||||
<javac srcdir="./java/src" source="1.3" target="1.3" deprecation="on" destdir="./java/build" />
|
||||
</target>
|
||||
|
||||
<target name="clean"
|
||||
description="Remove all object files">
|
||||
<delete dir="./java/build" />
|
||||
<delete dir="./java/jartemp" />
|
||||
</target>
|
||||
|
||||
<target name="dist" depends="build, jar"
|
||||
description="Create the jar and copy it to ../lib">
|
||||
<copy todir="../lib" file="./java/build/pants.jar" />
|
||||
</target>
|
||||
|
||||
<target name="distclean" depends="clean"
|
||||
description="Remove the jar and all object files" >
|
||||
<delete file="../lib/pants.jar" />
|
||||
</target>
|
||||
|
||||
<target name="jar">
|
||||
<delete dir="./java/jartemp" />
|
||||
<mkdir dir="./java/jartemp" />
|
||||
<copy todir="./java/jartemp">
|
||||
<fileset dir="./java/build" includes="**/*.class" />
|
||||
</copy>
|
||||
<jar basedir="./java/jartemp" jarfile="./java/build/pants.jar">
|
||||
<manifest>
|
||||
<section name="net.i2p.pants">
|
||||
<attribute name="Implementation-Title" value="Pants" />
|
||||
<attribute name="Implementation-Version" value="0.0.1" />
|
||||
<attribute name="Implementation-Vendor" value="I2P" />
|
||||
<attribute name="Implementation-Vendor-Id" value="I2P" />
|
||||
<attribute name="Implementation-URL" value="http://www.i2p.net" />
|
||||
</section>
|
||||
</manifest>
|
||||
</jar>
|
||||
<delete dir="./java/jartemp" />
|
||||
</target>
|
||||
</project>
|
||||
212
apps/pants/pants/java/src/net/i2p/pants/MatchTask.java
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Ports + Ant = Pants, a simple Ant-based package manager
|
||||
*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
*
|
||||
* Written by smeghead in 2005 and released into the public domain with no
|
||||
* warranty of any kind, either expressed or implied. It probably won't make
|
||||
* your computer catch on fire, or eat your children, but it might. Use at your
|
||||
* own risk.
|
||||
*/
|
||||
|
||||
package net.i2p.pants;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
/**
|
||||
* <p>Custom Ant task for matching the contents of a file against a given
|
||||
* regular expression and writing any matching groups to a file in
|
||||
* <code>java.util.Properties</code> format.
|
||||
* </p>
|
||||
* <p>Each key in the properties file is named after the number corresponding to
|
||||
* its matching group and its value is the contents of the matching group.
|
||||
* </p>
|
||||
* <p>Regular expressions passed to this task must conform to the specification
|
||||
* used by Sun's <code>java.util.regex</code> package and thus are mostly
|
||||
* compatible with Perl 5 regular expressions.
|
||||
* </p>
|
||||
* <p>When calling the <code>match</code> task, the attributes
|
||||
* <code>input</code>, <code>output</code>, and <code>regex</code> are required.
|
||||
* </p>
|
||||
* <p>Optional boolean attributes may be used to toggle various modes for the
|
||||
* regular expression engine (all are set to <code>false</code> by default):
|
||||
* </p>
|
||||
* <table>
|
||||
* <tr><td><code>canonicaleq</code></td><td>Enable canonical equivalence</td></tr>
|
||||
* <tr><td><code>caseinsensitive</code></td><td>Enable case-insensitive matching</td></tr>
|
||||
* <tr><td><code>comments</code></td><td>Permit whitespace and comments in pattern</td></tr>
|
||||
* <tr><td><code>dotall</code></td><td>Enable dotall mode</td></tr>
|
||||
* <tr><td><code>multiline</code></td><td>Enable multi-line mode</td></tr>
|
||||
* <tr><td><code>unicodecase</code></td><td>Enable Unicode-aware case folding</td></tr>
|
||||
* <tr><td><code>unixlines</code></td><td>Enable Unix lines mode</td></tr>
|
||||
* </table>
|
||||
* <p>There is one additional optional boolean attribute,
|
||||
* <code>failOnNoMatch</code>. If this attribute is <code>true</code> it causes
|
||||
* the <code>match</code> task to throw a
|
||||
* <code>org.apache.tools.ant.BuildException</code> and fail if no matches for
|
||||
* the regular expression are found. The default value is <code>false</code>,
|
||||
* meaning a failed match will simply result in a warning message to
|
||||
* <code>STDERR</code> and an empty (0 byte) <code>output</code> file being
|
||||
* created.
|
||||
* </p>
|
||||
* <p>
|
||||
* <h4>Example</h4>
|
||||
* </p>
|
||||
* <p>Contents of input file <code>letter.txt</code>:
|
||||
* <pre>
|
||||
* Dear Alice,
|
||||
*
|
||||
* How's about you and me gettin' together for some anonymous foo action?
|
||||
*
|
||||
* Kisses,
|
||||
* Bob
|
||||
* </pre>
|
||||
* </p>
|
||||
* <p>Ant <code>match</code> task and a <code>taskdef</code> defining it:
|
||||
* <pre>
|
||||
* <taskdef name="match" classname="net.i2p.pants.MatchTask" classpath="../../lib/pants.jar" />
|
||||
* <match input="letter.txt"
|
||||
* output="matches.txt"
|
||||
* regex="about (\S*?) and (\S*?) .+anonymous (\S*?)"
|
||||
* />
|
||||
* </pre>
|
||||
* </p>
|
||||
* <p>Contents of properties file <code>matches.txt</code> written by this task:
|
||||
* <pre>
|
||||
* group.0=about you and me gettin' together for some anonymous foo
|
||||
* group.1=you
|
||||
* group.2=me
|
||||
* group.3=foo
|
||||
* </pre>
|
||||
* </p>
|
||||
* <p>These values can be loaded from <code>matches.txt</code> into Ant
|
||||
* properties like so:
|
||||
* <pre>
|
||||
* <loadproperties srcFile="matches.txt" />
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @author smeghead
|
||||
*/
|
||||
public class MatchTask extends Task {
|
||||
|
||||
private boolean _failOnNoMatch;
|
||||
private String _inputFile;
|
||||
private String _outputFile;
|
||||
private String _regex;
|
||||
private int _regexFlags;
|
||||
|
||||
public void execute() throws BuildException {
|
||||
int charRead = 0;
|
||||
FileReader fileReader = null;
|
||||
FileWriter fileWriter = null;
|
||||
Matcher matcher = null;
|
||||
Pattern pattern = null;
|
||||
PrintWriter printWriter = null;
|
||||
StringBuffer text = new StringBuffer();
|
||||
|
||||
if (_inputFile == null)
|
||||
throw new BuildException("Error: 'match' task requires 'input' attribute");
|
||||
|
||||
if (_outputFile == null)
|
||||
throw new BuildException("Error: 'match' task requires 'output' attribute");
|
||||
|
||||
if (_regex == null)
|
||||
throw new BuildException("Error: 'match' task requires 'regex' attribute");
|
||||
|
||||
pattern = Pattern.compile(_regex, _regexFlags);
|
||||
|
||||
try {
|
||||
fileReader = new FileReader(_inputFile);
|
||||
|
||||
while ((charRead = fileReader.read()) != -1)
|
||||
text.append((char) charRead);
|
||||
|
||||
fileReader.close();
|
||||
matcher = pattern.matcher(text);
|
||||
|
||||
if (matcher.find()) {
|
||||
printWriter = new PrintWriter(new FileWriter(_outputFile));
|
||||
|
||||
for (int i = 0; i <= matcher.groupCount(); i++)
|
||||
printWriter.println("group." + Integer.toString(i) + "=" + matcher.group(i));
|
||||
|
||||
printWriter.flush();
|
||||
printWriter.close();
|
||||
} else {
|
||||
if (_failOnNoMatch) {
|
||||
throw new BuildException("Error: No matches found in " + _inputFile);
|
||||
} else {
|
||||
System.err.println("Warning: No matches found in " + _inputFile);
|
||||
// Create 0 byte output file.
|
||||
fileWriter = new FileWriter(_outputFile);
|
||||
fileWriter.close();
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
throw new BuildException("File " + _inputFile + " not found", fnfe);
|
||||
} catch (IOException ioe) {
|
||||
throw new BuildException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public void setCanonicalEq(boolean enableCanonicalEq) {
|
||||
if (enableCanonicalEq)
|
||||
_regexFlags |= Pattern.CANON_EQ;
|
||||
}
|
||||
|
||||
public void setCaseInsensitive(boolean enableCaseInsensitive) {
|
||||
if (enableCaseInsensitive)
|
||||
_regexFlags |= Pattern.CASE_INSENSITIVE;
|
||||
}
|
||||
|
||||
public void setComments(boolean enableComments) {
|
||||
if (enableComments)
|
||||
_regexFlags |= Pattern.COMMENTS;
|
||||
}
|
||||
|
||||
public void setDotall(boolean enableDotall) {
|
||||
if (enableDotall)
|
||||
_regexFlags |= Pattern.DOTALL;
|
||||
}
|
||||
|
||||
public void setFailOnNoMatch(boolean failOnNoMatch) {
|
||||
_failOnNoMatch = failOnNoMatch;
|
||||
}
|
||||
|
||||
public void setInput(String inputFile) {
|
||||
_inputFile = inputFile;
|
||||
}
|
||||
|
||||
public void setMultiLine(boolean enableMultiLine) {
|
||||
if (enableMultiLine)
|
||||
_regexFlags |= Pattern.MULTILINE;
|
||||
}
|
||||
|
||||
public void setOutput(String outputFile) {
|
||||
_outputFile = outputFile;
|
||||
}
|
||||
|
||||
public void setRegex(String regex) {
|
||||
_regex = regex;
|
||||
}
|
||||
|
||||
public void setUnicodeCase(boolean enableUnicodeCase) {
|
||||
if (enableUnicodeCase)
|
||||
_regexFlags |= Pattern.UNICODE_CASE;
|
||||
}
|
||||
|
||||
public void setUnixLines(boolean enableUnixLines) {
|
||||
if (enableUnixLines)
|
||||
_regexFlags |= Pattern.UNIX_LINES;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Ports + Ant = Pants, a simple Ant-based package manager
|
||||
*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
*
|
||||
* Written by smeghead in 2005 and released into the public domain with no
|
||||
* warranty of any kind, either expressed or implied. It probably won't make
|
||||
* your computer catch on fire, or eat your children, but it might. Use at your
|
||||
* own risk.
|
||||
*/
|
||||
|
||||
package net.i2p.pants;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
/**
|
||||
* <p>Custom Ant task for loading properties from a
|
||||
* <code>java.util.Properties</code> file then merging them with lists of
|
||||
* expected properties. When an expected property is found in the properties
|
||||
* file it is set to the value given for it in the file. If an expected property
|
||||
* from a list isn't found in the properties file its value will be set to "" or
|
||||
* "false", depending on the property's data type.
|
||||
* </p>
|
||||
* <p>A property's data type is determined by membership in one of two lists
|
||||
* which can be passed into an instance of this class: a string-typed list and a
|
||||
* boolean-typed list. Values for string-typed properties may be any valid
|
||||
* string accepted by <code>java.util.Properties</code>, and values for
|
||||
* boolean-typed properties must be either "false" or "true".
|
||||
* </p>
|
||||
* <p>Lists holding more than one property must be comma-delimited.
|
||||
* </p>
|
||||
* <p>The output of this class is a temporary <code>java.util.Properties</code>
|
||||
* file which is suitable for reading by the standard Ant
|
||||
* <code>loadproperties</code> task.
|
||||
* </p>
|
||||
* <p>Note that if any properties in the given lists have already been defined
|
||||
* before the <code>mergetypedproperties</code> task is called, their values
|
||||
* cannot be changed since Ant properties are immutable.
|
||||
* </p>
|
||||
* <h4>Example</h4>
|
||||
* </p>
|
||||
* <p>Contents of a properties file <code>my.properties</code>:
|
||||
* <pre>
|
||||
* some.property.exists=true
|
||||
* hasValue=false
|
||||
* some.property=this is a value
|
||||
* property0=bork bork
|
||||
* propertyX=this property wasn't passed in a list
|
||||
* </pre>
|
||||
* </p>
|
||||
* <p>Ant <code>mergetypedproperties</code> task and a <code>taskdef</code>
|
||||
* defining it:
|
||||
* <pre>
|
||||
* <taskdef name="mergetypedproperties" classname="net.i2p.pants.MergeTypedPropertiesTask" classpath="../../lib/pants.jar" />
|
||||
* <mergetypedproperties input="my.properties"
|
||||
* output="merged-properties.temp"
|
||||
* booleanList="some.property.exists,is.valid,hasValue"
|
||||
* stringList="some.property,another.property,property0"
|
||||
* />
|
||||
* </pre>
|
||||
* </p>
|
||||
* <p>Contents of properties file <code>merged-properties.temp</code> written by this task:
|
||||
* <pre>
|
||||
* some.property.exists=true
|
||||
* is.valid=false
|
||||
* hasValue=false
|
||||
* some.property=this is a value
|
||||
* another.property=
|
||||
* property0=bork bork
|
||||
* propertyX=this property wasn't passed in a list
|
||||
* </pre>
|
||||
* </p>
|
||||
* <p>If you don't want this task's output to include properties which weren't
|
||||
* in the lists of expected properties, you can set the attribute
|
||||
* <code>onlyExpected</code> to <code>true</code>. In the example, this would
|
||||
* result in the file <code>merged-properties.temp</code> containing only the
|
||||
* following properties:
|
||||
* <pre>
|
||||
* some.property.exists=true
|
||||
* is.valid=false
|
||||
* hasValue=false
|
||||
* some.property=this is a value
|
||||
* another.property=
|
||||
* property0=bork bork
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @author smeghead
|
||||
*/
|
||||
public class MergeTypedPropertiesTask extends Task {
|
||||
|
||||
private String _booleanList = "";
|
||||
private String _inputFile;
|
||||
private boolean _onlyExpected;
|
||||
private String _outputFile;
|
||||
private Properties _propertiesIn = new Properties();
|
||||
private Properties _propertiesOut = new Properties();
|
||||
private String _stringList = "";
|
||||
|
||||
public void execute() throws BuildException {
|
||||
StringTokenizer strtokBoolean = new StringTokenizer(_booleanList, ",");
|
||||
StringTokenizer strtokString = new StringTokenizer(_stringList, ",");
|
||||
String property = "";
|
||||
|
||||
if (_inputFile == null)
|
||||
throw new BuildException("Error: 'mergetypedproperties' task requires 'input' attribute");
|
||||
|
||||
if (_outputFile == null)
|
||||
throw new BuildException("Error: 'mergetypedproperties' task requires 'output' attribute");
|
||||
|
||||
// Add some type-checking on the list elements
|
||||
|
||||
try {
|
||||
_propertiesIn.load(new FileInputStream(_inputFile));
|
||||
|
||||
while (strtokBoolean.hasMoreTokens())
|
||||
_propertiesOut.setProperty(strtokBoolean.nextToken().trim(), "false");
|
||||
|
||||
while (strtokString.hasMoreTokens())
|
||||
_propertiesOut.setProperty(strtokString.nextToken().trim(), "");
|
||||
|
||||
for (Enumeration enum = _propertiesIn.elements(); enum.hasMoreElements(); ) {
|
||||
property = (String) enum.nextElement();
|
||||
|
||||
if (_onlyExpected && !_propertiesOut.containsKey(property))
|
||||
continue;
|
||||
else
|
||||
_propertiesOut.setProperty(property, _propertiesIn.getProperty(property));
|
||||
}
|
||||
|
||||
_propertiesOut.store(new FileOutputStream(_inputFile), "This is a temporary file. It is safe to delete it.");
|
||||
} catch (IOException ioe) {
|
||||
throw new BuildException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBooleanList(String booleanList) {
|
||||
_booleanList = booleanList;
|
||||
}
|
||||
|
||||
public void setInput(String inputFile) {
|
||||
_inputFile = inputFile;
|
||||
}
|
||||
|
||||
public void setOnlyExpected(boolean onlyExpected) {
|
||||
_onlyExpected = onlyExpected;
|
||||
}
|
||||
|
||||
public void setOutput(String outputFile) {
|
||||
_outputFile = outputFile;
|
||||
}
|
||||
|
||||
public void setStringList(String stringList) {
|
||||
_stringList = stringList;
|
||||
}
|
||||
}
|
||||
116
apps/pants/pants/resources/README
Normal file
@@ -0,0 +1,116 @@
|
||||
What is Pants?
|
||||
--------------
|
||||
|
||||
Pants is an Apache Ant-based package manager for the management of 3rd party
|
||||
dependencies in Java development projects. It's loosely modeled after
|
||||
FreeBSD's Ports and Gentoo Linux's Portage, with two major differences:
|
||||
|
||||
* Pants isn't intended for system-wide package management. It's tailored for
|
||||
per-project 3rd party package management. You will typically have one
|
||||
Pants repository per project and each repository will be located somewhere
|
||||
under your project's root directory. If you're familiar with Ports or
|
||||
Portage, a Pants repository is roughly analogous to /usr/ports or
|
||||
/usr/portage.
|
||||
|
||||
* Pants is extremely portable. It goes anywhere Apache Ant goes.
|
||||
|
||||
Pants takes a modular approach to the standard Ant buildfile, breaking it
|
||||
into 3 files for functionality and convenience:
|
||||
|
||||
1. The Pants public interface, pants/build.xml, provides a single consistent
|
||||
way to access and manipulate dependency packages and relieves some of the
|
||||
developer's burden by providing implementations for some frequently-used
|
||||
and complex Ant operations.
|
||||
|
||||
2. pbuild.xml is a specially-structured and slimmed-down Ant buildfile in
|
||||
which you implement custom handling for a package your project depends
|
||||
on. This is known as the "pbuild" and is roughly analogous to a FreeBSD
|
||||
port or a Gentoo ebuild. A fairly explanatory template for pbuilds,
|
||||
pbuild.template.xml, is provided.
|
||||
|
||||
3. pbuild.properties contains those properties for a specific pbuild which
|
||||
are most likely to change over time. It uses the java.util.Properties
|
||||
format which is more human-friendly for hand-editing than Ant/XML. A
|
||||
fairly explanatory template, pbuild.template.properties, is provided.
|
||||
|
||||
There is one more file that completes the Pants system: the metadata file
|
||||
pants/world is a database for keeping track of all packages managed by Pants
|
||||
for your project.
|
||||
|
||||
Pants automatically handles versioning for your project's dependency
|
||||
packages and keeps track of their recommended versions, currently used
|
||||
versions, and latest available versions. This makes it extremely simple for
|
||||
project developers to switch back and forth between different versions of a
|
||||
dependency, and makes it just as easy to update a dependency. You can even
|
||||
update all your project's Pants-managed packages with a single command.
|
||||
|
||||
Pbuilds are designed to automatically handle the downloading, building,
|
||||
repackaging and deployment of source archives, binary archives, and CVS
|
||||
sources, all in a manner that's completely transparent to the project
|
||||
developer. Pbuilds currently support tar + gzip, tar + bzip2, and zip
|
||||
archives.
|
||||
|
||||
Because it is based on Ant, Pants integrates very well with Ant buildfiles
|
||||
and will fit easily into your project's Ant build framework. However, its
|
||||
interface is simple enough to be called just as easily by more traditional
|
||||
build systems such as GNU Make.
|
||||
|
||||
|
||||
Why Should I Use Pants?
|
||||
-----------------------
|
||||
|
||||
There are many applications for Pants, but a few use cases should best serve
|
||||
to illustrate its usefulness:
|
||||
|
||||
1. You have a project that you ship with several 3rd party libraries but the
|
||||
versions you're using are stale. With a single command, Pants can
|
||||
automatically discover the latest release versions for all of these, then
|
||||
download, build, and place the fresh libraries where your project's main
|
||||
build system expects them to be at build time.
|
||||
|
||||
2. You want to test multiple versions of a 3rd party library against your
|
||||
project. Pants only requires you to issue a single command to switch
|
||||
library versions, so can spend more time testing and less time hunting
|
||||
packages down, unpackaging them, symlinking, etc.
|
||||
|
||||
3. Pants is public domain. You can ship it with your project if you need to
|
||||
without having to worry about petty intellectual property or licensing
|
||||
issues.
|
||||
|
||||
|
||||
Minimum Requirements
|
||||
--------------------
|
||||
|
||||
* Apache Ant 1.6.2 or higher is recommended
|
||||
|
||||
* Any Java runtime and operating system that will run Ant
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Not finished yet.
|
||||
|
||||
|
||||
Why the Silly Name?
|
||||
-------------------
|
||||
|
||||
Ports + Ant = Pants. Any other explanation is purely a product of your
|
||||
twisted imagination.
|
||||
|
||||
|
||||
Miscellaneous Pocket Fluff
|
||||
--------------------------
|
||||
|
||||
Author: smeghead <smeghead@i2pmail.org> <smeghead@mail.i2p>
|
||||
|
||||
License: No license necessary. This work is released into the public domain.
|
||||
|
||||
Price: Free! But if you really appreciate Pants, or you're just a sicko,
|
||||
please send me a picture of your worst or most unusual pair of
|
||||
pants so I can add it to the Whirling Hall of Pants on pants.i2p,
|
||||
the official Pants eepsite (that's an anonymous website on I2P--see
|
||||
http://www.i2p.net for more information).
|
||||
|
||||
|
||||
$Id$
|
||||
110
apps/pants/pants/resources/pbuild.template.properties
Normal file
@@ -0,0 +1,110 @@
|
||||
# The properties defined in this file can be overridden on the command line by
|
||||
# passing them in as parameters like so:
|
||||
#
|
||||
# ant -Dpbuild=myapp -Dversion.recommended=2.0.5 install
|
||||
#
|
||||
# *** DO NOT DEFINE A PROPERTY BUT LEAVE ITS VALUE BLANK. PANTS WILL BREAK! ***
|
||||
|
||||
|
||||
# Recommended Package Version
|
||||
#
|
||||
# Set this property's value to the package version you want Pants to use for the
|
||||
# pbuild by default. The version string specified must match the version
|
||||
# substring from the package's filename if the filename contains a version
|
||||
# number.
|
||||
#
|
||||
# Comment out this property to force use of the latest available version.
|
||||
#
|
||||
# If the pbuild is CVS-based rather than package-based, this property must be
|
||||
# set to 'CVS'.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# version.recommended=2.0.4
|
||||
|
||||
|
||||
# Latest Package Version
|
||||
#
|
||||
# There are currently two ways to inform Pants of the latest version number for
|
||||
# your package.
|
||||
#
|
||||
# Method 1: Manually modify the property 'version.latest' to reflect the latest
|
||||
# version number.
|
||||
#
|
||||
# Method 2: Provide a URL for a page on the package's website and a regular
|
||||
# expression with which to parse it in order to extract the version
|
||||
# number of the latest available package. For this you must define the
|
||||
# properties 'version.latest.find.url', 'version.latest.find.regex',
|
||||
# and any regular expression engine mode flags needed. The pattern
|
||||
# defined must have exactly one capturing group to encapsulate the
|
||||
# version string, otherwise the operation will fail.
|
||||
#
|
||||
# You may use both methods, in which case the version number specified by Method
|
||||
# 1 will be used as the fallback value if Method 2 for some reason is
|
||||
# unsuccessful.
|
||||
#
|
||||
# If neither method is enabled here or they fail to return a valid value to
|
||||
# Pants, the 'ant update' operation for this pbuild may exit ungracefully unless
|
||||
# the pbuild is CVS-based (none of the version.latest.* properties are used by
|
||||
# CVS-based pbuilds).
|
||||
#
|
||||
# The following is a list of boolean properties for optional mode flags used by
|
||||
# the regular expression engine. Set a value of "true" for any you wish to use.
|
||||
#
|
||||
# version.latest.find.regex.canonicaleq - Enable canonical equivalence
|
||||
# version.latest.find.regex.caseinsensitive - Enable case-insensitive matching
|
||||
# version.latest.find.regex.comments - Permit whitespace and comments
|
||||
# version.latest.find.regex.dotall - Enable dotall mode
|
||||
# version.latest.find.regex.multiline - Enable multi-line mode
|
||||
# version.latest.find.regex.unicodecase - Enable Unicode-aware case folding
|
||||
# version.latest.find.regex.unixlines - Enable Unix lines mode
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# version.latest=5.1.2
|
||||
# version.latest.find.url=http://sourceforge.net/projects/jetty/
|
||||
# version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
|
||||
|
||||
|
||||
# Package URL
|
||||
#
|
||||
# Specify the URL pointing to the pbuild's package from here. The token
|
||||
# '${pbuild.version}' if used will automatically be expanded to the appropriate
|
||||
# version string.
|
||||
#
|
||||
# The package URL property is not used by CVS-based pbuilds.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# package.url=ftp://borkbork.se/bork-${pbuild.version}.tar.bz2
|
||||
# package.url=http://bork.borkbork.se/bork-${pbuild.version}-src.tar.gz
|
||||
|
||||
|
||||
# CVS Repository
|
||||
#
|
||||
# The values expected for CVS properties here are the same as those expected by
|
||||
# their corresponding Apache Ant 'Cvs' task attributes. For details see:
|
||||
#
|
||||
# http://ant.apache.org/manual/CoreTasks/cvs.html
|
||||
#
|
||||
# Not all of the 'Cvs' task's attributes have corresponding Pants properties.
|
||||
# The following is a list of all valid CVS properties for Pants (and their
|
||||
# default values if applicable):
|
||||
#
|
||||
# cvs.compression.level
|
||||
# cvs.date
|
||||
# cvs.package
|
||||
# cvs.passfile=~/.cvspass
|
||||
# cvs.port=2401
|
||||
# cvs.root
|
||||
# cvs.rsh
|
||||
# cvs.tag
|
||||
#
|
||||
# Of these, only the 'cvs.root' property is required for CVS-based pbuilds.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# cvs.root=:pserver:anoncvs@borkbork.se:/cvsroot/bork
|
||||
# cvs.rsh=ssh
|
||||
# cvs.package=borkbork
|
||||
|
||||
69
apps/pants/pants/resources/pbuild.template.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
This is a template for Pants pbuilds. Pbuilds use standard Apache Ant syntax.
|
||||
For each target in the Public Interface section you must provide either an
|
||||
implementation or a stub. You may also add your own custom tasks and
|
||||
properties to this file. Be careful that none of your custom properties'
|
||||
names clash with the properties defined in pants/build.xml.
|
||||
-->
|
||||
|
||||
<project basedir="." default="build" name="name-of-pbuild-here">
|
||||
|
||||
<!-- ....................... Begin Public Interface ........................ -->
|
||||
|
||||
<!--
|
||||
When this target is called, the pbuild's sources and/or binaries have
|
||||
already been extracted/copied by Pants into the pbuild's working/
|
||||
subdirectory. This target must prepare those sources and/or binaries in
|
||||
the working/ subdirectory into deployable form, for example by building
|
||||
all necessary classes and jar files.
|
||||
|
||||
This target must not create or modify any files outside the pbuild's
|
||||
working/ subdirectory. (An automatic sandboxing mechanism should be added
|
||||
to Pants at some point.) It is however acceptable for a task called by
|
||||
'builddep' to modify files outside of this pbuild's working/ directory.
|
||||
-->
|
||||
<target name="build" depends="builddep" />
|
||||
|
||||
<!--
|
||||
Use this to call targets from other pbuilds, Ant buildfiles, Makefiles,
|
||||
etc. which perform tasks this pbuild's 'build' target depends on. If other
|
||||
pbuilds are called here, they must be called through the Pants interface
|
||||
or else it may leave Pants in an inconsistent state.
|
||||
|
||||
Most pbuilds probably won't need to implement this target.
|
||||
-->
|
||||
<target name="builddep" />
|
||||
|
||||
<!--
|
||||
This target must undo the actions performed by the 'build' target.
|
||||
-->
|
||||
<target name="clean" depends="depclean" />
|
||||
|
||||
<!--
|
||||
If the 'builddep' target is implemented, this target must be implemented
|
||||
to undo its actions.
|
||||
-->
|
||||
<target name="depclean" />
|
||||
|
||||
<!--
|
||||
This target must copy all deployable files generated by the 'build' target
|
||||
into the pbuild's dist/ subdirectory (for use by other pbuilds or Ant
|
||||
processes) or to their final deployment locations outside the pants/
|
||||
directory hierarchy. Note that the latter may require the user to gain
|
||||
superuser/admin privileges.
|
||||
-->
|
||||
<target name="dist" depends="build" />
|
||||
|
||||
<!--
|
||||
This target must remove all files from the pbuild's dist/ subdirectory
|
||||
and final deployment locations, reversing the actions of the 'dist'
|
||||
target. Note that removal of files from their final deployment locations
|
||||
may require the user to gain superuser/admin privileges.
|
||||
-->
|
||||
<target name="distclean" depends="clean" />
|
||||
|
||||
<!-- ........................ End Public Interface ......................... -->
|
||||
|
||||
</project>
|
||||
112
apps/pants/pbuilds/fortuna/pbuild.properties
Normal file
@@ -0,0 +1,112 @@
|
||||
# The properties defined in this file can be overridden on the command line by
|
||||
# passing them in as parameters like so:
|
||||
#
|
||||
# ant -Dpbuild=myapp -Dversion.recommended=2.0.5 install
|
||||
#
|
||||
# *** DO NOT DEFINE A PROPERTY BUT LEAVE ITS VALUE BLANK. PANTS WILL BREAK! ***
|
||||
|
||||
|
||||
# Recommended Package Version
|
||||
#
|
||||
# Set this property's value to the package version you want Pants to use for the
|
||||
# pbuild by default. The version string specified must match the version
|
||||
# substring from the package's filename if the filename contains a version
|
||||
# number.
|
||||
#
|
||||
# Comment out this property to force use of the latest available version.
|
||||
#
|
||||
# If the pbuild is CVS-based rather than package-based, this property must be
|
||||
# set to 'CVS'.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# version.recommended=2.0.4
|
||||
version.recommended=CVS
|
||||
|
||||
# Latest Package Version
|
||||
#
|
||||
# There are currently two ways to inform Pants of the latest version number for
|
||||
# your package.
|
||||
#
|
||||
# Method 1: Manually modify the property 'version.latest' to reflect the latest
|
||||
# version number.
|
||||
#
|
||||
# Method 2: Provide a URL for a page on the package's website and a regular
|
||||
# expression with which to parse it in order to extract the version
|
||||
# number of the latest available package. For this you must define the
|
||||
# properties 'version.latest.find.url', 'version.latest.find.regex',
|
||||
# and any regular expression engine mode flags needed. The pattern
|
||||
# defined must have exactly one capturing group to encapsulate the
|
||||
# version string, otherwise the operation will fail.
|
||||
#
|
||||
# You may use both methods, in which case the version number specified by Method
|
||||
# 1 will be used as the fallback value if Method 2 for some reason is
|
||||
# unsuccessful.
|
||||
#
|
||||
# If neither method is enabled here or they fail to return a valid value to
|
||||
# Pants, the 'ant update' operation for this pbuild may exit ungracefully unless
|
||||
# the pbuild is CVS-based (none of the version.latest.* properties are used by
|
||||
# CVS-based pbuilds).
|
||||
#
|
||||
# The following is a list of boolean properties for optional mode flags used by
|
||||
# the regular expression engine. Set a value of "true" for any you wish to use.
|
||||
#
|
||||
# version.latest.find.regex.canonicaleq - Enable canonical equivalence
|
||||
# version.latest.find.regex.caseinsensitive - Enable case-insensitive matching
|
||||
# version.latest.find.regex.comments - Permit whitespace and comments
|
||||
# version.latest.find.regex.dotall - Enable dotall mode
|
||||
# version.latest.find.regex.multiline - Enable multi-line mode
|
||||
# version.latest.find.regex.unicodecase - Enable Unicode-aware case folding
|
||||
# version.latest.find.regex.unixlines - Enable Unix lines mode
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# version.latest=5.1.2
|
||||
# version.latest.find.url=http://sourceforge.net/projects/jetty/
|
||||
# version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
|
||||
|
||||
|
||||
# Package URL
|
||||
#
|
||||
# Specify the URL pointing to the pbuild's package from here. The token
|
||||
# '${pbuild.version}' if used will automatically be expanded to the appropriate
|
||||
# version string.
|
||||
#
|
||||
# The package URL property is not used by CVS-based pbuilds.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# package.url=ftp://borkbork.se/bork-${pbuild.version}.tar.bz2
|
||||
# package.url=http://bork.borkbork.se/bork-${pbuild.version}-src.tar.gz
|
||||
|
||||
|
||||
# CVS Repository
|
||||
#
|
||||
# The values expected for CVS properties here are the same as those expected by
|
||||
# their corresponding Apache Ant 'Cvs' task attributes. For details see:
|
||||
#
|
||||
# http://ant.apache.org/manual/CoreTasks/cvs.html
|
||||
#
|
||||
# Not all of the 'Cvs' task's attributes have corresponding Pants properties.
|
||||
# The following is a list of all valid CVS properties for Pants (and their
|
||||
# default values if applicable):
|
||||
#
|
||||
# cvs.compression.level
|
||||
# cvs.date
|
||||
# cvs.package
|
||||
# cvs.passfile=~/.cvspass
|
||||
# cvs.port=2401
|
||||
# cvs.root
|
||||
# cvs.rsh
|
||||
# cvs.tag
|
||||
#
|
||||
# Of these, only the 'cvs.root' property is required for CVS-based pbuilds.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# cvs.root=:pserver:anoncvs@borkbork.se:/cvsroot/bork
|
||||
# cvs.rsh=ssh
|
||||
# cvs.package=borkbork
|
||||
cvs.root=:ext:anoncvs@savannah.gnu.org:/cvsroot/gnu-crypto
|
||||
cvs.rsh=ssh
|
||||
cvs.package=gnu-crypto
|
||||
127
apps/pants/pbuilds/fortuna/pbuild.xml
Normal file
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="build" name="fortuna-pbuild">
|
||||
|
||||
<property name="gnucrypt.base.dir" value="./working/gnu-crypto" />
|
||||
<property name="gnucrypt.etc.dir" value="${gnucrypt.base.dir}/etc" />
|
||||
<property name="gnucrypt.lib.dir" value="${gnucrypt.base.dir}/lib" />
|
||||
<property name="gnucrypt.object.dir" value="${gnucrypt.base.dir}/classes" />
|
||||
<property name="gnucrypt.base.crypto.object.dir" value="${gnucrypt.object.dir}/gnu/crypto" />
|
||||
<property name="gnucrypt.cipher.object.dir" value="${gnucrypt.base.crypto.object.dir}/cipher" />
|
||||
<property name="gnucrypt.hash.object.dir" value="${gnucrypt.base.crypto.object.dir}/hash" />
|
||||
<property name="gnucrypt.prng.object.dir" value="${gnucrypt.base.crypto.object.dir}/prng" />
|
||||
|
||||
<patternset id="fortuna.files">
|
||||
<include name="${gnucrypt.base.crypto.object.dir}/Registry.class" />
|
||||
<include name="${gnucrypt.prng.object.dir}/Fortuna*.class" />
|
||||
<include name="${gnucrypt.prng.object.dir}/BasePRNG.class" />
|
||||
<include name="${gnucrypt.prng.object.dir}/RandomEventListener.class" />
|
||||
<include name="${gnucrypt.prng.object.dir}/IRandom.class" />
|
||||
<include name="${gnucrypt.cipher.object.dir}/CipherFactory.class" />
|
||||
<include name="${gnucrypt.cipher.object.dir}/IBlockCipher.class" />
|
||||
<include name="${gnucrypt.hash.object.dir}/HashFactory.class" />
|
||||
<include name="${gnucrypt.hash.object.dir}/IMessageDigest.class" />
|
||||
</patternset>
|
||||
|
||||
<!--
|
||||
Add this when Fortuna tests are added to GNU Crypto, else write some
|
||||
-->
|
||||
<target name="-test" />
|
||||
|
||||
<!-- ....................... Begin Public Interface ........................ -->
|
||||
|
||||
<!--
|
||||
When this target is called, the pbuild's sources and/or binaries have
|
||||
already been extracted/copied by Pants into the pbuild's working/
|
||||
subdirectory. This target must prepare those sources and/or binaries in
|
||||
the working/ subdirectory into deployable form, for example by building
|
||||
all necessary classes and jar files.
|
||||
|
||||
This target must not create or modify any files outside the pbuild's
|
||||
working/ subdirectory. (An automatic sandboxing mechanism should be added
|
||||
to Pants at some point.) It is however acceptable for a task called by
|
||||
'builddep' to modify files outside of this pbuild's working/ directory.
|
||||
-->
|
||||
<target name="build" depends="builddep">
|
||||
<delete dir="./working/build" />
|
||||
<delete dir="./working/jartemp" />
|
||||
<mkdir dir="./working/build" />
|
||||
<mkdir dir="./working/jartemp/${gnucrypt.object.dir}" />
|
||||
<copy todir="./working/jartemp">
|
||||
<fileset dir=".">
|
||||
<patternset refid="fortuna.files" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<jar basedir="./working/jartemp/${gnucrypt.object.dir}" jarfile="./working/build/fortuna.jar">
|
||||
<manifest>
|
||||
<section name="fortuna">
|
||||
<attribute name="Implementation-Title" value="I2P Custom GNU Crypto Fortuna Library" />
|
||||
<attribute name="Implementation-Version" value="CVS HEAD" />
|
||||
<attribute name="Implementation-Vendor" value="Free Software Foundation" />
|
||||
<attribute name="Implementation-Vendor-Id" value="FSF" />
|
||||
<attribute name="Implementation-URL" value="http://www.gnu.org/software/gnu-crypto" />
|
||||
</section>
|
||||
</manifest>
|
||||
</jar>
|
||||
<delete dir="./working/jartemp" />
|
||||
</target>
|
||||
|
||||
<!--
|
||||
Use this to call targets from other pbuilds, Ant buildfiles, Makefiles,
|
||||
etc. which perform tasks this pbuild's 'build' target depends on. If other
|
||||
pbuilds are called here, they must be called through the Pants interface
|
||||
or else it may leave Pants in an inconsistent state.
|
||||
|
||||
Most pbuilds probably won't need to implement this target.
|
||||
-->
|
||||
<target name="builddep">
|
||||
<ant dir="${gnucrypt.base.dir}" target="jar" />
|
||||
</target>
|
||||
|
||||
<!--
|
||||
This target must undo the actions performed by the 'build' target.
|
||||
-->
|
||||
<target name="clean" depends="depclean">
|
||||
<delete dir="./working/jartemp" />
|
||||
</target>
|
||||
|
||||
<!--
|
||||
If the 'builddep' target is implemented, this target must be implemented
|
||||
to undo its actions.
|
||||
-->
|
||||
<target name="depclean">
|
||||
<!--
|
||||
Annoyingly the GNU Crypto distclean task called here doesn't clean
|
||||
*all* derived files from java/gnu-crypto/lib like it should (because
|
||||
a couple of lines are commented out).....
|
||||
-->
|
||||
<ant dir="${gnucrypt.base.dir}" target="distclean" />
|
||||
<!--
|
||||
.....and so we mop up the rest ourselves.
|
||||
-->
|
||||
<delete dir="${gnucrypt.lib.dir}" />
|
||||
</target>
|
||||
|
||||
<!--
|
||||
This target must copy all deployable files generated by the 'build' target
|
||||
into the pbuild's dist/ subdirectory (for use by other pbuilds or Ant
|
||||
processes) or to their final deployment locations outside the pants/
|
||||
directory hierarchy. Note that the latter may require the user to gain
|
||||
superuser/admin privileges.
|
||||
-->
|
||||
<target name="dist" depends="build">
|
||||
<copy todir="./dist/fortuna.jar" file="./working/build/fortuna.jar" />
|
||||
</target>
|
||||
|
||||
<!--
|
||||
This target must remove all files from the pbuild's dist/ subdirectory
|
||||
and final deployment locations, reversing the actions of the 'dist'
|
||||
target. Note that removal of files from their final deployment locations
|
||||
may require the user to gain superuser/admin privileges.
|
||||
-->
|
||||
<target name="distclean" depends="clean">
|
||||
<delete file="./dist/fortuna.jar" />
|
||||
</target>
|
||||
|
||||
<!-- ........................ End Public Interface ......................... -->
|
||||
|
||||
</project>
|
||||
112
apps/pants/pbuilds/jetty/pbuild.properties
Normal file
@@ -0,0 +1,112 @@
|
||||
# The properties defined in this file can be overridden on the command line by
|
||||
# passing them in as parameters like so:
|
||||
#
|
||||
# ant -Dpbuild=myapp -Dversion.recommended=2.0.5 install
|
||||
#
|
||||
# *** DO NOT DEFINE A PROPERTY BUT LEAVE ITS VALUE BLANK. PANTS WILL BREAK! ***
|
||||
|
||||
|
||||
# Recommended Package Version
|
||||
#
|
||||
# Set this property's value to the package version you want Pants to use for the
|
||||
# pbuild by default. The version string specified must match the version
|
||||
# substring from the package's filename if the filename contains a version
|
||||
# number.
|
||||
#
|
||||
# Comment out this property to force use of the latest available version.
|
||||
#
|
||||
# If the pbuild is CVS-based rather than package-based, this property must be
|
||||
# set to 'CVS'.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# version.recommended=2.0.4
|
||||
version.recommended=5.1.2
|
||||
|
||||
# Latest Package Version
|
||||
#
|
||||
# There are currently two ways to inform Pants of the latest version number for
|
||||
# your package.
|
||||
#
|
||||
# Method 1: Manually modify the property 'version.latest' to reflect the latest
|
||||
# version number.
|
||||
#
|
||||
# Method 2: Provide a URL for a page on the package's website and a regular
|
||||
# expression with which to parse it in order to extract the version
|
||||
# number of the latest available package. For this you must define the
|
||||
# properties 'version.latest.find.url', 'version.latest.find.regex',
|
||||
# and any regular expression engine mode flags needed. The pattern
|
||||
# defined must have exactly one capturing group to encapsulate the
|
||||
# version string, otherwise the operation will fail.
|
||||
#
|
||||
# You may use both methods, in which case the version number specified by Method
|
||||
# 1 will be used as the fallback value if Method 2 for some reason is
|
||||
# unsuccessful.
|
||||
#
|
||||
# If neither method is enabled here or they fail to return a valid value to
|
||||
# Pants, the 'ant update' operation for this pbuild may exit ungracefully unless
|
||||
# the pbuild is CVS-based (none of the version.latest.* properties are used by
|
||||
# CVS-based pbuilds).
|
||||
#
|
||||
# The following is a list of boolean properties for optional mode flags used by
|
||||
# the regular expression engine. Set a value of "true" for any you wish to use.
|
||||
#
|
||||
# version.latest.find.regex.canonicaleq - Enable canonical equivalence
|
||||
# version.latest.find.regex.caseinsensitive - Enable case-insensitive matching
|
||||
# version.latest.find.regex.comments - Permit whitespace and comments
|
||||
# version.latest.find.regex.dotall - Enable dotall mode
|
||||
# version.latest.find.regex.multiline - Enable multi-line mode
|
||||
# version.latest.find.regex.unicodecase - Enable Unicode-aware case folding
|
||||
# version.latest.find.regex.unixlines - Enable Unix lines mode
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# version.latest=5.1.2
|
||||
# version.latest.find.url=http://sourceforge.net/projects/jetty/
|
||||
# version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
|
||||
version.latest=5.1.2
|
||||
version.latest.find.url=http://sourceforge.net/projects/jetty/
|
||||
version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
|
||||
|
||||
# Package URL
|
||||
#
|
||||
# Specify the URL pointing to the pbuild's package from here. The token
|
||||
# '${pbuild.version}' if used will automatically be expanded to the appropriate
|
||||
# version string.
|
||||
#
|
||||
# The package URL property is not used by CVS-based pbuilds.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# package.url=ftp://borkbork.se/bork-${pbuild.version}.tar.bz2
|
||||
# package.url=http://bork.borkbork.se/bork-${pbuild.version}-src.tar.gz
|
||||
package.url=http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-${pbuild.version}.zip
|
||||
|
||||
# CVS Repository
|
||||
#
|
||||
# The values expected for CVS properties here are the same as those expected by
|
||||
# their corresponding Apache Ant 'Cvs' task attributes. For details see:
|
||||
#
|
||||
# http://ant.apache.org/manual/CoreTasks/cvs.html
|
||||
#
|
||||
# Not all of the 'Cvs' task's attributes have corresponding Pants properties.
|
||||
# The following is a list of all valid CVS properties for Pants (and their
|
||||
# default values if applicable):
|
||||
#
|
||||
# cvs.compression.level
|
||||
# cvs.date
|
||||
# cvs.package
|
||||
# cvs.passfile=~/.cvspass
|
||||
# cvs.port=2401
|
||||
# cvs.root
|
||||
# cvs.rsh
|
||||
# cvs.tag
|
||||
#
|
||||
# Of these, only the 'cvs.root' property is required for CVS-based pbuilds.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# cvs.root=:pserver:anoncvs@borkbork.se:/cvsroot/bork
|
||||
# cvs.rsh=ssh
|
||||
# cvs.package=borkbork
|
||||
|
||||
89
apps/pants/pbuilds/jetty/pbuild.xml
Normal file
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="jetty">
|
||||
|
||||
<!-- make this generic, place variables in properties file -->
|
||||
|
||||
<target name="all" depends="build"
|
||||
description="Run the build target" />
|
||||
|
||||
<target name="assignProperties" if="group.0">
|
||||
<property name="latest.jetty.version" value="${group.1}" />
|
||||
<available property="jetty.package.available" file="jetty-${latest.jetty.version}.zip" />
|
||||
<available property="jetty.package.unpacked.available" file="jettypkg/jetty-${latest.jetty.version}" />
|
||||
<echo message="Properties assigned" />
|
||||
</target>
|
||||
|
||||
<target name="build" depends="init, unpackJettyPackage" if="latest.jetty.version"
|
||||
description="Download latest Jetty package and copy needed libs to jettylib/">
|
||||
<property name="unpack.dir" value="jettypkg/jetty-${latest.jetty.version}" />
|
||||
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/ant.jar" />
|
||||
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/jasper-compiler.jar" />
|
||||
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/jasper-runtime.jar" />
|
||||
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/xercesImpl.jar" />
|
||||
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/xml-apis.jar" />
|
||||
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/extra/lib/org.mortbay.jetty-jdk1.2.jar" />
|
||||
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/lib/javax.servlet.jar" />
|
||||
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/lib/org.mortbay.jetty.jar" />
|
||||
<copy todir="jettylib" overwrite="true">
|
||||
<fileset dir="${unpack.dir}/ext" includes="xmlParserAPIs*.jar" />
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<target name="builddep"
|
||||
description="Build the custom helper Ant task for this buildfile">
|
||||
<mkdir dir="java/build"/>
|
||||
<javac srcdir="./java/src" source="1.3" target="1.3" deprecation="on" destdir="./java/build" />
|
||||
</target>
|
||||
|
||||
<target name="clean"
|
||||
description="Remove temp files and zip only; jettypkg/ requires manual deletion">
|
||||
<echo message="Not actually deleting the Jetty package directory since it's so large" />
|
||||
<delete>
|
||||
<fileset dir="." includes="*.zip jettytemp.html parsed.temp" />
|
||||
</delete>
|
||||
</target>
|
||||
|
||||
<target name="cleandep"
|
||||
description="Remove custom helper Ant task">
|
||||
<delete dir="java/build" />
|
||||
</target>
|
||||
|
||||
<target name="compile" />
|
||||
|
||||
<target name="distclean" depends="clean"
|
||||
description="Remove temp files, zip and jettylib/ contents" >
|
||||
<delete>
|
||||
<fileset dir="jettylib" includes="*.jar"/>
|
||||
</delete>
|
||||
</target>
|
||||
|
||||
<target name="fetchJettyPackage" if="latest.jetty.version" unless="jetty.package.available">
|
||||
<echo message="The Jetty libs are not necessary for using I2P, but are used by some" />
|
||||
<echo message="applications on top of I2P such as the routerconsole." />
|
||||
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-${latest.jetty.version}.zip" verbose="true" dest="jetty-${latest.jetty.version}.zip" />
|
||||
</target>
|
||||
|
||||
<target name="init" depends="builddep">
|
||||
<echo message="Checking SourceForge for latest Jetty version....." />
|
||||
<get src="http://sourceforge.net/projects/jetty/" dest="jettytemp.html" verbose="true" />
|
||||
<taskdef name="match" classname="net.i2p.pants.MatchTask" classpath="../../lib/pants.jar" />
|
||||
<match input="jettytemp.html"
|
||||
output="parsed.temp"
|
||||
regex="Stable.+?Jetty-(.+?)</A>"
|
||||
/>
|
||||
<loadproperties srcFile="parsed.temp" />
|
||||
<antcall target="assignProperties" />
|
||||
</target>
|
||||
|
||||
<target name="jar" />
|
||||
|
||||
<target name="showlatest" depends="init"
|
||||
description="Display latest version number for Jetty">
|
||||
<echo message="Latest Jetty version: ${latest.jetty.version}" />
|
||||
</target>
|
||||
|
||||
<target name="unpackJettyPackage" depends="fetchJettyPackage" if="latest.jetty.version" unless="jetty.package.unpacked.available">
|
||||
<mkdir dir="jettypkg" />
|
||||
<unzip src="jetty-${latest.jetty.version}.zip" dest="jettypkg" />
|
||||
</target>
|
||||
</project>
|
||||
2
apps/pants/world
Normal file
@@ -0,0 +1,2 @@
|
||||
version.using.fortuna=CVS
|
||||
version.using.jetty=5.1.2
|
||||
@@ -5,12 +5,19 @@
|
||||
** License: Public Domain
|
||||
** Date: 11 May 2004
|
||||
**
|
||||
** Revised: 07 Sep 2004
|
||||
** Changes:
|
||||
** Proxy recursion disabled by default (strict)
|
||||
** Password Authentication for session commands
|
||||
** Support for http://path?i2paddresshelper=BASE64
|
||||
** Support for http://i2p/BASE64/path syntax
|
||||
** Revised: 17 May 2004
|
||||
** Added:
|
||||
** Changes:
|
||||
** Ability for the user to control the proxy
|
||||
** status on a per browser-session basis.
|
||||
********************************************************/
|
||||
|
||||
|
||||
/* C O N F I G U R A T I O N
|
||||
*/
|
||||
|
||||
@@ -53,12 +60,13 @@ var proxyStatus = "auto";
|
||||
** could change your status mode by linking to command keywords...
|
||||
** eg. <img src="i2p.off" ...
|
||||
** This is why the default setting for statusKeyword is "limited", which only
|
||||
** allows you to set the proxy status to "on".
|
||||
** allows you to set the proxy status to "on". See also keywordAuthPassword.
|
||||
**
|
||||
** [1] "all" => All proxy status urls are available.
|
||||
** '-> i2p.on, i2p.off, i2p.auto (respective to proxyStatus settings)
|
||||
** '-> WARNING: Setting "all" is a big risk to your anonymity!
|
||||
**
|
||||
** '-> In this mode it is highly recommended you set an AuthPassword too!
|
||||
**
|
||||
** [2] "limited" => Only i2p.on is available..
|
||||
** '-> This setting lasts for the duration of the browser setting.
|
||||
** '-> You have to close your browser in order to revert to
|
||||
@@ -70,7 +78,7 @@ var proxyStatus = "auto";
|
||||
**
|
||||
*/
|
||||
|
||||
var statusKeyword = "limited";
|
||||
var statusKeyword = "all";
|
||||
|
||||
/*
|
||||
** By default if proxyStatus is set to "auto" the config script
|
||||
@@ -84,7 +92,24 @@ var statusKeyword = "limited";
|
||||
** sites will be rejected if the i2p proxy is offline. (safest)
|
||||
*/
|
||||
|
||||
var strict = false;
|
||||
var strict = true;
|
||||
|
||||
/*
|
||||
** By setting an authentication password, all activated session keywords
|
||||
** will require the addition of a password to prevent malicious sites from
|
||||
** hijacking your proxy settings. ie. <img src="i2p.off" ...
|
||||
** Users should append whatever they set here to any command keywords
|
||||
** they use.
|
||||
** eg. i2p.on.passw0rd
|
||||
** If left blank, authentication is ignored - it is recommended that
|
||||
** you use "limited" statusKeyword mode if you choose not to require a password.
|
||||
** If you do require this feature then you should replace the default "passw0rd" with
|
||||
** one of your own (recommend at least 8 letters with a case-sensitive alpha-numeric
|
||||
** mix of characters).
|
||||
**
|
||||
*/
|
||||
|
||||
var keywordAuthPassword = "passw0rd";
|
||||
|
||||
|
||||
/* E N D C O N F I G U R A T I O N
|
||||
@@ -96,7 +121,13 @@ var strict = false;
|
||||
*/
|
||||
|
||||
if (strict == false) {
|
||||
i2pProxy = i2pProxy + "; " + normal;
|
||||
i2pProxy = i2pProxy + "; " + normal;
|
||||
}
|
||||
|
||||
/*Check for User Authentication Password.
|
||||
*/
|
||||
if (keywordAuthPassword != "") {
|
||||
keywordAuthPassword = "." + keywordAuthPassword;
|
||||
}
|
||||
|
||||
/* This function gets called every time a url is submitted
|
||||
@@ -108,16 +139,17 @@ function FindProxyForURL(url, host) {
|
||||
*/
|
||||
|
||||
if (statusKeyword != "off") {
|
||||
if (host == "i2p.off" && statusKeyword == "all") {
|
||||
if (host == "i2p.off" + keywordAuthPassword && statusKeyword == "all") {
|
||||
/*Proxy is bypassed - outweb available only
|
||||
*/
|
||||
proxyStatus = "off";
|
||||
} else if (host == "i2p.auto" && statusKeyword == "all") {
|
||||
} else if (host == "i2p.auto" + keywordAuthPassword && statusKeyword == "all") {
|
||||
/* Proxy is used only for .i2p hosts otherwise
|
||||
** '-> browse as normal.
|
||||
*/
|
||||
proxyStatus = "auto";
|
||||
} else if (host == "i2p.on" && statusKeyword == "limited") {
|
||||
} else if (host == "i2p.on" + keywordAuthPassword && (statusKeyword == "limited" ||
|
||||
statusKeyword == "all" )) {
|
||||
/* Only I2P traffic is accepted.
|
||||
*/
|
||||
proxyStatus = "on";
|
||||
@@ -135,13 +167,14 @@ function FindProxyForURL(url, host) {
|
||||
}
|
||||
|
||||
host = host.toLowerCase();
|
||||
/* check tld for "i2p" - if found then redirect
|
||||
/* check tld for "i2p" or oOo's new "i2paddresshelper" syntax - if found then redirect
|
||||
** '-> request to the i2p proxy
|
||||
*/
|
||||
|
||||
if (shExpMatch(host, "*.i2p")) { // seems more reliable than:
|
||||
return i2pProxy; // dnsDomainIs(host, ".i2p") ||
|
||||
} else { // i2pRegex.test(host)
|
||||
if (url.match(/^http:\/\/i2p\/[a-zA-Z0-9\-\~]{516}|i2paddresshelper=/i) ||
|
||||
shExpMatch(host, "*.i2p")) { // seems more reliable than:
|
||||
return i2pProxy; // dnsDomainIs(host, ".i2p") ||
|
||||
} else { // i2pRegex.test(host)
|
||||
return normal;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
apps/q/doc/client.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
26
apps/q/doc/diagrams.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q System Diagrams</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Q Diagrams</h1>
|
||||
Informal system diagrams of Q network, hubs and clients.
|
||||
<center>
|
||||
<hr>
|
||||
<img src="overall.jpg">
|
||||
<hr>
|
||||
<img src="client.jpg">
|
||||
<hr>
|
||||
<img src="hub.jpg">
|
||||
</center>
|
||||
<hr>
|
||||
|
||||
<address><a href="mailto:aum@mail.i2p">aum</a></address>
|
||||
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Apr 18 14:06:02 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
BIN
apps/q/doc/hub.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
80
apps/q/doc/index.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Quartermaster - I2P Distributed File Store</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<center>
|
||||
<h1>Quartermaster<br>an I2P Distributed File Store</h1>
|
||||
|
||||
<h3>STATUS<h3>
|
||||
<i>Whole new (incompatible) version currently in development;
|
||||
ETA for release approx 4-7 days;
|
||||
view screenshots <a href="screenshots.html">here</a>
|
||||
</i>
|
||||
<br>
|
||||
<hr>
|
||||
|
||||
<small>
|
||||
<a href="manual/index.html">User Manual</a> |
|
||||
<a href="spec/index.html">Protocol Spec</a> |
|
||||
<a href="metadata.html">Metadata Spec</a> |
|
||||
<a href="diagrams.html">Q Pr0n (diagrams)</a> |
|
||||
<a href="api/index.html">API Spec</a> |
|
||||
<a href="qnoderefs.txt">qnoderefs.txt</a> |
|
||||
Full Download |
|
||||
Updated jar
|
||||
</small>
|
||||
</center>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Intro</h2>
|
||||
|
||||
Quartermaster, or Q for short, is a distributed file storage framework for I2P.
|
||||
|
||||
<h2>Features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Now features 'QSites' - the Q equivalent of Freenet freesites,
|
||||
static websites which are retrievable even if author is offline</li>
|
||||
<li>Easy web interface - interact with Q (and view/insert QSites)
|
||||
from your web browser</li>
|
||||
<li>Maximum expectations of content retrievability</li>
|
||||
<li>Content security akin to Freenet CHK and SSK keys</li>
|
||||
<li>Powerful, flexible search engine</li>
|
||||
<li>Comfortably accommodates both permanent and transient
|
||||
nodes without significant network disruption (for instance,
|
||||
no flooding of the I2P network with futile
|
||||
calls to offline nodes)</li>
|
||||
<li>Rapid query resolution, due to distributed catalogue
|
||||
mirroring which eliminates all in-network query traffic</li>
|
||||
<li>Modular, extensible architecture</li>
|
||||
<li>Simple interfaces for 3rd-party app developers</li>
|
||||
<li>Is custom-designed and built around I2P, so no duplication of
|
||||
I2P's encryption/anonymity features</li>
|
||||
<li>Simple XML-RPC interface for all inter-node communication, makes it easy to
|
||||
implement user-level clients in any language; also allows alternative
|
||||
implementations of core server and/or client nodes.</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Status</h2>
|
||||
|
||||
Q is presently under development, and a test release is expected soon.
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Architecture</h2>
|
||||
|
||||
Refer to the <a href="spec/index.html">Protocol Specification</a> for more information.
|
||||
|
||||
<hr>
|
||||
<!-- Created: Sat Mar 26 11:09:12 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Apr 18 18:55:19 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
805
apps/q/doc/manual/index.html
Normal file
@@ -0,0 +1,805 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q User/Programmer Manual</title>
|
||||
<style type="text/css">
|
||||
<!--
|
||||
td { vertical-align: top; }
|
||||
code { font-family: courier, monospace; font-weight: bold }
|
||||
-->
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="font-family: arial, helvetica, sans-serif">
|
||||
<center>
|
||||
<h1>Q User/Programmer Manual</h1>
|
||||
|
||||
<i>A brief but hopefully easy guide to installing and using the Q distributed file
|
||||
store within the I2P network</i>
|
||||
|
||||
<br><br>
|
||||
<i>(Return to <a href="../index.html">Q Homepage</a>)</i>
|
||||
<br>
|
||||
<br>
|
||||
<small>
|
||||
<a href="#intro">Introduction</a> |
|
||||
<a href="#checklist">Checklist</a> |
|
||||
<a href="#serverorclient">Server?orClient?</a> |
|
||||
<a href="#walkthrough">Walkthrough</a> |
|
||||
<a href="#server">Server Nodes</a> |
|
||||
<a href="#qmgr">About QMgr</a> |
|
||||
<a href="#contact">Contact us</a>
|
||||
</small>
|
||||
</center>
|
||||
|
||||
<a name="intro"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>1. Introduction</h2>
|
||||
|
||||
<blockquote>
|
||||
Q is a distributed Peer2Peer file storage/retrieval network that aims to deliver optimal
|
||||
performance by respecting the properties of the I2P network.<br>
|
||||
<br>
|
||||
This manual serves as a 'walkthrough' guide, to take you through the steps from initial
|
||||
download, to everyday usage. It also provides information for the benefit of higher-level
|
||||
client application authors.
|
||||
</blockquote>
|
||||
|
||||
<a name="checklist"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>2. Preliminary Checklist</h2>
|
||||
|
||||
<blockquote>
|
||||
OK, we assume here that you've already cracked the tarball, and are looking at
|
||||
the distribution files.<br>
|
||||
<br>
|
||||
In order to get Q set up and running, you'll need:
|
||||
<ol>
|
||||
<li>An I2P router installed, set up and (permanently or transiently) running</li>
|
||||
<li>Your system shell set up with at the environment variables:
|
||||
<ul>
|
||||
<li><b>CLASSPATH</b> - this should include:
|
||||
<ul>
|
||||
<li>The regular I2P jar files and 3rd party support jar files (eg <b>i2p.jar</b>,
|
||||
<b>i2ptunnel.jar</b>, <b>streaming.jar</b>,
|
||||
<b>mstreaming.jar</b>, <b>jbigi.jar</b>)</li>
|
||||
<li>Apache's XML-RPC support jarfile - included in this Q distro as
|
||||
<b>xmlrpc.jar</b></li>
|
||||
<li>Aum's jarfile <b>aum.jar</b>, which includes Q and all needed support code</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>PATH</b> - your execution search path <b><i>must</i></b> include the directory
|
||||
in which your main java VM execution program (<b>java</b>, or on windows systems,
|
||||
<b>java.exe</b>) resides.<br>
|
||||
<b>NOTE</b> - if <b>java[.exe]</b> is not on your <b>PATH</b>, then Q <i>will
|
||||
not run</i>.</li>
|
||||
</ul>
|
||||
</ol>
|
||||
</blockquote>
|
||||
|
||||
<a name="serverorclient"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>3. Q Server or Q Client?</h2>
|
||||
|
||||
<blockquote>
|
||||
Nearly everyone will want to run a <b>Q Client Node</b>.<br>
|
||||
<Br>
|
||||
It is only client nodes which provide users with full access to the Q network.<br>
|
||||
<br>
|
||||
However, if you have a (near-) permanently running I2P Router, and you're a kind and
|
||||
generous soul, you might <i>also</i> be willing to run a <b>Q Server Node</b> in addition
|
||||
to your <b>Q Client Node</b>.<br>
|
||||
<br>
|
||||
If you do choose to run a server node, you'll be expected to keep it running as near as
|
||||
possible to 24/7. While transience of client nodes - frequent entering and leaving the
|
||||
Q network - causes little or no disruption, transience of server nodes can significantly
|
||||
impair Q's usability for everyone, particularly if this transience occurs frequently amongst
|
||||
more than the smallest percentage of the server node pool.<br>
|
||||
<br>
|
||||
Until you're feeling well "settled in" with Q, your best approach is to just run a
|
||||
client node for now, and add a server node later when you feel ready.<br>
|
||||
</blockquote>
|
||||
|
||||
<a name="walkthrough"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>4. Q Walkthrough</h2>
|
||||
|
||||
<h3>4.1. Introduction</h3>
|
||||
|
||||
<blockquote>
|
||||
This chapter discusses the deployment and usage of a Q Client Node, and will take you
|
||||
through the steps of:
|
||||
<ol>
|
||||
<li>Double-checking that you've met the installation requirements</li>
|
||||
<li>Launching a Q Client Node</li>
|
||||
<li>Verifying that your Q Client Node is running</li>
|
||||
<li>If your node fails to launch, figuring out why</li>
|
||||
<li>Importing one or more noderefs into your node</li>
|
||||
<li>Observing that your node is discovering other nodes on the network</li>
|
||||
<li>Observing that your node is discovering content on the network</li>
|
||||
<li>Searching for items of content that match chosen criteria</li>
|
||||
<li>Retrieving stuff from the network</li>
|
||||
<li>Inserting stuff to the network</li>
|
||||
<li>Shutting down your client node</li>
|
||||
</ol>
|
||||
Setup and running of Q Server Nodes will be discussed in a later chapter.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.2. Verify Your Q Installation Is Correct</h3>
|
||||
|
||||
<blockquote>
|
||||
Ensure that all the needed I2P jarfiles, as well as <b>xmlrpc.jar</b> and
|
||||
Q's very own <b>aum.jar</b> are correctly listed in your <b>CLASSPATH</b> environment
|
||||
varaible, and your main java launcher is correctly listed in your <b>PATH</b> environment
|
||||
variable.<br>
|
||||
<br>
|
||||
Typically, you will likely copy the jarfiles <b>aum.jar</b> and <b>xmlrpc.jar</b>
|
||||
into the <b>lib/</b> subdirectory of your I2P router installation, along with all
|
||||
the other I2P jar files. Wherever you choose to put these files, make sure they're
|
||||
correctly listed in your <b>CLASSPATH</b>.
|
||||
<br>
|
||||
Also, you'll want to add execute permission to your <b>qmgr</b> (or <b>qmgr.bat</b>)
|
||||
wrapper script, and copy it into one of the directories listed in your <b>PATH</b>
|
||||
environment variable.<br>
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.3. Get Familiar With qmgr</h3>
|
||||
|
||||
<blockquote>
|
||||
<b>qmgr</b> (or <b>qmgr.bat</b>) is a convenience wrapper script to save your
|
||||
sore fingers from needless typing. It's just a wrapper which passes arguments
|
||||
to the java command <b><code>java net.i2p.aum.q.QMgr</code></b><br>
|
||||
<br>
|
||||
You can verify you've set up qmgr correctly with the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr help</pre></code></blockquote>
|
||||
This displays a brief summary of qmgr commands. On the other hand, the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr help verbose</pre></code></blockquote>
|
||||
floods your terminal window with a detailed explanation of all the qmgr commands
|
||||
and their arguments.<br>
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.4. Running A Q Client Node For The First Time</h3>
|
||||
|
||||
<blockquote>
|
||||
Provided you've successfully completed the preliminaries, you can launch your
|
||||
Q Client Node with the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr start</pre></code></blockquote>
|
||||
|
||||
All going well, you should have a Q Client Node now running in background.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.5. Verify that your Q Client Node is actually Running</h3>
|
||||
|
||||
<blockquote>
|
||||
After typed the <b>qmgr start</b> command, you will see little or no
|
||||
evidence that Q is actually running.<br>
|
||||
<br>
|
||||
You can test if the node is actually up by typing the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr status</pre></code></blockquote>
|
||||
If your Q Client Node is running, this <b>status</b> command should produce
|
||||
something like:
|
||||
<blockquote><code><pre>
|
||||
Pinging node at '/home/myusername/.quartermaster_client'...
|
||||
Node Ping:
|
||||
status=ok
|
||||
numPeers=0
|
||||
dest=-3LQaE215uIYwl-DsirnzXGQBI31EZQj9u~xx45E823WqjN5i2Umi37GPFTWc8KyislDjF37J7jy5newLUp-qrDpY7BZum3bRyTXo3Udl8a3sUjuu4qR5oBEWFfoghQiqDGYDQyJV9Rtz7DEGaKHGlhtoGsAYRXGXEa8a43T2llqZx2fqaXs~836g8t6sLZjryA5A9fpq98nE5lT0hcTalPieFpluJVairZREXpUiAUmGHG7wAIjF6iszXLEHSZ8Qc622Xgwy0d1yrPojL2yhZ64o05aueYcr~xNCiFxYoHyEJO3XYmkx~q-W-mzS3nn6pRevRda74MnX1~3fFDZ0u~OG6cLZoFkWgnxrwrWGFUUVMR87Yz251xMCKJAX6zErcoGjGFpqGZsWxl4~yq7yfkjPnq3GuTxp2cB75bRAOZRIAieqBOVJDEodFYW5amCinu4AxYE7G1ezz4ghqHFe~0yaAdO74Q1XoUny138YT6P33oNOOlISO1cAAAA
|
||||
uptime=4952
|
||||
load=0.0
|
||||
id=6LVZ9-~GgJJ52WUF1fLHt3UnH50TnXSoPQXy7WZ4GA=
|
||||
numLocalItems=47
|
||||
numRemoteItems=2173</pre></code></blockquote>
|
||||
|
||||
If you see something like this, then smile, because Q is now up on your system.<br>
|
||||
<br>
|
||||
If the node launch failed, you might see something like:
|
||||
<blockquote><code><pre>
|
||||
Pinging node at '/home/myusername/.quartermaster_client'...
|
||||
java.io.IOException: Connection refused
|
||||
at org.apache.xmlrpc.XmlRpcClient$Worker.execute(Unknown Source)
|
||||
at org.apache.xmlrpc.XmlRpcClient.execute(Unknown Source)
|
||||
at net.i2p.aum.q.QMgr.doStatus(QMgr.java:310)
|
||||
at net.i2p.aum.q.QMgr.execute(QMgr.java:813)
|
||||
at net.i2p.aum.q.QMgr.main(QMgr.java:869)
|
||||
Failed to ping node</pre></code></blockquote>
|
||||
This indicates that your Q client node has either crashed, or failed to launch in the
|
||||
first place.<br>
|
||||
<br>
|
||||
If you're having trouble like this, you might like to try running your Q client node
|
||||
in foreground, instead of spawning it off into background.<br>
|
||||
<br>
|
||||
The command to run a Q client node in foreground is:
|
||||
<blockquote><code><pre>
|
||||
qmgr foreground</pre></code></blockquote>
|
||||
You should see some meaningless startup messages, and no return to your shell prompt.<br>
|
||||
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.6. Diversion - Q Storage Directories</h3>
|
||||
|
||||
<blockquote>
|
||||
By default, when you run a Q Client Node, it creates a datastore directory tree
|
||||
at <b>~/.quartermaster_client</b>. (Windows users note - you'll find this directory
|
||||
wherever your user home directory is - this depends on what version of Windows
|
||||
you have installed).<br>
|
||||
<br>
|
||||
Within this directory tree, you should see a file called <b>node.log</b>, which
|
||||
will contain various debug log messages, and can help you to rectify any problems
|
||||
with your Q installation. If you hit a wall and can't rectify the problems
|
||||
yourself, you should send this file to the Q author (aum).<br>
|
||||
<br>
|
||||
It's possible to run your Q node from another directory, by passing that directory
|
||||
as a <b>-dir <path></b> argument to the
|
||||
<b>qmgr</b> <b>start</b>, <b>foreground</b> and <b>stop</b>
|
||||
commands. See <b>qmgr help verbose</b> for more information.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.7. Importing a Noderef</h3>
|
||||
|
||||
<blockquote>
|
||||
Note from the prior <b>qmgr status</b> command the line:
|
||||
<blockquote><code><pre>
|
||||
numPeers=0</pre></code></blockquote>
|
||||
This means that your Q client node is running standalone, and doesn't have any contact
|
||||
with any Q network. As such, your node is effectively useless. We need to hook up
|
||||
your node with other nodes in the Q network.<br>
|
||||
<br>
|
||||
Q doesn't ship with any means for new client nodes to automatically connect to any Q
|
||||
server nodes. This is deliberate.<br>
|
||||
<br>
|
||||
In all likelihood, there will be one 'main' Q network running within I2P, largely
|
||||
based around the author's own Q server node, and most people will likely want to
|
||||
use this Q network. But the author doesn't want to stop other people running their
|
||||
own private Q networks, for whatever purpose has meaning for them.
|
||||
|
||||
<blockquote><i><small>
|
||||
<hr>
|
||||
This is especially relevant for Q as opposed to Freenet. With Freenet, there's
|
||||
no way for a user to know of the existence of any item of content without
|
||||
first being given its 'key'. However, since Q works with published catalogs,
|
||||
any user can know everything that's available on a Q network, which might
|
||||
not be desirable to those wishing to share content in a private situation.<br>
|
||||
<Br>
|
||||
The Q author anticipates, and warmly supports, people running their own
|
||||
private Q networks within I2P, in addition to accessing the mainstream
|
||||
'official' Q network.<br>
|
||||
<br>
|
||||
The way Q is designed and implemented, there is no way for anyone, including
|
||||
Q's author, to know of the existence of anyone else's private Q network.
|
||||
It is beyond the author's control, (and thus arguably the author's
|
||||
legal responsibility), what private Q networks people set up, and what
|
||||
kind of content is trafficked on these networks. This claim of plausible
|
||||
deniability on the part of Q's author parallels that of a hardware retailer
|
||||
denying responsibility for what people do with tools that they purchase.
|
||||
<hr>
|
||||
</small>
|
||||
</i></blockquote>
|
||||
|
||||
Ok, getting back on topic - your brand new virgin Q client node is useless and lonely,
|
||||
and desperately needs some Q server nodes to talk to. So let's hook up your node to
|
||||
the mainstream Q network.<br>
|
||||
<br>
|
||||
You'll need to get one or more 'noderefs' for Q server nodes.<br>
|
||||
<br>
|
||||
There's nothing fancy about a Q noderef. It's just a regular I2P 'destination', with
|
||||
which your Q Client Node can connect with a Q Server Node.<br>
|
||||
<br>
|
||||
A 'semi-official' list of noderefs for the mainstream Q network can be downloaded
|
||||
from the url: <a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.<br>
|
||||
<br>
|
||||
Download this file, save it as (say) <b>qnoderefs.txt</b>. (Alternatively, if you're
|
||||
wanting to subscribe into a private Q network, then get a noderef for at least one
|
||||
of that network's server nodes from someone on that network who trusts you).<br>
|
||||
<br>
|
||||
Import these noderefs into your Q client node via the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr addref qnoderefs.txt</pre></code></blockquote>
|
||||
If all goes well, you should see no output from this command, or (possibly) a brief
|
||||
line or two suggesting success.<br>
|
||||
<br>
|
||||
Your client node is now subscribed into the Q network of your choice. Verify this
|
||||
with the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr status</pre></code></blockquote>
|
||||
In the output from that command, you should see the <b>numPeers=</b> line showing at least
|
||||
1 peer.<br>
|
||||
<br>
|
||||
If there is more than one Q Server Node on the Q network you've just subscribed to,
|
||||
then your local node should sooner or later discover all these server nodes, and
|
||||
the <b>numPeers</b> value should increase over time.<br>
|
||||
<br>
|
||||
<blockquote>
|
||||
<hr>
|
||||
While Q is in its early development and testing stages, the author may abdicate
|
||||
the mainstream Q network, and publish nodrefs for a whole new mainstream Q network.
|
||||
This will especially happen if the author makes any substantial changes to the
|
||||
inter-node protocol, and/or releases incompatible new versions of Q client/server
|
||||
nodes. Remember that
|
||||
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a> will
|
||||
serve as the authoritative source for noderefs for the mainstream Q network within
|
||||
the mainstream I2P network.
|
||||
<hr>
|
||||
</blockquote>
|
||||
|
||||
When your client node gets its noderefs to a Q network, it will periodically,
|
||||
from then on, retrieve differential peer list and catalog updates from servers
|
||||
it knows about.<br>
|
||||
<br>
|
||||
Even if you only feed your client just one ref for a single server node, it will
|
||||
in time discover all other operating server nodes on that Q network, and will
|
||||
build up a full local catalog of everything that's available on that Q network.<br>
|
||||
<br>
|
||||
Provided that your client is running ok, and has been fed with at least one
|
||||
ref for a live Q network that contains content, then over time, successive:
|
||||
<blockquote><code><pre>
|
||||
qmgr status</pre></code></blockquote>
|
||||
commands should report increasing values in the fields:
|
||||
<ul>
|
||||
<li><b>numPeers</b> - number of peers this client node knows about</li>
|
||||
<li><b>numLocalItems</b> - number of locally stored content items, ie items
|
||||
which you have either inserted to, or retrieved from, your client node</li>
|
||||
<li><b>numRemoteItems</b> - number of unique data items which are available
|
||||
on remote server nodes in the Q network, and which can be retrieved through
|
||||
your local client node.</li>
|
||||
</ul>
|
||||
|
||||
<blockquote>
|
||||
<hr>
|
||||
|
||||
<h4>4.7.1. One Big Warning</h4>
|
||||
|
||||
If you are participating in more than one distinct Q network, then <b>do not</b>
|
||||
insert noderefs for different networks into the same running instance of a
|
||||
local Q client, unless you don't plan on inserting content via that client.<br>
|
||||
<Br>
|
||||
For instance, let's say you are participating in two different Q networks:
|
||||
<ul>
|
||||
<li>The 'mainstream' Q netowrk</li>
|
||||
<li>A secret Q network - "My friends' teen angst diaries"</li>
|
||||
</ul>
|
||||
If you get a noderef for both these networks, and insert both of these into the
|
||||
same running Q client node, then this local client node will be transparently
|
||||
connected to both networks.<br>
|
||||
<br>
|
||||
If you only ever plan on retrieving content, and never inserting content, this
|
||||
won't be a problem, except that you won't be able to tell which content
|
||||
resides on the mainstream Q network, and which resides in the secret Q network.<br>
|
||||
<Br>
|
||||
The big problem arises from inserting content. Whenever you insert data through this
|
||||
'contaminated'
|
||||
Q client node, this node picks 3 different servers to which upload a copy of this
|
||||
data. You won't have any control over whether the data gets inserted to the mainstream
|
||||
Q network, or your secret Q network. You might insert something sensitive, intending it
|
||||
to go only into the secret Q network, where in fact it also ends up in the mainstream
|
||||
network, with consequences you might not want.
|
||||
</blockquote>
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.8. Content Data and Metadata</h3>
|
||||
|
||||
<blockquote>
|
||||
Whenever content gets stored on Q, it is actually stored as two separate items:
|
||||
<ul>
|
||||
<li>The <b>raw data</b> - whether a text file, or the raw bytes of image files,
|
||||
audio files etc</li>
|
||||
<li>The <b>metadata</b>, which contains human-readable and machine-readable
|
||||
descriptions of the data</li>
|
||||
</ul>
|
||||
Metadata consists of a set of <b>category=value</b> pairs.<br>
|
||||
<br>
|
||||
Confused yet? Don't worry, I'm confused as well. Let's illustrate this with an
|
||||
example of metadata for an MP3 audio recording:
|
||||
<ul>
|
||||
<li>title=Fight_Last_Thursday.mp3</li>
|
||||
<li>type=audio</li>
|
||||
<li>mimetype=audio/mpeg</li>
|
||||
<li>abstract=upcoming single recorded in our garage last April</li>
|
||||
<li>keywords=grunge,country,indie</li>
|
||||
<li>artist=Ring of Fire</li>
|
||||
<li>size=4379443</li>
|
||||
<li>contact=ring-of-fire@mail.i2p</li>
|
||||
<li>key=blah37blah24-yada23hfhyada</li>
|
||||
</ul>
|
||||
All metadata categories are optional. In fact, you can insert content with no metadata
|
||||
at all.<br>
|
||||
<br>
|
||||
If you fail to provide metadata when inserting an item, a blank set of metadata will
|
||||
be created with at least the following categories:
|
||||
<ul>
|
||||
<li><b>key</b> - the derived key, under which the item will later be retrievable
|
||||
by yourself and others</li>
|
||||
<li><b>title</b> - if not provided at insert time, this will be set to the key</li>
|
||||
<li><b>size</b> - size of the item's raw data, in bytes</li>
|
||||
</ul>
|
||||
Within Q, there is a convention to supply a minimal amount of metadata. While this
|
||||
is not expected or enforced, including all these categories is most strongly
|
||||
recommended. These core categories are:
|
||||
<ul>
|
||||
<li><b>title</b> - a meaningful title for the data item, consisting only of characters
|
||||
which are legal in filenames on all platforms, and which ends with a file extension.</li>
|
||||
<li><b>type</b> - one of a superset of eMule classifiers, such as:
|
||||
<ul>
|
||||
<li><b>text</b> - plain text</li>
|
||||
<li><b>html</b> - HTML content</li>
|
||||
<li><b>image</b> - content is in an image format, such as .png, .jpg, .gif etc</li>
|
||||
<li><b>audio</b> - content is an audio sample, such as .ogg, .mp3, .wav etc</li>
|
||||
<li><b>video</b> - due to the sheer size of video files, and Q's present design,
|
||||
it's unlikely people will be inserting video content anytime soon (unless it's
|
||||
very short)</li>
|
||||
<li><b>archive</b> - packed file collections, such as .tar.gz, .zip, .rar etc</li>
|
||||
<li><b>misc</b> - content does not fit into any of the above categories</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>mimetype</b> - not as important as the <b>type</b> category, but providing
|
||||
this category in your metadata is still strongly encouraged. Value for this category
|
||||
should be one of the standard mimetypes, eg <b>text/html</b>, <b>audio/ogg</b> etc.</li>
|
||||
<li><b>abstract</b> - a short description (<255 characters), intended for human reading</li>
|
||||
<li><b>keywords</b> - a comma-separated list of keywords, intended for
|
||||
machine-readability, should be all lowercase, no spaces</li>
|
||||
</ul>
|
||||
Note that you can supply extra metadata categories in addition to the above, and that
|
||||
people searching for content can search on these extra categories if they know about
|
||||
them.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.9. Searching For Content</h3>
|
||||
|
||||
<blockquote>
|
||||
As mentioned earlier - in constrast with Freenet, local Q nodes build up a complete
|
||||
catalog of all available content on whatever Q network they are connected to.<br>
|
||||
<br>
|
||||
This is a design decision, based on the choice to eliminate query traffic.<br>
|
||||
<br>
|
||||
The author hopes that this will result in a distributed storage network with a
|
||||
high retrievability guarantee, in contrast with freenet which offers no such
|
||||
guarantee.<br>
|
||||
<br>
|
||||
With Freenet, you only ever know of the existence of something if someone tells
|
||||
you about it.<br>
|
||||
<br>
|
||||
But with Q, your local client node builds up a global catalog of everything that's
|
||||
available within the whole network.<br>
|
||||
<br>
|
||||
The QMgr client has a command for searching your Q client node:
|
||||
<blockquote><code><pre>
|
||||
qmgr search -m category1=pattern1 category2=pattern2 ...</pre></code></blockquote>
|
||||
For example:
|
||||
<blockquote><code><pre>
|
||||
qmgr search -m type=audio artist=Mozart keywords=symphony</pre></code></blockquote>
|
||||
or:
|
||||
<blockquote><code><pre>
|
||||
qmgr search -m type=text title="bible|biblical|(Nag Hammadi)" keywords="apocrypha|Magdalene"</pre></code></blockquote>
|
||||
As implied in the latter example, search patterns are regular expressions. This example will
|
||||
locate all text items, whose <b>title</b> metadata category contains one of <b>bible</b>, <b>biblical</b> or <b>Nag Hammadi</b>, <i>and</i> whose <b>keywords</b> category contains either
|
||||
or both the words <b>apocrypha</b> or <b>Magdalene</b>.<br>
|
||||
<br>
|
||||
Please use the search function carefully, otherwise (if and when Q usage grows) you
|
||||
could be inundated with thousands or even millions of entries.<br>
|
||||
<br>
|
||||
If a search turns up nothing, qmgr will simply exit. But if it turns up one or more items,
|
||||
it will the items out one at a time, with the key first, then each metadata entry
|
||||
on an indented line following.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.10. Retrieving Content</h3>
|
||||
|
||||
<blockquote>
|
||||
Now, we're actually going to retrieve something.<br>
|
||||
<br>
|
||||
Presumably, after following the previous section, you will have seen one or more search
|
||||
results come up, with the 'keys' under which the items can be accessed.<br>
|
||||
<br>
|
||||
Now, choose one of the keys, preferably for a short text item. Try either of the following
|
||||
commands:
|
||||
<blockquote><code><pre>
|
||||
qmgr get <keystring> something.txt</pre></code></blockquote>
|
||||
<i>or</i>:
|
||||
<blockquote><code><pre>
|
||||
qmgr get <keystring> > something.txt</pre></code></blockquote>
|
||||
(both have the same effect - the first one explicitly writes to the named file, the second
|
||||
one dumps the raw data to stdout, which we shell-redirect into the file.<br>
|
||||
<br>
|
||||
<b><i>Note - redirection of fetched data to a file via shell is not working at present. Use only
|
||||
the first form till we fix the bug.</i></b>
|
||||
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.11. Inserting Content</h3>
|
||||
|
||||
<blockquote>
|
||||
Our last example in this walkthrough relates to inserting content.<br>
|
||||
<br>
|
||||
Firstly, create a small text file with 2-3 lines of text, and save it as (say)
|
||||
myqinsert.txt.<br>
|
||||
<br>
|
||||
Now, think of some metadata to insert along with the file. Or, you can just use
|
||||
the set:
|
||||
<blockquote><code><pre>
|
||||
type=text
|
||||
keywords=test
|
||||
abstract=My simple test of inserting into Q
|
||||
title=myqinsert.txt</pre></code></blockquote>
|
||||
|
||||
Now, let's insert the file. Ensure your Q client node is running, then type:
|
||||
<blockquote><code><pre>
|
||||
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
|
||||
abstract="My simple test of inserting into Q"</pre></code></blockquote>
|
||||
If all went well, this command should produce half a line of gibberish, followed
|
||||
immediately by your shell prompt, eg:
|
||||
<blockquote><code><pre>
|
||||
aRoFC~9MU~pM2C-uCTDBp5B7j79spFD8gUeu~BNkUf0=<b>$</b>
|
||||
</pre></code></blockquote>
|
||||
The '$' at the end is your shell prompt, and all the characters before it are the 'key'
|
||||
which was derived from the content you just inserted.<br>
|
||||
<br>
|
||||
To avoid the hassle of copying/pasting the key, you could just add output redirection
|
||||
to the above command, eg:
|
||||
<blockquote><code><pre>
|
||||
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
|
||||
abstract="My simple test of inserting into Q" \
|
||||
> myqinsert.key</pre></code></blockquote>
|
||||
This will cause the generated key to be written safe and sound into the file
|
||||
<b>myqinsert.key</b>.<br>
|
||||
<br>
|
||||
You can verify that this insert worked by a 'get' command, as in:
|
||||
<blockquote><code><pre>
|
||||
qmgr get `cat myqinsert.key` somefilename.ext</pre></code></blockquote>
|
||||
(Note that this won't work on windows because the DOS shell is irredeemably brain-damaged. If
|
||||
you're using Windows, you <b>will</b> have to cut/paste the key.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>4.12. Shutting Down your Node</h3>
|
||||
|
||||
<blockquote>
|
||||
If you've worked through to here, then congratulations! You've got your Q Client Node set up
|
||||
and working, and ready to meet all your distributed file storage and retrieval needs.<br>
|
||||
<br>
|
||||
You can leave your client node running 24/7 if you want. In fact, we recommend you keep your
|
||||
client node running as much of the time as possible, so that you get prompt catalog updates,
|
||||
and can more quickly stay in touch with new content.<br>
|
||||
<br>
|
||||
However, if you need to shut down your node, the command for doing this is:
|
||||
<blockquote><code><pre>
|
||||
qmgr stop</pre></code></blockquote>
|
||||
This command will take a while to complete (since the node has to wait for the I2P
|
||||
java shutdown hooks to complete before it can rest in peace). But once your node is
|
||||
shut down, you can start it up again at any time and pick up where you left off.
|
||||
</blockquote>
|
||||
|
||||
<a name="server"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>5. Running a Q Server Node</h2>
|
||||
|
||||
<h3>5.1. Introduction</h3>
|
||||
<blockquote>
|
||||
This section describes the requirements for, and procedures involved with, running
|
||||
a Q Server Node.<br>
|
||||
<br>
|
||||
We'll use a similar 'walkthrough' style to that which we used in the previous section
|
||||
on client nodes.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.2. Requirements and Choices</h3>
|
||||
<blockquote>
|
||||
Running a Q server is a generous thing to do, and helps substantially with making
|
||||
Q work at its best for everyone. However, please do make sure you can meet some
|
||||
basic requirements:
|
||||
<ul>
|
||||
<li>You are running a permanent (24/7) I2P Router, on a box with at least (say)
|
||||
98% uptime.</li>
|
||||
<li>You have a little bandwidth to spare, and don't mind the extra memory, disk and
|
||||
CPU-usage footprint of running a fulltime Q server node</li>
|
||||
<li>You have already been able to successfully run a Q client node.</li>
|
||||
</ul>
|
||||
Also, please decide whether you want your server node to contribute to the mainstream
|
||||
Q network, or whether you want to create your own private Q network, or join someone
|
||||
else's private network. Your contribution will be most appreciated, though, if you
|
||||
can run a server within the mainstream Q network.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.3. Starting Your Server Node</h3>
|
||||
|
||||
<blockquote>
|
||||
Starting up a Q Server node is very similar to starting up a Q client node, except
|
||||
that with the qmgr command line, you must put the keyword arg <b>server</b> before the
|
||||
command word. So the command is:
|
||||
<blockquote><code><pre>
|
||||
qmgr server start</pre></code></blockquote>
|
||||
Similar to Q client nodes, you can check the status of a running Q server node with
|
||||
the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr server status</pre></code></blockquote>
|
||||
(Note that this command will take longer to complete than with client nodes, because
|
||||
the communication passes through a multi-hop I2P tunnel, rather than just through
|
||||
localhost TCP).<br>
|
||||
<br>
|
||||
If the status command succeeds, then you'll know your new Q Server Node is happily
|
||||
running in background.
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.4. Joining A Q Network</h3>
|
||||
|
||||
<blockquote>
|
||||
When a Q Server node starts up for the first time, it is in a private network
|
||||
all by itself.<br>
|
||||
<br>
|
||||
If you want to link your server into an existing Q network, you'll have to add a
|
||||
noderef for at least one other server on that network. The command to do this
|
||||
is similar to that for subscribing a client node to a network:
|
||||
<blockquote><code><pre>
|
||||
qmgr server addref <noderef-file></pre></code></blockquote>
|
||||
where <noderef-file> is a file into which you've saved the noderef for
|
||||
the network you want to join.
|
||||
<blockquote>
|
||||
<hr><i><small>
|
||||
Recall from the section on client nodes that the authoritative noderefs
|
||||
for the mainstream Q network can be downloaded from
|
||||
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.
|
||||
</small></i><hr>
|
||||
</blockquote>
|
||||
After you've added the noderef, subsequent <b>qmgr server status</b> commands
|
||||
should show <b>numPeers</b> having a value of at least 1 (and growing, as more
|
||||
server nodes come online in the mainstream Q network.)
|
||||
|
||||
</blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>5.5. Private Networks - Exporting Your Server's Noderef</h3>
|
||||
|
||||
<blockquote>
|
||||
If you're planning to start your own private Q network, and want to include other
|
||||
server operators in this network, then you'll have to export your server's noderef
|
||||
and make it available to the others you want to invite into your network.<br>
|
||||
<br>
|
||||
The command to export your Q Server noderef is:
|
||||
<blockquote><code><pre>
|
||||
qmgr server getref <noderef-file></pre></code></blockquote>
|
||||
This will extract the <i>I2P Destination</i> of your running server node, and
|
||||
write it into <noderef-file>. You can then privately share this file with
|
||||
others who you want to invite into your private network. Each recipient of
|
||||
this file will do a <b>qmgr server addref <noderef-file></b> command
|
||||
to import your ref into their servers.<br>
|
||||
<br>
|
||||
Don't forget that if you're running, or participating in, a private Q network, then
|
||||
you'll need to run a separate client for accessing this network, separate from any
|
||||
mainstream Q network client you may already be running.<br>
|
||||
<br>
|
||||
To start this extra client, you'll have to choose a directory where you want this
|
||||
client to reside, a port number you want your client to listen on locally for
|
||||
user commands, and run the command:
|
||||
<blockquote><code><pre>
|
||||
qmgr -dir /path/to/my/new/client -port <portnum> start</pre></code></blockquote>
|
||||
You need the <b>-port <portnum></b> command, because otherwise it'll fail
|
||||
to launch (if you already have a client node running off the mainstream Q network).<br>
|
||||
<br>
|
||||
This will create, and launch, a new instance of a Q client, accessing your private
|
||||
Q network. Don't forget to import your server's noderef into this client. Also,
|
||||
note that you'll have to use this same <b>-port <portnum></b> argument when
|
||||
doing any operation on this client instance, such as get, put, status, search.
|
||||
|
||||
</blockquote>
|
||||
|
||||
<a name="qmgr"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>6. About the qmgr Utility</h2>
|
||||
|
||||
qmgr (or, to people fluent in Java, <b>net.i2p.aum.q.QMgr</b>), is just one simple
|
||||
Q client application, that happens to be bundled in with the Q distro.<br>
|
||||
<br>
|
||||
It is by no means the only, or even main facility for accessing the Q network. We
|
||||
anticipate that folks will write all manner of client apps, including fancy GUI
|
||||
apps.<br>
|
||||
<br>
|
||||
Anyway, qmgr does give you a rudimentary yet workable client for basic access
|
||||
to the Q network. Until fancy apps get written, qmgr will have to do.<br>
|
||||
<br>
|
||||
Don't forget that qmgr has very detailed inbuilt help. Run:
|
||||
<blockquote><code><pre>
|
||||
qmgr help</pre></code></blockquote>
|
||||
for a quick help summary, or:
|
||||
<blockquote><code><pre>
|
||||
qmgr help verbose</pre></code></blockquote>
|
||||
for the 'War and Peace' treatise.<br>
|
||||
<br>
|
||||
<blockquote><hr>
|
||||
One crucial concept to remember with qmgr is that client and server node instances
|
||||
are uniquely identified by the directories at which they reside. If you are running
|
||||
multiple server and/or client instances, you can specify an instance with the
|
||||
<b>-dir <dirpath></b> option - see the help for details.
|
||||
<hr></blockquote>
|
||||
|
||||
<hr>
|
||||
|
||||
One last note - we strongly discourage any writing of client apps that spawn a qmgr
|
||||
process, pass it arguments and parse its results. This is most definitely a path to
|
||||
pain, since qmgr's shell interface is subject to radical change at any time without
|
||||
notice.<br>
|
||||
<br>
|
||||
qmgr is for human usage, or at most, inclusion in init/at/cron scripts. Please respect
|
||||
this.<br>
|
||||
<br>
|
||||
If you want to write higher-level clients, your best course of action is to use the
|
||||
official client api library, which we anticipate will have versions available in
|
||||
Java, Python, Perl and C++. If you want to write in another language, such as
|
||||
OCaml, Scheme etc, then the existing api lib implementations should serve as an excellent
|
||||
reference to support you in writing a native port for your own language.
|
||||
|
||||
<a name="contact"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>8. Contacting the Author</h2>
|
||||
|
||||
I am <b>aum</b>, and can be reached as <b>aum</b> on in-I2P IRC networks, and also
|
||||
at the in-I2P email address of <b>aum@mail.i2p</b>.<br>
|
||||
<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<center>
|
||||
Return to <a href="../index.html">Q Homepage</a><br>
|
||||
<br>
|
||||
<small>
|
||||
<a href="#intro">Introduction</a> |
|
||||
<a href="#checklist">Checklist</a> |
|
||||
<a href="#serverorclient">Server?orClient?</a> |
|
||||
<a href="#walkthrough">Walkthrough</a> |
|
||||
<a href="#server">Server Nodes</a> |
|
||||
<a href="#qmgr">About QMgr</a> |
|
||||
<a href="#contact">Contact us</a>
|
||||
</small>
|
||||
</center>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Created: Fri Apr 1 11:03:27 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Sun Apr 3 20:06:53 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
23
apps/q/doc/manual/notes
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
rise on each hit:
|
||||
|
||||
dy = (1 - y) / kRise
|
||||
|
||||
fall after each time unit:
|
||||
|
||||
dy = y / kFall
|
||||
|
||||
fall after time dt:
|
||||
|
||||
dy = - y ** - (dt / kFall)
|
||||
|
||||
after the next hit:
|
||||
|
||||
y = y - y ** (- dt / kFall) + (1 - y) / kRise
|
||||
|
||||
first attempt at a load measurement algorithm:
|
||||
- kFall is an arbitrary constant which dictates decay rate of load
|
||||
in the absence of hits
|
||||
- kRise is another constant which dictates rise of load with each hit
|
||||
- dt is the time between each hit
|
||||
|
||||
372
apps/q/doc/metadata.html
Normal file
@@ -0,0 +1,372 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q Metadata Specification</title>
|
||||
|
||||
<style type="text/css">
|
||||
<!--
|
||||
td { vertical-align: top; }
|
||||
code { font-family: courier, monospace; font-weight: bolder; font-size:smaller }
|
||||
-->
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Q Metadata Specification</h1>
|
||||
|
||||
<h2>1. Introduction</h2>
|
||||
|
||||
This document lists the standard metadata keys for Q data items,
|
||||
discussing the rules of metadata insertion, processing and validation.<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>1.1. Definitions</h3>
|
||||
|
||||
To avoid confusions in terminology, this document will strictly abide the following definitions:
|
||||
<br>
|
||||
<br>
|
||||
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
|
||||
<tr style="font-weight: bold">
|
||||
<td>Term</td>
|
||||
<td>Definition</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>key</code></td>
|
||||
<td>A metadata category name, technically a <code>key</code> as the word is used with
|
||||
Java <code>Hashtable</code> and Python <code>dict</code> objects.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>uri</code></td>
|
||||
<td>A Uniform Resource Indicator for an item of content stored within the Q network.<br>
|
||||
Q URIs have the form: <code>Q:<basename>[,<cryptoKey>][<path>]</code>
|
||||
<br>
|
||||
<br>
|
||||
Some examples:
|
||||
<ul>
|
||||
<li><code>Q:fhvnr3HFSK234khsf90sdh42fsh</code> (a plain hash uri, no cryptoKey)</li>
|
||||
<li><code>Q:e54fhjeo39schr2kcy4osEH478D/files/johnny.mp3</code> (a secure space URI,
|
||||
no cryptoKey)</li>
|
||||
<li><code>Q:vhfh4se987WwfkhwWFEwkh3234S,47fhh2dkhseiyu</code> (a plain hash URI, with
|
||||
a cryptoKey)</li>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>basename</code></td>
|
||||
<td>The basic element of a Q uri. This will be a base64-encoded hash - refer below to
|
||||
URI calculation procedures</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>cryptoKey</code></td>
|
||||
<td>An optional session encryption key for the stored data, encoded as base64.
|
||||
This affords some protection to server node operators, and gives them a level
|
||||
of plausible deniability for whatever gets stored in their server's
|
||||
datastore without their direct human awareness.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>Whever an item of content is inserted in <code>secure space</code> mode, this path
|
||||
serves as a pseudo-pathname, and is conceptually similar to the <code>path</code>
|
||||
component in (for example) standard HTTP URLs
|
||||
<code>http://<domainname>[:<port>][<path>]</code>, such as
|
||||
<code>http://slashdot.org/faq/editorial.shtml</code> (whose <code>path</code>
|
||||
is <code>/faq/editorial.shtml</code>).<br>
|
||||
<br>
|
||||
Paths, if not empty, should contain a leading slash ("/").
|
||||
If an application specifies a non-empty <code>path</code> that doesn't begin with a
|
||||
leading '/', a '/' will be automatically prepended by the receiving node.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>plain hash</code></td>
|
||||
<td>A mode of inserting items, whereby the security of the resulting URI comes from
|
||||
computing the URI from a hash of the item's data and metadata (and imposing a
|
||||
mathematical barrier against spoofing content under a given URI). Corresponds to
|
||||
Freenet's <code>CHK@</code> keys.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>secure space</code></td>
|
||||
<td>A mode of inserting items where the security of the URI is based not on a hash of the
|
||||
item's data and metadata (as with <code>plain hash</code> mode),
|
||||
but on the <code>privateKey</code> provided by the
|
||||
application, and a content signature created from that private key.
|
||||
Corresponds to Freenet's <code>SSK@</code> keys. Within a secure space, you
|
||||
can insert any number of items under different pseudo-pathnames (as is the case
|
||||
with Freenet SSK keys).
|
||||
</li>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>2.1. Keys Inserted By Application Before sending <code>putItem</code> RPCs</h3>
|
||||
|
||||
As the heading suggests, this is a list of metadata keys which should be inserted by a
|
||||
Q application prior to invoking a <code>putItem</code> RPC on the local Q client node.<br>
|
||||
<br>
|
||||
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
|
||||
<tr style="font-weight: bold">
|
||||
<td>Key</td>
|
||||
<td>Data Type</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>title</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but strongly recommended. A free-text short description of the item,
|
||||
should be less than 80 characters. The idea is that applications should
|
||||
support a 'view' of catalogue data that shows item titles. (Prior Q convention of
|
||||
titles expressed as valid filename syntax has been abandoned).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but strongly recommended.
|
||||
A virtual 'pathname' for the item, which should be in valid *nix
|
||||
absolute pathname syntax (beginning with '/', containing no '//', consisting
|
||||
only of alphanumerics, '-', '_', '.' and '/'.<br>
|
||||
<br>
|
||||
In Q web interfaces, the <code>filename</code> component of this path will
|
||||
serve as the recommended filename when downloading/saving the item.<br>
|
||||
<br>
|
||||
If the application also provides a
|
||||
<code>privateKey</code> key, the path
|
||||
is used in conjunction with the private key to generate <code>publicKey</code>
|
||||
and <code>signature</code> keys (see below), and ultimately the final <code>uri</code>
|
||||
under which the item can be retrieved by others.<br>
|
||||
<br>
|
||||
Refer also to <code>mimetype</code> below.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>encrypt</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional. If this key is present, and has a value "1", "yes" or "true",
|
||||
this indicates that the application wishes the data to be stored on servers in
|
||||
encrypted form.<br>
|
||||
<br>
|
||||
If this key is present and set to a positive value, the Q node, on receiving the
|
||||
<code>putItem</code> RPC, will:
|
||||
<ol>
|
||||
<li>Generate a random symmetric encryption key</li>
|
||||
<li>Encrypt the item's data using this encryption key</li>
|
||||
<li>Delete the <code>encrypt</code> key from the metadata</li>
|
||||
<li>Enclose a base64 representation of this encryption key in the RPC response
|
||||
it sends back to the application (embedded in the <code>uri</code></li>
|
||||
</ol>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>type</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but strongly recommended. A standard ed2k specifier, one of <code>text html image
|
||||
audio video archive other</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>mimetype</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but moderately recommended. Mimetype designation of data, eg <code>text/html</code>,
|
||||
<code>image/jpeg</code> etc. If not specified, an attempt will be made to guess
|
||||
a mometype from the value of the <code>path</code> key. If this attempt fails, then
|
||||
this key will be set to <code>application/x-octet-stream</code> by the node receiving
|
||||
the <code>putItem</code> RPC.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>keywords</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional but moderately recommended.
|
||||
A set of keywords, under which the inserting app would like this item to be
|
||||
discoverable. Keywords should be entirely lower case and comma-separated. Content
|
||||
inserts should consider keywords carefully, and only use space characters inside
|
||||
keywords when necessary (eg, for flagging a distinctive phrase containing very
|
||||
common words).</td>
|
||||
<tr>
|
||||
<td><code>privateKey</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional. A Base64-encoded signing private key, in cases where the application wishes
|
||||
to insert an item in <code>signed space</code> mode. This can be accompanied by another key,
|
||||
<code>path</code>, indicating a 'path' within the signed space. If 'path'
|
||||
is not given, it will default to '/'.<br>
|
||||
<br>
|
||||
Either way, when a node receives a
|
||||
<code>putItem</code> RPC containing a <code>privateKey</code> in its metadata,
|
||||
it removes this key and replaces it with <code>publicKey</code> and
|
||||
<code>signature</code>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>String</td>
|
||||
<td>Optional. The virtual pathname, within signed space, under which to store the item.
|
||||
This gets ignored and deleted unless the application also provides a
|
||||
<code>privateKey</code> as well. But if the private key is given, the path
|
||||
is used in conjunction with the private key to generate <code>publicKey</code>
|
||||
and <code>signature</code> keys (see below).<br>
|
||||
<code>path</code> should be a 'unix-style pathname', ie, containing only slashes
|
||||
as (pseudo) directory delimiters, and alphanumeric, '-', '_' and '.' characters,
|
||||
and preferably ending in a meaningful file extension such as <code>.html</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>expiry</code></td>
|
||||
<td>int</td>
|
||||
<td>Unixtime at which the inserted item should expire. When this expiry time
|
||||
is reached, the item won't necessarily be deleted straight away, but may
|
||||
be deleted whenever a node's data store is full.<br>
|
||||
<br>
|
||||
If this is not provided, it will default to a given duration according to
|
||||
the client node's configuration.<br>
|
||||
<br>
|
||||
If it is provided, by an application, then the client node will transparently
|
||||
generate the required 'rent payment' before caching the data item and uploading
|
||||
it to servers.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>2.2. Keys Inserted By Node Upon Receipt Of <code>putItem</code> RPC</h3>
|
||||
|
||||
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
|
||||
<tr style="font-weight: bold">
|
||||
<td>Key</td>
|
||||
<td>Data Type</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>size</code></td>
|
||||
<td>Integer</td>
|
||||
<td>Size of the data to be inserted, in bytes.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dataHash</code></td>
|
||||
<td>String</td>
|
||||
<td>base64-encoded SHA256 hash of data.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>uri</code></td>
|
||||
<td>String</td>
|
||||
<td>This depends on whether the item is being inserted in <i>plain</i> or
|
||||
<i>signed space</i> mode.<br>
|
||||
<br>
|
||||
If inserting in <i>plain</i> mode, then the uri is in the form
|
||||
<code>Q:somebase64hash</code>, where the hash is computed according to
|
||||
the <a href="#plainhash">plain hash calculation procedure</a>.<br>
|
||||
<br>
|
||||
If inserting in <i>signed space</i> mode, then the uri will be in the form
|
||||
<code>Q:somebase64hash/path.ext</code>, where the hash is computed as per
|
||||
the <a href="#signedhash">signed space hash calculation procedure</a>, and
|
||||
the <code>/path.ext</code> is the verbatim value of the app-supplied
|
||||
<code>path</code> key.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>publicKey</code></td>
|
||||
<td>String</td>
|
||||
<td>Base64-encoded signing public key. In cases where app provides
|
||||
<code>privateKey</code>,
|
||||
a node will derive the signing public key from the private key,
|
||||
delete the private key from the metadata, and replace it with its corresponding
|
||||
public key
|
||||
key.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>signature</code></td>
|
||||
<td>String</td>
|
||||
<td>Base64-encoded signature of <code>path+dataHash</code>, created using
|
||||
the app-provided <code>privateKey</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rent</code></td>
|
||||
<td>String</td>
|
||||
<td>A rent payment for the data's accommodation on the server.<br>
|
||||
Intention is to support a variety of payment tokens. Initially, the
|
||||
only acceptable form of payment will be a hashcash-like token,
|
||||
in the form <code>hashcash:base64string</code>. The <code>hashcash:</code>
|
||||
prefix indicates that this payment is in hashcash currency, in which case
|
||||
the <code>base64String</code> should decode to a 16-byte string whose
|
||||
SHA256 hash partially collides with <code>dataHash</code>.
|
||||
The greater the number of bits in the collision,
|
||||
the longer the data's accommodation will be 'paid up for'.<br>
|
||||
<br>
|
||||
If this key is already present, a Q node will verify the hashcash,
|
||||
and adjust the <code>expiry</code> key value to the time the item's accommodation
|
||||
is paid up till.<br>
|
||||
<br>
|
||||
If the key is not present:
|
||||
<ul>
|
||||
<li>A client node will generate a value for this key with enough collision bits
|
||||
to pay the accommodation up till the given app-specified <code>expiry</code> date.</li>
|
||||
<li>A server node will grant temporary free accommodation, and adjust the <code>expiry</code>
|
||||
key to the end of the free accommodation period.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
|
||||
<a name="plainhash"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>3. URI Determination Procedures</h2>
|
||||
|
||||
<h3>3.1. Plain Hash URI Calculation Procedure</h3>
|
||||
|
||||
When items are inserted in <code>plain</code> mode, the final URI is determined from
|
||||
a hash of the data and metadata. Security of the item is based on the mathematical difficulty
|
||||
of creating an arbitrary data+metadata set whose hash collides with the target URI.<br>
|
||||
<br>
|
||||
Specifically, the recipe for calculating plain hash URIs is:
|
||||
<ol>
|
||||
<li>If the key <code>size</code> is missing, set this to the size of the data,
|
||||
in bytes</li>
|
||||
<li>If the key <code>dataHash</code> is missing, set this to the base64-encoded
|
||||
SHA256(data)</li>
|
||||
<li>If the key <code>title</code> is missing, set this to the value of <code>dataHash</code></li>
|
||||
<li>From the metadata, create a set of strings, each in the form <code>key=value</code>,
|
||||
where each line contains a metadata <code>key</code> and its <code>value</code>, and
|
||||
is terminated by an ASCII linefeed (\n, 0x10).</li>
|
||||
<li>Ensure that key <code>uri</code> is omitted</li>
|
||||
<li>Sort the strings into ascending ASCII sort order</li>
|
||||
<li>Concatenate the strings together into one big string</li>
|
||||
<li>Calculate the SHA256 hash of this string</li>
|
||||
<li>Encode the hash into Base64</li>
|
||||
<li>Prepend the string <code>Q:</code> to this</li>
|
||||
</ol>
|
||||
|
||||
<a name="signedhash"/>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>3.2. Signed Space URI Calculation Procedure</h3>
|
||||
|
||||
This is much simpler than determining plain hash URI, since the security of the URI
|
||||
is based not on hashes of data and metadata, but on the cryptographic <code>privateKey</code>
|
||||
given by the application.<br>
|
||||
<br>
|
||||
Calculation recipe for Signed Space URIs is:
|
||||
<ol>
|
||||
<li>Calculate the SHA256 hash of the private key's binary data (not its base64 representation)</li>
|
||||
<li>Encode this hash into base64, dropping any trailing '=' characters</li>
|
||||
<li>Append to this the value of metadata item <code>path</code> (recall that <code>path</code>,
|
||||
if not empty, must begin with a '/')</li>
|
||||
<li>Prepend the string <code>Q:</code> to this</li>
|
||||
</ol>
|
||||
The resulting URI then is in the form <code>Q:pubkeyHash/path.ext</code>
|
||||
|
||||
<hr>
|
||||
<!-- Created: Tue Apr 5 00:56:45 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Wed Apr 6 00:36:37 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
BIN
apps/q/doc/overall.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
1
apps/q/doc/qnoderefs.txt
Normal file
@@ -0,0 +1 @@
|
||||
rxvXpHKfWGWsql4PJaHglAERSUYyrdKKAzK6jPHT4QXRf9jgcVd4mInq0j6H4inVOzT9dG4L6c9GrlQwe4ysUm5jSTyZemxiZpQDCAazsoRzNDv6gevA40J6uGl10JtVtOjqXW8Ej0JUKubz88g~ogPb1h4Xibc-RrtqrvsJebg5xYFkLlnr7DxDtiWzIMRSZ9Ri2P~eq0SwZzd81tvASPj5fb3nySHeABAuY8HrNu0gqRLjeayDpd3OK1ogrxf1lMvfutn5pnLrlVcvKHa~6rNWWGSulsuEYWtpUd4Itj9aKqIgF9ES7RF77Z73W1f6NRTHO48ZLyLLaKVLjDIsHQP-0mOevszcPjFWtheqRKvT2D28WEMpVC-mPtfw91BkdgBa3pwWhwG~7KIhvWhGs8bj2NOKkqrwYU7xhNVaHdDDkzv4gsweCutHNiiCF~4yL54WzCIfSKDjcHjQxxVkh2NKeaItzgw9E~mPAKNZD22X~2oAuuL9i~0lldEV1ddUAAAA
|
||||
BIN
apps/q/doc/screenshot-home.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
apps/q/doc/screenshot-iewarn.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
apps/q/doc/screenshot-qsite.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
apps/q/doc/screenshot-search.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
apps/q/doc/screenshot.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
apps/q/doc/screenshot.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
23
apps/q/doc/screenshots.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Q Screenshots</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Q Screenshots</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="screenshot-search.jpg">Search Screen</li>
|
||||
<li><a href="screenshot-qsite.jpg">QSite Insertion Form</li>
|
||||
<li><a href="screenshot-iewarn.jpg">Q Security Features</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<address><a href="mailto:aum@mail.i2p">aum</a></address>
|
||||
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Apr 18 14:06:02 NZST 2005
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
1460
apps/q/doc/spec/index.html
Normal file
90
apps/q/java/build.xml
Normal file
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="aum">
|
||||
<!-- Written to assume that classpath is rooted in the current directory. -->
|
||||
<!-- So this should be OK if you make this script in the root of a filesystem. -->
|
||||
<!-- If not, just change src.dir to be the root of your sources' package tree -->
|
||||
<!-- and use e.g. View over a Filesystem to mount that subdirectory with all capabilities. -->
|
||||
<!-- The idea is that both Ant and NetBeans have to know what the package root is -->
|
||||
<!-- for the classes in your application. -->
|
||||
|
||||
<!-- Don't worry if you don't know the Ant syntax completely or need help on some tasks! -->
|
||||
<!-- The standard Ant documentation can be downloaded from AutoUpdate and -->
|
||||
<!-- and then you can access the Ant manual in the online help. -->
|
||||
|
||||
<target name="init">
|
||||
<property location="build" name="classes.dir"/>
|
||||
<property location="src" name="src.dir"/>
|
||||
<property location="doc/q/api" name="javadoc.dir"/>
|
||||
<property name="project.name" value="${ant.project.name}"/>
|
||||
<property location="${project.name}.jar" name="jar"/>
|
||||
<property location="q.war" name="war"/>
|
||||
</target>
|
||||
|
||||
<target name="builddep">
|
||||
<ant dir="../../i2ptunnel/java/" target="build" />
|
||||
<!-- i2ptunnel builds ministreaming and core -->
|
||||
</target>
|
||||
|
||||
<target depends="init,builddep" name="compile">
|
||||
<!-- Both srcdir and destdir should be package roots. -->
|
||||
<mkdir dir="${classes.dir}"/>
|
||||
<javac debug="true"
|
||||
deprecation="true"
|
||||
destdir="${classes.dir}"
|
||||
srcdir="${src.dir}"
|
||||
classpath="xmlrpc.jar:../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar:../../i2ptunnel/java/build/i2ptunnel.jar" >
|
||||
<!-- To add something to the classpath: -->
|
||||
<!-- <classpath><pathelement location="${mylib}"/></classpath> -->
|
||||
<!-- To exclude some files: -->
|
||||
<!-- <exclude name="com/foo/SomeFile.java"/><exclude name="com/foo/somepackage/"/> -->
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target depends="init,compile" name="jar">
|
||||
<!-- To make a standalone app, insert into <jar>: -->
|
||||
<!-- <manifest><attribute name="Main-Class" value="com.foo.Main"/></manifest> -->
|
||||
<!-- <jar basedir="${classes.dir}" compress="true" jarfile="${jar}"> -->
|
||||
<jar compress="true" jarfile="${jar}">
|
||||
<!-- <jar basedir="." compress="true" jarfile="${jar}" includes="**/*.class,doc/**/*.html"> -->
|
||||
<fileset dir="${classes.dir}"/>
|
||||
<fileset dir="." includes="qresources/**"/>
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.aum.q.QMgr"/>
|
||||
<attribute name="Class-Path" value="i2p.jar xmlrpc.jar mstreaming.jar streaming.jar jbigi.jar"/>
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target depends="init,compile" name="war">
|
||||
<!-- To make a standalone app, insert into <jar>: -->
|
||||
<!-- <manifest><attribute name="Main-Class" value="com.foo.Main"/></manifest> -->
|
||||
<war compress="true" jarfile="${war}" webxml="web.xml">
|
||||
<!-- <fileset file="build/net/i2p/aum/q/QConsole.class"/> -->
|
||||
<classes dir="build" includes="**/QConsole.class"/>
|
||||
<classes dir="build" includes="**/HTML/**"/>
|
||||
<!-- <fileset includes="**/HTML/*.class"/> -->
|
||||
<lib file="xmlrpc.jar"/>
|
||||
</war>
|
||||
</target>
|
||||
|
||||
<target depends="init,jar,war" description="Build everything." name="all"/>
|
||||
|
||||
<target depends="init" description="Javadoc for my API." name="javadoc">
|
||||
<mkdir dir="${javadoc.dir}"/>
|
||||
<javadoc destdir="${javadoc.dir}" packagenames="*">
|
||||
<sourcepath>
|
||||
<pathelement location="${src.dir}"/>
|
||||
</sourcepath>
|
||||
<sourcepath>
|
||||
<pathelement location="/java/xmlrpc-1.2-b1/src/java"/>
|
||||
</sourcepath>
|
||||
</javadoc>
|
||||
</target>
|
||||
|
||||
<target depends="init" description="Clean all build products." name="clean">
|
||||
<delete dir="${classes.dir}"/>
|
||||
<delete dir="${javadoc.dir}"/>
|
||||
<delete file="${jar}"/>
|
||||
</target>
|
||||
|
||||
</project>
|
||||
10
apps/q/java/qresources/html/404.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading">Item Not Found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=center>Failed to retrieve item:<br>
|
||||
<code><tmpl_var 404_uri></code>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
27
apps/q/java/qresources/html/about.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<TABLE align="center" class="mainpaneitem">
|
||||
<TR>
|
||||
<TD class="formHeading">About Q</TD>
|
||||
</tr>
|
||||
<tr class="formBody">
|
||||
<TD align=center>
|
||||
<p>Version
|
||||
<tmpl_if version>
|
||||
<tmpl_var version></p>
|
||||
<tmpl_else>
|
||||
0.0.1
|
||||
</tmpl_if>
|
||||
<p>Designed and engineered by <a href="mailto:aum@mail.i2p">aum</a></p>
|
||||
|
||||
<p>Copyright © 2005 by aum.
|
||||
<br>Released under the
|
||||
<br>GNU Lesser General Public License</p>
|
||||
|
||||
<p style="font-size: smaller; font-style: italic">
|
||||
Many thanks to jrandom, smeghead and frosk<br>
|
||||
for their patient and knowledgeable support<br>
|
||||
in helping this python programmer<br>
|
||||
get partly proficient in java.</p>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
40
apps/q/java/qresources/html/addrefform.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<table align="center" class="mainpaneitem" width=80%>
|
||||
<tr>
|
||||
<td class="formHeading">Add Hub Noderef</td>
|
||||
</tr>
|
||||
|
||||
<form action="/tools" method="POST" class="formBody">
|
||||
|
||||
<input type="hidden" name="cmd" value="addref">
|
||||
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width=70%>
|
||||
<tr>
|
||||
<td>
|
||||
In order for your node to join a Q network, it must have a ref to at
|
||||
least one of the running Q Hubs. You need to get a Q Hub noderef and
|
||||
paste it here.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align=center>
|
||||
<textarea name="noderef" cols=40 rows=6></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center">
|
||||
<input type="submit" name="submit" value="Add Hub Noderef!">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</form>
|
||||
|
||||
</table>
|
||||
|
||||
29
apps/q/java/qresources/html/aiealert.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<table align="center" class="mainpaneitem" width=80%>
|
||||
<tr>
|
||||
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellspacing=0 cellpadding=5 align=center border=1>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<p>You are attempting to view a QSite via the Internet Explorer web browser.
|
||||
</p>
|
||||
<p>We have blocked the connection to protect your anonymity.
|
||||
</p>
|
||||
<p>As a matter of policy, Q does not support QSite browsing via Microsoft
|
||||
Internet Explorer (MSIE), because of that browser's abysmal track record with
|
||||
regard to security. If we did allow you to browse Q via MSIE, it would
|
||||
be easy for a malicious QSite author to embed hostile content which
|
||||
undermines your computer's security and compromises your anonymity.
|
||||
</p>
|
||||
<p>If you want to surf I2P QSites, you'll need to use a more secure web
|
||||
browser such as <a href="http://www.mozilla.org">Mozilla or Firefox</a>.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
52
apps/q/java/qresources/html/anonalert.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<table align="center" class="mainpaneitem" width=80%>
|
||||
<tr>
|
||||
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellspacing=0 cellpadding=5 align=center border=1>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<p>You are attempting to view a QSite via an unprotected link.
|
||||
</p>
|
||||
<p>We have blocked the connection to protect your anonymity. If we don't
|
||||
do this, then a malicious QSite author can insert content into the QSite
|
||||
which triggers oubound hits to arbitrary servers on the mainstream web,
|
||||
which in turn can easily reveal a lot of personal information about you
|
||||
and the QSite you are accessing.
|
||||
</p>
|
||||
<p>If you want to browse QSites with your web browser with greater safety,
|
||||
you'll have to follow these simple steps:
|
||||
</p>
|
||||
<ol>
|
||||
<li>Edit your I2P <code>hosts.txt</code> file and add the following entry
|
||||
(all on one line):
|
||||
<blockquote><code><textarea rows=5 cols=50>q.i2p=<tmpl_var dest></textarea></code><blockquote>
|
||||
(and make sure you do NOT reveal this entry to anyone else)
|
||||
</li>
|
||||
<li>Configure one of your web browsers to use the proxy server:
|
||||
<blockquote><code>localhost:4444</code></blockquote>
|
||||
(or whatever address you have configured your I2P EEProxy to listen on,
|
||||
if you've changed it)<br><br>
|
||||
</li>
|
||||
<li>Start up the browser you have just configured, and enter the web address:
|
||||
<blockquote><code>http://q.i2p</code></blockquote>
|
||||
</li>
|
||||
</ol>
|
||||
<p>Even if you do this, you still won't have a 100% guarantee of anonymity, since
|
||||
a malicious QSite author can send code to your browser which exploits a vulnerability
|
||||
in the browser to compromise your anonymity (eg, accessing third party cookies, executing
|
||||
arbitrary code, accessing your local filesystem, uploading compromising information
|
||||
about you to hostile I2P EEPsites etc). But you can relax (somewhat) in the knowledge
|
||||
that such attacks are much more difficult, particularly if you use a decent web browser.
|
||||
</p>
|
||||
<p>Thank you for your co-operation. We wish you happy and safe browsing.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
9
apps/q/java/qresources/html/downloads.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading">Q Downloads</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Not implemented yet</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
28
apps/q/java/qresources/html/genkeysform.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td class="formHeading">Generate New Keypair</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Click the button to generate a new keypair for
|
||||
future inserts of signed space keys.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center">
|
||||
<form action="/tools" method="POST" class="formBody">
|
||||
<input type="hidden" name="cmd" value="genkeys">
|
||||
<input type="submit" name="submit" value="Generate Keys!">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
32
apps/q/java/qresources/html/genkeysresult.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<table align="center" class="mainpaneitem">
|
||||
<tr>
|
||||
<td colspan=2 class="formHeading">Your New Keys</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" colspan=2>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Please save these keys in a safe place,
|
||||
preferably on an encrypted partition:
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Public Key: </td>
|
||||
<td>
|
||||
<input readonly type="text" size=32 style="font-family: courier, monospace" value="<tmpl_var publickey>">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Private Key: </td>
|
||||
<td><input readonly type="text" size=32 style="font-family: courier, monospace" value="<tmpl_var privatekey>"></td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||