copyparty 1.16.5__py3-none-any.whl → 1.16.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/httpcli.py CHANGED
@@ -474,8 +474,8 @@ class HttpCli(object):
474
474
  if vpath.startswith(self.args.R):
475
475
  vpath = vpath[len(self.args.R) + 1 :]
476
476
  else:
477
- t = "incorrect --rp-loc or webserver config; expected vpath starting with [{}] but got [{}]"
478
- self.log(t.format(self.args.R, vpath), 1)
477
+ t = "incorrect --rp-loc or webserver config; expected vpath starting with %r but got %r"
478
+ self.log(t % (self.args.R, vpath), 1)
479
479
 
480
480
  self.ouparam = uparam.copy()
481
481
 
@@ -513,7 +513,7 @@ class HttpCli(object):
513
513
  return self.tx_qr()
514
514
 
515
515
  if relchk(self.vpath) and (self.vpath != "*" or self.mode != "OPTIONS"):
516
- self.log("invalid relpath [{}]".format(self.vpath))
516
+ self.log("invalid relpath %r" % ("/" + self.vpath,))
517
517
  self.cbonk(self.conn.hsrv.gmal, self.req, "bad_vp", "invalid relpaths")
518
518
  return self.tx_404() and self.keepalive
519
519
 
@@ -593,7 +593,7 @@ class HttpCli(object):
593
593
  self.uname = idp_usr
594
594
  self.html_head += "<script>var is_idp=1</script>\n"
595
595
  else:
596
- self.log("unknown username: [%s]" % (idp_usr), 1)
596
+ self.log("unknown username: %r" % (idp_usr,), 1)
597
597
 
598
598
  if self.args.ipu and self.uname == "*":
599
599
  self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
@@ -658,7 +658,7 @@ class HttpCli(object):
658
658
  origin = self.headers.get("origin", "<?>")
659
659
  proto = "https://" if self.is_https else "http://"
660
660
  guess = "modifying" if (origin and host) else "stripping"
661
- t = "cors-reject %s because request-header Origin='%s' does not match request-protocol '%s' and host '%s' based on request-header Host='%s' (note: if this request is not malicious, check if your reverse-proxy is accidentally %s request headers, in particular 'Origin', for example by running copyparty with --ihead='*' to show all request headers)"
661
+ t = "cors-reject %s because request-header Origin=%r does not match request-protocol %r and host %r based on request-header Host=%r (note: if this request is not malicious, check if your reverse-proxy is accidentally %s request headers, in particular 'Origin', for example by running copyparty with --ihead='*' to show all request headers)"
662
662
  self.log(t % (self.mode, origin, proto, self.host, host, guess), 3)
663
663
  raise Pebkac(403, "rejected by cors-check")
664
664
 
@@ -704,7 +704,7 @@ class HttpCli(object):
704
704
 
705
705
  if pex.code != 404 or self.do_log:
706
706
  self.log(
707
- "http%d: %s\033[0m, %s" % (pex.code, msg, self.vpath),
707
+ "http%d: %s\033[0m, %r" % (pex.code, msg, "/" + self.vpath),
708
708
  6 if em.startswith("client d/c ") else 3,
709
709
  )
710
710
 
@@ -1113,6 +1113,8 @@ class HttpCli(object):
1113
1113
  logmsg += " [\033[36m" + rval + "\033[0m]"
1114
1114
 
1115
1115
  self.log(logmsg)
1116
+ if "%" in self.req:
1117
+ self.log(" `-- %r" % (self.vpath,))
1116
1118
 
1117
1119
  # "embedded" resources
1118
1120
  if self.vpath.startswith(".cpr"):
@@ -1147,8 +1149,8 @@ class HttpCli(object):
1147
1149
  return self.tx_res(res_path)
1148
1150
 
1149
1151
  if res_path != undot(res_path):
1150
- t = "malicious user; attempted path traversal [{}] => [{}]"
1151
- self.log(t.format(self.vpath, res_path), 1)
1152
+ t = "malicious user; attempted path traversal %r => %r"
1153
+ self.log(t % ("/" + self.vpath, res_path), 1)
1152
1154
  self.cbonk(self.conn.hsrv.gmal, self.req, "trav", "path traversal")
1153
1155
 
1154
1156
  self.tx_404()
@@ -1159,11 +1161,11 @@ class HttpCli(object):
1159
1161
  return True
1160
1162
 
1161
1163
  if not self.can_read and not self.can_write and not self.can_get:
1162
- t = "@{} has no access to [{}]"
1164
+ t = "@%s has no access to %r"
1163
1165
 
1164
1166
  if "on403" in self.vn.flags:
1165
1167
  t += " (on403)"
1166
- self.log(t.format(self.uname, self.vpath))
1168
+ self.log(t % (self.uname, "/" + self.vpath))
1167
1169
  ret = self.on40x(self.vn.flags["on403"], self.vn, self.rem)
1168
1170
  if ret == "true":
1169
1171
  return True
@@ -1182,7 +1184,7 @@ class HttpCli(object):
1182
1184
  if self.vpath:
1183
1185
  ptn = self.args.nonsus_urls
1184
1186
  if not ptn or not ptn.search(self.vpath):
1185
- self.log(t.format(self.uname, self.vpath))
1187
+ self.log(t % (self.uname, "/" + self.vpath))
1186
1188
 
1187
1189
  return self.tx_404(True)
1188
1190
 
@@ -1226,6 +1228,9 @@ class HttpCli(object):
1226
1228
  if "dls" in self.uparam:
1227
1229
  return self.tx_dls()
1228
1230
 
1231
+ if "ru" in self.uparam:
1232
+ return self.tx_rups()
1233
+
1229
1234
  if "h" in self.uparam:
1230
1235
  return self.tx_mounts()
1231
1236
 
@@ -1376,6 +1381,8 @@ class HttpCli(object):
1376
1381
  def handle_propfind(self) :
1377
1382
  if self.do_log:
1378
1383
  self.log("PFIND %s @%s" % (self.req, self.uname))
1384
+ if "%" in self.req:
1385
+ self.log(" `-- %r" % (self.vpath,))
1379
1386
 
1380
1387
  if self.args.no_dav:
1381
1388
  raise Pebkac(405, "WebDAV is disabled in server config")
@@ -1426,14 +1433,14 @@ class HttpCli(object):
1426
1433
  if depth == "infinity":
1427
1434
  # allow depth:0 from unmapped root, but require read-axs otherwise
1428
1435
  if not self.can_read and (self.vpath or self.asrv.vfs.realpath):
1429
- t = "depth:infinity requires read-access in /%s"
1430
- t = t % (self.vpath,)
1436
+ t = "depth:infinity requires read-access in %r"
1437
+ t = t % ("/" + self.vpath,)
1431
1438
  self.log(t, 3)
1432
1439
  raise Pebkac(401, t)
1433
1440
 
1434
1441
  if not stat.S_ISDIR(topdir["st"].st_mode):
1435
- t = "depth:infinity can only be used on folders; /%s is 0o%o"
1436
- t = t % (self.vpath, topdir["st"])
1442
+ t = "depth:infinity can only be used on folders; %r is 0o%o"
1443
+ t = t % ("/" + self.vpath, topdir["st"])
1437
1444
  self.log(t, 3)
1438
1445
  raise Pebkac(400, t)
1439
1446
 
@@ -1459,7 +1466,7 @@ class HttpCli(object):
1459
1466
  elif depth == "0" or not stat.S_ISDIR(st.st_mode):
1460
1467
  # propfind on a file; return as topdir
1461
1468
  if not self.can_read and not self.can_get:
1462
- self.log("inaccessible: [%s]" % (self.vpath,))
1469
+ self.log("inaccessible: %r" % ("/" + self.vpath,))
1463
1470
  raise Pebkac(401, "authenticate")
1464
1471
 
1465
1472
  elif depth == "1":
@@ -1486,7 +1493,7 @@ class HttpCli(object):
1486
1493
  raise Pebkac(412, t.format(depth, t2))
1487
1494
 
1488
1495
  if not self.can_read and not self.can_write and not fgen:
1489
- self.log("inaccessible: [%s]" % (self.vpath,))
1496
+ self.log("inaccessible: %r" % ("/" + self.vpath,))
1490
1497
  raise Pebkac(401, "authenticate")
1491
1498
 
1492
1499
  fgen = itertools.chain([topdir], fgen)
@@ -1557,12 +1564,14 @@ class HttpCli(object):
1557
1564
  def handle_proppatch(self) :
1558
1565
  if self.do_log:
1559
1566
  self.log("PPATCH %s @%s" % (self.req, self.uname))
1567
+ if "%" in self.req:
1568
+ self.log(" `-- %r" % (self.vpath,))
1560
1569
 
1561
1570
  if self.args.no_dav:
1562
1571
  raise Pebkac(405, "WebDAV is disabled in server config")
1563
1572
 
1564
1573
  if not self.can_write:
1565
- self.log("{} tried to proppatch [{}]".format(self.uname, self.vpath))
1574
+ self.log("%s tried to proppatch %r" % (self.uname, "/" + self.vpath))
1566
1575
  raise Pebkac(401, "authenticate")
1567
1576
 
1568
1577
  from xml.etree import ElementTree as ET
@@ -1609,13 +1618,15 @@ class HttpCli(object):
1609
1618
  def handle_lock(self) :
1610
1619
  if self.do_log:
1611
1620
  self.log("LOCK %s @%s" % (self.req, self.uname))
1621
+ if "%" in self.req:
1622
+ self.log(" `-- %r" % (self.vpath,))
1612
1623
 
1613
1624
  if self.args.no_dav:
1614
1625
  raise Pebkac(405, "WebDAV is disabled in server config")
1615
1626
 
1616
1627
  # win7+ deadlocks if we say no; just smile and nod
1617
1628
  if not self.can_write and "Microsoft-WebDAV" not in self.ua:
1618
- self.log("{} tried to lock [{}]".format(self.uname, self.vpath))
1629
+ self.log("%s tried to lock %r" % (self.uname, "/" + self.vpath))
1619
1630
  raise Pebkac(401, "authenticate")
1620
1631
 
1621
1632
  from xml.etree import ElementTree as ET
@@ -1674,12 +1685,14 @@ class HttpCli(object):
1674
1685
  def handle_unlock(self) :
1675
1686
  if self.do_log:
1676
1687
  self.log("UNLOCK %s @%s" % (self.req, self.uname))
1688
+ if "%" in self.req:
1689
+ self.log(" `-- %r" % (self.vpath,))
1677
1690
 
1678
1691
  if self.args.no_dav:
1679
1692
  raise Pebkac(405, "WebDAV is disabled in server config")
1680
1693
 
1681
1694
  if not self.can_write and "Microsoft-WebDAV" not in self.ua:
1682
- self.log("{} tried to lock [{}]".format(self.uname, self.vpath))
1695
+ self.log("%s tried to lock %r" % (self.uname, "/" + self.vpath))
1683
1696
  raise Pebkac(401, "authenticate")
1684
1697
 
1685
1698
  self.send_headers(None, 204)
@@ -1691,6 +1704,8 @@ class HttpCli(object):
1691
1704
 
1692
1705
  if self.do_log:
1693
1706
  self.log("MKCOL %s @%s" % (self.req, self.uname))
1707
+ if "%" in self.req:
1708
+ self.log(" `-- %r" % (self.vpath,))
1694
1709
 
1695
1710
  try:
1696
1711
  return self._mkdir(self.vpath, True)
@@ -1742,6 +1757,8 @@ class HttpCli(object):
1742
1757
  def handle_options(self) :
1743
1758
  if self.do_log:
1744
1759
  self.log("OPTIONS %s @%s" % (self.req, self.uname))
1760
+ if "%" in self.req:
1761
+ self.log(" `-- %r" % (self.vpath,))
1745
1762
 
1746
1763
  oh = self.out_headers
1747
1764
  oh["Allow"] = ", ".join(self.conn.hsrv.mallow)
@@ -1757,10 +1774,14 @@ class HttpCli(object):
1757
1774
 
1758
1775
  def handle_delete(self) :
1759
1776
  self.log("DELETE %s @%s" % (self.req, self.uname))
1777
+ if "%" in self.req:
1778
+ self.log(" `-- %r" % (self.vpath,))
1760
1779
  return self.handle_rm([])
1761
1780
 
1762
1781
  def handle_put(self) :
1763
- self.log("PUT %s @%s" % (self.req, self.uname))
1782
+ self.log("PUT %s @%s" % (self.req, self.uname))
1783
+ if "%" in self.req:
1784
+ self.log(" `-- %r" % (self.vpath,))
1764
1785
 
1765
1786
  if not self.can_write:
1766
1787
  t = "user %s does not have write-access under /%s"
@@ -1779,6 +1800,8 @@ class HttpCli(object):
1779
1800
 
1780
1801
  def handle_post(self) :
1781
1802
  self.log("POST %s @%s" % (self.req, self.uname))
1803
+ if "%" in self.req:
1804
+ self.log(" `-- %r" % (self.vpath,))
1782
1805
 
1783
1806
  if self.headers.get("expect", "").lower() == "100-continue":
1784
1807
  try:
@@ -1823,7 +1846,7 @@ class HttpCli(object):
1823
1846
 
1824
1847
  if "save" in opt:
1825
1848
  post_sz, _, _, _, path, _ = self.dump_to_file(False)
1826
- self.log("urlform: {} bytes, {}".format(post_sz, path))
1849
+ self.log("urlform: %d bytes, %r" % (post_sz, path))
1827
1850
  elif "print" in opt:
1828
1851
  reader, _ = self.get_body_reader()
1829
1852
  buf = b""
@@ -1834,8 +1857,8 @@ class HttpCli(object):
1834
1857
 
1835
1858
  if buf:
1836
1859
  orig = buf.decode("utf-8", "replace")
1837
- t = "urlform_raw {} @ {}\n {}\n"
1838
- self.log(t.format(len(orig), self.vpath, orig))
1860
+ t = "urlform_raw %d @ %r\n %r\n"
1861
+ self.log(t % (len(orig), "/" + self.vpath, orig))
1839
1862
  try:
1840
1863
  zb = unquote(buf.replace(b"+", b" "))
1841
1864
  plain = zb.decode("utf-8", "replace")
@@ -1861,8 +1884,8 @@ class HttpCli(object):
1861
1884
  plain,
1862
1885
  )
1863
1886
 
1864
- t = "urlform_dec {} @ {}\n {}\n"
1865
- self.log(t.format(len(plain), self.vpath, plain))
1887
+ t = "urlform_dec %d @ %r\n %r\n"
1888
+ self.log(t % (len(plain), "/" + self.vpath, plain))
1866
1889
 
1867
1890
  except Exception as ex:
1868
1891
  self.log(repr(ex))
@@ -2110,7 +2133,7 @@ class HttpCli(object):
2110
2133
  try:
2111
2134
  ext = self.conn.hsrv.magician.ext(path)
2112
2135
  except Exception as ex:
2113
- self.log("filetype detection failed for [{}]: {}".format(path, ex), 6)
2136
+ self.log("filetype detection failed for %r: %s" % (path, ex), 6)
2114
2137
  ext = None
2115
2138
 
2116
2139
  if ext:
@@ -2210,8 +2233,8 @@ class HttpCli(object):
2210
2233
  def handle_stash(self, is_put ) :
2211
2234
  post_sz, sha_hex, sha_b64, remains, path, url = self.dump_to_file(is_put)
2212
2235
  spd = self._spd(post_sz)
2213
- t = "{} wrote {}/{} bytes to {} # {}"
2214
- self.log(t.format(spd, post_sz, remains, path, sha_b64[:28])) # 21
2236
+ t = "%s wrote %d/%d bytes to %r # %s"
2237
+ self.log(t % (spd, post_sz, remains, path, sha_b64[:28])) # 21
2215
2238
 
2216
2239
  ac = self.uparam.get(
2217
2240
  "want", self.headers.get("accept", "").lower().split(";")[-1]
@@ -2241,7 +2264,7 @@ class HttpCli(object):
2241
2264
  flags ,
2242
2265
  ) :
2243
2266
  now = time.time()
2244
- t = "bad-chunk: %.3f %s %s %d %s %s %s"
2267
+ t = "bad-chunk: %.3f %s %s %d %s %s %r"
2245
2268
  t = t % (now, bad_sha, good_sha, ofs, self.ip, self.uname, ap)
2246
2269
  self.log(t, 5)
2247
2270
 
@@ -2380,7 +2403,7 @@ class HttpCli(object):
2380
2403
  body = json.loads(json_buf.decode(enc, "replace"))
2381
2404
  try:
2382
2405
  zds = {k: v for k, v in body.items()}
2383
- zds["hash"] = "%d chunks" % (len(body["hash"]))
2406
+ zds["hash"] = "%d chunks" % (len(body["hash"]),)
2384
2407
  except:
2385
2408
  zds = body
2386
2409
  t = "POST len=%d type=%s ip=%s user=%s req=%r json=%s"
@@ -2424,7 +2447,7 @@ class HttpCli(object):
2424
2447
  if not bos.path.isdir(dst):
2425
2448
  bos.makedirs(dst)
2426
2449
  except OSError as ex:
2427
- self.log("makedirs failed [{}]".format(dst))
2450
+ self.log("makedirs failed %r" % (dst,))
2428
2451
  if not bos.path.isdir(dst):
2429
2452
  if ex.errno == errno.EACCES:
2430
2453
  raise Pebkac(500, "the server OS denied write-access")
@@ -2449,7 +2472,7 @@ class HttpCli(object):
2449
2472
  # strip common suffix (uploader's folder structure)
2450
2473
  vp_req, vp_vfs = vroots(self.vpath, vjoin(dbv.vpath, vrem))
2451
2474
  if not ret["purl"].startswith(vp_vfs):
2452
- t = "share-mapping failed; req=[%s] dbv=[%s] vrem=[%s] n1=[%s] n2=[%s] purl=[%s]"
2475
+ t = "share-mapping failed; req=%r dbv=%r vrem=%r n1=%r n2=%r purl=%r"
2453
2476
  zt = (self.vpath, dbv.vpath, vrem, vp_req, vp_vfs, ret["purl"])
2454
2477
  raise Pebkac(500, t % zt)
2455
2478
  ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :]
@@ -2498,13 +2521,13 @@ class HttpCli(object):
2498
2521
  # search by query params
2499
2522
  q = body["q"]
2500
2523
  n = body.get("n", self.args.srch_hits)
2501
- self.log("qj: {} |{}|".format(q, n))
2524
+ self.log("qj: %r |%d|" % (q, n))
2502
2525
  hits, taglist, trunc = idx.search(self.uname, vols, q, n)
2503
2526
  msg = len(hits)
2504
2527
 
2505
2528
  idx.p_end = time.time()
2506
2529
  idx.p_dur = idx.p_end - t0
2507
- self.log("q#: {} ({:.2f}s)".format(msg, idx.p_dur))
2530
+ self.log("q#: %r (%.2fs)" % (msg, idx.p_dur))
2508
2531
 
2509
2532
  order = []
2510
2533
  for t in self.args.mte:
@@ -2615,7 +2638,7 @@ class HttpCli(object):
2615
2638
  t = "your client is sending %d bytes which is too much (server expected %d bytes at most)"
2616
2639
  raise Pebkac(400, t % (remains, maxsize))
2617
2640
 
2618
- t = "writing %s %s+%d #%d+%d %s"
2641
+ t = "writing %r %s+%d #%d+%d %s"
2619
2642
  chunkno = cstart0[0] // chunksize
2620
2643
  zs = " ".join([chashes[0][:15]] + [x[:9] for x in chashes[1:]])
2621
2644
  self.log(t % (path, cstart0, remains, chunkno, len(chashes), zs))
@@ -2727,7 +2750,7 @@ class HttpCli(object):
2727
2750
  cinf = self.headers.get("x-up2k-stat", "")
2728
2751
 
2729
2752
  spd = self._spd(postsize)
2730
- self.log("{:70} thank {}".format(spd, cinf))
2753
+ self.log("%70s thank %r" % (spd, cinf))
2731
2754
  self.reply(b"thank")
2732
2755
  return True
2733
2756
 
@@ -2806,7 +2829,7 @@ class HttpCli(object):
2806
2829
  logpwd = "%" + ub64enc(zb[:12]).decode("ascii")
2807
2830
 
2808
2831
  if pwd != "x":
2809
- self.log("invalid password: {}".format(logpwd), 3)
2832
+ self.log("invalid password: %r" % (logpwd,), 3)
2810
2833
  self.cbonk(self.conn.hsrv.gpwd, pwd, "pw", "invalid passwords")
2811
2834
 
2812
2835
  msg = "naw dude"
@@ -2841,7 +2864,7 @@ class HttpCli(object):
2841
2864
  rem = sanitize_vpath(rem, "/")
2842
2865
  fn = vfs.canonical(rem)
2843
2866
  if not fn.startswith(vfs.realpath):
2844
- self.log("invalid mkdir [%s] [%s]" % (self.gctx, vpath), 1)
2867
+ self.log("invalid mkdir %r %r" % (self.gctx, vpath), 1)
2845
2868
  raise Pebkac(422)
2846
2869
 
2847
2870
  if not nullwrite:
@@ -3006,9 +3029,9 @@ class HttpCli(object):
3006
3029
  elif bos.path.exists(abspath):
3007
3030
  try:
3008
3031
  wunlink(self.log, abspath, vfs.flags)
3009
- t = "overwriting file with new upload: %s"
3032
+ t = "overwriting file with new upload: %r"
3010
3033
  except:
3011
- t = "toctou while deleting for ?replace: %s"
3034
+ t = "toctou while deleting for ?replace: %r"
3012
3035
  self.log(t % (abspath,))
3013
3036
  else:
3014
3037
  open_args = {}
@@ -3091,7 +3114,7 @@ class HttpCli(object):
3091
3114
  f, tnam = ren_open(tnam, "wb", self.args.iobuf, **open_args)
3092
3115
  try:
3093
3116
  tabspath = os.path.join(fdir, tnam)
3094
- self.log("writing to {}".format(tabspath))
3117
+ self.log("writing to %r" % (tabspath,))
3095
3118
  sz, sha_hex, sha_b64 = copier(
3096
3119
  p_data, f, hasher, max_sz, self.args.s_wr_slp
3097
3120
  )
@@ -3276,7 +3299,7 @@ class HttpCli(object):
3276
3299
  jmsg["files"].append(jpart)
3277
3300
 
3278
3301
  vspd = self._spd(sz_total, False)
3279
- self.log("{} {}".format(vspd, msg))
3302
+ self.log("%s %r" % (vspd, msg))
3280
3303
 
3281
3304
  suf = ""
3282
3305
  if not nullwrite and self.args.write_uplog:
@@ -3537,7 +3560,7 @@ class HttpCli(object):
3537
3560
  if req == zs:
3538
3561
  return True
3539
3562
 
3540
- t = "wrong dirkey, want %s, got %s\n vp: %s\n ap: %s"
3563
+ t = "wrong dirkey, want %s, got %s\n vp: %r\n ap: %r"
3541
3564
  self.log(t % (zs, req, self.req, ap), 6)
3542
3565
  return False
3543
3566
 
@@ -3565,7 +3588,7 @@ class HttpCli(object):
3565
3588
  if req == zs:
3566
3589
  return True
3567
3590
 
3568
- t = "wrong filekey, want %s, got %s\n vp: %s\n ap: %s"
3591
+ t = "wrong filekey, want %s, got %s\n vp: %r\n ap: %r"
3569
3592
  self.log(t % (zs, req, self.req, ap), 6)
3570
3593
  return False
3571
3594
 
@@ -3627,7 +3650,7 @@ class HttpCli(object):
3627
3650
  elif ph == "srv.htime":
3628
3651
  sv = datetime.now(UTC).strftime("%Y-%m-%d, %H:%M:%S")
3629
3652
  else:
3630
- self.log("unknown placeholder in server config: [%s]" % (ph), 3)
3653
+ self.log("unknown placeholder in server config: [%s]" % (ph,), 3)
3631
3654
  continue
3632
3655
 
3633
3656
  sv = self.conn.hsrv.ptn_hsafe.sub("_", sv)
@@ -3784,7 +3807,7 @@ class HttpCli(object):
3784
3807
  self.pipes.set(req_path, job)
3785
3808
  except Exception as ex:
3786
3809
  if getattr(ex, "errno", 0) != errno.ENOENT:
3787
- self.log("will not pipe [%s]; %s" % (ap_data, ex), 6)
3810
+ self.log("will not pipe %r; %s" % (ap_data, ex), 6)
3788
3811
  ptop = None
3789
3812
 
3790
3813
  #
@@ -4072,7 +4095,7 @@ class HttpCli(object):
4072
4095
  if lower >= data_end:
4073
4096
  if data_end:
4074
4097
  t = "pipe: uploader is too slow; aborting download at %.2f MiB"
4075
- self.log(t % (data_end / M))
4098
+ self.log(t % (data_end / M,))
4076
4099
  raise Pebkac(416, "uploader is too slow")
4077
4100
 
4078
4101
  raise Pebkac(416, "no data available yet; please retry in a bit")
@@ -4216,7 +4239,7 @@ class HttpCli(object):
4216
4239
 
4217
4240
  cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}"
4218
4241
  cdis = cdis.format(afn, ext, ufn, ext)
4219
- self.log(cdis)
4242
+ self.log(repr(cdis))
4220
4243
  self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis})
4221
4244
 
4222
4245
  fgen = vn.zipgen(
@@ -4878,9 +4901,9 @@ class HttpCli(object):
4878
4901
  raise Pebkac(500, "sqlite3 not found on server; unpost is disabled")
4879
4902
  raise Pebkac(500, "server busy, cannot unpost; please retry in a bit")
4880
4903
 
4881
- filt = self.uparam.get("filter") or ""
4882
- lm = "ups [{}]".format(filt)
4883
- self.log(lm)
4904
+ zs = self.uparam.get("filter") or ""
4905
+ filt = re.compile(zs, re.I) if zs else None
4906
+ lm = "ups %r" % (zs,)
4884
4907
 
4885
4908
  if self.args.shr and self.vpath.startswith(self.args.shr1):
4886
4909
  shr_dbv, shr_vrem = self.vn.get_dbv(self.rem)
@@ -4921,13 +4944,18 @@ class HttpCli(object):
4921
4944
 
4922
4945
  nfk, fk_alg = fk_vols.get(vol) or (0, 0)
4923
4946
 
4924
- q = "select sz, rd, fn, at from up where ip=? and at>?"
4947
+ n = 2000
4948
+ q = "select sz, rd, fn, at from up where ip=? and at>? order by at desc"
4925
4949
  for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
4926
4950
  vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
4927
- if filt and filt not in vp:
4951
+ if filt and not filt.search(vp):
4928
4952
  continue
4929
4953
 
4930
- rv = {"vp": quotep(vp), "sz": sz, "at": at, "nfk": nfk}
4954
+ n -= 1
4955
+ if not n:
4956
+ break
4957
+
4958
+ rv = {"vp": vp, "sz": sz, "at": at, "nfk": nfk}
4931
4959
  if nfk:
4932
4960
  rv["ap"] = vol.canonical(vjoin(rd, fn))
4933
4961
  rv["fk_alg"] = fk_alg
@@ -4937,9 +4965,13 @@ class HttpCli(object):
4937
4965
  ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
4938
4966
  ret = ret[:2000]
4939
4967
 
4968
+ if len(ret) > 2000:
4969
+ ret = ret[:2000]
4970
+
4940
4971
  ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
4941
- n = 0
4942
- for rv in ret[:11000]:
4972
+
4973
+ for rv in ret:
4974
+ rv["vp"] = quotep(rv["vp"])
4943
4975
  nfk = rv.pop("nfk")
4944
4976
  if not nfk:
4945
4977
  continue
@@ -4956,12 +4988,6 @@ class HttpCli(object):
4956
4988
  )
4957
4989
  rv["vp"] += "?k=" + fk[:nfk]
4958
4990
 
4959
- n += 1
4960
- if n > 2000:
4961
- break
4962
-
4963
- ret = ret[:2000]
4964
-
4965
4991
  if shr_dbv:
4966
4992
  # translate vpaths from share-target to share-url
4967
4993
  # to satisfy access checks
@@ -4984,6 +5010,125 @@ class HttpCli(object):
4984
5010
  self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
4985
5011
  return True
4986
5012
 
5013
+ def tx_rups(self) :
5014
+ if self.args.no_ups_page:
5015
+ raise Pebkac(500, "listing of recent uploads is disabled in server config")
5016
+
5017
+ idx = self.conn.get_u2idx()
5018
+ if not idx or not hasattr(idx, "p_end"):
5019
+ if not HAVE_SQLITE3:
5020
+ raise Pebkac(500, "sqlite3 not found on server; recent-uploads n/a")
5021
+ raise Pebkac(500, "server busy, cannot list recent uploads; please retry")
5022
+
5023
+ sfilt = self.uparam.get("filter") or ""
5024
+ filt = re.compile(sfilt, re.I) if sfilt else None
5025
+ lm = "ru %r" % (sfilt,)
5026
+ self.log(lm)
5027
+
5028
+ ret = []
5029
+ t0 = time.time()
5030
+ allvols = [
5031
+ x
5032
+ for x in self.asrv.vfs.all_vols.values()
5033
+ if "e2d" in x.flags and ("*" in x.axs.uread or self.uname in x.axs.uread)
5034
+ ]
5035
+ fk_vols = {
5036
+ vol: (vol.flags["fk"], 2 if "fka" in vol.flags else 1)
5037
+ for vol in allvols
5038
+ if "fk" in vol.flags and "*" not in vol.axs.uread
5039
+ }
5040
+
5041
+ for vol in allvols:
5042
+ cur = idx.get_cur(vol)
5043
+ if not cur:
5044
+ continue
5045
+
5046
+ nfk, fk_alg = fk_vols.get(vol) or (0, 0)
5047
+ adm = "*" in vol.axs.uadmin or self.uname in vol.axs.uadmin
5048
+ dots = "*" in vol.axs.udot or self.uname in vol.axs.udot
5049
+
5050
+ n = 1000
5051
+ q = "select sz, rd, fn, ip, at from up where at>0 order by at desc"
5052
+ for sz, rd, fn, ip, at in cur.execute(q):
5053
+ vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
5054
+ if filt and not filt.search(vp):
5055
+ continue
5056
+
5057
+ if not dots and "/." in vp:
5058
+ continue
5059
+
5060
+ n -= 1
5061
+ if not n:
5062
+ break
5063
+
5064
+ rv = {
5065
+ "vp": vp,
5066
+ "sz": sz,
5067
+ "ip": ip,
5068
+ "at": at,
5069
+ "nfk": nfk,
5070
+ "adm": adm,
5071
+ }
5072
+ if nfk:
5073
+ rv["ap"] = vol.canonical(vjoin(rd, fn))
5074
+ rv["fk_alg"] = fk_alg
5075
+
5076
+ ret.append(rv)
5077
+ if len(ret) > 2000:
5078
+ ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
5079
+ ret = ret[:1000]
5080
+
5081
+ if len(ret) > 1000:
5082
+ ret = ret[:1000]
5083
+
5084
+ ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
5085
+
5086
+ for rv in ret:
5087
+ rv["evp"] = quotep(rv["vp"])
5088
+ nfk = rv.pop("nfk")
5089
+ if not nfk:
5090
+ continue
5091
+
5092
+ alg = rv.pop("fk_alg")
5093
+ ap = rv.pop("ap")
5094
+ try:
5095
+ st = bos.stat(ap)
5096
+ except:
5097
+ continue
5098
+
5099
+ fk = self.gen_fk(
5100
+ alg, self.args.fk_salt, ap, st.st_size, 0 if ANYWIN else st.st_ino
5101
+ )
5102
+ rv["vp"] += "?k=" + fk[:nfk]
5103
+
5104
+ if self.args.ups_when:
5105
+ for rv in ret:
5106
+ adm = rv.pop("adm")
5107
+ if not adm:
5108
+ rv["ip"] = "(You)" if rv["ip"] == self.ip else "(?)"
5109
+ else:
5110
+ for rv in ret:
5111
+ adm = rv.pop("adm")
5112
+ if not adm:
5113
+ rv["ip"] = "(You)" if rv["ip"] == self.ip else "(?)"
5114
+ rv["at"] = 0
5115
+
5116
+ if self.is_vproxied:
5117
+ for v in ret:
5118
+ v["vp"] = self.args.SR + v["vp"]
5119
+
5120
+ self.log("%s #%d %.2fsec" % (lm, len(ret), time.time() - t0))
5121
+
5122
+ if "j" in self.ouparam:
5123
+ jtxt = json.dumps(ret, separators=(",\n", ": "))
5124
+ self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
5125
+ return True
5126
+
5127
+ rows = [[x["vp"], x["evp"], x["sz"], x["ip"], x["at"]] for x in ret]
5128
+ html = self.j2s("rups", this=self, rows=rows, filt=sfilt, now=int(time.time()))
5129
+ self.reply(html.encode("utf-8"), status=200)
5130
+ return True
5131
+
4987
5132
  def tx_shares(self) :
4988
5133
  if self.uname == "*":
4989
5134
  self.loud_reply("you're not logged in")
@@ -5689,7 +5834,7 @@ class HttpCli(object):
5689
5834
  linf = stats.get(fn) or bos.lstat(fspath)
5690
5835
  inf = bos.stat(fspath) if stat.S_ISLNK(linf.st_mode) else linf
5691
5836
  except:
5692
- self.log("broken symlink: {}".format(repr(fspath)))
5837
+ self.log("broken symlink: %r" % (fspath,))
5693
5838
  continue
5694
5839
 
5695
5840
  is_dir = stat.S_ISDIR(inf.st_mode)
@@ -5803,8 +5948,7 @@ class HttpCli(object):
5803
5948
  erd_efn = s3enc(idx.mem_cur, rd, fn)
5804
5949
  r = icur.execute(q, erd_efn)
5805
5950
  except:
5806
- t = "tag read error, {}/{}\n{}"
5807
- self.log(t.format(rd, fn, min_ex()))
5951
+ self.log("tag read error, %r / %r\n%s" % (rd, fn, min_ex()))
5808
5952
  break
5809
5953
 
5810
5954
  tags = {k: v for k, v in r}
@@ -5924,10 +6068,10 @@ class HttpCli(object):
5924
6068
  if doc.lower().endswith(".md") and "exp" in vn.flags:
5925
6069
  doctxt = self._expand(doctxt, vn.flags.get("exp_md") or [])
5926
6070
  else:
5927
- self.log("doc 2big: [{}]".format(doc), c=6)
6071
+ self.log("doc 2big: %r" % (doc,), 6)
5928
6072
  doctxt = "( size of textfile exceeds serverside limit )"
5929
6073
  else:
5930
- self.log("doc 404: [{}]".format(doc), c=6)
6074
+ self.log("doc 404: %r" % (doc,), 6)
5931
6075
  doctxt = "( textfile not found )"
5932
6076
 
5933
6077
  if doctxt is not None: