diff --git a/bin/copyparty-fuseb.py b/bin/copyparty-fuseb.py index 650038a5..f8801c92 100755 --- a/bin/copyparty-fuseb.py +++ b/bin/copyparty-fuseb.py @@ -11,14 +11,18 @@ import re import os import sys import time +import json import stat import errno import struct +import codecs +import platform import threading import http.client # py2: httplib import urllib.parse from datetime import datetime from urllib.parse import quote_from_bytes as quote +from urllib.parse import unquote_to_bytes as unquote try: import fuse @@ -38,7 +42,7 @@ except: mount a copyparty server (local or remote) as a filesystem usage: - python ./copyparty-fuseb.py -f -o allow_other,auto_unmount,nonempty,url=http://192.168.1.69:3923 /mnt/nas + python ./copyparty-fuseb.py -f -o allow_other,auto_unmount,nonempty,pw=wark,url=http://192.168.1.69:3923 /mnt/nas dependencies: sudo apk add fuse-dev python3-dev @@ -50,6 +54,10 @@ fork of copyparty-fuse.py based on fuse-python which """ +WINDOWS = sys.platform == "win32" +MACOS = platform.system() == "Darwin" + + def threadless_log(msg): print(msg + "\n", end="") @@ -93,6 +101,41 @@ def html_dec(txt): ) +def register_wtf8(): + def wtf8_enc(text): + return str(text).encode("utf-8", "surrogateescape"), len(text) + + def wtf8_dec(binary): + return bytes(binary).decode("utf-8", "surrogateescape"), len(binary) + + def wtf8_search(encoding_name): + return codecs.CodecInfo(wtf8_enc, wtf8_dec, name="wtf-8") + + codecs.register(wtf8_search) + + +bad_good = {} +good_bad = {} + + +def enwin(txt): + return "".join([bad_good.get(x, x) for x in txt]) + + for bad, good in bad_good.items(): + txt = txt.replace(bad, good) + + return txt + + +def dewin(txt): + return "".join([good_bad.get(x, x) for x in txt]) + + for bad, good in bad_good.items(): + txt = txt.replace(good, bad) + + return txt + + class CacheNode(object): def __init__(self, tag, data): self.tag = tag @@ -115,8 +158,9 @@ class Stat(fuse.Stat): class Gateway(object): - def __init__(self, base_url): + def __init__(self, base_url, pw): self.base_url = base_url + self.pw = pw ui = urllib.parse.urlparse(base_url) self.web_root = ui.path.strip("/") @@ -135,8 +179,7 @@ class Gateway(object): self.conns = {} def quotep(self, path): - # TODO: mojibake support - path = path.encode("utf-8", "ignore") + path = path.encode("wtf-8") return quote(path, safe="/") def getconn(self, tid=None): @@ -159,20 +202,29 @@ class Gateway(object): except: pass - def sendreq(self, *args, **kwargs): + def sendreq(self, *args, **ka): tid = get_tid() + if self.pw: + ck = "cppwd=" + self.pw + try: + ka["headers"]["Cookie"] = ck + except: + ka["headers"] = {"Cookie": ck} try: c = self.getconn(tid) - c.request(*list(args), **kwargs) + c.request(*list(args), **ka) return c.getresponse() except: self.closeconn(tid) c = self.getconn(tid) - c.request(*list(args), **kwargs) + c.request(*list(args), **ka) return c.getresponse() def listdir(self, path): - web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots" + if bad_good: + path = dewin(path) + + web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots&ls" r = self.sendreq("GET", web_path) if r.status != 200: self.closeconn() @@ -182,9 +234,12 @@ class Gateway(object): ) ) - return self.parse_html(r) + return self.parse_jls(r) def download_file_range(self, path, ofs1, ofs2): + if bad_good: + path = dewin(path) + web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?raw" hdr_range = "bytes={}-{}".format(ofs1, ofs2 - 1) log("downloading {}".format(hdr_range)) @@ -200,40 +255,27 @@ class Gateway(object): return r.read() - def parse_html(self, datasrc): - ret = [] - remainder = b"" - ptn = re.compile( - r"^