Compare commits

..

487 Commits

Author SHA1 Message Date
f493d026b8 Add additional PHP classes 2025-03-21 11:31:17 +09:00
f7256c674a
Update README.md 2025-03-13 10:41:40 +09:00
feee46aabd
Merge pull request #51 from gnh1201/dev
Allow a local source
2025-03-11 14:57:58 +09:00
02554d75a9
Update llm-code-review.yml 2025-03-11 14:54:58 +09:00
4eea005aa0 Update index.php 2025-03-11 14:42:32 +09:00
b7f9b96bc4 Update index.php 2025-03-11 14:28:00 +09:00
75dba7093a
Create llm-code-review.yml 2025-03-11 14:23:19 +09:00
caf0afa73a
Update README.md 2025-02-17 10:34:42 +09:00
4f46d3e75f
Update server.py 2025-02-17 10:04:36 +09:00
6d368eb9e6
Update index.php 2025-02-17 09:46:01 +09:00
cb215bb423
Update index.php 2025-02-13 13:25:03 +09:00
efb2401a66
Update index.php 2025-02-13 13:24:49 +09:00
f2ead73592 Add relay_get_env_hash method 2025-02-09 02:53:21 +09:00
cc41ac4a2e Fix an user agent resolved incorrectly 2025-02-09 02:26:03 +09:00
479eb560da
Update index.php 2025-02-09 02:03:44 +09:00
768dad77cf
Update index.php 2025-02-09 02:03:10 +09:00
3b24c6c209
Update index.php 2025-02-09 01:57:13 +09:00
54c6f6f33e
Update index.php 2025-02-09 01:52:46 +09:00
4486c1d411
Update console.html 2025-01-22 13:37:24 +09:00
7efc6612c7
Update README.md 2025-01-20 12:07:08 +09:00
d34f68c8a1
Update README.md 2025-01-09 11:33:49 +09:00
0c634f6da0
Update README.md 2025-01-09 11:02:50 +09:00
ddac81a036
Update README.md 2025-01-08 17:59:51 +09:00
03b2315211
Delete .github/workflows/lint.yml 2025-01-06 17:02:47 +09:00
a97160f9a0
Update index.php 2025-01-06 16:16:08 +09:00
895cc03d31
Update index.php 2025-01-02 22:33:56 +09:00
10c91d5045
Update index.php 2025-01-02 22:11:30 +09:00
51de2628bf
Update index.php 2025-01-02 22:11:21 +09:00
376fd71b07
Update index.php 2025-01-02 22:04:44 +09:00
0bfc39a5e9
Update index.php 2025-01-02 21:55:16 +09:00
be5496aa16
Update index.php 2025-01-02 21:18:07 +09:00
02befd1c17
Update index.php 2025-01-02 21:13:09 +09:00
9926e1564d
Update README.md 2024-12-29 06:31:30 +09:00
18ec101d84
Update README.md 2024-12-28 17:26:32 +09:00
d7cc297a80 Fix bugs and add execution time measureing in PHP worker 2024-11-26 02:42:59 +09:00
24e05065f1 Update index.php 2024-11-26 02:22:55 +09:00
9a6b68cf9a Fix incorrect user agent, Update the dynamic loading feature 2024-11-26 01:18:08 +09:00
359d070b54
Update README.md 2024-11-26 00:08:18 +09:00
09c542431d Merge branch 'main' of https://github.com/gnh1201/caterpillar 2024-11-26 00:01:55 +09:00
604a4d7886 Add the dynamic loading feature in relay_invoke_method method 2024-11-26 00:01:50 +09:00
bb0710b723
Update README.md 2024-11-25 21:33:07 +09:00
3977d0c719 Update index.php 2024-11-24 01:49:28 +09:00
3d0f55c1ee Update console.html 2024-11-24 01:41:15 +09:00
297f0595f7 Update console.html 2024-11-24 01:39:50 +09:00
e3b5a344e3 Update console.html 2024-11-24 01:35:04 +09:00
78eb86800c Update index.php 2024-11-24 01:25:37 +09:00
8803fb7f05 Update index.php 2024-11-23 21:59:44 +09:00
0c0cbd5247 Update index.php 2024-11-23 21:50:51 +09:00
8d22483225 Update index.php 2024-11-23 18:27:08 +09:00
40a42c2811
Update README.md 2024-11-20 19:31:12 +09:00
286d75642a
Update README.md 2024-11-20 19:04:40 +09:00
e783b641be
Update README.md 2024-11-20 19:04:07 +09:00
660cfb3818 Update console.html 2024-11-18 22:07:56 +09:00
2ddef30daf disable upgrade-insecure-requests 2024-11-18 22:00:25 +09:00
cac5b29280
Update README.md 2024-11-18 21:22:17 +09:00
5960bb5732
Update README.md 2024-11-18 21:21:55 +09:00
3ffc8ca29c
Update server.py 2024-11-18 21:05:07 +09:00
85f2b19b46
Update server.py 2024-11-18 21:02:12 +09:00
9cc6bb3b08
Update server.py 2024-11-14 17:00:38 +09:00
69a3c5f323
Update server.py 2024-11-14 16:47:09 +09:00
943ff478aa
Update server.py 2024-11-14 16:17:04 +09:00
50265ad56b
Update server.py 2024-11-14 15:26:09 +09:00
a50edb3c77
Update server.py 2024-11-14 15:24:21 +09:00
1ebbd96340
Update base.py 2024-11-14 15:13:42 +09:00
0ad61d0a30
Update README.md 2024-11-13 16:06:33 +09:00
65f387dbeb
Update lint.yml 2024-11-13 03:58:08 +09:00
d592109de4
Update server.py 2024-11-13 03:56:06 +09:00
fbe2f6fa87 Update README.md 2024-11-12 19:06:20 +09:00
b0cc2652ba
Update server.py 2024-11-12 17:28:06 +09:00
f343020ae6
Update server.py 2024-11-12 17:27:39 +09:00
d8dd92f9c0
Update server.py 2024-11-12 17:26:37 +09:00
ed91362515
Update base.py 2024-11-12 17:09:40 +09:00
c19a38a008
Update base.py 2024-11-12 16:10:20 +09:00
5567325620
Update base.py 2024-11-12 16:10:09 +09:00
75aec1d8bf Update the method relay_web_search 2024-11-08 06:17:26 +09:00
0791e79be9 Update console.html 2024-11-08 06:13:55 +09:00
fedfc5f041 Add method relay_web_search 2024-11-08 05:45:12 +09:00
2f828252c5
Update index.php 2024-11-08 05:32:13 +09:00
09ac94bf00
Update index.php 2024-11-08 05:10:15 +09:00
ff381b8e3e Update console.html 2024-11-08 03:45:41 +09:00
7810e85dec Update console.html 2024-11-08 03:32:22 +09:00
1c77b640dd Update console.html 2024-11-08 03:22:50 +09:00
564d3dba03 Update console.html 2024-11-08 02:17:15 +09:00
006b1b17bd Update console.html 2024-11-08 01:33:05 +09:00
add701f92d Update console.html 2024-11-07 14:42:48 +09:00
be2f38d276 Update console.html 2024-11-06 18:47:29 +09:00
a1abaee646 Update console.html 2024-11-06 18:46:17 +09:00
36804b3763 Update console.html 2024-11-06 18:43:50 +09:00
ea0a24ee5f Update console.html 2024-11-06 18:42:08 +09:00
6c16083d9b
Update FUNDING.yml 2024-11-06 16:21:45 +09:00
7e63b0b00b
Update FUNDING.yml 2024-11-06 16:16:50 +09:00
6c0d5193a6
Update FUNDING.yml 2024-11-06 16:14:20 +09:00
e79a7cf68a
Update server.py 2024-11-04 18:00:31 +09:00
e067afc735
Update server.py 2024-11-04 17:37:59 +09:00
9f069b48e6
Update server.py 2024-11-04 17:34:55 +09:00
18738fe80b
Update server.py 2024-11-04 17:17:32 +09:00
99f960307d
Fix a cache overfitting issue: use re.IGNORECASE 2024-11-04 17:17:19 +09:00
3af8879adb Fix bugs 2024-10-25 15:27:45 +09:00
965423addb Update plugins 2024-10-25 11:49:00 +09:00
549cc9a8f9 Update plugins 2024-10-25 11:44:42 +09:00
20ddfbbcbb Update plugins 2024-10-25 11:42:31 +09:00
441fd81a0e Update plugins 2024-10-25 11:13:50 +09:00
5efe392ace Update plugins 2024-10-25 11:03:33 +09:00
05b51f7e7f Update plugins 2024-10-25 10:22:04 +09:00
0759dbffaf Update console.html, plugins 2024-10-25 09:36:33 +09:00
44425dbb8b Fix bugs when dispatch the RPC method 2024-10-25 08:59:49 +09:00
08212459eb Update plugins 2024-10-25 07:15:09 +09:00
ef72ba9296 Update console.html, plugins 2024-10-25 07:08:39 +09:00
2fa3f1471f
Update index.php 2024-10-25 01:52:03 +09:00
a71b6023ae
Update index.php 2024-10-25 01:51:38 +09:00
dc65f9a827
Fix Call to undefined function mysqli_fetch_all() when try a mysql query 2024-10-25 01:51:26 +09:00
e75a5a4b2d
Revert LICENSE to MIT 2024-10-24 14:43:08 +09:00
0e936a044d
Delete requirements-dev.txt 2024-10-24 14:16:54 +09:00
a6fd4515f1
Update requirements.txt 2024-10-24 14:16:40 +09:00
66b73730dc Update plugins 2024-10-23 17:07:42 +09:00
5a11042f7f Update plugins 2024-10-20 00:08:24 +09:00
d473dd569c Update plugins 2024-10-19 21:56:14 +09:00
22dcce06ab
Update README.md 2024-10-19 17:39:37 +09:00
1a65c9fdb8
Update server.py 2024-10-19 16:22:46 +09:00
bf8635c8b7
Update README.md 2024-10-19 16:15:53 +09:00
0c3f32d4f8
Update README.md 2024-10-18 22:33:38 +09:00
db9454a568
Update README.md 2024-10-18 22:32:37 +09:00
94252ba409 Add submodule caterpillar-plugins 2024-10-18 22:07:28 +09:00
acc6393658 Remove all plug-ins 2024-10-18 22:06:47 +09:00
a7371b1fa2
Update fediverse.py
Some checks failed
Ruff / ruff (push) Has been cancelled
2024-10-09 04:20:31 +09:00
0d543d2da9
Update fediverse.py 2024-10-09 04:17:25 +09:00
7472260de7
Update fediverse.py 2024-10-09 04:11:15 +09:00
2fb49ccf5f
Update README.md 2024-10-09 04:05:42 +09:00
796123f83b
Update server.py 2024-10-09 03:56:10 +09:00
1d43b64ce9
Update server.py 2024-10-09 03:55:24 +09:00
071e768c53
Update fediverse.py 2024-10-09 03:50:52 +09:00
a6ea467f6c
Update server.py 2024-10-09 03:49:38 +09:00
bd2e017598
Update fediverse.py 2024-10-09 03:33:21 +09:00
447b152f85
Update fediverse.py 2024-10-09 03:21:14 +09:00
3de3620b1f
Update fediverse.py 2024-10-09 03:16:25 +09:00
eafb738ad2
Update fediverse.py 2024-10-09 03:13:58 +09:00
539b7c3b58
Update fediverse.py 2024-10-09 03:11:52 +09:00
a376b8084d
Update fediverse.py 2024-10-09 03:08:47 +09:00
56c8c62aa6
Update fediverse.py 2024-10-09 03:07:34 +09:00
58e7322555
Update fediverse.py 2024-10-09 03:06:25 +09:00
c272efe8b1
Update fediverse.py
Some checks are pending
Ruff / ruff (push) Waiting to run
2024-10-09 01:32:45 +09:00
a0775bd15a
Update fediverse.py 2024-10-09 01:15:10 +09:00
f81d2f4649
Update fediverse.py 2024-10-09 01:11:17 +09:00
579a7fe89c
Update fediverse.py 2024-10-09 01:04:10 +09:00
0466dffb07
Update server.py 2024-10-09 01:03:43 +09:00
d5b65c71b1
Update server.py 2024-10-09 01:03:12 +09:00
c0ac6151c2
Update server.py 2024-10-09 01:02:07 +09:00
ea10dd83fd
Update fediverse.py 2024-10-09 00:46:01 +09:00
0b1bfadd8a
Update fediverse.py 2024-10-08 23:55:37 +09:00
5618186699
Update base.py 2024-10-08 23:36:14 +09:00
eb701292ce
Update base.py 2024-10-08 23:36:02 +09:00
f01c5d26a0
Update fediverse.py 2024-10-08 23:01:06 +09:00
bc08241aa2
Merge pull request #45 from zeroday0619/refactoring
Some checks failed
Ruff / ruff (push) Has been cancelled
feat: refactoring typed programing
2024-09-13 11:32:08 +09:00
Euiseo Cha
6b99ee97ce
feat: refactoring typed programing 2024-08-31 17:46:03 +09:00
Euiseo Cha
910e5e4ed5
feat: refactoring typed programing 2024-08-31 15:48:35 +09:00
Euiseo Cha
93e0b4edd9
feat: refactoring typed programing 2024-08-31 14:37:21 +09:00
9c2b66fb07 Update requirements-dev.txt
Some checks failed
Ruff / ruff (push) Has been cancelled
2024-08-29 11:47:49 +09:00
44d68203fe Update requirements-dev.txt
Some checks are pending
Ruff / ruff (push) Waiting to run
2024-08-28 22:26:03 +09:00
ea379fb750 Update requirements-dev.txt 2024-08-28 21:56:25 +09:00
148e9a20cf Update requirements-dev.txt 2024-08-28 21:48:46 +09:00
d1ba38ca0a Create .env.example
Some checks are pending
Ruff / ruff (push) Waiting to run
2024-08-28 20:15:50 +09:00
b5cd9d79ab
Update requirements-dev.txt
Some checks failed
Ruff / ruff (push) Has been cancelled
2024-08-21 07:56:06 +09:00
2314327358
Merge pull request #42 from gnh1201/change-license-to-gplv3
Change license to GPLv3 / 라이선스 GPLv3로 변경
2024-08-21 07:35:41 +09:00
b10a58f502
Update README.md
Some checks are pending
Ruff / ruff (push) Waiting to run
2024-08-20 20:11:55 +09:00
0c52169f7a
Update README.md 2024-08-20 20:11:10 +09:00
0daa8840ef
Update README.md 2024-08-20 20:07:41 +09:00
08d60f4248
Update README.md 2024-08-20 20:04:35 +09:00
889f21d484
Update README.md 2024-08-20 20:03:30 +09:00
fe8738a2a4
Update README.md 2024-08-20 20:00:55 +09:00
3648be7e94
Update README.md 2024-08-20 19:54:52 +09:00
7f644eed54
Update README.md 2024-08-20 19:51:50 +09:00
1fcebe78b4
Update README.md 2024-08-20 19:51:29 +09:00
ec4d38ed6f
Update README.md 2024-08-20 19:48:56 +09:00
f937a0314b
Update README.md 2024-08-20 19:46:03 +09:00
3ec236e955
Update README.md 2024-08-20 19:45:04 +09:00
3a5ed1d983
Merge pull request #43 from fossabot/add-license-scan-badge
Add license scan report and status
2024-08-20 19:39:48 +09:00
fossabot
33b7e075c5 Add license scan report and status
Signed off by: fossabot <badges@fossa.com>
2024-08-20 04:38:47 -06:00
e18e288beb Revert "Update LICENSE"
This reverts commit fbe0d7f1e2.
2024-08-20 19:32:37 +09:00
fbe0d7f1e2
Update LICENSE 2024-08-20 17:33:08 +09:00
0d1eea08eb
Change license to GPLv3 2024-08-20 17:15:37 +09:00
b47c89db14
Update README.md
Some checks are pending
Ruff / ruff (push) Waiting to run
2024-08-20 14:50:06 +09:00
6fa63100b7 Update the cover image 2024-08-20 14:36:26 +09:00
6353cb69ad
Merge pull request #41 from AkiaCode/serial
Some checks failed
Ruff / ruff (push) Has been cancelled
Implement simple serial connector
2024-08-12 21:24:09 +09:00
AkiaCode
b13a55a18b
Change author 2024-08-12 15:12:50 +09:00
AkiaCode
bdd6615670
implement simple serial connector 2024-08-11 02:29:27 +09:00
724f9f071e
Merge pull request #40 from gnh1201/elasticsearch
Some checks failed
Ruff / ruff (push) Has been cancelled
Implement the Always Online Cache with Elasticsearch / Always Online Cache 기능 중 엘라스틱서치 관련 구현
2024-08-01 15:16:56 +09:00
c23d2adefa ruff checked 2024-07-31 15:39:48 +09:00
b845fe9356 Add AlwaysOnline feature with Elasticsearch 2024-07-31 15:36:16 +09:00
6fa48ac64b Add elasticsearch webpage cache 2024-07-31 13:39:09 +09:00
cccae65676
Update README.md
Some checks failed
Ruff / ruff (push) Has been cancelled
2024-07-19 11:43:00 +09:00
7139092c12
Merge pull request #39 from gnh1201/Container
Some checks failed
Ruff / ruff (push) Has been cancelled
add docker container lifecycle methods
2024-07-17 09:35:07 +09:00
67dc16d976
Update index.php
Some checks failed
Ruff / ruff (push) Has been cancelled
2024-07-15 00:28:51 +09:00
486b12f643
Merge pull request #37 from gnh1201/yara
Adopt VirusTotal/yara (Pattern matching)
2024-07-14 19:12:57 +09:00
tkgka
13494e285b add docker container lifecycle methods 2024-07-14 18:46:02 +09:00
7abc36d66f
Merge pull request #38 from zeroday0619/ruff-check-gh-action
Some checks are pending
Ruff / ruff (push) Waiting to run
GitHub action configure for ruff action
2024-07-13 21:04:38 +09:00
feb7cff398
Merge pull request #36 from gnh1201/smtp
SMTP fix #35 / SMTP 서버 수정
2024-07-13 21:03:55 +09:00
5bff160d17
Merge branch 'main' into smtp 2024-07-13 21:03:36 +09:00
Euiseo Cha
4be3fa4df8
Merge branch 'gnh1201:main' into ruff-check-gh-action 2024-07-13 20:35:18 +09:00
d7acbf42f0
Merge pull request #34 from gnh1201/ssl-negotiation
Fix SSL negotiation + ruff formatted / SSL 협상 관련 수정 및 ruff format 적용
2024-07-13 19:21:33 +09:00
Euiseo Cha
27bb1616c2
feat: github action configure for ruff action 2024-07-13 18:35:03 +09:00
7b2d3529f5
Update README.md 2024-07-12 16:28:40 +09:00
68ef47b569
Update README.md 2024-07-12 11:28:05 +09:00
52b0949ce1
Update README.md 2024-07-12 11:23:42 +09:00
b5c8cc7b87 Adopt VirusTotal/yara (Pattern matching) 2024-07-12 10:35:50 +09:00
1d39e8a3b6 SMTP fix #35 2024-07-12 10:14:39 +09:00
ce3c6e7623 Update roadmap.png 2024-07-12 00:59:42 +09:00
43c3ff3466 Update roadmap.png 2024-07-12 00:43:24 +09:00
9bf5078294 Fix SSL negotiation 2024-07-12 00:34:33 +09:00
c206ee99e5 Update roadmap.png 2024-07-11 21:42:23 +09:00
1832801918 Update roadmap.png 2024-07-11 21:07:57 +09:00
f25ed75eb1 Update roadmap.png 2024-07-11 21:05:18 +09:00
1414824f86 Update roadmap.png 2024-07-11 21:03:19 +09:00
6de1888077 Add roadmap image 2024-07-11 20:53:06 +09:00
e2442a6290 Fix SSL negotiation #32 2024-07-11 19:03:34 +09:00
77ae320f40 ruff formatted 2024-07-11 19:02:08 +09:00
1a8022df73 Revert "Fix SSL negotiation + ruff formatted"
This reverts commit 57ed60fd01.
2024-07-11 19:00:58 +09:00
57ed60fd01 Fix SSL negotiation + ruff formatted 2024-07-11 18:42:10 +09:00
f1d2d58374
Merge pull request #28 from zeroday0619/logging-system
[Critical] Implemented a custom logger and fixed some invalid code implementations and fixed some misspellings.
2024-07-11 17:12:40 +09:00
60bcc14a93
Update server.py 2024-07-11 16:45:21 +09:00
afc974ae37
Rename certs/download_certs.sh to download_certs.sh 2024-07-11 16:43:39 +09:00
67160232d0
Rename certs/download_certs.bat to download_certs.bat 2024-07-11 16:43:28 +09:00
7078a1a3b8
Rename download_certs.sh to certs/download_certs.sh 2024-07-11 16:35:57 +09:00
9fa17e8c7b
Rename download_certs.bat to certs/download_certs.bat 2024-07-11 16:35:43 +09:00
7fdf83be9d
Merge pull request #26 from gnh1201/certs_downloader
Add the local certificate downloader / 로컬 인증서 다운로더 추가
2024-07-11 16:32:59 +09:00
Euiseo Cha
8f38f3d5de
fix: use_extension value extension 2024-07-11 16:05:56 +09:00
Euiseo Cha
391fc021d6
fix: use_extension value extension 2024-07-11 16:02:51 +09:00
Euiseo Cha
4d97e006e5
Merge branch 'gnh1201:main' into logging-system 2024-07-11 15:41:13 +09:00
caca4e3f65
One more fix #24 2024-07-11 15:38:38 +09:00
aa009b5a27 Revert "One more fix #24"
This reverts commit fffac9dcb7.
2024-07-11 15:37:43 +09:00
fffac9dcb7
One more fix #24 2024-07-11 15:36:28 +09:00
Euiseo Cha
1b47fb744a
Merge branch 'main' into logging-system 2024-07-11 15:27:31 +09:00
4d3af6c128
Merge branch 'main' into certs_downloader 2024-07-11 15:21:58 +09:00
e262348e75
Merge pull request #30 from zeroday0619/main
ruff exclude append assets data
2024-07-11 15:21:20 +09:00
Euiseo Cha
e9aba0f803
feat: ruff exclude append assets data 2024-07-11 15:19:50 +09:00
2072b06dd3
Merge pull request #29 from zeroday0619/ruff
apply ruff linter
2024-07-11 15:16:31 +09:00
5d4d70a33a
Merge branch 'main' into ruff 2024-07-11 15:12:06 +09:00
Euiseo Cha
c527b1d831
feat: apply ruff linter and update .gitignore 2024-07-11 15:05:51 +09:00
b3e4165fab
Merge branch 'main' into certs_downloader 2024-07-11 14:11:35 +09:00
1fdb788e0c
Update .gitignore 2024-07-11 14:11:09 +09:00
83b46d3ede
Merge pull request #27 from gnh1201/importlib_with_env
Change the `Extension.register()` process / 확장 등록 프로세스 변경
2024-07-11 14:08:01 +09:00
a9783c6081 One more fix #27 2024-07-10 09:28:06 +09:00
f5caf1cac7 Fix fix fix 2024-07-09 17:02:29 +09:00
Euiseo Cha
cd7350655b
feat: implemented a custom logger and fixed some invalid code implementations
fixed some misspellings. Note that this modification is highly likely to cause conflicts.
2024-07-09 17:01:25 +09:00
1064dc017b
Update web.py 2024-07-09 16:50:48 +09:00
810d5041cb
Update web.py 2024-07-09 16:50:40 +09:00
16bbddcd94
Update base.py 2024-07-09 16:44:45 +09:00
d3f3b423c6
Update server.py 2024-07-09 16:41:15 +09:00
bbb8c7fe55
Update server.py 2024-07-09 16:41:02 +09:00
2d2e54cd2d
Update base.py 2024-07-09 16:38:19 +09:00
0b94de24e9
Update README.md 2024-07-09 16:31:41 +09:00
2e23938ca7
Update README.md 2024-07-09 16:17:33 +09:00
e539e3e670 Revert "Update server.py"
This reverts commit c087312455.
2024-07-09 16:15:48 +09:00
c087312455
Update server.py 2024-07-09 16:11:05 +09:00
d0b1cc2bf5
Update README.md 2024-07-09 16:08:52 +09:00
3f185a237f
Update README.md 2024-07-09 15:56:20 +09:00
bf8ea7be95
One more fix #24 2024-07-09 15:53:34 +09:00
352fc3229f
Merge pull request #24 from gnh1201/nmap
Add the network port scanning support / 네트워크 포트 스캐닝 지원
2024-07-09 14:16:44 +09:00
e72e835f7d
Merge branch 'main' into nmap 2024-07-09 14:16:35 +09:00
dc995854ba
Merge pull request #25 from gnh1201/extension_importlib_wrapper
Fix `Extension.register()` API and related files / 모듈 등록 방식 변경
2024-07-09 14:05:10 +09:00
9f8d221aea
Update and rename configure_certs.sh to download_certs.sh 2024-07-09 13:56:36 +09:00
e27f5c34ab
Create download_certs.bat 2024-07-09 13:54:53 +09:00
f214120c1c
Update bio.py 2024-07-07 19:19:31 +09:00
6e4413a010
Merge pull request #23 from zeroday0619/biopython
Create bio.py
2024-07-07 19:18:50 +09:00
f953341330 Fix Extension.register() API and related files 2024-07-06 22:52:53 +09:00
Euiseo Cha
9133f75c38
feat: removed unnecessary code and added comments 2024-07-05 20:50:39 +09:00
32af8bd701
Rename portscan.py to nmap.py 2024-07-04 15:02:51 +09:00
823c97015f
Update server.py 2024-07-04 15:00:58 +09:00
7d5d997881
Update portscan.py 2024-07-04 14:46:40 +09:00
4bef7a2417
Create portscan.py 2024-07-04 14:44:57 +09:00
Euiseo Cha
001852956c
Create bio.py 2024-07-02 19:52:32 +09:00
96f77b956f Remove unused workers (will be refactor) 2024-07-02 12:32:22 +09:00
a73ff414c2
Update README.md 2024-06-28 14:27:27 +09:00
65d5e26c1e Update console.html 2024-06-26 16:21:05 +09:00
b8fa3e6722 Update console.html 2024-06-25 19:39:32 +09:00
0c393a1338 Update console.html 2024-06-25 17:28:05 +09:00
243cadd5d0 Update console.html 2024-06-25 17:22:03 +09:00
d671daaf46 fix indentation 2024-06-25 16:33:33 +09:00
17f1753c1c Update methods relay_get_geolocation, relay_fetch_url 2024-06-25 16:30:41 +09:00
ac7bfccdf3 Update console.html 2024-06-25 15:43:53 +09:00
c407739e12 Update index.php 2024-06-25 14:57:42 +09:00
607c6b00c1 Update console.html 2024-06-25 13:34:53 +09:00
acf661db24 Update console.html 2024-06-25 11:31:53 +09:00
6587e4a39e
Update console.html 2024-06-24 21:51:12 +09:00
d494195996 Update console.html 2024-06-21 22:24:32 +09:00
5004e64363 Update console.html 2024-06-21 22:21:21 +09:00
3bbad3d53e Update console.html 2024-06-21 22:02:53 +09:00
350230b210 Update console.html 2024-06-21 22:00:58 +09:00
be29ac3d85 Update console.html 2024-06-21 21:58:50 +09:00
7f76da7530 Move example_client.py to assets/python/example_client.py 2024-06-21 16:49:31 +09:00
ff33dac5df Update index.php 2024-06-21 16:47:11 +09:00
8c02b81c0a Update console.html 2024-06-21 16:19:10 +09:00
6aebfa3cd3 Update console.html 2024-06-21 14:35:27 +09:00
11be6928c0 Update README.md 2024-06-21 14:27:01 +09:00
56759a7fa7 add register_shutdown_function 2024-06-21 14:21:51 +09:00
61458e20b6 Update README.md 2024-06-21 12:43:19 +09:00
47e3f90e90 Update console.html 2024-06-21 12:41:18 +09:00
1e3059c433 Update index.php 2024-06-21 12:25:59 +09:00
bcaa4e76df Update index.php 2024-06-21 12:16:27 +09:00
c2c7e2efdd Update console.html 2024-06-21 11:32:08 +09:00
af53d52df0 Update console.html 2024-06-21 11:24:18 +09:00
bb997f6116
Update README.md 2024-06-20 19:17:47 +09:00
3e521c42a1
Update README.md 2024-06-20 19:16:29 +09:00
f35ceb4238 Merge branch 'main' of https://github.com/gnh1201/caterpillar 2024-06-20 19:05:32 +09:00
deb83b42cf Update console.html 2024-06-20 19:05:28 +09:00
95b51c2224
Update README.md 2024-06-20 17:49:13 +09:00
5a0bc8f077
Update README.md 2024-06-20 17:48:26 +09:00
a15353eeaf
Update README.md 2024-06-20 17:21:34 +09:00
e18d355ce3
Update web.py 2024-06-20 17:21:19 +09:00
b0e0351694
Update smtp.py 2024-06-20 17:21:08 +09:00
963f14a71c
Update server.py 2024-06-20 17:20:57 +09:00
dfdacea03f
Update index.php 2024-06-20 17:20:45 +09:00
472d989a17
Update console.html 2024-06-20 17:01:32 +09:00
6642750172
Create console.html 2024-06-20 16:48:52 +09:00
7be40acafd Update index.php 2024-06-20 16:40:49 +09:00
c38952d301 Update server.py, assets/php/index.php 2024-06-20 16:38:23 +09:00
1ceee29dba
Rename client.py to example_client.py 2024-06-20 14:00:04 +09:00
31c16ef881
Update index.php 2024-06-20 13:08:50 +09:00
4e237fc892
Update index.php 2024-06-20 12:49:10 +09:00
c38319dcaf
Update index.php 2024-06-20 12:48:57 +09:00
0d7200012f
Update index.php 2024-06-19 15:24:24 +09:00
2c0ae61911
Update index.php 2024-06-19 14:47:31 +09:00
8214ea83c5
Update index.php 2024-06-19 14:46:56 +09:00
86111d5c8e Update README.md 2024-06-10 05:32:58 +09:00
bf401c3957 Update README.md 2024-06-10 05:31:51 +09:00
377388f7cb Update a certificate 2024-06-10 05:29:37 +09:00
ff8cae2a59
Update fediverse.py 2024-06-05 13:50:47 +09:00
b984905f34
Update fediverse.py 2024-06-05 13:50:31 +09:00
2f1faef447
Update fediverse.py 2024-06-05 13:49:16 +09:00
ed75144e82
Update base.py 2024-05-21 00:04:39 +09:00
94ec3c9237
Update smtp.py 2024-05-20 16:19:59 +09:00
8133336c40
Update server.py 2024-05-20 16:18:38 +09:00
6616832338
Update base.py 2024-05-20 16:18:09 +09:00
3360522dec
Update web.py 2024-05-20 02:40:00 +09:00
c96a6e4bc2
Update web.py 2024-05-20 02:22:20 +09:00
48c9036361
Update server.py 2024-05-20 02:21:53 +09:00
47392f5665
Update smtp.py 2024-05-20 02:21:15 +09:00
ceafc6aedb
Update web.py 2024-05-20 02:20:49 +09:00
b37f4262b4
Update base.py 2024-05-20 02:20:12 +09:00
fd0b1575c2
Update base.py 2024-05-20 02:19:54 +09:00
2288afe57d
Update server.py 2024-05-20 02:18:19 +09:00
aabf0d0d6d
Create base.py 2024-05-20 02:16:49 +09:00
13f06f130c
Create web.py 2024-05-20 02:16:12 +09:00
d9b6600302
Update index.php 2024-04-05 19:09:49 +09:00
06545dcc66
Update index.php 2024-04-05 18:36:42 +09:00
55c82d8df2
Update index.php 2024-04-05 18:36:28 +09:00
0c727618c0
Update index.php 2024-04-05 18:19:58 +09:00
0a402b5201
Update index.php 2024-04-05 17:58:16 +09:00
486a399b88
Update server.py 2024-03-13 17:55:55 +09:00
f2331f6250
Update server.py 2024-03-13 17:55:39 +09:00
0f0cbf8b80
Update README.md 2024-03-13 17:22:17 +09:00
e1d1bbca6e
Update README.md 2024-03-13 17:22:00 +09:00
e6553656b3
Update README.md 2024-03-13 17:21:40 +09:00
db94e4df41
Update README.md 2024-03-13 17:14:37 +09:00
505b34cd18
Update wayback.py 2024-03-13 16:50:26 +09:00
f1b587e6d3
Update wayback.py 2024-03-13 16:49:10 +09:00
51dbfe2eb5
Update smtp.py 2024-03-13 16:36:02 +09:00
bad26a1910
Update smtp.py 2024-03-13 16:35:45 +09:00
c99469caa9
Update server.py 2024-03-13 16:35:21 +09:00
b87586af6c
Update wayback.py 2024-03-13 16:16:46 +09:00
33f924d441
Update wayback.py 2024-03-13 16:16:17 +09:00
db07be976a
Update container.py 2024-03-13 16:15:24 +09:00
ca170a334f
Update fediverse.py 2024-03-13 16:13:18 +09:00
5cb72daabb
Update wayback.py 2024-03-13 16:12:54 +09:00
d933549473
Update wayback.py 2024-03-13 16:12:45 +09:00
8f8814a5d8
Create wayback.py 2024-03-13 16:11:29 +09:00
aefe835f69
Update smtp.py 2024-03-12 15:49:07 +09:00
17f6802be4
Update worker.pl 2024-03-07 17:30:49 +09:00
044fda00b4
Update worker.js 2024-03-07 17:30:10 +09:00
cac9523beb
Update Worker.java 2024-03-07 17:29:53 +09:00
d2ebaf2c96
Update worker.rb 2024-03-07 17:29:35 +09:00
61201ab6f1
Create worker.js 2024-03-07 17:27:03 +09:00
9ac4801118
Merge pull request #19 from gnh1201/worker-java
Java base worker (Experimental)
2024-03-07 16:57:09 +09:00
0f5fd28c36 fix 2024-03-07 16:55:58 +09:00
e7e6f7acc8
Update worker.rb 2024-03-07 16:50:35 +09:00
992ff329d5
Update worker.pl 2024-03-07 16:48:28 +09:00
968791c233
Update worker.rb 2024-03-07 16:28:23 +09:00
1d665793bc
Update worker.pl 2024-03-07 16:05:07 +09:00
e7137b0079
Update worker.pl 2024-03-07 16:00:07 +09:00
118746e2e4
Create worker.rb 2024-03-07 15:56:47 +09:00
88c4073f2f
Create worker.pl 2024-03-07 15:54:11 +09:00
9c745166c4
Merge pull request #18 from gnh1201/worker-java
Java based worker (Experimental)
2024-03-07 15:44:37 +09:00
1024c54bfa fix 2024-03-07 15:40:56 +09:00
f92f952a6e
Merge pull request #17 from gnh1201/worker-java
Java based worker (Experimental)
2024-03-07 15:38:58 +09:00
759dba418c fix 2024-03-07 15:38:10 +09:00
125a7a787b
Update server.py 2024-03-07 03:24:28 +09:00
b9e6265063
Update server.py 2024-03-07 02:53:32 +09:00
20e48d0d96
Update server.py 2024-03-06 18:28:17 +09:00
cfb7866cd6
Update server.py 2024-03-06 18:20:33 +09:00
99244de542
Update container.py 2024-03-06 18:09:48 +09:00
740c9f2813
Update container.py 2024-03-06 18:09:20 +09:00
7becf4efaa
Update server.py 2024-03-06 18:07:09 +09:00
c94125cf07
Update server.py 2024-03-06 18:01:46 +09:00
56c2db1442
Update container.py 2024-03-06 17:34:47 +09:00
c49b35e7d9
Update server.py 2024-03-06 17:20:13 +09:00
33b20d3766
Update container.py 2024-03-06 17:18:36 +09:00
9b70581a67
Update container.py 2024-03-06 17:11:24 +09:00
066c297adc
Update server.py 2024-03-06 16:29:24 +09:00
efbf9363e0
Update server.py 2024-03-06 16:25:36 +09:00
14878eea50
Update container.py 2024-03-06 16:18:15 +09:00
b267f5e22b
Update container.py 2024-03-06 16:09:24 +09:00
83b9f81085
Update container.py 2024-03-06 16:08:27 +09:00
36b6325a4a
Create client.py 2024-03-06 16:04:51 +09:00
9701d4b122
Update container.py 2024-03-06 16:01:08 +09:00
7198281941
Update container.py 2024-03-06 15:53:03 +09:00
538cea5e84
Update server.py 2024-03-06 15:51:15 +09:00
a9c0ab559f
Update container.py 2024-03-06 15:50:01 +09:00
bee65559da
Update container.py 2024-03-06 15:47:17 +09:00
2ef54e851a
Create container.py 2024-03-06 15:46:45 +09:00
a606a1bcb0
Update server.py 2024-03-06 15:46:35 +09:00
53ead7e74b
Update server.py 2024-03-06 15:28:01 +09:00
259a1674e5
Update server.py 2024-03-06 15:22:32 +09:00
acab9b3d5a
Update server.py 2024-03-06 15:19:51 +09:00
91e9730903
Create Dockerfile 2024-03-06 15:11:13 +09:00
0b5d733dfb
Update server.py 2024-03-06 14:13:03 +09:00
d0d640ec72
Update server.py 2024-03-06 13:48:14 +09:00
4eab93a5ef
Update server.py 2024-03-06 13:46:38 +09:00
4ea3653a15
Update server.py 2024-03-06 13:46:29 +09:00
c526bcb6e0
Update server.py 2024-03-06 11:38:53 +09:00
1ae1947c0f fix 2024-03-05 17:48:56 +09:00
82b8e5711e update gradle 8.0.2 to 8.6 2024-03-05 17:46:51 +09:00
4cd311cc24 fix 2024-03-05 17:32:37 +09:00
2b5c8e6daf fix 2024-03-05 16:58:41 +09:00
ecf1628ee6 readFromRemoteServer() 2024-03-05 16:55:28 +09:00
62ba25e761
Update index.php 2024-03-05 16:35:48 +09:00
b83e332bc0 Java based worker 2024-03-05 16:12:27 +09:00
11d0b177cf
Update server.py 2024-03-05 12:31:32 +09:00
670cd3c7e6
Update server.py 2024-03-05 12:29:27 +09:00
c089a8e099
Update server.py 2024-03-05 12:27:38 +09:00
6e7d1b7d78
Update server.py 2024-03-05 11:37:38 +09:00
1df5a10cd6
Update server.py 2024-03-05 11:32:27 +09:00
32c192d524
Update server.py 2024-03-05 11:25:19 +09:00
b0e5280be5
Update server.py 2024-03-04 23:54:45 +09:00
699e455bef
Update server.py 2024-03-04 23:15:39 +09:00
f296b165d6
Update server.py 2024-03-04 23:10:04 +09:00
d8df399d1a
Update server.py 2024-03-04 23:00:58 +09:00
6185279e39
Update server.py 2024-03-04 18:38:54 +09:00
551656fa68
Update server.py 2024-03-04 18:37:44 +09:00
486e71b1bd
Update README.md 2024-03-04 17:38:18 +09:00
6b8ae266dc
Update README.md 2024-03-04 17:37:57 +09:00
0d07353af5
Update README.md 2024-03-04 17:32:36 +09:00
f8bcc45d52
Update README.md 2024-03-04 17:32:03 +09:00
b0ae95e728
Update README.md 2024-03-04 17:29:05 +09:00
023052489d
Update README.md 2024-03-04 17:26:38 +09:00
85c83d0996
Update README.md 2024-03-04 17:18:23 +09:00
dd67a463e0
Update README.md 2024-03-04 17:09:26 +09:00
68b3a9080f
Update README.md 2024-03-04 17:08:50 +09:00
4f07e5bdac
Update index.php 2024-03-04 16:29:48 +09:00
018c5a39eb
Update smtp.py 2024-03-02 19:49:48 +09:00
3e4a15a54c
Update smtp.py 2024-03-02 17:36:30 +09:00
dd0913cb67
Update server.py 2024-03-02 16:01:03 +09:00
4993806c22
Update server.py 2024-03-02 01:04:48 +09:00
8d0b9472a2
Update server.py 2024-03-02 01:04:07 +09:00
73538cc6be
Update server.py 2024-03-02 00:51:20 +09:00
65be31ef59
Update fediverse.py 2024-03-02 00:39:41 +09:00
3716522a9f
Update server.py 2024-03-02 00:38:48 +09:00
a678f6c04e
Update index.php 2024-03-01 04:43:54 +09:00
9e9c540a7e
Update smtp.py 2024-03-01 04:39:58 +09:00
58fc7ec088
Update smtp.py 2024-03-01 04:39:02 +09:00
e8c9f3ceb1
Update smtp.py 2024-03-01 04:36:04 +09:00
493d5dcb5f
Update smtp.py 2024-03-01 04:09:45 +09:00
50299e7b68
Update README.md 2024-03-01 03:10:30 +09:00
e0be93c7a6
Update server.py 2024-03-01 02:02:48 +09:00
f4d54d1b4e
Update smtp.py 2024-03-01 00:59:36 +09:00
01db314f0a
Update smtp.py 2024-03-01 00:54:08 +09:00
39a8012323
Update smtp.py 2024-03-01 00:53:33 +09:00
11aa65e53d
Update smtp.py 2024-03-01 00:49:39 +09:00
03649d8e53
Update smtp.py 2024-03-01 00:49:20 +09:00
c2d4a5e030
Update and rename smtp.py.tmp to smtp.py 2024-03-01 00:44:59 +09:00
2279a3b595
Create smtp.py.tmp 2024-03-01 00:40:54 +09:00
6e2ca2e87f
Update index.php 2024-02-29 23:10:53 +09:00
4cb3df0c76
Update index.php 2024-02-29 18:43:02 +09:00
3b5036979f
Update index.php 2024-02-29 18:39:43 +09:00
e7cb0c106f
Update index.php 2024-02-29 18:15:38 +09:00
4c9a7f42d5
Update index.php 2024-02-29 17:54:36 +09:00
61851cd1b1
Update index.php 2024-02-29 17:40:46 +09:00
801f18fe25
Update index.php 2024-02-29 17:38:49 +09:00
f9df624357
Update index.php 2024-02-29 17:38:03 +09:00
fd369d9464
Update index.php 2024-02-29 17:28:25 +09:00
2eb1c061c9
Update index.php 2024-02-29 17:27:53 +09:00
0138d6563c
Update index.php 2024-02-29 16:39:42 +09:00
2bd7209e69
Update index.php 2024-02-29 16:36:40 +09:00
2545efa7e2
Update index.php 2024-02-29 16:36:05 +09:00
524f860b28
Update server.py 2024-02-29 13:03:27 +09:00
719beea5c2
Update server.py 2024-02-28 17:04:06 +09:00
def06aef42
Update README.md 2024-02-28 17:00:25 +09:00
146016f477
Update README.md 2024-02-28 16:55:09 +09:00
60597942e6
Update README.md 2024-02-28 16:54:48 +09:00
40 changed files with 3647 additions and 721 deletions

13
.env.example Normal file
View File

@ -0,0 +1,13 @@
[settings]
PORT=5555
SERVER_URL=localhost
SERVER_CONNECTION_TYPE=proxy
CA_KEY=ca.key
CA_CERT=ca.crt
CERT_KEY=cert.key
CERT_DIR=certs/
#OPENSSL_BINPATH=openssl
CLIENT_ENCODING=utf-8
USE_EXTENSIONS=wayback.Wayback,bio.PyBio,alwaysonline.AlwaysOnline
ES_HOST=http://127.0.0.1:9200
ES_INDEX=alwaysonline

8
.github/FUNDING.yml vendored
View File

@ -1,8 +1,2 @@
# These are supported funding model platforms
github: gnh1201
open_collective: welsonjs
liberapay: catswords
custom: ['https://www.buymeacoffee.com/catswords', 'https://toss.me/catswords']
patreon: catswords # Replace with a single Patreon username
ko_fi: catswords
custom: ['https://gnh1201.link']

23
.github/workflows/llm-code-review.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: AI Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
issues:
types: [opened, reopened]
jobs:
repofix:
runs-on: ubuntu-latest
steps:
- name: Run RepoFixAI
uses: Manav916/llm-code-review@main
with:
groq_api_key: ${{ secrets.GROQ_API_KEY }}
groq_model: 'gemma-2-9b-it'
github_token: ${{ secrets.GITHUB_TOKEN }}
# exclude_extensions: 'txt'
repo_owner: ${{ github.repository_owner }}
repo_name: ${{ github.event.repository.name }}
event_number: ${{ github.event.number || github.event.issue.number }} # when listening for both pull requests and issues
event_name: ${{ github.event_name }}

175
.gitignore vendored
View File

@ -1,4 +1,179 @@
certs/
savedfiles/
logs/
settings.ini
.env
*.crt
*.key
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "plugins"]
path = plugins
url = https://github.com/gnh1201/caterpillar-plugins

8
Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM python:3.12
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt
COPY . /app
EXPOSE 5555

121
README.md
View File

@ -1,94 +1,115 @@
# gnh1201/caterpillar
Caterpillar - The simple and parasitic web proxy with spam filter (formerly, php-httpproxy)
# Caterpillar Proxy (Songchoongi Project)
![title image](assets/img/title.jfif)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fgnh1201%2Fcaterpillar.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fgnh1201%2Fcaterpillar?ref=badge_shield)
[![DOI 10.5281/zenodo.13346533](https://zenodo.org/badge/DOI/10.5281/zenodo.13346533.svg)](https://doi.org/10.5281/zenodo.13346533)
[![ChatGPT available](https://img.shields.io/badge/ChatGPT-74aa9c?logo=openai&logoColor=white)](#)
[![slideshare.net available](https://img.shields.io/badge/SlideShare-black?logo=slideshare)](https://www.slideshare.net/slideshow/2024-caterpillar-project-in-2024-korea-oss-contest/273031732)
[![Discord chat](https://img.shields.io/discord/359930650330923008?logo=discord)](https://discord.gg/9VVTHpfsVW)
[![Open to work](https://img.shields.io/badge/%23-OPENTOWORK-green)](https://github.com/gnh1201/welsonjs/discussions/167)
Caterpillar Proxy (Songchoongi Project) - The simple web debugging proxy (formerly, php-httpproxy)
![A cover image: Caterpillar on a tree looking at a rocket flying over the clouds](assets/img/cover.png)
You can connect all physical and logical channels with communication capabilities to the web!
Imagine various means such as time machines, satellites, quantum technology, sound, light, the Philosopher's Stone, or Excalibur, just like in science fiction movies! Caterpillar Proxy supports the implementation of extensions for Connectors, Filters, and RPC methods to bring your imagination to life.
:rocket: [Open the Caterpillar Proxy Web Console](https://pub-1a7a176eea68479cb5423e44273657ad.r2.dev/console.html)
## Use cases
* [Build a network tunnel using Python and the LAMP(PHP) stack (qiita.com)](https://qiita.com/gnh1201/items/40f9350ca6d308def6d4)
* [K-Anonymity for Spam Filtering: Case with Mastodon, and Misskey (qiita.com)](https://qiita.com/gnh1201/items/09f4081f84610db3a9d3)
* [File Upload Vulnerability Attack Test (Caterpillar Proxy) (youtu.be) ](https://youtu.be/sPZOCgYtLRw)
* [Real-time processing of emergency disaster sensor data (e.g., fire detection).](https://catswords.social/@catswords_oss/114016647285923011)
## How it works
### Basic structure
```
You <-> Proxy client (Python) <-> Parasitized proxy server (Optional, PHP) <-> On the Web
* You <-> Proxy client (Python) <-> Parasitized proxy server (Optional, PHP/LAMP) <-> On the Web
* You <-> Proxy client (Python) <-> Connector extensions (Optional, Python) <-> On the Web
```
For example, build a simple web debugging proxy on the shared servers.
This project supports two modes of connection. The default is stateless. You can use the stateful mode to avoid being constrained by transfer capacity limits (e.g., `max_upload_size`).
### Stateful mode
This project supports two modes of connection. The default is stateless. You can use the stateful mode to avoid being constrained by transfer capacity limits. See the [Stateful mode (catswords-oss.rdbl.io)](https://catswords-oss.rdbl.io/1155378128/5211324242).
### Spam filtering strategy
* [K-Anonymity](https://en.wikipedia.org/wiki/K-anonymity) test - Estimating whether the characters has been arranged by humans. (use [Have I Been Pwned](https://haveibeenpwned.com/Passwords))
* Not CAPTCHA - Image spam containing characters that look very similar to [CAPTCHA](https://en.wikipedia.org/wiki/CAPTCHA). (use [TrueCaptcha](https://truecaptcha.org/))
* VowelRatio10 - In characters arranged by humans, there is a high frequency of [vowels](https://en.wikipedia.org/wiki/Vowel) (aeiou) and [semivowels](https://en.wikipedia.org/wiki/Semivowel) (wy) and vowel-ending patterns included in strings that are 10 characters.
* Palindrome4 - Detect [palindromes](https://en.wikipedia.org/wiki/Palindrome) composed of 4 or more characters
* KnownWords4 - Detect [well-known words](https://github.com/dwyl/english-words) composed of 4 or more characters
* SearchEngine3 - In public search engine, the given string yields more than 2 results. (use [LibreY](https://github.com/Ahwxorg/librey))
* RepeatedNumber3 - Detect a repeated numbers 3 times or more.
* SSL decryption (MITM) when relaying to federated servers.
The strategies were implemented to respond to [the Fediverse Spam Attacks which started on the 15th of February](https://github.com/Mastodon-DE/blocklists/blob/main/spam%2F2024-02-15%2F2024-02-15-spam-mute-list.md).
### Connector extensions
This project supports the implementation of Connector extensions. The provided basic examples include implementations of web archives (caches) and serial communication as Connector extensions. Go to the [caterpillar-plugins repository (github.com)](https://github.com/gnh1201/caterpillar-plugins)
## (Optional) Before to use
If you have an ***will be parasitize*** server that you want to proxy, you can install the `index.php` (assets/php/index.php) file.
If you have a server that ***will be parasitized*** and you want to proxy it, you should upload the `index.php` file to a shared server. The index.php file is located in the `assets/php` directory within this repository.
## How to use
1. Write a file `.env`(Linux) or `settings.ini`(Windows). Like this:
```
[settings]
CONNECTION_TIMEOUT=1
PORT=5555
SERVER_URL=http://example.org
SERVER_CONNECTION_TYPE=stateless
SERVER_URL=localhost
SERVER_CONNECTION_TYPE=
CA_KEY=ca.key
CA_CERT=ca.crt
CERT_KEY=cert.key
CERT_DIR=certs/
OPENSSL_BINPATH=openssl
CLIENT_ENCODING=utf-8
LOCAL_DOMAIN=example.org
PROXY_PASS=http://127.0.0.1:3000
DICTIONARY_FILE=
TRUECAPTCHA_USERID=
TRUECAPTCHA_APIKEY=
LIBREY_APIURL=
USE_EXTENSIONS=wayback.Wayback,bio.PyBio
```
- (Optional) Install RootCA for SSL decryption ([Download CA Certificate](ca.crt))
***Note***: If using Caterpillar Proxy (Python) alone, set `SERVER_URL=localhost`. Otherwise, use the endpoint URL of the Worker script (PHP or Java), e.g., `SERVER_URL=http://example.org`.
- (Optional) Create a certificate for SSL decryption
```bash
chmod +x configure_certs.sh
./configure_certs.sh
sudo apt-get install -y ca-certificates
sudo cp ca.crt /usr/local/share/ca-certificates/caterpillar-ca.crt
sudo update-ca-certificates
```
2. Run `python3 server.py` and set HTTP(S) proxy in your web browser (e.g. Firefox)
2. Run `python3 server.py` and set HTTP(S) proxy in your web browser (e.g. Firefox, Chromium)
3. Test [100MB](http://speed.hetzner.de/100MB.bin)/[SSL](https://speed.hetzner.de/100MB.bin), [1GB](http://speed.hetzner.de/1GB.bin)/[SSL](https://speed.hetzner.de/1GB.bin), [10GB](http://speed.hetzner.de/10GB.bin)/[SSL](http://speed.hetzner.de/10GB.bin) download and check the speed (e.g. https://speed.hetzner.de/1GB.bin)
3. Test [100MB](http://speed.hetzner.de/100MB.bin)/[SSL](https://speed.hetzner.de/100MB.bin), [1GB](http://speed.hetzner.de/1GB.bin)/[SSL](https://speed.hetzner.de/1GB.bin), [10GB](http://speed.hetzner.de/10GB.bin)/[SSL](http://speed.hetzner.de/10GB.bin) download and check the speed.
3. Enjoy it
4. (Optional) With [Cloudflare](https://cloudflare.com), we can expect to accelerate the 4x speed and reduce the network stuck.
## (Optional) For Mastodon users
### In [Caterpillar installed directory]/settings.ini or .env
1. set `SERVER_URL` variable to `localhost` in `.env` (e.g. `SERVER_URL=localhost`)
2. set `PROXY_PASS` variable to Mastodon backend URI (e.g. `http://127.0.0.1:3000`)
3. if you want use notification, set `MASTODON_SERVER`(server domain) and `MASTODON_USER_TOKEN`(access token) variables
### In [Mastodon installed directory]/env.production
1. set `http_proxy` variable to `http://localhost:5555` (e.g. `http_proxy=http://localhost:5555`)
### In NGINX configuration
1. Check your port number of Caterpillar (default: 5555)
1. In NGINX configuration (e.g. `/etc/nginx/conf.d/mastodon.conf`), edit the `proxy_pass` like a `proxy_pass http://localhost:5555`
## References
* https://github.com/anapeksha/python-proxy-server
* https://github.com/inaz2/proxy2
## Extensions
* [Web Console](https://pub-1a7a176eea68479cb5423e44273657ad.r2.dev/console.html)
* Fediverse (e.g., Mastodon): See the [Fediverse (catswords-oss.rdbl.io)](https://catswords-oss.rdbl.io/1155378128/3821602484).
* Wayback (Private browsing with Google or Wayback cache): See the [Wayback (catswords-oss.rdbl.io)](https://catswords-oss.rdbl.io/1155378128/6994492654)
## Thanks to
* Pan Art by [@yeohangdang@i.peacht.art](#): [Image File](assets/img/logo.png)
* [GitHub Sponsors](https://github.com/sponsors/gnh1201)
### Pan Art by [@yeohangdang@i.peacht.art](https://i.peacht.art/@yeohangdang)
![Caterpillar Project Pan Art by @yeohangdang@i.peacht.art](assets/img/logo.png)
## Contributors
<a href="https://github.com/gnh1201/caterpillar/graphs/contributors">
<img src="https://contrib.rocks/image?repo=gnh1201/caterpillar" alt="Contributors" />
</a>
## Contact
* ActivityPub [@gnh1201@catswords.social](https://catswords.social/@gnh1201)
* abuse@catswords.net
## Our roadmap
![Roadmap image](assets/img/roadmap.png)
## Report abuse
- abuse@catswords.net
- [GitHub Security Advisories (gnh1201/caterpillar)](https://github.com/gnh1201/caterpillar/security)
## Join the community
- ActivityPub [@catswords_oss@catswords.social](https://catswords.social/@catswords_oss)
- XMPP [catswords@conference.omemo.id](xmpp:catswords@conference.omemo.id?join)
- [Join Catswords OSS on Microsoft Teams (teams.live.com)](https://teams.live.com/l/community/FEACHncAhq8ldnojAI)
- [Join Catswords OSS #caterpillar on Discord (discord.gg)](https://discord.gg/9VVTHpfsVW)
## Special channels
- [A paid consultation channel (m.expert.naver.com)](https://m.expert.naver.com/mobile/expert/product/detail?storeId=100051156&productId=100144540) is available for Korean (한국어) region.
- [Join the private operations channel (forms.gle)](https://forms.gle/ZKAAaGTiGamksHoo8) is available for all regions.
## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fgnh1201%2Fcaterpillar.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fgnh1201%2Fcaterpillar?ref=badge_large)

BIN
assets/img/cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

BIN
assets/img/roadmap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

9
assets/java/.gitattributes vendored Normal file
View File

@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf

5
assets/java/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build

View File

@ -0,0 +1,41 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java application project to get you started.
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
* User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html
*/
plugins {
// Apply the application plugin to add support for building a CLI application in Java.
id 'application'
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit Jupiter for testing.
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1'
// This dependency is used by the application.
implementation 'com.google.guava:guava:31.1-jre'
// Servlet API for Java EE
implementation 'javax.servlet:javax.servlet-api:4.0.1'
// https://mvnrepository.com/artifact/org.json/json
implementation group: 'org.json', name: 'json', version: '20240303'
}
application {
// Define the main class for the application.
mainClass = 'com.github.gnh1201.caterpillar.App'
}
tasks.named('test') {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}

View File

@ -0,0 +1,8 @@
package com.github.gnh1201.caterpillar;
public class App {
public static void main(String[] args) {
// Stateful mode only
throw new UnsupportedOperationException("This method is not yet implemented.");
}
}

View File

@ -0,0 +1,197 @@
// https://github.com/gnh1201/caterpillar
package com.github.gnh1201.caterpillar;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONObject;
import javax.servlet.jsp.JspWriter;
public class Worker {
@SuppressWarnings("unused")
private static class JsonRpc2 {
public class Call {
public Call(Map<String, String> params, String id, String method) {
this.params = params;
this.id = id;
this.method = method;
}
public final String jsonrpc = "2.0";
public String id;
public String method;
public Map<String, String> params;
public String toString() {
return (new JSONObject(this)).toString();
}
}
public class Error {
public Error(Map<String, String> error, String id) {
this.error = error;
this.id = id;
}
public final String jsonrpc = "2.0";
public String id;
public Map<String, String> error;
public String toString() {
return (new JSONObject(this)).toString();
}
}
public class Result {
public Result(Map<String, String> result, String id) {
this.result = result;
this.id = id;
}
public final String jsonrpc = "2.0";
public String id;
public Map<String, String> result;
public String toString() {
return (new JSONObject(this)).toString();
}
}
}
private static Map<String, String> parseHeaders(String str) {
Map<String, String> headers = new HashMap<>();
String[] lines = str.split("\r?\n");
String firstLine = lines[0];
headers.put("@method", firstLine.split(" ")[0]);
for (int i = 1; i < lines.length; i++) {
String line = lines[i];
Matcher matcher = Pattern.compile("^([^:]+):(.*)$").matcher(line);
if (matcher.matches()) {
headers.put(matcher.group(1), matcher.group(2).trim());
}
}
return headers;
}
private static readFromRemoteServer(String remoteAddress, int remotePort, String scheme, byte[] requestData, object _out, int bufferSize, String id) {
JspWriter jspWriterOut = (out instanceof JspWriter ? (JspWriter) _out : null);
Socket conn = (out instanceof Socket ? (Socket) _out : null);
char[] buffer = new char[bufferSize];
int bytesRead;
try {
// connect to the remote server
Socket sock = new Socket();
sock.connect(new InetSocketAddress(remoteAddress, remotePort));
DataOutputStream outRemote = new DataOutputStream(sock.getOutputStream());
BufferedReader inRemote = new BufferedReader(new InputStreamReader(sock.getInputStream()));
DataOutputStream outClient = new DataOutputStream(conn.getOutputStream());
BufferedReader inClient = new BufferedReader(new InputStreamReader(conn.getInputStream()));
// send data to the remote server
if (jspWriterOut != null) {
outRemote.write(requestData, 0, requestData.length);
} else if (conn != null) {
while ((bytesRead = inClient.read(buffer, 0, bufferSize)) != -1) {
char[] outBuffer = new char[bytesRead];
System.arraycopy(buffer, 0, outBuffer, 0, bytesRead);
outRemote.write(outBuffer);
}
}
// receive a response and forward to the client
while ((bytesRead = inRemote.read(buffer, 0, bufferSize)) != -1) {
if (jspWriterOut != null) {
out.write(buffer, 0, bytesRead);
} else if (conn != null) {
char[] outBuffer = new char[bytesRead];
System.arraycopy(buffer, 0, outBuffer, 0, bytesRead);
outClient.write(outBuffer);
}
}
} catch (Exception e) {
// build a description of the error
Map<String, Object> error = new HashMap<>();
error.put("status", 502);
error.put("code", e.getMessage());
error.put("message", e.getMessage());
String response = new JsonRpc2.Error(error, id);
// send output to the client
if (jspWriterOut != null) {
out.println(response.toString());
} else if (conn != null) {
conn.getOutputStream().write(response.toString().getBytes());
}
}
}
// Stateless (Servlet only)
public static void relayRequest(Map<String, Object> params, String id, JspWriter out) {
int bufferSize = Integer.parseInt((String) params.get("buffer_size"));
byte[] requestData = java.util.Base64.getDecoder().decode((String) params.get("request_data"));
Map<String, String> requestHeader = parseHeaders(new String(requestData));
int requestLength = Integer.parseInt((String) params.get("request_length"));
String clientAddress = (String) params.get("client_address");
int clientPort = Integer.parseInt((String) params.get("client_port"));
String clientEncoding = (String) params.get("client_encoding");
String remoteAddress = (String) params.get("remote_address");
int remotePort = Integer.parseInt((String) params.get("remote_port"));
String scheme = (String) params.get("scheme");
String datetime = (String) params.get("datetime");
switch (requestHeader.get("@method")) {
case "CONNECT":
Map<String, Object> error = new HashMap<>();
error.put("status", 405);
error.put("code", -1);
error.put("message", "Method Not Allowed");
out.println((new JsonRpc2.Error(error, id)).toString());
break;
default:
readFromRemoteServer(remoteAddress, remotePort, scheme, requestData, out, bufferSize, id);
}
}
// Stateful mode (Servlet + Socket combination)
public static void relayConenct(Map<String, Object> params, String id, JspWriter out) {
int bufferSize = Integer.parseInt((String) params.get("buffer_size"));
String clientAddress = (String) params.get("client_address");
int clientPort = Integer.parseInt((String) params.get("client_port"));
String clientEncoding = (String) params.get("client_encoding");
String remoteAddress = (String) params.get("remote_address");
int remotePort = Integer.parseInt((String) params.get("remote_port"));
String scheme = (String) params.get("scheme");
String datetime = (String) params.get("datetime");
long startTime = System.currentTimeMillis();
try {
Socket conn = new Socket(clientAddress, clientPort);
long stopTime = System.currentTimeMillis();
long connectionSpeed = stopTime - startTime;
JSONObject data = new JSONObject();
data.put("success", true);
data.put("connection_speed", connectionSpeed);
String jsonData = JsonPpc2.Call("relay_accept", data, id).toString();
DataOutputStream outToClient = new DataOutputStream(conn.getOutputStream());
outToClient.writeBytes(jsonData + "\r\n\r\n");
readFromRemoteServer(remoteAddress, remotePort, scheme, null, conn, bufferSize, id);
conn.close();
} catch (Exception e) {
JSONObject error = new JSONObject();
error.put("status", 502);
error.put("code", e.getMessage());
error.put("message", e.getMessage());
error.put("_params", params);
out.println(new JsonRpc2.Error(error, id)).toString();
}
}
}

View File

@ -0,0 +1,11 @@
package com.github.gnh1201.caterpillar;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class AppTest {
@Test void appHasAGreeting() {
App classUnderTest = new App();
assertNotNull(classUnderTest.getGreeting(), "app should have a greeting");
}
}

Binary file not shown.

View File

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
assets/java/gradlew vendored Normal file
View File

@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
assets/java/gradlew.bat vendored Normal file
View File

@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,11 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/8.0.2/userguide/multi_project_builds.html
*/
rootProject.name = 'caterpillar-worker'
include('app')

61
assets/php/class.tfa.php Normal file
View File

@ -0,0 +1,61 @@
<?php
// https://github.com/dimamedia/PHP-Simple-TOTP-and-PubKey
class tfa {
// RFC4648 Base32 alphabet
private $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
function getOtp($key) {
/* Base32 decoder */
// Remove spaces from the given public key and converting to an array
$key = str_split(str_replace(" ","",$key));
$n = 0;
$j = 0;
$binary_key = "";
// Decode public key's each character to base32 and save into binary chunks
foreach($key as $char) {
$n = $n << 5;
$n = $n + stripos($this->alphabet, $char);
$j += 5;
if($j >= 8) {
$j -= 8;
$binary_key .= chr(($n & (0xFF << $j)) >> $j);
}
}
/* End of Base32 decoder */
// current unix time 30sec period as binary
$binary_timestamp = pack('N*', 0) . pack('N*', floor(microtime(true)/30));
// generate keyed hash
$hash = hash_hmac('sha1', $binary_timestamp, $binary_key, true);
// generate otp from hash
$offset = ord($hash[19]) & 0xf;
$otp = (
((ord($hash[$offset+0]) & 0x7f) << 24 ) |
((ord($hash[$offset+1]) & 0xff) << 16 ) |
((ord($hash[$offset+2]) & 0xff) << 8 ) |
(ord($hash[$offset+3]) & 0xff)
) % pow(10, 6);
return $otp;
}
function getPubKey() {
$alphabet = str_split($this->alphabet);
$key = '';
// generate 16 chars public key from Base32 alphabet
for ($i = 0; $i < 16; $i++) $key .= $alphabet[mt_rand(0,31)];
// split into 4x4 chunks for easy reading
return implode(" ", str_split($key, 4));
}
}
?>

View File

@ -0,0 +1,70 @@
<?php
// coupang.class.php
// Coupang Product Search API integration class
// Namhyeon Go <gnh1201@gmail.com>
// https://github.com/gnh1201/welsonjs
//
date_default_timezone_set("GMT+0");
class CoupangProductSearch {
private $accessKey = "";
private $secretKey = "";
private $baseUrl = "https://api-gateway.coupang.com";
private function generateSignature($method, $path, $query = "") {
$datetime = (new \DateTime("now", new \DateTimeZone("GMT")))->format("ymd\THis\Z");
$message = $datetime . $method . $path . $query;
$signature = hash_hmac('sha256', $message, $this->secretKey);
return [
'authorization' => "CEA algorithm=HmacSHA256, access-key={$this->accessKey}, signed-date={$datetime}, signature={$signature}",
'datetime' => $datetime
];
}
public function searchProducts($keyword, $limit = 10, $subId = null, $imageSize = null, $srpLinkOnly = false) {
$path = "/v2/providers/affiliate_open_api/apis/openapi/products/search";
$queryParams = http_build_query([
'keyword' => $keyword,
'limit' => $limit,
'subId' => $subId,
'imageSize' => $imageSize,
'srpLinkOnly' => $srpLinkOnly
]);
$fullPath = $path . '?' . $queryParams;
$url = $this->baseUrl . $fullPath;
$signatureData = $this->generateSignature("GET", $path, $queryParams);
$authorization = $signatureData['authorization'];
$datetime = $signatureData['datetime'];
$headers = [
"Content-Type: application/json;charset=UTF-8",
"Authorization: $authorization"
];
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($httpCode === 200) {
return json_decode($response, true);
} else {
try {
return json_decode($response, true);
} catch (Exception $e) {
return [
"status" => $httpCode,
"message" => $e->getMessage()
];
}
}
}
}

View File

@ -1,25 +1,72 @@
<?php
// Caterpillar - The simple and parasitic web proxy with spam filter
// Namhyeon Go (Catswords Research) <abuse@catswords.net>
// https://github.com/gnh1201/caterpillar
// Created at: 2022-10-06
// Updated at: 2024-02-27
/* index.php
* Caterpillar Proxy Worker on PHP runtime
*
* Caterpillar Proxy - The simple web debugging proxy (formerly, php-httpproxy)
* Namhyeon Go (Catswords Research) <abuse@catswords.net>
* https://github.com/gnh1201/caterpillar
* Created at: 2022-10-06
* Updated at: 2025-03-11
*/
define("PERF_START_TIME", microtime(true));
define("PHP_HTTPPROXY_VERSION", "0.1.6.10");
define("DEFAULT_SOCKET_TIMEOUT", 1);
define("STATEFUL_SOCKET_TIMEOUT", 30);
define("MAX_EXECUTION_TIME", 0);
define("ALLOW_INVOKE_INSECURE_METHOD", false);
define("ALLOW_LOAD_INSECURE_SCRIPT", true);
define("DEFAULT_USER_AGENT", 'php-httpproxy/' . PHP_HTTPPROXY_VERSION . ' (Server; PHP ' . phpversion() . '; Caterpillar Proxy)');
define("RELAY_ALLOW_METHODS", ""); // e.g., GET,POST
define("RELAY_PROXY_PASS", ""); // e.g., https://example.org
define("RELAY_IMAGE_FILE_EXTENSIONS", ".png,.gif,.jpg");
define("RELAY_STATIC_FILE_EXTENSIONS", ".js,.css");
define("RELAY_ENABLE_JS_REDIRECT", false);
define("PHP_HTTPPROXY_VERSION", "0.1.5");
error_reporting(E_ALL);
ini_set("display_errors", 0);
ini_set("default_socket_timeout", DEFAULT_SOCKET_TIMEOUT); // must be. because of `feof()` works
ini_set("max_execution_time", MAX_EXECUTION_TIME);
if (strpos($_SERVER['HTTP_USER_AGENT'], "php-httpproxy/") !== 0) {
exit('<!DOCTYPE html><html><head><title>It works!</title><meta charset="utf-8"></head><body><h1>It works!</h1><p><a href="https://github.com/gnh1201/caterpillar">Download the client</a></p><hr><p>php-httpproxy/' . PHP_HTTPPROXY_VERSION . ' (Server; PHP ' . phpversion() . '; abuse@catswords.net)</p></body></html>');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: *');
header("Access-Control-Allow-Headers: *");
function get_current_execution_time() {
$end_time = microtime(true);
return $end_time - PERF_START_TIME;
}
ini_set("default_socket_timeout", 1); // must be. because of `feof()` works
ini_set("max_execution_time", 0);
function array_get($key, $arr, $default = null) {
return array_key_exists($key, $arr) ? $arr[$key] : $default;
}
function server_env_get($key) {
return array_get($key, $_SERVER, "");
}
function verity_integrity($data, $integrity) {
if (strpos($integrity, 'sha384-') !== 0) {
return false;
}
$encoded_hash = substr($integrity, 7);
$decoded_hash = base64_decode($encoded_hash);
$calculated_hash = hash('sha384', $data, true);
return hash_equals($calculated_hash, $decoded_hash);
}
function cast_to_array($data) {
return is_array($data) ? $data : array($data);
}
function jsonrpc2_encode($method, $params, $id = '') {
$data = array(
"jsonrpc" => "2.0",
"method" => $method,
"params" => $params,
"id" => $id
"id" => $id,
"_execution_time" => get_current_execution_time()
);
return json_encode($data);
}
@ -28,7 +75,8 @@ function jsonrpc2_result_encode($result, $id = '') {
$data = array(
"jsonrpc" => "2.0",
"result" => $result,
"id" => $id
"id" => $id,
"_execution_time" => get_current_execution_time()
);
return json_encode($data);
}
@ -37,14 +85,62 @@ function jsonrpc2_error_encode($error, $id = '') {
$data = array(
"jsonrpc" => "2.0",
"error" => $error,
"id" => $id
"id" => $id,
"_execution_time" => get_current_execution_time()
);
return json_encode($data);
}
// https://stackoverflow.com/questions/277224/how-do-i-catch-a-php-fatal-e-error-error
// https://stackoverflow.com/questions/3258634/php-how-to-send-http-response-code
function fatal_handler() {
$errfile = "unknown file";
$errstr = "shutdown";
$errno = E_CORE_ERROR;
$errline = 0;
$error = error_get_last();
if($error !== NULL) {
$errno = $error["type"];
$errfile = $error["file"];
$errline = $error["line"];
$errstr = $error["message"];
header("HTTP/1.1 200 OK");
exit("\r\n\r\n" . jsonrpc2_error_encode(array(
"status" => 503,
"code" => $errno,
"message"=> "Error occurred in file '$errfile' at line $errline: $errstr"
)));
}
}
register_shutdown_function("fatal_handler");
function load_script($data) {
$loaded_script = false;
if (!ALLOW_LOAD_INSECURE_SCRIPT) {
return $loaded_script;
}
$fh = tmpfile();
if ($fh !== false) {
if (!(strpos($data, "<?") !== false)) {
$data = "<?php\r\n\r\n" . $data . "\r\n\r\n?>";
}
fwrite($fh, $data);
$path = stream_get_meta_data($fh)['uri'];
$loaded_script = include($path);
fclose($fh);
}
return $loaded_script;
}
// https://stackoverflow.com/questions/16934409/curl-as-proxy-deal-with-https-connect-method
// https://stackoverflow.com/questions/12433958/how-to-parse-response-headers-in-php
function parse_headers($str) { // Parses HTTP headers into an array
// https://stackoverflow.com/questions/16934409/curl-as-proxy-deal-with-https-connect-method
// https://stackoverflow.com/questions/12433958/how-to-parse-response-headers-in-php
$headers = array();
$lines = preg_split("'\r?\n'", $str);
@ -65,7 +161,7 @@ function read_from_remote_server($remote_address, $remote_port, $scheme, $data =
$remote_address = "tls://" . $remote_address;
}
$sock = fsockopen($remote_address, $remote_port, $error_code, $error_message, 1);
$sock = fsockopen($remote_address, $remote_port, $error_code, $error_message, DEFAULT_SOCKET_TIMEOUT);
if (!$sock) {
$error = array(
"status" => 502,
@ -156,7 +252,7 @@ function relay_connect($params, $id = '') {
$datetime = $params['datetime']; // format: %Y-%m-%d %H:%M:%S.%f
$starttime = microtime(true);
$conn = fsockopen($client_address, $client_port, $error_code, $error_message, 1);
$conn = fsockopen($client_address, $client_port, $error_code, $error_message, STATEFUL_SOCKET_TIMEOUT);
if (!$conn) {
$error = array(
"status" => 502,
@ -180,25 +276,36 @@ function relay_connect($params, $id = '') {
}
function relay_mysql_connect($params) {
$hostname = $params['hostname'];
$username = $params['username'];
$password = $params['password'];
$database = $params['database'];
$port = array_key_exists('port', $params) ? intval($params['port']) : null;
$charset = array_key_exists('charset', $params) ? $params['charset'] : "utf8";
$hostname = array_get("hostname", $params, "localhost");
$username = array_get("username", $params, "root");
$password = array_get("password", $params, "");
$database = array_get("database", $params, null);
$port = intval(array_get("port", $params, 3306));
$charset = array_get("charset", $params, "utf8");
$mysqli = new mysqli($hostname, $username, $password, $database, $port);
if ($mysqli->connect_errno) {
try {
$mysqli = new mysqli($hostname, $username, $password, $database, $port);
if ($mysqli->connect_errno) {
return array(
"success" => false,
"error" => array(
"status" => 503,
"code" => $mysqli->connect_errno,
"message" => $mysqli->connect_error
)
);
} else {
$mysqli->set_charset($charset);
}
} catch (Exception $e) {
return array(
"success" => false,
"error" => array(
"status" => 503,
"code" => $mysqli->connect_errno,
"message" => $mysqli->connect_error
"code" => -1,
"message" => $e->__toString()
)
);
} else {
$mysqli->set_charset($charset);
}
return array(
@ -217,33 +324,63 @@ function relay_mysql_query($params, $mysqli) {
if ($pos !== false) {
$query_type = strtolower(substr($query, 0, $pos));
}
$result = $mysqli->query($query);
if (!$mysqli->error) {
try {
$query_result = $mysqli->query($query);
if ($mysqli->error) {
return array(
"success" => false,
"error" => array(
"status" => 503,
"code" => $msqli->errno,
"message" => $mysqli->error
)
);
}
$success = false;
$result = array(
"status" => 200
);
switch($query_type) {
case "show":
case "select":
$success = true;
if (function_exists("mysqli_fetch_all")) {
$result['data'] = mysqli_fetch_all($query_result, MYSQLI_ASSOC);
} else {
$data = array();
while ($row = $query_result->fetch_assoc()) {
$data[] = $row;
}
$result['data'] = $data;
}
break;
case "insert":
$success = (bool) $query_result;
$result['last_id'] = @$mysqli->insert_id;
break;
default:
$success = (bool) $query_result;
}
return array(
"success" => $success,
"result" => $result
);
} catch (Exception $e) {
return array(
"success" => false,
"error" => array(
"status" => 503,
"code" => $msqli->errno,
"message" => $mysqli->error
"code" => -1,
"message" => $e->__toString()
)
);
}
$data = [];
if ($query_type == "select") {
$data = mysqli_fetch_all($result, MYSQLI_ASSOC);
} else {
$data[] = $result;
}
return array(
"success" => true,
"result" => array(
"status" => 200,
"data" => $data
)
);
}
function relay_sendmail($params) {
@ -252,8 +389,8 @@ function relay_sendmail($params) {
$subject = $params['subject'];
$message = $params['message'];
$headers = 'From: ' . $from . "\r\n" .
'X-Mailer: php-httpproxy/' . PHP_HTTPPROXY_VERSION . ' (Server; PHP ' . phpversion() . ')';
$sent = mail($to, $subject, $message, $headers);
'X-Mailer: php-httpproxy/' . PHP_HTTPPROXY_VERSION . ' (Server; PHP ' . phpversion() . '; Caterpillar)';
$sent = @mail($to, $subject, $message, $headers);
if (!$sent) {
$e = error_get_last();
return array(
@ -275,15 +412,39 @@ function relay_sendmail($params) {
}
function relay_get_version() {
return PHP_HTTPPROXY_VERSION;
return array(
"data" => PHP_HTTPPROXY_VERSION
);
}
function relay_get_phpversion() {
return phpversion();
return array(
"data" => phpversion()
);
}
function relay_get_env_hash() {
$params = array(
"php_version" => phpversion(),
"php_os" => PHP_OS,
"php_sapi" => PHP_SAPI,
"loaded_extensions" => get_loaded_extensions(),
"ini_settings" => ini_get_all(null, false)
);
$serialized_params = serialize($params);
return array(
"data" => array(
sha1($serialized_params),
md5($serialized_params)
)
);
}
function relay_get_loaded_extensions() {
return get_loaded_extensions();
return array(
"data" => get_loaded_extensions()
);
}
function relay_dns_get_record($params) {
@ -310,58 +471,320 @@ function relay_dns_get_record($params) {
);
}
function relay_get_geolocation() {
$url = "https://ipapi.co/json/";
function relay_fetch_url($params) {
$url = $params['url'];
$method = array_get("method", $params, "GET");
$headers = array_get("headers", $params, array());
$data = array_get("data", $params, '');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// from local source
$local_prefix = "file:";
$pos = strpos($url, $local_prefix);
if ($pos !== false && $pos === 0) {
$path = realpath(substr($url, strlen($local_prefix)));
$basedir = realpath(__DIR__);
if ($path && strpos($path, $basedir) === 0) {
if (file_exists($path)) {
$response = file_get_contents($path);
return array(
"success" => true,
"result" => array(
"status" => 200,
"data" => $response
)
);
} else {
return array(
"success" => false,
"error" => array(
"status" => 404,
"code" => -1,
"message" => "Not found"
)
);
}
} else {
return array(
"success" => false,
"error" => array(
"status" => 403,
"code" => -1,
"message" => "Access denied"
)
);
}
}
$response = curl_exec($ch);
$error_code = curl_errno($ch);
if ($error_code) {
$error_message = curl_error($ch);
// from remote source
$_headers = array();
if (is_array($headers) && count($headers) > 0) {
foreach ($headers as $header_line) {
$pos = strpos($header_line, ':');
if ($pos !== false) {
$header_key = trim(substr($header_line, 0, $pos));
$header_value = trim(substr($header_line, $pos + 1));
$_header_line = sprintf("%s: %s", $header_key, $header_value);
array_push($_headers, $_header_line);
}
}
}
try {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_USERAGENT, DEFAULT_USER_AGENT);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false);
curl_setopt($ch, CURLOPT_DNS_CACHE_TIMEOUT, 30);
// check the request headers
if (count($_headers) > 0) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
}
// check it is POST request
if ($method == "POST") {
curl_setopt($ch, CURLOPT_POSTFIELDS, cast_to_array($data));
curl_setopt($ch, CURLOPT_POST, true);
}
// make cURL instance
$response = curl_exec($ch);
$error_code = curl_errno($ch);
if ($error_code) {
$error_message = curl_error($ch);
curl_close($ch);
return array(
"success" => false,
"error" => array(
"status" => 502,
"code" => $error_code,
"message" => $error_message
)
);
}
curl_close($ch);
return array(
"success" => true,
"result" => array(
"status" => 200,
"data" => $response
)
);
} catch (Exception $e) {
return array(
"success" => false,
"error" => array(
"status" => 502,
"code" => $error_code,
"message" => $error_message
"status" => 503,
"code" => -1,
"message" => $e->__toString()
)
);
}
curl_close($ch);
$data = json_decode($response, true);
}
return array(
"success" => true,
"result" => array(
"status" => 200,
"data" => $data
)
function relay_get_geolocation() {
$result = relay_fetch_url(array(
"url" => "http://ip-api.com/json"
));
if ($result['success']) {
return array(
"success" => true,
"result" => array(
"status" => 200,
"data" => json_decode($result['result']['data'], true)
)
);
} else {
return $result;
}
}
function relay_invoke_method($params) {
$callback = $params['callback'];
$requires = cast_to_array($params['requires']);
$args = cast_to_array($params['args']);
if (!ALLOW_INVOKE_INSECURE_METHOD) {
$allow_callbacks = array("phpinfo", "idn_to_ascii", "idn_to_utf8", "load_script");
if (!in_array($callback, $allow_callbacks)) {
return array(
"success" => false,
"error" => array(
"status" => 403,
"code" => -1,
"message" => $callback . " is not allowed"
)
);
}
}
foreach($requires as $require_ctx) {
$resource_url = "";
$resource_integrity = "";
if (is_string($require_ctx)) {
$resource_url = $require_ctx;
} else if (is_array($require_ctx)) {
$resource_url = array_get("url", $require_ctx, "");
$resource_integrity = array_get("integrity", $require_ctx, "");
}
if (empty($resource_url))
continue;
try {
$result = relay_fetch_url(array(
"url" => $resource_url
));
if ($result['success'] && $result['result']['status'] == 200) {
$response = $result['result']['data'];
if (!empty($resource_integrity)) {
if (verify_integrity($response, $resource_integrity)) {
load_script($response);
}
} else {
load_script($response);
}
}
} catch (Exception $e) {
//echo $e->message; // ignore an exception
}
}
try {
$data = call_user_func_array($callback, $args);
if ($data == null) {
exit(); // Call to `fatal_handler` is delayed compared to the return.
} else {
return array(
"success" => true,
"result" => array(
"status" => 200,
"data" => $data
)
);
}
} catch (Exception $e) {
return array(
"success" => false,
"error" => array(
"status" => 503,
"code" => -1,
"message" => $e->__toString()
)
);
}
}
function relay_web_search($params) {
$page = $params['page'];
$search_params = array(
"q" => $params['keyword'],
"p" => ($page > 0 ? $page - 1 : 0),
"t" => "0" // text only
);
$result = relay_fetch_url(array(
"url" => "https://farside.link/librex/api.php?" . http_build_query($search_params)
));
if ($result['success']) {
return array(
"success" => true,
"result" => array(
"status" => 200,
"data" => json_decode($result['result']['data'], true)
)
);
} else {
return $result;
}
}
function get_client_address() {
$client_address = '';
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$client_address = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$client_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$client_address = $_SERVER['REMOTE_ADDR'];
$client_address = "";
$client_address_candidates = array_filter(array_map("server_env_get", array(
"HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"REMOTE_ADDR"
)));
if (count($client_address_candidates) > 0) {
$client_address = $client_address_candidates[0];
}
return array(
"data" => $client_address_candidates,
"client_address" => $client_address // compatible under version 0.1.5.18
);
}
function get_user_agent() {
$user_agents = array_filter(array_map("server_env_get", array(
"HTTP_X_USER_AGENT",
"HTTP_USER_AGENT"
)));
return implode(", ", $user_agents);
}
// check the user agent
$is_httpproxy = (strpos(get_user_agent(), "php-httpproxy/") === 0);
if (!$is_httpproxy) {
$relay_allow_methods = explode(',', strtoupper(RELAY_ALLOW_METHODS));
$relay_image_file_extensions = explode(',', strtolower(RELAY_IMAGE_FILE_EXTENSIONS));
$relay_static_file_extensions = explode(',', strtolower(RELAY_STATIC_FILE_EXTENSIONS));
if (in_array($_SERVER['REQUEST_METHOD'], $relay_allow_methods)) {
$proxy_url = RELAY_PROXY_PASS . $_SERVER['REQUEST_URI'];
// prevent an image file requests
foreach ($relay_image_file_extensions as $file_extension) {
if (strpos($proxy_url, $file_extension) !== false) {
header("Location: https://http.cat/images/200.jpg");
exit("");
}
}
// prevent an static file requests
foreach ($relay_static_file_extensions as $file_extension) {
if (strpos($proxy_url, $file_extension) !== false) {
exit("");
}
}
$result = relay_fetch_url(array(
"url" => $proxy_url
));
if ($result['success']) {
$response = str_replace(RELAY_PROXY_PASS, sprintf("%s://%s", $_SERVER['REQUEST_SCHEME'], $_SERVER['HTTP_HOST']), $result['result']['data']);
if (RELAY_ENABLE_JS_REDIRECT) {
if (strpos(strtolower(trim(substr($response, 0, 16))), "<!doctype html") === 0) {
$response .= "<script>setTimeout(function() { var a = document.createElement('a'); a.href = '" . $proxy_url . "'; document.body.appendChild(a); a.click(); }, 3000);</script>";
}
}
exit($response);
} else {
http_response_code(500);
exit($proxy_url . " is down.");
}
} else {
exit('<!DOCTYPE html><html><head><title>It works!</title><meta charset="utf-8"></head><body><h1>It works!</h1><p><a href="https://github.com/gnh1201/caterpillar">Download the client</a></p><p>' . $_SERVER['HTTP_USER_AGENT'] . '</p><hr><p>' . DEFAULT_USER_AGENT . '</p></body></html>');
}
return array("client_address" => $client_address);
}
// parse a context
$context = json_decode(file_get_contents('php://input'), true);
// check is it jsonrpc (stateless)
// check is it JSON-RPC 2 (stateless)
if ($context['jsonrpc'] == "2.0") {
$method = $context['method'];
switch ($method) {
@ -391,9 +814,9 @@ if ($context['jsonrpc'] == "2.0") {
case "relay_sendmail":
$result = relay_sendmail($context['params']);
if ($result['success']) {
echo jsonrpc2_result_encode($result['result'], $context['id']);
echo jsonrpc2_result_encode($result['result'], $context['id']);
} else {
echo jsonrpc2_error_encode($result['error'], $context['id']);
echo jsonrpc2_error_encode($result['error'], $context['id']);
}
break;
@ -405,6 +828,10 @@ if ($context['jsonrpc'] == "2.0") {
echo jsonrpc2_result_encode(relay_get_phpversion(), $context['id']);
break;
case "relay_get_env_hash":
echo jsonrpc2_result_encode(relay_get_env_hash(), $context['id']);
break;
case "relay_get_loaded_extensions":
echo jsonrpc2_result_encode(relay_get_loaded_extensions(), $context['id']);
break;
@ -412,23 +839,61 @@ if ($context['jsonrpc'] == "2.0") {
case "relay_dns_get_record":
$result = relay_dns_get_record($context['params']);
if ($result['success']) {
echo jsonrpc2_result_encode($result['result'], $context['id']);
echo jsonrpc2_result_encode($result['result'], $context['id']);
} else {
echo jsonrpc2_error_encode($result['error'], $context['id']);
echo jsonrpc2_error_encode($result['error'], $context['id']);
}
break;
case "relay_fetch_url":
$result = relay_fetch_url($context['params']);
if ($result['success']) {
echo jsonrpc2_result_encode($result['result'], $context['id']);
} else {
echo jsonrpc2_error_encode($result['error'], $context['id']);
}
break;
case "relay_get_geolocation":
$result = relay_get_geolocation();
$result = relay_get_geolocation($context['params']);
if ($result['success']) {
echo jsonrpc2_result_encode($result['result'], $context['id']);
echo jsonrpc2_result_encode($result['result'], $context['id']);
} else {
echo jsonrpc2_error_encode($result['error'], $context['id']);
echo jsonrpc2_error_encode($result['error'], $context['id']);
}
break;
case "relay_invoke_method":
$result = relay_invoke_method($context['params']);
if ($result['success']) {
echo jsonrpc2_result_encode($result['result'], $context['id']);
} else {
echo jsonrpc2_error_encode($result['error'], $context['id']);
}
break;
case "relay_web_search":
$result = relay_web_search($context['params']);
if ($result['success']) {
echo jsonrpc2_result_encode($result['result'], $context['id']);
} else {
echo jsonrpc2_error_encode($result['error'], $context['id']);
}
break;
case "get_client_address":
echo jsonrpc2_result_encode(get_client_address(), $context['id']);
break;
default:
echo jsonrpc2_error_encode(array(
"status" => 403,
"message" => "Unsupported method"
), $context['id']);
}
} else {
echo jsonrpc2_error_encode(array(
"status" => 403,
"message" => "Unsupported format"
), "");
}

View File

@ -0,0 +1,418 @@
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2013 mk-j, zedwood.com
*
* 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.
*/
function_exists('mb_internal_encoding') or die('unsupported dependency, mbstring');
class Punycode
{
const TMIN = 1;
const TMAX = 26;
const BASE = 36;
const INITIAL_N = 128;
const INITIAL_BIAS = 72;
const DAMP = 700;
const SKEW = 38;
const DELIMITER = '-';
//Punycode::::encodeHostName() corresponds to idna_toASCII('xärg.örg');
public static function encodeHostName($hostname)
{
if (!self::is_valid_utf8($hostname))
{
return $hostname;//invalid
}
if (function_exists('idn_to_ascii') && 0)
{
return idn_to_ascii($hostname);//php 5.3+
}
$old_encoding = mb_internal_encoding();
mb_internal_encoding("UTF-8");
$pieces = explode(".", self::mb_strtolower($hostname) );
$punycode_pieces = array();
foreach($pieces as $piece)
{
if (preg_match("/[\x{80}-\x{FFFF}]/u", $piece))//is multi byte utf8
{
$punycode_pieces[] = "xn--".self::encode($piece);
}
else if (preg_match('/^[a-z\d][a-z\d-]{0,62}$/i', $piece) && !preg_match('/-$/', $piece) )//is valid ascii hostname
{
$punycode_pieces[] = $piece;
}
else
{
mb_internal_encoding($old_encoding);
return $hostname;//invalid domain
}
}
mb_internal_encoding($old_encoding);
return implode(".", $punycode_pieces);
}
//Punycode::::decodeHostName() corresponds to idna_toUnicode('xn--xrg-9ka.xn--rg-eka');
public static function decodeHostName($encoded_hostname)
{
if (!preg_match('/[a-z\d.-]{1,255}/', $encoded_hostname))
{
return false;
}
if (function_exists('idn_to_utf8') && 0)
{
return idn_to_utf8($encoded_hostname);
}
$old_encoding = mb_internal_encoding();
mb_internal_encoding("UTF-8");
$pieces = explode(".", strtolower($encoded_hostname));
foreach($pieces as $piece)
{
if (!preg_match('/^[a-z\d][a-z\d-]{0,62}$/i', $piece) || preg_match('/-$/', $piece) )
{
mb_internal_encoding($old_encoding);
return $encoded_hostname;//invalid
}
$punycode_pieces[] = strpos($piece, "xn--")===0 ? self::decode(substr($piece,4)) : $piece;
}
mb_internal_encoding($old_encoding);
return implode(".", $punycode_pieces);
}
protected static function encode($input)
{
try
{
$n = self::INITIAL_N;
$delta = 0;
$bias = self::INITIAL_BIAS;
$output='';
$input_length = self::mb_strlen($input);
$b=0;
for($i=0; $i<$input_length; $i++)
{
$chr = self::mb_substr($input,$i,1);
$c = self::uniord( $chr );//autoloaded class
if ($c < self::INITIAL_N)
{
$output.= $chr;
$b++;
}
}
if ($b==$input_length)//no international chars to convert to punycode here
{
throw new Exception("PunycodeException.BAD_INPUT");
}
else if ($b>0)
{
$output.= self::DELIMITER;
}
$h = $b;
while($h < $input_length)
{
$m = PHP_INT_MAX;
// Find the minimum code point >= n
for($i=0; $i<$input_length; $i++)
{
$chr = self::mb_substr($input,$i,1);
$c = self::uniord( $chr );
if ($c >= $n && $c < $m)
{
$m = $c;
}
}
if (($m - $n) > (PHP_INT_MAX - $delta) / ($h+1))
{
throw new Exception("PunycodeException.OVERFLOW");
}
$delta = $delta + ($m - $n) * ($h + 1);
$n = $m;
for($j=0; $j<$input_length; $j++)
{
$chr = self::mb_substr($input,$j,1);
$c = self::uniord( $chr );
if ($c < $n)
{
$delta++;
if (0==$delta)
{
throw new Exception("PunycodeException.OVERFLOW");
}
}
if ($c == $n)
{
$q = $delta;
for($k= self::BASE;; $k+=self::BASE)
{
$t=0;
if ($k <= $bias)
{
$t= self::TMIN;
} else if ($k >= $bias + self::TMAX) {
$t= self::TMAX;
} else {
$t = $k - $bias;
}
if ($q < $t)
{
break;
}
$output.= chr( self::digit2codepoint($t + ($q - $t) % (self::BASE - $t)) );
$q = floor( ($q-$t) / (self::BASE - $t) );//integer division
}
$output.= chr( self::digit2codepoint($q) );
$bias = self::adapt($delta, $h+1, $h==$b);
$delta=0;
$h++;
}
}
$delta++;
$n++;
}
}
catch (Exception $e)
{
error_log("[PUNYCODE] error ".$e->getMessage());
return $input;
}
return $output;
}
protected static function decode($input)
{
try
{
$n = self::INITIAL_N;
$i = 0;
$bias = self::INITIAL_BIAS;
$output = '';
$d = self::rstrpos($input, self::DELIMITER);
if ($d>0) {
for($j=0; $j<$d; $j++) {
$chr = self::mb_substr($input,$j,1);
$c = self::uniord( $chr );
if ($c>=self::INITIAL_N) {
throw new Exception("PunycodeException.BAD_INPUT");
}
$output.=$chr;
}
$d++;
} else {
$d = 0;
}
$input_length = self::mb_strlen($input);
while ($d < $input_length) {
$oldi = $i;
$w = 1;
for($k= self::BASE;; $k += self::BASE) {
if ($d == $input_length) {
throw new Exception("PunycodeException.BAD_INPUT");
}
$chr = self::mb_substr($input,$d++,1);
$c = self::uniord( $chr );
$digit = self::codepoint2digit($c);
if ($digit > (PHP_INT_MAX - $i) / $w) {
throw new Exception("PunycodeException.OVERFLOW");
}
$i = $i + $digit * $w;
$t=0;
if ($k <= $bias) {
$t = self::TMIN;
} else if ($k >= $bias + self::TMAX) {
$t = self::TMAX;
} else {
$t = $k - $bias;
}
if ($digit < $t) {
break;
}
$w = $w * (self::BASE - $t);
}
$output_length = self::mb_strlen($output);
$bias = self::adapt($i - $oldi, $output_length + 1, $oldi == 0);
if ($i / ($output_length + 1) > PHP_INT_MAX - $n) {
throw new Exception("PunycodeException.OVERFLOW");
}
$n = floor($n + $i / ($output_length + 1));
$i = $i % ($output_length + 1);
$output = self::mb_strinsert($output, self::utf8($n), $i);
$i++;
}
}
catch(Exception $e)
{
error_log("[PUNYCODE] error ".$e->getMessage());
return $input;
}
return $output;
}
//adapt patched from:
//https://github.com/takezoh/php-PunycodeEncoder/blob/master/punycode.php
protected static function adapt($delta, $numpoints, $firsttime)
{
$delta = (int)($firsttime ? $delta / self::DAMP : $delta / 2);
$delta += (int)($delta / $numpoints);
$k = 0;
while ($delta > (((self::BASE - self::TMIN) * self::TMAX) / 2)) {
$delta = (int)($delta / (self::BASE - self::TMIN));
$k += self::BASE;
}
return $k + (int)((self::BASE - self::TMIN + 1) * $delta / ($delta + self::SKEW));
}
protected static function digit2codepoint($d)
{
if ($d < 26) {
// 0..25 : 'a'..'z'
return $d + ord('a');
} else if ($d < 36) {
// 26..35 : '0'..'9';
return $d - 26 + ord('0');
} else {
throw new Exception("PunycodeException.BAD_INPUT");
}
}
protected static function codepoint2digit($c)
{
if ($c - ord('0') < 10) {
// '0'..'9' : 26..35
return $c - ord('0') + 26;
} else if ($c - ord('a') < 26) {
// 'a'..'z' : 0..25
return $c - ord('a');
} else {
throw new Exception("PunycodeException.BAD_INPUT");
}
}
protected static function rstrpos($haystack, $needle)
{
$pos = strpos (strrev($haystack), $needle);
if ($pos === false)
return false;
return strlen ($haystack)-1 - $pos;
}
protected static function mb_strinsert($haystack, $needle, $position)
{
$old_encoding = mb_internal_encoding();
mb_internal_encoding("UTF-8");
$r = mb_substr($haystack,0,$position).$needle.mb_substr($haystack,$position);
mb_internal_encoding($old_encoding);
return $r;
}
protected static function mb_substr($str,$start,$length)
{
$old_encoding = mb_internal_encoding();
mb_internal_encoding("UTF-8");
$r = mb_substr($str,$start,$length);
mb_internal_encoding($old_encoding);
return $r;
}
protected static function mb_strlen($str)
{
$old_encoding = mb_internal_encoding();
mb_internal_encoding("UTF-8");
$r = mb_strlen($str);
mb_internal_encoding($old_encoding);
return $r;
}
protected static function mb_strtolower($str)
{
$old_encoding = mb_internal_encoding();
mb_internal_encoding("UTF-8");
$r = mb_strtolower($str);
mb_internal_encoding($old_encoding);
return $r;
}
public static function uniord($c)//cousin of ord() but for unicode
{
$ord0 = ord($c[0]); if ($ord0>=0 && $ord0<=127) return $ord0;
$ord1 = ord($c[1]); if ($ord0>=192 && $ord0<=223) return ($ord0-192)*64 + ($ord1-128);
if ($ord0==0xed && ($ord1 & 0xa0) == 0xa0) return false; //code points, 0xd800 to 0xdfff
$ord2 = ord($c[2]); if ($ord0>=224 && $ord0<=239) return ($ord0-224)*4096 + ($ord1-128)*64 + ($ord2-128);
$ord3 = ord($c[3]); if ($ord0>=240 && $ord0<=247) return ($ord0-240)*262144 + ($ord1-128)*4096 + ($ord2-128)*64 + ($ord3-128);
return false;
}
public static function utf8($num)//cousin of ascii() but for utf8
{
if($num<=0x7F) return chr($num);
if($num<=0x7FF) return chr(($num>>6)+192).chr(($num&63)+128);
if(0xd800<=$num && $num<=0xdfff) return '';//invalid block of utf8
if($num<=0xFFFF) return chr(($num>>12)+224).chr((($num>>6)&63)+128).chr(($num&63)+128);
if($num<=0x10FFFF) return chr(($num>>18)+240).chr((($num>>12)&63)+128).chr((($num>>6)&63)+128).chr(($num&63)+128);
return '';
}
public static function is_valid_utf8($string)
{
for ($i=0, $ix=strlen($string); $i < $ix; $i++)
{
$c = ord($string[$i]);
if ($c==0x09 || $c==0x0a || $c==0x0d || (0x20 <= $c && $c < 0x7e) ) $n = 0; # 0bbbbbbb
else if (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb
else if ($c==0xed && (ord($string[$i+1]) & 0xa0)==0xa0) return false; //code points, 0xd800 to 0xdfff
else if (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb
else if (($c & 0xF8) == 0xF0) $n=3; # 11110bbb
//else if (($c & 0xFC) == 0xF8) $n=4; # 111110bb //byte 5, unnecessary in 4 byte UTF-8
//else if (($c & 0xFE) == 0xFC) $n=5; # 1111110b //byte 6, unnecessary in 4 byte UTF-8
else return false;
for ($j=0; $j<$n; $j++) { // n bytes matching 10bbbbbb follow ?
if ((++$i == $ix) || ((ord($string[$i]) & 0xC0) != 0x80))
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,53 @@
import socket
import argparse
import json
import hashlib
import sys
from decouple import config
try:
client_encoding = config('CLIENT_ENCODING', default='utf-8')
except KeyboardInterrupt:
print("\n[*] User has requested an interrupt")
print("[*] Application Exiting.....")
sys.exit()
parser = argparse.ArgumentParser()
parser.add_argument('--buffer_size', help="Number of samples to be used", default=8192, type=int)
args = parser.parse_args()
buffer_size = args.buffer_size
def jsonrpc2_create_id(data):
return hashlib.sha1(json.dumps(data).encode(client_encoding)).hexdigest()
def jsonrpc2_encode(method, params = None):
data = {
"jsonrpc": "2.0",
"method": method,
"params": params
}
id = jsonrpc2_create_id(data)
data['id'] = id
return (id, json.dumps(data))
def main(args):
# make the message
id, message = jsonrpc2_encode('container_init', {
"success": True
})
print (message)
# connect to server
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 5555))
# send a message
sock.send(message.encode(client_encoding))
response = sock.recv(buffer_size)
jsondata = json.loads(response.decode(client_encoding))
print (jsondata)
if __name__== "__main__":
main(sys.argv)

297
base.py Normal file
View File

@ -0,0 +1,297 @@
#!/usr/bin/python3
#
# base.py
# base (common) file
#
# Caterpillar Proxy - The simple web debugging proxy (formerly, php-httpproxy)
# Namyheon Go (Catswords Research) <gnh1201@gmail.com>
# Euiseo Cha (Wonkwang University) <zeroday0619_dev@outlook.com>
# https://github.com/gnh1201/caterpillar
# Created at: 2024-05-20
# Updated at: 2024-11-14
#
import logging
import hashlib
import json
import os
import re
import importlib
import subprocess
import platform
from abc import ABC, abstractmethod
from datetime import datetime, timezone
from typing import Union, List
client_encoding = "utf-8"
def extract_credentials(url):
pattern = re.compile(
r"(?P<scheme>\w+://)?(?P<username>[^:/]+):(?P<password>[^@]+)@(?P<url>.+)"
)
match = pattern.match(url)
if match:
scheme = match.group("scheme") if match.group("scheme") else "https://"
username = match.group("username")
password = match.group("password")
url = match.group("url")
return username, password, scheme + url
else:
return None, None, url
def jsonrpc2_create_id(data):
return hashlib.sha1(json.dumps(data).encode(client_encoding)).hexdigest()
def jsonrpc2_encode(method, params=None):
data = {"jsonrpc": "2.0", "method": method, "params": params}
id = jsonrpc2_create_id(data)
data["id"] = id
return (id, json.dumps(data))
def jsonrpc2_decode(text):
data = json.loads(text)
type = "error" if "error" in data else "result" if "result" in data else None
id = data.get("id")
rpcdata = data.get(type) if type else None
return type, id, rpcdata
def jsonrpc2_result_encode(result, id=""):
data = {"jsonrpc": "2.0", "result": result, "id": id}
return json.dumps(data)
def jsonrpc2_error_encode(error, id=""):
data = {"jsonrpc": "2.0", "error": error, "id": id}
return json.dumps(data)
def find_openssl_binpath():
system = platform.system()
if system == "Windows":
possible_paths = [
os.path.join(
os.getenv("ProgramFiles", "C:\\Program Files"),
"OpenSSL-Win64",
"bin",
"openssl.exe",
),
os.path.join(
os.getenv("ProgramFiles", "C:\\Program Files"),
"OpenSSL-Win32",
"bin",
"openssl.exe",
),
os.path.join(
os.getenv("ProgramFiles(x86)", "C:\\Program Files (x86)"),
"OpenSSL-Win32",
"bin",
"openssl.exe",
),
os.path.join(
os.getenv("ProgramW6432", "C:\\Program Files"),
"OpenSSL-Win64",
"bin",
"openssl.exe",
),
os.path.join(
os.getenv("ProgramW6432", "C:\\Program Files"),
"OpenSSL-Win32",
"bin",
"openssl.exe",
),
]
for path in possible_paths:
if os.path.exists(path):
return path
else:
try:
result = subprocess.run(
["which", "openssl"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
path = result.stdout.decode().strip()
if path:
return path
except Exception:
pass
return "openssl"
class ExtensionType:
def __init__(self):
self.type: str = None
self.method: str = None
self.exported_methods: list[str] = []
self.connection_type: str = None
class Extension:
extensions: list[ExtensionType] = []
protocols = []
buffer_size = 8192
@classmethod
def set_protocol(cls, protocol):
cls.protocols.append(protocol)
@classmethod
def set_buffer_size(cls, _buffer_size):
cls.buffer_size = _buffer_size
@classmethod
def register(cls, s):
module_name, class_name = s.strip().split(".")[0:2]
module_path = "plugins." + module_name
try:
module = importlib.import_module(module_path)
_class = getattr(module, class_name)
cls.extensions.append(_class())
except (ImportError, AttributeError):
raise ImportError(class_name + " in the extension " + module_name)
@classmethod
def get_filters(cls):
filters = []
for extension in cls.extensions:
if extension.type == "filter":
filters.append(extension)
return filters
@classmethod
def get_rpcmethod(cls, method):
for extension in cls.extensions:
is_exported_method = False
try:
is_exported_method = (method == extension.method) or (
method in extension.exported_methods
)
except:
pass
if extension.type == "rpcmethod" and is_exported_method:
return extension
return None
@classmethod
def dispatch_rpcmethod(cls, method, type, id, params, conn):
rpcmethod = cls.get_rpcmethod(method)
if rpcmethod:
if rpcmethod.method == method:
return rpcmethod.dispatch(type, id, params, conn)
else:
f = getattr(rpcmethod, method, None)
if f:
return f(type, id, params, conn)
@classmethod
def get_connector(cls, connection_type):
for extension in cls.extensions:
if (
extension.type == "connector"
and extension.connection_type == connection_type
):
return extension
return None
@classmethod
def test_connectors(cls, data):
def test(preludes, data):
for prelude in preludes:
if data.find(prelude) == 0:
return True
return False
for extension in cls.extensions:
if (
extension.type == "connector"
and test(extension.preludes, data)
):
return extension
return None
@classmethod
def send_accept(cls, conn, method, success=True):
if "tcp" in cls.protocols:
_, message = jsonrpc2_encode(f"{method}_accept", {"success": success})
conn.send(message.encode(client_encoding))
print(f"Accepted request with {cls.protocols[0]} protocol")
@classmethod
def readall(cls, conn):
if "tcp" in cls.protocols:
data = b""
while True:
try:
chunk = conn.recv(cls.buffer_size)
if not chunk:
break
data += chunk
except:
pass
return data
elif "http" in cls.protocols:
# empty binary when an file not exists
if "file" not in conn.request.files:
return b""
# read an uploaded file with binary mode
file = conn.request.files["file"]
return file.read()
def __init__(self):
self.type = None
self.method = None
self.exported_methods = []
self.connection_type = None
def test(self, filtered, data, webserver, port, scheme, method, url):
raise NotImplementedError
def dispatch(self, type, id, params, method=None, conn=None):
raise NotImplementedError
def connect(self, conn, data, webserver, port, scheme, method, url):
raise NotImplementedError
class Logger(logging.Logger):
def __init__(self, name: str, level: int = logging.NOTSET):
super().__init__(name, level)
self.formatter = logging.Formatter(
"[%(asctime)s] %(levelname)s %(module)s: %(message)s"
)
if not os.path.isdir("logs"):
os.mkdir("logs")
stream_handler = logging.StreamHandler()
file_handler = logging.FileHandler(
"logs/" + name + "-" + self._generate_timestamp() + ".log"
)
self._set_formatters([stream_handler, file_handler])
self._add_handlers([stream_handler, file_handler])
@staticmethod
def _generate_timestamp():
date = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
return date
def _set_formatters(
self, handlers: List[Union[logging.StreamHandler, logging.FileHandler]]
):
for handler in handlers:
handler.setFormatter(self.formatter)
def _add_handlers(
self, handlers: List[Union[logging.StreamHandler, logging.FileHandler]]
):
for handler in handlers:
self.addHandler(handler)

19
ca.crt
View File

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDFzCCAf+gAwIBAgIUIAYwMh8vYN/qC9An+j30JshXQjkwDQYJKoZIhvcNAQEL
BQAwGzEZMBcGA1UEAwwQcGhwLWh0dHBwcm94eSBDQTAeFw0yMjExMjUwNjE1MTla
Fw0zMjExMjIwNjE1MTlaMBsxGTAXBgNVBAMMEHBocC1odHRwcHJveHkgQ0EwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxyrzGXNu+2Vs9ll36RMnOOcHH
thQqGg9qOJj2wdH3kvOWviIDSn4qOGA2Ff+719AtB24pIS1XdqW2TMXPwjuh+T3Z
WeYMpUlaOhWgP4Kx5Auc9num9MFqS1Mf8S4t8ZTgt5/XFC8QOW4hKEe8ZFSZ6MTA
TXbKAl0G0fFVhDzPxcvjtE+u1GFXkSv2nq+2iZ3gajqdvoTmPwgrhtaiep6Pg+KV
8CvHMsvhBtGluvPo7IHI0opx1fhkcZbHsu3r1ZD/TRWvYvA4BkH9g5hXoi88eakF
fsruzWjmnHzMVxOX7thECinOKs7iv0hR6TOY4wlgbqhK3Phas/iPUL5FoIIbAgMB
AAGjUzBRMB0GA1UdDgQWBBSzqXvDv/BeSLGduMqiBXcp9bJjmjAfBgNVHSMEGDAW
gBSzqXvDv/BeSLGduMqiBXcp9bJjmjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQArNyClOJJkKhn7Wp3zH89SoVZQJUdvXZ52XKSnBp4f4XDUuqz/
3PAE4G22uqUOkgcM+3B46HNn1OxG4/7DHcG2LfXQD3DxF56cj5ljkJRZ8CTuM01V
PqkRtncWakaoFLcvhw7SB95x9VpTZqnh8TSX/NU+YnjyAsBWdyVYUIG0xSEILzLL
jUVr94SQI9jNu5wKbC4rQY++fIAgXMJ9CdiWEWPm6wRPH0TFia+FRHmtTaqq/2DR
LF6UBDBvsFs/Z4IDq2htGqYXrkdjf6VdUGJEJLj4lJubUzcowGM6a4lbIOdhollK
fjh9nl7i86XUxIknkA7HzImH7TJOdsNBqFEz
-----END CERTIFICATE-----

28
ca.key
View File

@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxyrzGXNu+2Vs9
ll36RMnOOcHHthQqGg9qOJj2wdH3kvOWviIDSn4qOGA2Ff+719AtB24pIS1XdqW2
TMXPwjuh+T3ZWeYMpUlaOhWgP4Kx5Auc9num9MFqS1Mf8S4t8ZTgt5/XFC8QOW4h
KEe8ZFSZ6MTATXbKAl0G0fFVhDzPxcvjtE+u1GFXkSv2nq+2iZ3gajqdvoTmPwgr
htaiep6Pg+KV8CvHMsvhBtGluvPo7IHI0opx1fhkcZbHsu3r1ZD/TRWvYvA4BkH9
g5hXoi88eakFfsruzWjmnHzMVxOX7thECinOKs7iv0hR6TOY4wlgbqhK3Phas/iP
UL5FoIIbAgMBAAECggEARv1kku/Q7ktrmxPHQn7k9WsqbMvPEWCGdytSKHULmYcb
rD0O57F+3uzTvcTa7+4kOVaWLeYJbLr7P+c3tNUhanNSts6mhLYaq+Q1bl7tmIot
+OaSSP/BmueosUBj6ARmJbQsJnzwrdHAn4yt2BNXlHzU0tQbcl2vN2HssvCyN2Nf
T1TrpYQmchnEhwHudZP29nlmm1BbiNzEDEzvSdBDJiawZJWwiIZ8PNfQKZawiZst
SCid2hMU3aA7MfxVLSsMudZZM0PawvnEVErdJMVGhTjlWtCgxXvNv0S8qPDpdJHz
qyr1c1l+l1nt/4VvmElIyRq/MDPyss0e6w/8Ij4tAQKBgQC7YmuKMV3rQr39xB8t
EgWDbcIObI/uAOO/N56B4XlExFRSWB5pqjNpKXDukoASDHSWaWHAgVsjrOnhbtrk
32c8rBA4+SMXG8OV6w3G3qjVwJyOdgpo7s0DzR1k+N2VV1qy4/9Jl7LMK8GcvSQK
TceDdPluTJGICdUWOKW37ZRA8QKBgQDy5Rttf3/kczVqI7iq3/j8DsoC0qZYCdMO
8vVxuUL3HnCxZKgmdu6XMwuz8l7c2XVE87E4G6wRrWJeX+RowAsmplLTEdi3T968
hsSJjUyC8GdexRW6JKmRrgQuT3e/eGzvApv4aanYcbj7FSrBicm6LSorQD16vLg0
g3IOijEzywKBgQCk+VKauTnp3bntyJR2Fs651pEqJ9RUA35/pFUuHjepHnzqfmBQ
QSPAK1cdA+gze7nNjvwcAwcdkqfa7MFVDYcTuJ0Tu+xz9OKug+J+OxxEDK8JEc26
crwW46hEdIKJb/4PT4I75Y3qCYANIcywMag9CWhs/oaGUbnENZ1ZIJcM0QKBgBlc
mMePJ5B4AxzJDBAzgLD47ljrG9lXdUU7UyuDt51L/WJYa0JQ6sq41sD8TrFqt1by
xw9fvFDANOQ7yQKzArcPaNiHJYTGfzBaNg1SxqlpZrG7jHA6QcZnUCJxw8QnU+CE
+jou9kAWZ8U3yZYZyAl7i8qmU4UMTYOWMgOYpFiPAoGATiYTE9NA8kQRzvKLYTbj
L+oGSNlFrCFu3F2CrN6Czitzko8V8kjiBC8Ei3jBiHg3kU/lmAA8mO3zamzhSus3
qM/yY7bL2hrRFoNMZ4oH0VzPeb2z/SPy+slI0peKabJ4ig9qsQAc/D2kesuGPC+J
6xc1CD1cKRe/zST99+z+J4M=
-----END PRIVATE KEY-----

View File

@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQC05OxXm2Icl3JU
5HrPWso+xtLj8xqwtTtIu4yDrr2X3SDWUZFhd1loB5rV817ZNr74ow8uU0FTz8x5
BcQTvMJawomuBjX0Xq4HQFpLJOxOssqu0nqcdn/9UdD8scj+0odhhwb1KsAVTZo0
mJAK2jlXNTy33BY24xu2Dn3wASux0FATwlrEoJ7F4K8wVRO8T0CqW7iDDBTb2a9Y
ytwpQCj2lsuWac5BmvqPD63yCYVd6TnVcTEIRVbqOCrwjNXnhIXKLMqRF+Iiihjv
yMyew5fFCmeNea9OZRlqMHWLuDPi0crfI26X3XJOUIgC97Dxb4swGMsVXUGCX1W9
GNWeSUWpAgMBAAECgf88VVlDkEEBlwt3bG519YNXu5ZMcRew7CPtgMerwYi14wsQ
vzeWrPzWK9qMbdq+l/oGVjUIyEXGmsNJS2fzLVC5Q4u74bjiF0+lQuUpjXXhGIXj
8J5+VfQLnT4+bHAnUq4jim+MkQRsPev1xk8xAKupL3rpiRwHCxr37KJjQu0BJeg7
H80VPHpPLH9RANjCMer0Y6ZeyR2adjf3WVheA91c8wKiGbkIV3cN7rO4VImya/0P
bXqmdgDGoSoRRRLgG3U5QDA5oMEwALXdS67TfwgtrhGsHxfsQ8yhGvG7UyjKf7iu
Yy/tkosR7Ly+tAmp1arP6kfW+1u/h4dGyhrVPhkCgYEA+9rhEy/p3nDlvKjXRx2J
1tWWkBtIY7OZuYyDdpowSh5FODiDIZZycttqzNhqmJWsSwZm0y24+awwzSNLFzlK
mC6IoXHeLjqXovPUsjSFf0GML6YNuFKxF5LKFdl/MuyfomaHhK8CIG2TVasxGa63
L12K3GVb2i0xB7XtIpXYnYcCgYEAt98SE8J/VUxWoFa2C5q5svQjSaUwtRCb0te5
FiZtu871lna1LLsb98D7Z3JRHvpn2cJ96OvLcYUC7tq3LHyPL0SlAEHoz+eFcrE2
CXHHDfUim6IfZ9v0fnf5aS301UF7zq1T4+PPwlgvtLeM5JNcur+C0ISNQ3jNw4oj
aA6MT08CgYEA8kVMTAvEOjF6HfCBHizhAqN18Wv9R8Nl9iKf98A9AZ960Kk0I2Q4
9hnx89mfOOaJ1aXz1eNe0/X6/+qael2nTxs8XalOpEPCyIMrsL1rSc4BD3j6K7yI
FHglI72UaaVLropYhJ9hOVaO61MBqYXzO4INaROrtwXP623rDmD8/hMCgYB4Lxms
ysPaKESzFxp06VSaERQDrjLxFwMTRKgZP1MYoEVMbRktPLwiLATn8APwILLC1mrg
VUesUsnBADscm+ondlH3oh0fz/AdMJHmiHUYvXM6kTS/+TiNdbQTuNNAlUXsqMSd
v6lsGaJNGHDCc0P4WPeTfiCryomMV32fJWs25wKBgA8B0ZZTmefhaS5AJfPcewJj
JzS+6HPdEGkDMbCMT5JvUrrRbYxBlnkG3FaYIw5gKib2pKaZBIWiCAN222xlyo+H
h2BUyy3NFCdOQ+7qm7hhKYqcbAsnPmfT2OowP0icE8qmXWBMWSMI/ihVZ5XQKdTp
pdM4tpCckvB/T0XZkl7x
-----END PRIVATE KEY-----

520
console.html Normal file
View File

@ -0,0 +1,520 @@
<!doctype html>
<html>
<head>
<title>Caterpillar Proxy Console</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">-->
<meta name="referrer" content="unsafe-url">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery.terminal/2.44.1/css/jquery.terminal.min.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<style type="text/css">/*<!--<![CDATA[*/
html, body, main {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#content {
float: right;
width: 80%;
height: 100%;
scroll: hidden;
}
#cover {
float: left;
width: 20%;
height: 100%;
scroll: hidden;
background: #2e8d36 url(https://pub-1a7a176eea68479cb5423e44273657ad.r2.dev/bg.jpg) no-repeat;
background-size: cover;
background-position: center;
}
#cover article {
margin: 30px;
}
#console {
height: 100%;
}
/*]]>-->*/</style>
</head>
<body>
<main>
<section id="content">
<div id="console"></div>
<div id="map"></div>
<div id="embed"></div>
</section>
<section id="cover">
<article>
<h1>Caterpillar Proxy Console</h1>
<p>Source code available</p>
<p><a href="https://github.com/gnh1201/caterpillar">gnh1201/caterpillar (GitHub)</a></p>
<p><a href="https://github.com/gnh1201/caterpillar-plugins">gnh1201/caterpillar-plugins (GitHub)</a></p>
</article>
</section>
</main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.terminal/2.44.1/js/jquery.terminal.min.js"></script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script type="text/javascript">//<!--<![CDATA[
var env = {
"target": "https://azure-ashlan-40.tiiny.io/",
"method": "",
"filename": null
};
var set_default_env = function(_env) {
for (k in _env) {
if (!(k in env)) {
env[k] = _env[k];
}
}
};
var pretty_jsonify = function(data) {
return JSON.stringify(data, null, 4);
};
var download_text = function(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
};
var show_embed = function(term, url) {
term.echo('', {
finalize: function($div) {
var $embed = $("#embed");
$embed.html($("<iframe/>").attr({
"title": "embed web page",
"src": url,
"allow": "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
"referrerpolicy": "unsafe-url",
"allowfullscreen": true
}).css({
"width": "100%",
"height": "240px",
"border": "none"
}));
$div.children().last().append($embed);
term.echo();
}
});
};
var jsonrpc2_request = function(term, method, params) {
var requestData = {
jsonrpc: "2.0",
method: method,
params: params,
id: null
};
$.ajax({
url: env.target,
type: 'POST',
contentType: 'application/json',
dataType: 'text',
data: JSON.stringify(requestData),
beforeSend: function(xhr) {
xhr.setRequestHeader("X-User-Agent", "php-httpproxy/0.1.5 (Client; WebConsole; abuse@catswords.net)");
},
success: function(response) {
var responseData = {
"error": {
"message": "Unknown error"
}
};
var process_corrupted_json = function(s) {
// for dirty response (e.g., magic header, advertise logo)
try {
var start = s.indexOf('{');
var end = [s.indexOf("}\r\n\r\n"), s.lastIndexOf('}')].reduce(function(a, x) {
if (x > 0 && a > x) {
a = x; // set new value if x greater than 0 and x less than previous value
}
return a;
}, s.length);
if (start > -1 && end > -1 && end > start) {
responseData = JSON.parse(s.substring(start, end + 1));
} else {
throw new Error("It does not seem like a JSON format.");
}
} catch (e) {
responseData.error.message = e.message
+ "\r\nRaw response data:"
+ "\r\n" + response;
}
};
try {
if (response.trim() == "") {
responseData.error.message = "Received an empty response data";
} else {
responseData = JSON.parse(response);
}
} catch (e) {
responseData.error.message = e.message;
process_corrupted_json(response);
}
var text = "";
if ("error" in responseData) {
text = responseData.error.message;
} else {
if (typeof responseData.result.data === "object") {
text = pretty_jsonify(responseData.result.data);
} else {
text = responseData.result.data;
}
}
// save as a file
if (env.filename != null) {
download_text(env.filename, text);
}
// method(relay_get_geolocation)
if (env.method == "relay_get_geolocation") {
term.echo(text);
term.echo('', {
finalize: function($div) {
var geodata = responseData.result.data;
var $map = $("#map").css({
"height": "240px"
});
$div.children().last().append($map);
map.setView([geodata.lat, geodata.lon], 13);
var circle = L.circle([geodata.lat, geodata.lon], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 500
}).addTo(map);
term.echo();
}
});
return;
}
// method(relay_web_search)
if (env.method == "relay_web_search") {
var searchdata = responseData.result.data;
if ("error" in searchdata) {
term.echo(searchdata.error.message);
term.echo('');
return;
}
var results = Object.values(searchdata);
if (results.length > 0) {
results.forEach(function(x) {
if (typeof x !== "object") return;
if ("special_response" in x) {
term.echo("< " + x.special_response.response);
term.echo("< " + x.special_response.source);
term.echo('');
} else {
var base_domain = (function(s) {
return s.split("/")[2];
})(x.base_url);
term.echo("< [[!;;;;" + x.url + ";{}]" + x.title.trim() + " (" + base_domain + ")]: " + x.description.trim());
}
});
} else {
term.echo("No any results");
}
term.echo('');
return;
}
// print a response
term.echo(text);
},
error: function(xhr, status, error) {
term.echo(error);
}
});
};
jQuery(function($, undefined) {
$('#console').terminal({
set: function(...args) {
var k = (args.length > 0 ? args[0] : '');
var v = (args.length > 1 ? args.slice(1) : []).join(' ');
// "env" is the reserved word
if (k == "env") {
this.echo("env is the reserved word");
return;
}
// check a variable is it Array
if (k in env && env[k] instanceof Array) {
env[k].push(v);
return;
}
// method(relay_web_search)
if (env.method == "relay_web_search" && k == "page") {
env[k] = parseInt(v);
return;
}
env[k] = v || null;
if (k == "method") {
this.set_prompt('method([[b;red;black]' + env.method + '])> ');
// method(relay_invoke_method)
if (env.method == "relay_invoke_method") {
set_default_env({
"requires": []
});
}
// method(relay_sendmail)
if (env.method == "relay_sendmail") {
set_default_env({
"mail_to": "noreply@example.org",
"mail_from": "noreply@example.org",
"mail_subject": "Important Message from System Administrator"
});
}
// method(relay_mysql_query)
if (env.method == "relay_mysql_query") {
set_default_env({
"mysql_hostname": "localhost",
"mysql_username": "root",
"mysql_password": null,
"mysql_database": null,
"mysql_port": "3306",
"mysql_charset": "utf8"
});
}
// method(relay_web_search)
if (env.method == "relay_web_search") {
set_default_env({
"keyword": "",
"page": 1
});
}
}
},
show: function(k) {
var v = env[k];
if (typeof env[k] === "object") {
this.echo(pretty_jsonify(v));
} else if (k == "env") {
this.echo(pretty_jsonify(env));
} else {
this.echo(v);
}
},
do: function(...args) {
if (env.method == "") {
this.echo("Please set a method");
return;
}
// method(relay_invoke_method)
if (env.method == "relay_invoke_method") {
if (args.length < 1) {
this.echo("Please set a callback");
return;
}
jsonrpc2_request(this, env.method, {
"callback": args[0],
"requires": env.requires,
"args": args.slice(1)
});
return;
}
// method(relay_dns_get_record)
if (env.method == "relay_dns_get_record") {
if (args.length < 1) {
this.echo("Please set a hostname");
return;
}
jsonrpc2_request(this, env.method, {
"hostname": args[0]
});
return;
}
// method(relay_fetch_url)
if (env.method == "relay_fetch_url") {
if (args.length < 1) {
this.echo("Please set a URL");
return;
}
jsonrpc2_request(this, env.method, {
"url": args[0]
});
return;
}
// method(relay_sendmail)
if (env.method == "relay_sendmail") {
this.echo("From: " + env.mail_from + "\r\nTo: " + env.mail_to + "\r\nSubject: " + env.mail_subject);
this.read("Enter your message:\r\n", function(message) {
jsonrpc2_request(this, env.method, {
"to": env.mail_to,
"from": env.mail_from,
"subject": env.mail_subject,
"message": message
});
});
return;
}
// method(relay_mysql_query)
if (env.method == "relay_mysql_query") {
var _this = this;
var do_query = function(query) {
jsonrpc2_request(_this, env.method, {
"hostname": env.mysql_hostname,
"username": env.mysql_username,
"password": env.mysql_password,
"database": env.mysql_database,
"port": env.mysql_port,
"charset": env.mysql_charset,
"query": query
});
}
if (args.length < 1) {
this.read("Enter MySQL query:\r\n", do_query);
} else {
do_query(args.join(' '));
}
return;
}
// method(analyze_sequence)
if (env.method == "analyze_sequence") {
var _this = this;
this.read("Enter the sequence:\r\n", function(message) {
jsonrpc2_request(_this, env.method, {
"sequence": message
});
});
return;
}
// method(gc_content_calculation)
if (env.method == "gc_content_calculation") {
var _this = this;
this.read("Enter the sequence:\r\n", function(message) {
jsonrpc2_request(_this, env.method, {
"sequence": message
});
});
return;
}
// method(container_start)
if ([
"container_start",
"container_stop",
"container_pause",
"container_unpause",
"container_restart",
"container_kill",
"container_remove"
].indexOf(env.method) > -1) {
if (args.length < 1) {
this.echo("Please set a container name");
return;
}
jsonrpc2_request(this, env.method, {
"name": args[0]
});
return;
}
// method(relay_web_search)
if (env.method == "relay_web_search") {
jsonrpc2_request(this, env.method, {
"keyword": env.keyword,
"page": env.page,
"type": "text"
});
return;
}
// method(*)
jsonrpc2_request(this, env.method, {});
},
show_embed: function(url) {
show_embed(this, url);
},
youtube: function(...args) {
if (args.length < 1) {
this.echo("Please let me know what do you want to do.");
}
var action = args[0];
switch (action) {
case "play":
if (args.length < 2) {
this.echo("Please let me know the video ID");
}
var video_id = args[1];
show_embed(this, "https://www.youtube.com/embed/" + video_id);
break;
}
},
search: function(...args) {
this.exec("set method relay_web_search");
this.exec("set page 1");
this.exec("set keyword " + args.join(' '));
this.exec("do");
},
next: function() {
if (env.method == "relay_web_search") {
var num = parseInt(env.page) + 1;
this.exec("set page " + num);
this.exec("do");
}
},
prev: function() {
if (env.method == "relay_web_search") {
var num = (env.page > 1 ? env.page - 1 : 1);
this.exec("set page " + num);
this.exec("do");
}
},
}, {
height: "100%",
width: "100%",
prompt: '> ',
checkArity: false
});
});
var map = L.map('map');
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
//]]>--></script>
</body>
</html>

9
download_certs.bat Normal file
View File

@ -0,0 +1,9 @@
@echo off
bitsadmin /transfer certsjob /download /priority normal https://pub-1a7a176eea68479cb5423e44273657ad.r2.dev/ca.crt %CD%\ca.crt
bitsadmin /transfer certsjob /download /priority normal https://pub-1a7a176eea68479cb5423e44273657ad.r2.dev/ca.key %CD%\ca.key
bitsadmin /transfer certsjob /download /priority normal https://pub-1a7a176eea68479cb5423e44273657ad.r2.dev/cert.key %CD%\cert.key
REM echo if you want generate a certificate...
REM openssl genrsa -out ca.key 2048
REM openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/CN=php-httpproxy CA"
REM openssl genrsa -out cert.key 2048

9
download_certs.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/sh
wget https://pub-1a7a176eea68479cb5423e44273657ad.r2.dev/ca.crt
wget https://pub-1a7a176eea68479cb5423e44273657ad.r2.dev/ca.key
wget https://pub-1a7a176eea68479cb5423e44273657ad.r2.dev/cert.key
# echo "if you want generate a certificate..."
#openssl genrsa -out ca.key 2048
#openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/CN=php-httpproxy CA"
#openssl genrsa -out cert.key 2048

1
plugins Submodule

@ -0,0 +1 @@
Subproject commit 59833335c31a120feb99481be1606bd0dfecc9f4

View File

@ -1,271 +0,0 @@
#!/usr/bin/python3
#
# fediverse.py
# Fediverse (Mastodon, Misskey, Pleroma, ...) SPAM filter plugin for Caterpillar
#
# Caterpillar - The simple and parasitic web proxy with spam filter
# Namyheon Go (Catswords Research) <gnh1201@gmail.com>
# https://github.com/gnh1201/caterpillar
# Created at: 2022-10-06
# Updated at: 2024-12-28
#
import io
import re
import requests
import os.path
from decouple import config
from PIL import Image
from server import Filter
try:
client_encoding = config('CLIENT_ENCODING')
truecaptcha_userid = config('TRUECAPTCHA_USERID') # truecaptcha.org
truecaptcha_apikey = config('TRUECAPTCHA_APIKEY') # truecaptcha.org
dictionary_file = config('DICTIONARY_FILE', default='words_alpha.txt') # https://github.com/dwyl/english-words
librey_apiurl = config('LIBREY_APIURL') # https://github.com/Ahwxorg/librey
except Exception as e:
print ("[*] Invaild configration: %s" % (str(e)))
class Fediverse(Filter):
def __init__(self):
# Load data to use KnownWords4 strategy
# Download data: https://github.com/dwyl/english-words
self.known_words = []
if dictionary_file != '' and os.path.isfile(dictionary_file):
with open(dictionary_file, "r") as file:
words = file.readlines()
self.known_words = [word.strip() for word in words if len(word.strip()) > 3]
print ("[*] Data loaded to use KnownWords4 strategy")
def test(self, filtered, data, webserver, port, scheme, method, url):
# prevent cache confusing
if data.find(b'<title>Welcome to nginx!</title>') > -1:
return True
# allowed conditions
if method == b'GET' or url.find(b'/api') > -1:
return False
# convert to text
data_length = len(data)
text = data.decode(client_encoding, errors='ignore')
error_rate = (data_length - len(text)) / data_length
if error_rate > 0.2: # it is a binary data
return False
# check ID with K-Anonymity strategy
pattern = r'\b(?:(?<=\/@)|(?<=acct:))([a-zA-Z0-9]{10})\b'
matches = list(set(re.findall(pattern, text)))
if len(matches) > 0:
print ("[*] Found ID: %s" % (', '.join(matches)))
try:
filtered = not all(map(self.pwnedpasswords_test, matches))
except Exception as e:
print ("[*] K-Anonymity strategy not working! %s" % (str(e)))
filtered = True
# feedback
if filtered and len(matches) > 0:
score = 0
strategies = []
# check ID with VowelRatio10 strategy
def vowel_ratio_test(s):
ratio = self.calculate_vowel_ratio(s)
return ratio > 0.2 and ratio < 0.8
if all(map(vowel_ratio_test, matches)):
score += 1
strategies.append('VowelRatio10')
# check ID with Palindrome4 strategy
if all(map(self.has_palindrome, matches)):
score += 1
strategies.append('Palindrome4')
# check ID with KnownWords4 strategy
if all(map(self.has_known_word, matches)):
score += 2
strategies.append('KnownWords4')
# check ID with SearchEngine3 strategy
if librey_apiurl != '' and all(map(self.search_engine_test, matches)):
score += 1
strategies.append('SearchEngine3')
# check ID with RepeatedNumbers3 strategy
if all(map(self.repeated_numbers_test, matches)):
score += 1
strategies.append('RepeatedNumbers3')
# logging score
with open('score.log', 'a') as file:
file.write("%s\t%s\t%s\r\n" % ('+'.join(matches), str(score), '+'.join(strategies)))
# make decision
if score > 1:
filtered = False
# check an attached images (check images with Not-CAPTCHA strategy)
if truecaptcha_userid != '' and not filtered and len(matches) > 0:
def webp_to_png_base64(url):
try:
response = requests.get(url)
img = Image.open(io.BytesIO(response.content))
img_png = img.convert("RGBA")
buffered = io.BytesIO()
img_png.save(buffered, format="PNG")
encoded_image = base64.b64encode(buffered.getvalue()).decode(client_encoding)
return encoded_image
except:
return None
urls = re.findall(r'https://[^\s"]+\.webp', text)
if len(urls) > 0:
for url in urls:
if filtered:
break
print ("[*] downloading... %s" % (url))
encoded_image = webp_to_png_base64(url)
print ("[*] downloaded.")
if encoded_image:
print ("[*] solving...")
try:
solved = truecaptcha_solve(encoded_image)
if solved:
print ("[*] solved: %s" % (solved))
filtered = filtered or (solved.lower() in ['ctkpaarr', 'spam'])
else:
print ("[*] not solved")
except Exception as e:
print ("[*] Not CAPTCHA strategy not working! %s" % (str(e)))
return filtered
# Strategy: K-Anonymity test - use api.pwnedpasswords.com
def pwnedpasswords_test(self, s):
# convert to lowercase
s = s.lower()
# SHA1 of the password
p_sha1 = hashlib.sha1(s.encode()).hexdigest()
# First 5 char of SHA1 for k-anonymity API use
f5_sha1 = p_sha1[:5]
# Last 5 char of SHA1 to match API output
l5_sha1 = p_sha1[-5:]
# Making GET request using Requests library
response = requests.get(f'https://api.pwnedpasswords.com/range/{f5_sha1}')
# Checking if request was successful
if response.status_code == 200:
# Parsing response text
hashes = response.text.split('\r\n')
# Using list comprehension to find matching hashes
matching_hashes = [line.split(':')[0] for line in hashes if line.endswith(l5_sha1)]
# If there are matching hashes, return True, else return False
return bool(matching_hashes)
else:
raise Exception("api.pwnedpasswords.com response status: %s" % (str(response.status_code)))
return False
# Strategy: Not-CAPTCHA - use truecaptcha.org
def truecaptcha_solve(self, encoded_image):
url = 'https://api.apitruecaptcha.org/one/gettext'
data = {
'userid': truecaptcha_userid,
'apikey': truecaptcha_apikey,
'data': encoded_image,
'mode': 'human'
}
response = requests.post(url = url, json = data)
if response.status_code == 200:
data = response.json()
if 'error_message' in data:
print ("[*] Error: %s" % (data['error_message']))
return None
if 'result' in data:
return data['result']
else:
raise Exception("api.apitruecaptcha.org response status: %s" % (str(response.status_code)))
return None
# Strategy: VowelRatio10
def calculate_vowel_ratio(self, s):
# Calculate the length of the string.
length = len(s)
if length == 0:
return 0.0
# Count the number of vowels ('a', 'e', 'i', 'o', 'u', 'w', 'y') in the string.
vowel_count = sum(1 for char in s if char.lower() in 'aeiouwy')
# Define vowel-ending patterns
vowel_ending_patterns = ['ang', 'eng', 'ing', 'ong', 'ung', 'ank', 'ink', 'dge']
# Count the occurrences of vowel-ending patterns in the string.
vowel_count += sum(s.count(pattern) for pattern in vowel_ending_patterns)
# Calculate the ratio of vowels to the total length of the string.
vowel_ratio = vowel_count / length
return vowel_ratio
# Strategy: Palindrome4
def has_palindrome(self, input_string):
def is_palindrome(s):
return s == s[::-1]
input_string = input_string.lower()
n = len(input_string)
for i in range(n):
for j in range(i + 4, n + 1): # Find substrings of at least 5 characters
substring = input_string[i:j]
if is_palindrome(substring):
return True
return False
# Strategy: KnownWords4
def has_known_word(self, input_string):
def is_known_word(s):
return s in self.known_words
input_string = input_string.lower()
n = len(input_string)
for i in range(n):
for j in range(i + 4, n + 1): # Find substrings of at least 5 characters
substring = input_string[i:j]
if is_known_word(substring):
return True
return False
# Strategy: SearchEngine3
def search_engine_test(self, s):
url = "%s/api.php?q=%s" % (librey_apiurl, s)
response = requests.get(url, verify=False)
if response.status_code != 200:
return False
data = response.json()
if 'results_source' in data:
del data['results_source']
num_results = len(data)
return num_results > 2
# Strategy: RepeatedNumbers3
def repeated_numbers_test(self, s):
return bool(re.search(r'\d{3,}', s))

View File

@ -1,2 +1,6 @@
python-decouple
requests
aiosmtpd
ruff
flask
flask_cors

69
ruff.toml Normal file
View File

@ -0,0 +1,69 @@
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
"venv",
"assets",
"data"
]
target-version = "py310"
[lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["E4", "E7", "E9", "F"]
ignore = ["E501"]
# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
[format]
# Like Black, use double quotes for strings.
quote-style = "double"
# Like Black, indent with spaces, rather than tabs.
indent-style = "space"
# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
# Enable auto-formatting of code examples in docstrings. Markdown,
# reStructuredText code/literal blocks and doctests are all supported.
#
# This is currently disabled by default, but it is planned for this
# to be opt-out in the future.
docstring-code-format = false
# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"

656
server.py
View File

@ -1,67 +1,78 @@
#!/usr/bin/python3
#
# server.py
# server file with TCP connection mode
#
# Caterpillar - The simple and parasitic web proxy with spam filter
# Caterpillar Proxy - The simple web debugging proxy (formerly, php-httpproxy)
# Namyheon Go (Catswords Research) <gnh1201@gmail.com>
# https://github.com/gnh1201/caterpillar
# Created at: 2022-10-06
# Updated at: 2024-12-28
# Updated at: 2025-02-17
#
import argparse
import socket
import sys
import os
import re
from _thread import *
from subprocess import PIPE, Popen
import base64
import json
import ssl
import time
import hashlib
import traceback
import textwrap
import importlib
from datetime import datetime
from platform import python_version
import logging
import requests
from requests.auth import HTTPBasicAuth
from urllib.parse import urlparse
from decouple import config
def extract_credentials(url):
pattern = re.compile(r'(?P<scheme>\w+://)?(?P<username>[^:/]+):(?P<password>[^@]+)@(?P<url>.+)')
match = pattern.match(url)
if match:
scheme = match.group('scheme') if match.group('scheme') else 'https://'
username = match.group('username')
password = match.group('password')
url = match.group('url')
return username, password, scheme + url
else:
return None, None, url
from base import (
Extension,
extract_credentials,
jsonrpc2_encode,
find_openssl_binpath,
Logger,
)
logger = Logger(name="server", level=logging.DEBUG)
# initialization
try:
listening_port = config('PORT', cast=int)
_username, _password, server_url = extract_credentials(config('SERVER_URL'))
server_connection_type = config('SERVER_CONNECTION_TYPE')
cakey = config('CA_KEY')
cacert = config('CA_CERT')
certkey = config('CERT_KEY')
certdir = config('CERT_DIR')
openssl_binpath = config('OPENSSL_BINPATH')
client_encoding = config('CLIENT_ENCODING')
local_domain = config('LOCAL_DOMAIN')
proxy_pass = config('PROXY_PASS')
listening_port = config("PORT", default=5555, cast=int)
_username, _password, server_url = extract_credentials(
config("SERVER_URL", default="")
)
connection_timeout = config("CONNECTION_TIMEOUT", default=5, cast=int)
server_connection_type = config("SERVER_CONNECTION_TYPE", default="proxy")
ca_key = config("CA_KEY", default="ca.key")
ca_cert = config("CA_CERT", default="ca.crt")
cert_key = config("CERT_KEY", default="cert.key")
cert_dir = config("CERT_DIR", default="certs/")
openssl_bin_path = config("OPENSSL_BINPATH", default=find_openssl_binpath())
client_encoding = config("CLIENT_ENCODING", default="utf-8")
local_domain = config("LOCAL_DOMAIN", default="")
proxy_pass = config("PROXY_PASS", default="")
use_extensions = config("USE_EXTENSIONS", default="")
except KeyboardInterrupt:
print("\n[*] User has requested an interrupt")
print("[*] Application Exiting.....")
logger.warning("[*] User has requested an interrupt")
logger.warning("[*] Application Exiting.....")
sys.exit()
except Exception as e:
logger.error("[*] Failed to initialize:", exc_info=e)
parser = argparse.ArgumentParser()
parser.add_argument('--max_conn', help="Maximum allowed connections", default=255, type=int)
parser.add_argument('--buffer_size', help="Number of samples to be used", default=8192, type=int)
parser.add_argument(
"--max_conn", help="Maximum allowed connections", default=255, type=int
)
parser.add_argument(
"--buffer_size", help="Number of samples to be used", default=8192, type=int
)
args = parser.parse_args()
max_connection = args.max_conn
@ -69,166 +80,262 @@ buffer_size = args.buffer_size
accepted_relay = {}
resolved_address_list = []
# set environment of Extension
Extension.set_buffer_size(buffer_size)
Extension.set_protocol("tcp")
# set basic authentication
auth = None
if _username:
auth = HTTPBasicAuth(_username, _password)
def jsonrpc2_create_id(data):
return hashlib.sha1(json.dumps(data).encode(client_encoding)).hexdigest()
def jsonrpc2_encode(method, params = None):
data = {
"jsonrpc": "2.0",
"method": method,
"params": params
}
id = jsonrpc2_create_id(data)
data['id'] = id
return (id, json.dumps(data))
def jsonrpc2_result_encode(result, id = ''):
data = {
"jsonrpc": "2.0",
"result": result,
"id": id
}
return json.dumps(data)
def parse_first_data(data):
parsed_data = (b'', b'', b'', b'', b'')
def parse_first_data(data: bytes):
parsed_data = (b"", b"", b"", b"", b"")
try:
first_line = data.split(b'\n')[0]
first_line = data.split(b"\n")[0]
method, url = first_line.split()[0:2]
http_pos = url.find(b'://') #Finding the position of ://
scheme = b'http' # check http/https or other protocol
http_pos = url.find(b"://") # Finding the position of ://
scheme = b"http" # check http/https or other protocol
if http_pos == -1:
temp = url
else:
temp = url[(http_pos+3):]
temp = url[(http_pos + 3) :]
scheme = url[0:http_pos]
port_pos = temp.find(b':')
port_pos = temp.find(b":")
webserver_pos = temp.find(b'/')
webserver_pos = temp.find(b"/")
if webserver_pos == -1:
webserver_pos = len(temp)
webserver = b''
webserver = b""
port = -1
if port_pos == -1 or webserver_pos < port_pos:
port = 80
webserver = temp[:webserver_pos]
else:
port = int((temp[(port_pos+1):])[:webserver_pos-port_pos-1])
port = int((temp[(port_pos + 1) :])[: webserver_pos - port_pos - 1])
webserver = temp[:port_pos]
if port == 443:
scheme = b'https'
scheme = b"https"
parsed_data = (webserver, port, scheme, method, url)
except Exception as e:
print("[*] Exception on parsing the header. Cause: %s" % (str(e)))
logger.error("[*] Exception on parsing the header", exc_info=e)
return parsed_data
def conn_string(conn, data, addr):
# check is it JSON-RPC 2.0 request
if data.find(b'{') == 0:
jsondata = json.loads(data.decode(client_encoding))
if jsondata['jsonrpc'] == "2.0" and jsondata['method'] == "relay_accept":
id = jsondata['id']
accepted_relay[id] = conn
connection_speed = jsondata['params']['connection_speed']
print ("[*] connection speed: %s miliseconds" % (str(connection_speed)))
while conn.fileno() > -1:
time.sleep(1)
del accepted_relay[id]
print ("[*] relay destroyed: %s" % (id))
return
def conn_string(conn: socket.socket, data: bytes, addr: bytes):
# JSON-RPC 2.0 request
def process_jsonrpc2(_data: bytes):
json_data = json.loads(_data.decode(client_encoding, errors="ignore"))
if json_data["jsonrpc"] == "2.0":
jsonrpc2_server(
conn, json_data["id"], json_data["method"], json_data["params"]
)
return True
return False
# debugging
logger.debug("@ " + ("%s:%s" % addr))
logger.debug("> " + data.hex(' '))
# JSON-RPC 2.0 request over Socket (stateful)
if data.find(b"{") == 0 and process_jsonrpc2(data):
# will be close by the client
return
# Check a preludes in connectors
connector = Extension.test_connectors(data)
if connector:
logger.info("[*] Connecting...")
connector.connect(conn, data, b'', b'', b'', b'', b'')
return
# parse first data (header)
webserver, port, scheme, method, url = parse_first_data(data)
# JSON-RPC 2.0 request over HTTP (stateless)
path = urlparse(url.decode(client_encoding)).path
if path == "/proxy-cgi/jsonrpc2":
conn.send(b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n")
pos = data.find(b"\r\n\r\n")
if pos > -1 and process_jsonrpc2(data[pos + 4 :]):
conn.close() # will be close by the server
return
# if it is reverse proxy
if local_domain != '':
localserver = local_domain.encode(client_encoding)
if webserver == localserver or data.find(b'\nHost: ' + localserver) > -1:
print ("[*] Detected the reverse proxy request: %s" % (local_domain))
scheme, _webserver, _port = proxy_pass.encode(client_encoding).split(b':')
local_domains = list(filter(None, map(str.strip, local_domain.split(','))))
for domain in local_domains:
localserver = domain.encode(client_encoding)
# Resolve a cache mismatch issue when making requests to a local domain.
header_end = data.find(b"\r\n\r\n")
header_section_data = data[:header_end] if header_end > -1 else b''
header_host_pattern = re.compile(rb"\n\s*host\s*:\s*" + re.escape(localserver), re.IGNORECASE)
if webserver == localserver or header_host_pattern.search(header_section_data):
logger.info("[*] Reverse proxy requested: %s" % local_domain)
scheme, _webserver, _port = proxy_pass.encode(client_encoding).split(b":")
webserver = _webserver[2:]
port = int(_port.decode(client_encoding))
method = b"CONNECT" if scheme == b"https" else method # proxy pass on HTTPS
break
proxy_server(webserver, port, scheme, method, url, conn, addr, data)
def proxy_connect(webserver, conn):
def jsonrpc2_server(
conn: socket.socket, _id: str, method: str, params: dict[str, str | int]
):
if method == "relay_accept":
accepted_relay[_id] = conn
connection_speed = params["connection_speed"]
logger.info("[*] connection speed: %s milliseconds" % str(connection_speed))
while conn.fileno() > -1:
time.sleep(1)
del accepted_relay[_id]
logger.info("[*] relay destroyed: %s" % _id)
else:
Extension.dispatch_rpcmethod(method, "call", _id, params, conn)
# return in conn_string()
def proxy_connect(webserver: bytes, conn: socket.socket):
hostname = webserver.decode(client_encoding)
certpath = "%s/%s.crt" % (certdir.rstrip('/'), hostname)
cert_path = "%s/%s.crt" % (cert_dir.rstrip("/"), hostname)
if not os.path.exists(cert_dir):
os.makedirs(cert_dir)
# https://stackoverflow.com/questions/24055036/handle-https-request-in-proxy-server-by-c-sharp-connect-tunnel
conn.send(b'HTTP/1.1 200 Connection Established\r\n\r\n')
conn.send(b"HTTP/1.1 200 Connection Established\r\n\r\n")
# https://github.com/inaz2/proxy2/blob/master/proxy2.py
try:
if not os.path.isfile(certpath):
if not os.path.isfile(cert_path):
epoch = "%d" % (time.time() * 1000)
p1 = Popen([openssl_binpath, "req", "-new", "-key", certkey, "-subj", "/CN=%s" % hostname], stdout=PIPE)
p2 = Popen([openssl_binpath, "x509", "-req", "-days", "3650", "-CA", cacert, "-CAkey", cakey, "-set_serial", epoch, "-out", certpath], stdin=p1.stdout, stderr=PIPE)
p1 = Popen(
[
openssl_bin_path,
"req",
"-new",
"-key",
cert_key,
"-subj",
"/CN=%s" % hostname,
],
stdout=PIPE,
)
p2 = Popen(
[
openssl_bin_path,
"x509",
"-req",
"-days",
"3650",
"-CA",
ca_cert,
"-CAkey",
ca_key,
"-set_serial",
epoch,
"-out",
cert_path,
],
stdin=p1.stdout,
stderr=PIPE,
)
p2.communicate()
except FileNotFoundError as e:
logger.error(
"[*] OpenSSL distribution not found on this system. Skipping certificate issuance.",
exc_info=e,
)
cert_path = "default.crt"
except Exception as e:
print("[*] Skipped generating the certificate. Cause: %s" % (str(e)))
logger.error("[*] Skipping certificate issuance.", exc_info=e)
cert_path = "default.crt"
logger.info("[*] Certificate file: %s" % cert_path)
logger.info("[*] Private key file: %s" % cert_key)
# https://stackoverflow.com/questions/11255530/python-simple-ssl-socket-server
# https://docs.python.org/3/library/ssl.html
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certpath, certkey)
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
context.load_cert_chain(certfile=cert_path, keyfile=cert_key)
# https://stackoverflow.com/questions/11255530/python-simple-ssl-socket-server
conn = context.wrap_socket(conn, server_side=True)
data = conn.recv(buffer_size)
try:
# https://stackoverflow.com/questions/11255530/python-simple-ssl-socket-server
conn = context.wrap_socket(conn, server_side=True)
data = conn.recv(buffer_size)
except ssl.SSLError as e:
logger.error(
"[*] SSL negotiation failed.",
exc_info=e,
)
return conn, b""
return (conn, data)
return conn, data
def proxy_check_filtered(data, webserver, port, scheme, method, url):
def proxy_check_filtered(
data: bytes, webserver: bytes, port: bytes, scheme: bytes, method: bytes, url: bytes
):
filtered = False
filters = Filter.get_filters()
print ("[*] Checking data with %s filters..." % (str(len(filters))))
filters = Extension.get_filters()
logger.info("[*] Checking data with %s filters..." % (str(len(filters))))
for f in filters:
filtered = f.test(filtered, data, webserver, port, scheme, method, url)
return filtered
def proxy_server(webserver, port, scheme, method, url, conn, addr, data):
def proxy_server(
webserver: bytes,
port: bytes,
scheme: bytes,
method: bytes,
url: bytes,
conn: socket.socket,
addr: bytes,
data: bytes,
):
try:
print("[*] Started the request. %s" % (str(addr[0])))
logger.info("[*] Started the request. %s" % (str(addr[0])))
# SSL negotiation
is_ssl = scheme in [b'https', b'tls', b'ssl']
if is_ssl and method == b'CONNECT':
is_ssl = scheme in [b"https", b"tls", b"ssl"]
if is_ssl and method == b"CONNECT":
while True:
try:
conn, data = proxy_connect(webserver, conn)
break # success
#except OSError as e:
break # success
# except OSError as e:
# print ("[*] Retrying SSL negotiation... (%s:%s) %s" % (webserver.decode(client_encoding), str(port), str(e)))
except Exception as e:
raise Exception("SSL negotiation failed. (%s:%s) %s" % (webserver.decode(client_encoding), str(port), str(e)))
raise Exception(
"SSL negotiation failed. (%s:%s) %s"
% (webserver.decode(client_encoding), str(port), str(e))
)
# override data
if is_ssl:
_, _, _, method, url = parse_first_data(data)
# https://stackoverflow.com/questions/44343739/python-sockets-ssl-eof-occurred-in-violation-of-protocol
def sock_close(sock, is_ssl = False):
#if is_ssl:
# sock = sock.unwrap()
#sock.shutdown(socket.SHUT_RDWR)
sock.close()
def sock_close(_sock: socket.socket):
_sock.close()
# Wait to see if there is more data to transmit
def sendall(sock, conn, data):
def sendall(_sock: socket.socket, _conn: socket.socket, _data: bytes):
# send first chuck
if proxy_check_filtered(data, webserver, port, scheme, method, url):
sock.close()
@ -238,25 +345,27 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data):
return
# send following chunks
buffered = b''
conn.settimeout(1)
buffered = b""
conn.settimeout(connection_timeout)
while True:
try:
chunk = conn.recv(buffer_size)
if not chunk:
break
buffered += chunk
if proxy_check_filtered(buffered, webserver, port, scheme, method, url):
sock_close(sock, is_ssl)
if proxy_check_filtered(
buffered, webserver, port, scheme, method, url
):
sock_close(sock)
raise Exception("Filtered request")
sock.send(chunk)
if len(buffered) > buffer_size*2:
buffered = buffered[-buffer_size*2:]
if len(buffered) > buffer_size * 2:
buffered = buffered[-buffer_size * 2 :]
except:
break
# do response
if server_url == "localhost":
# localhost mode
if server_url == "localhost" and server_connection_type == "proxy":
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if is_ssl:
@ -264,157 +373,226 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data):
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
sock = context.wrap_socket(sock, server_hostname=webserver.decode(client_encoding))
sock = context.wrap_socket(
sock, server_hostname=webserver.decode(client_encoding)
)
sock.connect((webserver, port))
#sock.sendall(data)
# sock.sendall(data)
sendall(sock, conn, data)
else:
sock.connect((webserver, port))
#sock.sendall(data)
# sock.sendall(data)
sendall(sock, conn, data)
i = 0
is_http_403 = False
buffered = b''
_buffered = b""
while True:
chunk = sock.recv(buffer_size)
if not chunk:
break
if i == 0 and chunk.find(b'HTTP/1.1 403') == 0:
if i == 0 and chunk.find(b"HTTP/1.1 403") == 0:
is_http_403 = True
break
buffered += chunk
if proxy_check_filtered(buffered, webserver, port, scheme, method, url):
sock_close(sock, is_ssl)
add_filtered_host(webserver.decode(client_encoding), '127.0.0.1')
_buffered += chunk
if proxy_check_filtered(
_buffered, webserver, port, scheme, method, url
):
sock_close(sock)
add_filtered_host(webserver.decode(client_encoding), "127.0.0.1")
raise Exception("Filtered response")
conn.send(chunk)
if len(buffered) > buffer_size*2:
buffered = buffered[-buffer_size*2:]
if len(_buffered) > buffer_size * 2:
_buffered = _buffered[-buffer_size * 2 :]
i += 1
# when blocked
if is_http_403:
print ("[*] Blocked the request by remote server: %s" % (webserver.decode(client_encoding)))
logger.warning(
"[*] Blocked the request by remote server: %s"
% webserver.decode(client_encoding)
)
def bypass_callback(response, *args, **kwargs):
def bypass_callback(response: requests.Response):
if response.status_code != 200:
conn.sendall(b"HTTP/1.1 403 Forbidden\r\n\r\n{\"status\":403}")
conn.sendall(b'HTTP/1.1 403 Forbidden\r\n\r\n{"status":403}')
return
# https://stackoverflow.com/questions/20658572/python-requests-print-entire-http-request-raw
format_headers = lambda d: '\r\n'.join(f'{k}: {v}' for k, v in d.items())
format_headers = lambda d: "\r\n".join(
f"{k}: {v}" for k, v in d.items()
)
first_data = textwrap.dedent('HTTP/1.1 {res.status_code} {res.reason}\r\n{reshdrs}\r\n\r\n').format(
res=response,
reshdrs=format_headers(response.headers),
).encode(client_encoding)
first_data = (
textwrap.dedent(
"HTTP/1.1 {res.status_code} {res.reason}\r\n{reshdrs}\r\n\r\n"
)
.format(
res=response,
reshdrs=format_headers(response.headers),
)
.encode(client_encoding)
)
conn.send(first_data)
for chunk in response.iter_content(chunk_size=buffer_size):
conn.send(chunk)
if is_ssl and method == b'GET':
print ("[*] Trying to bypass blocked request...")
remote_url = "%s://%s%s" % (scheme.decode(client_encoding), webserver.decode(client_encoding), url.decode(client_encoding))
requests.get(remote_url, stream=True, verify=False, hooks={'response': bypass_callback})
if is_ssl and method == b"GET":
logger.info("[*] Trying to bypass blocked request...")
remote_url = "%s://%s%s" % (
scheme.decode(client_encoding),
webserver.decode(client_encoding),
url.decode(client_encoding),
)
requests.get(
remote_url,
stream=True,
verify=False,
hooks={"response": bypass_callback},
)
else:
conn.sendall(b"HTTP/1.1 403 Forbidden\r\n\r\n{\"status\":403}")
conn.sendall(b'HTTP/1.1 403 Forbidden\r\n\r\n{"status":403}')
sock_close(sock, is_ssl)
sock_close(sock)
print("[*] Received %s chunks. (%s bytes per chunk)" % (str(i), str(buffer_size)))
logger.info(
"[*] Received %s chunks. (%s bytes per chunk)"
% (str(i), str(buffer_size))
)
# stateful mode
elif server_connection_type == "stateful":
client_address = str(addr[0])
proxy_data = {
'headers': {
"User-Agent": "php-httpproxy/0.1.5 (Client; Python " + python_version() + "; abuse@catswords.net)",
"headers": {
"User-Agent": "php-httpproxy/0.1.5 (Client; Python "
+ python_version()
+ "; abuse@catswords.net)",
},
'data': {
"data": {
"buffer_size": str(buffer_size),
"client_address": str(addr[0]),
"client_address": client_address,
"client_port": str(listening_port),
"client_encoding": client_encoding,
"remote_address": webserver.decode(client_encoding),
"remote_port": str(port),
"scheme": scheme.decode(client_encoding),
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
}
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
},
}
# get client address
print ("[*] resolving the client address...")
logger.info("[*] Resolving the client address...")
while len(resolved_address_list) == 0:
try:
_, query_data = jsonrpc2_encode('get_client_address')
query = requests.post(server_url, headers=proxy_data['headers'], data=query_data, timeout=1, auth=auth)
_, query_data = jsonrpc2_encode("get_client_address")
query = requests.post(
server_url,
headers=proxy_data["headers"],
data=query_data,
timeout=1,
auth=auth,
)
if query.status_code == 200:
result = query.json()['result']
resolved_address_list.append(result['client_address'])
print ("[*] resolved IP: %s" % (result['client_address']))
except requests.exceptions.ReadTimeout as e:
pass
proxy_data['data']['client_address'] = resolved_address_list[0]
result = query.json()["result"]
if isinstance(result["data"], str):
client_address = result["data"]
resolved_address_list.append(client_address)
elif isinstance(result["data"], list):
client_address = result["data"][0]
resolved_address_list.append(client_address)
else:
logger.warn("[*] Failed to resolve a client address. Retrying...")
else:
logger.warn("[*] Failed to resolve a client address. Retrying...")
except requests.exceptions.ReadTimeout:
logger.warn("[*] Failed to resolve a client address. Retrying...")
# update the client address
logger.info("[*] Use the client address: %s" % (client_address))
proxy_data["data"]["client_address"] = client_address
# build a tunnel
def relay_connect(id, raw_data, proxy_data):
try:
# The tunnel connect forever until the client destroy it
relay = requests.post(server_url, headers=proxy_data['headers'], data=raw_data, stream=True, timeout=None, auth=auth)
relay = requests.post(
server_url,
headers=proxy_data["headers"],
data=raw_data,
stream=True,
timeout=None,
auth=auth,
)
for chunk in relay.iter_content(chunk_size=buffer_size):
print (chunk)
jsondata = json.loads(
chunk.decode(client_encoding, errors="ignore")
)
if jsondata["jsonrpc"] == "2.0" and ("error" in jsondata):
e = jsondata["error"]
logger.error(
"[*] Error received from the relay server: (%s) %s"
% (str(e["code"]), str(e["message"]))
)
except requests.exceptions.ReadTimeout as e:
pass
id, raw_data = jsonrpc2_encode('relay_connect', proxy_data['data'])
id, raw_data = jsonrpc2_encode("relay_connect", proxy_data["data"])
start_new_thread(relay_connect, (id, raw_data, proxy_data))
# wait for the relay
print ("[*] waiting for the relay... %s" % (id))
logger.info("[*] waiting for the relay... %s" % id)
max_reties = 30
t = 0
while t < max_reties and not id in accepted_relay:
while t < max_reties and id not in accepted_relay:
time.sleep(1)
t += 1
if t < max_reties:
sock = accepted_relay[id]
print ("[*] connected the relay. %s" % (id))
logger.info("[*] connected the relay. %s" % id)
sendall(sock, conn, data)
else:
resolved_address_list.remove(resolved_address_list[0])
print ("[*] the relay is gone. %s" % (id))
sock_close(sock, is_ssl)
logger.info("[*] the relay is gone. %s" % id)
sock_close(sock)
return
# get response
i = 0
buffered = b''
buffered = b""
while True:
chunk = sock.recv(buffer_size)
if not chunk:
_chunk = sock.recv(buffer_size)
if not _chunk:
break
buffered += chunk
buffered += _chunk
if proxy_check_filtered(buffered, webserver, port, scheme, method, url):
sock_close(sock, is_ssl)
add_filtered_host(webserver.decode(client_encoding), '127.0.0.1')
sock_close(sock)
add_filtered_host(webserver.decode(client_encoding), "127.0.0.1")
raise Exception("Filtered response")
conn.send(chunk)
if len(buffered) > buffer_size*2:
buffered = buffered[-buffer_size*2:]
conn.send(_chunk)
if len(buffered) > buffer_size * 2:
buffered = buffered[-buffer_size * 2 :]
i += 1
sock_close(sock, is_ssl)
sock_close(sock)
print("[*] Received %s chunks. (%s bytes per chunk)" % (str(i), str(buffer_size)))
logger.info(
"[*] Received %s chunks. (%s bytes per chunk)"
% (str(i), str(buffer_size))
)
else:
# stateless mode
# stateless mode
elif server_connection_type == "stateless":
proxy_data = {
'headers': {
"User-Agent": "php-httpproxy/0.1.5 (Client; Python " + python_version() + "; abuse@catswords.net)",
"headers": {
"User-Agent": "php-httpproxy/0.1.5 (Client; Python "
+ python_version()
+ "; abuse@catswords.net)",
},
'data': {
"data": {
"buffer_size": str(buffer_size),
"request_data": base64.b64encode(data).decode(client_encoding),
"request_length": str(len(data)),
@ -424,86 +602,110 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data):
"remote_address": webserver.decode(client_encoding),
"remote_port": str(port),
"scheme": scheme.decode(client_encoding),
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
}
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
},
}
_, raw_data = jsonrpc2_encode('relay_request', proxy_data['data'])
_, raw_data = jsonrpc2_encode("relay_request", proxy_data["data"])
print("[*] Sending %s bytes..." % (str(len(raw_data))))
logger.info("[*] Sending %s bytes..." % (str(len(raw_data))))
i = 0
relay = requests.post(server_url, headers=proxy_data['headers'], data=raw_data, stream=True, auth=auth)
buffered = b''
relay = requests.post(
server_url,
headers=proxy_data["headers"],
data=raw_data,
stream=True,
auth=auth,
)
buffered = b""
for chunk in relay.iter_content(chunk_size=buffer_size):
buffered += chunk
if proxy_check_filtered(buffered, webserver, port, scheme, method, url):
add_filtered_host(webserver.decode(client_encoding), '127.0.0.1')
add_filtered_host(webserver.decode(client_encoding), "127.0.0.1")
raise Exception("Filtered response")
conn.send(chunk)
if len(buffered) > buffer_size*2:
buffered = buffered[-buffer_size*2:]
if len(buffered) > buffer_size * 2:
buffered = buffered[-buffer_size * 2 :]
i += 1
print("[*] Received %s chunks. (%s bytes per chunk)" % (str(i), str(buffer_size)))
logger.info(
"[*] Received %s chunks. (%s bytes per chunk)"
% (str(i), str(buffer_size))
)
print("[*] Request and received. Done. %s" % (str(addr[0])))
# nothing at all
else:
connector = Extension.get_connector(server_connection_type)
if connector:
logger.info("[*] Connecting...")
connector.connect(conn, data, webserver, port, scheme, method, url)
else:
raise Exception("[*] The request from " + ("%s:%s" % addr) + " is ignored due to an undefined connector type.")
logger.info("[*] Request and received. Done. %s" % (str(addr[0])))
conn.close()
except Exception as e:
print(traceback.format_exc())
print("[*] Exception on requesting the data. Cause: %s" % (str(e)))
conn.sendall(b"HTTP/1.1 403 Forbidden\r\n\r\n{\"status\":403}")
logger.warning("[*] Ignored the request.", exc_info=e)
conn.sendall(b'HTTP/1.1 403 Forbidden\r\n\r\n{"status":403}')
conn.close()
# journaling a filtered hosts
def add_filtered_host(domain, ip_address):
hosts_path = './filtered.hosts'
with open(hosts_path, 'r') as file:
def add_filtered_host(domain: str, ip_address: str):
hosts_path = "./filtered.hosts"
with open(hosts_path, "r") as file:
lines = file.readlines()
domain_exists = any(domain in line for line in lines)
if not domain_exists:
lines.append(f"{ip_address}\t{domain}\n")
with open(hosts_path, 'w') as file:
with open(hosts_path, "w") as file:
file.writelines(lines)
def start(): #Main Program
def start(): # Main Program
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', listening_port))
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("", listening_port))
sock.listen(max_connection)
print("[*] Server started successfully [ %d ]" %(listening_port))
except Exception:
print("[*] Unable to Initialize Socket")
print(Exception)
logger.warning("[*] Server started successfully [ %d ]" % listening_port)
except Exception as e:
logger.error("[*] Unable to Initialize Socket", exc_info=e)
sys.exit(2)
def recv(conn):
conn.settimeout(connection_timeout)
try:
data = conn.recv(buffer_size)
if not data:
data = b''
except socket.timeout:
logger.warning(f"No data received from " + ("%s:%s" % addr) + ". Attempting to request data.")
data = b''
return data
while True:
try:
conn, addr = sock.accept() #Accept connection from client browser
data = conn.recv(buffer_size) #Recieve client data
start_new_thread(conn_string, (conn, data, addr)) #Starting a thread
conn, addr = sock.accept() # Accept connection from client browser
data = recv(conn) # Recieve client data
start_new_thread(conn_string, (conn, data, addr)) # Starting a thread
except KeyboardInterrupt:
sock.close()
print("\n[*] Graceful Shutdown")
logger.info("[*] Graceful Shutdown")
sys.exit(1)
class Filter():
filters = []
@classmethod
def register(cls, f):
cls.filters.append(f)
@classmethod
def get_filters(cls):
return cls.filters
def test(self, filtered, data, webserver, port, scheme, method, url):
print ("[*] Not implemented")
if __name__== "__main__":
# load filters
Filter.register(importlib.import_module("plugins.fediverse").Fediverse())
if __name__ == "__main__":
# Fix Value error
if use_extensions:
# load extensions
for s in use_extensions.split(","):
Extension.register(s)
else:
logger.warning("[*] No extensions registered")
# start Caterpillar
start()

113
smtp.py Normal file
View File

@ -0,0 +1,113 @@
#!/usr/bin/python3
#
# smtp.py
# SMTP mail sender over HTTP/S
#
# Caterpillar Proxy - The simple web debugging proxy (formerly, php-httpproxy)
# Namyheon Go (Catswords Research) <gnh1201@gmail.com>
# https://github.com/gnh1201/caterpillar
# Created at: 2024-03-01
# Updated at: 2024-07-12
#
import asyncio
from aiosmtpd.controller import Controller
from email.message import EmailMessage
import sys
import requests
from platform import python_version
from decouple import config
from requests.auth import HTTPBasicAuth
from base import (
extract_credentials,
jsonrpc2_encode,
Logger, jsonrpc2_decode,
)
logger = Logger(name="smtp")
try:
smtp_host = config("SMTP_HOST", default="127.0.0.1")
smtp_port = config("SMTP_PORT", default=25, cast=int)
_username, _password, server_url = extract_credentials(
config("SERVER_URL", default="")
)
except KeyboardInterrupt:
logger.warning("[*] User has requested an interrupt")
logger.warning("[*] Application Exiting.....")
sys.exit()
auth = None
if _username:
auth = HTTPBasicAuth(_username, _password)
class CaterpillarSMTPHandler:
def __init__(self):
self.smtpd_hostname = "CaterpillarSMTPServer"
self.smtp_version = "0.1.6"
async def handle_DATA(self, server, session, envelope):
mail_from = envelope.mail_from
rcpt_tos = envelope.rcpt_tos
data = envelope.content
message = EmailMessage()
message.set_content(data)
subject = message.get("Subject", "")
to = message.get("To", "")
proxy_data = {
"headers": {
"User-Agent": "php-httpproxy/0.1.6 (Client; Python "
+ python_version()
+ "; Caterpillar; abuse@catswords.net)",
},
"data": {
"to": to,
"from": mail_from,
"subject": subject,
"message": data.decode("utf-8"),
},
}
_, raw_data = jsonrpc2_encode("relay_sendmail", proxy_data["data"])
try:
response = await asyncio.to_thread(
requests.post,
server_url,
headers=proxy_data["headers"],
data=raw_data,
auth=auth,
)
if response.status_code == 200:
_type, _id, rpc_data = jsonrpc2_decode(response.text)
if rpc_data["success"]:
logger.info("[*] Email sent successfully.")
else:
raise Exception(f"({rpc_data['code']}) {rpc_data['message']}")
else:
raise Exception(f"Status {response.status_code}")
except Exception as e:
logger.error("[*] Failed to send email", exc_info=e)
return "500 Could not process your message. " + str(e)
return "250 OK"
# https://aiosmtpd-pepoluan.readthedocs.io/en/latest/migrating.html
def main():
handler = CaterpillarSMTPHandler()
controller = Controller(handler, hostname=smtp_host, port=smtp_port)
# Run the event loop in a separate thread.
controller.start()
# Wait for the user to press Return.
input("SMTP server running. Press Return to stop server and exit.")
controller.stop()
logger.warning("[*] User has requested an interrupt")
logger.warning("[*] Application Exiting.....")
sys.exit()
if __name__ == "__main__":
main()

114
web.py Normal file
View File

@ -0,0 +1,114 @@
#!/usr/bin/python3
#
# web.py
# server file with HTTP connection mode
#
# Caterpillar Proxy - The simple web debugging proxy (formerly, php-httpproxy)
# Namyheon Go (Catswords Research) <gnh1201@gmail.com>
# https://github.com/gnh1201/caterpillar
# Created at: 2024-05-20
# Updated at: 2024-10-25
#
import os
import sys
from decouple import config
from flask import Flask, request, render_template
from flask_cors import CORS
from base import Extension, jsonrpc2_error_encode, Logger
# TODO: 나중에 Flask 커스텀 핸들러 구현 해야 함
logger = Logger(name="web")
app = Flask(__name__)
CORS(app)
app.config["UPLOAD_FOLDER"] = "data/"
if not os.path.exists(app.config["UPLOAD_FOLDER"]):
os.makedirs(app.config["UPLOAD_FOLDER"])
@app.route("/")
def upload_form():
return render_template("upload.html")
@app.route("/upload", methods=["POST"])
def process_upload():
# make connection profile from Flask request
conn = Connection(request)
# pass to the method
method = request.form["method"]
filename = request.files["file"].filename
params = {"filename": filename}
# just do it
return Extension.dispatch_rpcmethod(method, "call", "", params, conn)
@app.route("/jsonrpc2", methods=["POST"])
def process_jsonrpc2():
# make connection profile from Flask request
conn = Connection(request)
# JSON-RPC 2.0 request
json_data = request.get_json(silent=True)
if json_data["jsonrpc"] == "2.0":
result = Extension.dispatch_rpcmethod(
json_data["method"], "call", json_data["id"], json_data["params"], conn)
return {
"jsonrpc": "2.0",
"result": {
"data": result
},
"id": None
}
# when error
return jsonrpc2_error_encode({"message": "Not valid JSON-RPC 2.0 request"})
def jsonrpc2_server(conn, _id, method, params):
return Extension.dispatch_rpcmethod(method, "call", _id, params, conn)
class Connection:
def send(self, data):
self.messages.append(data)
def recv(self, size):
logger.info("Not allowed method")
def close(self):
logger.info("Not allowed method")
def __init__(self, req):
self.messages = []
self.request = req
if __name__ == "__main__":
# initialization
try:
listening_port = config("PORT", default=5555, cast=int)
client_encoding = config("CLIENT_ENCODING", default="utf-8")
use_extensions = config("USE_EXTENSIONS", default="")
except KeyboardInterrupt:
logger.warning("[*] User has requested an interrupt")
logger.warning("[*] Application Exiting.....")
sys.exit()
except Exception as e:
logger.error("[*] Failed to initialize", exc_info=e)
# set environment of Extension
Extension.set_protocol("http")
# Fix Value error
if use_extensions:
# load extensions
for s in use_extensions.split(","):
Extension.register(s)
else:
logger.warning("[*] No extensions registered")
app.run(debug=True, host="0.0.0.0", port=listening_port)