diff --git a/README.md b/README.md index 8ae5a2ae..93cab981 100644 --- a/README.md +++ b/README.md @@ -62,9 +62,7 @@ pip install black bandit pylint flake8 # vscode tooling roughly sorted by priority -* http error handling (conn.status or handler-retval) * look into android thumbnail cache file format -* last-modified header * support pillow-simd * figure out the deal with pixel3a not being connectable as hotspot * pixel3a having unpredictable 3sec latency in general :|||| diff --git a/copyparty/__main__.py b/copyparty/__main__.py index b15ec3e7..17018e99 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -8,6 +8,7 @@ __copyright__ = 2019 __license__ = "MIT" __url__ = "https://github.com/9001/copyparty/" +import locale import argparse from textwrap import dedent @@ -35,6 +36,18 @@ class RiceFormatter(argparse.HelpFormatter): def main(): + for x in [ + "en_US.UTF-8", + "English_United States.UTF8", + "English_United States.1252", + ]: + try: + locale.setlocale(locale.LC_ALL, x) + print("Locale:", x) + break + except: + continue + ap = argparse.ArgumentParser( formatter_class=RiceFormatter, prog="copyparty", diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 1f1af0f6..504ea05f 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -6,6 +6,7 @@ import os import stat import time from datetime import datetime +import calendar import mimetypes import cgi @@ -45,7 +46,7 @@ class HttpCli(object): return False try: - mode, self.req, _ = headerlines[0].split(" ") + self.mode, self.req, _ = headerlines[0].split(" ") except: raise Pebkac("bad headers:\n" + "\n".join(headerlines)) @@ -99,12 +100,12 @@ class HttpCli(object): self.vpath = unquotep(vpath) try: - if mode == "GET": + if self.mode in ["GET", "HEAD"]: return self.handle_get() - elif mode == "POST": + elif self.mode == "POST": return self.handle_post() else: - raise Pebkac('invalid HTTP mode "{0}"'.format(mode)) + raise Pebkac('invalid HTTP mode "{0}"'.format(self.mode)) except Pebkac as ex: try: @@ -114,6 +115,8 @@ class HttpCli(object): return False + return True + def reply(self, body, status="200 OK", mime="text/html", headers=[]): # TODO something to reply with user-supplied values safely response = [ @@ -139,7 +142,7 @@ class HttpCli(object): self.reply(b"
" + body.encode("utf-8"), *list(args), **kwargs)
def handle_get(self):
- self.log("GET " + self.req)
+ self.log("{:4} {}".format(self.mode, self.req))
# "embedded" resources
if self.vpath.startswith(".cpr"):
@@ -312,15 +315,41 @@ class HttpCli(object):
self.parser.drop()
def tx_file(self, path):
- sz = os.path.getsize(fsenc(path))
- mime = mimetypes.guess_type(path)[0]
- header = "HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: {}\r\nContent-Length: {}\r\n\r\n".format(
- mime, sz
- ).encode(
- "utf-8"
- )
+ file_ts = os.path.getmtime(fsenc(path))
+ file_dt = datetime.utcfromtimestamp(file_ts)
+ file_lastmod = file_dt.strftime("%a, %b %d %Y %H:%M:%S GMT")
- self.s.send(header)
+ do_send = True
+ if "if-modified-since" in self.headers:
+ cli_lastmod = self.headers["if-modified-since"]
+ try:
+ cli_dt = time.strptime(cli_lastmod, "%a, %b %d %Y %H:%M:%S GMT")
+ cli_ts = calendar.timegm(cli_dt)
+ do_send = int(file_ts) > int(cli_ts)
+ except:
+ self.log("bad lastmod format: {}".format(cli_lastmod))
+ do_send = file_lastmod != cli_lastmod
+
+ status = "200 OK"
+ if not do_send:
+ status = "304 Not Modified"
+
+ headers = [
+ "HTTP/1.1 " + status,
+ "Connection: Keep-Alive",
+ "Content-Type: " + mimetypes.guess_type(path)[0],
+ "Content-Length: " + str(os.path.getsize(fsenc(path))),
+ "Last-Modified: " + file_lastmod,
+ ]
+
+ headers = "\r\n".join(headers).encode("utf-8") + b"\r\n\r\n"
+ self.s.send(headers)
+
+ logmsg = "{:4} {} {}".format("", self.req, status)
+
+ if self.mode == "HEAD" or not do_send:
+ self.log(logmsg)
+ return True
with open(fsenc(path), "rb") as f:
while True:
@@ -333,6 +362,9 @@ class HttpCli(object):
except ConnectionResetError:
return False
+ self.log(logmsg)
+ return True
+
def tx_mounts(self):
html = self.conn.tpl_mounts.render(this=self)
self.reply(html.encode("utf-8"))