{"repo": "beak2825/remotezip-web", "files": {"app.py": {"212e2ce6f9ca2bf2d0386f98a8652a7549254d20": {"t": 1785604169.2261007, "n": 39630, "h": "cb29f654ce715a19", "c": "#!/usr/bin/env python3\n\"\"\"\nzipstream - Browse remote ZIP files without downloading them.\nRange-request based ZIP central directory parser + on-the-fly file streaming.\n\"\"\"\n\nimport os\nimport io\nimport json\nimport zlib\nimport struct\nimport sqlite3\nimport hashlib\nimport mimetypes\nimport threading\nimport urllib.request\nimport urllib.error\nfrom datetime import datetime\nfrom flask import Flask, request, Response, render_template_string, jsonify, redirect, url_for, stream_with_context\n\napp = Flask(__name__)\nDB_PATH = os.path.expanduser(\"~/.zipstream_cache.db\")\ndb_lock = threading.Lock()\n\n# ──────────────────────────────────────────────\n# DATABASE\n# ──────────────────────────────────────────────\n\ndef db_init():\n with sqlite3.connect(DB_PATH) as con:\n con.execute(\"\"\"\n CREATE TABLE IF NOT EXISTS zip_cache (\n url_hash TEXT PRIMARY KEY,\n url TEXT NOT NULL,\n total_size INTEGER,\n entry_count INTEGER,\n fetched_at TEXT,\n entries_json TEXT\n )\n \"\"\")\n con.commit()\n\ndef url_hash(url):\n return hashlib.sha256(url.encode()).hexdigest()\n\ndef cache_get(url):\n h = url_hash(url)\n with sqlite3.connect(DB_PATH) as con:\n row = con.execute(\n \"SELECT url, total_size, entry_count, fetched_at, entries_json FROM zip_cache WHERE url_hash=?\",\n (h,)\n ).fetchone()\n if row:\n return {\n \"url\": row[0],\n \"total_size\": row[1],\n \"entry_count\": row[2],\n \"fetched_at\": row[3],\n \"entries\": json.loads(row[4])\n }\n return None\n\ndef cache_set(url, total_size, entries):\n h = url_hash(url)\n now = datetime.utcnow().isoformat()\n entries_json = json.dumps(entries)\n with db_lock:\n with sqlite3.connect(DB_PATH) as con:\n con.execute(\"\"\"\n INSERT OR REPLACE INTO zip_cache\n (url_hash, url, total_size, entry_count, fetched_at, entries_json)\n VALUES (?, ?, ?, ?, ?, ?)\n \"\"\", (h, url, total_size, len(entries), now, entries_json))\n con.commit()\n\ndef cache_list_all():\n with sqlite3.connect(DB_PATH) as con:\n rows = con.execute(\n \"SELECT url, total_size, entry_count, fetched_at FROM zip_cache ORDER BY fetched_at DESC\"\n ).fetchall()\n return [{\"url\": r[0], \"total_size\": r[1], \"entry_count\": r[2], \"fetched_at\": r[3]} for r in rows]\n\n# ──────────────────────────────────────────────\n# ZIP PARSING (via range requests)\n# ──────────────────────────────────────────────\n\ndef http_head(url):\n req = urllib.request.Request(url, method='HEAD')\n req.add_header('User-Agent', 'zipstream/1.0')\n try:\n with urllib.request.urlopen(req, timeout=15) as r:\n size = r.headers.get('Content-Length')\n accept_ranges = r.headers.get('Accept-Ranges', '')\n return int(size) if size else None, accept_ranges\n except Exception as e:\n raise RuntimeError(f\"HEAD request failed: {e}\")\n\ndef http_range(url, start, end):\n req = urllib.request.Request(url)\n req.add_header('User-Agent', 'zipstream/1.0')\n req.add_header('Range', f'bytes={start}-{end}')\n try:\n with urllib.request.urlopen(req, timeout=30) as r:\n return r.read()\n except Exception as e:\n raise RuntimeError(f\"Range request failed ({start}-{end}): {e}\")\n\ndef find_eocd(data):\n \"\"\"Find End of Central Directory record signature.\"\"\"\n sig = b'PK\\x05\\x06'\n # Search from end\n pos = data.rfind(sig)\n if pos == -1:\n raise ValueError(\"EOCD signature not found — not a valid ZIP or needs more tail bytes\")\n return pos\n\ndef parse_eocd(data, base_offset):\n \"\"\"Parse EOCD record, return (cd_offset, cd_size, total_entries).\"\"\"\n # Check for ZIP64 EOCD locator just before EOCD\n zip64_locator_sig = b'PK\\x06\\x07'\n eocd_offset_in_data = data.rfind(b'PK\\x05\\x06')\n \n # Try ZIP64 first\n locator_pos = data.rfind(zip64_locator_sig)\n if locator_pos != -1:\n # ZIP64 EOCD locator found\n zip64_eocd_offset = struct.unpack_from('> 5) & 0x3F\n hour = (mod_time_raw >> 11) & 0x1F\n day = mod_date_raw & 0x1F\n month = (mod_date_raw >> 5) & 0x0F\n year = ((mod_date_raw >> 9) & 0x7F) + 1980\n dt = datetime(year, month, day, hour, minute, second)\n mod_str = dt.strftime('%Y-%m-%d %H:%M')\n except Exception:\n mod_str = '—'\n\n # Handle ZIP64 extended info in extra field\n extra = cd_data[pos+46+fname_len : pos+46+fname_len+extra_len]\n if compressed_size == 0xFFFFFFFF or uncompressed_size == 0xFFFFFFFF or local_header_off == 0xFFFFFFFF:\n epos = 0\n while epos < len(extra) - 4:\n eid = struct.unpack_from('= 0 and cd_start_in_tail + cd_size <= len(tail):\n cd_data = tail[cd_start_in_tail : cd_start_in_tail + cd_size]\n else:\n cd_data = http_range(url, cd_offset, cd_offset + cd_size - 1)\n\n entries = parse_central_directory(cd_data)\n return total_size, entries\n\n# ──────────────────────────────────────────────\n# FILE STREAMING\n# ──────────────────────────────────────────────\n\nLOCAL_HEADER_FIXED = 30 # bytes before variable fields\n\ndef stream_entry(url, entry):\n \"\"\"Fetch and decompress a single ZIP entry on-the-fly.\"\"\"\n off = entry['local_header_offset']\n # Read local file header to get actual variable field lengths\n header_data = http_range(url, off, off + LOCAL_HEADER_FIXED - 1)\n if header_data[:4] != b'PK\\x03\\x04':\n raise RuntimeError(\"Bad local file header signature\")\n fname_len = struct.unpack_from('= 2:\n d = parts[0]\n if d not in dirs_seen:\n dirs_seen.add(d)\n full_path = (current_prefix + '/' + d).lstrip('/')\n subdirs.append({\"name\": d, \"path\": full_path})\n return subdirs, files\n\n subdirs, files = get_children(stripped, path)\n\n zip_name = url.split('/')[-1].split('?')[0] or 'archive.zip'\n\n return render_template_string(BROWSE_TEMPLATE,\n url=url, path=path, zip_name=zip_name,\n subdirs=subdirs, files=files, crumbs=crumbs,\n total_size=total_size, entry_count=len(entries),\n fetched_at=fetched_at, from_cache=from_cache,\n error=error, fmt_size=fmt_size\n )\n\n@app.route('/download')\ndef download():\n url = request.args.get('url', '').strip()\n entry_name = request.args.get('entry', '').strip()\n if not url or not entry_name:\n return \"Missing url or entry parameter\", 400\n\n cached = cache_get(url)\n if cached:\n entries = cached['entries']\n else:\n try:\n total_size, entries = fetch_zip_directory(url)\n cache_set(url, total_size, entries)\n except Exception as e:\n return f\"Error fetching ZIP directory: {e}\", 500\n\n entry = next((e for e in entries if e['name'] == entry_name), None)\n if not entry:\n return f\"Entry not found: {entry_name}\", 404\n if entry['is_dir']:\n return \"Cannot download a directory\", 400\n\n try:\n data = stream_entry(url, entry)\n except Exception as e:\n return f\"Error streaming entry: {e}\", 500\n\n filename = os.path.basename(entry_name)\n mime = get_mime(filename)\n render = should_render(filename)\n\n disposition = 'inline' if render else 'attachment'\n\n # For viewer page (text/code/image/video)\n view_param = request.args.get('view', '0')\n if view_param == '1' and render:\n return render_template_string(VIEWER_TEMPLATE,\n url=url, entry=entry_name, filename=filename,\n mime=mime, data=data, fmt_size=fmt_size\n )\n\n resp = Response(data, mimetype=mime)\n resp.headers['Content-Disposition'] = f'{disposition}; filename=\"{filename}\"'\n resp.headers['Content-Length'] = str(len(data))\n return resp\n\n@app.route('/api/cache')\ndef api_cache():\n return jsonify(cache_list_all())\n\n@app.route('/api/invalidate', methods=['POST'])\ndef api_invalidate():\n url = request.json.get('url', '')\n if not url:\n return jsonify({\"error\": \"no url\"}), 400\n h = url_hash(url)\n with db_lock:\n with sqlite3.connect(DB_PATH) as con:\n con.execute(\"DELETE FROM zip_cache WHERE url_hash=?\", (h,))\n con.commit()\n return jsonify({\"ok\": True})\n\n# ──────────────────────────────────────────────\n# TEMPLATES\n# ──────────────────────────────────────────────\n\nBASE_STYLE = \"\"\"\n\n\n\n\n\n\"\"\"\n\nHOME_TEMPLATE = \"\"\"\n\nzipstream\"\"\" + BASE_STYLE + \"\"\"\n\n
\n 📦zipstream\n
\n \n \n
\n
\n\n
\n
\n
Fetching ZIP directory…
\n
\n\n
\n
zipstream
\n
Browse remote ZIP files without downloading them. Range requests only.
\n\n
\n

// paste a zip url

\n
\n
\n \n \n
\n
\n
\n\n {% if recent %}\n \n {% endif %}\n
\n\n\n\"\"\"\n\nBROWSE_TEMPLATE = \"\"\"\n\n{{ zip_name }} — zipstream\"\"\" + BASE_STYLE + \"\"\"\n\n
\n 📦zipstream\n
\n \n \n \n
\n
\n\n
\n
\n
Fetching…
\n
\n\n
\n {% if error %}\n
⚠ {{ error }}
\n {% else %}\n\n
\n
\n Archive\n {{ zip_name }}\n
\n
\n Total Size\n {{ fmt_size(total_size) }}\n
\n
\n Files\n {{ entry_count }}\n
\n
\n Status\n \n {% if from_cache %}● cached\n {% else %}● live{% endif %}\n \n
\n {% if fetched_at %}\n
\n Fetched\n {{ fetched_at[:16].replace('T',' ') }}\n
\n {% endif %}\n
\n\n
\n 🏠 root\n {% for crumb in crumbs %}\n /\n {{ crumb.name }}\n {% endfor %}\n
\n\n \n \n \n \n \n \n \n \n \n \n \n \n {% if crumbs %}\n \n \n \n {% endif %}\n\n {% for d in subdirs %}\n \n \n \n \n \n \n \n \n {% endfor %}\n\n {% for f in files %}\n {% set fname = f.display_name.split('/')[-1] %}\n {% set ext = fname.rsplit('.',1)[-1].lower() if '.' in fname else '' %}\n {% set is_renderable = ext in ['txt','md','json','xml','csv','log','ini','cfg','conf','yaml','yml','toml','env','sh','py','js','ts','html','htm','css','c','cpp','h','rs','go','java','rb','kt','swift','sql','r','bat','gitignore','dockerfile','png','jpg','jpeg','gif','webp','svg','bmp','ico','mp4','webm','ogg','mp3','wav','flac','pdf'] %}\n \n \n \n \n \n \n \n \n {% endfor %}\n\n {% if not subdirs and not files %}\n \n {% endif %}\n \n
NameSizeCompressedModifiedMethod
\n
\n ↑\n {% set parent_path = crumbs[:-1]|map(attribute='path')|list|join('/') if crumbs|length > 1 else '' %}\n ..\n
\n
\n
\n 📁\n {{ d.name }}/\n
\n
———
\n
\n \n {% if ext in ['png','jpg','jpeg','gif','webp','svg','bmp','ico'] %}🖼️\n {% elif ext in ['mp4','webm','ogg','avi','mov'] %}🎬\n {% elif ext in ['mp3','wav','flac','aac'] %}🎵\n {% elif ext in ['py','js','ts','rs','go','c','cpp','h','java','rb','kt','swift','sh','bat'] %}💻\n {% elif ext in ['json','xml','yaml','yml','toml','csv'] %}📋\n {% elif ext in ['txt','md','log'] %}📄\n {% elif ext in ['pdf'] %}📕\n {% elif ext in ['zip','gz','tar','rar','7z'] %}📦\n {% elif ext in ['exe','msi','deb','apk'] %}⚙️\n {% else %}📄{% endif %}\n \n {% if is_renderable %}\n {{ fname }}\n {% else %}\n {{ fname }}\n {% endif %}\n
\n
{{ fmt_size(f.uncompressed_size) }}{{ fmt_size(f.compressed_size) }}{{ f.modified }}{{ 'deflate' if f.compress_method == 8 else ('store' if f.compress_method == 0 else f.compress_method) }}\n ↓\n
Empty directory
\n {% endif %}\n
\n\n\n\n\"\"\"\n\nVIEWER_TEMPLATE = \"\"\"\n\n{{ filename }} — zipstream\"\"\" + BASE_STYLE + \"\"\"\n\n\n\n
\n 📦zipstream\n
\n {% set parts = entry.split('/') %}\n {% for i, p in parts|enumerate %}\n {% if loop.last %}\n {{ p }}\n {% else %}\n {% set partial = parts[:loop.index]|join('/') %}\n {{ p }}/\n {% endif %}\n {% endfor %}\n
\n {{ fmt_size(data|length) }}\n ← back\n ↓ Download\n
\n\n{% set ext = filename.rsplit('.',1)[-1].lower() if '.' in filename else '' %}\n\n{% if ext in ['png','jpg','jpeg','gif','webp','svg','bmp','ico'] %}\n
\n \"{{\n
\n{% elif ext in ['mp4','webm','ogg'] %}\n
\n \n
\n{% elif ext in ['mp3','wav','flac','aac'] %}\n
\n
{{ filename }}
\n \n
\n{% elif ext == 'pdf' %}\n \n{% else %}\n
{{ data | decode_safe }}
\n{% endif %}\n\n\n\"\"\"\n\n# ──────────────────────────────────────────────\n# JINJA2 FILTERS / GLOBALS\n# ──────────────────────────────────────────────\n\nimport base64\n\n@app.template_filter('b64encode')\ndef b64encode_filter(data):\n return base64.b64encode(data).decode('ascii')\n\n@app.template_filter('decode_safe')\ndef decode_safe_filter(data):\n try:\n return data.decode('utf-8')\n except Exception:\n return data.decode('latin-1', errors='replace')\n\n@app.template_filter('urlencode')\ndef urlencode_filter(s):\n import urllib.parse\n return urllib.parse.quote(str(s), safe='')\n\n@app.template_global('enumerate')\ndef jinja_enumerate(it):\n return enumerate(it)\n\n# ──────────────────────────────────────────────\n# MAIN\n# ──────────────────────────────────────────────\n\nif __name__ == '__main__':\n db_init()\n print(\"📦 zipstream running → http://localhost:5000\")\n print(f\" Cache: {DB_PATH}\")\n app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)\n"}}}}