Compare commits
1027 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6012939f3 | ||
|
|
bda3dcc670 | ||
|
|
a96e7945e1 | ||
|
|
278e1b4099 | ||
|
|
c369c78b42 | ||
|
|
a61a461436 | ||
|
|
d616b8fff7 | ||
|
|
3252f2abb2 | ||
|
|
53323cb8c4 | ||
|
|
d7ebb3d97c | ||
|
|
30684fb383 | ||
|
|
09376c5c4b | ||
|
|
917d9f16b4 | ||
|
|
1adf845284 | ||
|
|
10bd3b47e9 | ||
|
|
0d48e346ad | ||
|
|
234eb86f69 | ||
|
|
856e1c966e | ||
|
|
12cc09f6e5 | ||
|
|
7470ed1ea2 | ||
|
|
555cfc085a | ||
|
|
325cff6661 | ||
|
|
71369dee36 | ||
|
|
146f6413a9 | ||
|
|
38acf6f7e3 | ||
|
|
81d3593164 | ||
|
|
3ca13a405f | ||
|
|
b0a01a6634 | ||
|
|
c1392acb70 | ||
|
|
73ca44f064 | ||
|
|
a7235e1fcd | ||
|
|
7bb1ea7a01 | ||
|
|
4266ff0e41 | ||
|
|
2d9769837f | ||
|
|
c275151ae4 | ||
|
|
9231dc6bdd | ||
|
|
2e18768770 | ||
|
|
5c5e00310a | ||
|
|
9be175f1b0 | ||
|
|
8c4594ad31 | ||
|
|
519ce8c669 | ||
|
|
76b1df5096 | ||
|
|
d6f1a63b7b | ||
|
|
d0da7aafa2 | ||
|
|
ca8c36f38b | ||
|
|
f9241b73fd | ||
|
|
bb74eafd36 | ||
|
|
149d7364fb | ||
|
|
cdf3aa0a31 | ||
|
|
e663aa90cb | ||
|
|
986d005449 | ||
|
|
0d8e4e10d6 | ||
|
|
95de3790d7 | ||
|
|
defee4b4f6 | ||
|
|
c146b150b8 | ||
|
|
a29b8e6d1d | ||
|
|
4c1facd68e | ||
|
|
0f1d75a2ef | ||
|
|
0017def27c | ||
|
|
319a403082 | ||
|
|
90ed1f797e | ||
|
|
bc9dbfd905 | ||
|
|
01609df902 | ||
|
|
6fbb02c196 | ||
|
|
1b81055552 | ||
|
|
de623679fb | ||
|
|
f8cf2eeb82 | ||
|
|
565f252628 | ||
|
|
d49aaa05fe | ||
|
|
df5b2994f6 | ||
|
|
1a43826308 | ||
|
|
69af916d36 | ||
|
|
bf26db019c | ||
|
|
35bf1548c4 | ||
|
|
f7bad72f6a | ||
|
|
f32d060bef | ||
|
|
6f3e0f1eaf | ||
|
|
8681a4dc6d | ||
|
|
07c0a804d7 | ||
|
|
5b93408c6e | ||
|
|
dccd6fe395 | ||
|
|
5a6f36626d | ||
|
|
677f44da6d | ||
|
|
66efe74094 | ||
|
|
5da253e940 | ||
|
|
e439b65daa | ||
|
|
6413c1c3e1 | ||
|
|
ce3c65ef1d | ||
|
|
42bb64c434 | ||
|
|
8f226f72b6 | ||
|
|
4f1a6a8d4f | ||
|
|
9235c341f9 | ||
|
|
b2f446fd6b | ||
|
|
5ea4661e4b | ||
|
|
522ef9ef23 | ||
|
|
bc8b4b8ef9 | ||
|
|
d7c7440ae4 | ||
|
|
527af8a612 | ||
|
|
da51eb8da3 | ||
|
|
b5da962bd1 | ||
|
|
72e51d8c2a | ||
|
|
c9bc41fc58 | ||
|
|
94cc704928 | ||
|
|
6b25ca4bff | ||
|
|
8577745d74 | ||
|
|
232e162f8c | ||
|
|
4c120c4963 | ||
|
|
7c3dc4453a | ||
|
|
3e804a4a09 | ||
|
|
907f2b3824 | ||
|
|
05da5007ed | ||
|
|
791e4c22d2 | ||
|
|
dd3e3ff1ab | ||
|
|
44f2b94164 | ||
|
|
6c7120edbb | ||
|
|
1e34537bb2 | ||
|
|
0d0effd983 | ||
|
|
e85575f3a5 | ||
|
|
0afdcd7005 | ||
|
|
ca8f011844 | ||
|
|
55518176e1 | ||
|
|
e502cf0c82 | ||
|
|
5cf4d7c103 | ||
|
|
c28c4786a1 | ||
|
|
6f974d96e8 | ||
|
|
c2cb15cb73 | ||
|
|
2d648aa580 | ||
|
|
80074b3727 | ||
|
|
e47ca8fbcb | ||
|
|
d011290559 | ||
|
|
50e0f625bc | ||
|
|
94af2374c3 | ||
|
|
a4322faa25 | ||
|
|
23dce2aa3e | ||
|
|
88002b322d | ||
|
|
9246408278 | ||
|
|
fbf1eeedce | ||
|
|
e4c9f2d9fb | ||
|
|
c0c3370d9b | ||
|
|
f6bd13fa06 | ||
|
|
f83616e9bd | ||
|
|
23f385ab2f | ||
|
|
78feb393de | ||
|
|
69fc3cee47 | ||
|
|
a5da77cf49 | ||
|
|
f66e17dd46 | ||
|
|
63c9201938 | ||
|
|
ca7a5979cc | ||
|
|
fc457557e0 | ||
|
|
f044f4dd86 | ||
|
|
a2e71c7e1a | ||
|
|
dff34fa7fc | ||
|
|
08103bb7bb | ||
|
|
4091ef0496 | ||
|
|
381f8369a3 | ||
|
|
a95d67e31c | ||
|
|
b0920bbe70 | ||
|
|
ed55c746c2 | ||
|
|
8e10eac5c5 | ||
|
|
43f1c53538 | ||
|
|
3acac5d53b | ||
|
|
66f0d1264a | ||
|
|
ee713c5146 | ||
|
|
6181903c83 | ||
|
|
7acd2def69 | ||
|
|
eb0909033e | ||
|
|
f6d862eac0 | ||
|
|
5a6005f1eb | ||
|
|
f6633cbac9 | ||
|
|
d8ef7cda20 | ||
|
|
7622e636e4 | ||
|
|
bb018fd725 | ||
|
|
863dabb93a | ||
|
|
6f5f16aa1c | ||
|
|
0f1ca436cd | ||
|
|
4929635eb8 | ||
|
|
3f12f75fae | ||
|
|
e255d472a6 | ||
|
|
2ffd4daeaf | ||
|
|
accb310337 | ||
|
|
2b4c088100 | ||
|
|
a591833dbf | ||
|
|
b3668a6f5c | ||
|
|
b05c3c8c42 | ||
|
|
13bc190f8b | ||
|
|
1b90484bb8 | ||
|
|
05359d6976 | ||
|
|
cc4d74c490 | ||
|
|
d81116382f | ||
|
|
94cc880568 | ||
|
|
24b907f4bb | ||
|
|
8166b2dbed | ||
|
|
14187f6f30 | ||
|
|
90682d3ebd | ||
|
|
f4057aa28b | ||
|
|
3c2548aa69 | ||
|
|
7bab9c3cb5 | ||
|
|
355628f895 | ||
|
|
eaffc6dcac | ||
|
|
4baec420c8 | ||
|
|
cd89e8043f | ||
|
|
1324ff8897 | ||
|
|
8bc7a62b9e | ||
|
|
0c49ac3f02 | ||
|
|
8b4ab4c86c | ||
|
|
f8f69e3157 | ||
|
|
4e75e131c4 | ||
|
|
e599e56415 | ||
|
|
956598c427 | ||
|
|
618ae1e5d0 | ||
|
|
7763a971f2 | ||
|
|
d2b7c80369 | ||
|
|
241176d8a4 | ||
|
|
719f9b62c9 | ||
|
|
483c5255bb | ||
|
|
03e6dcd220 | ||
|
|
22ddbd83c2 | ||
|
|
6b930c365b | ||
|
|
1f6851f33b | ||
|
|
61224b4e6b | ||
|
|
6f2771e7b5 | ||
|
|
c21beccb7a | ||
|
|
40f5e9ca1e | ||
|
|
ed8c0475bf | ||
|
|
d7d7d3c8dc | ||
|
|
945ac74690 | ||
|
|
fff6f48001 | ||
|
|
28ef8e99ac | ||
|
|
28bb36b321 | ||
|
|
ab2cb477a8 | ||
|
|
abc5029fce | ||
|
|
e7f67ebea7 | ||
|
|
c7f0228786 | ||
|
|
741714504a | ||
|
|
be8a6689ff | ||
|
|
de9c54e57a | ||
|
|
53fc9ebd99 | ||
|
|
4159ba7668 | ||
|
|
064f84ccd3 | ||
|
|
8ac1e3a501 | ||
|
|
11bc4eecc6 | ||
|
|
d92cff92b9 | ||
|
|
00f12b5742 | ||
|
|
4f1f57b1ba | ||
|
|
a240a4b982 | ||
|
|
508e0be335 | ||
|
|
e1644401da | ||
|
|
c83eb59963 | ||
|
|
f874e30151 | ||
|
|
b278f5a41d | ||
|
|
ce91e3b420 | ||
|
|
30a6b14443 | ||
|
|
87152f8706 | ||
|
|
48eb2a2a7c | ||
|
|
816ad09a50 | ||
|
|
a2228e80c2 | ||
|
|
230de9184d | ||
|
|
3c781069ca | ||
|
|
7274a8bd4b | ||
|
|
3382306342 | ||
|
|
ad47739c8f | ||
|
|
2f33b14b3d | ||
|
|
4025efcd48 | ||
|
|
f41a2afead | ||
|
|
ba75f7badb | ||
|
|
55c954117a | ||
|
|
4d083f8d10 | ||
|
|
9ad2af18bd | ||
|
|
47fc96b98f | ||
|
|
d4bba88fb1 | ||
|
|
c1367671b6 | ||
|
|
1e572067ec | ||
|
|
8aa9f040f6 | ||
|
|
96bb34485e | ||
|
|
159f0f8d9b | ||
|
|
c4d5c89e5f | ||
|
|
6ac67b68fc | ||
|
|
0af35d7149 | ||
|
|
f91c7a9948 | ||
|
|
cff8cddd13 | ||
|
|
d724f11297 | ||
|
|
80aabea29b | ||
|
|
f8bb011eac | ||
|
|
ef68dfa54c | ||
|
|
8ab65d06b5 | ||
|
|
9c82914e7c | ||
|
|
95717f16c9 | ||
|
|
5a58ae294b | ||
|
|
0aa5c80393 | ||
|
|
23a8904193 | ||
|
|
d5e63cd0bf | ||
|
|
1b1dc5cdfe | ||
|
|
cd1162f85c | ||
|
|
ad7a22b218 | ||
|
|
e3a18cd7a7 | ||
|
|
8611bfa44a | ||
|
|
b3c0625659 | ||
|
|
40e5893bb8 | ||
|
|
e1b49e6d1c | ||
|
|
fae2e81b4a | ||
|
|
f5a02df17b | ||
|
|
d813aa2fa9 | ||
|
|
8d8d06557e | ||
|
|
3ae46c3c18 | ||
|
|
1fce28e72f | ||
|
|
ac3caa5564 | ||
|
|
790ee9e6eb | ||
|
|
0656bb7851 | ||
|
|
5ed8204cb1 | ||
|
|
1220d54a65 | ||
|
|
642d20b48d | ||
|
|
58bb226ba5 | ||
|
|
080c21b66b | ||
|
|
4944355b75 | ||
|
|
3c1d60d6d2 | ||
|
|
5bdd4453cd | ||
|
|
5b333fc85c | ||
|
|
90e4e33dcd | ||
|
|
3135bd74e3 | ||
|
|
7c98ae3d5a | ||
|
|
908f33cd61 | ||
|
|
9eaba7be7c | ||
|
|
b3db9d88c1 | ||
|
|
9006cde1b3 | ||
|
|
8f75bb8299 | ||
|
|
faf64aadf1 | ||
|
|
65760b1189 | ||
|
|
24679d4e02 | ||
|
|
8b9977a7c4 | ||
|
|
ae71b4b63b | ||
|
|
c462b5948f | ||
|
|
a2c1dc6a89 | ||
|
|
98391bff63 | ||
|
|
adadaba75e | ||
|
|
0024beaedc | ||
|
|
63feece074 | ||
|
|
9e280d7f48 | ||
|
|
7c7be95d6b | ||
|
|
c20beb1be7 | ||
|
|
e08c5e7f38 | ||
|
|
3b819346fa | ||
|
|
2467411bae | ||
|
|
b436b31970 | ||
|
|
e3ce565ca6 | ||
|
|
e04981698c | ||
|
|
07a67ed54e | ||
|
|
9a8e584aca | ||
|
|
fa7ae114f5 | ||
|
|
8a4326bc0d | ||
|
|
ae20f2b5ac | ||
|
|
cd6eb1ba53 | ||
|
|
8454008b45 | ||
|
|
fce4629120 | ||
|
|
fd0db2ad55 | ||
|
|
7f9688c7a1 | ||
|
|
f1e15a54ee | ||
|
|
0f7f449d7f | ||
|
|
b27982a742 | ||
|
|
30f5a3e84e | ||
|
|
786fc8d34f | ||
|
|
8de4508876 | ||
|
|
e640e4fce0 | ||
|
|
4f8b2aba9b | ||
|
|
1517767129 | ||
|
|
3dedd41557 | ||
|
|
69f939c0cd | ||
|
|
cd5872bd2d | ||
|
|
1c6583f925 | ||
|
|
40ac320261 | ||
|
|
1851327df7 | ||
|
|
0e93f1e285 | ||
|
|
7e0814a57e | ||
|
|
c6bcb79928 | ||
|
|
134673691e | ||
|
|
d226ba536e | ||
|
|
b50fe88474 | ||
|
|
37ae453ff7 | ||
|
|
ee772c4cec | ||
|
|
ed030375e5 | ||
|
|
ad36e21051 | ||
|
|
4ea9a96d88 | ||
|
|
f0f9b80bdf | ||
|
|
7a0c0a8200 | ||
|
|
5270755c15 | ||
|
|
b7e4a73c27 | ||
|
|
da5d19ef28 | ||
|
|
c68cfcea8a | ||
|
|
38ed8ecc3c | ||
|
|
a5d3e00f2b | ||
|
|
9de06a6bf9 | ||
|
|
a6a3a57361 | ||
|
|
148c1a6cdd | ||
|
|
817905cf5a | ||
|
|
6158b42ad1 | ||
|
|
80539f27c3 | ||
|
|
35652e60a3 | ||
|
|
2ad8b1f1ce | ||
|
|
b316c0159f | ||
|
|
f78ee15605 | ||
|
|
15d0209a29 | ||
|
|
386c99d057 | ||
|
|
fd99a405fd | ||
|
|
54f52e5427 | ||
|
|
ec949888d8 | ||
|
|
0d52ead66e | ||
|
|
b715993435 | ||
|
|
7e817e1cbf | ||
|
|
4abdbf1ad2 | ||
|
|
087c22d833 | ||
|
|
789b5bf607 | ||
|
|
e42f81a404 | ||
|
|
20bf7dd91d | ||
|
|
4a8c19b115 | ||
|
|
a1a17ce6f7 | ||
|
|
00c4d248fd | ||
|
|
073196ccd2 | ||
|
|
635b362ca0 | ||
|
|
53f35c1da0 | ||
|
|
030ed2a03d | ||
|
|
c28d731a66 | ||
|
|
ef6683b550 | ||
|
|
f6a75c8f53 | ||
|
|
7c1a59c102 | ||
|
|
32a188fa85 | ||
|
|
ce8fb796b3 | ||
|
|
1bd705d2e1 | ||
|
|
1a635de69c | ||
|
|
353ae0db16 | ||
|
|
9f66842322 | ||
|
|
94c0f66a26 | ||
|
|
b0e7d4d5df | ||
|
|
9b5252d54c | ||
|
|
1d3c822ef3 | ||
|
|
df78f3333d | ||
|
|
b45e625298 | ||
|
|
7390e7ed45 | ||
|
|
f210ef8877 | ||
|
|
2964ede174 | ||
|
|
cc837fa863 | ||
|
|
87bfdf11a8 | ||
|
|
c30492609e | ||
|
|
2f733891db | ||
|
|
3eb66284e0 | ||
|
|
1645759742 | ||
|
|
da8beb65bf | ||
|
|
d30433658b | ||
|
|
d372fa1fa4 | ||
|
|
8ff3fb9b89 | ||
|
|
696479024e | ||
|
|
9dfa0284fa | ||
|
|
a12f374aba | ||
|
|
8ee1825f84 | ||
|
|
f99c33d794 | ||
|
|
417b371454 | ||
|
|
2ffb20201c | ||
|
|
89efd38c10 | ||
|
|
49f50a53d5 | ||
|
|
5d9afe03ef | ||
|
|
1637765aad | ||
|
|
ce628ea604 | ||
|
|
ff4307c3c6 | ||
|
|
e5f59fa0f9 | ||
|
|
04662df347 | ||
|
|
4ea66fbd18 | ||
|
|
d96c98f4ce | ||
|
|
31db6a76e5 | ||
|
|
a996f3c4f4 | ||
|
|
4334c870f5 | ||
|
|
2b1367a09b | ||
|
|
add1650cbd | ||
|
|
04f2dfac97 | ||
|
|
060ce516b1 | ||
|
|
78b6f2d858 | ||
|
|
d77c83ce09 | ||
|
|
a34d29befc | ||
|
|
914d0c98a8 | ||
|
|
55cb35d12b | ||
|
|
860a75fee6 | ||
|
|
e380c7b1f1 | ||
|
|
2381f26c61 | ||
|
|
de454bbf5a | ||
|
|
e19e06a652 | ||
|
|
6deafaadae | ||
|
|
ef3953a586 | ||
|
|
1d3fdffed6 | ||
|
|
94b2aff637 | ||
|
|
9ce2321d01 | ||
|
|
d75530b78b | ||
|
|
f64f68d5a9 | ||
|
|
be7705efc3 | ||
|
|
df2e29b74d | ||
|
|
b73fdff702 | ||
|
|
664936feb2 | ||
|
|
4b1eb6519c | ||
|
|
86aeed93db | ||
|
|
7993e73ac2 | ||
|
|
d922808fd8 | ||
|
|
8d22737fd3 | ||
|
|
d442755814 | ||
|
|
0afdf71305 | ||
|
|
ecf3eb872e | ||
|
|
646c879edc | ||
|
|
cbe9cbe32a | ||
|
|
4796404b7e | ||
|
|
3e2c88c5bc | ||
|
|
0124356cd7 | ||
|
|
f2ef6f19a5 | ||
|
|
5e0d6522b0 | ||
|
|
c72aaf63cb | ||
|
|
a9e77673f4 | ||
|
|
aa93d3e30f | ||
|
|
15a19e1998 | ||
|
|
e1ccb58d8f | ||
|
|
d0ebb7ad38 | ||
|
|
2bfbacf972 | ||
|
|
e29b4c47fd | ||
|
|
b4deedf367 | ||
|
|
68e507dbba | ||
|
|
66f30c0565 | ||
|
|
340bafba3d | ||
|
|
e47776bb10 | ||
|
|
422f099e5e | ||
|
|
bd4886ae3c | ||
|
|
d5fd2060dd | ||
|
|
7c28279a58 | ||
|
|
27b7cdbf12 | ||
|
|
24fb8746a4 | ||
|
|
45cb545a82 | ||
|
|
8f5c73837a | ||
|
|
b90f9329ca | ||
|
|
968950f783 | ||
|
|
5b0883e1fe | ||
|
|
420030998f | ||
|
|
de2bcf114c | ||
|
|
ab5ae5cbb6 | ||
|
|
94b3dba034 | ||
|
|
1a47543be9 | ||
|
|
407e162ec2 | ||
|
|
35106d4dbe | ||
|
|
d6af319ad8 | ||
|
|
85e7576311 | ||
|
|
7ac7be37af | ||
|
|
669aeb377a | ||
|
|
b655afb533 | ||
|
|
e9949ce3d7 | ||
|
|
91a3dd5866 | ||
|
|
026487a833 | ||
|
|
f8ef096784 | ||
|
|
0c2089bd4c | ||
|
|
5ad68b9ced | ||
|
|
9286b3c965 | ||
|
|
c1edf24338 | ||
|
|
d38fc18390 | ||
|
|
f4cedd1e91 | ||
|
|
fc66c3b91f | ||
|
|
2ac7d4f31f | ||
|
|
a96762b942 | ||
|
|
0a0f7a03d7 | ||
|
|
8148e191ae | ||
|
|
25a985464a | ||
|
|
333ece706e | ||
|
|
daf10395a3 | ||
|
|
9231b1a661 | ||
|
|
3e56d4cdb9 | ||
|
|
cec39ba2ce | ||
|
|
7a997156ed | ||
|
|
c02b4f1256 | ||
|
|
babfd5f35f | ||
|
|
be5ceae8c7 | ||
|
|
6be4080f05 | ||
|
|
b0693751c1 | ||
|
|
8250a39534 | ||
|
|
2131e99826 | ||
|
|
628043b228 | ||
|
|
362a30e5e7 | ||
|
|
828de41d6c | ||
|
|
876ce5764b | ||
|
|
59b287df7f | ||
|
|
3fd125178f | ||
|
|
136faac448 | ||
|
|
85a0bfb898 | ||
|
|
4718f3897c | ||
|
|
068d5c1031 | ||
|
|
4dfa1a9236 | ||
|
|
6ba6ec9d32 | ||
|
|
c373b38b59 | ||
|
|
f2af5b7256 | ||
|
|
483f840401 | ||
|
|
ff12c33e7f | ||
|
|
e4cecf670b | ||
|
|
94e0e3ca67 | ||
|
|
a9d582cc6a | ||
|
|
ab9396937c | ||
|
|
c1e546ede6 | ||
|
|
63ba28c9b0 | ||
|
|
6caf72df82 | ||
|
|
2273438373 | ||
|
|
50a0d86e3d | ||
|
|
50aa24d86d | ||
|
|
674bff7a13 | ||
|
|
757ed180fb | ||
|
|
76648a2926 | ||
|
|
0b76edc362 | ||
|
|
c39061c7fc | ||
|
|
fb2723d80b | ||
|
|
f84341a197 | ||
|
|
6e9156d49c | ||
|
|
cd6d9a5aa0 | ||
|
|
3e5b876a32 | ||
|
|
a9487a332c | ||
|
|
de924f6a4f | ||
|
|
03a27b6683 | ||
|
|
0b719b9b77 | ||
|
|
fc34d21cb8 | ||
|
|
bb05d48fe9 | ||
|
|
949480517f | ||
|
|
a20ebd7120 | ||
|
|
d49bb8632b | ||
|
|
10e57c3f97 | ||
|
|
be95662e35 | ||
|
|
105fb9a3ce | ||
|
|
30783ce79c | ||
|
|
a46f09ee1c | ||
|
|
cdabf629b6 | ||
|
|
40cda1bed7 | ||
|
|
92ab62d03e | ||
|
|
31012b8f7d | ||
|
|
4b0d01d8f0 | ||
|
|
648eb6c71d | ||
|
|
b19a028aea | ||
|
|
9588f2b4a4 | ||
|
|
cd2802b8b5 | ||
|
|
c5ff55a193 | ||
|
|
1cf309faf2 | ||
|
|
8dc5bd6e04 | ||
|
|
37f09049a4 | ||
|
|
eaa18cd502 | ||
|
|
b9d461e5df | ||
|
|
89ea0166fa | ||
|
|
77fd78e408 | ||
|
|
edeb7c8886 | ||
|
|
4481fe2336 | ||
|
|
ef43873c3b | ||
|
|
0bec0b26ab | ||
|
|
681c824ef4 | ||
|
|
950b6f83d9 | ||
|
|
7f5d704a26 | ||
|
|
a6a8ab71ce | ||
|
|
de56d2f6dd | ||
|
|
2f96b77b73 | ||
|
|
0548c2e45f | ||
|
|
0b60fef246 | ||
|
|
3ccd4d07e9 | ||
|
|
b5f1b35213 | ||
|
|
90eda5639f | ||
|
|
77d0c7ae2e | ||
|
|
493eb075f1 | ||
|
|
247d2b18ec | ||
|
|
6b8a14d831 | ||
|
|
743b2c1bf4 | ||
|
|
17f3df7e36 | ||
|
|
dab683c9dc | ||
|
|
e829ca0ff4 | ||
|
|
e27c32cf6e | ||
|
|
a2b243ad94 | ||
|
|
05640cd49f | ||
|
|
037dbbd4e9 | ||
|
|
0994e4eda5 | ||
|
|
235f065a9c | ||
|
|
52bc6312e1 | ||
|
|
d735cca970 | ||
|
|
af5ba6ead8 | ||
|
|
5978896936 | ||
|
|
2ffafd4607 | ||
|
|
e7ed4fc05f | ||
|
|
1da2f58a53 | ||
|
|
284af7fc7a | ||
|
|
dbc4a08ad0 | ||
|
|
c55e9be0ef | ||
|
|
b740e491e6 | ||
|
|
10ed62cfd5 | ||
|
|
8d430b6cb1 | ||
|
|
cdb1981358 | ||
|
|
f51ea8bfdc | ||
|
|
a45e66733b | ||
|
|
23f97c61ac | ||
|
|
cbb728b9a2 | ||
|
|
9bb2d2b7cc | ||
|
|
3f10a147dd | ||
|
|
d112f43f63 | ||
|
|
bf9ebfc4b4 | ||
|
|
d3aa2be9ab | ||
|
|
034e012227 | ||
|
|
05c82809c6 | ||
|
|
79f85fda9b | ||
|
|
6ad8e08b0c | ||
|
|
3bcc83a8fc | ||
|
|
6c86188564 | ||
|
|
1d9393d39b | ||
|
|
4663df1059 | ||
|
|
817e0b78f6 | ||
|
|
4070f12cbf | ||
|
|
8e50b59671 | ||
|
|
61ffdc61f3 | ||
|
|
ff45ccd346 | ||
|
|
5a791e98af | ||
|
|
0d767af80a | ||
|
|
bb971a9937 | ||
|
|
c88d3ad62a | ||
|
|
1a242711cc | ||
|
|
77732c49a0 | ||
|
|
b9b22ad041 | ||
|
|
7a73e4912c | ||
|
|
3129b097af | ||
|
|
1967ef57be | ||
|
|
b95c2c781b | ||
|
|
ea0e334feb | ||
|
|
de03a5167a | ||
|
|
12e09020d1 | ||
|
|
e85ac9f8bf | ||
|
|
9f54f2d08f | ||
|
|
f5624007c8 | ||
|
|
9b6c33c300 | ||
|
|
b66cfb4cc1 | ||
|
|
a5bcf382e0 | ||
|
|
539d66806a | ||
|
|
768f3e1318 | ||
|
|
a968f494fa | ||
|
|
911bb22b2d | ||
|
|
58796824bb | ||
|
|
f3697f6aad | ||
|
|
5374be1a0f | ||
|
|
1d874b5dbe | ||
|
|
586fc08768 | ||
|
|
fa1359f1b8 | ||
|
|
b12fc47951 | ||
|
|
5a2d51c060 | ||
|
|
3396743d73 | ||
|
|
e75b75164f | ||
|
|
224c442567 | ||
|
|
669920d4b5 | ||
|
|
626490545a | ||
|
|
5a0ec9431f | ||
|
|
bc8c5db898 | ||
|
|
c877497cc9 | ||
|
|
4675976f08 | ||
|
|
f484635321 | ||
|
|
d0f3bad818 | ||
|
|
4e6efc2678 | ||
|
|
4ce170e73d | ||
|
|
04e92a4ccd | ||
|
|
43d2ff0a09 | ||
|
|
78e4d5e179 | ||
|
|
3d0b5b9d34 | ||
|
|
dab52b4af7 | ||
|
|
a313a96a8e | ||
|
|
a507b98cbe | ||
|
|
2de73cb138 | ||
|
|
20a8603ffe | ||
|
|
ebe713e881 | ||
|
|
28c38a4b9e | ||
|
|
1faebcf2e2 | ||
|
|
db388f47a6 | ||
|
|
a0cc9e943b | ||
|
|
9ac64f5c75 | ||
|
|
7b15dcd2dc | ||
|
|
fda0bf7fd4 | ||
|
|
7d155adcf1 | ||
|
|
ebc2cb2d03 | ||
|
|
b144fa28e7 | ||
|
|
f9f32a4b0e | ||
|
|
b916b89f7e | ||
|
|
32bb98fd4f | ||
|
|
ad6e7dff43 | ||
|
|
a48bf7bbe0 | ||
|
|
de8dfb10aa | ||
|
|
833668e8f2 | ||
|
|
8a0800ca90 | ||
|
|
4fc40274db | ||
|
|
8c116cf66a | ||
|
|
acda80d35c | ||
|
|
1534e913a3 | ||
|
|
b9e834104a | ||
|
|
0aabd23c61 | ||
|
|
ccba416e85 | ||
|
|
8258222cc5 | ||
|
|
f70eb17afb | ||
|
|
5ad8bd8e2c | ||
|
|
9a20d507a7 | ||
|
|
f2c5ca820a | ||
|
|
ce7c497c09 | ||
|
|
1226a802e4 | ||
|
|
f20c42319b | ||
|
|
a5e882ec25 | ||
|
|
9d5302b1b7 | ||
|
|
bdb859c15c | ||
|
|
22c35412ac | ||
|
|
f2af0b7ed5 | ||
|
|
5d6546ba58 | ||
|
|
f4fe211bbe | ||
|
|
f950633a86 | ||
|
|
65b6111512 | ||
|
|
2db2a914f2 | ||
|
|
345ef0567e | ||
|
|
189890259e | ||
|
|
e771e20ac4 | ||
|
|
5e78725afa | ||
|
|
fc14ef25ed | ||
|
|
b48c96fc37 | ||
|
|
87e54acd8a | ||
|
|
555ddec5e3 | ||
|
|
ff99b07ef1 | ||
|
|
26056a8366 | ||
|
|
3692e32dfb | ||
|
|
11efb6be5e | ||
|
|
900424ca66 | ||
|
|
bd2376e920 | ||
|
|
def4f94ffd | ||
|
|
bbc0ce4e9c | ||
|
|
8b6c0d1c39 | ||
|
|
d55327d344 | ||
|
|
c7ba55fc83 | ||
|
|
85b370291d | ||
|
|
81ad30c633 | ||
|
|
ba2c46c1cb | ||
|
|
0eb095bf76 | ||
|
|
8fbb7e5019 | ||
|
|
48d63e4854 | ||
|
|
ea781e569b | ||
|
|
a8c5bef7c0 | ||
|
|
b188a2691e | ||
|
|
20967519fd | ||
|
|
09a37a2be4 | ||
|
|
7bc9dc1826 | ||
|
|
2345148fde | ||
|
|
50bf003022 | ||
|
|
695444159e | ||
|
|
0533f0c96e | ||
|
|
52182099eb | ||
|
|
e250eeede6 | ||
|
|
4aa7f05630 | ||
|
|
3a805f8be3 | ||
|
|
588e650d80 | ||
|
|
baddb93c4e | ||
|
|
c9a58513e1 | ||
|
|
8f13aedc19 | ||
|
|
c0a05401c9 | ||
|
|
ae384490d6 | ||
|
|
e3ed4ba998 | ||
|
|
43480dd997 | ||
|
|
6874d9eede | ||
|
|
1b646d5528 | ||
|
|
827b5d3a86 | ||
|
|
0beeebc351 | ||
|
|
f4047d5500 | ||
|
|
4deced8827 | ||
|
|
c260147714 | ||
|
|
81e9f772a1 | ||
|
|
8b11c5b661 | ||
|
|
009f36f16c | ||
|
|
73dec72a74 | ||
|
|
ee9188b082 | ||
|
|
fdf383b2d7 | ||
|
|
7fd8fda107 | ||
|
|
1255a79a57 | ||
|
|
b4e1ed2697 | ||
|
|
0377223a67 | ||
|
|
dd9cd82732 | ||
|
|
91468709aa | ||
|
|
23c21bcafd | ||
|
|
78b643792e | ||
|
|
532cfa9cd9 | ||
|
|
cf6b13d156 | ||
|
|
9440d8eb04 | ||
|
|
14f6a4a8be | ||
|
|
93e45c6942 | ||
|
|
6dc44ec002 | ||
|
|
4211cea92e | ||
|
|
1740bda985 | ||
|
|
189d1f7b2a | ||
|
|
86ebbaa618 | ||
|
|
2e7708e3cd | ||
|
|
cb522f2f06 | ||
|
|
97f7f28f6e | ||
|
|
3ed036b51d | ||
|
|
a3e81e1d87 | ||
|
|
e56e65207f | ||
|
|
f6ea7243ea | ||
|
|
f9f4f291da | ||
|
|
d1c2777906 | ||
|
|
0860283407 | ||
|
|
cb025ae8d4 | ||
|
|
c058bcbb6b | ||
|
|
9bf470ef29 | ||
|
|
11d4fead2c | ||
|
|
9c0bc3efc3 | ||
|
|
386f4ff958 | ||
|
|
97bf35bf93 | ||
|
|
f8fd239eec | ||
|
|
72378947b5 | ||
|
|
b0125b63e7 | ||
|
|
b60a841044 | ||
|
|
44c3d43fa0 | ||
|
|
7a605dd5c8 | ||
|
|
aae56043cf | ||
|
|
09d35d248f | ||
|
|
03fe1c770a | ||
|
|
1dd75c4bc3 | ||
|
|
8b6500adfd | ||
|
|
3cf474e6e8 | ||
|
|
820225b684 | ||
|
|
eddf361893 | ||
|
|
2d6206b84f | ||
|
|
074a869d6e | ||
|
|
0c305fee08 | ||
|
|
5e37a308fe | ||
|
|
bc1f89ff68 | ||
|
|
788a63cfa6 | ||
|
|
a87b571376 | ||
|
|
0b99be5b30 | ||
|
|
c3adc2981f | ||
|
|
5fce2a2e27 | ||
|
|
d5846bc88d | ||
|
|
2217f7ca61 | ||
|
|
4c56ec283d | ||
|
|
bc0841d8a1 | ||
|
|
d4cbe03d0d | ||
|
|
df79b2ed8e | ||
|
|
29882db39b | ||
|
|
073569f292 | ||
|
|
d6694a9b41 | ||
|
|
5369fbc3aa | ||
|
|
03d9785408 | ||
|
|
fbcad738bb | ||
|
|
fd20b25a30 | ||
|
|
aa54ff18e5 | ||
|
|
44286ce473 | ||
|
|
4824033619 | ||
|
|
493ae9306d | ||
|
|
25902edf5b | ||
|
|
3d9bbff7a7 | ||
|
|
02be7abe32 | ||
|
|
4dd2922b5f | ||
|
|
06dd3ba62f | ||
|
|
1ae85758f0 | ||
|
|
f12940bbcd | ||
|
|
247a698757 | ||
|
|
47d84b9947 | ||
|
|
f6ad8f57cf | ||
|
|
24cb8f0103 | ||
|
|
f52447acd9 | ||
|
|
3c03719863 | ||
|
|
5bafc99741 | ||
|
|
20061b484e | ||
|
|
88c4257816 | ||
|
|
1a544e4624 | ||
|
|
eb85477086 | ||
|
|
5faacd3328 | ||
|
|
04cad7b9a5 | ||
|
|
60d6489c69 | ||
|
|
0e555179f8 | ||
|
|
3d672d6ac2 | ||
|
|
b27b814b49 | ||
|
|
cc6af6e5e8 | ||
|
|
252475f72e | ||
|
|
e3ba938582 | ||
|
|
1cdccfa884 | ||
|
|
e84e89b786 | ||
|
|
fb35da42ed | ||
|
|
d91ce7e469 | ||
|
|
cbc55a84a7 | ||
|
|
0694d61294 | ||
|
|
f102195354 | ||
|
|
a5571306c2 | ||
|
|
60030cdfbb | ||
|
|
181dee9991 | ||
|
|
bcb2b5d783 | ||
|
|
af95cddd8d | ||
|
|
6b26af4dca | ||
|
|
b9b3995f02 | ||
|
|
d0c2dc1be8 | ||
|
|
544d3e0144 | ||
|
|
c369f67745 | ||
|
|
76fc3f1a77 | ||
|
|
baf5d6f27f | ||
|
|
497ffb8c2f | ||
|
|
7b5acc89a4 | ||
|
|
eb22400db0 | ||
|
|
79e5f4445b | ||
|
|
0f15698970 | ||
|
|
419a766c6f | ||
|
|
19b5923f48 | ||
|
|
2f2e19aa91 | ||
|
|
ae412375ef | ||
|
|
21c1efe319 | ||
|
|
4206605a2d | ||
|
|
9d5eec4246 | ||
|
|
3941762bf3 | ||
|
|
648c41d707 | ||
|
|
c7f419ca2d | ||
|
|
fe8dad42f7 | ||
|
|
6a036d68f6 | ||
|
|
b755d3f827 | ||
|
|
25776a1d96 | ||
|
|
9ec23ae367 | ||
|
|
43927a6dbb | ||
|
|
b8bcbb3ecc | ||
|
|
b7625ef55c | ||
|
|
b7565177be | ||
|
|
a7aed67c65 | ||
|
|
b4f8fafb63 | ||
|
|
04724f4900 | ||
|
|
b2e81fb50d | ||
|
|
1754333ac0 | ||
|
|
f3799213b6 | ||
|
|
bfa7768781 | ||
|
|
acd7d8d8ec | ||
|
|
bdb8bf3ac4 | ||
|
|
a0d9d74d5d | ||
|
|
68f039fbb0 | ||
|
|
9afae3b6b0 | ||
|
|
c7624535e9 | ||
|
|
da626334e0 | ||
|
|
aaeda77af4 | ||
|
|
ffddd3e989 | ||
|
|
4b8d32ff75 | ||
|
|
53328fa58b |
|
|
@ -0,0 +1,3 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: schollz
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: New issue
|
||||
about: Create an issue
|
||||
title: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
*Read this and delete before submitting:* Thanks for starting a discussion! Please provide as much context as possible so that others can understand your thoughts. If you have a specific change in mind, consider submitting a pull request instead.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Go unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
- run: go version
|
||||
- run: go test -v ./...
|
||||
- name: Build files
|
||||
run: |
|
||||
go version
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc
|
||||
GOARM=5 CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags '-extldflags "-static"' -o croc
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags '-s -extldflags "-sectcreate __TEXT __info_plist Info.plist"' -o croc
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags '-s -extldflags "-sectcreate __TEXT __info_plist Info.plist"' -o croc
|
||||
CGO_ENABLED=0 GOOS=dragonfly GOARCH=amd64 go build -ldflags '' -o croc
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags '' -o croc
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build -ldflags '' -o croc
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build -ldflags '' -o croc
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build -ldflags '' -o croc
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=arm64 go build -ldflags '' -o croc
|
||||
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build -ldflags '' -o croc
|
||||
CGO_ENABLED=0 GOOS=openbsd GOARCH=arm64 go build -ldflags '' -o croc
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.20.7' # go1.20.8+ refuses to build go1.22 code...
|
||||
- name: Build Windows 7
|
||||
run: |
|
||||
go version
|
||||
rm go.mod go.sum
|
||||
go mod init github.com/schollz/croc/v10
|
||||
go mod tidy
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: CD
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the main branch
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: schollz/croc
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm,linux/arm64,linux/386
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
name: Make release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.23'
|
||||
- name: Prepare source tarball
|
||||
run: |
|
||||
git clone -b ${{ github.event.release.name }} --depth 1 https://github.com/schollz/croc croc-${{ github.event.release.name }}
|
||||
cd croc-${{ github.event.release.name }} && go mod tidy && go mod vendor
|
||||
cd .. && tar -czvf croc_${{ github.event.release.name }}_src.tar.gz croc-${{ github.event.release.name }}
|
||||
- name: Build files
|
||||
run: |
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
zip croc_${{ github.event.release.name }}_Windows-64bit.zip croc.exe LICENSE
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
zip croc_${{ github.event.release.name }}_Windows-32bit.zip croc.exe LICENSE
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
zip croc_${{ github.event.release.name }}_Windows-ARM.zip croc.exe LICENSE
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
zip croc_${{ github.event.release.name }}_Windows-ARM64.zip croc.exe LICENSE
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_Linux-64bit.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_Linux-32bit.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_Linux-ARM.tar.gz croc LICENSE
|
||||
GOARM=5 CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_Linux-ARMv5.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags '-extldflags "-static"' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_Linux-ARM64.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags '-s -extldflags "-sectcreate __TEXT __info_plist Info.plist"' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_macOS-64bit.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags '-s -extldflags "-sectcreate __TEXT __info_plist Info.plist"' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_macOS-ARM64.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=dragonfly GOARCH=amd64 go build -ldflags '' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_DragonFlyBSD-64bit.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags '' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_FreeBSD-64bit.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build -ldflags '' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_FreeBSD-ARM64.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build -ldflags '' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_NetBSD-32bit.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build -ldflags '' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_NetBSD-64bit.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=arm64 go build -ldflags '' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_NetBSD-ARM64.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build -ldflags '' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_OpenBSD-64bit.tar.gz croc LICENSE
|
||||
CGO_ENABLED=0 GOOS=openbsd GOARCH=arm64 go build -ldflags '' -o croc
|
||||
tar -czvf croc_${{ github.event.release.name }}_OpenBSD-ARM64.tar.gz croc LICENSE
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.20.7' # go1.20.8+ refuses to build go1.22 code...
|
||||
- name: Build Windows 7
|
||||
run: |
|
||||
go version
|
||||
rm go.mod go.sum
|
||||
go mod init github.com/schollz/croc/v10
|
||||
go mod tidy
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
zip croc_${{ github.event.release.name }}_Windows7-64bit.zip croc.exe
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc.exe
|
||||
zip croc_${{ github.event.release.name }}_Windows7-32bit.zip croc.exe
|
||||
- name: Create checksums.txt
|
||||
run: |
|
||||
touch croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_src.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Windows-64bit.zip >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Windows-32bit.zip >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Windows-ARM.zip >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Windows-ARM64.zip >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Windows7-64bit.zip >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Windows7-32bit.zip >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Linux-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Linux-32bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Linux-ARM.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Linux-ARMv5.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_Linux-ARM64.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_macOS-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_macOS-ARM64.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_DragonFlyBSD-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_FreeBSD-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_FreeBSD-ARM64.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_NetBSD-32bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_NetBSD-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_NetBSD-ARM64.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_OpenBSD-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
sha256sum croc_${{ github.event.release.name }}_OpenBSD-ARM64.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
croc_${{ github.event.release.name }}_checksums.txt
|
||||
croc_${{ github.event.release.name }}_src.tar.gz
|
||||
croc_${{ github.event.release.name }}_Windows-64bit.zip
|
||||
croc_${{ github.event.release.name }}_Windows-32bit.zip
|
||||
croc_${{ github.event.release.name }}_Windows-ARM.zip
|
||||
croc_${{ github.event.release.name }}_Windows-ARM64.zip
|
||||
croc_${{ github.event.release.name }}_Windows7-64bit.zip
|
||||
croc_${{ github.event.release.name }}_Windows7-32bit.zip
|
||||
croc_${{ github.event.release.name }}_Linux-64bit.tar.gz
|
||||
croc_${{ github.event.release.name }}_Linux-32bit.tar.gz
|
||||
croc_${{ github.event.release.name }}_Linux-ARM.tar.gz
|
||||
croc_${{ github.event.release.name }}_Linux-ARMv5.tar.gz
|
||||
croc_${{ github.event.release.name }}_Linux-ARM64.tar.gz
|
||||
croc_${{ github.event.release.name }}_macOS-64bit.tar.gz
|
||||
croc_${{ github.event.release.name }}_macOS-ARM64.tar.gz
|
||||
croc_${{ github.event.release.name }}_DragonFlyBSD-64bit.tar.gz
|
||||
croc_${{ github.event.release.name }}_FreeBSD-64bit.tar.gz
|
||||
croc_${{ github.event.release.name }}_FreeBSD-ARM64.tar.gz
|
||||
croc_${{ github.event.release.name }}_NetBSD-32bit.tar.gz
|
||||
croc_${{ github.event.release.name }}_NetBSD-64bit.tar.gz
|
||||
croc_${{ github.event.release.name }}_NetBSD-ARM64.tar.gz
|
||||
croc_${{ github.event.release.name }}_OpenBSD-64bit.tar.gz
|
||||
croc_${{ github.event.release.name }}_OpenBSD-ARM64.tar.gz
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
|
||||
#
|
||||
# You can adjust the behavior by modifying this file.
|
||||
# For more information, see:
|
||||
# https://github.com/actions/stale
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '19 12 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'Stale issue message'
|
||||
stale-pr-message: 'Stale pull request message'
|
||||
stale-issue-label: 'no-issue-activity'
|
||||
stale-pr-label: 'no-pr-activity'
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
name: Publish to Winget
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||
with:
|
||||
identifier: schollz.croc
|
||||
installers-regex: '_Windows-\w+\.zip$'
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Binaries
|
||||
/croc
|
||||
/croc.exe
|
||||
zsh_autocomplete
|
||||
bash_autocomplete
|
||||
dist
|
||||
bin
|
||||
croc-stdin*
|
||||
|
||||
.idea/
|
||||
.vscode/
|
||||
src/utils/bigfile.test
|
||||
test1/
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
project_name: croc
|
||||
build:
|
||||
main: main.go
|
||||
binary: croc
|
||||
ldflags: -s -w -X main.Version="v{{.Version}}-{{.Date}}"
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
- netbsd
|
||||
- openbsd
|
||||
- dragonfly
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm
|
||||
- arm64
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
goarm:
|
||||
- 7
|
||||
nfpms:
|
||||
-
|
||||
formats:
|
||||
- deb
|
||||
vendor: "schollz.com"
|
||||
homepage: "https://schollz.com/software/croc/"
|
||||
maintainer: "Zack Scholl <zack.scholl@gmail.com>"
|
||||
description: "A simple, secure, and fast way to transfer data."
|
||||
license: "MIT"
|
||||
file_name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}"
|
||||
replacements:
|
||||
amd64: 64bit
|
||||
386: 32bit
|
||||
arm: ARM
|
||||
arm64: ARM64
|
||||
darwin: macOS
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
openbsd: OpenBSD
|
||||
netbsd: NetBSD
|
||||
freebsd: FreeBSD
|
||||
dragonfly: DragonFlyBSD
|
||||
archives:
|
||||
-
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}"
|
||||
replacements:
|
||||
amd64: 64bit
|
||||
386: 32bit
|
||||
arm: ARM
|
||||
arm64: ARM64
|
||||
darwin: macOS
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
openbsd: OpenBSD
|
||||
netbsd: NetBSD
|
||||
freebsd: FreeBSD
|
||||
dragonfly: DragonFlyBSD
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- zsh_autocomplete
|
||||
- bash_autocomplete
|
||||
|
||||
brews:
|
||||
-
|
||||
tap:
|
||||
owner: schollz
|
||||
name: homebrew-tap
|
||||
folder: Formula
|
||||
description: "croc is a tool that allows any two computers to simply and securely transfer files and folders."
|
||||
homepage: "https://schollz.com/software/croc/"
|
||||
install: |
|
||||
bin.install "croc"
|
||||
|
||||
test: |
|
||||
system "#{bin}/croc --version"
|
||||
|
||||
scoop:
|
||||
bucket:
|
||||
owner: schollz
|
||||
name: scoop-bucket
|
||||
homepage: "https://schollz.com/software/croc/"
|
||||
description: "croc is a tool that allows any two computers to simply and securely transfer files and folders."
|
||||
license: MIT
|
||||
announce:
|
||||
twitter:
|
||||
# Wether its enabled or not.
|
||||
# Defaults to false.
|
||||
enabled: false
|
||||
12
.travis.yml
12
.travis.yml
|
|
@ -10,12 +10,12 @@ install: true
|
|||
|
||||
script:
|
||||
- env GO111MODULE=on go build -v
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/compress
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/croc
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/crypt
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/tcp
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/utils
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/comm
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/compress
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/croc
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/crypt
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/tcp
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/utils
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/comm
|
||||
|
||||
branches:
|
||||
except:
|
||||
|
|
|
|||
13
Dockerfile
13
Dockerfile
|
|
@ -1,15 +1,16 @@
|
|||
FROM golang:1.12-alpine as builder
|
||||
RUN apk add --no-cache git
|
||||
FROM golang:1.22-alpine as builder
|
||||
RUN apk add --no-cache git gcc musl-dev
|
||||
WORKDIR /go/croc
|
||||
COPY . .
|
||||
RUN go build -v
|
||||
RUN go build -v -ldflags="-s -w"
|
||||
|
||||
FROM alpine:latest
|
||||
FROM alpine:latest
|
||||
EXPOSE 9009
|
||||
EXPOSE 9010
|
||||
EXPOSE 9011
|
||||
EXPOSE 9012
|
||||
EXPOSE 9013
|
||||
COPY --from=builder /go/croc/croc /croc
|
||||
ENTRYPOINT ["/croc"]
|
||||
COPY --from=builder /go/croc/croc /go/croc/croc-entrypoint.sh /
|
||||
USER nobody
|
||||
ENTRYPOINT ["/croc-entrypoint.sh"]
|
||||
CMD ["relay"]
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Zack
|
||||
Copyright (c) 2017-2024 Zack
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
419
README.md
419
README.md
|
|
@ -1,138 +1,281 @@
|
|||
|
||||
<p align="center">
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/6550035/46709024-9b23ad00-cbf6-11e8-9fb2-ca8b20b7dbec.jpg"
|
||||
width="408px" border="0" alt="croc">
|
||||
<br>
|
||||
<a href="https://github.com/schollz/croc/releases/latest"><img src="https://img.shields.io/badge/version-v6.1.5-brightgreen.svg?style=flat-square" alt="Version"></a>
|
||||
<a href="https://coveralls.io/github/schollz/croc"><img src="https://img.shields.io/badge/coverage-80%25-green.svg?style=flat-square" alt="Coverage"></a>
|
||||
<a href="https://travis-ci.org/schollz/croc"><img
|
||||
src="https://img.shields.io/travis/schollz/croc.svg?style=flat-square" alt="Build
|
||||
Status"></a>
|
||||
<a href="https://saythanks.io/to/schollz"><img src="https://img.shields.io/badge/Say%20Thanks-!-brightgreen.svg?style=flat-square" alt="Say thanks"></a>
|
||||
</p>
|
||||
|
||||
|
||||
<p align="center"><code>curl https://getcroc.schollz.com | bash</code></p>
|
||||
|
||||
`croc` is a tool that allows any two computers to simply and securely transfer files and folders. AFAIK, *croc* is the only CLI file-transfer tool does **all** of the following:
|
||||
|
||||
- allows **any two computers** to transfer data (using a relay)
|
||||
- provides **end-to-end encryption** (using PAKE)
|
||||
- enables easy **cross-platform** transfers (Windows, Linux, Mac)
|
||||
- allows **multiple file** transfers
|
||||
- allows **resuming transfers** that are interrupted
|
||||
- local server or port-forwarding **not needed**
|
||||
- **faster** than [wormhole](https://github.com/warner/magic-wormhole), [rsync](https://linux.die.net/man/1/rsync), [scp](https://linux.die.net/man/1/scp) through compression and multiplexing (speedups 1.5x to 4x)
|
||||
|
||||
For more information about `croc`, see [my blog post](https://schollz.com/software/croc6).
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
Download [the latest release for your system](https://github.com/schollz/croc/releases/latest), or install a release from the command-line:
|
||||
|
||||
```
|
||||
$ curl https://getcroc.schollz.com | bash
|
||||
```
|
||||
|
||||
|
||||
On macOS you can install the latest release with [Homebrew](https://brew.sh/):
|
||||
|
||||
```
|
||||
$ brew install schollz/tap/croc
|
||||
```
|
||||
|
||||
|
||||
On Windows you can install the latest release with [Scoop](https://scoop.sh/):
|
||||
|
||||
```
|
||||
$ scoop bucket add schollz-bucket https://github.com/schollz/scoop-bucket.git
|
||||
$ scoop install croc
|
||||
```
|
||||
|
||||
|
||||
Or, you can [install Go](https://golang.org/dl/) and build from source (requires Go 1.12+):
|
||||
|
||||
```
|
||||
$ go get -v github.com/schollz/croc/v6
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
To send a file, simply do:
|
||||
|
||||
```
|
||||
$ croc send [file(s)-or-folder]
|
||||
Sending 'file-or-folder' (X MB)
|
||||
Code is: code-phrase
|
||||
```
|
||||
|
||||
Then to receive the file (or folder) on another computer, you can just do
|
||||
|
||||
```
|
||||
$ croc code-phrase
|
||||
```
|
||||
|
||||
The code phrase is used to establish password-authenticated key agreement ([PAKE](https://en.wikipedia.org/wiki/Password-authenticated_key_agreement)) which generates a secret key for the sender and recipient to use for end-to-end encryption.
|
||||
|
||||
There are a number of configurable options (see `--help`). A set of options (like custom relay, ports, and code phrase) can be set using `--remember`.
|
||||
|
||||
### Custom code phrase
|
||||
|
||||
You can send with your own code phrase (must be more than 4 characters).
|
||||
|
||||
```
|
||||
$ croc send --code [code-phrase] [file(s)-or-folder]
|
||||
```
|
||||
|
||||
### Use pipes - stdin and stdout
|
||||
|
||||
You can pipe to `croc`:
|
||||
|
||||
```
|
||||
$ cat [filename] | croc send
|
||||
```
|
||||
|
||||
In this case `croc` will automatically use the stdin data and send and assign a filename like "croc-stdin-123456789". To receive to `stdout` at you can always just use the `--yes` will automatically approve the transfer and pipe it out to `stdout`.
|
||||
|
||||
```
|
||||
$ croc --yes [code-phrase] > out
|
||||
```
|
||||
|
||||
All of the other text printed to the console is going to `stderr` so it will not interfere with the message going to `stdout`.
|
||||
|
||||
### Self-host relay
|
||||
|
||||
The relay is needed to staple the parallel incoming and outgoing connections. By default, `croc` uses a public relay but you can also run your own relay:
|
||||
|
||||
```
|
||||
$ croc relay
|
||||
```
|
||||
|
||||
Make sure to open up TCP ports (see `croc relay --help` for which ports to open).
|
||||
|
||||
You can send files using your relay by entering `--relay` to change the relay that you are using if you want to custom host your own.
|
||||
|
||||
```
|
||||
$ croc --relay "myrelay.example.com:9009" send [filename]
|
||||
```
|
||||
|
||||
If it's easier you can also run a relay with Docker:
|
||||
|
||||
|
||||
```
|
||||
$ docker run -d -p 9009:9009 -p 9010:9010 -p 9011:9011 -p 9012:9012 -p 9013:9013 schollz/croc
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
`croc` has been through many iterations, and I am awed by all the great contributions! If you feel like contributing, in any way, by all means you can send an Issue, a PR, ask a question, or tweet me ([@yakczar](http://ctt.ec/Rq054)).
|
||||
|
||||
Thanks [@warner](https://github.com/warner) for the [idea](https://github.com/warner/magic-wormhole), [@tscholl2](https://github.com/tscholl2) for the [encryption gists](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28), [@skorokithakis](https://github.com/skorokithakis) for [code on proxying two connections](https://www.stavros.io/posts/proxying-two-connections-go/). Finally thanks for making pull requests [@meyermarcel](https://github.com/meyermarcel), [@Girbons](https://github.com/Girbons), [@techtide](https://github.com/techtide), [@heymatthew](https://github.com/heymatthew), [@Lunsford94](https://github.com/Lunsford94), [@lummie](https://github.com/lummie), [@jesuiscamille](https://github.com/jesuiscamille), [@threefjord](https://github.com/threefjord), [@marcossegovia](https://github.com/marcossegovia), [@csleong98](https://github.com/csleong98), [@afotescu](https://github.com/afotescu), [@callmefever](https://github.com/callmefever), [@El-JojA](https://github.com/El-JojA), [@anatolyyyyyy](https://github.com/anatolyyyyyy), [@goggle](https://github.com/goggle), [@smileboywtu](https://github.com/smileboywtu), [@nicolashardy](https://github.com/nicolashardy)!
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/6550035/46709024-9b23ad00-cbf6-11e8-9fb2-ca8b20b7dbec.jpg"
|
||||
width="408px" border="0" alt="croc">
|
||||
<br>
|
||||
<a href="https://github.com/schollz/croc/releases/latest"><img src="https://img.shields.io/badge/version-v10.1.1-brightgreen.svg?style=flat-square" alt="Version"></a>
|
||||
<a href="https://github.com/schollz/croc/actions/workflows/ci.yml"><img
|
||||
src="https://github.com/schollz/croc/actions/workflows/ci.yml/badge.svg" alt="Build
|
||||
Status"></a>
|
||||
<p align="center">This project is supported by <a href="https://github.com/sponsors/schollz">Github sponsors</a>.</p>
|
||||
|
||||
`croc` is a tool that allows any two computers to simply and securely transfer files and folders. AFAIK, *croc* is the only CLI file-transfer tool that does **all** of the following:
|
||||
|
||||
- allows **any two computers** to transfer data (using a relay)
|
||||
- provides **end-to-end encryption** (using PAKE)
|
||||
- enables easy **cross-platform** transfers (Windows, Linux, Mac)
|
||||
- allows **multiple file** transfers
|
||||
- allows **resuming transfers** that are interrupted
|
||||
- local server or port-forwarding **not needed**
|
||||
- **ipv6-first** with ipv4 fallback
|
||||
- can **use proxy**, like tor
|
||||
|
||||
For more information about `croc`, see [my blog post](https://schollz.com/software/croc6) or read a [recent interview I did](https://console.substack.com/p/console-91).
|
||||
|
||||

|
||||
|
||||
## Install
|
||||
|
||||
Download [the latest release for your system](https://github.com/schollz/croc/releases/latest), or install a release from the command-line:
|
||||
|
||||
```
|
||||
curl https://getcroc.schollz.com | bash
|
||||
```
|
||||
|
||||
|
||||
On macOS you can install the latest release with [Homebrew](https://brew.sh/):
|
||||
|
||||
```
|
||||
brew install croc
|
||||
```
|
||||
|
||||
On macOS you can also install the latest release with [MacPorts](https://macports.org/):
|
||||
|
||||
```
|
||||
sudo port selfupdate
|
||||
sudo port install croc
|
||||
```
|
||||
|
||||
On Windows you can install the latest release with [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org), or [Winget](https://learn.microsoft.com/en-us/windows/package-manager/):
|
||||
|
||||
```
|
||||
scoop install croc
|
||||
```
|
||||
|
||||
```
|
||||
choco install croc
|
||||
```
|
||||
|
||||
```
|
||||
winget install schollz.croc
|
||||
```
|
||||
|
||||
On Unix you can install the latest release with [Nix](https://nixos.org/nix):
|
||||
|
||||
```
|
||||
nix-env -i croc
|
||||
```
|
||||
|
||||
|
||||
On Alpine Linux you have to install dependencies first:
|
||||
|
||||
```
|
||||
apk add bash coreutils
|
||||
wget -qO- https://getcroc.schollz.com | bash
|
||||
```
|
||||
|
||||
On Arch Linux you can install the latest release with `pacman`:
|
||||
|
||||
```
|
||||
pacman -S croc
|
||||
```
|
||||
|
||||
On Fedora you can install with `dnf`:
|
||||
|
||||
```
|
||||
dnf install croc
|
||||
```
|
||||
|
||||
On Gentoo you can install with `portage`:
|
||||
```
|
||||
emerge net-misc/croc
|
||||
```
|
||||
|
||||
On Termux you can install with `pkg`:
|
||||
|
||||
```
|
||||
pkg install croc
|
||||
```
|
||||
|
||||
On FreeBSD you can install with `pkg`:
|
||||
|
||||
```
|
||||
pkg install croc
|
||||
```
|
||||
|
||||
On Linux, macOS, and Windows you can install from [conda-forge](https://github.com/conda-forge/croc-feedstock/) globally with [`pixi`](https://pixi.sh/):
|
||||
|
||||
```
|
||||
pixi global install croc
|
||||
```
|
||||
|
||||
or into a particular environment with [`conda`](https://docs.conda.io/projects/conda/):
|
||||
|
||||
```
|
||||
conda install --channel conda-forge croc
|
||||
```
|
||||
|
||||
Or, you can [install Go](https://golang.org/dl/) and build from source (requires Go 1.17+):
|
||||
|
||||
```
|
||||
go install github.com/schollz/croc/v10@latest
|
||||
```
|
||||
|
||||
On Android there is a 3rd party F-Droid app [available to download](https://f-droid.org/en/packages/com.github.howeyc.crocgui/).
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
To send a file, simply do:
|
||||
|
||||
```
|
||||
$ croc send [file(s)-or-folder]
|
||||
Sending 'file-or-folder' (X MB)
|
||||
Code is: code-phrase
|
||||
```
|
||||
|
||||
Then to receive the file (or folder) on another computer, you can just do
|
||||
|
||||
```
|
||||
croc code-phrase
|
||||
```
|
||||
|
||||
The code phrase is used to establish password-authenticated key agreement ([PAKE](https://en.wikipedia.org/wiki/Password-authenticated_key_agreement)) which generates a secret key for the sender and recipient to use for end-to-end encryption.
|
||||
|
||||
There are a number of configurable options (see `--help`). A set of options (like custom relay, ports, and code phrase) can be set using `--remember`.
|
||||
|
||||
### Using `croc` on Linux or Mac OS
|
||||
|
||||
On Linux and Mac OS, the sending & receiving is slightly different to avoid [leaking the secret via the process name](https://nvd.nist.gov/vuln/detail/CVE-2023-43621). On these systems you will need to run `croc` with the secret as an environment variable. For example, to receive with the secret `***`:
|
||||
|
||||
```
|
||||
CROC_SECRET=*** croc
|
||||
```
|
||||
|
||||
This will show only `croc` in the process list of a multi-user system and not leak the secret.
|
||||
|
||||
For a single-user system the default behavior can be permanently enabled by running
|
||||
|
||||
```
|
||||
croc --classic
|
||||
```
|
||||
|
||||
and confirming.
|
||||
Run this command again to disable classic mode.
|
||||
|
||||
### Custom code phrase
|
||||
|
||||
You can send with your own code phrase (must be more than 6 characters).
|
||||
|
||||
```
|
||||
croc send --code [code-phrase] [file(s)-or-folder]
|
||||
```
|
||||
|
||||
### Allow overwriting without prompt
|
||||
|
||||
By default, croc will prompt whether to overwrite a file. You can automatically overwrite files by using the `--overwrite` flag (recipient only). For example, receive a file to automatically overwrite:
|
||||
|
||||
```
|
||||
croc --yes --overwrite <code>
|
||||
```
|
||||
|
||||
|
||||
### Use pipes - stdin and stdout
|
||||
|
||||
You can pipe to `croc`:
|
||||
|
||||
```
|
||||
cat [filename] | croc send
|
||||
```
|
||||
|
||||
In this case `croc` will automatically use the stdin data and send and assign a filename like "croc-stdin-123456789". To receive to `stdout` at you can always just use the `--yes` will automatically approve the transfer and pipe it out to `stdout`.
|
||||
|
||||
```
|
||||
croc --yes [code-phrase] > out
|
||||
```
|
||||
|
||||
All of the other text printed to the console is going to `stderr` so it will not interfere with the message going to `stdout`.
|
||||
|
||||
|
||||
### Send text
|
||||
|
||||
Sometimes you want to send URLs or short text. In addition to piping, you can easily send text with `croc`:
|
||||
|
||||
```
|
||||
croc send --text "hello world"
|
||||
```
|
||||
|
||||
This will automatically tell the receiver to use `stdout` when they receive the text so it will be displayed.
|
||||
|
||||
|
||||
### Use a proxy
|
||||
|
||||
You can use a proxy as your connection to the relay by adding a proxy address with `--socks5`. For example, you can send via a tor relay:
|
||||
|
||||
```
|
||||
croc --socks5 "127.0.0.1:9050" send SOMEFILE
|
||||
```
|
||||
|
||||
### Change encryption curve
|
||||
|
||||
You can choose from several different elliptic curves to use for encryption by using the `--curve` flag. Only the recipient can choose the curve. For example, receive a file using the P-521 curve:
|
||||
|
||||
```
|
||||
croc --curve p521 <codephrase>
|
||||
```
|
||||
|
||||
Available curves are P-256, P-348, P-521 and SIEC. P-256 is the default curve.
|
||||
|
||||
### Change hash algorithm
|
||||
|
||||
You can choose from several different hash algorithms. The default is the `xxhash` algorithm which is fast and thorough. If you want to optimize for speed you can use the `imohash` algorithm which is even faster, but since it samples files (versus reading the whole file) it can mistakenly determine that a file is the same on the two computers transferring - though this is only a problem if you are syncing files versus sending a new file to a computer.
|
||||
|
||||
```
|
||||
croc send --hash imohash SOMEFILE
|
||||
```
|
||||
|
||||
### Self-host relay
|
||||
|
||||
The relay is needed to staple the parallel incoming and outgoing connections. By default, `croc` uses a public relay but you can also run your own relay:
|
||||
|
||||
```
|
||||
croc relay
|
||||
```
|
||||
|
||||
By default it uses TCP ports 9009-9013. Make sure to open those up. You can customize the ports (e.g. `croc relay --ports 1111,1112`), but you must have a minimum of **2** ports for the relay. The first port is for communication and the subsequent ports are used for the multiplexed data transfer.
|
||||
|
||||
You can send files using your relay by entering `--relay` to change the relay that you are using if you want to custom host your own.
|
||||
|
||||
```
|
||||
croc --relay "myrelay.example.com:9009" send [filename]
|
||||
```
|
||||
|
||||
Note, when sending, you only need to include the first port (the communication port). The subsequent ports for data transfer will be transmitted back to the user from the relay.
|
||||
|
||||
#### Self-host relay (docker)
|
||||
|
||||
If it's easier you can also run a relay with Docker:
|
||||
|
||||
|
||||
```
|
||||
docker run -d -p 9009-9013:9009-9013 -e CROC_PASS='YOURPASSWORD' schollz/croc
|
||||
```
|
||||
|
||||
Be sure to include the password for the relay otherwise any requests will be rejected.
|
||||
|
||||
```
|
||||
croc --pass YOURPASSWORD --relay "myreal.example.com:9009" send [filename]
|
||||
```
|
||||
|
||||
Note: when including `--pass YOURPASSWORD` you can instead pass a file with the password, e.g. `--pass FILEWITHPASSWORD`.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
`croc` has gone through many iterations, and I am awed by all the great contributions! If you feel like contributing, in any way, by all means you can send an Issue, a PR, or ask a question.
|
||||
|
||||
Thanks [@warner](https://github.com/warner) for the [idea](https://github.com/warner/magic-wormhole), [@tscholl2](https://github.com/tscholl2) for the [encryption gists](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28), [@skorokithakis](https://github.com/skorokithakis) for [code on proxying two connections](https://www.stavros.io/posts/proxying-two-connections-go/). Finally thanks for making pull requests [@maximbaz](https://github.com/maximbaz), [@meyermarcel](https://github.com/meyermarcel), [@Girbons](https://github.com/Girbons), [@techtide](https://github.com/techtide), [@heymatthew](https://github.com/heymatthew), [@Lunsford94](https://github.com/Lunsford94), [@lummie](https://github.com/lummie), [@jesuiscamille](https://github.com/jesuiscamille), [@threefjord](https://github.com/threefjord), [@marcossegovia](https://github.com/marcossegovia), [@csleong98](https://github.com/csleong98), [@afotescu](https://github.com/afotescu), [@callmefever](https://github.com/callmefever), [@El-JojA](https://github.com/El-JojA), [@anatolyyyyyy](https://github.com/anatolyyyyyy), [@goggle](https://github.com/goggle), [@smileboywtu](https://github.com/smileboywtu), [@nicolashardy](https://github.com/nicolashardy), [@fbartels](https://github.com/fbartels), [@rkuprov](https://github.com/rkuprov), [@hreese](https://github.com/hreese), [@xenrox](https://github.com/xenrox) and [Ipar](https://github.com/lpar)!
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
if [ -n "$CROC_PASS" ]; then
|
||||
set -- --pass "$CROC_PASS" "$@"
|
||||
fi
|
||||
exec /croc "$@"
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[Unit]
|
||||
Description=croc relay
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
DynamicUser=yes
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
ExecStart=/usr/bin/croc relay
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
57
go.mod
57
go.mod
|
|
@ -1,30 +1,39 @@
|
|||
module github.com/schollz/croc/v6
|
||||
module github.com/schollz/croc/v10
|
||||
|
||||
go 1.13
|
||||
go 1.22
|
||||
|
||||
toolchain go1.23.1
|
||||
|
||||
require (
|
||||
github.com/OneOfOne/xxhash v1.2.5 // indirect
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/chzyer/readline v1.5.1
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/kalafut/imohash v1.0.0
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.9 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/schollz/logger v1.0.1
|
||||
github.com/schollz/mnemonicode v1.0.1
|
||||
github.com/schollz/pake v1.1.1
|
||||
github.com/schollz/peerdiscovery v1.4.1
|
||||
github.com/schollz/progressbar/v2 v2.14.0
|
||||
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/urfave/cli v1.22.1
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823 // indirect
|
||||
golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
github.com/kalafut/imohash v1.1.0
|
||||
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b
|
||||
github.com/minio/highwayhash v1.0.3
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
|
||||
github.com/schollz/cli/v2 v2.2.1
|
||||
github.com/schollz/logger v1.2.0
|
||||
github.com/schollz/pake/v3 v3.0.5
|
||||
github.com/schollz/peerdiscovery v1.7.5
|
||||
github.com/schollz/progressbar/v3 v3.17.1
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/crypto v0.29.0
|
||||
golang.org/x/net v0.31.0
|
||||
golang.org/x/sys v0.27.0
|
||||
golang.org/x/term v0.26.0
|
||||
golang.org/x/time v0.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406 // indirect
|
||||
github.com/twmb/murmur3 v1.1.8 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
198
go.sum
198
go.sum
|
|
@ -1,102 +1,148 @@
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
|
||||
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/kalafut/imohash v1.0.0 h1:LgCJ+p/BwM2HKpOxFopkeddpzVCfm15EtXMroXD1SYE=
|
||||
github.com/kalafut/imohash v1.0.0/go.mod h1:c3RHT80ZAp5C/aYgQI92ZlrOymqkZnRDprU87kg75HI=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/kalafut/imohash v1.1.0 h1:Lldcmx0SXgMSoABB2WBD8mTgf0OlVnISn2Dyrfg2Ep8=
|
||||
github.com/kalafut/imohash v1.1.0/go.mod h1:6cn9lU0Sj8M4eu9UaQm1kR/5y3k/ayB68yntRhGloL4=
|
||||
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b h1:xZ59n7Frzh8CwyfAapUZLSg+gXH5m63YEaFCMpDHhpI=
|
||||
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b/go.mod h1:uDd4sYVYsqcxAB8j+Q7uhL6IJCs/r1kxib1HV4bgOMg=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
|
||||
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/schollz/logger v1.0.1 h1:BuBAU+euqphM0Ny9qFVScl4RSxatis4nCHIkOxO2cUU=
|
||||
github.com/schollz/logger v1.0.1/go.mod h1:P6F4/dGMGcx8wh+kG1zrNEd4vnNpEBY/mwEMd/vn6AM=
|
||||
github.com/schollz/mnemonicode v1.0.1 h1:LiH5hwADZwjwnfXsaD4xgnMyTAtaKHN+e5AyjRU6WSU=
|
||||
github.com/schollz/mnemonicode v1.0.1/go.mod h1:cl4UAOhUV0mkdjMj/QYaUZbZZdF8BnOqoz8rHMzwboY=
|
||||
github.com/schollz/pake v1.1.1 h1:QKeojDWzdAdtRC4m89b6HAxw/8gjqrVu7r4SAOxOFg8=
|
||||
github.com/schollz/pake v1.1.1/go.mod h1:aWMxQ1jwqZRwk3StflHcdyzPR+CyW5W7+WIZD6Y3dEY=
|
||||
github.com/schollz/peerdiscovery v1.4.1 h1:xtZ/D8/4eq9O6UEhRupZZiJm4BA8+u1IVUgeHo5VPm4=
|
||||
github.com/schollz/peerdiscovery v1.4.1/go.mod h1:WDdk0/JVyVHVIA/bmhzTkUg32dhJ20O4tExNqV1u6sk=
|
||||
github.com/schollz/progressbar/v2 v2.13.2 h1:3L9bP5KQOGEnFP8P5V8dz+U0yo5I29iY5Oa9s9EAwn0=
|
||||
github.com/schollz/progressbar/v2 v2.13.2/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8=
|
||||
github.com/schollz/progressbar/v2 v2.14.0 h1:vo7bdkI9E4/CIk9DnL5uVIaybLQiVtiCC2vO+u9j5IM=
|
||||
github.com/schollz/progressbar/v2 v2.14.0/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8=
|
||||
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9 h1:y08o5oQ/slxXE/F0uh5dd8mdVvb+w4NLcNSDSq4c2F0=
|
||||
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9/go.mod h1:kCMoQsqzx4MzGJWaALr6tKyCnlrY0kILGLkA1FOiLF4=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
|
||||
github.com/schollz/cli/v2 v2.2.1 h1:ou22Mj7ZPjrKz+8k2iDTWaHskEEV5NiAxGrdsCL36VU=
|
||||
github.com/schollz/cli/v2 v2.2.1/go.mod h1:My6bfphRLZUhZdlFUK8scAxMWHydE7k4s2ed2Dtnn+s=
|
||||
github.com/schollz/logger v1.2.0 h1:5WXfINRs3lEUTCZ7YXhj0uN+qukjizvITLm3Ca2m0Ho=
|
||||
github.com/schollz/logger v1.2.0/go.mod h1:P6F4/dGMGcx8wh+kG1zrNEd4vnNpEBY/mwEMd/vn6AM=
|
||||
github.com/schollz/pake/v3 v3.0.5 h1:MnZVdI987lkjln9BSx/zUb724TZISa2jbO+dPj6BvgQ=
|
||||
github.com/schollz/pake/v3 v3.0.5/go.mod h1:OGbG6htRwSKo6V8R5tg61ufpFmZM1b/PrrSp6g2ZLLc=
|
||||
github.com/schollz/peerdiscovery v1.7.5 h1:0cEhO+o8i4fpeKBwl7u0UY3Kt3XVt5fSzS4rg17ZPb4=
|
||||
github.com/schollz/peerdiscovery v1.7.5/go.mod h1:Crht2FOfD1/eL3U/AIM0vvwVZDPePlBgSX3Xw+TnJoE=
|
||||
github.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U=
|
||||
github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tscholl2/siec v0.0.0-20180721101609-21667da05937 h1:lhssCpSe3TjKcbvUoPzFMuv9oUyZDgI3Cmgolfw2C90=
|
||||
github.com/tscholl2/siec v0.0.0-20180721101609-21667da05937/go.mod h1:KL9+ubr1JZdaKjgAaHr+tCytEncXBa1pR6FjbTsOJnw=
|
||||
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tscholl2/siec v0.0.0-20210707234609-9bdfc483d499/go.mod h1:KL9+ubr1JZdaKjgAaHr+tCytEncXBa1pR6FjbTsOJnw=
|
||||
github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406 h1:sDWDZkwYqX0jvLWstKzFwh+pYhQNaVg65BgSkCP/f7U=
|
||||
github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406/go.mod h1:KL9+ubr1JZdaKjgAaHr+tCytEncXBa1pR6FjbTsOJnw=
|
||||
github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
|
||||
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823 h1:Ypyv6BNJh07T1pUSrehkLemqPKXhus2MkfktJ91kRh4=
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed h1:5TJcLJn2a55mJjzYk0yOoqN8X1OdvBDUnaZaKKyQtkY=
|
||||
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 h1:I7efaDQAsIQmkTF+WSdcydwVWzK07Yuz8IFF8rNkDe0=
|
||||
golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/tylerb/is.v1 v1.1.2 h1:AB/MANFml2ySf+adwcinvajyHvsYltAOD+rb/8njfSU=
|
||||
gopkg.in/tylerb/is.v1 v1.1.2/go.mod h1:9yQB2tyIhZ5oph6Kk5Sq7cJMd9c5Jpa1p3hr9kxzPqo=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
project_name: croc
|
||||
build:
|
||||
main: main.go
|
||||
binary: croc
|
||||
ldflags: -s -w -X main.Version="v{{.Version}}-{{.Date}}"
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
- netbsd
|
||||
- openbsd
|
||||
- dragonfly
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm
|
||||
- arm64
|
||||
goarm:
|
||||
- 7
|
||||
nfpm:
|
||||
formats:
|
||||
- deb
|
||||
vendor: "schollz.com"
|
||||
homepage: "https://schollz.com/software/croc/"
|
||||
maintainer: "Zack Scholl <zack.scholl@gmail.com>"
|
||||
description: "A simple, secure, and fast way to transfer data."
|
||||
license: "MIT"
|
||||
name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}"
|
||||
replacements:
|
||||
amd64: 64bit
|
||||
386: 32bit
|
||||
arm: ARM
|
||||
arm64: ARM64
|
||||
darwin: macOS
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
openbsd: OpenBSD
|
||||
netbsd: NetBSD
|
||||
freebsd: FreeBSD
|
||||
dragonfly: DragonFlyBSD
|
||||
archives:
|
||||
-
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}"
|
||||
replacements:
|
||||
amd64: 64bit
|
||||
386: 32bit
|
||||
arm: ARM
|
||||
arm64: ARM64
|
||||
darwin: macOS
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
openbsd: OpenBSD
|
||||
netbsd: NetBSD
|
||||
freebsd: FreeBSD
|
||||
dragonfly: DragonFlyBSD
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
|
||||
brew:
|
||||
github:
|
||||
owner: schollz
|
||||
name: homebrew-tap
|
||||
folder: Formula
|
||||
description: "croc is a tool that allows any two computers to simply and securely transfer files and folders."
|
||||
homepage: "https://schollz.com/software/croc/"
|
||||
install: |
|
||||
bin.install "croc"
|
||||
|
||||
test: |
|
||||
system "#{bin}/croc --version"
|
||||
|
||||
scoop:
|
||||
bucket:
|
||||
owner: schollz
|
||||
name: scoop-bucket
|
||||
homepage: "https://schollz.com/software/croc/"
|
||||
description: "croc is a tool that allows any two computers to simply and securely transfer files and folders."
|
||||
license: MIT
|
||||
47
main.go
47
main.go
|
|
@ -1,18 +1,55 @@
|
|||
package main
|
||||
|
||||
//go:generate git tag -af v$VERSION -m "v$VERSION"
|
||||
//go:generate go run src/install/updateversion.go
|
||||
//go:generate git commit -am "bump $VERSION"
|
||||
//go:generate git tag -af v$VERSION -m "v$VERSION"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/schollz/croc/v6/src/cli"
|
||||
"github.com/schollz/croc/v10/src/cli"
|
||||
"github.com/schollz/croc/v10/src/utils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := cli.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
// "github.com/pkg/profile"
|
||||
// go func() {
|
||||
// for {
|
||||
// f, err := os.Create("croc.pprof")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// runtime.GC() // get up-to-date statistics
|
||||
// if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// f.Close()
|
||||
// time.Sleep(3 * time.Second)
|
||||
// fmt.Println("wrote profile")
|
||||
// }
|
||||
// }()
|
||||
|
||||
// Create a channel to receive OS signals
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
if err := cli.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Exit the program gracefully
|
||||
utils.RemoveMarkedFiles()
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
// Wait for a termination signal
|
||||
_ = <-sigs
|
||||
utils.RemoveMarkedFiles()
|
||||
|
||||
// Exit the program gracefully
|
||||
os.Exit(0)
|
||||
}
|
||||
|
|
|
|||
621
src/cli/cli.go
621
src/cli/cli.go
|
|
@ -5,26 +5,30 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/croc/v6/src/croc"
|
||||
"github.com/schollz/croc/v6/src/models"
|
||||
"github.com/schollz/croc/v6/src/tcp"
|
||||
"github.com/schollz/croc/v6/src/utils"
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/schollz/cli/v2"
|
||||
"github.com/schollz/croc/v10/src/comm"
|
||||
"github.com/schollz/croc/v10/src/croc"
|
||||
"github.com/schollz/croc/v10/src/mnemonicode"
|
||||
"github.com/schollz/croc/v10/src/models"
|
||||
"github.com/schollz/croc/v10/src/tcp"
|
||||
"github.com/schollz/croc/v10/src/utils"
|
||||
log "github.com/schollz/logger"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/schollz/pake/v3"
|
||||
)
|
||||
|
||||
// Version specifies the version
|
||||
var Version string
|
||||
|
||||
// Run will run the command line proram
|
||||
// Run will run the command line program
|
||||
func Run() (err error) {
|
||||
// use all of the processors
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
|
@ -32,96 +36,197 @@ func Run() (err error) {
|
|||
app := cli.NewApp()
|
||||
app.Name = "croc"
|
||||
if Version == "" {
|
||||
Version = "v6.1.5-af39b8c"
|
||||
Version = "v10.1.0"
|
||||
}
|
||||
app.Version = Version
|
||||
app.Compiled = time.Now()
|
||||
app.Usage = "easily and securely transfer stuff from one computer to another"
|
||||
app.UsageText = "croc allows any two computers to directly and securely transfer files"
|
||||
// app.ArgsUsage = "[args and such]"
|
||||
app.Commands = []cli.Command{
|
||||
app.UsageText = `croc [GLOBAL OPTIONS] [COMMAND] [COMMAND OPTIONS] [filename(s) or folder]
|
||||
|
||||
USAGE EXAMPLES:
|
||||
Send a file:
|
||||
croc send file.txt
|
||||
|
||||
-git to respect your .gitignore
|
||||
Send multiple files:
|
||||
croc send file1.txt file2.txt file3.txt
|
||||
or
|
||||
croc send *.jpg
|
||||
|
||||
Send everything in a folder:
|
||||
croc send example-folder-name
|
||||
|
||||
Send a file with a custom code:
|
||||
croc send --code secret-code file.txt
|
||||
|
||||
Receive a file using code:
|
||||
croc secret-code`
|
||||
app.Commands = []*cli.Command{
|
||||
{
|
||||
Name: "send",
|
||||
Usage: "send a file",
|
||||
Description: "send a file over the relay",
|
||||
ArgsUsage: "[filename]",
|
||||
Usage: "send file(s), or folder (see options with croc send -h)",
|
||||
Description: "send file(s), or folder, over the relay",
|
||||
ArgsUsage: "[filename(s) or folder]",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "code, c", Usage: "codephrase used to connect to relay"},
|
||||
cli.BoolFlag{Name: "no-local", Usage: "disable local relay when sending"},
|
||||
cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the local relay (optional)"},
|
||||
&cli.BoolFlag{Name: "zip", Usage: "zip folder before sending"},
|
||||
&cli.StringFlag{Name: "code", Aliases: []string{"c"}, Usage: "codephrase used to connect to relay"},
|
||||
&cli.StringFlag{Name: "hash", Value: "xxhash", Usage: "hash algorithm (xxhash, imohash, md5)"},
|
||||
&cli.StringFlag{Name: "text", Aliases: []string{"t"}, Usage: "send some text"},
|
||||
&cli.BoolFlag{Name: "no-local", Usage: "disable local relay when sending"},
|
||||
&cli.BoolFlag{Name: "no-multi", Usage: "disable multiplexing"},
|
||||
&cli.BoolFlag{Name: "git", Usage: "enable .gitignore respect / don't send ignored files"},
|
||||
&cli.IntFlag{Name: "port", Value: 9009, Usage: "base port for the relay"},
|
||||
&cli.IntFlag{Name: "transfers", Value: 4, Usage: "number of ports to use for transfers"},
|
||||
&cli.BoolFlag{Name: "qrcode", Aliases: []string{"qr"}, Usage: "show receive code as a qrcode"},
|
||||
},
|
||||
HelpName: "croc send",
|
||||
Action: func(c *cli.Context) error {
|
||||
return send(c)
|
||||
},
|
||||
Action: send,
|
||||
},
|
||||
{
|
||||
Name: "relay",
|
||||
Usage: "start your own relay (optional)",
|
||||
Description: "start relay",
|
||||
HelpName: "croc relay",
|
||||
Action: func(c *cli.Context) error {
|
||||
return relay(c)
|
||||
},
|
||||
Action: relay,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the relay"},
|
||||
&cli.StringFlag{Name: "host", Usage: "host of the relay"},
|
||||
&cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the relay"},
|
||||
&cli.IntFlag{Name: "port", Value: 9009, Usage: "base port for the relay"},
|
||||
&cli.IntFlag{Name: "transfers", Value: 5, Usage: "number of ports to use for relay"},
|
||||
},
|
||||
},
|
||||
}
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{Name: "remember", Usage: "save these settings to reuse next time"},
|
||||
cli.BoolFlag{Name: "debug", Usage: "increase verbosity (a lot)"},
|
||||
cli.BoolFlag{Name: "yes", Usage: "automatically agree to all prompts"},
|
||||
cli.BoolFlag{Name: "stdout", Usage: "redirect file to stdout"},
|
||||
cli.BoolFlag{Name: "ask", Usage: "make sure sender and recipient are prompted"},
|
||||
cli.StringFlag{Name: "relay", Value: models.DEFAULT_RELAY, Usage: "address of the relay"},
|
||||
cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"},
|
||||
&cli.BoolFlag{Name: "internal-dns", Usage: "use a built-in DNS stub resolver rather than the host operating system"},
|
||||
&cli.BoolFlag{Name: "classic", Usage: "toggle between the classic mode (insecure due to local attack vector) and new mode (secure)"},
|
||||
&cli.BoolFlag{Name: "remember", Usage: "save these settings to reuse next time"},
|
||||
&cli.BoolFlag{Name: "debug", Usage: "toggle debug mode"},
|
||||
&cli.BoolFlag{Name: "yes", Usage: "automatically agree to all prompts"},
|
||||
&cli.BoolFlag{Name: "stdout", Usage: "redirect file to stdout"},
|
||||
&cli.BoolFlag{Name: "no-compress", Usage: "disable compression"},
|
||||
&cli.BoolFlag{Name: "ask", Usage: "make sure sender and recipient are prompted"},
|
||||
&cli.BoolFlag{Name: "local", Usage: "force to use only local connections"},
|
||||
&cli.BoolFlag{Name: "ignore-stdin", Usage: "ignore piped stdin"},
|
||||
&cli.BoolFlag{Name: "overwrite", Usage: "do not prompt to overwrite or resume"},
|
||||
&cli.BoolFlag{Name: "testing", Usage: "flag for testing purposes"},
|
||||
&cli.StringFlag{Name: "multicast", Value: "239.255.255.250", Usage: "multicast address to use for local discovery"},
|
||||
&cli.StringFlag{Name: "curve", Value: "p256", Usage: "choose an encryption curve (" + strings.Join(pake.AvailableCurves(), ", ") + ")"},
|
||||
&cli.StringFlag{Name: "ip", Value: "", Usage: "set sender ip if known e.g. 10.0.0.1:9009, [::1]:9009"},
|
||||
&cli.StringFlag{Name: "relay", Value: models.DEFAULT_RELAY, Usage: "address of the relay", EnvVars: []string{"CROC_RELAY"}},
|
||||
&cli.StringFlag{Name: "relay6", Value: models.DEFAULT_RELAY6, Usage: "ipv6 address of the relay", EnvVars: []string{"CROC_RELAY6"}},
|
||||
&cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"},
|
||||
&cli.StringFlag{Name: "pass", Value: models.DEFAULT_PASSPHRASE, Usage: "password for the relay", EnvVars: []string{"CROC_PASS"}},
|
||||
&cli.StringFlag{Name: "socks5", Value: "", Usage: "add a socks5 proxy", EnvVars: []string{"SOCKS5_PROXY"}},
|
||||
&cli.StringFlag{Name: "connect", Value: "", Usage: "add a http proxy", EnvVars: []string{"HTTP_PROXY"}},
|
||||
&cli.StringFlag{Name: "throttleUpload", Value: "", Usage: "Throttle the upload speed e.g. 500k"},
|
||||
}
|
||||
app.EnableBashCompletion = true
|
||||
app.HideHelp = false
|
||||
app.HideVersion = false
|
||||
app.BashComplete = func(c *cli.Context) {
|
||||
fmt.Fprintf(c.App.Writer, "send\nreceive\relay")
|
||||
}
|
||||
app.Action = func(c *cli.Context) error {
|
||||
allStringsAreFiles := func(strs []string) bool {
|
||||
for _, str := range strs {
|
||||
if !utils.Exists(str) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// check if "classic" is set
|
||||
classicFile := getClassicConfigFile(true)
|
||||
classicInsecureMode := utils.Exists(classicFile)
|
||||
if c.Bool("classic") {
|
||||
if classicInsecureMode {
|
||||
// classic mode not enabled
|
||||
fmt.Print(`Classic mode is currently ENABLED.
|
||||
|
||||
Disabling this mode will prevent the shared secret from being visible
|
||||
on the host's process list when passed via the command line. On a
|
||||
multi-user system, this will help ensure that other local users cannot
|
||||
access the shared secret and receive the files instead of the intended
|
||||
recipient.
|
||||
|
||||
Do you wish to continue to DISABLE the classic mode? (y/N) `)
|
||||
choice := strings.ToLower(utils.GetInput(""))
|
||||
if choice == "y" || choice == "yes" {
|
||||
os.Remove(classicFile)
|
||||
fmt.Print("\nClassic mode DISABLED.\n\n")
|
||||
fmt.Print(`To send and receive, export the CROC_SECRET variable with the code phrase:
|
||||
|
||||
Send: CROC_SECRET=*** croc send file.txt
|
||||
|
||||
Receive: CROC_SECRET=*** croc` + "\n\n")
|
||||
} else {
|
||||
fmt.Print("\nClassic mode ENABLED.\n")
|
||||
|
||||
}
|
||||
} else {
|
||||
// enable classic mode
|
||||
// touch the file
|
||||
fmt.Print(`Classic mode is currently DISABLED.
|
||||
|
||||
Please note that enabling this mode will make the shared secret visible
|
||||
on the host's process list when passed via the command line. On a
|
||||
multi-user system, this could allow other local users to access the
|
||||
shared secret and receive the files instead of the intended recipient.
|
||||
|
||||
Do you wish to continue to enable the classic mode? (y/N) `)
|
||||
choice := strings.ToLower(utils.GetInput(""))
|
||||
if choice == "y" || choice == "yes" {
|
||||
fmt.Print("\nClassic mode ENABLED.\n\n")
|
||||
os.WriteFile(classicFile, []byte("enabled"), 0o644)
|
||||
fmt.Print(`To send and receive, use the code phrase:
|
||||
|
||||
Send: croc send --code *** file.txt
|
||||
|
||||
Receive: croc ***` + "\n\n")
|
||||
} else {
|
||||
fmt.Print("\nClassic mode DISABLED.\n")
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// if trying to send but forgot send, let the user know
|
||||
if c.Args().First() != "" && utils.Exists(c.Args().First()) {
|
||||
_, fname := filepath.Split(c.Args().First())
|
||||
yn := utils.GetInput(fmt.Sprintf("Did you mean to send '%s'? (y/n) ", fname))
|
||||
if strings.ToLower(yn) == "y" {
|
||||
if c.Args().Present() && allStringsAreFiles(c.Args().Slice()) {
|
||||
fnames := []string{}
|
||||
for _, fpath := range c.Args().Slice() {
|
||||
_, basename := filepath.Split(fpath)
|
||||
fnames = append(fnames, "'"+basename+"'")
|
||||
}
|
||||
promptMessage := fmt.Sprintf("Did you mean to send %s? (Y/n) ", strings.Join(fnames, ", "))
|
||||
choice := strings.ToLower(utils.GetInput(promptMessage))
|
||||
if choice == "" || choice == "y" || choice == "yes" {
|
||||
return send(c)
|
||||
}
|
||||
}
|
||||
|
||||
return receive(c)
|
||||
}
|
||||
|
||||
return app.Run(os.Args)
|
||||
}
|
||||
|
||||
func getConfigDir() (homedir string, err error) {
|
||||
homedir, err = os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
homedir = path.Join(homedir, ".config", "croc")
|
||||
if _, err = os.Stat(homedir); os.IsNotExist(err) {
|
||||
log.Debugf("creating home directory %s", homedir)
|
||||
err = os.MkdirAll(homedir, 0700)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setDebugLevel(c *cli.Context) {
|
||||
if c.GlobalBool("debug") {
|
||||
if c.Bool("debug") {
|
||||
log.SetLevel("debug")
|
||||
log.Debug("debug mode on")
|
||||
// print the public IP address
|
||||
ip, err := utils.PublicIP()
|
||||
if err == nil {
|
||||
log.Debugf("public IP address: %s", ip)
|
||||
} else {
|
||||
log.Debug(err)
|
||||
}
|
||||
|
||||
} else {
|
||||
log.SetLevel("info")
|
||||
}
|
||||
}
|
||||
|
||||
func getConfigFile() string {
|
||||
configFile, err := getConfigDir()
|
||||
func getSendConfigFile(requireValidPath bool) string {
|
||||
configFile, err := utils.GetConfigDir(requireValidPath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return ""
|
||||
|
|
@ -129,21 +234,85 @@ func getConfigFile() string {
|
|||
return path.Join(configFile, "send.json")
|
||||
}
|
||||
|
||||
func getClassicConfigFile(requireValidPath bool) string {
|
||||
configFile, err := utils.GetConfigDir(requireValidPath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return ""
|
||||
}
|
||||
return path.Join(configFile, "classic_enabled")
|
||||
}
|
||||
|
||||
func getReceiveConfigFile(requireValidPath bool) (string, error) {
|
||||
configFile, err := utils.GetConfigDir(requireValidPath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
return path.Join(configFile, "receive.json"), nil
|
||||
}
|
||||
|
||||
func determinePass(c *cli.Context) (pass string) {
|
||||
pass = c.String("pass")
|
||||
b, err := os.ReadFile(pass)
|
||||
if err == nil {
|
||||
pass = strings.TrimSpace(string(b))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func send(c *cli.Context) (err error) {
|
||||
setDebugLevel(c)
|
||||
crocOptions := croc.Options{
|
||||
SharedSecret: c.String("code"),
|
||||
IsSender: true,
|
||||
Debug: c.GlobalBool("debug"),
|
||||
NoPrompt: c.GlobalBool("yes"),
|
||||
RelayAddress: c.GlobalString("relay"),
|
||||
Stdout: c.GlobalBool("stdout"),
|
||||
DisableLocal: c.Bool("no-local"),
|
||||
RelayPorts: strings.Split(c.String("ports"), ","),
|
||||
Ask: c.GlobalBool("ask"),
|
||||
comm.Socks5Proxy = c.String("socks5")
|
||||
comm.HttpProxy = c.String("connect")
|
||||
|
||||
portParam := c.Int("port")
|
||||
if portParam == 0 {
|
||||
portParam = 9009
|
||||
}
|
||||
b, errOpen := ioutil.ReadFile(getConfigFile())
|
||||
if errOpen == nil && !c.GlobalBool("remember") {
|
||||
transfersParam := c.Int("transfers")
|
||||
if transfersParam == 0 {
|
||||
transfersParam = 4
|
||||
}
|
||||
|
||||
ports := make([]string, transfersParam+1)
|
||||
for i := 0; i <= transfersParam; i++ {
|
||||
ports[i] = strconv.Itoa(portParam + i)
|
||||
}
|
||||
|
||||
crocOptions := croc.Options{
|
||||
SharedSecret: c.String("code"),
|
||||
IsSender: true,
|
||||
Debug: c.Bool("debug"),
|
||||
NoPrompt: c.Bool("yes"),
|
||||
RelayAddress: c.String("relay"),
|
||||
RelayAddress6: c.String("relay6"),
|
||||
Stdout: c.Bool("stdout"),
|
||||
DisableLocal: c.Bool("no-local"),
|
||||
OnlyLocal: c.Bool("local"),
|
||||
IgnoreStdin: c.Bool("ignore-stdin"),
|
||||
RelayPorts: ports,
|
||||
Ask: c.Bool("ask"),
|
||||
NoMultiplexing: c.Bool("no-multi"),
|
||||
RelayPassword: determinePass(c),
|
||||
SendingText: c.String("text") != "",
|
||||
NoCompress: c.Bool("no-compress"),
|
||||
Overwrite: c.Bool("overwrite"),
|
||||
Curve: c.String("curve"),
|
||||
HashAlgorithm: c.String("hash"),
|
||||
ThrottleUpload: c.String("throttleUpload"),
|
||||
ZipFolder: c.Bool("zip"),
|
||||
GitIgnore: c.Bool("git"),
|
||||
ShowQrCode: c.Bool("qrcode"),
|
||||
MulticastAddress: c.String("multicast"),
|
||||
}
|
||||
if crocOptions.RelayAddress != models.DEFAULT_RELAY {
|
||||
crocOptions.RelayAddress6 = ""
|
||||
} else if crocOptions.RelayAddress6 != models.DEFAULT_RELAY6 {
|
||||
crocOptions.RelayAddress = ""
|
||||
}
|
||||
b, errOpen := os.ReadFile(getSendConfigFile(false))
|
||||
if errOpen == nil && !c.Bool("remember") {
|
||||
var rememberedOptions croc.Options
|
||||
err = json.Unmarshal(b, &rememberedOptions)
|
||||
if err != nil {
|
||||
|
|
@ -151,46 +320,105 @@ func send(c *cli.Context) (err error) {
|
|||
return
|
||||
}
|
||||
// update anything that isn't explicitly set
|
||||
if !c.GlobalIsSet("relay") {
|
||||
if !c.IsSet("relay") && rememberedOptions.RelayAddress != "" {
|
||||
crocOptions.RelayAddress = rememberedOptions.RelayAddress
|
||||
}
|
||||
if !c.IsSet("no-local") {
|
||||
crocOptions.DisableLocal = rememberedOptions.DisableLocal
|
||||
}
|
||||
if !c.IsSet("ports") {
|
||||
if !c.IsSet("ports") && len(rememberedOptions.RelayPorts) > 0 {
|
||||
crocOptions.RelayPorts = rememberedOptions.RelayPorts
|
||||
}
|
||||
if !c.IsSet("code") {
|
||||
crocOptions.SharedSecret = rememberedOptions.SharedSecret
|
||||
}
|
||||
if !c.IsSet("pass") && rememberedOptions.RelayPassword != "" {
|
||||
crocOptions.RelayPassword = rememberedOptions.RelayPassword
|
||||
}
|
||||
if !c.IsSet("relay6") && rememberedOptions.RelayAddress6 != "" {
|
||||
crocOptions.RelayAddress6 = rememberedOptions.RelayAddress6
|
||||
}
|
||||
if !c.IsSet("overwrite") {
|
||||
crocOptions.Overwrite = rememberedOptions.Overwrite
|
||||
}
|
||||
if !c.IsSet("curve") && rememberedOptions.Curve != "" {
|
||||
crocOptions.Curve = rememberedOptions.Curve
|
||||
}
|
||||
if !c.IsSet("local") {
|
||||
crocOptions.OnlyLocal = rememberedOptions.OnlyLocal
|
||||
}
|
||||
if !c.IsSet("hash") {
|
||||
crocOptions.HashAlgorithm = rememberedOptions.HashAlgorithm
|
||||
}
|
||||
if !c.IsSet("git") {
|
||||
crocOptions.GitIgnore = rememberedOptions.GitIgnore
|
||||
}
|
||||
}
|
||||
|
||||
var fnames []string
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
if ((stat.Mode() & os.ModeCharDevice) == 0) && !c.Bool("ignore-stdin") {
|
||||
fnames, err = getStdin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
utils.MarkFileForRemoval(fnames[0])
|
||||
defer func() {
|
||||
err = os.Remove(fnames[0])
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
e := os.Remove(fnames[0])
|
||||
if e != nil {
|
||||
log.Error(e)
|
||||
}
|
||||
}()
|
||||
} else if c.String("text") != "" {
|
||||
fnames, err = makeTempFileWithString(c.String("text"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
utils.MarkFileForRemoval(fnames[0])
|
||||
defer func() {
|
||||
e := os.Remove(fnames[0])
|
||||
if e != nil {
|
||||
log.Error(e)
|
||||
}
|
||||
}()
|
||||
|
||||
} else {
|
||||
fnames = append([]string{c.Args().First()}, c.Args().Tail()...)
|
||||
fnames = c.Args().Slice()
|
||||
}
|
||||
if len(fnames) == 0 {
|
||||
return errors.New("must specify file: croc send [filename]")
|
||||
return errors.New("must specify file: croc send [filename(s) or folder]")
|
||||
}
|
||||
|
||||
classicInsecureMode := utils.Exists(getClassicConfigFile(true))
|
||||
if !classicInsecureMode {
|
||||
// if operating system is UNIX, then use environmental variable to set the code
|
||||
if (!(runtime.GOOS == "windows") && c.IsSet("code")) || os.Getenv("CROC_SECRET") != "" {
|
||||
crocOptions.SharedSecret = os.Getenv("CROC_SECRET")
|
||||
if crocOptions.SharedSecret == "" {
|
||||
fmt.Printf(`On UNIX systems, to send with a custom code phrase,
|
||||
you need to set the environmental variable CROC_SECRET:
|
||||
|
||||
CROC_SECRET=**** croc send file.txt
|
||||
|
||||
Or you can have the code phrase automatically generated:
|
||||
|
||||
croc send file.txt
|
||||
|
||||
Or you can go back to the classic croc behavior by enabling classic mode:
|
||||
|
||||
croc --classic
|
||||
|
||||
`)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(crocOptions.SharedSecret) == 0 {
|
||||
// generate code phrase
|
||||
crocOptions.SharedSecret = utils.GetRandomName()
|
||||
}
|
||||
|
||||
paths, haveFolder, err := getPaths(fnames)
|
||||
minimalFileInfos, emptyFoldersToTransfer, totalNumberFolders, err := croc.GetFilesInfo(fnames, crocOptions.ZipFolder, crocOptions.GitIgnore)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -203,16 +431,13 @@ func send(c *cli.Context) (err error) {
|
|||
// save the config
|
||||
saveConfig(c, crocOptions)
|
||||
|
||||
err = cr.Send(croc.TransferOptions{
|
||||
PathToFiles: paths,
|
||||
KeepPathInRemote: haveFolder,
|
||||
})
|
||||
err = cr.Send(minimalFileInfos, emptyFoldersToTransfer, totalNumberFolders)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getStdin() (fnames []string, err error) {
|
||||
f, err := ioutil.TempFile(".", "croc-stdin-")
|
||||
f, err := os.CreateTemp(".", "croc-stdin-")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -228,40 +453,28 @@ func getStdin() (fnames []string, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func getPaths(fnames []string) (paths []string, haveFolder bool, err error) {
|
||||
haveFolder = false
|
||||
paths = []string{}
|
||||
for _, fname := range fnames {
|
||||
stat, errStat := os.Stat(fname)
|
||||
if errStat != nil {
|
||||
err = errStat
|
||||
return
|
||||
}
|
||||
if stat.IsDir() {
|
||||
haveFolder = true
|
||||
err = filepath.Walk(fname,
|
||||
func(pathName string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
paths = append(paths, filepath.ToSlash(pathName))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
paths = append(paths, filepath.ToSlash(fname))
|
||||
}
|
||||
func makeTempFileWithString(s string) (fnames []string, err error) {
|
||||
f, err := os.CreateTemp(".", "croc-stdin-")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = f.WriteString(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fnames = []string{f.Name()}
|
||||
return
|
||||
}
|
||||
|
||||
func saveConfig(c *cli.Context, crocOptions croc.Options) {
|
||||
if c.GlobalBool("remember") {
|
||||
configFile := getConfigFile()
|
||||
if c.Bool("remember") {
|
||||
configFile := getSendConfigFile(true)
|
||||
log.Debug("saving config file")
|
||||
var bConfig []byte
|
||||
// if the code wasn't set, don't save it
|
||||
|
|
@ -273,7 +486,7 @@ func saveConfig(c *cli.Context, crocOptions croc.Options) {
|
|||
log.Error(err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(configFile, bConfig, 0644)
|
||||
err = os.WriteFile(configFile, bConfig, 0o644)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
|
|
@ -282,53 +495,159 @@ func saveConfig(c *cli.Context, crocOptions croc.Options) {
|
|||
}
|
||||
}
|
||||
|
||||
func receive(c *cli.Context) (err error) {
|
||||
crocOptions := croc.Options{
|
||||
SharedSecret: c.String("code"),
|
||||
IsSender: false,
|
||||
Debug: c.GlobalBool("debug"),
|
||||
NoPrompt: c.GlobalBool("yes"),
|
||||
RelayAddress: c.GlobalString("relay"),
|
||||
Stdout: c.GlobalBool("stdout"),
|
||||
Ask: c.GlobalBool("ask"),
|
||||
type TabComplete struct{}
|
||||
|
||||
func (t TabComplete) Do(line []rune, pos int) ([][]rune, int) {
|
||||
var words = strings.SplitAfter(string(line), "-")
|
||||
var lastPartialWord = words[len(words)-1]
|
||||
var nbCharacter = len(lastPartialWord)
|
||||
if nbCharacter == 0 {
|
||||
// No completion
|
||||
return [][]rune{[]rune("")}, 0
|
||||
}
|
||||
if c.Args().First() != "" {
|
||||
if len(words) == 1 && nbCharacter == utils.NbPinNumbers {
|
||||
// Check if word is indeed a number
|
||||
_, err := strconv.Atoi(lastPartialWord)
|
||||
if err == nil {
|
||||
return [][]rune{[]rune("-")}, nbCharacter
|
||||
}
|
||||
}
|
||||
var strArray [][]rune
|
||||
for _, s := range mnemonicode.WordList {
|
||||
if strings.HasPrefix(s, lastPartialWord) {
|
||||
var completionCandidate = s[nbCharacter:]
|
||||
if len(words) <= mnemonicode.WordsRequired(utils.NbBytesWords) {
|
||||
completionCandidate += "-"
|
||||
}
|
||||
strArray = append(strArray, []rune(completionCandidate))
|
||||
}
|
||||
}
|
||||
return strArray, nbCharacter
|
||||
}
|
||||
|
||||
func receive(c *cli.Context) (err error) {
|
||||
comm.Socks5Proxy = c.String("socks5")
|
||||
comm.HttpProxy = c.String("connect")
|
||||
crocOptions := croc.Options{
|
||||
SharedSecret: c.String("code"),
|
||||
IsSender: false,
|
||||
Debug: c.Bool("debug"),
|
||||
NoPrompt: c.Bool("yes"),
|
||||
RelayAddress: c.String("relay"),
|
||||
RelayAddress6: c.String("relay6"),
|
||||
Stdout: c.Bool("stdout"),
|
||||
Ask: c.Bool("ask"),
|
||||
RelayPassword: determinePass(c),
|
||||
OnlyLocal: c.Bool("local"),
|
||||
IP: c.String("ip"),
|
||||
Overwrite: c.Bool("overwrite"),
|
||||
Curve: c.String("curve"),
|
||||
TestFlag: c.Bool("testing"),
|
||||
MulticastAddress: c.String("multicast"),
|
||||
}
|
||||
if crocOptions.RelayAddress != models.DEFAULT_RELAY {
|
||||
crocOptions.RelayAddress6 = ""
|
||||
} else if crocOptions.RelayAddress6 != models.DEFAULT_RELAY6 {
|
||||
crocOptions.RelayAddress = ""
|
||||
}
|
||||
|
||||
switch c.Args().Len() {
|
||||
case 1:
|
||||
crocOptions.SharedSecret = c.Args().First()
|
||||
case 3:
|
||||
fallthrough
|
||||
case 4:
|
||||
var phrase []string
|
||||
phrase = append(phrase, c.Args().First())
|
||||
phrase = append(phrase, c.Args().Tail()...)
|
||||
crocOptions.SharedSecret = strings.Join(phrase, "-")
|
||||
}
|
||||
|
||||
// load options here
|
||||
setDebugLevel(c)
|
||||
configFile, err := getConfigDir()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
||||
doRemember := c.Bool("remember")
|
||||
configFile, err := getReceiveConfigFile(doRemember)
|
||||
if err != nil && doRemember {
|
||||
return
|
||||
}
|
||||
configFile = path.Join(configFile, "receive.json")
|
||||
b, errOpen := ioutil.ReadFile(configFile)
|
||||
if errOpen == nil && !c.GlobalBool("remember") {
|
||||
b, errOpen := os.ReadFile(configFile)
|
||||
if errOpen == nil && !doRemember {
|
||||
var rememberedOptions croc.Options
|
||||
err = json.Unmarshal(b, &rememberedOptions)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
// update anything that isn't explicitly set
|
||||
if !c.GlobalIsSet("relay") {
|
||||
// update anything that isn't explicitly Globally set
|
||||
if !c.IsSet("relay") && rememberedOptions.RelayAddress != "" {
|
||||
crocOptions.RelayAddress = rememberedOptions.RelayAddress
|
||||
}
|
||||
if !c.GlobalIsSet("yes") {
|
||||
if !c.IsSet("yes") {
|
||||
crocOptions.NoPrompt = rememberedOptions.NoPrompt
|
||||
}
|
||||
if crocOptions.SharedSecret == "" {
|
||||
crocOptions.SharedSecret = rememberedOptions.SharedSecret
|
||||
}
|
||||
if !c.IsSet("pass") && rememberedOptions.RelayPassword != "" {
|
||||
crocOptions.RelayPassword = rememberedOptions.RelayPassword
|
||||
}
|
||||
if !c.IsSet("relay6") && rememberedOptions.RelayAddress6 != "" {
|
||||
crocOptions.RelayAddress6 = rememberedOptions.RelayAddress6
|
||||
}
|
||||
if !c.IsSet("overwrite") {
|
||||
crocOptions.Overwrite = rememberedOptions.Overwrite
|
||||
}
|
||||
if !c.IsSet("curve") && rememberedOptions.Curve != "" {
|
||||
crocOptions.Curve = rememberedOptions.Curve
|
||||
}
|
||||
if !c.IsSet("local") {
|
||||
crocOptions.OnlyLocal = rememberedOptions.OnlyLocal
|
||||
}
|
||||
}
|
||||
|
||||
if crocOptions.SharedSecret == "" {
|
||||
crocOptions.SharedSecret = utils.GetInput("Enter receive code: ")
|
||||
classicInsecureMode := utils.Exists(getClassicConfigFile(true))
|
||||
if crocOptions.SharedSecret == "" && os.Getenv("CROC_SECRET") != "" {
|
||||
crocOptions.SharedSecret = os.Getenv("CROC_SECRET")
|
||||
} else if !(runtime.GOOS == "windows") && crocOptions.SharedSecret != "" && !classicInsecureMode {
|
||||
crocOptions.SharedSecret = os.Getenv("CROC_SECRET")
|
||||
if crocOptions.SharedSecret == "" {
|
||||
fmt.Printf(`On UNIX systems, to receive with croc you either need
|
||||
to set a code phrase using your environmental variables:
|
||||
|
||||
CROC_SECRET=**** croc
|
||||
|
||||
Or you can specify the code phrase when you run croc without
|
||||
declaring the secret on the command line:
|
||||
|
||||
croc
|
||||
Enter receive code: ****
|
||||
|
||||
Or you can go back to the classic croc behavior by enabling classic mode:
|
||||
|
||||
croc --classic
|
||||
|
||||
`)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
if c.GlobalString("out") != "" {
|
||||
os.Chdir(c.GlobalString("out"))
|
||||
if crocOptions.SharedSecret == "" {
|
||||
l, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "Enter receive code: ",
|
||||
AutoComplete: TabComplete{},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
crocOptions.SharedSecret, err = l.Readline()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.String("out") != "" {
|
||||
if err = os.Chdir(c.String("out")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cr, err := croc.New(crocOptions)
|
||||
|
|
@ -337,7 +656,7 @@ func receive(c *cli.Context) (err error) {
|
|||
}
|
||||
|
||||
// save the config
|
||||
if c.GlobalBool("remember") {
|
||||
if doRemember {
|
||||
log.Debug("saving config file")
|
||||
var bConfig []byte
|
||||
bConfig, err = json.MarshalIndent(crocOptions, "", " ")
|
||||
|
|
@ -345,7 +664,7 @@ func receive(c *cli.Context) (err error) {
|
|||
log.Error(err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(configFile, bConfig, 0644)
|
||||
err = os.WriteFile(configFile, bConfig, 0o644)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
|
|
@ -358,22 +677,42 @@ func receive(c *cli.Context) (err error) {
|
|||
}
|
||||
|
||||
func relay(c *cli.Context) (err error) {
|
||||
log.Infof("starting croc relay version %v", Version)
|
||||
debugString := "info"
|
||||
if c.GlobalBool("debug") {
|
||||
if c.Bool("debug") {
|
||||
debugString = "debug"
|
||||
}
|
||||
ports := strings.Split(c.String("ports"), ",")
|
||||
host := c.String("host")
|
||||
var ports []string
|
||||
|
||||
if c.IsSet("ports") {
|
||||
ports = strings.Split(c.String("ports"), ",")
|
||||
} else {
|
||||
portString := c.Int("port")
|
||||
if portString == 0 {
|
||||
portString = 9009
|
||||
}
|
||||
transfersString := c.Int("transfers")
|
||||
if transfersString == 0 {
|
||||
transfersString = 4
|
||||
}
|
||||
ports = make([]string, transfersString)
|
||||
for i := range ports {
|
||||
ports[i] = strconv.Itoa(portString + i)
|
||||
}
|
||||
}
|
||||
|
||||
tcpPorts := strings.Join(ports[1:], ",")
|
||||
for i, port := range ports {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
go func(portStr string) {
|
||||
err = tcp.Run(debugString, portStr)
|
||||
err := tcp.Run(debugString, host, portStr, determinePass(c))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}(port)
|
||||
}
|
||||
return tcp.Run(debugString, ports[0], tcpPorts)
|
||||
return tcp.Run(debugString, host, ports[0], determinePass(c), tcpPorts)
|
||||
}
|
||||
|
|
|
|||
166
src/comm/comm.go
166
src/comm/comm.go
|
|
@ -4,12 +4,23 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/magisterquis/connectproxy"
|
||||
"github.com/schollz/croc/v10/src/utils"
|
||||
log "github.com/schollz/logger"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
var Socks5Proxy = ""
|
||||
var HttpProxy = ""
|
||||
|
||||
var MAGIC_BYTES = []byte("croc")
|
||||
|
||||
// Comm is some basic TCP communication
|
||||
type Comm struct {
|
||||
connection net.Conn
|
||||
|
|
@ -21,19 +32,73 @@ func NewConnection(address string, timelimit ...time.Duration) (c *Comm, err err
|
|||
if len(timelimit) > 0 {
|
||||
tlimit = timelimit[0]
|
||||
}
|
||||
connection, err := net.DialTimeout("tcp", address, tlimit)
|
||||
var connection net.Conn
|
||||
if Socks5Proxy != "" && !utils.IsLocalIP(address) {
|
||||
var dialer proxy.Dialer
|
||||
// prepend schema if no schema is given
|
||||
if !strings.Contains(Socks5Proxy, `://`) {
|
||||
Socks5Proxy = `socks5://` + Socks5Proxy
|
||||
}
|
||||
socks5ProxyURL, urlParseError := url.Parse(Socks5Proxy)
|
||||
if urlParseError != nil {
|
||||
err = fmt.Errorf("unable to parse socks proxy url: %s", urlParseError)
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
dialer, err = proxy.FromURL(socks5ProxyURL, proxy.Direct)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("proxy failed: %w", err)
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
log.Debug("dialing with dialer.Dial")
|
||||
connection, err = dialer.Dial("tcp", address)
|
||||
} else if HttpProxy != "" && !utils.IsLocalIP(address) {
|
||||
var dialer proxy.Dialer
|
||||
// prepend schema if no schema is given
|
||||
if !strings.Contains(HttpProxy, `://`) {
|
||||
HttpProxy = `http://` + HttpProxy
|
||||
}
|
||||
HttpProxyURL, urlParseError := url.Parse(HttpProxy)
|
||||
if urlParseError != nil {
|
||||
err = fmt.Errorf("unable to parse http proxy url: %s", urlParseError)
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
dialer, err = connectproxy.New(HttpProxyURL, proxy.Direct)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("proxy failed: %w", err)
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
log.Debug("dialing with dialer.Dial")
|
||||
connection, err = dialer.Dial("tcp", address)
|
||||
|
||||
} else {
|
||||
log.Debugf("dialing to %s with timelimit %s", address, tlimit)
|
||||
connection, err = net.DialTimeout("tcp", address, tlimit)
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("comm.NewConnection failed: %w", err)
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
c = New(connection)
|
||||
log.Debugf("connected to '%s'", address)
|
||||
return
|
||||
}
|
||||
|
||||
// New returns a new comm
|
||||
func New(c net.Conn) *Comm {
|
||||
c.SetReadDeadline(time.Now().Add(3 * time.Hour))
|
||||
c.SetDeadline(time.Now().Add(3 * time.Hour))
|
||||
c.SetWriteDeadline(time.Now().Add(3 * time.Hour))
|
||||
if err := c.SetReadDeadline(time.Now().Add(3 * time.Hour)); err != nil {
|
||||
log.Warnf("error setting read deadline: %v", err)
|
||||
}
|
||||
if err := c.SetDeadline(time.Now().Add(3 * time.Hour)); err != nil {
|
||||
log.Warnf("error setting overall deadline: %v", err)
|
||||
}
|
||||
if err := c.SetWriteDeadline(time.Now().Add(3 * time.Hour)); err != nil {
|
||||
log.Errorf("error setting write deadline: %v", err)
|
||||
}
|
||||
comm := new(Comm)
|
||||
comm.connection = c
|
||||
return comm
|
||||
|
|
@ -46,65 +111,78 @@ func (c *Comm) Connection() net.Conn {
|
|||
|
||||
// Close closes the connection
|
||||
func (c *Comm) Close() {
|
||||
c.connection.Close()
|
||||
if err := c.connection.Close(); err != nil {
|
||||
log.Warnf("error closing connection: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Comm) Write(b []byte) (int, error) {
|
||||
func (c *Comm) Write(b []byte) (n int, err error) {
|
||||
header := new(bytes.Buffer)
|
||||
err := binary.Write(header, binary.LittleEndian, uint32(len(b)))
|
||||
err = binary.Write(header, binary.LittleEndian, uint32(len(b)))
|
||||
if err != nil {
|
||||
fmt.Println("binary.Write failed:", err)
|
||||
}
|
||||
tmpCopy := append(header.Bytes(), b...)
|
||||
n, err := c.connection.Write(tmpCopy)
|
||||
if n != len(tmpCopy) {
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("wanted to write %d but wrote %d", len(b), n))
|
||||
} else {
|
||||
err = fmt.Errorf("wanted to write %d but wrote %d", len(b), n)
|
||||
}
|
||||
tmpCopy = append(MAGIC_BYTES, tmpCopy...)
|
||||
n, err = c.connection.Write(tmpCopy)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("connection.Write failed: %w", err)
|
||||
return
|
||||
}
|
||||
// log.Printf("wanted to write %d but wrote %d", n, len(b))
|
||||
return n, err
|
||||
if n != len(tmpCopy) {
|
||||
err = fmt.Errorf("wanted to write %d but wrote %d", len(b), n)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Comm) Read() (buf []byte, numBytes int, bs []byte, err error) {
|
||||
// long read deadline in case waiting for file
|
||||
if err = c.connection.SetReadDeadline(time.Now().Add(3 * time.Hour)); err != nil {
|
||||
log.Warnf("error setting read deadline: %v", err)
|
||||
}
|
||||
// must clear the timeout setting
|
||||
defer c.connection.SetDeadline(time.Time{})
|
||||
|
||||
// read until we get 4 bytes for the magic
|
||||
header := make([]byte, 4)
|
||||
_, err = io.ReadFull(c.connection, header)
|
||||
if err != nil {
|
||||
log.Debugf("initial read error: %v", err)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(header, MAGIC_BYTES) {
|
||||
err = fmt.Errorf("initial bytes are not magic: %x", header)
|
||||
return
|
||||
}
|
||||
|
||||
// read until we get 4 bytes for the header
|
||||
var header []byte
|
||||
numBytes = 4
|
||||
for {
|
||||
tmp := make([]byte, numBytes-len(header))
|
||||
n, errRead := c.connection.Read(tmp)
|
||||
if errRead != nil {
|
||||
err = errRead
|
||||
return
|
||||
}
|
||||
header = append(header, tmp[:n]...)
|
||||
if numBytes == len(header) {
|
||||
break
|
||||
}
|
||||
header = make([]byte, 4)
|
||||
_, err = io.ReadFull(c.connection, header)
|
||||
if err != nil {
|
||||
log.Debugf("initial read error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var numBytesUint32 uint32
|
||||
rbuf := bytes.NewReader(header)
|
||||
err = binary.Read(rbuf, binary.LittleEndian, &numBytesUint32)
|
||||
if err != nil {
|
||||
fmt.Println("binary.Read failed:", err)
|
||||
err = fmt.Errorf("binary.Read failed: %w", err)
|
||||
log.Debug(err.Error())
|
||||
return
|
||||
}
|
||||
numBytes = int(numBytesUint32)
|
||||
buf = make([]byte, 0)
|
||||
for {
|
||||
// log.Debugf("bytes: %d/%d",len(buf),numBytes)
|
||||
tmp := make([]byte, numBytes-len(buf))
|
||||
n, errRead := c.connection.Read(tmp)
|
||||
if errRead != nil {
|
||||
err = errRead
|
||||
return
|
||||
}
|
||||
buf = append(buf, tmp[:n]...)
|
||||
if numBytes == len(buf) {
|
||||
break
|
||||
}
|
||||
|
||||
// shorten the reading deadline in case getting weird data
|
||||
if err = c.connection.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil {
|
||||
log.Warnf("error setting read deadline: %v", err)
|
||||
}
|
||||
buf = make([]byte, numBytes)
|
||||
_, err = io.ReadFull(c.connection, buf)
|
||||
if err != nil {
|
||||
log.Debugf("consecutive read error: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ import (
|
|||
)
|
||||
|
||||
func TestComm(t *testing.T) {
|
||||
token := make([]byte, 40000000)
|
||||
rand.Read(token)
|
||||
token := make([]byte, 3000)
|
||||
if _, err := rand.Read(token); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
port := "8001"
|
||||
go func() {
|
||||
|
|
@ -29,7 +31,7 @@ func TestComm(t *testing.T) {
|
|||
log.Error(err)
|
||||
}
|
||||
log.Debugf("client %s connected", connection.RemoteAddr().String())
|
||||
go func(port string, connection net.Conn) {
|
||||
go func(_ string, connection net.Conn) {
|
||||
c := New(connection)
|
||||
err = c.Send([]byte("hello, world"))
|
||||
assert.Nil(t, err)
|
||||
|
|
@ -47,7 +49,7 @@ func TestComm(t *testing.T) {
|
|||
}()
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
a, err := NewConnection("localhost:"+port, 10*time.Minute)
|
||||
a, err := NewConnection("127.0.0.1:"+port, 10*time.Minute)
|
||||
assert.Nil(t, err)
|
||||
data, err := a.Receive()
|
||||
assert.Equal(t, []byte("hello, world"), data)
|
||||
|
|
@ -61,5 +63,4 @@ func TestComm(t *testing.T) {
|
|||
assert.NotNil(t, a.Send(token))
|
||||
_, err = a.Write(token)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"bytes"
|
||||
"compress/flate"
|
||||
"io"
|
||||
|
||||
log "github.com/schollz/logger"
|
||||
)
|
||||
|
||||
// CompressWithOption returns compressed data using the specified level
|
||||
|
|
@ -30,14 +32,22 @@ func Decompress(src []byte) []byte {
|
|||
|
||||
// compress uses flate to compress a byte slice to a corresponding level
|
||||
func compress(src []byte, dest io.Writer, level int) {
|
||||
compressor, _ := flate.NewWriter(dest, level)
|
||||
compressor.Write(src)
|
||||
compressor, err := flate.NewWriter(dest, level)
|
||||
if err != nil {
|
||||
log.Debugf("error level data: %v", err)
|
||||
return
|
||||
}
|
||||
if _, err := compressor.Write(src); err != nil {
|
||||
log.Debugf("error writing data: %v", err)
|
||||
}
|
||||
compressor.Close()
|
||||
}
|
||||
|
||||
// compress uses flate to decompress an io.Reader
|
||||
func decompress(src io.Reader, dest io.Writer) {
|
||||
decompressor := flate.NewReader(src)
|
||||
io.Copy(dest, decompressor)
|
||||
if _, err := io.Copy(dest, decompressor); err != nil {
|
||||
log.Debugf("error copying data: %v", err)
|
||||
}
|
||||
decompressor.Close()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ func BenchmarkCompressLevelNine(b *testing.B) {
|
|||
|
||||
func BenchmarkCompressLevelMinusTwoBinary(b *testing.B) {
|
||||
data := make([]byte, 1000000)
|
||||
rand.Read(data)
|
||||
if _, err := rand.Read(data); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
CompressWithOption(data, -2)
|
||||
}
|
||||
|
|
@ -59,7 +61,9 @@ func BenchmarkCompressLevelMinusTwoBinary(b *testing.B) {
|
|||
|
||||
func BenchmarkCompressLevelNineBinary(b *testing.B) {
|
||||
data := make([]byte, 1000000)
|
||||
rand.Read(data)
|
||||
if _, err := rand.Read(data); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
CompressWithOption(data, 9)
|
||||
}
|
||||
|
|
@ -83,12 +87,16 @@ func TestCompress(t *testing.T) {
|
|||
assert.True(t, len(compressedB) < len(fable))
|
||||
|
||||
data := make([]byte, 4096)
|
||||
rand.Read(data)
|
||||
if _, err := rand.Read(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
compressedB = CompressWithOption(data, -2)
|
||||
dataRateSavings = 100 * (1.0 - float64(len(compressedB))/float64(len(data)))
|
||||
fmt.Printf("random, Level -2: %2.0f%% percent space savings\n", dataRateSavings)
|
||||
|
||||
rand.Read(data)
|
||||
if _, err := rand.Read(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
compressedB = CompressWithOption(data, 9)
|
||||
dataRateSavings = 100 * (1.0 - float64(len(compressedB))/float64(len(data)))
|
||||
|
||||
|
|
|
|||
1790
src/croc/croc.go
1790
src/croc/croc.go
File diff suppressed because it is too large
Load Diff
|
|
@ -1,37 +1,48 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/croc/v6/src/tcp"
|
||||
"github.com/schollz/croc/v10/src/tcp"
|
||||
log "github.com/schollz/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCroc(t *testing.T) {
|
||||
func init() {
|
||||
log.SetLevel("trace")
|
||||
|
||||
go tcp.Run("debug", "127.0.0.1", "8281", "pass123", "8282,8283,8284,8285")
|
||||
go tcp.Run("debug", "127.0.0.1", "8282", "pass123")
|
||||
go tcp.Run("debug", "127.0.0.1", "8283", "pass123")
|
||||
go tcp.Run("debug", "127.0.0.1", "8284", "pass123")
|
||||
go tcp.Run("debug", "127.0.0.1", "8285", "pass123")
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestCrocReadme(t *testing.T) {
|
||||
defer os.Remove("README.md")
|
||||
go tcp.Run("debug", "8081", "8082,8083,8084,8085")
|
||||
go tcp.Run("debug", "8082")
|
||||
go tcp.Run("debug", "8083")
|
||||
go tcp.Run("debug", "8084")
|
||||
go tcp.Run("debug", "8085")
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
log.Debug("setting up sender")
|
||||
sender, err := New(Options{
|
||||
IsSender: true,
|
||||
SharedSecret: "test",
|
||||
Debug: true,
|
||||
RelayAddress: "localhost:8081",
|
||||
RelayPorts: []string{"8081"},
|
||||
Stdout: false,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
IsSender: true,
|
||||
SharedSecret: "8123-testingthecroc",
|
||||
Debug: true,
|
||||
RelayAddress: "127.0.0.1:8281",
|
||||
RelayPorts: []string{"8281"},
|
||||
RelayPassword: "pass123",
|
||||
Stdout: false,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
Curve: "siec",
|
||||
Overwrite: true,
|
||||
GitIgnore: false,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
@ -39,13 +50,16 @@ func TestCroc(t *testing.T) {
|
|||
|
||||
log.Debug("setting up receiver")
|
||||
receiver, err := New(Options{
|
||||
IsSender: false,
|
||||
SharedSecret: "test",
|
||||
Debug: true,
|
||||
RelayAddress: "localhost:8081",
|
||||
Stdout: false,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
IsSender: false,
|
||||
SharedSecret: "8123-testingthecroc",
|
||||
Debug: true,
|
||||
RelayAddress: "127.0.0.1:8281",
|
||||
RelayPassword: "pass123",
|
||||
Stdout: false,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
Curve: "siec",
|
||||
Overwrite: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
@ -54,20 +68,197 @@ func TestCroc(t *testing.T) {
|
|||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
sender.Send(TransferOptions{
|
||||
PathToFiles: []string{"../../README.md"},
|
||||
})
|
||||
filesInfo, emptyFolders, totalNumberFolders, errGet := GetFilesInfo([]string{"../../README.md"}, false, false)
|
||||
if errGet != nil {
|
||||
t.Errorf("failed to get minimal info: %v", errGet)
|
||||
}
|
||||
err := sender.Send(filesInfo, emptyFolders, totalNumberFolders)
|
||||
if err != nil {
|
||||
t.Errorf("send failed: %v", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
go func() {
|
||||
receiver.Receive()
|
||||
err := receiver.Receive()
|
||||
if err != nil {
|
||||
t.Errorf("receive failed: %v", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestCrocEmptyFolder(t *testing.T) {
|
||||
pathName := "../../testEmpty"
|
||||
defer os.RemoveAll(pathName)
|
||||
defer os.RemoveAll("./testEmpty")
|
||||
os.MkdirAll(pathName, 0o755)
|
||||
|
||||
log.Debug("setting up sender")
|
||||
sender, err := New(Options{
|
||||
IsSender: true,
|
||||
SharedSecret: "8123-testingthecroc",
|
||||
Debug: true,
|
||||
RelayAddress: "127.0.0.1:8281",
|
||||
RelayPorts: []string{"8281"},
|
||||
RelayPassword: "pass123",
|
||||
Stdout: false,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
Curve: "siec",
|
||||
Overwrite: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Debug("setting up receiver")
|
||||
receiver, err := New(Options{
|
||||
IsSender: false,
|
||||
SharedSecret: "8123-testingthecroc",
|
||||
Debug: true,
|
||||
RelayAddress: "127.0.0.1:8281",
|
||||
RelayPassword: "pass123",
|
||||
Stdout: false,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
Curve: "siec",
|
||||
Overwrite: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
filesInfo, emptyFolders, totalNumberFolders, errGet := GetFilesInfo([]string{pathName}, false, false)
|
||||
if errGet != nil {
|
||||
t.Errorf("failed to get minimal info: %v", errGet)
|
||||
}
|
||||
err := sender.Send(filesInfo, emptyFolders, totalNumberFolders)
|
||||
if err != nil {
|
||||
t.Errorf("send failed: %v", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
go func() {
|
||||
err := receiver.Receive()
|
||||
if err != nil {
|
||||
t.Errorf("receive failed: %v", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestCrocSymlink(t *testing.T) {
|
||||
pathName := "../link-in-folder"
|
||||
defer os.RemoveAll(pathName)
|
||||
defer os.RemoveAll("./link-in-folder")
|
||||
os.MkdirAll(pathName, 0o755)
|
||||
os.Symlink("../../README.md", filepath.Join(pathName, "README.link"))
|
||||
|
||||
log.Debug("setting up sender")
|
||||
sender, err := New(Options{
|
||||
IsSender: true,
|
||||
SharedSecret: "8124-testingthecroc",
|
||||
Debug: true,
|
||||
RelayAddress: "127.0.0.1:8281",
|
||||
RelayPorts: []string{"8281"},
|
||||
RelayPassword: "pass123",
|
||||
Stdout: false,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
Curve: "siec",
|
||||
Overwrite: true,
|
||||
GitIgnore: false,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Debug("setting up receiver")
|
||||
receiver, err := New(Options{
|
||||
IsSender: false,
|
||||
SharedSecret: "8124-testingthecroc",
|
||||
Debug: true,
|
||||
RelayAddress: "127.0.0.1:8281",
|
||||
RelayPassword: "pass123",
|
||||
Stdout: false,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
Curve: "siec",
|
||||
Overwrite: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
filesInfo, emptyFolders, totalNumberFolders, errGet := GetFilesInfo([]string{pathName}, false, false)
|
||||
if errGet != nil {
|
||||
t.Errorf("failed to get minimal info: %v", errGet)
|
||||
}
|
||||
err = sender.Send(filesInfo, emptyFolders, totalNumberFolders)
|
||||
if err != nil {
|
||||
t.Errorf("send failed: %v", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
go func() {
|
||||
err = receiver.Receive()
|
||||
if err != nil {
|
||||
t.Errorf("receive failed: %v", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
s, err := filepath.EvalSymlinks(path.Join(pathName, "README.link"))
|
||||
if s != "../../README.md" && s != "..\\..\\README.md" {
|
||||
log.Debug(s)
|
||||
t.Errorf("symlink failed to transfer in folder")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("symlink transfer failed: %s", err.Error())
|
||||
}
|
||||
}
|
||||
func TestCrocIgnoreGit(t *testing.T) {
|
||||
log.SetLevel("trace")
|
||||
defer os.Remove(".gitignore")
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
file, err := os.Create(".gitignore")
|
||||
if err != nil {
|
||||
log.Errorf("error creating file")
|
||||
}
|
||||
_, err = file.WriteString("LICENSE")
|
||||
if err != nil {
|
||||
log.Errorf("error writing to file")
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
// due to how files are ignored in this function, all we have to do to test is make sure LICENSE doesn't get included in FilesInfo.
|
||||
filesInfo, _, _, errGet := GetFilesInfo([]string{"../../LICENSE", ".gitignore", "croc.go"}, false, true)
|
||||
if errGet != nil {
|
||||
t.Errorf("failed to get minimal info: %v", errGet)
|
||||
}
|
||||
for _, file := range filesInfo {
|
||||
if strings.Contains(file.Name, "LICENSE") {
|
||||
t.Errorf("test failed, should ignore LICENSE")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrocLocal(t *testing.T) {
|
||||
log.SetLevel("trace")
|
||||
defer os.Remove("LICENSE")
|
||||
|
|
@ -76,14 +267,18 @@ func TestCrocLocal(t *testing.T) {
|
|||
|
||||
log.Debug("setting up sender")
|
||||
sender, err := New(Options{
|
||||
IsSender: true,
|
||||
SharedSecret: "test",
|
||||
Debug: true,
|
||||
RelayAddress: "localhost:8181",
|
||||
RelayPorts: []string{"8181", "8182"},
|
||||
Stdout: true,
|
||||
NoPrompt: true,
|
||||
DisableLocal: false,
|
||||
IsSender: true,
|
||||
SharedSecret: "8123-testingthecroc",
|
||||
Debug: true,
|
||||
RelayAddress: "127.0.0.1:8181",
|
||||
RelayPorts: []string{"8181", "8182"},
|
||||
RelayPassword: "pass123",
|
||||
Stdout: true,
|
||||
NoPrompt: true,
|
||||
DisableLocal: false,
|
||||
Curve: "siec",
|
||||
Overwrite: true,
|
||||
GitIgnore: false,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
@ -92,13 +287,16 @@ func TestCrocLocal(t *testing.T) {
|
|||
|
||||
log.Debug("setting up receiver")
|
||||
receiver, err := New(Options{
|
||||
IsSender: false,
|
||||
SharedSecret: "test",
|
||||
Debug: true,
|
||||
RelayAddress: "localhost:8181",
|
||||
Stdout: true,
|
||||
NoPrompt: true,
|
||||
DisableLocal: false,
|
||||
IsSender: false,
|
||||
SharedSecret: "8123-testingthecroc",
|
||||
Debug: true,
|
||||
RelayAddress: "127.0.0.1:8181",
|
||||
RelayPassword: "pass123",
|
||||
Stdout: true,
|
||||
NoPrompt: true,
|
||||
DisableLocal: false,
|
||||
Curve: "siec",
|
||||
Overwrite: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
@ -108,15 +306,22 @@ func TestCrocLocal(t *testing.T) {
|
|||
os.Create("touched")
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
sender.Send(TransferOptions{
|
||||
PathToFiles: []string{"../../LICENSE", "touched"},
|
||||
KeepPathInRemote: false,
|
||||
})
|
||||
filesInfo, emptyFolders, totalNumberFolders, errGet := GetFilesInfo([]string{"../../LICENSE", "touched"}, false, false)
|
||||
if errGet != nil {
|
||||
t.Errorf("failed to get minimal info: %v", errGet)
|
||||
}
|
||||
err := sender.Send(filesInfo, emptyFolders, totalNumberFolders)
|
||||
if err != nil {
|
||||
t.Errorf("send failed: %v", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
go func() {
|
||||
receiver.Receive()
|
||||
err := receiver.Receive()
|
||||
if err != nil {
|
||||
t.Errorf("send failed: %v", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
|
|
@ -125,37 +330,71 @@ func TestCrocLocal(t *testing.T) {
|
|||
|
||||
func TestCrocError(t *testing.T) {
|
||||
content := []byte("temporary file's content")
|
||||
tmpfile, err := ioutil.TempFile("", "example")
|
||||
tmpfile, err := os.CreateTemp("", "example")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer os.Remove(tmpfile.Name()) // clean up
|
||||
|
||||
if _, err := tmpfile.Write(content); err != nil {
|
||||
if _, err = tmpfile.Write(content); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
if err = tmpfile.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Debug(false)
|
||||
log.SetLevel("warn")
|
||||
sender, _ := New(Options{
|
||||
IsSender: true,
|
||||
SharedSecret: "test33",
|
||||
Debug: true,
|
||||
RelayAddress: "doesntexistok.com:8381",
|
||||
RelayPorts: []string{"8381", "8382"},
|
||||
Stdout: true,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
})
|
||||
err = sender.Send(TransferOptions{
|
||||
PathToFiles: []string{tmpfile.Name()},
|
||||
KeepPathInRemote: true,
|
||||
IsSender: true,
|
||||
SharedSecret: "8123-testingthecroc2",
|
||||
Debug: true,
|
||||
RelayAddress: "doesntexistok.com:8381",
|
||||
RelayPorts: []string{"8381", "8382"},
|
||||
RelayPassword: "pass123",
|
||||
Stdout: true,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
Curve: "siec",
|
||||
Overwrite: true,
|
||||
})
|
||||
filesInfo, emptyFolders, totalNumberFolders, errGet := GetFilesInfo([]string{tmpfile.Name()}, false, false)
|
||||
if errGet != nil {
|
||||
t.Errorf("failed to get minimal info: %v", errGet)
|
||||
}
|
||||
err = sender.Send(filesInfo, emptyFolders, totalNumberFolders)
|
||||
log.Debug(err)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestCleanUp(t *testing.T) {
|
||||
// windows allows files to be deleted only if they
|
||||
// are not open by another program so the remove actions
|
||||
// from the above tests will not always do a good clean up
|
||||
// This "test" will make sure
|
||||
operatingSystem := runtime.GOOS
|
||||
log.Debugf("The operating system is %s", operatingSystem)
|
||||
if operatingSystem == "windows" {
|
||||
time.Sleep(1 * time.Second)
|
||||
log.Debug("Full cleanup")
|
||||
var err error
|
||||
|
||||
for _, file := range []string{"README.md", "./README.md"} {
|
||||
err = os.Remove(file)
|
||||
if err == nil {
|
||||
log.Debugf("Successfully purged %s", file)
|
||||
} else {
|
||||
log.Debugf("%s was already purged.", file)
|
||||
}
|
||||
}
|
||||
for _, folder := range []string{"./testEmpty", "./link-in-folder"} {
|
||||
err = os.RemoveAll(folder)
|
||||
if err == nil {
|
||||
log.Debugf("Successfully purged %s", folder)
|
||||
} else {
|
||||
log.Debugf("%s was already purged.", folder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,56 +5,44 @@ import (
|
|||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// Encryption is the basic type for storing
|
||||
// the key, passphrase and salt
|
||||
type Encryption struct {
|
||||
key []byte
|
||||
passphrase []byte
|
||||
salt []byte
|
||||
}
|
||||
|
||||
// New generates a new Encryption, using the supplied passphrase and
|
||||
// an optional supplied salt.
|
||||
// Passing nil passphrase will not use decryption.
|
||||
func New(passphrase []byte, salt []byte) (e Encryption, err error) {
|
||||
if passphrase == nil {
|
||||
e = Encryption{nil, nil, nil}
|
||||
// New generates a new key based on a passphrase and salt
|
||||
func New(passphrase []byte, usersalt []byte) (key []byte, salt []byte, err error) {
|
||||
if len(passphrase) < 1 {
|
||||
err = fmt.Errorf("need more than that for passphrase")
|
||||
return
|
||||
}
|
||||
e.passphrase = passphrase
|
||||
if salt == nil {
|
||||
e.salt = make([]byte, 8)
|
||||
if usersalt == nil {
|
||||
salt = make([]byte, 8)
|
||||
// http://www.ietf.org/rfc/rfc2898.txt
|
||||
// Salt.
|
||||
rand.Read(e.salt)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
log.Fatalf("can't get random salt: %v", err)
|
||||
}
|
||||
} else {
|
||||
e.salt = salt
|
||||
salt = usersalt
|
||||
}
|
||||
e.key = pbkdf2.Key([]byte(passphrase), e.salt, 100, 32, sha256.New)
|
||||
key = pbkdf2.Key(passphrase, salt, 100, 32, sha256.New)
|
||||
return
|
||||
}
|
||||
|
||||
// Salt returns the salt bytes
|
||||
func (e Encryption) Salt() []byte {
|
||||
return e.salt
|
||||
}
|
||||
|
||||
// Encrypt will generate an Encryption, prefixed with the IV
|
||||
func (e Encryption) Encrypt(plaintext []byte) (encrypted []byte, err error) {
|
||||
if e.passphrase == nil {
|
||||
encrypted = plaintext
|
||||
return
|
||||
}
|
||||
// Encrypt will encrypt using the pre-generated key
|
||||
func Encrypt(plaintext []byte, key []byte) (encrypted []byte, err error) {
|
||||
// generate a random iv each time
|
||||
// http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||
// Section 8.2
|
||||
ivBytes := make([]byte, 12)
|
||||
rand.Read(ivBytes)
|
||||
b, err := aes.NewCipher(e.key)
|
||||
if _, err = rand.Read(ivBytes); err != nil {
|
||||
log.Fatalf("can't initialize crypto: %v", err)
|
||||
}
|
||||
b, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -67,13 +55,13 @@ func (e Encryption) Encrypt(plaintext []byte) (encrypted []byte, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Decrypt an Encryption
|
||||
func (e Encryption) Decrypt(encrypted []byte) (plaintext []byte, err error) {
|
||||
if e.passphrase == nil {
|
||||
plaintext = encrypted
|
||||
// Decrypt using the pre-generated key
|
||||
func Decrypt(encrypted []byte, key []byte) (plaintext []byte, err error) {
|
||||
if len(encrypted) < 13 {
|
||||
err = fmt.Errorf("incorrect passphrase")
|
||||
return
|
||||
}
|
||||
b, err := aes.NewCipher(e.key)
|
||||
b, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -84,3 +72,54 @@ func (e Encryption) Decrypt(encrypted []byte) (plaintext []byte, err error) {
|
|||
plaintext, err = aesgcm.Open(nil, encrypted[:12], encrypted[12:], nil)
|
||||
return
|
||||
}
|
||||
|
||||
// NewArgon2 generates a new key based on a passphrase and salt
|
||||
// using argon2
|
||||
// https://pkg.go.dev/golang.org/x/crypto/argon2
|
||||
func NewArgon2(passphrase []byte, usersalt []byte) (aead cipher.AEAD, salt []byte, err error) {
|
||||
if len(passphrase) < 1 {
|
||||
err = fmt.Errorf("need more than that for passphrase")
|
||||
return
|
||||
}
|
||||
if usersalt == nil {
|
||||
salt = make([]byte, 8)
|
||||
// http://www.ietf.org/rfc/rfc2898.txt
|
||||
// Salt.
|
||||
if _, err = rand.Read(salt); err != nil {
|
||||
log.Fatalf("can't get random salt: %v", err)
|
||||
}
|
||||
} else {
|
||||
salt = usersalt
|
||||
}
|
||||
aead, err = chacha20poly1305.NewX(argon2.IDKey(passphrase, salt, 1, 64*1024, 4, 32))
|
||||
return
|
||||
}
|
||||
|
||||
// EncryptChaCha will encrypt ChaCha20-Poly1305 using the pre-generated key
|
||||
// https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305
|
||||
func EncryptChaCha(plaintext []byte, aead cipher.AEAD) (encrypted []byte, err error) {
|
||||
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(plaintext)+aead.Overhead())
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Encrypt the message and append the ciphertext to the nonce.
|
||||
encrypted = aead.Seal(nonce, nonce, plaintext, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// DecryptChaCha will encrypt ChaCha20-Poly1305 using the pre-generated key
|
||||
// https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305
|
||||
func DecryptChaCha(encryptedMsg []byte, aead cipher.AEAD) (encrypted []byte, err error) {
|
||||
if len(encryptedMsg) < aead.NonceSize() {
|
||||
err = fmt.Errorf("ciphertext too short")
|
||||
return
|
||||
}
|
||||
|
||||
// Split nonce and ciphertext.
|
||||
nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():]
|
||||
|
||||
// Decrypt the message and check it wasn't tampered with.
|
||||
encrypted, err = aead.Open(nil, nonce, ciphertext, nil)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,119 @@
|
|||
package crypt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func BenchmarkEncryptionNew(b *testing.B) {
|
||||
func BenchmarkEncrypt(b *testing.B) {
|
||||
bob, _, _ := New([]byte("password"), nil)
|
||||
for i := 0; i < b.N; i++ {
|
||||
bob, _ := New([]byte("password"), nil)
|
||||
bob.Encrypt([]byte("hello, world"))
|
||||
Encrypt([]byte("hello, world"), bob)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncryption(b *testing.B) {
|
||||
bob, _ := New([]byte("password"), nil)
|
||||
func BenchmarkDecrypt(b *testing.B) {
|
||||
key, _, _ := New([]byte("password"), nil)
|
||||
msg := []byte("hello, world")
|
||||
enc, _ := Encrypt(msg, key)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
bob.Encrypt([]byte("hello, world"))
|
||||
Decrypt(enc, key)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewPbkdf2(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
New([]byte("password"), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewArgon2(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
NewArgon2([]byte("password"), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncryptChaCha(b *testing.B) {
|
||||
bob, _, _ := NewArgon2([]byte("password"), nil)
|
||||
for i := 0; i < b.N; i++ {
|
||||
EncryptChaCha([]byte("hello, world"), bob)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecryptChaCha(b *testing.B) {
|
||||
key, _, _ := NewArgon2([]byte("password"), nil)
|
||||
msg := []byte("hello, world")
|
||||
enc, _ := EncryptChaCha(msg, key)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
DecryptChaCha(enc, key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryption(t *testing.T) {
|
||||
bob, err := New([]byte("password"), nil)
|
||||
key, salt, err := New([]byte("password"), nil)
|
||||
assert.Nil(t, err)
|
||||
jane, err := New([]byte("password"), bob.Salt())
|
||||
msg := []byte("hello, world")
|
||||
enc, err := Encrypt(msg, key)
|
||||
assert.Nil(t, err)
|
||||
enc, err := bob.Encrypt([]byte("hello, world"))
|
||||
dec, err := Decrypt(enc, key)
|
||||
assert.Nil(t, err)
|
||||
dec, err := jane.Decrypt(enc)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, dec, []byte("hello, world"))
|
||||
assert.Equal(t, msg, dec)
|
||||
|
||||
jane2, err := New([]byte("password"), nil)
|
||||
// check reusing the salt
|
||||
key2, _, _ := New([]byte("password"), salt)
|
||||
dec, err = Decrypt(enc, key2)
|
||||
assert.Nil(t, err)
|
||||
dec, err = jane2.Decrypt(enc)
|
||||
assert.Equal(t, msg, dec)
|
||||
|
||||
// check reusing the salt
|
||||
key2, _, _ = New([]byte("wrong password"), salt)
|
||||
dec, err = Decrypt(enc, key2)
|
||||
assert.NotNil(t, err)
|
||||
assert.NotEqual(t, dec, []byte("hello, world"))
|
||||
assert.NotEqual(t, msg, dec)
|
||||
|
||||
jane3, err := New([]byte("passwordwrong"), bob.Salt())
|
||||
assert.Nil(t, err)
|
||||
dec, err = jane3.Decrypt(enc)
|
||||
// error with no password
|
||||
_, err = Decrypt([]byte(""), key)
|
||||
assert.NotNil(t, err)
|
||||
assert.NotEqual(t, dec, []byte("hello, world"))
|
||||
|
||||
// error with small password
|
||||
_, _, err = New([]byte(""), nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestNoEncryption(t *testing.T) {
|
||||
bob, err := New(nil, nil)
|
||||
func TestEncryptionChaCha(t *testing.T) {
|
||||
key, salt, err := NewArgon2([]byte("password"), nil)
|
||||
fmt.Printf("key: %x\n", key)
|
||||
assert.Nil(t, err)
|
||||
jane, err := New(nil, nil)
|
||||
msg := []byte("hello, world")
|
||||
enc, err := EncryptChaCha(msg, key)
|
||||
assert.Nil(t, err)
|
||||
enc, err := bob.Encrypt([]byte("hello, world"))
|
||||
dec, err := DecryptChaCha(enc, key)
|
||||
assert.Nil(t, err)
|
||||
dec, err := jane.Decrypt(enc)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, dec, []byte("hello, world"))
|
||||
assert.Equal(t, enc, []byte("hello, world"))
|
||||
assert.Equal(t, msg, dec)
|
||||
|
||||
// check reusing the salt
|
||||
key2, _, _ := NewArgon2([]byte("password"), salt)
|
||||
dec, err = DecryptChaCha(enc, key2)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, msg, dec)
|
||||
|
||||
// check reusing the salt
|
||||
key2, _, _ = NewArgon2([]byte("wrong password"), salt)
|
||||
dec, err = DecryptChaCha(enc, key2)
|
||||
assert.NotNil(t, err)
|
||||
assert.NotEqual(t, msg, dec)
|
||||
|
||||
// error with no password
|
||||
_, err = DecryptChaCha([]byte(""), key)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// error with small password
|
||||
_, _, err = NewArgon2([]byte(""), nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package diskusage
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// DiskUsage contains usage data and provides user-friendly access methods
|
||||
type DiskUsage struct {
|
||||
stat *unix.Statfs_t
|
||||
}
|
||||
|
||||
// NewDiskUsage returns an object holding the disk usage of volumePath
|
||||
// or nil in case of error (invalid path, etc)
|
||||
func NewDiskUsage(volumePath string) *DiskUsage {
|
||||
stat := unix.Statfs_t{}
|
||||
err := unix.Statfs(volumePath, &stat)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &DiskUsage{&stat}
|
||||
}
|
||||
|
||||
// Free returns total free bytes on file system
|
||||
func (du *DiskUsage) Free() uint64 {
|
||||
return uint64(du.stat.Bfree) * uint64(du.stat.Bsize)
|
||||
}
|
||||
|
||||
// Available return total available bytes on file system to an unprivileged user
|
||||
func (du *DiskUsage) Available() uint64 {
|
||||
return uint64(du.stat.Bavail) * uint64(du.stat.Bsize)
|
||||
}
|
||||
|
||||
// Size returns total size of the file system
|
||||
func (du *DiskUsage) Size() uint64 {
|
||||
return uint64(du.stat.Blocks) * uint64(du.stat.Bsize)
|
||||
}
|
||||
|
||||
// Used returns total bytes used in file system
|
||||
func (du *DiskUsage) Used() uint64 {
|
||||
return du.Size() - du.Free()
|
||||
}
|
||||
|
||||
// Usage returns percentage of use on the file system
|
||||
func (du *DiskUsage) Usage() float32 {
|
||||
return float32(du.Used()) / float32(du.Size())
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package diskusage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var KB = uint64(1024)
|
||||
|
||||
func TestNewDiskUsage(t *testing.T) {
|
||||
usage := NewDiskUsage(".")
|
||||
fmt.Println("Free:", usage.Free()/(KB*KB))
|
||||
fmt.Println("Available:", usage.Available()/(KB*KB))
|
||||
fmt.Println("Size:", usage.Size()/(KB*KB))
|
||||
fmt.Println("Used:", usage.Used()/(KB*KB))
|
||||
fmt.Println("Usage:", usage.Usage()*100, "%")
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package diskusage
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type DiskUsage struct {
|
||||
freeBytes int64
|
||||
totalBytes int64
|
||||
availBytes int64
|
||||
}
|
||||
|
||||
// NewDiskUsage returns an object holding the disk usage of volumePath
|
||||
// or nil in case of error (invalid path, etc)
|
||||
func NewDiskUsage(volumePath string) *DiskUsage {
|
||||
h := windows.MustLoadDLL("kernel32.dll")
|
||||
c := h.MustFindProc("GetDiskFreeSpaceExW")
|
||||
|
||||
du := &DiskUsage{}
|
||||
|
||||
c.Call(
|
||||
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(volumePath))),
|
||||
uintptr(unsafe.Pointer(&du.freeBytes)),
|
||||
uintptr(unsafe.Pointer(&du.totalBytes)),
|
||||
uintptr(unsafe.Pointer(&du.availBytes)))
|
||||
|
||||
return du
|
||||
}
|
||||
|
||||
// Free returns total free bytes on file system
|
||||
func (du *DiskUsage) Free() uint64 {
|
||||
return uint64(du.freeBytes)
|
||||
}
|
||||
|
||||
// Available returns total available bytes on file system to an unprivileged user
|
||||
func (du *DiskUsage) Available() uint64 {
|
||||
return uint64(du.availBytes)
|
||||
}
|
||||
|
||||
// Size returns total size of the file system
|
||||
func (du *DiskUsage) Size() uint64 {
|
||||
return uint64(du.totalBytes)
|
||||
}
|
||||
|
||||
// Used returns total bytes used in file system
|
||||
func (du *DiskUsage) Used() uint64 {
|
||||
return du.Size() - du.Free()
|
||||
}
|
||||
|
||||
// Usage returns percentage of use on the file system
|
||||
func (du *DiskUsage) Usage() float32 {
|
||||
return float32(du.Used()) / float32(du.Size())
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# VERSION=8.X.Y make release
|
||||
|
||||
release:
|
||||
cd ../../ && go run src/install/updateversion.go
|
||||
git commit -am "bump ${VERSION}"
|
||||
git tag -af v${VERSION} -m "v${VERSION}"
|
||||
git push
|
||||
git push --tags
|
||||
cp zsh_autocomplete ../../
|
||||
cp bash_autocomplete ../../
|
||||
cd ../../ && goreleaser release
|
||||
cd ../../ && ./src/install/prepare-sources-tarball.sh
|
||||
cd ../../ && ./src/install/upload-src-tarball.sh
|
||||
|
||||
test:
|
||||
cp zsh_autocomplete ../../
|
||||
cp bash_autocomplete ../../
|
||||
cd ../../ && go generate
|
||||
cd ../../ && goreleaser release --skip-publish
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
: ${PROG:=$(basename ${BASH_SOURCE})}
|
||||
|
||||
_cli_bash_autocomplete() {
|
||||
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
|
||||
local cur opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
|
||||
else
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||
fi
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG
|
||||
unset PROG
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 582 KiB |
|
|
@ -23,7 +23,7 @@
|
|||
# Issues: https://github.com/schollz/croc/issues
|
||||
#
|
||||
# CREATED: 08/10/2019 16:41
|
||||
# REVISION: 0.9.1
|
||||
# REVISION: 0.9.2
|
||||
#===============================================================================
|
||||
set -o nounset # Treat unset variables as an error
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ print_help() {
|
|||
Default = /usr/local/bin ('\${PREFIX}/bin' on Termux for Android)
|
||||
|
||||
-h
|
||||
Prints this helpfull message and exit."
|
||||
Prints this helpful message and exit."
|
||||
|
||||
echo "${help_header}"
|
||||
echo ""
|
||||
|
|
@ -156,10 +156,10 @@ make_tempdir() {
|
|||
|
||||
#--- FUNCTION ----------------------------------------------------------------
|
||||
# NAME: determine_os
|
||||
# DESCRIPTION: Attempts to determin host os using uname
|
||||
# DESCRIPTION: Attempts to determine host os using uname
|
||||
# PARAMETERS: none
|
||||
# RETURNS: 0 = OS Detected. Also prints detected os to stdout
|
||||
# 1 = Unkown OS
|
||||
# 1 = Unknown OS
|
||||
# 20 = 'uname' not found in path
|
||||
#-------------------------------------------------------------------------------
|
||||
determine_os() {
|
||||
|
|
@ -180,10 +180,10 @@ determine_os() {
|
|||
|
||||
#--- FUNCTION ----------------------------------------------------------------
|
||||
# NAME: determine_arch
|
||||
# DESCRIPTION: Attempt to determin architecture of host
|
||||
# DESCRIPTION: Attempt to determine architecture of host
|
||||
# PARAMETERS: none
|
||||
# RETURNS: 0 = Arch Detected. Also prints detected arch to stdout
|
||||
# 1 = Unkown arch
|
||||
# 1 = Unknown arch
|
||||
# 20 = 'uname' not found in path
|
||||
#-------------------------------------------------------------------------------
|
||||
determine_arch() {
|
||||
|
|
@ -238,7 +238,7 @@ download_file() {
|
|||
#--- FUNCTION ----------------------------------------------------------------
|
||||
# NAME: checksum_check
|
||||
# DESCRIPTION: Attempt to verify checksum of downloaded file to ensure
|
||||
# integrity. Tries multiple tools before faling.
|
||||
# integrity. Tries multiple tools before failing.
|
||||
# PARAMETERS: $1 = path to checksum file
|
||||
# $2 = location of file to check
|
||||
# $3 = working directory
|
||||
|
|
@ -300,10 +300,10 @@ checksum_check() {
|
|||
#--- FUNCTION ----------------------------------------------------------------
|
||||
# NAME: extract_file
|
||||
# DESCRIPTION: Extracts a file into a location. Attempts to determine which
|
||||
# tool to use by checking file extention.
|
||||
# tool to use by checking file extension.
|
||||
# PARAMETERS: $1 = file to extract
|
||||
# $2 = location to extract file into
|
||||
# $3 = extention
|
||||
# $3 = extension
|
||||
# RETURNS: Return code of the tool used to extract the file
|
||||
# 20 = Failed to determine which tool to use
|
||||
# 30 = Failed to find tool in path
|
||||
|
|
@ -339,6 +339,42 @@ extract_file() {
|
|||
return "${rcode}"
|
||||
}
|
||||
|
||||
#--- FUNCTION ----------------------------------------------------------------
|
||||
# NAME: create_prefix
|
||||
# DESCRIPTION: Creates the install prefix (and any parent directories). If
|
||||
# EUID not 0, then attempt to use sudo.
|
||||
# PARAMETERS: $1 = prefix
|
||||
# RETURNS: Return code of the tool used to make the directory
|
||||
# 0 = Created the directory
|
||||
# >0 = Failed to create directory
|
||||
# 20 = Could not find mkdir command
|
||||
# 21 = Could not find sudo command
|
||||
#-------------------------------------------------------------------------------
|
||||
create_prefix() {
|
||||
local prefix
|
||||
local rcode
|
||||
|
||||
prefix="${1}"
|
||||
|
||||
if command -v mkdir >/dev/null 2>&1; then
|
||||
if [[ "${EUID}" == "0" ]]; then
|
||||
mkdir -p "${prefix}"
|
||||
rcode="${?}"
|
||||
else
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
sudo mkdir -p "${prefix}"
|
||||
rcode="${?}"
|
||||
else
|
||||
rcode="21"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
rcode="20"
|
||||
fi
|
||||
|
||||
return "${rcode}"
|
||||
}
|
||||
|
||||
#--- FUNCTION ----------------------------------------------------------------
|
||||
# NAME: install_file_freebsd
|
||||
# DESCRIPTION: Installs a file into a location using 'install'. If EUID not
|
||||
|
|
@ -377,7 +413,6 @@ install_file_freebsd() {
|
|||
return "${rcode}"
|
||||
}
|
||||
|
||||
|
||||
#--- FUNCTION ----------------------------------------------------------------
|
||||
# NAME: install_file_linux
|
||||
# DESCRIPTION: Installs a file into a location using 'install'. If EUID not
|
||||
|
|
@ -485,12 +520,22 @@ main() {
|
|||
local checksum_check_rcode
|
||||
local extract_file_rcode
|
||||
local install_file_rcode
|
||||
local create_prefix_rcode
|
||||
local bash_autocomplete_file
|
||||
local bash_autocomplete_prefix
|
||||
local zsh_autocomplete_file
|
||||
local zsh_autocomplete_prefix
|
||||
local autocomplete_install_rcode
|
||||
|
||||
croc_bin_name="croc"
|
||||
croc_version="6.1.5"
|
||||
croc_version="10.1.1"
|
||||
croc_dl_ext="tar.gz"
|
||||
croc_base_url="https://github.com/schollz/croc/releases/download"
|
||||
prefix="${1}"
|
||||
bash_autocomplete_file="bash_autocomplete"
|
||||
bash_autocomplete_prefix="/etc/bash_completion.d"
|
||||
zsh_autocomplete_file="zsh_autocomplete"
|
||||
zsh_autocomplete_prefix="/etc/zsh"
|
||||
|
||||
print_banner
|
||||
print_message "== Install prefix set to ${prefix}" "info"
|
||||
|
|
@ -532,6 +577,9 @@ main() {
|
|||
|
||||
case "${croc_os}" in
|
||||
"Darwin" ) croc_os="macOS";;
|
||||
*"BusyBox"* )
|
||||
croc_os="Linux"
|
||||
;;
|
||||
"CYGWIN"* ) croc_os="Windows";
|
||||
croc_dl_ext="zip";
|
||||
print_message "== Cygwin is currently unsupported." "error";
|
||||
|
|
@ -542,16 +590,19 @@ main() {
|
|||
"x86_64" ) croc_arch="64bit";;
|
||||
"amd64" ) croc_arch="64bit";;
|
||||
"aarch64" ) croc_arch="ARM64";;
|
||||
"arm64" ) croc_arch="ARM64";;
|
||||
"armv7l" ) croc_arch="ARM";;
|
||||
"armv8l" ) croc_arch="ARM";;
|
||||
"armv9l" ) croc_arch="ARM";;
|
||||
"i686" ) croc_arch="32bit";;
|
||||
* ) croc_arch="unknown";;
|
||||
esac
|
||||
|
||||
croc_file="${croc_bin_name}_${croc_version}_${croc_os}-${croc_arch}.${croc_dl_ext}"
|
||||
croc_checksum_file="${croc_bin_name}_${croc_version}_checksums.txt"
|
||||
croc_file="${croc_bin_name}_v${croc_version}_${croc_os}-${croc_arch}.${croc_dl_ext}"
|
||||
croc_checksum_file="${croc_bin_name}_v${croc_version}_checksums.txt"
|
||||
croc_url="${croc_base_url}/v${croc_version}/${croc_file}"
|
||||
croc_checksum_url="${croc_base_url}/v${croc_version}/${croc_checksum_file}"
|
||||
|
||||
echo "${croc_url}" "${tmpdir}" "${croc_file}"
|
||||
download_file "${croc_url}" "${tmpdir}" "${croc_file}"
|
||||
download_file_rcode="${?}"
|
||||
if [[ "${download_file_rcode}" == "0" ]]; then
|
||||
|
|
@ -620,6 +671,26 @@ main() {
|
|||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "${prefix}" ]]; then
|
||||
create_prefix "${prefix}"
|
||||
create_prefix_rcode="${?}"
|
||||
if [[ "${create_prefix_rcode}" == "0" ]]; then
|
||||
print_message "== Created install prefix at ${prefix}" "info"
|
||||
elif [[ "${create_prefix_rcode}" == "20" ]]; then
|
||||
print_message "== Failed to find mkdir in path" "error"
|
||||
exit 1
|
||||
elif [[ "${create_prefix_rcode}" == "21" ]]; then
|
||||
print_message "== Failed to find sudo in path" "error"
|
||||
exit 1
|
||||
else
|
||||
print_message "== Failed to create the install prefix: ${prefix}" "error"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
print_message "== Install prefix already exists. No need to create it." "info"
|
||||
fi
|
||||
|
||||
[ ! -d "${bash_autocomplete_prefix}/croc" ] && mkdir -p "${bash_autocomplete_prefix}/croc" >/dev/null 2>&1
|
||||
case "${croc_os}" in
|
||||
"Linux" ) install_file_linux "${tmpdir}/${croc_bin_name}" "${prefix}/";
|
||||
install_file_rcode="${?}";;
|
||||
|
|
@ -630,7 +701,8 @@ main() {
|
|||
"Windows" ) install_file_cygwin "${tmpdir}/${croc_bin_name}" "${prefix}/";
|
||||
install_file_rcode="${?}";;
|
||||
esac
|
||||
if [[ "${install_file_rcode}" == "0" ]]; then
|
||||
|
||||
if [[ "${install_file_rcode}" == "0" ]] ; then
|
||||
print_message "== Installed ${croc_bin_name} to ${prefix}/" "ok"
|
||||
elif [[ "${install_file_rcode}" == "1" ]]; then
|
||||
print_message "== Failed to install ${croc_bin_name}" "error"
|
||||
|
|
@ -646,6 +718,28 @@ main() {
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# case "$(basename ${SHELL})" in
|
||||
# "bash" ) install_file_linux "${tmpdir}/${bash_autocomplete_file}" "${bash_autocomplete_prefix}/croc";
|
||||
# autocomplete_install_rcode="${?}";;
|
||||
# "zsh" ) install_file_linux "${tmpdir}/${zsh_autocomplete_file}" "${zsh_autocomplete_prefix}/zsh_autocomplete_croc";
|
||||
# autocomplete_install_rcode="${?}";
|
||||
# print_message "== You will need to add the following to your ~/.zshrc to enable autocompletion" "info";
|
||||
# print_message "\nPROG=croc\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/zsh_autocomplete_croc\n" "info";;
|
||||
# *) autocomplete_install_rcode="1";;
|
||||
# esac
|
||||
|
||||
# if [[ "${autocomplete_install_rcode}" == "0" ]] ; then
|
||||
# print_message "== Installed autocompletions for $(basename "${SHELL}")" "ok"
|
||||
# elif [[ "${autocomplete_install_rcode}" == "1" ]]; then
|
||||
# print_message "== Failed to install ${bash_autocomplete_file}" "error"
|
||||
# elif [[ "${autocomplete_install_rcode}" == "20" ]]; then
|
||||
# print_message "== Failed to locate 'install' command" "error"
|
||||
# elif [[ "${autocomplete_install_rcode}" == "21" ]]; then
|
||||
# print_message "== Failed to locate 'sudo' command" "error"
|
||||
# else
|
||||
# print_message "== Install attempt returned an unexpected value of ${autocomplete_install_rcode}" "error"
|
||||
# fi
|
||||
|
||||
print_message "== Installation complete" "ok"
|
||||
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
tmp=$(mktemp -d)
|
||||
echo $VERSION
|
||||
git clone -b v${VERSION} --depth 1 https://github.com/schollz/croc $tmp/croc-${VERSION}
|
||||
(cd $tmp/croc-${VERSION} && go mod tidy && go mod vendor)
|
||||
(cd $tmp && tar -cvzf croc_${VERSION}_src.tar.gz croc-${VERSION})
|
||||
mv $tmp/croc_${VERSION}_src.tar.gz dist/
|
||||
|
|
@ -2,7 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -15,11 +15,7 @@ func main() {
|
|||
}
|
||||
|
||||
func run() (err error) {
|
||||
version, err := exec.Command("git", "describe", "--abbrev=0").Output()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
versionNew := strings.TrimSpace(string(version))
|
||||
versionNew := "v" + os.Getenv("VERSION")
|
||||
versionHash, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output()
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -46,7 +42,7 @@ func run() (err error) {
|
|||
}
|
||||
|
||||
func replaceInFile(fname, start, end, replacement string) (err error) {
|
||||
b, err := ioutil.ReadFile(fname)
|
||||
b, err := os.ReadFile(fname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -61,7 +57,7 @@ func replaceInFile(fname, start, end, replacement string) (err error) {
|
|||
fmt.Sprintf("%s%s%s", start, replacement, end),
|
||||
1,
|
||||
)
|
||||
err = ioutil.WriteFile(fname, []byte(newF), 0644)
|
||||
err = os.WriteFile(fname, []byte(newF), 0o644)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
#!/bin/bash
|
||||
VERSION=$(cat ./src/cli/cli.go | grep 'Version = "v' | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
|
||||
echo $VERSION
|
||||
|
||||
# Check dependencies.
|
||||
set -e
|
||||
xargs=$(which gxargs || which xargs)
|
||||
|
||||
# Validate settings.
|
||||
[ "$TRACE" ] && set -x
|
||||
|
||||
CONFIG=$@
|
||||
|
||||
for line in $CONFIG; do
|
||||
eval "$line"
|
||||
done
|
||||
|
||||
owner="schollz"
|
||||
repo="croc"
|
||||
tag="v${VERSION}"
|
||||
filename="dist/croc_${VERSION}_src.tar.gz"
|
||||
|
||||
# Define variables.
|
||||
GH_API="https://api.github.com"
|
||||
GH_REPO="$GH_API/repos/$owner/$repo"
|
||||
GH_TAGS="$GH_REPO/releases/tags/$tag"
|
||||
AUTH="Authorization: token $GITHUB_TOKEN"
|
||||
WGET_ARGS="--content-disposition --auth-no-challenge --no-cookie"
|
||||
CURL_ARGS="-LJO#"
|
||||
|
||||
if [[ "$tag" == 'LATEST' ]]; then
|
||||
GH_TAGS="$GH_REPO/releases/latest"
|
||||
fi
|
||||
|
||||
# Validate token.
|
||||
curl -o /dev/null -sH "$AUTH" $GH_REPO || { echo "Error: Invalid repo, token or network issue!"; exit 1; }
|
||||
|
||||
# Read asset tags.
|
||||
response=$(curl -sH "$AUTH" $GH_TAGS)
|
||||
|
||||
# Get ID of the asset based on given filename.
|
||||
eval $(echo "$response" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=')
|
||||
[ "$id" ] || { echo "Error: Failed to get release id for tag: $tag"; echo "$response" | awk 'length($0)<100' >&2; exit 1; }
|
||||
|
||||
# Upload asset
|
||||
echo "Uploading asset... "
|
||||
|
||||
# Construct url
|
||||
GH_ASSET="https://uploads.github.com/repos/$owner/$repo/releases/$id/assets?name=$(basename $filename)"
|
||||
|
||||
curl "$GITHUB_OAUTH_BASIC" --data-binary @"$filename" -H "Authorization: token $GITHUB_TOKEN" -H "Content-Type: application/octet-stream" $GH_ASSET
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#compdef $PROG
|
||||
|
||||
_cli_zsh_autocomplete() {
|
||||
|
||||
local -a opts
|
||||
local cur
|
||||
cur=${words[-1]}
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
||||
else
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
compdef _cli_zsh_autocomplete $PROG
|
||||
|
|
@ -3,17 +3,32 @@ package message
|
|||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/schollz/croc/v6/src/comm"
|
||||
"github.com/schollz/croc/v6/src/compress"
|
||||
"github.com/schollz/croc/v6/src/crypt"
|
||||
"github.com/schollz/croc/v10/src/comm"
|
||||
"github.com/schollz/croc/v10/src/compress"
|
||||
"github.com/schollz/croc/v10/src/crypt"
|
||||
log "github.com/schollz/logger"
|
||||
)
|
||||
|
||||
// Type is a message type
|
||||
type Type string
|
||||
|
||||
const (
|
||||
TypePAKE Type = "pake"
|
||||
TypeExternalIP Type = "externalip"
|
||||
TypeFinished Type = "finished"
|
||||
TypeError Type = "error"
|
||||
TypeCloseRecipient Type = "close-recipient"
|
||||
TypeCloseSender Type = "close-sender"
|
||||
TypeRecipientReady Type = "recipientready"
|
||||
TypeFileInfo Type = "fileinfo"
|
||||
)
|
||||
|
||||
// Message is the possible payload for messaging
|
||||
type Message struct {
|
||||
Type string `json:"t,omitempty"`
|
||||
Type Type `json:"t,omitempty"`
|
||||
Message string `json:"m,omitempty"`
|
||||
Bytes []byte `json:"b,omitempty"`
|
||||
Bytes2 []byte `json:"b2,omitempty"`
|
||||
Num int `json:"n,omitempty"`
|
||||
}
|
||||
|
||||
|
|
@ -23,34 +38,47 @@ func (m Message) String() string {
|
|||
}
|
||||
|
||||
// Send will send out
|
||||
func Send(c *comm.Comm, key crypt.Encryption, m Message) (err error) {
|
||||
func Send(c *comm.Comm, key []byte, m Message) (err error) {
|
||||
mSend, err := Encode(key, m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("writing %s message (%d bytes)", m.Type, len(mSend))
|
||||
_, err = c.Write(mSend)
|
||||
err = c.Send(mSend)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode will convert to bytes
|
||||
func Encode(key crypt.Encryption, m Message) (b []byte, err error) {
|
||||
func Encode(key []byte, m Message) (b []byte, err error) {
|
||||
b, err = json.Marshal(m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b = compress.Compress(b)
|
||||
b, err = key.Encrypt(b)
|
||||
if key != nil {
|
||||
log.Debugf("writing %s message (encrypted)", m.Type)
|
||||
b, err = crypt.Encrypt(b, key)
|
||||
} else {
|
||||
log.Debugf("writing %s message (unencrypted)", m.Type)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Decode will convert from bytes
|
||||
func Decode(key crypt.Encryption, b []byte) (m Message, err error) {
|
||||
b, err = key.Decrypt(b)
|
||||
if err != nil {
|
||||
return
|
||||
func Decode(key []byte, b []byte) (m Message, err error) {
|
||||
if key != nil {
|
||||
b, err = crypt.Decrypt(b, key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
b = compress.Decompress(b)
|
||||
err = json.Unmarshal(b, &m)
|
||||
if err == nil {
|
||||
if key != nil {
|
||||
log.Debugf("read %s message (encrypted)", m.Type)
|
||||
} else {
|
||||
log.Debugf("read %s message (unencrypted)", m.Type)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,17 +7,20 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/croc/v6/src/comm"
|
||||
"github.com/schollz/croc/v6/src/crypt"
|
||||
"github.com/schollz/croc/v10/src/comm"
|
||||
"github.com/schollz/croc/v10/src/crypt"
|
||||
log "github.com/schollz/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var TypeMessage Type = "message"
|
||||
|
||||
func TestMessage(t *testing.T) {
|
||||
m := Message{Type: "message", Message: "hello, world"}
|
||||
e, err := crypt.New(nil, nil)
|
||||
log.SetLevel("debug")
|
||||
m := Message{Type: TypeMessage, Message: "hello, world"}
|
||||
e, salt, err := crypt.New([]byte("pass"), nil)
|
||||
assert.Nil(t, err)
|
||||
fmt.Println(e.Salt())
|
||||
fmt.Println(string(salt))
|
||||
b, err := Encode(e, m)
|
||||
assert.Nil(t, err)
|
||||
fmt.Printf("%x\n", b)
|
||||
|
|
@ -26,6 +29,23 @@ func TestMessage(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.Equal(t, m, m2)
|
||||
assert.Equal(t, `{"t":"message","m":"hello, world"}`, m.String())
|
||||
_, err = Decode([]byte("not pass"), b)
|
||||
assert.NotNil(t, err)
|
||||
_, err = Encode([]byte("0"), m)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestMessageNoPass(t *testing.T) {
|
||||
log.SetLevel("debug")
|
||||
m := Message{Type: TypeMessage, Message: "hello, world"}
|
||||
b, err := Encode(nil, m)
|
||||
assert.Nil(t, err)
|
||||
fmt.Printf("%x\n", b)
|
||||
|
||||
m2, err := Decode(nil, b)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, m, m2)
|
||||
assert.Equal(t, `{"t":"message","m":"hello, world"}`, m.String())
|
||||
}
|
||||
|
||||
func TestSend(t *testing.T) {
|
||||
|
|
@ -47,7 +67,7 @@ func TestSend(t *testing.T) {
|
|||
log.Error(err)
|
||||
}
|
||||
log.Debugf("client %s connected", connection.RemoteAddr().String())
|
||||
go func(port string, connection net.Conn) {
|
||||
go func(_ string, connection net.Conn) {
|
||||
c := comm.New(connection)
|
||||
err = c.Send([]byte("hello, world"))
|
||||
assert.Nil(t, err)
|
||||
|
|
@ -64,11 +84,12 @@ func TestSend(t *testing.T) {
|
|||
}
|
||||
}()
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
a, err := comm.NewConnection("localhost:"+port, 10*time.Minute)
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
a, err := comm.NewConnection("127.0.0.1:"+port, 10*time.Minute)
|
||||
assert.Nil(t, err)
|
||||
m := Message{Type: "message", Message: "hello, world"}
|
||||
e, err := crypt.New(nil, nil)
|
||||
m := Message{Type: TypeMessage, Message: "hello, world"}
|
||||
e, salt, err := crypt.New([]byte("pass"), nil)
|
||||
log.Debug(salt)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Nil(t, Send(a, e, m))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
// From GitHub version/fork maintained by Stephen Paul Weber available at:
|
||||
// https://github.com/singpolyma/mnemonicode
|
||||
//
|
||||
// Originally from:
|
||||
// http://web.archive.org/web/20101031205747/http://www.tothink.com/mnemonic/
|
||||
|
||||
/*
|
||||
Copyright (c) 2000 Oren Tirosh <oren@hishome.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package mnemonicode
|
||||
|
||||
const base = 1626
|
||||
|
||||
// WordsRequired returns the number of words required to encode input
|
||||
// data of length bytes using mnomonic encoding.
|
||||
//
|
||||
// Every four bytes of input is encoded into three words. If there
|
||||
// is an extra one or two bytes they get an extra one or two words
|
||||
// respectively. If there is an extra three bytes, they will be encoded
|
||||
// into three words with the last word being one of a small set of very
|
||||
// short words (only needed to encode the last 3 bits).
|
||||
func WordsRequired(length int) int {
|
||||
return ((length + 1) * 3) / 4
|
||||
}
|
||||
|
||||
// EncodeWordList encodes src into mnemomic words which are appended to dst.
|
||||
// The final wordlist is returned.
|
||||
// There will be WordsRequired(len(src)) words appeneded.
|
||||
func EncodeWordList(dst []string, src []byte) (result []string) {
|
||||
if n := len(dst) + WordsRequired(len(src)); cap(dst) < n {
|
||||
result = make([]string, len(dst), n)
|
||||
copy(result, dst)
|
||||
} else {
|
||||
result = dst
|
||||
}
|
||||
|
||||
var x uint32
|
||||
for len(src) >= 4 {
|
||||
x = uint32(src[0])
|
||||
x |= uint32(src[1]) << 8
|
||||
x |= uint32(src[2]) << 16
|
||||
x |= uint32(src[3]) << 24
|
||||
src = src[4:]
|
||||
|
||||
i0 := int(x % base)
|
||||
i1 := int(x/base) % base
|
||||
i2 := int(x/base/base) % base
|
||||
result = append(result, WordList[i0], WordList[i1], WordList[i2])
|
||||
}
|
||||
if len(src) > 0 {
|
||||
x = 0
|
||||
for i := len(src) - 1; i >= 0; i-- {
|
||||
x <<= 8
|
||||
x |= uint32(src[i])
|
||||
}
|
||||
i := int(x % base)
|
||||
result = append(result, WordList[i])
|
||||
if len(src) >= 2 {
|
||||
i = int(x/base) % base
|
||||
result = append(result, WordList[i])
|
||||
}
|
||||
if len(src) == 3 {
|
||||
i = base + int(x/base/base)%7
|
||||
result = append(result, WordList[i])
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
// From GitHub version/fork maintained by Stephen Paul Weber available at:
|
||||
// https://github.com/singpolyma/mnemonicode
|
||||
//
|
||||
// Originally from:
|
||||
// http://web.archive.org/web/20101031205747/http://www.tothink.com/mnemonic/
|
||||
|
||||
/*
|
||||
Copyright (c) 2000 Oren Tirosh <oren@hishome.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package mnemonicode
|
||||
|
||||
// WordListVersion is the version of compiled in word list.
|
||||
const WordListVersion = "0.7"
|
||||
|
||||
var wordMap = make(map[string]int, len(WordList))
|
||||
|
||||
func init() {
|
||||
for i, w := range WordList {
|
||||
wordMap[w] = i
|
||||
}
|
||||
}
|
||||
|
||||
const longestWord = 7
|
||||
|
||||
var WordList = []string{
|
||||
"academy", "acrobat", "active", "actor", "adam", "admiral",
|
||||
"adrian", "africa", "agenda", "agent", "airline", "airport",
|
||||
"aladdin", "alarm", "alaska", "albert", "albino", "album",
|
||||
"alcohol", "alex", "algebra", "alibi", "alice", "alien",
|
||||
"alpha", "alpine", "amadeus", "amanda", "amazon", "amber",
|
||||
"america", "amigo", "analog", "anatomy", "angel", "animal",
|
||||
"antenna", "antonio", "apollo", "april", "archive", "arctic",
|
||||
"arizona", "arnold", "aroma", "arthur", "artist", "asia",
|
||||
"aspect", "aspirin", "athena", "athlete", "atlas", "audio",
|
||||
"august", "austria", "axiom", "aztec", "balance", "ballad",
|
||||
"banana", "bandit", "banjo", "barcode", "baron", "basic",
|
||||
"battery", "belgium", "berlin", "bermuda", "bernard", "bikini",
|
||||
"binary", "bingo", "biology", "block", "blonde", "bonus",
|
||||
"boris", "boston", "boxer", "brandy", "bravo", "brazil",
|
||||
"bronze", "brown", "bruce", "bruno", "burger", "burma",
|
||||
"cabinet", "cactus", "cafe", "cairo", "cake", "calypso",
|
||||
"camel", "camera", "campus", "canada", "canal", "cannon",
|
||||
"canoe", "cantina", "canvas", "canyon", "capital", "caramel",
|
||||
"caravan", "carbon", "cargo", "carlo", "carol", "carpet",
|
||||
"cartel", "casino", "castle", "castro", "catalog", "caviar",
|
||||
"cecilia", "cement", "center", "century", "ceramic", "chamber",
|
||||
"chance", "change", "chaos", "charlie", "charm", "charter",
|
||||
"chef", "chemist", "cherry", "chess", "chicago", "chicken",
|
||||
"chief", "china", "cigar", "cinema", "circus", "citizen",
|
||||
"city", "clara", "classic", "claudia", "clean", "client",
|
||||
"climax", "clinic", "clock", "club", "cobra", "coconut",
|
||||
"cola", "collect", "colombo", "colony", "color", "combat",
|
||||
"comedy", "comet", "command", "compact", "company", "complex",
|
||||
"concept", "concert", "connect", "consul", "contact", "context",
|
||||
"contour", "control", "convert", "copy", "corner", "corona",
|
||||
"correct", "cosmos", "couple", "courage", "cowboy", "craft",
|
||||
"crash", "credit", "cricket", "critic", "crown", "crystal",
|
||||
"cuba", "culture", "dallas", "dance", "daniel", "david",
|
||||
"decade", "decimal", "deliver", "delta", "deluxe", "demand",
|
||||
"demo", "denmark", "derby", "design", "detect", "develop",
|
||||
"diagram", "dialog", "diamond", "diana", "diego", "diesel",
|
||||
"diet", "digital", "dilemma", "diploma", "direct", "disco",
|
||||
"disney", "distant", "doctor", "dollar", "dominic", "domino",
|
||||
"donald", "dragon", "drama", "dublin", "duet", "dynamic",
|
||||
"east", "ecology", "economy", "edgar", "egypt", "elastic",
|
||||
"elegant", "element", "elite", "elvis", "email", "energy",
|
||||
"engine", "english", "episode", "equator", "escort", "ethnic",
|
||||
"europe", "everest", "evident", "exact", "example", "exit",
|
||||
"exotic", "export", "express", "extra", "fabric", "factor",
|
||||
"falcon", "family", "fantasy", "fashion", "fiber", "fiction",
|
||||
"fidel", "fiesta", "figure", "film", "filter", "final",
|
||||
"finance", "finish", "finland", "flash", "florida", "flower",
|
||||
"fluid", "flute", "focus", "ford", "forest", "formal",
|
||||
"format", "formula", "fortune", "forum", "fragile", "france",
|
||||
"frank", "friend", "frozen", "future", "gabriel", "galaxy",
|
||||
"gallery", "gamma", "garage", "garden", "garlic", "gemini",
|
||||
"general", "genetic", "genius", "germany", "global", "gloria",
|
||||
"golf", "gondola", "gong", "good", "gordon", "gorilla",
|
||||
"grand", "granite", "graph", "green", "group", "guide",
|
||||
"guitar", "guru", "hand", "happy", "harbor", "harmony",
|
||||
"harvard", "havana", "hawaii", "helena", "hello", "henry",
|
||||
"hilton", "history", "horizon", "hotel", "human", "humor",
|
||||
"icon", "idea", "igloo", "igor", "image", "impact",
|
||||
"import", "index", "india", "indigo", "input", "insect",
|
||||
"instant", "iris", "italian", "jacket", "jacob", "jaguar",
|
||||
"janet", "japan", "jargon", "jazz", "jeep", "john",
|
||||
"joker", "jordan", "jumbo", "june", "jungle", "junior",
|
||||
"jupiter", "karate", "karma", "kayak", "kermit", "kilo",
|
||||
"king", "koala", "korea", "labor", "lady", "lagoon",
|
||||
"laptop", "laser", "latin", "lava", "lecture", "left",
|
||||
"legal", "lemon", "level", "lexicon", "liberal", "libra",
|
||||
"limbo", "limit", "linda", "linear", "lion", "liquid",
|
||||
"liter", "little", "llama", "lobby", "lobster", "local",
|
||||
"logic", "logo", "lola", "london", "lotus", "lucas",
|
||||
"lunar", "machine", "macro", "madam", "madonna", "madrid",
|
||||
"maestro", "magic", "magnet", "magnum", "major", "mama",
|
||||
"mambo", "manager", "mango", "manila", "marco", "marina",
|
||||
"market", "mars", "martin", "marvin", "master", "matrix",
|
||||
"maximum", "media", "medical", "mega", "melody", "melon",
|
||||
"memo", "mental", "mentor", "menu", "mercury", "message",
|
||||
"metal", "meteor", "meter", "method", "metro", "mexico",
|
||||
"miami", "micro", "million", "mineral", "minimum", "minus",
|
||||
"minute", "miracle", "mirage", "miranda", "mister", "mixer",
|
||||
"mobile", "model", "modem", "modern", "modular", "moment",
|
||||
"monaco", "monica", "monitor", "mono", "monster", "montana",
|
||||
"morgan", "motel", "motif", "motor", "mozart", "multi",
|
||||
"museum", "music", "mustang", "natural", "neon", "nepal",
|
||||
"neptune", "nerve", "neutral", "nevada", "news", "ninja",
|
||||
"nirvana", "normal", "nova", "novel", "nuclear", "numeric",
|
||||
"nylon", "oasis", "object", "observe", "ocean", "octopus",
|
||||
"olivia", "olympic", "omega", "opera", "optic", "optimal",
|
||||
"orange", "orbit", "organic", "orient", "origin", "orlando",
|
||||
"oscar", "oxford", "oxygen", "ozone", "pablo", "pacific",
|
||||
"pagoda", "palace", "pamela", "panama", "panda", "panel",
|
||||
"panic", "paradox", "pardon", "paris", "parker", "parking",
|
||||
"parody", "partner", "passage", "passive", "pasta", "pastel",
|
||||
"patent", "patriot", "patrol", "patron", "pegasus", "pelican",
|
||||
"penguin", "pepper", "percent", "perfect", "perfume", "period",
|
||||
"permit", "person", "peru", "phone", "photo", "piano",
|
||||
"picasso", "picnic", "picture", "pigment", "pilgrim", "pilot",
|
||||
"pirate", "pixel", "pizza", "planet", "plasma", "plaster",
|
||||
"plastic", "plaza", "pocket", "poem", "poetic", "poker",
|
||||
"polaris", "police", "politic", "polo", "polygon", "pony",
|
||||
"popcorn", "popular", "postage", "postal", "precise", "prefix",
|
||||
"premium", "present", "price", "prince", "printer", "prism",
|
||||
"private", "product", "profile", "program", "project", "protect",
|
||||
"proton", "public", "pulse", "puma", "pyramid", "queen",
|
||||
"radar", "radio", "random", "rapid", "rebel", "record",
|
||||
"recycle", "reflex", "reform", "regard", "regular", "relax",
|
||||
"report", "reptile", "reverse", "ricardo", "ringo", "ritual",
|
||||
"robert", "robot", "rocket", "rodeo", "romeo", "royal",
|
||||
"russian", "safari", "salad", "salami", "salmon", "salon",
|
||||
"salute", "samba", "sandra", "santana", "sardine", "school",
|
||||
"screen", "script", "second", "secret", "section", "segment",
|
||||
"select", "seminar", "senator", "senior", "sensor", "serial",
|
||||
"service", "sheriff", "shock", "sierra", "signal", "silicon",
|
||||
"silver", "similar", "simon", "single", "siren", "slogan",
|
||||
"social", "soda", "solar", "solid", "solo", "sonic",
|
||||
"soviet", "special", "speed", "spiral", "spirit", "sport",
|
||||
"static", "station", "status", "stereo", "stone", "stop",
|
||||
"street", "strong", "student", "studio", "style", "subject",
|
||||
"sultan", "super", "susan", "sushi", "suzuki", "switch",
|
||||
"symbol", "system", "tactic", "tahiti", "talent", "tango",
|
||||
"tarzan", "taxi", "telex", "tempo", "tennis", "texas",
|
||||
"textile", "theory", "thermos", "tiger", "titanic", "tokyo",
|
||||
"tomato", "topic", "tornado", "toronto", "torpedo", "total",
|
||||
"totem", "tourist", "tractor", "traffic", "transit", "trapeze",
|
||||
"travel", "tribal", "trick", "trident", "trilogy", "tripod",
|
||||
"tropic", "trumpet", "tulip", "tuna", "turbo", "twist",
|
||||
"ultra", "uniform", "union", "uranium", "vacuum", "valid",
|
||||
"vampire", "vanilla", "vatican", "velvet", "ventura", "venus",
|
||||
"vertigo", "veteran", "victor", "video", "vienna", "viking",
|
||||
"village", "vincent", "violet", "violin", "virtual", "virus",
|
||||
"visa", "vision", "visitor", "visual", "vitamin", "viva",
|
||||
"vocal", "vodka", "volcano", "voltage", "volume", "voyage",
|
||||
"water", "weekend", "welcome", "western", "window", "winter",
|
||||
"wizard", "wolf", "world", "xray", "yankee", "yoga",
|
||||
"yogurt", "yoyo", "zebra", "zero", "zigzag", "zipper",
|
||||
"zodiac", "zoom", "abraham", "action", "address", "alabama",
|
||||
"alfred", "almond", "ammonia", "analyze", "annual", "answer",
|
||||
"apple", "arena", "armada", "arsenal", "atlanta", "atomic",
|
||||
"avenue", "average", "bagel", "baker", "ballet", "bambino",
|
||||
"bamboo", "barbara", "basket", "bazaar", "benefit", "bicycle",
|
||||
"bishop", "blitz", "bonjour", "bottle", "bridge", "british",
|
||||
"brother", "brush", "budget", "cabaret", "cadet", "candle",
|
||||
"capitan", "capsule", "career", "cartoon", "channel", "chapter",
|
||||
"cheese", "circle", "cobalt", "cockpit", "college", "compass",
|
||||
"comrade", "condor", "crimson", "cyclone", "darwin", "declare",
|
||||
"degree", "delete", "delphi", "denver", "desert", "divide",
|
||||
"dolby", "domain", "domingo", "double", "drink", "driver",
|
||||
"eagle", "earth", "echo", "eclipse", "editor", "educate",
|
||||
"edward", "effect", "electra", "emerald", "emotion", "empire",
|
||||
"empty", "escape", "eternal", "evening", "exhibit", "expand",
|
||||
"explore", "extreme", "ferrari", "first", "flag", "folio",
|
||||
"forget", "forward", "freedom", "fresh", "friday", "fuji",
|
||||
"galileo", "garcia", "genesis", "gold", "gravity", "habitat",
|
||||
"hamlet", "harlem", "helium", "holiday", "house", "hunter",
|
||||
"ibiza", "iceberg", "imagine", "infant", "isotope", "jackson",
|
||||
"jamaica", "jasmine", "java", "jessica", "judo", "kitchen",
|
||||
"lazarus", "letter", "license", "lithium", "loyal", "lucky",
|
||||
"magenta", "mailbox", "manual", "marble", "mary", "maxwell",
|
||||
"mayor", "milk", "monarch", "monday", "money", "morning",
|
||||
"mother", "mystery", "native", "nectar", "nelson", "network",
|
||||
"next", "nikita", "nobel", "nobody", "nominal", "norway",
|
||||
"nothing", "number", "october", "office", "oliver", "opinion",
|
||||
"option", "order", "outside", "package", "pancake", "pandora",
|
||||
"panther", "papa", "patient", "pattern", "pedro", "pencil",
|
||||
"people", "phantom", "philips", "pioneer", "pluto", "podium",
|
||||
"portal", "potato", "prize", "process", "protein", "proxy",
|
||||
"pump", "pupil", "python", "quality", "quarter", "quiet",
|
||||
"rabbit", "radical", "radius", "rainbow", "ralph", "ramirez",
|
||||
"ravioli", "raymond", "respect", "respond", "result", "resume",
|
||||
"retro", "richard", "right", "risk", "river", "roger",
|
||||
"roman", "rondo", "sabrina", "salary", "salsa", "sample",
|
||||
"samuel", "saturn", "savage", "scarlet", "scoop", "scorpio",
|
||||
"scratch", "scroll", "sector", "serpent", "shadow", "shampoo",
|
||||
"sharon", "sharp", "short", "shrink", "silence", "silk",
|
||||
"simple", "slang", "smart", "smoke", "snake", "society",
|
||||
"sonar", "sonata", "soprano", "source", "sparta", "sphere",
|
||||
"spider", "sponsor", "spring", "acid", "adios", "agatha",
|
||||
"alamo", "alert", "almanac", "aloha", "andrea", "anita",
|
||||
"arcade", "aurora", "avalon", "baby", "baggage", "balloon",
|
||||
"bank", "basil", "begin", "biscuit", "blue", "bombay",
|
||||
"brain", "brenda", "brigade", "cable", "carmen", "cello",
|
||||
"celtic", "chariot", "chrome", "citrus", "civil", "cloud",
|
||||
"common", "compare", "cool", "copper", "coral", "crater",
|
||||
"cubic", "cupid", "cycle", "depend", "door", "dream",
|
||||
"dynasty", "edison", "edition", "enigma", "equal", "eric",
|
||||
"event", "evita", "exodus", "extend", "famous", "farmer",
|
||||
"food", "fossil", "frog", "fruit", "geneva", "gentle",
|
||||
"george", "giant", "gilbert", "gossip", "gram", "greek",
|
||||
"grille", "hammer", "harvest", "hazard", "heaven", "herbert",
|
||||
"heroic", "hexagon", "husband", "immune", "inca", "inch",
|
||||
"initial", "isabel", "ivory", "jason", "jerome", "joel",
|
||||
"joshua", "journal", "judge", "juliet", "jump", "justice",
|
||||
"kimono", "kinetic", "leonid", "lima", "maze", "medusa",
|
||||
"member", "memphis", "michael", "miguel", "milan", "mile",
|
||||
"miller", "mimic", "mimosa", "mission", "monkey", "moral",
|
||||
"moses", "mouse", "nancy", "natasha", "nebula", "nickel",
|
||||
"nina", "noise", "orchid", "oregano", "origami", "orinoco",
|
||||
"orion", "othello", "paper", "paprika", "prelude", "prepare",
|
||||
"pretend", "profit", "promise", "provide", "puzzle", "remote",
|
||||
"repair", "reply", "rival", "riviera", "robin", "rose",
|
||||
"rover", "rudolf", "saga", "sahara", "scholar", "shelter",
|
||||
"ship", "shoe", "sigma", "sister", "sleep", "smile",
|
||||
"spain", "spark", "split", "spray", "square", "stadium",
|
||||
"star", "storm", "story", "strange", "stretch", "stuart",
|
||||
"subway", "sugar", "sulfur", "summer", "survive", "sweet",
|
||||
"swim", "table", "taboo", "target", "teacher", "telecom",
|
||||
"temple", "tibet", "ticket", "tina", "today", "toga",
|
||||
"tommy", "tower", "trivial", "tunnel", "turtle", "twin",
|
||||
"uncle", "unicorn", "unique", "update", "valery", "vega",
|
||||
"version", "voodoo", "warning", "william", "wonder", "year",
|
||||
"yellow", "young", "absent", "absorb", "accent", "alfonso",
|
||||
"alias", "ambient", "andy", "anvil", "appear", "apropos",
|
||||
"archer", "ariel", "armor", "arrow", "austin", "avatar",
|
||||
"axis", "baboon", "bahama", "bali", "balsa", "bazooka",
|
||||
"beach", "beast", "beatles", "beauty", "before", "benny",
|
||||
"betty", "between", "beyond", "billy", "bison", "blast",
|
||||
"bless", "bogart", "bonanza", "book", "border", "brave",
|
||||
"bread", "break", "broken", "bucket", "buenos", "buffalo",
|
||||
"bundle", "button", "buzzer", "byte", "caesar", "camilla",
|
||||
"canary", "candid", "carrot", "cave", "chant", "child",
|
||||
"choice", "chris", "cipher", "clarion", "clark", "clever",
|
||||
"cliff", "clone", "conan", "conduct", "congo", "content",
|
||||
"costume", "cotton", "cover", "crack", "current", "danube",
|
||||
"data", "decide", "desire", "detail", "dexter", "dinner",
|
||||
"dispute", "donor", "druid", "drum", "easy", "eddie",
|
||||
"enjoy", "enrico", "epoxy", "erosion", "except", "exile",
|
||||
"explain", "fame", "fast", "father", "felix", "field",
|
||||
"fiona", "fire", "fish", "flame", "flex", "flipper",
|
||||
"float", "flood", "floor", "forbid", "forever", "fractal",
|
||||
"frame", "freddie", "front", "fuel", "gallop", "game",
|
||||
"garbo", "gate", "gibson", "ginger", "giraffe", "gizmo",
|
||||
"glass", "goblin", "gopher", "grace", "gray", "gregory",
|
||||
"grid", "griffin", "ground", "guest", "gustav", "gyro",
|
||||
"hair", "halt", "harris", "heart", "heavy", "herman",
|
||||
"hippie", "hobby", "honey", "hope", "horse", "hostel",
|
||||
"hydro", "imitate", "info", "ingrid", "inside", "invent",
|
||||
"invest", "invite", "iron", "ivan", "james", "jester",
|
||||
"jimmy", "join", "joseph", "juice", "julius", "july",
|
||||
"justin", "kansas", "karl", "kevin", "kiwi", "ladder",
|
||||
"lake", "laura", "learn", "legacy", "legend", "lesson",
|
||||
"life", "light", "list", "locate", "lopez", "lorenzo",
|
||||
"love", "lunch", "malta", "mammal", "margo", "marion",
|
||||
"mask", "match", "mayday", "meaning", "mercy", "middle",
|
||||
"mike", "mirror", "modest", "morph", "morris", "nadia",
|
||||
"nato", "navy", "needle", "neuron", "never", "newton",
|
||||
"nice", "night", "nissan", "nitro", "nixon", "north",
|
||||
"oberon", "octavia", "ohio", "olga", "open", "opus",
|
||||
"orca", "oval", "owner", "page", "paint", "palma",
|
||||
"parade", "parent", "parole", "paul", "peace", "pearl",
|
||||
"perform", "phoenix", "phrase", "pierre", "pinball", "place",
|
||||
"plate", "plato", "plume", "pogo", "point", "polite",
|
||||
"polka", "poncho", "powder", "prague", "press", "presto",
|
||||
"pretty", "prime", "promo", "quasi", "quest", "quick",
|
||||
"quiz", "quota", "race", "rachel", "raja", "ranger",
|
||||
"region", "remark", "rent", "reward", "rhino", "ribbon",
|
||||
"rider", "road", "rodent", "round", "rubber", "ruby",
|
||||
"rufus", "sabine", "saddle", "sailor", "saint", "salt",
|
||||
"satire", "scale", "scuba", "season", "secure", "shake",
|
||||
"shallow", "shannon", "shave", "shelf", "sherman", "shine",
|
||||
"shirt", "side", "sinatra", "sincere", "size", "slalom",
|
||||
"slow", "small", "snow", "sofia", "song", "sound",
|
||||
"south", "speech", "spell", "spend", "spoon", "stage",
|
||||
"stamp", "stand", "state", "stella", "stick", "sting",
|
||||
"stock", "store", "sunday", "sunset", "support", "sweden",
|
||||
"swing", "tape", "think", "thomas", "tictac", "time",
|
||||
"toast", "tobacco", "tonight", "torch", "torso", "touch",
|
||||
"toyota", "trade", "tribune", "trinity", "triton", "truck",
|
||||
"trust", "type", "under", "unit", "urban", "urgent",
|
||||
"user", "value", "vendor", "venice", "verona", "vibrate",
|
||||
"virgo", "visible", "vista", "vital", "voice", "vortex",
|
||||
"waiter", "watch", "wave", "weather", "wedding", "wheel",
|
||||
"whiskey", "wisdom", "deal", "null", "nurse", "quebec",
|
||||
"reserve", "reunion", "roof", "singer", "verbal", "amen",
|
||||
"ego", "fax", "jet", "job", "rio", "ski",
|
||||
"yes",
|
||||
}
|
||||
|
|
@ -1,7 +1,169 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/croc/v10/src/utils"
|
||||
log "github.com/schollz/logger"
|
||||
)
|
||||
|
||||
// TCP_BUFFER_SIZE is the maximum packet size
|
||||
const TCP_BUFFER_SIZE = 1024 * 64
|
||||
|
||||
// DEFAULT_RELAY is the default relay used (can be set using --relay)
|
||||
const DEFAULT_RELAY = "142.93.177.120:9009"
|
||||
var (
|
||||
DEFAULT_RELAY = "croc.schollz.com"
|
||||
DEFAULT_RELAY6 = "croc6.schollz.com"
|
||||
DEFAULT_PORT = "9009"
|
||||
DEFAULT_PASSPHRASE = "pass123"
|
||||
INTERNAL_DNS = false
|
||||
)
|
||||
|
||||
// publicDNS are servers to be queried if a local lookup fails
|
||||
var publicDNS = []string{
|
||||
"1.0.0.1", // Cloudflare
|
||||
"1.1.1.1", // Cloudflare
|
||||
"[2606:4700:4700::1111]", // Cloudflare
|
||||
"[2606:4700:4700::1001]", // Cloudflare
|
||||
"8.8.4.4", // Google
|
||||
"8.8.8.8", // Google
|
||||
"[2001:4860:4860::8844]", // Google
|
||||
"[2001:4860:4860::8888]", // Google
|
||||
"9.9.9.9", // Quad9
|
||||
"149.112.112.112", // Quad9
|
||||
"[2620:fe::fe]", // Quad9
|
||||
"[2620:fe::fe:9]", // Quad9
|
||||
"8.26.56.26", // Comodo
|
||||
"8.20.247.20", // Comodo
|
||||
"208.67.220.220", // Cisco OpenDNS
|
||||
"208.67.222.222", // Cisco OpenDNS
|
||||
"[2620:119:35::35]", // Cisco OpenDNS
|
||||
"[2620:119:53::53]", // Cisco OpenDNS
|
||||
}
|
||||
|
||||
func getConfigFile(requireValidPath bool) (fname string, err error) {
|
||||
configFile, err := utils.GetConfigDir(requireValidPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fname = path.Join(configFile, "internal-dns")
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.SetLevel("info")
|
||||
log.SetOutput(os.Stderr)
|
||||
doRemember := false
|
||||
for _, flag := range os.Args {
|
||||
if flag == "--internal-dns" {
|
||||
INTERNAL_DNS = true
|
||||
break
|
||||
}
|
||||
if flag == "--remember" {
|
||||
doRemember = true
|
||||
}
|
||||
}
|
||||
if doRemember {
|
||||
// save in config file
|
||||
fname, err := getConfigFile(true)
|
||||
if err == nil {
|
||||
f, _ := os.Create(fname)
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
if !INTERNAL_DNS {
|
||||
fname, err := getConfigFile(false)
|
||||
if err == nil {
|
||||
INTERNAL_DNS = utils.Exists(fname)
|
||||
}
|
||||
}
|
||||
log.Trace("Using internal DNS: ", INTERNAL_DNS)
|
||||
var err error
|
||||
var addr string
|
||||
addr, err = lookup(DEFAULT_RELAY)
|
||||
if err == nil {
|
||||
DEFAULT_RELAY = net.JoinHostPort(addr, DEFAULT_PORT)
|
||||
} else {
|
||||
DEFAULT_RELAY = ""
|
||||
}
|
||||
log.Tracef("Default ipv4 relay: %s", addr)
|
||||
addr, err = lookup(DEFAULT_RELAY6)
|
||||
if err == nil {
|
||||
DEFAULT_RELAY6 = net.JoinHostPort(addr, DEFAULT_PORT)
|
||||
} else {
|
||||
DEFAULT_RELAY6 = ""
|
||||
}
|
||||
log.Tracef("Default ipv6 relay: %s", addr)
|
||||
}
|
||||
|
||||
// Resolve a hostname to an IP address using DNS.
|
||||
func lookup(address string) (ipaddress string, err error) {
|
||||
if !INTERNAL_DNS {
|
||||
log.Tracef("Using local DNS to resolve %s", address)
|
||||
return localLookupIP(address)
|
||||
}
|
||||
type Result struct {
|
||||
s string
|
||||
err error
|
||||
}
|
||||
result := make(chan Result, len(publicDNS))
|
||||
for _, dns := range publicDNS {
|
||||
go func(dns string) {
|
||||
var r Result
|
||||
r.s, r.err = remoteLookupIP(address, dns)
|
||||
log.Tracef("Resolved %s to %s using %s", address, r.s, dns)
|
||||
result <- r
|
||||
}(dns)
|
||||
}
|
||||
for i := 0; i < len(publicDNS); i++ {
|
||||
ipaddress = (<-result).s
|
||||
log.Tracef("Resolved %s to %s", address, ipaddress)
|
||||
if ipaddress != "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("failed to resolve %s: all DNS servers exhausted", address)
|
||||
return
|
||||
}
|
||||
|
||||
// localLookupIP returns a host's IP address using the local DNS configuration.
|
||||
func localLookupIP(address string) (ipaddress string, err error) {
|
||||
// Create a context with a 500 millisecond timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
r := &net.Resolver{}
|
||||
|
||||
// Use the context with timeout in the LookupHost function
|
||||
ip, err := r.LookupHost(ctx, address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ipaddress = ip[0]
|
||||
return
|
||||
}
|
||||
|
||||
// remoteLookupIP returns a host's IP address based on a remote DNS server.
|
||||
func remoteLookupIP(address, dns string) (ipaddress string, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
r := &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
|
||||
d := new(net.Dialer)
|
||||
return d.DialContext(ctx, network, dns+":53")
|
||||
},
|
||||
}
|
||||
ip, err := r.LookupHost(ctx, address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ipaddress = ip[0]
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
|
|
@ -0,0 +1,9 @@
|
|||
package tcp
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
DEFAULT_LOG_LEVEL = "debug"
|
||||
DEFAULT_ROOM_CLEANUP_INTERVAL = 10 * time.Minute
|
||||
DEFAULT_ROOM_TTL = 3 * time.Hour
|
||||
)
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO: maybe export from logger library?
|
||||
var availableLogLevels = []string{"info", "error", "warn", "debug", "trace"}
|
||||
|
||||
type serverOptsFunc func(s *server) error
|
||||
|
||||
func WithBanner(banner ...string) serverOptsFunc {
|
||||
return func(s *server) error {
|
||||
if len(banner) > 0 {
|
||||
s.banner = banner[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithLogLevel(level string) serverOptsFunc {
|
||||
return func(s *server) error {
|
||||
if !containsSlice(availableLogLevels, level) {
|
||||
return fmt.Errorf("invalid log level specified: %s", level)
|
||||
}
|
||||
s.debugLevel = level
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithRoomCleanupInterval(interval time.Duration) serverOptsFunc {
|
||||
return func(s *server) error {
|
||||
s.roomCleanupInterval = interval
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithRoomTTL(ttl time.Duration) serverOptsFunc {
|
||||
return func(s *server) error {
|
||||
s.roomTTL = ttl
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func containsSlice(s []string, e string) bool {
|
||||
for _, ss := range s {
|
||||
if e == ss {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
415
src/tcp/tcp.go
415
src/tcp/tcp.go
|
|
@ -8,17 +8,26 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/schollz/croc/v6/src/comm"
|
||||
"github.com/schollz/croc/v6/src/models"
|
||||
log "github.com/schollz/logger"
|
||||
"github.com/schollz/pake/v3"
|
||||
|
||||
"github.com/schollz/croc/v10/src/comm"
|
||||
"github.com/schollz/croc/v10/src/crypt"
|
||||
"github.com/schollz/croc/v10/src/models"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
host string
|
||||
port string
|
||||
debugLevel string
|
||||
banner string
|
||||
password string
|
||||
rooms roomMap
|
||||
|
||||
roomCleanupInterval time.Duration
|
||||
roomTTL time.Duration
|
||||
|
||||
stopRoomCleanup chan struct{}
|
||||
}
|
||||
|
||||
type roomInfo struct {
|
||||
|
|
@ -33,43 +42,57 @@ type roomMap struct {
|
|||
sync.Mutex
|
||||
}
|
||||
|
||||
var timeToRoomDeletion = 10 * time.Minute
|
||||
const pingRoom = "pinglkasjdlfjsaldjf"
|
||||
|
||||
// Run starts a tcp listener, run async
|
||||
func Run(debugLevel, port string, banner ...string) (err error) {
|
||||
// newDefaultServer initializes a new server, with some default configuration options
|
||||
func newDefaultServer() *server {
|
||||
s := new(server)
|
||||
s.roomCleanupInterval = DEFAULT_ROOM_CLEANUP_INTERVAL
|
||||
s.roomTTL = DEFAULT_ROOM_TTL
|
||||
s.debugLevel = DEFAULT_LOG_LEVEL
|
||||
s.stopRoomCleanup = make(chan struct{})
|
||||
return s
|
||||
}
|
||||
|
||||
// RunWithOptionsAsync asynchronously starts a TCP listener.
|
||||
func RunWithOptionsAsync(host, port, password string, opts ...serverOptsFunc) error {
|
||||
s := newDefaultServer()
|
||||
s.host = host
|
||||
s.port = port
|
||||
s.debugLevel = debugLevel
|
||||
if len(banner) > 0 {
|
||||
s.banner = banner[0]
|
||||
s.password = password
|
||||
for _, opt := range opts {
|
||||
err := opt(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not apply optional configurations: %w", err)
|
||||
}
|
||||
}
|
||||
return s.start()
|
||||
}
|
||||
|
||||
// Run starts a tcp listener, run async
|
||||
func Run(debugLevel, host, port, password string, banner ...string) (err error) {
|
||||
return RunWithOptionsAsync(host, port, password, WithBanner(banner...), WithLogLevel(debugLevel))
|
||||
}
|
||||
|
||||
func (s *server) start() (err error) {
|
||||
log.SetLevel(s.debugLevel)
|
||||
|
||||
// Mask our password in logs
|
||||
maskedPassword := ""
|
||||
if len(s.password) > 2 {
|
||||
maskedPassword = fmt.Sprintf("%c***%c", s.password[0], s.password[len(s.password)-1])
|
||||
} else {
|
||||
maskedPassword = s.password
|
||||
}
|
||||
|
||||
log.Debugf("starting with password '%s'", maskedPassword)
|
||||
|
||||
s.rooms.Lock()
|
||||
s.rooms.rooms = make(map[string]roomInfo)
|
||||
s.rooms.Unlock()
|
||||
|
||||
// delete old rooms
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(timeToRoomDeletion)
|
||||
roomsToDelete := []string{}
|
||||
s.rooms.Lock()
|
||||
for room := range s.rooms.rooms {
|
||||
if time.Since(s.rooms.rooms[room].opened) > 3*time.Hour {
|
||||
roomsToDelete = append(roomsToDelete, room)
|
||||
}
|
||||
}
|
||||
s.rooms.Unlock()
|
||||
|
||||
for _, room := range roomsToDelete {
|
||||
s.deleteRoom(room)
|
||||
}
|
||||
}
|
||||
}()
|
||||
go s.deleteOldRooms()
|
||||
defer s.stopRoomDeletion()
|
||||
|
||||
err = s.run()
|
||||
if err != nil {
|
||||
|
|
@ -79,47 +102,211 @@ func (s *server) start() (err error) {
|
|||
}
|
||||
|
||||
func (s *server) run() (err error) {
|
||||
log.Infof("starting TCP server on " + s.port)
|
||||
server, err := net.Listen("tcp", ":"+s.port)
|
||||
network := "tcp"
|
||||
addr := net.JoinHostPort(s.host, s.port)
|
||||
if s.host != "" {
|
||||
ip := net.ParseIP(s.host)
|
||||
if ip == nil {
|
||||
var tcpIP *net.IPAddr
|
||||
tcpIP, err = net.ResolveIPAddr("ip", s.host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ip = tcpIP.IP
|
||||
}
|
||||
addr = net.JoinHostPort(ip.String(), s.port)
|
||||
if s.host != "" {
|
||||
if ip.To4() != nil {
|
||||
network = "tcp4"
|
||||
} else {
|
||||
network = "tcp6"
|
||||
}
|
||||
}
|
||||
}
|
||||
addr = strings.Replace(addr, "127.0.0.1", "0.0.0.0", 1)
|
||||
log.Infof("starting TCP server on " + addr)
|
||||
server, err := net.Listen(network, addr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error listening on :"+s.port)
|
||||
return fmt.Errorf("error listening on %s: %w", addr, err)
|
||||
}
|
||||
defer server.Close()
|
||||
// spawn a new goroutine whenever a client connects
|
||||
for {
|
||||
connection, err := server.Accept()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "problem accepting connection")
|
||||
return fmt.Errorf("problem accepting connection: %w", err)
|
||||
}
|
||||
log.Debugf("client %s connected", connection.RemoteAddr().String())
|
||||
go func(port string, connection net.Conn) {
|
||||
errCommunication := s.clientCommuncation(port, comm.New(connection))
|
||||
c := comm.New(connection)
|
||||
room, errCommunication := s.clientCommunication(port, c)
|
||||
log.Debugf("room: %+v", room)
|
||||
log.Debugf("err: %+v", errCommunication)
|
||||
if errCommunication != nil {
|
||||
log.Warnf("relay-%s: %s", connection.RemoteAddr().String(), errCommunication.Error())
|
||||
log.Debugf("relay-%s: %s", connection.RemoteAddr().String(), errCommunication.Error())
|
||||
connection.Close()
|
||||
return
|
||||
}
|
||||
if room == pingRoom {
|
||||
log.Debugf("got ping")
|
||||
connection.Close()
|
||||
return
|
||||
}
|
||||
for {
|
||||
// check connection
|
||||
log.Debugf("checking connection of room %s for %+v", room, c)
|
||||
deleteIt := false
|
||||
s.rooms.Lock()
|
||||
if _, ok := s.rooms.rooms[room]; !ok {
|
||||
log.Debug("room is gone")
|
||||
s.rooms.Unlock()
|
||||
return
|
||||
}
|
||||
log.Debugf("room: %+v", s.rooms.rooms[room])
|
||||
if s.rooms.rooms[room].first != nil && s.rooms.rooms[room].second != nil {
|
||||
log.Debug("rooms ready")
|
||||
s.rooms.Unlock()
|
||||
break
|
||||
} else {
|
||||
if s.rooms.rooms[room].first != nil {
|
||||
errSend := s.rooms.rooms[room].first.Send([]byte{1})
|
||||
if errSend != nil {
|
||||
log.Debug(errSend)
|
||||
deleteIt = true
|
||||
}
|
||||
}
|
||||
}
|
||||
s.rooms.Unlock()
|
||||
if deleteIt {
|
||||
s.deleteRoom(room)
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}(s.port, connection)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) clientCommuncation(port string, c *comm.Comm) (err error) {
|
||||
// deleteOldRooms checks for rooms at a regular interval and removes those that
|
||||
// have exceeded their allocated TTL.
|
||||
func (s *server) deleteOldRooms() {
|
||||
ticker := time.NewTicker(s.roomCleanupInterval)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
var roomsToDelete []string
|
||||
s.rooms.Lock()
|
||||
for room := range s.rooms.rooms {
|
||||
if time.Since(s.rooms.rooms[room].opened) > s.roomTTL {
|
||||
roomsToDelete = append(roomsToDelete, room)
|
||||
}
|
||||
}
|
||||
s.rooms.Unlock()
|
||||
|
||||
for _, room := range roomsToDelete {
|
||||
s.deleteRoom(room)
|
||||
log.Debugf("room cleaned up: %s", room)
|
||||
}
|
||||
case <-s.stopRoomCleanup:
|
||||
ticker.Stop()
|
||||
log.Debug("room cleanup stopped")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) stopRoomDeletion() {
|
||||
log.Debug("stop room cleanup fired")
|
||||
s.stopRoomCleanup <- struct{}{}
|
||||
}
|
||||
|
||||
var weakKey = []byte{1, 2, 3}
|
||||
|
||||
func (s *server) clientCommunication(port string, c *comm.Comm) (room string, err error) {
|
||||
// establish secure password with PAKE for communication with relay
|
||||
B, err := pake.InitCurve(weakKey, 1, "siec")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
Abytes, err := c.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("Abytes: %s", Abytes)
|
||||
if bytes.Equal(Abytes, []byte("ping")) {
|
||||
room = pingRoom
|
||||
log.Debug("sending back pong")
|
||||
c.Send([]byte("pong"))
|
||||
return
|
||||
}
|
||||
err = B.Update(Abytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Send(B.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
strongKey, err := B.SessionKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("strongkey: %x", strongKey)
|
||||
|
||||
// receive salt
|
||||
salt, err := c.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
strongKeyForEncryption, _, err := crypt.New(strongKey, salt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("waiting for password")
|
||||
passwordBytesEnc, err := c.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
passwordBytes, err := crypt.Decrypt(passwordBytesEnc, strongKeyForEncryption)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(string(passwordBytes)) != s.password {
|
||||
err = fmt.Errorf("bad password")
|
||||
enc, _ := crypt.Encrypt([]byte(err.Error()), strongKeyForEncryption)
|
||||
if err = c.Send(enc); err != nil {
|
||||
return "", fmt.Errorf("send error: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// send ok to tell client they are connected
|
||||
banner := s.banner
|
||||
if len(banner) == 0 {
|
||||
banner = "ok"
|
||||
}
|
||||
log.Debugf("sending '%s'", banner)
|
||||
err = c.Send([]byte(banner + "|||" + c.Connection().RemoteAddr().String()))
|
||||
bSend, err := crypt.Encrypt([]byte(banner+"|||"+c.Connection().RemoteAddr().String()), strongKeyForEncryption)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Send(bSend)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// wait for client to tell me which room they want
|
||||
log.Debug("waiting for answer")
|
||||
roomBytes, err := c.Receive()
|
||||
enc, err := c.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
room := string(roomBytes)
|
||||
roomBytes, err := crypt.Decrypt(enc, strongKeyForEncryption)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
room = string(roomBytes)
|
||||
|
||||
s.rooms.Lock()
|
||||
// create the room if it is new
|
||||
|
|
@ -130,24 +317,32 @@ func (s *server) clientCommuncation(port string, c *comm.Comm) (err error) {
|
|||
}
|
||||
s.rooms.Unlock()
|
||||
// tell the client that they got the room
|
||||
err = c.Send([]byte("ok"))
|
||||
|
||||
bSend, err = crypt.Encrypt([]byte("ok"), strongKeyForEncryption)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Send(bSend)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
s.deleteRoom(room)
|
||||
return
|
||||
}
|
||||
log.Debugf("room %s has 1", room)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
if s.rooms.rooms[room].full {
|
||||
s.rooms.Unlock()
|
||||
err = c.Send([]byte("room full"))
|
||||
bSend, err = crypt.Encrypt([]byte("room full"), strongKeyForEncryption)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
s.deleteRoom(room)
|
||||
return
|
||||
}
|
||||
return nil
|
||||
err = c.Send(bSend)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Debugf("room %s has 2", room)
|
||||
s.rooms.rooms[room] = roomInfo{
|
||||
|
|
@ -172,7 +367,11 @@ func (s *server) clientCommuncation(port string, c *comm.Comm) (err error) {
|
|||
}(otherConnection, c, &wg)
|
||||
|
||||
// tell the sender everything is ready
|
||||
err = c.Send([]byte("ok"))
|
||||
bSend, err = crypt.Encrypt([]byte("ok"), strongKeyForEncryption)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Send(bSend)
|
||||
if err != nil {
|
||||
s.deleteRoom(room)
|
||||
return
|
||||
|
|
@ -181,7 +380,7 @@ func (s *server) clientCommuncation(port string, c *comm.Comm) (err error) {
|
|||
|
||||
// delete room
|
||||
s.deleteRoom(room)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) deleteRoom(room string) {
|
||||
|
|
@ -199,17 +398,19 @@ func (s *server) deleteRoom(room string) {
|
|||
}
|
||||
s.rooms.rooms[room] = roomInfo{first: nil, second: nil}
|
||||
delete(s.rooms.rooms, room)
|
||||
|
||||
}
|
||||
|
||||
// chanFromConn creates a channel from a Conn object, and sends everything it
|
||||
// Read()s from the socket to the channel.
|
||||
//
|
||||
// Read()s from the socket to the channel.
|
||||
func chanFromConn(conn net.Conn) chan []byte {
|
||||
c := make(chan []byte, 1)
|
||||
if err := conn.SetReadDeadline(time.Now().Add(3 * time.Hour)); err != nil {
|
||||
log.Warnf("can't set read deadline: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
b := make([]byte, models.TCP_BUFFER_SIZE)
|
||||
|
||||
for {
|
||||
n, err := conn.Read(b)
|
||||
if n > 0 {
|
||||
|
|
@ -242,47 +443,151 @@ func pipe(conn1 net.Conn, conn2 net.Conn) {
|
|||
if b1 == nil {
|
||||
return
|
||||
}
|
||||
conn2.Write(b1)
|
||||
if _, err := conn2.Write(b1); err != nil {
|
||||
log.Errorf("write error on channel 1: %v", err)
|
||||
}
|
||||
|
||||
case b2 := <-chan2:
|
||||
if b2 == nil {
|
||||
return
|
||||
}
|
||||
conn1.Write(b2)
|
||||
if _, err := conn1.Write(b2); err != nil {
|
||||
log.Errorf("write error on channel 2: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PingServer(address string) (err error) {
|
||||
log.Debugf("pinging %s", address)
|
||||
c, err := comm.NewConnection(address, 300*time.Millisecond)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
err = c.Send([]byte("ping"))
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
b, err := c.Receive()
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
if bytes.Equal(b, []byte("pong")) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("no pong")
|
||||
}
|
||||
|
||||
// ConnectToTCPServer will initiate a new connection
|
||||
// to the specified address, room with optional time limit
|
||||
func ConnectToTCPServer(address, room string, timelimit ...time.Duration) (c *comm.Comm, banner string, ipaddr string, err error) {
|
||||
func ConnectToTCPServer(address, password, room string, timelimit ...time.Duration) (c *comm.Comm, banner string, ipaddr string, err error) {
|
||||
if len(timelimit) > 0 {
|
||||
c, err = comm.NewConnection(address, timelimit[0])
|
||||
} else {
|
||||
c, err = comm.NewConnection(address)
|
||||
}
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
// get PAKE connection with server to establish strong key to transfer info
|
||||
A, err := pake.InitCurve(weakKey, 0, "siec")
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
err = c.Send(A.Bytes())
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
Bbytes, err := c.Receive()
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
err = A.Update(Bbytes)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
strongKey, err := A.SessionKey()
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
log.Debugf("strong key: %x", strongKey)
|
||||
|
||||
strongKeyForEncryption, salt, err := crypt.New(strongKey, nil)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
// send salt
|
||||
err = c.Send(salt)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("sending password")
|
||||
bSend, err := crypt.Encrypt([]byte(password), strongKeyForEncryption)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
err = c.Send(bSend)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
log.Debug("waiting for first ok")
|
||||
data, err := c.Receive()
|
||||
enc, err := c.Receive()
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
data, err := crypt.Decrypt(enc, strongKeyForEncryption)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
if !strings.Contains(string(data), "|||") {
|
||||
err = fmt.Errorf("bad response: %s", string(data))
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
banner = strings.Split(string(data), "|||")[0]
|
||||
ipaddr = strings.Split(string(data), "|||")[1]
|
||||
log.Debug("sending room")
|
||||
err = c.Send([]byte(room))
|
||||
log.Debugf("sending room; %s", room)
|
||||
bSend, err = crypt.Encrypt([]byte(room), strongKeyForEncryption)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
err = c.Send(bSend)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
log.Debug("waiting for room confirmation")
|
||||
data, err = c.Receive()
|
||||
enc, err = c.Receive()
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
data, err = crypt.Decrypt(enc, strongKeyForEncryption)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(data, []byte("ok")) {
|
||||
err = fmt.Errorf("got bad response: %s", data)
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
log.Debug("all set")
|
||||
|
|
|
|||
|
|
@ -1,34 +1,68 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/schollz/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTCP(t *testing.T) {
|
||||
timeToRoomDeletion = 100 * time.Millisecond
|
||||
go Run("debug", "8281", "8282")
|
||||
func BenchmarkConnection(b *testing.B) {
|
||||
log.SetLevel("trace")
|
||||
go Run("debug", "127.0.0.1", "8283", "pass123", "8284")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
c1, banner, _, err := ConnectToTCPServer("localhost:8281", "testRoom", 1*time.Minute)
|
||||
assert.Equal(t, banner, "8282")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c, _, _, _ := ConnectToTCPServer("127.0.0.1:8283", "pass123", fmt.Sprintf("testroom%d", i), 1*time.Minute)
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestTCP(t *testing.T) {
|
||||
log.SetLevel("error")
|
||||
timeToRoomDeletion := 100 * time.Millisecond
|
||||
go RunWithOptionsAsync("127.0.0.1", "8381", "pass123", WithBanner("8382"), WithLogLevel("debug"), WithRoomTTL(timeToRoomDeletion))
|
||||
time.Sleep(timeToRoomDeletion)
|
||||
err := PingServer("127.0.0.1:8381")
|
||||
assert.Nil(t, err)
|
||||
c2, _, _, err := ConnectToTCPServer("localhost:8281", "testRoom")
|
||||
assert.Nil(t, err)
|
||||
_, _, _, err = ConnectToTCPServer("localhost:8281", "testRoom")
|
||||
err = PingServer("127.0.0.1:8333")
|
||||
assert.NotNil(t, err)
|
||||
_, _, _, err = ConnectToTCPServer("localhost:8281", "testRoom", 1*time.Nanosecond)
|
||||
|
||||
time.Sleep(timeToRoomDeletion)
|
||||
c1, banner, _, err := ConnectToTCPServer("127.0.0.1:8381", "pass123", "testRoom", 1*time.Minute)
|
||||
assert.Equal(t, banner, "8382")
|
||||
assert.Nil(t, err)
|
||||
c2, _, _, err := ConnectToTCPServer("127.0.0.1:8381", "pass123", "testRoom")
|
||||
assert.Nil(t, err)
|
||||
_, _, _, err = ConnectToTCPServer("127.0.0.1:8381", "pass123", "testRoom")
|
||||
assert.NotNil(t, err)
|
||||
_, _, _, err = ConnectToTCPServer("127.0.0.1:8381", "pass123", "testRoom", 1*time.Nanosecond)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// try sending data
|
||||
assert.Nil(t, c1.Send([]byte("hello, c2")))
|
||||
data, err := c2.Receive()
|
||||
var data []byte
|
||||
for {
|
||||
data, err = c2.Receive()
|
||||
if bytes.Equal(data, []byte{1}) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte("hello, c2"), data)
|
||||
|
||||
assert.Nil(t, c2.Send([]byte("hello, c1")))
|
||||
data, err = c1.Receive()
|
||||
for {
|
||||
data, err = c1.Receive()
|
||||
if bytes.Equal(data, []byte{1}) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte("hello, c1"), data)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,64 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/kalafut/imohash"
|
||||
"github.com/schollz/mnemonicode"
|
||||
"github.com/minio/highwayhash"
|
||||
"github.com/schollz/croc/v10/src/mnemonicode"
|
||||
log "github.com/schollz/logger"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
)
|
||||
|
||||
const NbPinNumbers = 4
|
||||
const NbBytesWords = 4
|
||||
|
||||
// Get or create home directory
|
||||
func GetConfigDir(requireValidPath bool) (homedir string, err error) {
|
||||
if envHomedir, isSet := os.LookupEnv("CROC_CONFIG_DIR"); isSet {
|
||||
homedir = envHomedir
|
||||
} else if xdgConfigHome, isSet := os.LookupEnv("XDG_CONFIG_HOME"); isSet {
|
||||
homedir = path.Join(xdgConfigHome, "croc")
|
||||
} else {
|
||||
homedir, err = os.UserHomeDir()
|
||||
if err != nil {
|
||||
if !requireValidPath {
|
||||
err = nil
|
||||
homedir = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
homedir = path.Join(homedir, ".config", "croc")
|
||||
}
|
||||
|
||||
if requireValidPath {
|
||||
if _, err = os.Stat(homedir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(homedir, 0o700)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Exists reports whether the named file or directory exists.
|
||||
func Exists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
|
|
@ -39,13 +77,83 @@ func GetInput(prompt string) string {
|
|||
return strings.TrimSpace(text)
|
||||
}
|
||||
|
||||
// HashFile returns the hash of a file
|
||||
func HashFile(fname string) (hash256 []byte, err error) {
|
||||
return IMOHashFile(fname)
|
||||
// HashFile returns the hash of a file or, in case of a symlink, the
|
||||
// SHA256 hash of its target. Takes an argument to specify the algorithm to use.
|
||||
func HashFile(fname string, algorithm string, showProgress ...bool) (hash256 []byte, err error) {
|
||||
doShowProgress := false
|
||||
if len(showProgress) > 0 {
|
||||
doShowProgress = showProgress[0]
|
||||
}
|
||||
var fstats os.FileInfo
|
||||
fstats, err = os.Lstat(fname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fstats.Mode()&os.ModeSymlink != 0 {
|
||||
var target string
|
||||
target, err = os.Readlink(fname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(SHA256(target)), nil
|
||||
}
|
||||
switch algorithm {
|
||||
case "imohash":
|
||||
return IMOHashFile(fname)
|
||||
case "md5":
|
||||
return MD5HashFile(fname, doShowProgress)
|
||||
case "xxhash":
|
||||
return XXHashFile(fname, doShowProgress)
|
||||
case "highway":
|
||||
return HighwayHashFile(fname, doShowProgress)
|
||||
}
|
||||
err = fmt.Errorf("unspecified algorithm")
|
||||
return
|
||||
}
|
||||
|
||||
// HighwayHashFile returns highwayhash of a file
|
||||
func HighwayHashFile(fname string, doShowProgress bool) (hashHighway []byte, err error) {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
key, err := hex.DecodeString("1553c5383fb0b86578c3310da665b4f6e0521acf22eb58a99532ffed02a6b115")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h, err := highwayhash.New(key)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not create highwayhash: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if doShowProgress {
|
||||
stat, _ := f.Stat()
|
||||
fnameShort := path.Base(fname)
|
||||
if len(fnameShort) > 20 {
|
||||
fnameShort = fnameShort[:20] + "..."
|
||||
}
|
||||
bar := progressbar.NewOptions64(stat.Size(),
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionShowBytes(true),
|
||||
progressbar.OptionSetDescription(fmt.Sprintf("Hashing %s", fnameShort)),
|
||||
progressbar.OptionClearOnFinish(),
|
||||
)
|
||||
if _, err = io.Copy(io.MultiWriter(h, bar), f); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if _, err = io.Copy(h, f); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
hashHighway = h.Sum(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// MD5HashFile returns MD5 hash
|
||||
func MD5HashFile(fname string) (hash256 []byte, err error) {
|
||||
func MD5HashFile(fname string, doShowProgress bool) (hash256 []byte, err error) {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -53,23 +161,50 @@ func MD5HashFile(fname string) (hash256 []byte, err error) {
|
|||
defer f.Close()
|
||||
|
||||
h := md5.New()
|
||||
if _, err = io.Copy(h, f); err != nil {
|
||||
return
|
||||
if doShowProgress {
|
||||
stat, _ := f.Stat()
|
||||
fnameShort := path.Base(fname)
|
||||
if len(fnameShort) > 20 {
|
||||
fnameShort = fnameShort[:20] + "..."
|
||||
}
|
||||
bar := progressbar.NewOptions64(stat.Size(),
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionShowBytes(true),
|
||||
progressbar.OptionSetDescription(fmt.Sprintf("Hashing %s", fnameShort)),
|
||||
progressbar.OptionClearOnFinish(),
|
||||
)
|
||||
if _, err = io.Copy(io.MultiWriter(h, bar), f); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if _, err = io.Copy(h, f); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
hash256 = h.Sum(nil)
|
||||
return
|
||||
}
|
||||
|
||||
var imofull = imohash.NewCustom(0, 0)
|
||||
var imopartial = imohash.NewCustom(16*16*8*1024, 128*1024)
|
||||
|
||||
// IMOHashFile returns imohash
|
||||
func IMOHashFile(fname string) (hash []byte, err error) {
|
||||
b, err := imohash.SumFile(fname)
|
||||
b, err := imopartial.SumFile(fname)
|
||||
hash = b[:]
|
||||
return
|
||||
}
|
||||
|
||||
// IMOHashFileFull returns imohash of full file
|
||||
func IMOHashFileFull(fname string) (hash []byte, err error) {
|
||||
b, err := imofull.SumFile(fname)
|
||||
hash = b[:]
|
||||
return
|
||||
}
|
||||
|
||||
// XXHashFile returns the xxhash of a file
|
||||
func XXHashFile(fname string) (hash256 []byte, err error) {
|
||||
func XXHashFile(fname string, doShowProgress bool) (hash256 []byte, err error) {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -77,8 +212,25 @@ func XXHashFile(fname string) (hash256 []byte, err error) {
|
|||
defer f.Close()
|
||||
|
||||
h := xxhash.New()
|
||||
if _, err = io.Copy(h, f); err != nil {
|
||||
return
|
||||
if doShowProgress {
|
||||
stat, _ := f.Stat()
|
||||
fnameShort := path.Base(fname)
|
||||
if len(fnameShort) > 20 {
|
||||
fnameShort = fnameShort[:20] + "..."
|
||||
}
|
||||
bar := progressbar.NewOptions64(stat.Size(),
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionShowBytes(true),
|
||||
progressbar.OptionSetDescription(fmt.Sprintf("Hashing %s", fnameShort)),
|
||||
progressbar.OptionClearOnFinish(),
|
||||
)
|
||||
if _, err = io.Copy(io.MultiWriter(h, bar), f); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if _, err = io.Copy(h, f); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
hash256 = h.Sum(nil)
|
||||
|
|
@ -89,24 +241,26 @@ func XXHashFile(fname string) (hash256 []byte, err error) {
|
|||
func SHA256(s string) string {
|
||||
sha := sha256.New()
|
||||
sha.Write([]byte(s))
|
||||
return fmt.Sprintf("%x", sha.Sum(nil))
|
||||
return hex.EncodeToString(sha.Sum(nil))
|
||||
}
|
||||
|
||||
// PublicIP returns public ip address
|
||||
func PublicIP() (ip string, err error) {
|
||||
resp, err := http.Get("https://canhazip.com")
|
||||
// ask ipv4.icanhazip.com for the public ip
|
||||
// by making http request
|
||||
// if the request fails, return nothing
|
||||
resp, err := http.Get("http://ipv4.icanhazip.com")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ip = strings.TrimSpace(string(bodyBytes))
|
||||
}
|
||||
// read the body of the response
|
||||
// and return the ip address
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(resp.Body)
|
||||
ip = strings.TrimSpace(buf.String())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +268,8 @@ func PublicIP() (ip string, err error) {
|
|||
func LocalIP() string {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Error(err)
|
||||
return ""
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
|
|
@ -123,18 +278,32 @@ func LocalIP() string {
|
|||
return localAddr.IP.String()
|
||||
}
|
||||
|
||||
// GetRandomName returns mnemoicoded random name
|
||||
func GenerateRandomPin() string {
|
||||
s := ""
|
||||
max := new(big.Int)
|
||||
max.SetInt64(9)
|
||||
for i := 0; i < NbPinNumbers; i++ {
|
||||
v, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s += fmt.Sprintf("%d", v)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// GetRandomName returns mnemonicoded random name
|
||||
func GetRandomName() string {
|
||||
result := []string{}
|
||||
bs := make([]byte, 4)
|
||||
var result []string
|
||||
bs := make([]byte, NbBytesWords)
|
||||
rand.Read(bs)
|
||||
result = mnemonicode.EncodeWordList(result, bs)
|
||||
return strings.Join(result, "-")
|
||||
return GenerateRandomPin() + "-" + strings.Join(result, "-")
|
||||
}
|
||||
|
||||
// ByteCountDecimal converts bytes to human readable byte string
|
||||
func ByteCountDecimal(b int64) string {
|
||||
const unit = 1000
|
||||
const unit = 1024
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
|
|
@ -157,7 +326,7 @@ func MissingChunks(fname string, fsize int64, chunkSize int) (chunkRanges []int6
|
|||
defer f.Close()
|
||||
|
||||
fstat, err := os.Stat(fname)
|
||||
if fstat.Size() != fsize || err != nil {
|
||||
if err != nil || fstat.Size() != fsize {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +364,6 @@ func MissingChunks(fname string, fsize int64, chunkSize int) (chunkRanges []int6
|
|||
}
|
||||
}
|
||||
chunkRanges = append(chunkRanges, int64(curCount+1))
|
||||
chunks = chunkRanges
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -232,3 +400,238 @@ func GetLocalIPs() (ips []string, err error) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RandomFileName() (fname string, err error) {
|
||||
f, err := os.CreateTemp(".", "croc-stdin-")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fname = f.Name()
|
||||
_ = f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
func FindOpenPorts(host string, portNumStart, numPorts int) (openPorts []int) {
|
||||
openPorts = []int{}
|
||||
for port := portNumStart; port-portNumStart < 200; port++ {
|
||||
timeout := 100 * time.Millisecond
|
||||
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, fmt.Sprint(port)), timeout)
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
} else if err != nil {
|
||||
openPorts = append(openPorts, port)
|
||||
}
|
||||
if len(openPorts) >= numPorts {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// local ip determination
|
||||
// https://stackoverflow.com/questions/41240761/check-if-ip-address-is-in-private-network-space
|
||||
var privateIPBlocks []*net.IPNet
|
||||
|
||||
func init() {
|
||||
for _, cidr := range []string{
|
||||
"127.0.0.0/8", // IPv4 loopback
|
||||
"10.0.0.0/8", // RFC1918
|
||||
"172.16.0.0/12", // RFC1918
|
||||
"192.168.0.0/16", // RFC1918
|
||||
"169.254.0.0/16", // RFC3927 link-local
|
||||
"::1/128", // IPv6 loopback
|
||||
"fe80::/10", // IPv6 link-local
|
||||
"fc00::/7", // IPv6 unique local addr
|
||||
} {
|
||||
_, block, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("parse error on %q: %v", cidr, err))
|
||||
}
|
||||
privateIPBlocks = append(privateIPBlocks, block)
|
||||
}
|
||||
}
|
||||
|
||||
func IsLocalIP(ipaddress string) bool {
|
||||
if strings.Contains(ipaddress, "127.0.0.1") {
|
||||
return true
|
||||
}
|
||||
host, _, _ := net.SplitHostPort(ipaddress)
|
||||
ip := net.ParseIP(host)
|
||||
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||
return true
|
||||
}
|
||||
for _, block := range privateIPBlocks {
|
||||
if block.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ZipDirectory(destination string, source string) (err error) {
|
||||
if _, err = os.Stat(destination); err == nil {
|
||||
log.Errorf("%s file already exists!\n", destination)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Zipping %s to %s\n", source, destination)
|
||||
file, err := os.Create(destination)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
defer file.Close()
|
||||
writer := zip.NewWriter(file)
|
||||
// no compression because croc does its compression on the fly
|
||||
writer.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
|
||||
return flate.NewWriter(out, flate.NoCompression)
|
||||
})
|
||||
defer writer.Close()
|
||||
err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if info.Mode().IsRegular() {
|
||||
f1, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
defer f1.Close()
|
||||
zipPath := strings.ReplaceAll(path, source, strings.TrimSuffix(destination, ".zip"))
|
||||
zipPath = filepath.ToSlash(zipPath)
|
||||
w1, err := writer.Create(zipPath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if _, err := io.Copy(w1, f1); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\r\033[2K")
|
||||
fmt.Fprintf(os.Stderr, "\rAdding %s", zipPath)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func UnzipDirectory(destination string, source string) error {
|
||||
archive, err := zip.OpenReader(source)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
defer archive.Close()
|
||||
|
||||
for _, f := range archive.File {
|
||||
filePath := filepath.Join(destination, f.Name)
|
||||
fmt.Fprintf(os.Stderr, "\r\033[2K")
|
||||
fmt.Fprintf(os.Stderr, "\rUnzipping file %s", filePath)
|
||||
// Issue #593 conceal path traversal vulnerability
|
||||
// make sure the filepath does not have ".."
|
||||
filePath = filepath.Clean(filePath)
|
||||
if strings.Contains(filePath, "..") {
|
||||
log.Errorf("Invalid file path %s\n", filePath)
|
||||
}
|
||||
if f.FileInfo().IsDir() {
|
||||
os.MkdirAll(filePath, os.ModePerm)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
// check if file exists
|
||||
if _, err := os.Stat(filePath); err == nil {
|
||||
prompt := fmt.Sprintf("\nOverwrite '%s'? (y/N) ", filePath)
|
||||
choice := strings.ToLower(GetInput(prompt))
|
||||
if choice != "y" && choice != "yes" {
|
||||
fmt.Fprintf(os.Stderr, "Skipping '%s'\n", filePath)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
fileInArchive, err := f.Open()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
dstFile.Close()
|
||||
fileInArchive.Close()
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidFileName checks if a filename is valid
|
||||
// by making sure it has no invisible characters
|
||||
func ValidFileName(fname string) (err error) {
|
||||
// make sure it doesn't contain unicode or invisible characters
|
||||
for _, r := range fname {
|
||||
if !unicode.IsGraphic(r) {
|
||||
err = fmt.Errorf("non-graphical unicode: %x U+%d in '%x'", string(r), r, fname)
|
||||
return
|
||||
}
|
||||
if !unicode.IsPrint(r) {
|
||||
err = fmt.Errorf("non-printable unicode: %x U+%d in '%x'", string(r), r, fname)
|
||||
return
|
||||
}
|
||||
}
|
||||
// make sure basename does not include ".." or path separators
|
||||
_, basename := filepath.Split(fname)
|
||||
if strings.Contains(basename, "..") {
|
||||
err = fmt.Errorf("basename cannot contain '..': '%s'", basename)
|
||||
return
|
||||
}
|
||||
if strings.Contains(basename, string(os.PathSeparator)) {
|
||||
err = fmt.Errorf("basename cannot contain path separators: '%s'", basename)
|
||||
return
|
||||
}
|
||||
// make sure the filename is not an absolute path
|
||||
if filepath.IsAbs(fname) {
|
||||
err = fmt.Errorf("filename cannot be an absolute path: '%s'", fname)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const crocRemovalFile = "croc-marked-files.txt"
|
||||
|
||||
func MarkFileForRemoval(fname string) {
|
||||
// append the fname to the list of files to remove
|
||||
f, err := os.OpenFile(crocRemovalFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.WriteString(fname + "\n")
|
||||
}
|
||||
|
||||
func RemoveMarkedFiles() (err error) {
|
||||
// read the file and remove all the files
|
||||
f, err := os.Open(crocRemovalFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
fname := scanner.Text()
|
||||
err = os.Remove(fname)
|
||||
if err == nil {
|
||||
log.Tracef("Removed %s", fname)
|
||||
}
|
||||
}
|
||||
os.Remove(crocRemovalFile)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,31 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const TCP_BUFFER_SIZE = 1024 * 64
|
||||
|
||||
var bigFileSize = 75000000
|
||||
|
||||
func bigFile() {
|
||||
rand.Seed(0)
|
||||
bigBuff := make([]byte, 75000000)
|
||||
rand.Read(bigBuff)
|
||||
ioutil.WriteFile("bigfile.test", bigBuff, 0666)
|
||||
os.WriteFile("bigfile.test", bytes.Repeat([]byte("z"), bigFileSize), 0o666)
|
||||
}
|
||||
|
||||
func BenchmarkMD5(b *testing.B) {
|
||||
bigFile()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
MD5HashFile("bigfile.test")
|
||||
MD5HashFile("bigfile.test", false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,9 +33,10 @@ func BenchmarkXXHash(b *testing.B) {
|
|||
bigFile()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
XXHashFile("bigfile.test")
|
||||
XXHashFile("bigfile.test", false)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkImoHash(b *testing.B) {
|
||||
bigFile()
|
||||
b.ResetTimer()
|
||||
|
|
@ -42,6 +45,37 @@ func BenchmarkImoHash(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkHighwayHash(b *testing.B) {
|
||||
bigFile()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
HighwayHashFile("bigfile.test", false)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkImoHashFull(b *testing.B) {
|
||||
bigFile()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
IMOHashFileFull("bigfile.test")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSha256(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
SHA256("hello,world")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMissingChunks(b *testing.B) {
|
||||
bigFile()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
MissingChunks("bigfile.test", int64(bigFileSize), TCP_BUFFER_SIZE/2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExists(t *testing.T) {
|
||||
bigFile()
|
||||
defer os.Remove("bigfile.test")
|
||||
|
|
@ -53,10 +87,20 @@ func TestExists(t *testing.T) {
|
|||
func TestMD5HashFile(t *testing.T) {
|
||||
bigFile()
|
||||
defer os.Remove("bigfile.test")
|
||||
b, err := MD5HashFile("bigfile.test")
|
||||
b, err := MD5HashFile("bigfile.test", false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "9fed05acbacbc6a36555c998501c21f6", fmt.Sprintf("%x", b))
|
||||
_, err = MD5HashFile("bigfile.test.nofile")
|
||||
assert.Equal(t, "8304ff018e02baad0e3555bade29a405", fmt.Sprintf("%x", b))
|
||||
_, err = MD5HashFile("bigfile.test.nofile", false)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestHighwayHashFile(t *testing.T) {
|
||||
bigFile()
|
||||
defer os.Remove("bigfile.test")
|
||||
b, err := HighwayHashFile("bigfile.test", false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "3c32999529323ed66a67aeac5720c7bf1301dcc5dca87d8d46595e85ff990329", fmt.Sprintf("%x", b))
|
||||
_, err = HighwayHashFile("bigfile.test.nofile", false)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -65,16 +109,16 @@ func TestIMOHashFile(t *testing.T) {
|
|||
defer os.Remove("bigfile.test")
|
||||
b, err := IMOHashFile("bigfile.test")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "c0d1e123a96a598ea801cc503d3db8c0", fmt.Sprintf("%x", b))
|
||||
assert.Equal(t, "c0d1e12301e6c635f6d4a8ea5c897437", fmt.Sprintf("%x", b))
|
||||
}
|
||||
|
||||
func TestXXHashFile(t *testing.T) {
|
||||
bigFile()
|
||||
defer os.Remove("bigfile.test")
|
||||
b, err := XXHashFile("bigfile.test")
|
||||
b, err := XXHashFile("bigfile.test", false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "f2da6ee7e75e8324", fmt.Sprintf("%x", b))
|
||||
_, err = XXHashFile("nofile")
|
||||
assert.Equal(t, "4918740eb5ccb6f7", fmt.Sprintf("%x", b))
|
||||
_, err = XXHashFile("nofile", false)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -83,9 +127,9 @@ func TestSHA256(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestByteCountDecimal(t *testing.T) {
|
||||
assert.Equal(t, "10.0 kB", ByteCountDecimal(10000))
|
||||
assert.Equal(t, "10.0 kB", ByteCountDecimal(10240))
|
||||
assert.Equal(t, "50 B", ByteCountDecimal(50))
|
||||
assert.Equal(t, "12.4 MB", ByteCountDecimal(12378517))
|
||||
assert.Equal(t, "12.4 MB", ByteCountDecimal(13002343))
|
||||
}
|
||||
|
||||
func TestMissingChunks(t *testing.T) {
|
||||
|
|
@ -94,9 +138,9 @@ func TestMissingChunks(t *testing.T) {
|
|||
rand.Seed(1)
|
||||
bigBuff := make([]byte, fileSize)
|
||||
rand.Read(bigBuff)
|
||||
ioutil.WriteFile("missing.test", bigBuff, 0644)
|
||||
os.WriteFile("missing.test", bigBuff, 0o644)
|
||||
empty := make([]byte, chunkSize)
|
||||
f, err := os.OpenFile("missing.test", os.O_RDWR, 0644)
|
||||
f, err := os.OpenFile("missing.test", os.O_RDWR, 0o644)
|
||||
assert.Nil(t, err)
|
||||
for block := 0; block < fileSize/chunkSize; block++ {
|
||||
if block == 0 || block == 4 || block == 5 || block >= 7 {
|
||||
|
|
@ -114,7 +158,7 @@ func TestMissingChunks(t *testing.T) {
|
|||
os.Remove("missing.test")
|
||||
|
||||
content := []byte("temporary file's content")
|
||||
tmpfile, err := ioutil.TempFile("", "example")
|
||||
tmpfile, err := os.CreateTemp("", "example")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
@ -146,38 +190,78 @@ func TestMissingChunks(t *testing.T) {
|
|||
|
||||
func TestHashFile(t *testing.T) {
|
||||
content := []byte("temporary file's content")
|
||||
tmpfile, err := ioutil.TempFile("", "example")
|
||||
tmpfile, err := os.CreateTemp("", "example")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.Remove(tmpfile.Name()) // clean up
|
||||
|
||||
if _, err := tmpfile.Write(content); err != nil {
|
||||
if _, err = tmpfile.Write(content); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
if err = tmpfile.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
hashed, err := HashFile(tmpfile.Name())
|
||||
hashed, err := HashFile(tmpfile.Name(), "xxhash")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "18c9673a4bb8325d323e7f24fda9ae1e", fmt.Sprintf("%x", hashed))
|
||||
assert.Equal(t, "e66c561610ad51e2", fmt.Sprintf("%x", hashed))
|
||||
}
|
||||
|
||||
func TestPublicIP(t *testing.T) {
|
||||
ip, err := PublicIP()
|
||||
fmt.Println(ip)
|
||||
assert.True(t, strings.Contains(ip, "."))
|
||||
assert.True(t, strings.Contains(ip, ".") || strings.Contains(ip, ":"))
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestLocalIP(t *testing.T) {
|
||||
ip := LocalIP()
|
||||
fmt.Println(ip)
|
||||
assert.True(t, strings.Contains(ip, "."))
|
||||
assert.True(t, strings.Contains(ip, ".") || strings.Contains(ip, ":"))
|
||||
}
|
||||
|
||||
func TestGetRandomName(t *testing.T) {
|
||||
name := GetRandomName()
|
||||
fmt.Println(name)
|
||||
assert.NotEmpty(t, name)
|
||||
}
|
||||
|
||||
func intSliceSame(a, b []int) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestFindOpenPorts(t *testing.T) {
|
||||
openPorts := FindOpenPorts("127.0.0.1", 9009, 4)
|
||||
if !intSliceSame(openPorts, []int{9009, 9010, 9011, 9012}) && !intSliceSame(openPorts, []int{9014, 9015, 9016, 9017}) {
|
||||
t.Errorf("openPorts: %v", openPorts)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLocalIP(t *testing.T) {
|
||||
assert.True(t, IsLocalIP("192.168.0.14:9009"))
|
||||
}
|
||||
|
||||
func TestValidFileName(t *testing.T) {
|
||||
// contains regular characters
|
||||
assert.Nil(t, ValidFileName("中文.csl"))
|
||||
// contains regular characters
|
||||
assert.Nil(t, ValidFileName("[something].csl"))
|
||||
// contains regular characters
|
||||
assert.Nil(t, ValidFileName("[(something)].csl"))
|
||||
// contains invisible character
|
||||
err := ValidFileName("D中文.cslouglas")
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "non-graphical unicode: e2808b U+8203 in '44e4b8ade696872e63736c6f75676c6173e2808b'", err.Error())
|
||||
assert.NotNil(t, ValidFileName("hi..txt"))
|
||||
assert.NotNil(t, ValidFileName(path.Join(string(os.PathSeparator), "abs", string(os.PathSeparator), "hi.txt")))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue