CroxyProxyProxy / app.py
MB-IDK's picture
Update app.py
19eac07 verified
Raw
History Blame Contribute Delete
17 kB
"""
app.py β€” CroxyProxy Rotating Proxy API
L'interface (UI) est dans ui.py.
UI
GET / - Interface navigateur
API
GET /api - Infos API
GET /health - Status + stats
GET /servers - Liste des serveurs
GET /view - Rend une page via proxy (utilisΓ© par l'UI)
POST /proxy/fetch - Proxy rotatif
POST /proxy/random - Serveur alΓ©atoire
POST /proxy/batch - Plusieurs URLs
"""
import json, base64, re, random, time, threading, socket, ipaddress
from datetime import datetime, timezone
from urllib.parse import urlparse
from flask import Flask, request, jsonify, Response
from bs4 import BeautifulSoup
import cloudscraper
from html import unescape, escape
import warnings
warnings.filterwarnings("ignore")
from ui import BROWSER_UI, error_page
BASE = "https://www.croxyproxy.com"
app = Flask(__name__)
KEEP_HEADERS = {
"content-type", "content-length", "content-encoding",
"server", "date", "connection",
"access-control-allow-origin", "access-control-allow-credentials",
"cache-control", "etag", "last-modified",
"x-ratelimit-limit", "x-ratelimit-remaining",
"x-request-id", "location", "retry-after",
}
DROP_HEADERS = {
"set-cookie", "__cph", "__cpc",
"content-security-policy", "strict-transport-security",
"referrer-policy", "access-control-allow-headers",
"x-frame-options", "x-content-type-options",
"permissions-policy", "cross-origin-opener-policy",
"cross-origin-embedder-policy",
}
class S:
servers = []
idx = 0
lock = threading.Lock()
last = None
stats = {"req": 0, "ok": 0, "fail": 0}
def dec(e):
try:
return json.loads(bytes.fromhex(base64.b64decode(e).decode()).decode())
except Exception:
return None
def filter_headers(raw_headers, include_all=False):
if include_all:
return dict(raw_headers)
cleaned = {}
for k, v in raw_headers.items():
kl = k.lower()
if kl in DROP_HEADERS:
continue
if kl in KEEP_HEADERS:
cleaned[k] = v
return cleaned
def parse_body(text, content_type=""):
if not text:
return None
if "json" in content_type.lower() or text.strip().startswith(("{", "[")):
try:
return json.loads(text)
except (json.JSONDecodeError, ValueError):
pass
if "html" in content_type.lower() or text.strip().startswith("<"):
return {
"_type": "html", "_length": len(text), "content": text,
"_preview": text[:300].strip() + ("..." if len(text) > 300 else ""),
}
if len(text) > 2000:
return {
"_type": "text", "_length": len(text), "content": text,
"_preview": text[:500].strip() + "...",
}
return text
def extract_ip(url_str):
return (url_str or "").replace("https://", "").replace("http://", "").split("/")[0]
def format_result(raw, include_raw_headers=False):
if not raw.get("success"):
return {"success": False, "error": raw.get("error"), "server": raw.get("server")}
ct = ""
if raw.get("headers"):
ct = raw["headers"].get("Content-Type", raw["headers"].get("content-type", ""))
result = {
"success": True,
"status": raw.get("status"),
"url": raw.get("url"),
"body": parse_body(raw.get("body", ""), ct),
"proxy": raw.get("proxy"),
"servers_available": raw.get("servers_available"),
}
if raw.get("headers"):
result["headers"] = filter_headers(raw["headers"], include_all=include_raw_headers)
return result
def fetch_raw(url, sid=None):
sc = cloudscraper.create_scraper(
browser={"browser": "chrome", "platform": "windows", "desktop": True}
)
S.stats["req"] += 1
try:
r1 = sc.get(BASE, timeout=30)
if r1.status_code != 200:
S.stats["fail"] += 1
return {"success": False, "error": f"Homepage {r1.status_code}"}
s1 = BeautifulSoup(r1.text, "lxml")
ci = s1.find("input", {"name": "csrf"})
if not ci:
S.stats["fail"] += 1
return {"success": False, "error": "No CSRF"}
r2 = sc.post(
f"{BASE}/servers",
data={"url": url, "proxyServerId": "274", "csrf": ci["value"],
"demo": "0", "frontOrigin": BASE},
headers={"Content-Type": "application/x-www-form-urlencoded",
"Origin": BASE, "Referer": BASE + "/"},
allow_redirects=True, timeout=30,
)
if r2.status_code != 200:
S.stats["fail"] += 1
return {"success": False, "error": f"Servers {r2.status_code}"}
s2 = BeautifulSoup(r2.text, "lxml")
sel = s2.find("script", {"id": "serverSelectorScript"})
if not sel:
S.stats["fail"] += 1
return {"success": False, "error": "No selector"}
ss = [x for x in (dec(i) for i in json.loads(unescape(sel.get("data-ss", ""))))
if x and x.get("id")]
csrf2 = unescape(sel.get("data-csrf", "")).strip('"')
fo = unescape(sel.get("data-fo", "")).strip('"')
if not ss:
S.stats["fail"] += 1
return {"success": False, "error": "No servers"}
S.servers = ss
S.last = datetime.now(timezone.utc).isoformat()
ch = None
if sid:
ch = next((x for x in ss if x["id"] == sid), None)
if not ch:
with S.lock:
ch = ss[S.idx % len(ss)]
S.idx += 1
r3 = sc.post(
f"{BASE}/requests?fso=",
data={"url": url, "proxyServerId": str(ch["id"]), "csrf": csrf2,
"demo": "0", "frontOrigin": fo},
headers={"Content-Type": "application/x-www-form-urlencoded",
"Origin": BASE, "Referer": f"{BASE}/servers"},
allow_redirects=False, timeout=30,
)
loc = r3.headers.get("Location") or r3.headers.get("location")
if not loc:
S.stats["fail"] += 1
return {"success": False, "error": f"No redirect ({r3.status_code})",
"server": ch.get("name")}
r4 = sc.get(loc, timeout=30, allow_redirects=True)
dr = re.search(r'data-r="([^"]+)"', r4.text)
if not dr:
S.stats["fail"] += 1
return {"success": False, "error": "No data-r", "server": ch.get("name")}
final = base64.b64decode(dr.group(1)).decode()
r5 = sc.get(final, timeout=30, allow_redirects=True)
S.stats["ok"] += 1
return {
"success": True,
"status": r5.status_code,
"headers": dict(r5.headers),
"body": r5.text,
"url": url,
"proxy": {
"server_id": ch["id"],
"server_name": ch.get("name"),
"ip": extract_ip(ch.get("url", "")),
},
"servers_available": len(ss),
}
except Exception as e:
S.stats["fail"] += 1
return {"success": False, "error": str(e)}
# ═══════════════════════════════════════════════
# RÉÉCRITURE HTML (mode navigation)
# ═══════════════════════════════════════════════
# JS injectΓ© dans chaque page : reroute clics + formulaires GET par le proxy.
INJECT_JS = r"""
(function(){
var sidParam = __SID__ ? ("&server_id=" + __SID__) : "";
function proxify(u){ return "/view?url=" + encodeURIComponent(u) + sidParam; }
document.addEventListener("click", function(e){
var a = e.target && e.target.closest ? e.target.closest("a") : null;
if(!a || !a.href) return;
var raw = a.getAttribute("href") || "";
if(raw.startsWith("#")) return;
var h = a.href;
if(/^(javascript:|mailto:|tel:|blob:|data:)/i.test(h)) return;
e.preventDefault();
window.location.href = proxify(h);
}, true);
document.addEventListener("submit", function(e){
var f = e.target;
if(!f || f.tagName !== "FORM") return;
if((f.getAttribute("method") || "get").toLowerCase() !== "get") return;
e.preventDefault();
var action = f.action || __BASE__;
var params = new URLSearchParams(new FormData(f)).toString();
var sep = action.indexOf("?") === -1 ? "?" : "&";
window.location.href = proxify(action + (params ? sep + params : ""));
}, true);
})();
"""
def rewrite_html(html, base_url, server_id=None):
"""Injecte <base> (rΓ©solution des ressources relatives) + script de navigation."""
try:
soup = BeautifulSoup(html, "lxml")
head = soup.find("head")
if head is None:
head = soup.new_tag("head")
(soup.html or soup).insert(0, head)
for b in soup.find_all("base"):
b.decompose()
head.insert(0, soup.new_tag("base", href=base_url))
# Retire CSP / refresh meta qui cassent l'affichage
for meta in soup.find_all("meta", attrs={"http-equiv": True}):
if meta.get("http-equiv", "").lower() in ("content-security-policy", "refresh"):
meta.decompose()
body = soup.find("body") or soup
sid_js = json.dumps(str(server_id)) if server_id else '""'
js = INJECT_JS.replace("__SID__", sid_js).replace("__BASE__", json.dumps(base_url))
script_tag = soup.new_tag("script")
script_tag.string = js
body.append(script_tag)
return str(soup)
except Exception:
# Fallback : injection texte minimale si BeautifulSoup Γ©choue
base_tag = f'<base href="{escape(base_url, quote=True)}">'
return base_tag + html
# ═══════════════════════════════════════════════
# FETCH DIRECT (mode navigation β€” exit IP = la Space)
# ═══════════════════════════════════════════════
def is_blocked_host(url):
"""Bloque les adresses internes/privΓ©es (anti-SSRF)."""
try:
host = urlparse(url).hostname
if not host:
return True
if host == "localhost" or host.endswith(".local") or host.endswith(".internal"):
return True
for info in socket.getaddrinfo(host, None):
ip = ipaddress.ip_address(info[4][0])
if (ip.is_private or ip.is_loopback or ip.is_link_local
or ip.is_reserved or ip.is_multicast):
return True
return False
except Exception:
# Si la rΓ©solution Γ©choue, on laisse le fetch Γ©chouer naturellement
return False
def fetch_direct(url):
"""Récupère une page directement depuis le serveur."""
sc = cloudscraper.create_scraper(
browser={"browser": "chrome", "platform": "windows", "desktop": True}
)
S.stats["req"] += 1
try:
r = sc.get(
url, timeout=30, allow_redirects=True,
headers={
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "fr-FR,fr;q=0.9,en;q=0.8",
},
)
S.stats["ok"] += 1
ct = r.headers.get("Content-Type", "")
out = {
"success": True,
"status": r.status_code,
"content_type": ct,
"final_url": r.url,
"content": r.content,
}
ctl = ct.lower()
if "html" in ctl or "json" in ctl or ctl.startswith("text/") or not ct:
out["text"] = r.text
return out
except Exception as e:
S.stats["fail"] += 1
return {"success": False, "error": str(e)}
def warmup():
print("Warming up β€” populating server list...")
result = fetch_raw("https://httpbin.org/ip")
if result.get("success"):
print(f"βœ“ {len(S.servers)} servers loaded")
else:
print(f"βœ— Warm-up failed: {result.get('error')}")
def post_fork(server, worker):
warmup()
warmup()
# ═══════════════════════════════════════════════
# ROUTES
# ═══════════════════════════════════════════════
@app.route("/")
def ui():
return Response(BROWSER_UI, mimetype="text/html")
@app.route("/view")
def view():
try:
url = request.args.get("url", "").strip()
if not url:
return Response(error_page("Aucune URL fournie."), mimetype="text/html", status=400)
if not re.match(r"^https?://", url, re.I):
url = "https://" + url
if is_blocked_host(url):
return Response(error_page("HΓ΄te non autorisΓ© (adresse interne ou privΓ©e)."),
mimetype="text/html", status=403)
res = fetch_direct(url)
if not res.get("success"):
return Response(error_page(res.get("error", "Erreur inconnue."), url),
mimetype="text/html", status=502)
ct = (res.get("content_type") or "").lower()
final_url = res.get("final_url", url)
text = res.get("text", "")
# HTML β†’ réécriture pour garder la navigation dans le proxy
if "html" in ct or (not ct and text.lstrip().startswith("<")):
return Response(rewrite_html(text, final_url), mimetype="text/html")
# JSON / texte β†’ affichage lisible
if "json" in ct or ct.startswith("text/"):
wrap = (
'<!doctype html><html><head><meta charset="utf-8">'
'<style>body{background:#0d0f17;color:#e6e8f0;margin:0;padding:20px;'
'font-family:ui-monospace,Menlo,monospace;font-size:13px;line-height:1.6;'
'white-space:pre-wrap;word-break:break-word}</style></head><body>'
+ escape(text) + '</body></html>'
)
return Response(wrap, mimetype="text/html")
# Binaire (images, etc.) β†’ renvoi brut avec le bon content-type
return Response(res.get("content", b""), mimetype=ct or "application/octet-stream")
except Exception as e:
return Response(error_page(f"Erreur serveur : {e}"), mimetype="text/html", status=500)
@app.route("/api")
def api_index():
return jsonify({
"name": "CroxyProxy Rotating Proxy API",
"version": "2.1",
"ui": "GET / (interface navigateur)",
"endpoints": {
"GET /health": "Status + stats",
"GET /servers": "Liste des serveurs",
"GET /view?url=&server_id=": "Rend une page via proxy (utilisΓ© par l'UI)",
"POST /proxy/fetch": "Proxy rotatif {url, server_id?, raw_headers?}",
"POST /proxy/random": "Serveur alΓ©atoire {url, raw_headers?}",
"POST /proxy/batch": "Plusieurs URLs {urls: [...], raw_headers?}",
},
})
@app.route("/health")
def health():
return jsonify({
"status": "ready",
"servers": len(S.servers),
"last_refresh": S.last,
"stats": S.stats,
})
@app.route("/servers")
def servers():
return jsonify({
"count": len(S.servers),
"servers": [
{"id": s.get("id"), "name": s.get("name"), "ip": extract_ip(s.get("url", ""))}
for s in S.servers
],
})
@app.route("/proxy/fetch", methods=["POST"])
def proxy_fetch():
d = request.get_json() or {}
if not d.get("url"):
return jsonify({"error": "url required"}), 400
raw = fetch_raw(d["url"], d.get("server_id"))
return jsonify(format_result(raw, include_raw_headers=d.get("raw_headers", False)))
@app.route("/proxy/random", methods=["POST"])
def proxy_random():
d = request.get_json() or {}
if not d.get("url"):
return jsonify({"error": "url required"}), 400
sid = random.choice(S.servers)["id"] if S.servers else None
raw = fetch_raw(d["url"], sid)
return jsonify(format_result(raw, include_raw_headers=d.get("raw_headers", False)))
@app.route("/proxy/batch", methods=["POST"])
def proxy_batch():
d = request.get_json() or {}
urls = d.get("urls", [])
if not urls:
return jsonify({"error": "urls required"}), 400
include_raw = d.get("raw_headers", False)
results = []
for u in urls:
raw = fetch_raw(u)
results.append(format_result(raw, include_raw_headers=include_raw))
time.sleep(0.5)
return jsonify({
"count": len(results),
"success_count": sum(1 for r in results if r.get("success")),
"results": results,
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860)