Subir archivos a "/"

This commit is contained in:
nix 2025-11-08 00:19:44 +00:00
parent 50c4c9669f
commit 0cf44045c9
3 changed files with 215 additions and 125 deletions

View File

@ -1,94 +1,73 @@
import os
import hashlib
import json
import hashlib
import requests
import subprocess
from datetime import datetime, timedelta
from flask import Flask, jsonify, Response, request
from urllib.parse import urljoin
import tokenext
from healthcheck import ensure_config_exists
BASE_API = "https://ws1.smn.gob.ar"
CACHE_DIR = "json.api.cache"
config = ensure_config_exists()
server_cfg = config["server"]
PORT = int(server_cfg.get("port", 6942))
PASSWORD = server_cfg.get("password", "").strip()
CACHE_DIR = server_cfg.get("cache_dir", "cache")
BASE_API = server_cfg.get("base_api", "https://ws1.smn.gob.ar")
LOG_FILE = server_cfg.get("log_file", "").strip()
SMN_TOKEN_FILE = "token"
CACHE_TTL = timedelta(minutes=60)
SMN_TOKEN_FILE = "token.txt"
ACCESS_TOKEN = "i.hate.smn"
TIMEOUT = 10
AUTH_ENABLED = PASSWORD != ""
app = Flask(__name__)
app.config["JSONIFY_PRETTYPRINT_REGULAR"] = False
@app.after_request
def remove_server_header(response):
response.headers["Server"] = ""
response.headers["X-Powered-By"] = ""
return response
def log(msg: str):
if LOG_FILE:
with open(LOG_FILE, "a") as f:
f.write(f"[{datetime.now().isoformat()}] {msg}\n")
print(msg)
def get_cache_filename(url: str) -> str:
h = hashlib.sha256(url.encode()).hexdigest()
return os.path.join(CACHE_DIR, f"{h}.json")
def load_cache(url: str):
try:
path = get_cache_filename(url)
if not os.path.exists(path):
return None
mtime = datetime.fromtimestamp(os.path.getmtime(path))
if datetime.now() - mtime > CACHE_TTL:
return None
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return None
def save_cache(url: str, data: dict):
try:
os.makedirs(CACHE_DIR, exist_ok=True)
path = get_cache_filename(url)
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False)
except Exception as e:
print(f"[CACHE] Failed to save cache: {e}")
json.dump(data, f, indent=2, ensure_ascii=False)
def load_smn_token():
try:
with open(SMN_TOKEN_FILE, "r", encoding="utf-8") as f:
token = f.read().strip()
if not token:
raise ValueError("Empty token file.")
return token
except Exception as e:
print(f"[TOKEN] Error loading token: {e}")
return ""
with open(SMN_TOKEN_FILE, "r") as f:
return f.read().strip()
def refresh_smn_token():
print("[TOKEN] Refreshing SMN token...")
try:
result = subprocess.run(
["python3", "tokenext.py"],
capture_output=True,
text=True,
timeout=60
)
if result.returncode == 0:
print("[TOKEN] Token refreshed successfully.")
log("[TOKEN] Refreshing SMN token...")
ok = tokenext.refresh_token(output_file=SMN_TOKEN_FILE, headless=True, wait_seconds=8)
if ok:
log("[TOKEN] Token refreshed successfully.")
else:
print(f"[TOKEN] Refresh failed: {result.stderr.strip()}")
except Exception as e:
print(f"[TOKEN] Error running tokenext.py: {e}")
log("[TOKEN] Failed to refresh token.")
def check_access_token():
if not AUTH_ENABLED:
return True
header_token = request.headers.get("Authorization", "").strip()
if header_token != ACCESS_TOKEN:
# Drop unauthorized message (empty response)
return Response("", status=401)
return None
return header_token == PASSWORD
def fetch_from_smn(url: str, retry: bool = True):
token = load_smn_token()
@ -99,52 +78,50 @@ def fetch_from_smn(url: str, retry: bool = True):
}
try:
resp = requests.get(url, headers=headers, timeout=TIMEOUT)
resp = requests.get(url, headers=headers, timeout=10)
except requests.RequestException as e:
print(f"[ERROR] Request failed: {e}")
return None
return Response(str(e), status=502)
if resp.status_code == 401 and retry:
print("[AUTH] SMN token expired, refreshing...")
log("[AUTH] SMN token expired, trying to refresh...")
refresh_smn_token()
return fetch_from_smn(url, retry=False)
return resp
@app.route("/<path:subpath>")
@app.route("/smn/<path:subpath>")
def smn_proxy(subpath):
unauthorized = check_access_token()
if unauthorized:
return unauthorized
if not check_access_token():
return jsonify({"error": "Unauthorized"}), 401
url = f"{BASE_API}/{subpath}"
if ".." in subpath or subpath.startswith("/"):
return jsonify({"error": "Invalid path"}), 400
url = urljoin(BASE_API + "/", subpath)
# Cache check
cached = load_cache(url)
if cached:
print(f"[CACHE] Loaded {subpath}")
log(f"[CACHE] Loaded {subpath}")
return jsonify(cached)
print(f"[FETCH] {url}")
log(f"[FETCH] {url}")
resp = fetch_from_smn(url)
if not resp:
return Response("", status=502)
if resp.status_code >= 400:
print(f"[WARN] {resp.status_code} for {url}")
return Response("", status=resp.status_code)
if not hasattr(resp, "status_code"):
return Response("Upstream error", status=502)
if resp.status_code != 200:
return Response(resp.text, status=resp.status_code,
content_type=resp.headers.get("Content-Type", "text/plain"))
try:
data = resp.json()
except Exception:
print("[ERROR] Invalid JSON response.")
return Response("", status=502)
save_cache(url, data)
return jsonify(data)
except Exception:
return Response("Invalid JSON from SMN", status=502)
if __name__ == "__main__":
os.makedirs(CACHE_DIR, exist_ok=True)
app.run(host="0.0.0.0", port=6942, debug=False, use_reloader=False)
log(f"[STARTUP] Server starting on port {PORT}")
app.run(host="0.0.0.0", port=PORT)

39
healthcheck.py Normal file
View File

@ -0,0 +1,39 @@
import os
import configparser
CONFIG_FILE = "config.ini"
DEFAULT_CONFIG = {
"server": {
"port": "6942",
"password": "debug",
"cache_dir": "cache",
"base_api": "https://ws1.smn.gob.ar",
"log_file": ""
}
}
def ensure_config_exists():
config = configparser.ConfigParser()
if not os.path.exists(CONFIG_FILE):
print("[CONFIG] config.ini not found - creating with default values")
config.read_dict(DEFAULT_CONFIG)
with open(CONFIG_FILE, "w") as f:
config.write(f)
else:
config.read(CONFIG_FILE)
changed = False
for section, values in DEFAULT_CONFIG.items():
if section not in config:
config[section] = values
changed = True
else:
for key, val in values.items():
if key not in config[section]:
config[section][key] = val
changed = True
if changed:
with open(CONFIG_FILE, "w") as f:
config.write(f)
print("[CONFIG] Missing keys added to config.ini")
return config

View File

@ -1,62 +1,136 @@
# tokenext.py
import re
import time
from typing import Optional
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
URL = "https://www.smn.gob.ar/"
OUTPUT_FILE = "token.txt"
DEFAULT_URL = "https://www.smn.gob.ar/"
DEFAULT_OUTPUT_FILE = "token"
def extract_token_from_source(source: str):
m = re.search(r"localStorage\.setItem\(\s*['\"]token['\"]\s*,\s*['\"]([^'\"]+)['\"]\s*\)", source)
return m.group(1) if m else None
chrome_options = Options()
chrome_options.add_argument("--headless=new")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_argument("--window-size=1920,1080")
chrome_options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
def extract_token_from_source(source: str) -> Optional[str]:
m = re.search(
r"localStorage\.setItem\(\s*['\"]token['\"]\s*,\s*['\"]([^'\"]+)['\"]\s*\)",
source,
)
if m:
return m.group(1)
m = re.search(r"localStorage\.token\s*=\s*['\"]([^'\"]+)['\"]", source)
if m:
return m.group(1)
return None
def make_chrome_options(headless: bool = True) -> Options:
opts = Options()
if headless:
try:
opts.add_argument("--headless=new")
except Exception:
opts.add_argument("--headless")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
opts.add_argument("--disable-gpu")
opts.add_argument("--disable-blink-features=AutomationControlled")
opts.add_argument("--window-size=1920,1080")
opts.add_argument(
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/118.0.5993.90 Safari/537.36")
"Chrome/118.0.5993.90 Safari/537.36"
)
return opts
driver = webdriver.Chrome(options=chrome_options)
try:
print(f"Loading URL {URL}")
driver.get(URL)
def get_token(
url: str = DEFAULT_URL,
headless: bool = True,
wait_seconds: int = 8,
driver_wait_timeout: int = 20,
chrome_driver_path: Optional[str] = None,
) -> Optional[str]:
time.sleep(8)
options = make_chrome_options(headless=headless)
driver = None
try:
if chrome_driver_path:
driver = webdriver.Chrome(executable_path=chrome_driver_path, options=options)
else:
driver = webdriver.Chrome(options=options)
# load page
driver.get(url)
if wait_seconds:
time.sleep(wait_seconds)
try:
WebDriverWait(driver, 20).until(
WebDriverWait(driver, driver_wait_timeout).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
except Exception:
pass
token = None
try:
token = driver.execute_script("return window.localStorage.getItem('token');")
except Exception:
token = None
# fallback to searching page source
if not token:
token = extract_token_from_source(driver.page_source)
return token
except Exception as ex:
return None
finally:
if driver:
try:
driver.quit()
except Exception:
pass
def refresh_token(
output_file: str = DEFAULT_OUTPUT_FILE,
**get_token_kwargs,
) -> bool:
token = get_token(**get_token_kwargs)
if token:
try:
with open(output_file, "w", encoding="utf-8") as f:
f.write(token)
return True
except Exception:
return False
return False
if __name__ == "__main__":
import sys
url = DEFAULT_URL
if len(sys.argv) > 1:
url = sys.argv[1]
print(f"Loading URL {url}")
token = get_token(headless=True)
if token:
print(f"\n[+] Token found:\n{token}\n")
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
f.write(token)
print(f"[+] Saved to {OUTPUT_FILE}")
try:
with open(DEFAULT_OUTPUT_FILE, "w", encoding="utf-8") as fh:
fh.write(token)
print(f"[+] Saved to {DEFAULT_OUTPUT_FILE}")
except Exception as e:
print("[!] Failed to save token:", e)
else:
print("[!] No token found in localStorage or page source.")
with open("page_debug.html", "w", encoding="utf-8") as f:
f.write(driver.page_source)
print("[i] Saved full page source to page_debug.html for manual check.")
finally:
driver.quit()