copyparty 1.16.5__py3-none-any.whl → 1.16.7__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/__init__.py +3 -0
- copyparty/__main__.py +4 -1
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +33 -20
- copyparty/fsutil.py +4 -4
- copyparty/httpcli.py +273 -83
- copyparty/httpsrv.py +6 -9
- copyparty/mtag.py +4 -4
- copyparty/smbd.py +1 -1
- copyparty/sutil.py +1 -1
- copyparty/tcpsrv.py +3 -3
- copyparty/tftpd.py +1 -1
- copyparty/th_cli.py +3 -3
- copyparty/th_srv.py +6 -6
- copyparty/u2idx.py +3 -3
- copyparty/up2k.py +92 -87
- copyparty/util.py +13 -13
- copyparty/web/a/u2c.py +1 -1
- copyparty/web/browser.html +1 -1
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/md.html +2 -2
- copyparty/web/mde.html +2 -2
- copyparty/web/rups.css.gz +0 -0
- copyparty/web/rups.html +50 -0
- copyparty/web/rups.js.gz +0 -0
- copyparty/web/shares.css.gz +0 -0
- copyparty/web/shares.html +6 -3
- copyparty/web/splash.html +2 -1
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/svcs.html +1 -1
- copyparty/web/up2k.js.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.16.5.dist-info → copyparty-1.16.7.dist-info}/METADATA +14 -2
- {copyparty-1.16.5.dist-info → copyparty-1.16.7.dist-info}/RECORD +38 -35
- {copyparty-1.16.5.dist-info → copyparty-1.16.7.dist-info}/LICENSE +0 -0
- {copyparty-1.16.5.dist-info → copyparty-1.16.7.dist-info}/WHEEL +0 -0
- {copyparty-1.16.5.dist-info → copyparty-1.16.7.dist-info}/entry_points.txt +0 -0
- {copyparty-1.16.5.dist-info → copyparty-1.16.7.dist-info}/top_level.txt +0 -0
copyparty/httpcli.py
CHANGED
@@ -141,6 +141,14 @@ A_FILE = os.stat_result(
|
|
141
141
|
(0o644, -1, -1, 1, 1000, 1000, 8, 0x39230101, 0x39230101, 0x39230101)
|
142
142
|
)
|
143
143
|
|
144
|
+
RE_CC = re.compile(r"[\x00-\x1f]") # search always faster
|
145
|
+
RE_HSAFE = re.compile(r"[\x00-\x1f<>\"'&]") # search always much faster
|
146
|
+
RE_HOST = re.compile(r"[^][0-9a-zA-Z.:_-]") # search faster <=17ch
|
147
|
+
RE_MHOST = re.compile(r"^[][0-9a-zA-Z.:_-]+$") # match faster >=18ch
|
148
|
+
RE_K = re.compile(r"[^0-9a-zA-Z_-]") # search faster <=17ch
|
149
|
+
|
150
|
+
UPARAM_CC_OK = set("doc move tree".split())
|
151
|
+
|
144
152
|
|
145
153
|
class HttpCli(object):
|
146
154
|
"""
|
@@ -384,6 +392,15 @@ class HttpCli(object):
|
|
384
392
|
self.host = self.headers.get("x-forwarded-host") or self.host
|
385
393
|
trusted_xff = True
|
386
394
|
|
395
|
+
m = RE_HOST.search(self.host)
|
396
|
+
if m and self.host != self.args.name:
|
397
|
+
zs = self.host
|
398
|
+
t = "malicious user; illegal Host header; req(%r) host(%r) => %r"
|
399
|
+
self.log(t % (self.req, zs, zs[m.span()[0] :]), 1)
|
400
|
+
self.cbonk(self.conn.hsrv.gmal, zs, "bad_host", "illegal Host header")
|
401
|
+
self.terse_reply(b"illegal Host header", 400)
|
402
|
+
return False
|
403
|
+
|
387
404
|
if self.is_banned():
|
388
405
|
return False
|
389
406
|
|
@@ -429,6 +446,16 @@ class HttpCli(object):
|
|
429
446
|
self.loud_reply(t, status=400)
|
430
447
|
return False
|
431
448
|
|
449
|
+
ptn_cc = RE_CC
|
450
|
+
m = ptn_cc.search(self.req)
|
451
|
+
if m:
|
452
|
+
zs = self.req
|
453
|
+
t = "malicious user; Cc in req0 %r => %r"
|
454
|
+
self.log(t % (zs, zs[m.span()[0] :]), 1)
|
455
|
+
self.cbonk(self.conn.hsrv.gmal, zs, "cc_r0", "Cc in req0")
|
456
|
+
self.terse_reply(b"", 500)
|
457
|
+
return False
|
458
|
+
|
432
459
|
# split req into vpath + uparam
|
433
460
|
uparam = {}
|
434
461
|
if "?" not in self.req:
|
@@ -441,8 +468,8 @@ class HttpCli(object):
|
|
441
468
|
self.trailing_slash = vpath.endswith("/")
|
442
469
|
vpath = undot(vpath)
|
443
470
|
|
444
|
-
|
445
|
-
k_safe =
|
471
|
+
re_k = RE_K
|
472
|
+
k_safe = UPARAM_CC_OK
|
446
473
|
for k in arglist.split("&"):
|
447
474
|
if "=" in k:
|
448
475
|
k, zs = k.split("=", 1)
|
@@ -452,6 +479,14 @@ class HttpCli(object):
|
|
452
479
|
else:
|
453
480
|
sv = ""
|
454
481
|
|
482
|
+
m = re_k.search(k)
|
483
|
+
if m:
|
484
|
+
t = "malicious user; bad char in query key; req(%r) qk(%r) => %r"
|
485
|
+
self.log(t % (self.req, k, k[m.span()[0] :]), 1)
|
486
|
+
self.cbonk(self.conn.hsrv.gmal, self.req, "bc_q", "illegal qkey")
|
487
|
+
self.terse_reply(b"", 500)
|
488
|
+
return False
|
489
|
+
|
455
490
|
k = k.lower()
|
456
491
|
uparam[k] = sv
|
457
492
|
|
@@ -459,23 +494,32 @@ class HttpCli(object):
|
|
459
494
|
continue
|
460
495
|
|
461
496
|
zs = "%s=%s" % (k, sv)
|
462
|
-
m =
|
497
|
+
m = ptn_cc.search(zs)
|
463
498
|
if not m:
|
464
499
|
continue
|
465
500
|
|
466
|
-
|
467
|
-
t
|
468
|
-
self.log(t.format(self.req, hit), 1)
|
501
|
+
t = "malicious user; Cc in query; req(%r) qp(%r) => %r"
|
502
|
+
self.log(t % (self.req, zs, zs[m.span()[0] :]), 1)
|
469
503
|
self.cbonk(self.conn.hsrv.gmal, self.req, "cc_q", "Cc in query")
|
470
504
|
self.terse_reply(b"", 500)
|
471
505
|
return False
|
472
506
|
|
507
|
+
if "k" in uparam:
|
508
|
+
m = RE_K.search(uparam["k"])
|
509
|
+
if m:
|
510
|
+
zs = uparam["k"]
|
511
|
+
t = "malicious user; illegal filekey; req(%r) k(%r) => %r"
|
512
|
+
self.log(t % (self.req, zs, zs[m.span()[0] :]), 1)
|
513
|
+
self.cbonk(self.conn.hsrv.gmal, zs, "bad_k", "illegal filekey")
|
514
|
+
self.terse_reply(b"illegal filekey", 400)
|
515
|
+
return False
|
516
|
+
|
473
517
|
if self.is_vproxied:
|
474
518
|
if vpath.startswith(self.args.R):
|
475
519
|
vpath = vpath[len(self.args.R) + 1 :]
|
476
520
|
else:
|
477
|
-
t = "incorrect --rp-loc or webserver config; expected vpath starting with
|
478
|
-
self.log(t
|
521
|
+
t = "incorrect --rp-loc or webserver config; expected vpath starting with %r but got %r"
|
522
|
+
self.log(t % (self.args.R, vpath), 1)
|
479
523
|
|
480
524
|
self.ouparam = uparam.copy()
|
481
525
|
|
@@ -513,7 +557,7 @@ class HttpCli(object):
|
|
513
557
|
return self.tx_qr()
|
514
558
|
|
515
559
|
if relchk(self.vpath) and (self.vpath != "*" or self.mode != "OPTIONS"):
|
516
|
-
self.log("
|
560
|
+
self.log("illegal relpath; req(%r) => %r" % (self.req, "/" + self.vpath))
|
517
561
|
self.cbonk(self.conn.hsrv.gmal, self.req, "bad_vp", "invalid relpaths")
|
518
562
|
return self.tx_404() and self.keepalive
|
519
563
|
|
@@ -593,7 +637,7 @@ class HttpCli(object):
|
|
593
637
|
self.uname = idp_usr
|
594
638
|
self.html_head += "<script>var is_idp=1</script>\n"
|
595
639
|
else:
|
596
|
-
self.log("unknown username:
|
640
|
+
self.log("unknown username: %r" % (idp_usr,), 1)
|
597
641
|
|
598
642
|
if self.args.ipu and self.uname == "*":
|
599
643
|
self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
|
@@ -658,7 +702,7 @@ class HttpCli(object):
|
|
658
702
|
origin = self.headers.get("origin", "<?>")
|
659
703
|
proto = "https://" if self.is_https else "http://"
|
660
704
|
guess = "modifying" if (origin and host) else "stripping"
|
661
|
-
t = "cors-reject %s because request-header Origin
|
705
|
+
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
706
|
self.log(t % (self.mode, origin, proto, self.host, host, guess), 3)
|
663
707
|
raise Pebkac(403, "rejected by cors-check")
|
664
708
|
|
@@ -704,7 +748,7 @@ class HttpCli(object):
|
|
704
748
|
|
705
749
|
if pex.code != 404 or self.do_log:
|
706
750
|
self.log(
|
707
|
-
"http%d: %s\033[0m, %
|
751
|
+
"http%d: %s\033[0m, %r" % (pex.code, msg, "/" + self.vpath),
|
708
752
|
6 if em.startswith("client d/c ") else 3,
|
709
753
|
)
|
710
754
|
|
@@ -866,12 +910,12 @@ class HttpCli(object):
|
|
866
910
|
for k, zs in list(self.out_headers.items()) + self.out_headerlist:
|
867
911
|
response.append("%s: %s" % (k, zs))
|
868
912
|
|
913
|
+
ptn_cc = RE_CC
|
869
914
|
for zs in response:
|
870
|
-
m =
|
915
|
+
m = ptn_cc.search(zs)
|
871
916
|
if m:
|
872
|
-
|
873
|
-
t
|
874
|
-
self.log(t.format(zs, hit), 1)
|
917
|
+
t = "malicious user; Cc in out-hdr; req(%r) hdr(%r) => %r"
|
918
|
+
self.log(t % (self.req, zs, zs[m.span()[0] :]), 1)
|
875
919
|
self.cbonk(self.conn.hsrv.gmal, zs, "cc_hdr", "Cc in out-hdr")
|
876
920
|
raise Pebkac(999)
|
877
921
|
|
@@ -997,7 +1041,7 @@ class HttpCli(object):
|
|
997
1041
|
if not kv:
|
998
1042
|
return ""
|
999
1043
|
|
1000
|
-
r = ["%s=%s" % (k, quotep(zs)) if zs else k for k, zs in kv.items()]
|
1044
|
+
r = ["%s=%s" % (quotep(k), quotep(zs)) if zs else k for k, zs in kv.items()]
|
1001
1045
|
return "?" + "&".join(r)
|
1002
1046
|
|
1003
1047
|
def ourlq(self) :
|
@@ -1113,6 +1157,8 @@ class HttpCli(object):
|
|
1113
1157
|
logmsg += " [\033[36m" + rval + "\033[0m]"
|
1114
1158
|
|
1115
1159
|
self.log(logmsg)
|
1160
|
+
if "%" in self.req:
|
1161
|
+
self.log(" `-- %r" % (self.vpath,))
|
1116
1162
|
|
1117
1163
|
# "embedded" resources
|
1118
1164
|
if self.vpath.startswith(".cpr"):
|
@@ -1147,8 +1193,8 @@ class HttpCli(object):
|
|
1147
1193
|
return self.tx_res(res_path)
|
1148
1194
|
|
1149
1195
|
if res_path != undot(res_path):
|
1150
|
-
t = "malicious user; attempted path traversal
|
1151
|
-
self.log(t
|
1196
|
+
t = "malicious user; attempted path traversal; req(%r) vp(%r) => %r"
|
1197
|
+
self.log(t % (self.req, "/" + self.vpath, res_path), 1)
|
1152
1198
|
self.cbonk(self.conn.hsrv.gmal, self.req, "trav", "path traversal")
|
1153
1199
|
|
1154
1200
|
self.tx_404()
|
@@ -1159,11 +1205,11 @@ class HttpCli(object):
|
|
1159
1205
|
return True
|
1160
1206
|
|
1161
1207
|
if not self.can_read and not self.can_write and not self.can_get:
|
1162
|
-
t = "
|
1208
|
+
t = "@%s has no access to %r"
|
1163
1209
|
|
1164
1210
|
if "on403" in self.vn.flags:
|
1165
1211
|
t += " (on403)"
|
1166
|
-
self.log(t
|
1212
|
+
self.log(t % (self.uname, "/" + self.vpath))
|
1167
1213
|
ret = self.on40x(self.vn.flags["on403"], self.vn, self.rem)
|
1168
1214
|
if ret == "true":
|
1169
1215
|
return True
|
@@ -1182,7 +1228,7 @@ class HttpCli(object):
|
|
1182
1228
|
if self.vpath:
|
1183
1229
|
ptn = self.args.nonsus_urls
|
1184
1230
|
if not ptn or not ptn.search(self.vpath):
|
1185
|
-
self.log(t
|
1231
|
+
self.log(t % (self.uname, "/" + self.vpath))
|
1186
1232
|
|
1187
1233
|
return self.tx_404(True)
|
1188
1234
|
|
@@ -1226,6 +1272,9 @@ class HttpCli(object):
|
|
1226
1272
|
if "dls" in self.uparam:
|
1227
1273
|
return self.tx_dls()
|
1228
1274
|
|
1275
|
+
if "ru" in self.uparam:
|
1276
|
+
return self.tx_rups()
|
1277
|
+
|
1229
1278
|
if "h" in self.uparam:
|
1230
1279
|
return self.tx_mounts()
|
1231
1280
|
|
@@ -1288,8 +1337,8 @@ class HttpCli(object):
|
|
1288
1337
|
|
1289
1338
|
pw = self.ouparam.get("pw")
|
1290
1339
|
if pw:
|
1291
|
-
q_pw = "?pw=%s" % (pw,)
|
1292
|
-
a_pw = "&pw=%s" % (pw,)
|
1340
|
+
q_pw = "?pw=%s" % (html_escape(pw, True, True),)
|
1341
|
+
a_pw = "&pw=%s" % (html_escape(pw, True, True),)
|
1293
1342
|
for i in hits:
|
1294
1343
|
i["rp"] += a_pw if "?" in i["rp"] else q_pw
|
1295
1344
|
else:
|
@@ -1376,6 +1425,8 @@ class HttpCli(object):
|
|
1376
1425
|
def handle_propfind(self) :
|
1377
1426
|
if self.do_log:
|
1378
1427
|
self.log("PFIND %s @%s" % (self.req, self.uname))
|
1428
|
+
if "%" in self.req:
|
1429
|
+
self.log(" `-- %r" % (self.vpath,))
|
1379
1430
|
|
1380
1431
|
if self.args.no_dav:
|
1381
1432
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
@@ -1426,14 +1477,14 @@ class HttpCli(object):
|
|
1426
1477
|
if depth == "infinity":
|
1427
1478
|
# allow depth:0 from unmapped root, but require read-axs otherwise
|
1428
1479
|
if not self.can_read and (self.vpath or self.asrv.vfs.realpath):
|
1429
|
-
t = "depth:infinity requires read-access in
|
1430
|
-
t = t % (self.vpath,)
|
1480
|
+
t = "depth:infinity requires read-access in %r"
|
1481
|
+
t = t % ("/" + self.vpath,)
|
1431
1482
|
self.log(t, 3)
|
1432
1483
|
raise Pebkac(401, t)
|
1433
1484
|
|
1434
1485
|
if not stat.S_ISDIR(topdir["st"].st_mode):
|
1435
|
-
t = "depth:infinity can only be used on folders;
|
1436
|
-
t = t % (self.vpath, topdir["st"])
|
1486
|
+
t = "depth:infinity can only be used on folders; %r is 0o%o"
|
1487
|
+
t = t % ("/" + self.vpath, topdir["st"])
|
1437
1488
|
self.log(t, 3)
|
1438
1489
|
raise Pebkac(400, t)
|
1439
1490
|
|
@@ -1459,7 +1510,7 @@ class HttpCli(object):
|
|
1459
1510
|
elif depth == "0" or not stat.S_ISDIR(st.st_mode):
|
1460
1511
|
# propfind on a file; return as topdir
|
1461
1512
|
if not self.can_read and not self.can_get:
|
1462
|
-
self.log("inaccessible:
|
1513
|
+
self.log("inaccessible: %r" % ("/" + self.vpath,))
|
1463
1514
|
raise Pebkac(401, "authenticate")
|
1464
1515
|
|
1465
1516
|
elif depth == "1":
|
@@ -1486,7 +1537,7 @@ class HttpCli(object):
|
|
1486
1537
|
raise Pebkac(412, t.format(depth, t2))
|
1487
1538
|
|
1488
1539
|
if not self.can_read and not self.can_write and not fgen:
|
1489
|
-
self.log("inaccessible:
|
1540
|
+
self.log("inaccessible: %r" % ("/" + self.vpath,))
|
1490
1541
|
raise Pebkac(401, "authenticate")
|
1491
1542
|
|
1492
1543
|
fgen = itertools.chain([topdir], fgen)
|
@@ -1557,12 +1608,14 @@ class HttpCli(object):
|
|
1557
1608
|
def handle_proppatch(self) :
|
1558
1609
|
if self.do_log:
|
1559
1610
|
self.log("PPATCH %s @%s" % (self.req, self.uname))
|
1611
|
+
if "%" in self.req:
|
1612
|
+
self.log(" `-- %r" % (self.vpath,))
|
1560
1613
|
|
1561
1614
|
if self.args.no_dav:
|
1562
1615
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
1563
1616
|
|
1564
1617
|
if not self.can_write:
|
1565
|
-
self.log("
|
1618
|
+
self.log("%s tried to proppatch %r" % (self.uname, "/" + self.vpath))
|
1566
1619
|
raise Pebkac(401, "authenticate")
|
1567
1620
|
|
1568
1621
|
from xml.etree import ElementTree as ET
|
@@ -1609,13 +1662,15 @@ class HttpCli(object):
|
|
1609
1662
|
def handle_lock(self) :
|
1610
1663
|
if self.do_log:
|
1611
1664
|
self.log("LOCK %s @%s" % (self.req, self.uname))
|
1665
|
+
if "%" in self.req:
|
1666
|
+
self.log(" `-- %r" % (self.vpath,))
|
1612
1667
|
|
1613
1668
|
if self.args.no_dav:
|
1614
1669
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
1615
1670
|
|
1616
1671
|
# win7+ deadlocks if we say no; just smile and nod
|
1617
1672
|
if not self.can_write and "Microsoft-WebDAV" not in self.ua:
|
1618
|
-
self.log("
|
1673
|
+
self.log("%s tried to lock %r" % (self.uname, "/" + self.vpath))
|
1619
1674
|
raise Pebkac(401, "authenticate")
|
1620
1675
|
|
1621
1676
|
from xml.etree import ElementTree as ET
|
@@ -1644,7 +1699,7 @@ class HttpCli(object):
|
|
1644
1699
|
|
1645
1700
|
token = str(uuid.uuid4())
|
1646
1701
|
|
1647
|
-
if
|
1702
|
+
if lk.find(r"./{DAV:}depth") is None:
|
1648
1703
|
depth = self.headers.get("depth", "infinity")
|
1649
1704
|
lk.append(mktnod("D:depth", depth))
|
1650
1705
|
|
@@ -1674,12 +1729,14 @@ class HttpCli(object):
|
|
1674
1729
|
def handle_unlock(self) :
|
1675
1730
|
if self.do_log:
|
1676
1731
|
self.log("UNLOCK %s @%s" % (self.req, self.uname))
|
1732
|
+
if "%" in self.req:
|
1733
|
+
self.log(" `-- %r" % (self.vpath,))
|
1677
1734
|
|
1678
1735
|
if self.args.no_dav:
|
1679
1736
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
1680
1737
|
|
1681
1738
|
if not self.can_write and "Microsoft-WebDAV" not in self.ua:
|
1682
|
-
self.log("
|
1739
|
+
self.log("%s tried to lock %r" % (self.uname, "/" + self.vpath))
|
1683
1740
|
raise Pebkac(401, "authenticate")
|
1684
1741
|
|
1685
1742
|
self.send_headers(None, 204)
|
@@ -1691,6 +1748,8 @@ class HttpCli(object):
|
|
1691
1748
|
|
1692
1749
|
if self.do_log:
|
1693
1750
|
self.log("MKCOL %s @%s" % (self.req, self.uname))
|
1751
|
+
if "%" in self.req:
|
1752
|
+
self.log(" `-- %r" % (self.vpath,))
|
1694
1753
|
|
1695
1754
|
try:
|
1696
1755
|
return self._mkdir(self.vpath, True)
|
@@ -1742,6 +1801,8 @@ class HttpCli(object):
|
|
1742
1801
|
def handle_options(self) :
|
1743
1802
|
if self.do_log:
|
1744
1803
|
self.log("OPTIONS %s @%s" % (self.req, self.uname))
|
1804
|
+
if "%" in self.req:
|
1805
|
+
self.log(" `-- %r" % (self.vpath,))
|
1745
1806
|
|
1746
1807
|
oh = self.out_headers
|
1747
1808
|
oh["Allow"] = ", ".join(self.conn.hsrv.mallow)
|
@@ -1757,10 +1818,14 @@ class HttpCli(object):
|
|
1757
1818
|
|
1758
1819
|
def handle_delete(self) :
|
1759
1820
|
self.log("DELETE %s @%s" % (self.req, self.uname))
|
1821
|
+
if "%" in self.req:
|
1822
|
+
self.log(" `-- %r" % (self.vpath,))
|
1760
1823
|
return self.handle_rm([])
|
1761
1824
|
|
1762
1825
|
def handle_put(self) :
|
1763
|
-
self.log("PUT
|
1826
|
+
self.log("PUT %s @%s" % (self.req, self.uname))
|
1827
|
+
if "%" in self.req:
|
1828
|
+
self.log(" `-- %r" % (self.vpath,))
|
1764
1829
|
|
1765
1830
|
if not self.can_write:
|
1766
1831
|
t = "user %s does not have write-access under /%s"
|
@@ -1779,6 +1844,8 @@ class HttpCli(object):
|
|
1779
1844
|
|
1780
1845
|
def handle_post(self) :
|
1781
1846
|
self.log("POST %s @%s" % (self.req, self.uname))
|
1847
|
+
if "%" in self.req:
|
1848
|
+
self.log(" `-- %r" % (self.vpath,))
|
1782
1849
|
|
1783
1850
|
if self.headers.get("expect", "").lower() == "100-continue":
|
1784
1851
|
try:
|
@@ -1823,7 +1890,7 @@ class HttpCli(object):
|
|
1823
1890
|
|
1824
1891
|
if "save" in opt:
|
1825
1892
|
post_sz, _, _, _, path, _ = self.dump_to_file(False)
|
1826
|
-
self.log("urlform:
|
1893
|
+
self.log("urlform: %d bytes, %r" % (post_sz, path))
|
1827
1894
|
elif "print" in opt:
|
1828
1895
|
reader, _ = self.get_body_reader()
|
1829
1896
|
buf = b""
|
@@ -1834,8 +1901,8 @@ class HttpCli(object):
|
|
1834
1901
|
|
1835
1902
|
if buf:
|
1836
1903
|
orig = buf.decode("utf-8", "replace")
|
1837
|
-
t = "urlform_raw
|
1838
|
-
self.log(t
|
1904
|
+
t = "urlform_raw %d @ %r\n %r\n"
|
1905
|
+
self.log(t % (len(orig), "/" + self.vpath, orig))
|
1839
1906
|
try:
|
1840
1907
|
zb = unquote(buf.replace(b"+", b" "))
|
1841
1908
|
plain = zb.decode("utf-8", "replace")
|
@@ -1861,8 +1928,8 @@ class HttpCli(object):
|
|
1861
1928
|
plain,
|
1862
1929
|
)
|
1863
1930
|
|
1864
|
-
t = "urlform_dec
|
1865
|
-
self.log(t
|
1931
|
+
t = "urlform_dec %d @ %r\n %r\n"
|
1932
|
+
self.log(t % (len(plain), "/" + self.vpath, plain))
|
1866
1933
|
|
1867
1934
|
except Exception as ex:
|
1868
1935
|
self.log(repr(ex))
|
@@ -2110,7 +2177,7 @@ class HttpCli(object):
|
|
2110
2177
|
try:
|
2111
2178
|
ext = self.conn.hsrv.magician.ext(path)
|
2112
2179
|
except Exception as ex:
|
2113
|
-
self.log("filetype detection failed for
|
2180
|
+
self.log("filetype detection failed for %r: %s" % (path, ex), 6)
|
2114
2181
|
ext = None
|
2115
2182
|
|
2116
2183
|
if ext:
|
@@ -2210,8 +2277,8 @@ class HttpCli(object):
|
|
2210
2277
|
def handle_stash(self, is_put ) :
|
2211
2278
|
post_sz, sha_hex, sha_b64, remains, path, url = self.dump_to_file(is_put)
|
2212
2279
|
spd = self._spd(post_sz)
|
2213
|
-
t = "
|
2214
|
-
self.log(t
|
2280
|
+
t = "%s wrote %d/%d bytes to %r # %s"
|
2281
|
+
self.log(t % (spd, post_sz, remains, path, sha_b64[:28])) # 21
|
2215
2282
|
|
2216
2283
|
ac = self.uparam.get(
|
2217
2284
|
"want", self.headers.get("accept", "").lower().split(";")[-1]
|
@@ -2241,7 +2308,7 @@ class HttpCli(object):
|
|
2241
2308
|
flags ,
|
2242
2309
|
) :
|
2243
2310
|
now = time.time()
|
2244
|
-
t = "bad-chunk: %.3f %s %s %d %s %s %
|
2311
|
+
t = "bad-chunk: %.3f %s %s %d %s %s %r"
|
2245
2312
|
t = t % (now, bad_sha, good_sha, ofs, self.ip, self.uname, ap)
|
2246
2313
|
self.log(t, 5)
|
2247
2314
|
|
@@ -2380,7 +2447,7 @@ class HttpCli(object):
|
|
2380
2447
|
body = json.loads(json_buf.decode(enc, "replace"))
|
2381
2448
|
try:
|
2382
2449
|
zds = {k: v for k, v in body.items()}
|
2383
|
-
zds["hash"] = "%d chunks" % (len(body["hash"]))
|
2450
|
+
zds["hash"] = "%d chunks" % (len(body["hash"]),)
|
2384
2451
|
except:
|
2385
2452
|
zds = body
|
2386
2453
|
t = "POST len=%d type=%s ip=%s user=%s req=%r json=%s"
|
@@ -2424,7 +2491,7 @@ class HttpCli(object):
|
|
2424
2491
|
if not bos.path.isdir(dst):
|
2425
2492
|
bos.makedirs(dst)
|
2426
2493
|
except OSError as ex:
|
2427
|
-
self.log("makedirs failed
|
2494
|
+
self.log("makedirs failed %r" % (dst,))
|
2428
2495
|
if not bos.path.isdir(dst):
|
2429
2496
|
if ex.errno == errno.EACCES:
|
2430
2497
|
raise Pebkac(500, "the server OS denied write-access")
|
@@ -2449,7 +2516,7 @@ class HttpCli(object):
|
|
2449
2516
|
# strip common suffix (uploader's folder structure)
|
2450
2517
|
vp_req, vp_vfs = vroots(self.vpath, vjoin(dbv.vpath, vrem))
|
2451
2518
|
if not ret["purl"].startswith(vp_vfs):
|
2452
|
-
t = "share-mapping failed; req
|
2519
|
+
t = "share-mapping failed; req=%r dbv=%r vrem=%r n1=%r n2=%r purl=%r"
|
2453
2520
|
zt = (self.vpath, dbv.vpath, vrem, vp_req, vp_vfs, ret["purl"])
|
2454
2521
|
raise Pebkac(500, t % zt)
|
2455
2522
|
ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :]
|
@@ -2498,13 +2565,13 @@ class HttpCli(object):
|
|
2498
2565
|
# search by query params
|
2499
2566
|
q = body["q"]
|
2500
2567
|
n = body.get("n", self.args.srch_hits)
|
2501
|
-
self.log("qj:
|
2568
|
+
self.log("qj: %r |%d|" % (q, n))
|
2502
2569
|
hits, taglist, trunc = idx.search(self.uname, vols, q, n)
|
2503
2570
|
msg = len(hits)
|
2504
2571
|
|
2505
2572
|
idx.p_end = time.time()
|
2506
2573
|
idx.p_dur = idx.p_end - t0
|
2507
|
-
self.log("q#:
|
2574
|
+
self.log("q#: %r (%.2fs)" % (msg, idx.p_dur))
|
2508
2575
|
|
2509
2576
|
order = []
|
2510
2577
|
for t in self.args.mte:
|
@@ -2615,7 +2682,7 @@ class HttpCli(object):
|
|
2615
2682
|
t = "your client is sending %d bytes which is too much (server expected %d bytes at most)"
|
2616
2683
|
raise Pebkac(400, t % (remains, maxsize))
|
2617
2684
|
|
2618
|
-
t = "writing %
|
2685
|
+
t = "writing %r %s+%d #%d+%d %s"
|
2619
2686
|
chunkno = cstart0[0] // chunksize
|
2620
2687
|
zs = " ".join([chashes[0][:15]] + [x[:9] for x in chashes[1:]])
|
2621
2688
|
self.log(t % (path, cstart0, remains, chunkno, len(chashes), zs))
|
@@ -2727,7 +2794,7 @@ class HttpCli(object):
|
|
2727
2794
|
cinf = self.headers.get("x-up2k-stat", "")
|
2728
2795
|
|
2729
2796
|
spd = self._spd(postsize)
|
2730
|
-
self.log("
|
2797
|
+
self.log("%70s thank %r" % (spd, cinf))
|
2731
2798
|
self.reply(b"thank")
|
2732
2799
|
return True
|
2733
2800
|
|
@@ -2806,7 +2873,7 @@ class HttpCli(object):
|
|
2806
2873
|
logpwd = "%" + ub64enc(zb[:12]).decode("ascii")
|
2807
2874
|
|
2808
2875
|
if pwd != "x":
|
2809
|
-
self.log("invalid password:
|
2876
|
+
self.log("invalid password: %r" % (logpwd,), 3)
|
2810
2877
|
self.cbonk(self.conn.hsrv.gpwd, pwd, "pw", "invalid passwords")
|
2811
2878
|
|
2812
2879
|
msg = "naw dude"
|
@@ -2841,7 +2908,7 @@ class HttpCli(object):
|
|
2841
2908
|
rem = sanitize_vpath(rem, "/")
|
2842
2909
|
fn = vfs.canonical(rem)
|
2843
2910
|
if not fn.startswith(vfs.realpath):
|
2844
|
-
self.log("invalid mkdir
|
2911
|
+
self.log("invalid mkdir %r %r" % (self.gctx, vpath), 1)
|
2845
2912
|
raise Pebkac(422)
|
2846
2913
|
|
2847
2914
|
if not nullwrite:
|
@@ -3006,9 +3073,9 @@ class HttpCli(object):
|
|
3006
3073
|
elif bos.path.exists(abspath):
|
3007
3074
|
try:
|
3008
3075
|
wunlink(self.log, abspath, vfs.flags)
|
3009
|
-
t = "overwriting file with new upload: %
|
3076
|
+
t = "overwriting file with new upload: %r"
|
3010
3077
|
except:
|
3011
|
-
t = "toctou while deleting for ?replace: %
|
3078
|
+
t = "toctou while deleting for ?replace: %r"
|
3012
3079
|
self.log(t % (abspath,))
|
3013
3080
|
else:
|
3014
3081
|
open_args = {}
|
@@ -3091,7 +3158,7 @@ class HttpCli(object):
|
|
3091
3158
|
f, tnam = ren_open(tnam, "wb", self.args.iobuf, **open_args)
|
3092
3159
|
try:
|
3093
3160
|
tabspath = os.path.join(fdir, tnam)
|
3094
|
-
self.log("writing to
|
3161
|
+
self.log("writing to %r" % (tabspath,))
|
3095
3162
|
sz, sha_hex, sha_b64 = copier(
|
3096
3163
|
p_data, f, hasher, max_sz, self.args.s_wr_slp
|
3097
3164
|
)
|
@@ -3276,7 +3343,7 @@ class HttpCli(object):
|
|
3276
3343
|
jmsg["files"].append(jpart)
|
3277
3344
|
|
3278
3345
|
vspd = self._spd(sz_total, False)
|
3279
|
-
self.log("
|
3346
|
+
self.log("%s %r" % (vspd, msg))
|
3280
3347
|
|
3281
3348
|
suf = ""
|
3282
3349
|
if not nullwrite and self.args.write_uplog:
|
@@ -3537,7 +3604,7 @@ class HttpCli(object):
|
|
3537
3604
|
if req == zs:
|
3538
3605
|
return True
|
3539
3606
|
|
3540
|
-
t = "wrong dirkey, want %s, got %s\n vp: %
|
3607
|
+
t = "wrong dirkey, want %s, got %s\n vp: %r\n ap: %r"
|
3541
3608
|
self.log(t % (zs, req, self.req, ap), 6)
|
3542
3609
|
return False
|
3543
3610
|
|
@@ -3565,7 +3632,7 @@ class HttpCli(object):
|
|
3565
3632
|
if req == zs:
|
3566
3633
|
return True
|
3567
3634
|
|
3568
|
-
t = "wrong filekey, want %s, got %s\n vp: %
|
3635
|
+
t = "wrong filekey, want %s, got %s\n vp: %r\n ap: %r"
|
3569
3636
|
self.log(t % (zs, req, self.req, ap), 6)
|
3570
3637
|
return False
|
3571
3638
|
|
@@ -3613,6 +3680,7 @@ class HttpCli(object):
|
|
3613
3680
|
return logues, readmes
|
3614
3681
|
|
3615
3682
|
def _expand(self, txt , phs ) :
|
3683
|
+
ptn_hsafe = RE_HSAFE
|
3616
3684
|
for ph in phs:
|
3617
3685
|
if ph.startswith("hdr."):
|
3618
3686
|
sv = str(self.headers.get(ph[4:], ""))
|
@@ -3627,10 +3695,10 @@ class HttpCli(object):
|
|
3627
3695
|
elif ph == "srv.htime":
|
3628
3696
|
sv = datetime.now(UTC).strftime("%Y-%m-%d, %H:%M:%S")
|
3629
3697
|
else:
|
3630
|
-
self.log("unknown placeholder in server config: [%s]" % (ph), 3)
|
3698
|
+
self.log("unknown placeholder in server config: [%s]" % (ph,), 3)
|
3631
3699
|
continue
|
3632
3700
|
|
3633
|
-
sv =
|
3701
|
+
sv = ptn_hsafe.sub("_", sv)
|
3634
3702
|
txt = txt.replace("{{%s}}" % (ph,), sv)
|
3635
3703
|
|
3636
3704
|
return txt
|
@@ -3784,7 +3852,7 @@ class HttpCli(object):
|
|
3784
3852
|
self.pipes.set(req_path, job)
|
3785
3853
|
except Exception as ex:
|
3786
3854
|
if getattr(ex, "errno", 0) != errno.ENOENT:
|
3787
|
-
self.log("will not pipe
|
3855
|
+
self.log("will not pipe %r; %s" % (ap_data, ex), 6)
|
3788
3856
|
ptop = None
|
3789
3857
|
|
3790
3858
|
#
|
@@ -4072,7 +4140,7 @@ class HttpCli(object):
|
|
4072
4140
|
if lower >= data_end:
|
4073
4141
|
if data_end:
|
4074
4142
|
t = "pipe: uploader is too slow; aborting download at %.2f MiB"
|
4075
|
-
self.log(t % (data_end / M))
|
4143
|
+
self.log(t % (data_end / M,))
|
4076
4144
|
raise Pebkac(416, "uploader is too slow")
|
4077
4145
|
|
4078
4146
|
raise Pebkac(416, "no data available yet; please retry in a bit")
|
@@ -4216,7 +4284,7 @@ class HttpCli(object):
|
|
4216
4284
|
|
4217
4285
|
cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}"
|
4218
4286
|
cdis = cdis.format(afn, ext, ufn, ext)
|
4219
|
-
self.log(cdis)
|
4287
|
+
self.log(repr(cdis))
|
4220
4288
|
self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis})
|
4221
4289
|
|
4222
4290
|
fgen = vn.zipgen(
|
@@ -4878,9 +4946,9 @@ class HttpCli(object):
|
|
4878
4946
|
raise Pebkac(500, "sqlite3 not found on server; unpost is disabled")
|
4879
4947
|
raise Pebkac(500, "server busy, cannot unpost; please retry in a bit")
|
4880
4948
|
|
4881
|
-
|
4882
|
-
|
4883
|
-
|
4949
|
+
zs = self.uparam.get("filter") or ""
|
4950
|
+
filt = re.compile(zs, re.I) if zs else None
|
4951
|
+
lm = "ups %r" % (zs,)
|
4884
4952
|
|
4885
4953
|
if self.args.shr and self.vpath.startswith(self.args.shr1):
|
4886
4954
|
shr_dbv, shr_vrem = self.vn.get_dbv(self.rem)
|
@@ -4921,13 +4989,18 @@ class HttpCli(object):
|
|
4921
4989
|
|
4922
4990
|
nfk, fk_alg = fk_vols.get(vol) or (0, 0)
|
4923
4991
|
|
4924
|
-
|
4992
|
+
n = 2000
|
4993
|
+
q = "select sz, rd, fn, at from up where ip=? and at>? order by at desc"
|
4925
4994
|
for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
|
4926
4995
|
vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
|
4927
|
-
if filt and
|
4996
|
+
if filt and not filt.search(vp):
|
4928
4997
|
continue
|
4929
4998
|
|
4930
|
-
|
4999
|
+
n -= 1
|
5000
|
+
if not n:
|
5001
|
+
break
|
5002
|
+
|
5003
|
+
rv = {"vp": vp, "sz": sz, "at": at, "nfk": nfk}
|
4931
5004
|
if nfk:
|
4932
5005
|
rv["ap"] = vol.canonical(vjoin(rd, fn))
|
4933
5006
|
rv["fk_alg"] = fk_alg
|
@@ -4938,8 +5011,12 @@ class HttpCli(object):
|
|
4938
5011
|
ret = ret[:2000]
|
4939
5012
|
|
4940
5013
|
ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
|
4941
|
-
|
4942
|
-
|
5014
|
+
|
5015
|
+
if len(ret) > 2000:
|
5016
|
+
ret = ret[:2000]
|
5017
|
+
|
5018
|
+
for rv in ret:
|
5019
|
+
rv["vp"] = quotep(rv["vp"])
|
4943
5020
|
nfk = rv.pop("nfk")
|
4944
5021
|
if not nfk:
|
4945
5022
|
continue
|
@@ -4956,12 +5033,6 @@ class HttpCli(object):
|
|
4956
5033
|
)
|
4957
5034
|
rv["vp"] += "?k=" + fk[:nfk]
|
4958
5035
|
|
4959
|
-
n += 1
|
4960
|
-
if n > 2000:
|
4961
|
-
break
|
4962
|
-
|
4963
|
-
ret = ret[:2000]
|
4964
|
-
|
4965
5036
|
if shr_dbv:
|
4966
5037
|
# translate vpaths from share-target to share-url
|
4967
5038
|
# to satisfy access checks
|
@@ -4984,6 +5055,126 @@ class HttpCli(object):
|
|
4984
5055
|
self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
|
4985
5056
|
return True
|
4986
5057
|
|
5058
|
+
def tx_rups(self) :
|
5059
|
+
if self.args.no_ups_page:
|
5060
|
+
raise Pebkac(500, "listing of recent uploads is disabled in server config")
|
5061
|
+
|
5062
|
+
idx = self.conn.get_u2idx()
|
5063
|
+
if not idx or not hasattr(idx, "p_end"):
|
5064
|
+
if not HAVE_SQLITE3:
|
5065
|
+
raise Pebkac(500, "sqlite3 not found on server; recent-uploads n/a")
|
5066
|
+
raise Pebkac(500, "server busy, cannot list recent uploads; please retry")
|
5067
|
+
|
5068
|
+
sfilt = self.uparam.get("filter") or ""
|
5069
|
+
filt = re.compile(sfilt, re.I) if sfilt else None
|
5070
|
+
lm = "ru %r" % (sfilt,)
|
5071
|
+
self.log(lm)
|
5072
|
+
|
5073
|
+
ret = []
|
5074
|
+
t0 = time.time()
|
5075
|
+
allvols = [
|
5076
|
+
x
|
5077
|
+
for x in self.asrv.vfs.all_vols.values()
|
5078
|
+
if "e2d" in x.flags and ("*" in x.axs.uread or self.uname in x.axs.uread)
|
5079
|
+
]
|
5080
|
+
fk_vols = {
|
5081
|
+
vol: (vol.flags["fk"], 2 if "fka" in vol.flags else 1)
|
5082
|
+
for vol in allvols
|
5083
|
+
if "fk" in vol.flags and "*" not in vol.axs.uread
|
5084
|
+
}
|
5085
|
+
|
5086
|
+
for vol in allvols:
|
5087
|
+
cur = idx.get_cur(vol)
|
5088
|
+
if not cur:
|
5089
|
+
continue
|
5090
|
+
|
5091
|
+
nfk, fk_alg = fk_vols.get(vol) or (0, 0)
|
5092
|
+
adm = "*" in vol.axs.uadmin or self.uname in vol.axs.uadmin
|
5093
|
+
dots = "*" in vol.axs.udot or self.uname in vol.axs.udot
|
5094
|
+
|
5095
|
+
n = 1000
|
5096
|
+
q = "select sz, rd, fn, ip, at from up where at>0 order by at desc"
|
5097
|
+
for sz, rd, fn, ip, at in cur.execute(q):
|
5098
|
+
vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
|
5099
|
+
if filt and not filt.search(vp):
|
5100
|
+
continue
|
5101
|
+
|
5102
|
+
if not dots and "/." in vp:
|
5103
|
+
continue
|
5104
|
+
|
5105
|
+
rv = {
|
5106
|
+
"vp": vp,
|
5107
|
+
"sz": sz,
|
5108
|
+
"ip": ip,
|
5109
|
+
"at": at,
|
5110
|
+
"nfk": nfk,
|
5111
|
+
"adm": adm,
|
5112
|
+
}
|
5113
|
+
if nfk:
|
5114
|
+
rv["ap"] = vol.canonical(vjoin(rd, fn))
|
5115
|
+
rv["fk_alg"] = fk_alg
|
5116
|
+
|
5117
|
+
ret.append(rv)
|
5118
|
+
if len(ret) > 2000:
|
5119
|
+
ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
|
5120
|
+
ret = ret[:1000]
|
5121
|
+
|
5122
|
+
n -= 1
|
5123
|
+
if not n:
|
5124
|
+
break
|
5125
|
+
|
5126
|
+
ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
|
5127
|
+
|
5128
|
+
if len(ret) > 1000:
|
5129
|
+
ret = ret[:1000]
|
5130
|
+
|
5131
|
+
for rv in ret:
|
5132
|
+
rv["vp"] = quotep(rv["vp"])
|
5133
|
+
nfk = rv.pop("nfk")
|
5134
|
+
if not nfk:
|
5135
|
+
continue
|
5136
|
+
|
5137
|
+
alg = rv.pop("fk_alg")
|
5138
|
+
ap = rv.pop("ap")
|
5139
|
+
try:
|
5140
|
+
st = bos.stat(ap)
|
5141
|
+
except:
|
5142
|
+
continue
|
5143
|
+
|
5144
|
+
fk = self.gen_fk(
|
5145
|
+
alg, self.args.fk_salt, ap, st.st_size, 0 if ANYWIN else st.st_ino
|
5146
|
+
)
|
5147
|
+
rv["vp"] += "?k=" + fk[:nfk]
|
5148
|
+
|
5149
|
+
if self.args.ups_when:
|
5150
|
+
for rv in ret:
|
5151
|
+
adm = rv.pop("adm")
|
5152
|
+
if not adm:
|
5153
|
+
rv["ip"] = "(You)" if rv["ip"] == self.ip else "(?)"
|
5154
|
+
else:
|
5155
|
+
for rv in ret:
|
5156
|
+
adm = rv.pop("adm")
|
5157
|
+
if not adm:
|
5158
|
+
rv["ip"] = "(You)" if rv["ip"] == self.ip else "(?)"
|
5159
|
+
rv["at"] = 0
|
5160
|
+
|
5161
|
+
if self.is_vproxied:
|
5162
|
+
for v in ret:
|
5163
|
+
v["vp"] = self.args.SR + v["vp"]
|
5164
|
+
|
5165
|
+
now = time.time()
|
5166
|
+
self.log("%s #%d %.2fsec" % (lm, len(ret), now - t0))
|
5167
|
+
|
5168
|
+
ret2 = {"now": int(now), "filter": sfilt, "ups": ret}
|
5169
|
+
jtxt = json.dumps(ret2, separators=(",\n", ": "))
|
5170
|
+
if "j" in self.ouparam:
|
5171
|
+
self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
|
5172
|
+
return True
|
5173
|
+
|
5174
|
+
html = self.j2s("rups", this=self, v=jtxt)
|
5175
|
+
self.reply(html.encode("utf-8"), status=200)
|
5176
|
+
return True
|
5177
|
+
|
4987
5178
|
def tx_shares(self) :
|
4988
5179
|
if self.uname == "*":
|
4989
5180
|
self.loud_reply("you're not logged in")
|
@@ -5689,7 +5880,7 @@ class HttpCli(object):
|
|
5689
5880
|
linf = stats.get(fn) or bos.lstat(fspath)
|
5690
5881
|
inf = bos.stat(fspath) if stat.S_ISLNK(linf.st_mode) else linf
|
5691
5882
|
except:
|
5692
|
-
self.log("broken symlink:
|
5883
|
+
self.log("broken symlink: %r" % (fspath,))
|
5693
5884
|
continue
|
5694
5885
|
|
5695
5886
|
is_dir = stat.S_ISDIR(inf.st_mode)
|
@@ -5803,8 +5994,7 @@ class HttpCli(object):
|
|
5803
5994
|
erd_efn = s3enc(idx.mem_cur, rd, fn)
|
5804
5995
|
r = icur.execute(q, erd_efn)
|
5805
5996
|
except:
|
5806
|
-
|
5807
|
-
self.log(t.format(rd, fn, min_ex()))
|
5997
|
+
self.log("tag read error, %r / %r\n%s" % (rd, fn, min_ex()))
|
5808
5998
|
break
|
5809
5999
|
|
5810
6000
|
tags = {k: v for k, v in r}
|
@@ -5924,10 +6114,10 @@ class HttpCli(object):
|
|
5924
6114
|
if doc.lower().endswith(".md") and "exp" in vn.flags:
|
5925
6115
|
doctxt = self._expand(doctxt, vn.flags.get("exp_md") or [])
|
5926
6116
|
else:
|
5927
|
-
self.log("doc 2big:
|
6117
|
+
self.log("doc 2big: %r" % (doc,), 6)
|
5928
6118
|
doctxt = "( size of textfile exceeds serverside limit )"
|
5929
6119
|
else:
|
5930
|
-
self.log("doc 404:
|
6120
|
+
self.log("doc 404: %r" % (doc,), 6)
|
5931
6121
|
doctxt = "( textfile not found )"
|
5932
6122
|
|
5933
6123
|
if doctxt is not None:
|