copyparty 1.16.4__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
 
@@ -537,8 +537,14 @@ class HttpCli(object):
537
537
  except:
538
538
  pass
539
539
 
540
+ self.pw = uparam.get("pw") or self.headers.get("pw") or bauth or cookie_pw
541
+ self.uname = (
542
+ self.asrv.sesa.get(self.pw)
543
+ or self.asrv.iacct.get(self.asrv.ah.hash(self.pw))
544
+ or "*"
545
+ )
546
+
540
547
  if self.args.idp_h_usr:
541
- self.pw = ""
542
548
  idp_usr = self.headers.get(self.args.idp_h_usr) or ""
543
549
  if idp_usr:
544
550
  idp_grp = (
@@ -583,20 +589,11 @@ class HttpCli(object):
583
589
  idp_grp = ""
584
590
 
585
591
  if idp_usr in self.asrv.vfs.aread:
592
+ self.pw = ""
586
593
  self.uname = idp_usr
587
594
  self.html_head += "<script>var is_idp=1</script>\n"
588
595
  else:
589
- self.log("unknown username: [%s]" % (idp_usr), 1)
590
- self.uname = "*"
591
- else:
592
- self.uname = "*"
593
- else:
594
- self.pw = uparam.get("pw") or self.headers.get("pw") or bauth or cookie_pw
595
- self.uname = (
596
- self.asrv.sesa.get(self.pw)
597
- or self.asrv.iacct.get(self.asrv.ah.hash(self.pw))
598
- or "*"
599
- )
596
+ self.log("unknown username: %r" % (idp_usr,), 1)
600
597
 
601
598
  if self.args.ipu and self.uname == "*":
602
599
  self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
@@ -661,7 +658,7 @@ class HttpCli(object):
661
658
  origin = self.headers.get("origin", "<?>")
662
659
  proto = "https://" if self.is_https else "http://"
663
660
  guess = "modifying" if (origin and host) else "stripping"
664
- 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)"
665
662
  self.log(t % (self.mode, origin, proto, self.host, host, guess), 3)
666
663
  raise Pebkac(403, "rejected by cors-check")
667
664
 
@@ -707,7 +704,7 @@ class HttpCli(object):
707
704
 
708
705
  if pex.code != 404 or self.do_log:
709
706
  self.log(
710
- "http%d: %s\033[0m, %s" % (pex.code, msg, self.vpath),
707
+ "http%d: %s\033[0m, %r" % (pex.code, msg, "/" + self.vpath),
711
708
  6 if em.startswith("client d/c ") else 3,
712
709
  )
713
710
 
@@ -1116,6 +1113,8 @@ class HttpCli(object):
1116
1113
  logmsg += " [\033[36m" + rval + "\033[0m]"
1117
1114
 
1118
1115
  self.log(logmsg)
1116
+ if "%" in self.req:
1117
+ self.log(" `-- %r" % (self.vpath,))
1119
1118
 
1120
1119
  # "embedded" resources
1121
1120
  if self.vpath.startswith(".cpr"):
@@ -1150,8 +1149,8 @@ class HttpCli(object):
1150
1149
  return self.tx_res(res_path)
1151
1150
 
1152
1151
  if res_path != undot(res_path):
1153
- t = "malicious user; attempted path traversal [{}] => [{}]"
1154
- 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)
1155
1154
  self.cbonk(self.conn.hsrv.gmal, self.req, "trav", "path traversal")
1156
1155
 
1157
1156
  self.tx_404()
@@ -1162,11 +1161,11 @@ class HttpCli(object):
1162
1161
  return True
1163
1162
 
1164
1163
  if not self.can_read and not self.can_write and not self.can_get:
1165
- t = "@{} has no access to [{}]"
1164
+ t = "@%s has no access to %r"
1166
1165
 
1167
1166
  if "on403" in self.vn.flags:
1168
1167
  t += " (on403)"
1169
- self.log(t.format(self.uname, self.vpath))
1168
+ self.log(t % (self.uname, "/" + self.vpath))
1170
1169
  ret = self.on40x(self.vn.flags["on403"], self.vn, self.rem)
1171
1170
  if ret == "true":
1172
1171
  return True
@@ -1185,7 +1184,7 @@ class HttpCli(object):
1185
1184
  if self.vpath:
1186
1185
  ptn = self.args.nonsus_urls
1187
1186
  if not ptn or not ptn.search(self.vpath):
1188
- self.log(t.format(self.uname, self.vpath))
1187
+ self.log(t % (self.uname, "/" + self.vpath))
1189
1188
 
1190
1189
  return self.tx_404(True)
1191
1190
 
@@ -1229,6 +1228,9 @@ class HttpCli(object):
1229
1228
  if "dls" in self.uparam:
1230
1229
  return self.tx_dls()
1231
1230
 
1231
+ if "ru" in self.uparam:
1232
+ return self.tx_rups()
1233
+
1232
1234
  if "h" in self.uparam:
1233
1235
  return self.tx_mounts()
1234
1236
 
@@ -1379,6 +1381,8 @@ class HttpCli(object):
1379
1381
  def handle_propfind(self) :
1380
1382
  if self.do_log:
1381
1383
  self.log("PFIND %s @%s" % (self.req, self.uname))
1384
+ if "%" in self.req:
1385
+ self.log(" `-- %r" % (self.vpath,))
1382
1386
 
1383
1387
  if self.args.no_dav:
1384
1388
  raise Pebkac(405, "WebDAV is disabled in server config")
@@ -1429,14 +1433,14 @@ class HttpCli(object):
1429
1433
  if depth == "infinity":
1430
1434
  # allow depth:0 from unmapped root, but require read-axs otherwise
1431
1435
  if not self.can_read and (self.vpath or self.asrv.vfs.realpath):
1432
- t = "depth:infinity requires read-access in /%s"
1433
- t = t % (self.vpath,)
1436
+ t = "depth:infinity requires read-access in %r"
1437
+ t = t % ("/" + self.vpath,)
1434
1438
  self.log(t, 3)
1435
1439
  raise Pebkac(401, t)
1436
1440
 
1437
1441
  if not stat.S_ISDIR(topdir["st"].st_mode):
1438
- t = "depth:infinity can only be used on folders; /%s is 0o%o"
1439
- 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"])
1440
1444
  self.log(t, 3)
1441
1445
  raise Pebkac(400, t)
1442
1446
 
@@ -1462,7 +1466,7 @@ class HttpCli(object):
1462
1466
  elif depth == "0" or not stat.S_ISDIR(st.st_mode):
1463
1467
  # propfind on a file; return as topdir
1464
1468
  if not self.can_read and not self.can_get:
1465
- self.log("inaccessible: [%s]" % (self.vpath,))
1469
+ self.log("inaccessible: %r" % ("/" + self.vpath,))
1466
1470
  raise Pebkac(401, "authenticate")
1467
1471
 
1468
1472
  elif depth == "1":
@@ -1489,7 +1493,7 @@ class HttpCli(object):
1489
1493
  raise Pebkac(412, t.format(depth, t2))
1490
1494
 
1491
1495
  if not self.can_read and not self.can_write and not fgen:
1492
- self.log("inaccessible: [%s]" % (self.vpath,))
1496
+ self.log("inaccessible: %r" % ("/" + self.vpath,))
1493
1497
  raise Pebkac(401, "authenticate")
1494
1498
 
1495
1499
  fgen = itertools.chain([topdir], fgen)
@@ -1560,12 +1564,14 @@ class HttpCli(object):
1560
1564
  def handle_proppatch(self) :
1561
1565
  if self.do_log:
1562
1566
  self.log("PPATCH %s @%s" % (self.req, self.uname))
1567
+ if "%" in self.req:
1568
+ self.log(" `-- %r" % (self.vpath,))
1563
1569
 
1564
1570
  if self.args.no_dav:
1565
1571
  raise Pebkac(405, "WebDAV is disabled in server config")
1566
1572
 
1567
1573
  if not self.can_write:
1568
- self.log("{} tried to proppatch [{}]".format(self.uname, self.vpath))
1574
+ self.log("%s tried to proppatch %r" % (self.uname, "/" + self.vpath))
1569
1575
  raise Pebkac(401, "authenticate")
1570
1576
 
1571
1577
  from xml.etree import ElementTree as ET
@@ -1612,13 +1618,15 @@ class HttpCli(object):
1612
1618
  def handle_lock(self) :
1613
1619
  if self.do_log:
1614
1620
  self.log("LOCK %s @%s" % (self.req, self.uname))
1621
+ if "%" in self.req:
1622
+ self.log(" `-- %r" % (self.vpath,))
1615
1623
 
1616
1624
  if self.args.no_dav:
1617
1625
  raise Pebkac(405, "WebDAV is disabled in server config")
1618
1626
 
1619
1627
  # win7+ deadlocks if we say no; just smile and nod
1620
1628
  if not self.can_write and "Microsoft-WebDAV" not in self.ua:
1621
- self.log("{} tried to lock [{}]".format(self.uname, self.vpath))
1629
+ self.log("%s tried to lock %r" % (self.uname, "/" + self.vpath))
1622
1630
  raise Pebkac(401, "authenticate")
1623
1631
 
1624
1632
  from xml.etree import ElementTree as ET
@@ -1677,12 +1685,14 @@ class HttpCli(object):
1677
1685
  def handle_unlock(self) :
1678
1686
  if self.do_log:
1679
1687
  self.log("UNLOCK %s @%s" % (self.req, self.uname))
1688
+ if "%" in self.req:
1689
+ self.log(" `-- %r" % (self.vpath,))
1680
1690
 
1681
1691
  if self.args.no_dav:
1682
1692
  raise Pebkac(405, "WebDAV is disabled in server config")
1683
1693
 
1684
1694
  if not self.can_write and "Microsoft-WebDAV" not in self.ua:
1685
- self.log("{} tried to lock [{}]".format(self.uname, self.vpath))
1695
+ self.log("%s tried to lock %r" % (self.uname, "/" + self.vpath))
1686
1696
  raise Pebkac(401, "authenticate")
1687
1697
 
1688
1698
  self.send_headers(None, 204)
@@ -1694,6 +1704,8 @@ class HttpCli(object):
1694
1704
 
1695
1705
  if self.do_log:
1696
1706
  self.log("MKCOL %s @%s" % (self.req, self.uname))
1707
+ if "%" in self.req:
1708
+ self.log(" `-- %r" % (self.vpath,))
1697
1709
 
1698
1710
  try:
1699
1711
  return self._mkdir(self.vpath, True)
@@ -1745,6 +1757,8 @@ class HttpCli(object):
1745
1757
  def handle_options(self) :
1746
1758
  if self.do_log:
1747
1759
  self.log("OPTIONS %s @%s" % (self.req, self.uname))
1760
+ if "%" in self.req:
1761
+ self.log(" `-- %r" % (self.vpath,))
1748
1762
 
1749
1763
  oh = self.out_headers
1750
1764
  oh["Allow"] = ", ".join(self.conn.hsrv.mallow)
@@ -1760,10 +1774,14 @@ class HttpCli(object):
1760
1774
 
1761
1775
  def handle_delete(self) :
1762
1776
  self.log("DELETE %s @%s" % (self.req, self.uname))
1777
+ if "%" in self.req:
1778
+ self.log(" `-- %r" % (self.vpath,))
1763
1779
  return self.handle_rm([])
1764
1780
 
1765
1781
  def handle_put(self) :
1766
- 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,))
1767
1785
 
1768
1786
  if not self.can_write:
1769
1787
  t = "user %s does not have write-access under /%s"
@@ -1782,6 +1800,8 @@ class HttpCli(object):
1782
1800
 
1783
1801
  def handle_post(self) :
1784
1802
  self.log("POST %s @%s" % (self.req, self.uname))
1803
+ if "%" in self.req:
1804
+ self.log(" `-- %r" % (self.vpath,))
1785
1805
 
1786
1806
  if self.headers.get("expect", "").lower() == "100-continue":
1787
1807
  try:
@@ -1826,7 +1846,7 @@ class HttpCli(object):
1826
1846
 
1827
1847
  if "save" in opt:
1828
1848
  post_sz, _, _, _, path, _ = self.dump_to_file(False)
1829
- self.log("urlform: {} bytes, {}".format(post_sz, path))
1849
+ self.log("urlform: %d bytes, %r" % (post_sz, path))
1830
1850
  elif "print" in opt:
1831
1851
  reader, _ = self.get_body_reader()
1832
1852
  buf = b""
@@ -1837,8 +1857,8 @@ class HttpCli(object):
1837
1857
 
1838
1858
  if buf:
1839
1859
  orig = buf.decode("utf-8", "replace")
1840
- t = "urlform_raw {} @ {}\n {}\n"
1841
- 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))
1842
1862
  try:
1843
1863
  zb = unquote(buf.replace(b"+", b" "))
1844
1864
  plain = zb.decode("utf-8", "replace")
@@ -1864,8 +1884,8 @@ class HttpCli(object):
1864
1884
  plain,
1865
1885
  )
1866
1886
 
1867
- t = "urlform_dec {} @ {}\n {}\n"
1868
- 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))
1869
1889
 
1870
1890
  except Exception as ex:
1871
1891
  self.log(repr(ex))
@@ -2113,7 +2133,7 @@ class HttpCli(object):
2113
2133
  try:
2114
2134
  ext = self.conn.hsrv.magician.ext(path)
2115
2135
  except Exception as ex:
2116
- self.log("filetype detection failed for [{}]: {}".format(path, ex), 6)
2136
+ self.log("filetype detection failed for %r: %s" % (path, ex), 6)
2117
2137
  ext = None
2118
2138
 
2119
2139
  if ext:
@@ -2213,8 +2233,8 @@ class HttpCli(object):
2213
2233
  def handle_stash(self, is_put ) :
2214
2234
  post_sz, sha_hex, sha_b64, remains, path, url = self.dump_to_file(is_put)
2215
2235
  spd = self._spd(post_sz)
2216
- t = "{} wrote {}/{} bytes to {} # {}"
2217
- 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
2218
2238
 
2219
2239
  ac = self.uparam.get(
2220
2240
  "want", self.headers.get("accept", "").lower().split(";")[-1]
@@ -2244,7 +2264,7 @@ class HttpCli(object):
2244
2264
  flags ,
2245
2265
  ) :
2246
2266
  now = time.time()
2247
- t = "bad-chunk: %.3f %s %s %d %s %s %s"
2267
+ t = "bad-chunk: %.3f %s %s %d %s %s %r"
2248
2268
  t = t % (now, bad_sha, good_sha, ofs, self.ip, self.uname, ap)
2249
2269
  self.log(t, 5)
2250
2270
 
@@ -2383,7 +2403,7 @@ class HttpCli(object):
2383
2403
  body = json.loads(json_buf.decode(enc, "replace"))
2384
2404
  try:
2385
2405
  zds = {k: v for k, v in body.items()}
2386
- zds["hash"] = "%d chunks" % (len(body["hash"]))
2406
+ zds["hash"] = "%d chunks" % (len(body["hash"]),)
2387
2407
  except:
2388
2408
  zds = body
2389
2409
  t = "POST len=%d type=%s ip=%s user=%s req=%r json=%s"
@@ -2427,7 +2447,7 @@ class HttpCli(object):
2427
2447
  if not bos.path.isdir(dst):
2428
2448
  bos.makedirs(dst)
2429
2449
  except OSError as ex:
2430
- self.log("makedirs failed [{}]".format(dst))
2450
+ self.log("makedirs failed %r" % (dst,))
2431
2451
  if not bos.path.isdir(dst):
2432
2452
  if ex.errno == errno.EACCES:
2433
2453
  raise Pebkac(500, "the server OS denied write-access")
@@ -2452,7 +2472,7 @@ class HttpCli(object):
2452
2472
  # strip common suffix (uploader's folder structure)
2453
2473
  vp_req, vp_vfs = vroots(self.vpath, vjoin(dbv.vpath, vrem))
2454
2474
  if not ret["purl"].startswith(vp_vfs):
2455
- 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"
2456
2476
  zt = (self.vpath, dbv.vpath, vrem, vp_req, vp_vfs, ret["purl"])
2457
2477
  raise Pebkac(500, t % zt)
2458
2478
  ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :]
@@ -2501,13 +2521,13 @@ class HttpCli(object):
2501
2521
  # search by query params
2502
2522
  q = body["q"]
2503
2523
  n = body.get("n", self.args.srch_hits)
2504
- self.log("qj: {} |{}|".format(q, n))
2524
+ self.log("qj: %r |%d|" % (q, n))
2505
2525
  hits, taglist, trunc = idx.search(self.uname, vols, q, n)
2506
2526
  msg = len(hits)
2507
2527
 
2508
2528
  idx.p_end = time.time()
2509
2529
  idx.p_dur = idx.p_end - t0
2510
- self.log("q#: {} ({:.2f}s)".format(msg, idx.p_dur))
2530
+ self.log("q#: %r (%.2fs)" % (msg, idx.p_dur))
2511
2531
 
2512
2532
  order = []
2513
2533
  for t in self.args.mte:
@@ -2618,7 +2638,7 @@ class HttpCli(object):
2618
2638
  t = "your client is sending %d bytes which is too much (server expected %d bytes at most)"
2619
2639
  raise Pebkac(400, t % (remains, maxsize))
2620
2640
 
2621
- t = "writing %s %s+%d #%d+%d %s"
2641
+ t = "writing %r %s+%d #%d+%d %s"
2622
2642
  chunkno = cstart0[0] // chunksize
2623
2643
  zs = " ".join([chashes[0][:15]] + [x[:9] for x in chashes[1:]])
2624
2644
  self.log(t % (path, cstart0, remains, chunkno, len(chashes), zs))
@@ -2730,7 +2750,7 @@ class HttpCli(object):
2730
2750
  cinf = self.headers.get("x-up2k-stat", "")
2731
2751
 
2732
2752
  spd = self._spd(postsize)
2733
- self.log("{:70} thank {}".format(spd, cinf))
2753
+ self.log("%70s thank %r" % (spd, cinf))
2734
2754
  self.reply(b"thank")
2735
2755
  return True
2736
2756
 
@@ -2809,7 +2829,7 @@ class HttpCli(object):
2809
2829
  logpwd = "%" + ub64enc(zb[:12]).decode("ascii")
2810
2830
 
2811
2831
  if pwd != "x":
2812
- self.log("invalid password: {}".format(logpwd), 3)
2832
+ self.log("invalid password: %r" % (logpwd,), 3)
2813
2833
  self.cbonk(self.conn.hsrv.gpwd, pwd, "pw", "invalid passwords")
2814
2834
 
2815
2835
  msg = "naw dude"
@@ -2844,7 +2864,7 @@ class HttpCli(object):
2844
2864
  rem = sanitize_vpath(rem, "/")
2845
2865
  fn = vfs.canonical(rem)
2846
2866
  if not fn.startswith(vfs.realpath):
2847
- self.log("invalid mkdir [%s] [%s]" % (self.gctx, vpath), 1)
2867
+ self.log("invalid mkdir %r %r" % (self.gctx, vpath), 1)
2848
2868
  raise Pebkac(422)
2849
2869
 
2850
2870
  if not nullwrite:
@@ -3009,9 +3029,9 @@ class HttpCli(object):
3009
3029
  elif bos.path.exists(abspath):
3010
3030
  try:
3011
3031
  wunlink(self.log, abspath, vfs.flags)
3012
- t = "overwriting file with new upload: %s"
3032
+ t = "overwriting file with new upload: %r"
3013
3033
  except:
3014
- t = "toctou while deleting for ?replace: %s"
3034
+ t = "toctou while deleting for ?replace: %r"
3015
3035
  self.log(t % (abspath,))
3016
3036
  else:
3017
3037
  open_args = {}
@@ -3094,7 +3114,7 @@ class HttpCli(object):
3094
3114
  f, tnam = ren_open(tnam, "wb", self.args.iobuf, **open_args)
3095
3115
  try:
3096
3116
  tabspath = os.path.join(fdir, tnam)
3097
- self.log("writing to {}".format(tabspath))
3117
+ self.log("writing to %r" % (tabspath,))
3098
3118
  sz, sha_hex, sha_b64 = copier(
3099
3119
  p_data, f, hasher, max_sz, self.args.s_wr_slp
3100
3120
  )
@@ -3279,7 +3299,7 @@ class HttpCli(object):
3279
3299
  jmsg["files"].append(jpart)
3280
3300
 
3281
3301
  vspd = self._spd(sz_total, False)
3282
- self.log("{} {}".format(vspd, msg))
3302
+ self.log("%s %r" % (vspd, msg))
3283
3303
 
3284
3304
  suf = ""
3285
3305
  if not nullwrite and self.args.write_uplog:
@@ -3540,7 +3560,7 @@ class HttpCli(object):
3540
3560
  if req == zs:
3541
3561
  return True
3542
3562
 
3543
- 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"
3544
3564
  self.log(t % (zs, req, self.req, ap), 6)
3545
3565
  return False
3546
3566
 
@@ -3568,7 +3588,7 @@ class HttpCli(object):
3568
3588
  if req == zs:
3569
3589
  return True
3570
3590
 
3571
- 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"
3572
3592
  self.log(t % (zs, req, self.req, ap), 6)
3573
3593
  return False
3574
3594
 
@@ -3630,7 +3650,7 @@ class HttpCli(object):
3630
3650
  elif ph == "srv.htime":
3631
3651
  sv = datetime.now(UTC).strftime("%Y-%m-%d, %H:%M:%S")
3632
3652
  else:
3633
- self.log("unknown placeholder in server config: [%s]" % (ph), 3)
3653
+ self.log("unknown placeholder in server config: [%s]" % (ph,), 3)
3634
3654
  continue
3635
3655
 
3636
3656
  sv = self.conn.hsrv.ptn_hsafe.sub("_", sv)
@@ -3787,7 +3807,7 @@ class HttpCli(object):
3787
3807
  self.pipes.set(req_path, job)
3788
3808
  except Exception as ex:
3789
3809
  if getattr(ex, "errno", 0) != errno.ENOENT:
3790
- self.log("will not pipe [%s]; %s" % (ap_data, ex), 6)
3810
+ self.log("will not pipe %r; %s" % (ap_data, ex), 6)
3791
3811
  ptop = None
3792
3812
 
3793
3813
  #
@@ -4075,7 +4095,7 @@ class HttpCli(object):
4075
4095
  if lower >= data_end:
4076
4096
  if data_end:
4077
4097
  t = "pipe: uploader is too slow; aborting download at %.2f MiB"
4078
- self.log(t % (data_end / M))
4098
+ self.log(t % (data_end / M,))
4079
4099
  raise Pebkac(416, "uploader is too slow")
4080
4100
 
4081
4101
  raise Pebkac(416, "no data available yet; please retry in a bit")
@@ -4219,7 +4239,7 @@ class HttpCli(object):
4219
4239
 
4220
4240
  cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}"
4221
4241
  cdis = cdis.format(afn, ext, ufn, ext)
4222
- self.log(cdis)
4242
+ self.log(repr(cdis))
4223
4243
  self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis})
4224
4244
 
4225
4245
  fgen = vn.zipgen(
@@ -4881,9 +4901,9 @@ class HttpCli(object):
4881
4901
  raise Pebkac(500, "sqlite3 not found on server; unpost is disabled")
4882
4902
  raise Pebkac(500, "server busy, cannot unpost; please retry in a bit")
4883
4903
 
4884
- filt = self.uparam.get("filter") or ""
4885
- lm = "ups [{}]".format(filt)
4886
- 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,)
4887
4907
 
4888
4908
  if self.args.shr and self.vpath.startswith(self.args.shr1):
4889
4909
  shr_dbv, shr_vrem = self.vn.get_dbv(self.rem)
@@ -4924,13 +4944,18 @@ class HttpCli(object):
4924
4944
 
4925
4945
  nfk, fk_alg = fk_vols.get(vol) or (0, 0)
4926
4946
 
4927
- 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"
4928
4949
  for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
4929
4950
  vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
4930
- if filt and filt not in vp:
4951
+ if filt and not filt.search(vp):
4931
4952
  continue
4932
4953
 
4933
- 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}
4934
4959
  if nfk:
4935
4960
  rv["ap"] = vol.canonical(vjoin(rd, fn))
4936
4961
  rv["fk_alg"] = fk_alg
@@ -4940,9 +4965,13 @@ class HttpCli(object):
4940
4965
  ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
4941
4966
  ret = ret[:2000]
4942
4967
 
4968
+ if len(ret) > 2000:
4969
+ ret = ret[:2000]
4970
+
4943
4971
  ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
4944
- n = 0
4945
- for rv in ret[:11000]:
4972
+
4973
+ for rv in ret:
4974
+ rv["vp"] = quotep(rv["vp"])
4946
4975
  nfk = rv.pop("nfk")
4947
4976
  if not nfk:
4948
4977
  continue
@@ -4959,12 +4988,6 @@ class HttpCli(object):
4959
4988
  )
4960
4989
  rv["vp"] += "?k=" + fk[:nfk]
4961
4990
 
4962
- n += 1
4963
- if n > 2000:
4964
- break
4965
-
4966
- ret = ret[:2000]
4967
-
4968
4991
  if shr_dbv:
4969
4992
  # translate vpaths from share-target to share-url
4970
4993
  # to satisfy access checks
@@ -4987,6 +5010,125 @@ class HttpCli(object):
4987
5010
  self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
4988
5011
  return True
4989
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
+
4990
5132
  def tx_shares(self) :
4991
5133
  if self.uname == "*":
4992
5134
  self.loud_reply("you're not logged in")
@@ -5692,7 +5834,7 @@ class HttpCli(object):
5692
5834
  linf = stats.get(fn) or bos.lstat(fspath)
5693
5835
  inf = bos.stat(fspath) if stat.S_ISLNK(linf.st_mode) else linf
5694
5836
  except:
5695
- self.log("broken symlink: {}".format(repr(fspath)))
5837
+ self.log("broken symlink: %r" % (fspath,))
5696
5838
  continue
5697
5839
 
5698
5840
  is_dir = stat.S_ISDIR(inf.st_mode)
@@ -5806,8 +5948,7 @@ class HttpCli(object):
5806
5948
  erd_efn = s3enc(idx.mem_cur, rd, fn)
5807
5949
  r = icur.execute(q, erd_efn)
5808
5950
  except:
5809
- t = "tag read error, {}/{}\n{}"
5810
- self.log(t.format(rd, fn, min_ex()))
5951
+ self.log("tag read error, %r / %r\n%s" % (rd, fn, min_ex()))
5811
5952
  break
5812
5953
 
5813
5954
  tags = {k: v for k, v in r}
@@ -5927,10 +6068,10 @@ class HttpCli(object):
5927
6068
  if doc.lower().endswith(".md") and "exp" in vn.flags:
5928
6069
  doctxt = self._expand(doctxt, vn.flags.get("exp_md") or [])
5929
6070
  else:
5930
- self.log("doc 2big: [{}]".format(doc), c=6)
6071
+ self.log("doc 2big: %r" % (doc,), 6)
5931
6072
  doctxt = "( size of textfile exceeds serverside limit )"
5932
6073
  else:
5933
- self.log("doc 404: [{}]".format(doc), c=6)
6074
+ self.log("doc 404: %r" % (doc,), 6)
5934
6075
  doctxt = "( textfile not found )"
5935
6076
 
5936
6077
  if doctxt is not None: