Compare commits

..

No commits in common. "main" and "0.1.1" have entirely different histories.
main ... 0.1.1

40 changed files with 149 additions and 4265 deletions

View File

@ -1,13 +0,0 @@
[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

2
.github/FUNDING.yml vendored
View File

@ -1,2 +0,0 @@
github: gnh1201
custom: ['https://gnh1201.link']

View File

@ -1,23 +0,0 @@
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 }}

179
.gitignore vendored
View File

@ -1,179 +0,0 @@
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
View File

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

View File

@ -1,8 +0,0 @@
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

113
README.md
View File

@ -1,115 +1,32 @@
# Caterpillar Proxy (Songchoongi Project)
# php-httpproxy
HTTP proxy implementation with PHP socket
[![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
## How to works
```
* You <-> Proxy client (Python) <-> Parasitized proxy server (Optional, PHP/LAMP) <-> On the Web
* You <-> Proxy client (Python) <-> Connector extensions (Optional, Python) <-> On the Web
You <-----> HTTP proxy (Python) <-----> Web hosting (PHP) <-----> On the Web
```
For example, build a simple web debugging proxy on the shared servers.
### 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).
### 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 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.
HTTP proxy over the web hosting!
## How to use
1. Write a file `.env`(Linux) or `settings.ini`(Windows). Like this:
1. Write a file with filename like `.env`(Linux) or `settings.ini`(Windows). Like this:
```
[settings]
CONNECTION_TIMEOUT=1
PORT=5555
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
USE_EXTENSIONS=wayback.Wayback,bio.PyBio
PROXY_URL=http://example.org
```
***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, 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.
2. Run `python server.py` and set HTTP proxy in your web browser (e.g. Firefox)
3. Enjoy it
4. (Optional) With [Cloudflare](https://cloudflare.com), we can expect to accelerate the 4x speed and reduce the network stuck.
## Screenshot
![Screenshot, Transferring one megabyte per second](screenshot.png)
## 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)
## References
* https://github.com/anapeksha/python-proxy-server
## Thanks to
* Pan Art by [@yeohangdang@i.peacht.art](#): [Image File](assets/img/logo.png)
* [GitHub Sponsors](https://github.com/sponsors/gnh1201)
## Contributors
<a href="https://github.com/gnh1201/caterpillar/graphs/contributors">
<img src="https://contrib.rocks/image?repo=gnh1201/caterpillar" alt="Contributors" />
</a>
## 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)
## Contact
* gnh1201@gmail.com

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 KiB

View File

@ -1,9 +0,0 @@
#
# 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

View File

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

View File

@ -1,41 +0,0 @@
/*
* 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

@ -1,8 +0,0 @@
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

@ -1,197 +0,0 @@
// 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

@ -1,11 +0,0 @@
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

@ -1,7 +0,0 @@
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
View File

@ -1,249 +0,0 @@
#!/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" "$@"

View File

@ -1,92 +0,0 @@
@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

@ -1,11 +0,0 @@
/*
* 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')

View File

@ -1,61 +0,0 @@
<?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

@ -1,70 +0,0 @@
<?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,899 +0,0 @@
<?php
/* 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);
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);
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;
}
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,
"_execution_time" => get_current_execution_time()
);
return json_encode($data);
}
function jsonrpc2_result_encode($result, $id = '') {
$data = array(
"jsonrpc" => "2.0",
"result" => $result,
"id" => $id,
"_execution_time" => get_current_execution_time()
);
return json_encode($data);
}
function jsonrpc2_error_encode($error, $id = '') {
$data = array(
"jsonrpc" => "2.0",
"error" => $error,
"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
$headers = array();
$lines = preg_split("'\r?\n'", $str);
$first_line = array_shift($lines);
$headers['@method'] = explode(' ', $first_line);
foreach ($lines as $line) {
if (!preg_match('/^([^:]+):(.*)$/', $line, $out)) continue;
$headers[$out[1]] = trim($out[2]);
}
return $headers;
}
function read_from_remote_server($remote_address, $remote_port, $scheme, $data = null, $conn = null, $buffer_size = 8192, $id = '') {
if (in_array($scheme, array("https", "ssl", "tls"))) {
$remote_address = "tls://" . $remote_address;
}
$sock = fsockopen($remote_address, $remote_port, $error_code, $error_message, DEFAULT_SOCKET_TIMEOUT);
if (!$sock) {
$error = array(
"status" => 502,
"code" => $error_code,
"message" => $error_message
);
if ($conn == null) {
echo jsonrpc2_error_encode($error, $id);
} else {
$buf = sprintf("HTTP/1.1 502 Bad Gateway\r\n\r\n");
$buf .= jsonrpc2_error_encode($error, $id);
fwrite($conn, $buf);
}
} else {
if ($conn == null) {
// send data
fwrite($sock, $data);
// receive data
$buf = null;
while (!feof($sock) && $buf !== false) {
$buf = fgets($sock, $buffer_size);
echo $buf;
}
} else {
// send data
$buf = null;
while (!feof($conn) && $buf !== false) {
$buf = fgets($conn, $buffer_size);
fwrite($sock, $buf);
}
// receive data
$buf = null;
while (!feof($sock) && $buf !== false) {
$buf = fgets($sock, $buffer_size);
fwrite($conn, $buf);
}
}
fclose($sock);
}
}
// stateless mode
function relay_request($params, $id = '') {
$buffer_size = $params['buffer_size'];
$request_data = base64_decode($params['request_data']);
$request_header = parse_headers($request_data);
$request_length = intval($params['request_length']);
$client_address = $params['client_address'];
$client_port = intval($params['client_port']);
$client_encoding = $params['client_encoding'];
$remote_address = $params['remote_address'];
$remote_port = intval($params['remote_port']);
$scheme = $params['scheme'];
$datetime = $params['datetime']; // format: %Y-%m-%d %H:%M:%S.%f
if (in_array($scheme, array("https", "ssl", "tls"))) {
$remote_address = "tls://" . $remote_address;
}
switch ($request_header['@method'][0]) {
case "CONNECT":
$error = array(
"status" => 405,
"code" => -1,
"message" => "Method Not Allowed"
);
echo jsonrpc2_error_encode($error, $id);
break;
default:
read_from_remote_server($remote_address, $remote_port, $scheme, $request_data, null, $buffer_size, $id);
}
}
// stateful mode
function relay_connect($params, $id = '') {
$buffer_size = $params['buffer_size'];
$client_address = $params['client_address'];
$client_port = intval($params['client_port']);
$client_encoding = $params['client_encoding'];
$remote_address = $params['remote_address'];
$remote_port = intval($params['remote_port']);
$scheme = $params['scheme'];
$datetime = $params['datetime']; // format: %Y-%m-%d %H:%M:%S.%f
$starttime = microtime(true);
$conn = fsockopen($client_address, $client_port, $error_code, $error_message, STATEFUL_SOCKET_TIMEOUT);
if (!$conn) {
$error = array(
"status" => 502,
"code" => $error_code,
"message" => $error_message,
"_params" => $params
);
echo jsonrpc2_error_encode($error, $id);
} else {
$stoptime = microtime(true);
$connection_speed = floor(($stoptime - $starttime) * 1000);
$data = jsonrpc2_encode("relay_accept", array(
"success" => true,
"connection_speed" => $connection_speed
), $id);
fwrite($conn, $data . "\r\n\r\n");
read_from_remote_server($remote_address, $remote_port, $scheme, null, $conn, $buffer_size, $id);
fclose($conn);
}
}
function relay_mysql_connect($params) {
$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");
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" => -1,
"message" => $e->__toString()
)
);
}
return array(
"success" => true,
"mysqli" => $mysqli,
"result" => array(
"status" => 200
)
);
}
function relay_mysql_query($params, $mysqli) {
$query = trim($params['query']);
$query_type = ""; // e.g., select, insert, update, delete
$pos = strpos($query, ' ');
if ($pos !== false) {
$query_type = strtolower(substr($query, 0, $pos));
}
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" => -1,
"message" => $e->__toString()
)
);
}
}
function relay_sendmail($params) {
$to = $params['to'];
$from = $params['from'];
$subject = $params['subject'];
$message = $params['message'];
$headers = 'From: ' . $from . "\r\n" .
'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(
"success" => false,
"error" => array(
"status" => 500,
"code" => $e['type'],
"message" => $e['message']
)
);
}
return array(
"success" => true,
"result" => array(
"status" => 200
)
);
}
function relay_get_version() {
return array(
"data" => PHP_HTTPPROXY_VERSION
);
}
function relay_get_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 array(
"data" => get_loaded_extensions()
);
}
function relay_dns_get_record($params) {
$hostname = $params['hostname'];
$data = dns_get_record($hostname);
if (!$data) {
return array(
"success" => false,
"error" => array(
"status" => 502,
"code" => -1,
"message" => $hostname . " is not found in DNS records"
)
);
}
return array(
"success" => true,
"result" => array(
"status" => 200,
"data" => $data
)
);
}
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, '');
// 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"
)
);
}
}
// 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" => 503,
"code" => -1,
"message" => $e->__toString()
)
);
}
}
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 = "";
$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>');
}
}
// parse a context
$context = json_decode(file_get_contents('php://input'), true);
// check is it JSON-RPC 2 (stateless)
if ($context['jsonrpc'] == "2.0") {
$method = $context['method'];
switch ($method) {
case "relay_request":
relay_request($context['params'], $context['id']); // stateless mode
break;
case "relay_connect":
relay_connect($context['params'], $context['id']); // stateful mode
break;
case "relay_mysql_query":
$result = relay_mysql_connect($context['params']);
if ($result['success']) {
$mysqli = $result['mysqli'];
$query_result = relay_mysql_query($context['params'], $mysqli);
if ($query_result['success']) {
echo jsonrpc2_result_encode($query_result['result'], $context['id']);
} else {
echo jsonrpc2_error_encode($query_result['error'], $context['id']);
}
} else {
echo jsonrpc2_error_encode($result['error'], $context['id']);
}
break;
case "relay_sendmail":
$result = relay_sendmail($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_version":
echo jsonrpc2_result_encode(relay_get_version(), $context['id']);
break;
case "relay_get_phpversion":
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;
case "relay_dns_get_record":
$result = relay_dns_get_record($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_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($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_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

@ -1,418 +0,0 @@
<?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

@ -1,53 +0,0 @@
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
View File

@ -1,297 +0,0 @@
#!/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)

View File

@ -1,14 +0,0 @@
[Unit]
Description=Caterpillar (php-httpproxy) Web Debugging Proxy Service
Documentation=https://github.com/gnh1201/caterpillar
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=/opt/caterpillar
ExecStart=/usr/bin/python3 /opt/caterpillar/server.py
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -1,520 +0,0 @@
<!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>

View File

@ -1,9 +0,0 @@
@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

View File

@ -1,9 +0,0 @@
#!/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

65
index.php Normal file
View File

@ -0,0 +1,65 @@
<?php
// HTTP proxy implementation with PHP socket
// Namhyeon Go <gnh1201@gmail.com>
// Created at: 2022-10-06
// Updated at: 2022-10-07
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></body></html>');
}
ini_set("default_socket_timeout", 1); // must be. because of `feof()` works
ini_set("max_execution_time", 0);
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);
$first_line = array_shift($lines);
$headers['@method'] = explode(' ', $first_line);
foreach ($lines as $line) {
if (!preg_match('/^([^:]+):(.*)$/', $line, $out)) continue;
$headers[$out[1]] = trim($out[2]);
}
return $headers;
}
$data = json_decode(file_get_contents('php://input'), true);
$buffer_size = $data['chunksize'];
$relay_data = base64_decode($data['data']);
$relay_headers = parse_headers($relay_data);
$relay_port = intval($data['port']);
$relay_scheme = $data['scheme'];
$relay_hostname = $data['server'];
switch ($relay_headers['@method'][0]) {
case "CONNECT": // {
echo sprintf("%s 200 Connection Established\r\n\r\n", $relay_headers['@method'][2]);
break;
// }
default: // {
$fp = fsockopen($relay_hostname, $relay_port, $errno, $errstr, 1);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
fwrite($fp, $relay_data);
$buf = null;
while (!feof($fp) && $buf !== false) {
$buf = fgets($fp, $buffer_size);
echo $buf;
}
fclose($fp);
}
// }
}

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

View File

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

View File

@ -1,69 +0,0 @@
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"

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

727
server.py
View File

@ -1,711 +1,122 @@
#!/usr/bin/python3
#
# server.py
# server file with TCP connection mode
#
# Caterpillar Proxy - The simple web debugging proxy (formerly, php-httpproxy)
# Namyheon Go (Catswords Research) <gnh1201@gmail.com>
# https://github.com/gnh1201/caterpillar
# gnh1201/php-httpproxy
# Go Namyheon <gnh1201@gmail.com>
# Created at: 2022-10-06
# Updated at: 2025-02-17
#
# Updated at: 2022-10-08
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 traceback
import textwrap
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
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", 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="")
listening_port = config('PORT', cast=int)
proxy_url = config('PROXY_URL')
except KeyboardInterrupt:
logger.warning("[*] User has requested an interrupt")
logger.warning("[*] Application Exiting.....")
print("\n[*] User has requested an interrupt")
print("[*] 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=5, 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
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 parse_first_data(data: bytes):
parsed_data = (b"", b"", b"", b"", b"")
def start(): #Main Program
try:
first_line = data.split(b"\n")[0]
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', listening_port))
sock.listen(max_connection)
print("[*] Server started successfully [ %d ]" %(listening_port))
except Exception:
print("[*] Unable to Initialize Socket")
print(Exception)
sys.exit(2)
method, url = first_line.split()[0:2]
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
except KeyboardInterrupt:
sock.close()
print("\n[*] Graceful Shutdown")
sys.exit(1)
http_pos = url.find(b"://") # Finding the position of ://
scheme = b"http" # check http/https or other protocol
def conn_string(conn, data, addr):
try:
first_line = data.split(b'\n')[0]
url = first_line.split()[1]
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 = ""
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)
proxy_server(webserver, port, scheme, url, conn, addr, data)
except Exception as e:
logger.error("[*] Exception on parsing the header", exc_info=e)
pass
return parsed_data
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
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 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)
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")
# https://github.com/inaz2/proxy2/blob/master/proxy2.py
def proxy_server(webserver, port, scheme, url, conn, addr, data):
try:
if not os.path.isfile(cert_path):
epoch = "%d" % (time.time() * 1000)
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:
logger.error("[*] Skipping certificate issuance.", exc_info=e)
cert_path = "default.crt"
print("[*] Started Request. %s" % (str(addr[0])))
logger.info("[*] Certificate file: %s" % cert_path)
logger.info("[*] Private key file: %s" % cert_key)
headers = {
"User-Agent": "php-httpproxy/0.1.1 (Client; Python " + python_version() + ")",
}
data = {
"data": base64.b64encode(data).decode("utf-8"),
"client": str(addr[0]),
"server": webserver.decode("utf-8"),
"port": str(port),
"scheme": scheme.decode("utf-8"),
"url": url.decode("utf-8"),
"length": str(len(data)),
"chunksize": str(buffer_size),
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
}
# 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.check_hostname = False
context.verify_mode = ssl.CERT_NONE
context.load_cert_chain(certfile=cert_path, keyfile=cert_key)
relay = requests.post(proxy_url, headers=headers, json=data, stream=True)
for chunk in relay.iter_content(chunk_size=buffer_size):
conn.send(chunk)
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""
print("[*] Request Done. %s" % (str(addr[0])))
return conn, data
def proxy_check_filtered(
data: bytes, webserver: bytes, port: bytes, scheme: bytes, method: bytes, url: bytes
):
filtered = False
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: bytes,
port: bytes,
scheme: bytes,
method: bytes,
url: bytes,
conn: socket.socket,
addr: bytes,
data: bytes,
):
try:
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":
while True:
try:
conn, data = proxy_connect(webserver, conn)
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))
)
# 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: socket.socket):
_sock.close()
# Wait to see if there is more data to transmit
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()
raise Exception("Filtered request")
sock.send(data)
if len(data) < buffer_size:
return
# send following chunks
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)
raise Exception("Filtered request")
sock.send(chunk)
if len(buffered) > buffer_size * 2:
buffered = buffered[-buffer_size * 2 :]
except:
break
# localhost mode
if server_url == "localhost" and server_connection_type == "proxy":
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if is_ssl:
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
sock = context.wrap_socket(
sock, server_hostname=webserver.decode(client_encoding)
)
sock.connect((webserver, port))
# sock.sendall(data)
sendall(sock, conn, data)
else:
sock.connect((webserver, port))
# sock.sendall(data)
sendall(sock, conn, data)
i = 0
is_http_403 = False
_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:
is_http_403 = True
break
_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 :]
i += 1
# when blocked
if is_http_403:
logger.warning(
"[*] Blocked the request by remote server: %s"
% webserver.decode(client_encoding)
)
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}')
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()
)
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":
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}')
sock_close(sock)
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)",
},
"data": {
"buffer_size": str(buffer_size),
"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"),
},
}
# get 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,
)
if query.status_code == 200:
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,
)
for chunk in relay.iter_content(chunk_size=buffer_size):
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"])
start_new_thread(relay_connect, (id, raw_data, proxy_data))
# wait for the relay
logger.info("[*] waiting for the relay... %s" % id)
max_reties = 30
t = 0
while t < max_reties and id not in accepted_relay:
time.sleep(1)
t += 1
if t < max_reties:
sock = accepted_relay[id]
logger.info("[*] connected the relay. %s" % id)
sendall(sock, conn, data)
else:
resolved_address_list.remove(resolved_address_list[0])
logger.info("[*] the relay is gone. %s" % id)
sock_close(sock)
return
# get response
i = 0
buffered = b""
while True:
_chunk = sock.recv(buffer_size)
if not _chunk:
break
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 :]
i += 1
sock_close(sock)
logger.info(
"[*] Received %s chunks. (%s bytes per chunk)"
% (str(i), str(buffer_size))
)
# stateless mode
elif server_connection_type == "stateless":
proxy_data = {
"headers": {
"User-Agent": "php-httpproxy/0.1.5 (Client; Python "
+ python_version()
+ "; abuse@catswords.net)",
},
"data": {
"buffer_size": str(buffer_size),
"request_data": base64.b64encode(data).decode(client_encoding),
"request_length": str(len(data)),
"client_address": str(addr[0]),
"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"),
},
}
_, raw_data = jsonrpc2_encode("relay_request", proxy_data["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""
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")
raise Exception("Filtered response")
conn.send(chunk)
if len(buffered) > buffer_size * 2:
buffered = buffered[-buffer_size * 2 :]
i += 1
logger.info(
"[*] Received %s chunks. (%s bytes per chunk)"
% (str(i), str(buffer_size))
)
# 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())
logger.warning("[*] Ignored the request.", exc_info=e)
conn.sendall(b'HTTP/1.1 403 Forbidden\r\n\r\n{"status":403}')
except socket.error:
sock.close()
conn.close()
print(sock.error)
sys.exit(1)
# journaling a filtered hosts
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:
file.writelines(lines)
def start(): # Main Program
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("", listening_port))
sock.listen(max_connection)
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 = recv(conn) # Recieve client data
start_new_thread(conn_string, (conn, data, addr)) # Starting a thread
except KeyboardInterrupt:
sock.close()
logger.info("[*] Graceful Shutdown")
sys.exit(1)
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
if __name__== "__main__":
start()

113
smtp.py
View File

@ -1,113 +0,0 @@
#!/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
View File

@ -1,114 +0,0 @@
#!/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)