copyparty 1.15.9__py3-none-any.whl → 1.16.0__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
@@ -2,7 +2,6 @@
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
4
  import argparse # typechk
5
- import calendar
6
5
  import copy
7
6
  import errno
8
7
  import gzip
@@ -19,7 +18,6 @@ import threading # typechk
19
18
  import time
20
19
  import uuid
21
20
  from datetime import datetime
22
- from email.utils import parsedate
23
21
  from operator import itemgetter
24
22
 
25
23
  import jinja2 # typechk
@@ -107,6 +105,7 @@ from .util import (
107
105
  unquotep,
108
106
  vjoin,
109
107
  vol_san,
108
+ vroots,
110
109
  vsplit,
111
110
  wrename,
112
111
  wunlink,
@@ -123,6 +122,11 @@ _ = (argparse, threading)
123
122
 
124
123
  NO_CACHE = {"Cache-Control": "no-cache"}
125
124
 
125
+ ALL_COOKIES = "k304 no304 js idxh dots cppwd cppws".split()
126
+
127
+ H_CONN_KEEPALIVE = "Connection: Keep-Alive"
128
+ H_CONN_CLOSE = "Connection: Close"
129
+
126
130
  LOGUES = [[0, ".prologue.html"], [1, ".epilogue.html"]]
127
131
 
128
132
  READMES = [[0, ["preadme.md", "PREADME.md"]], [1, ["readme.md", "README.md"]]]
@@ -177,6 +181,7 @@ class HttpCli(object):
177
181
  self.rem = " "
178
182
  self.vpath = " "
179
183
  self.vpaths = " "
184
+ self.dl_id = ""
180
185
  self.gctx = " " # additional context for garda
181
186
  self.trailing_slash = True
182
187
  self.uname = " "
@@ -628,7 +633,7 @@ class HttpCli(object):
628
633
  avn.can_access("", self.uname) if avn else [False] * 8
629
634
  )
630
635
  self.avn = avn
631
- self.vn = vn
636
+ self.vn = vn # note: do not dbv due to walk/zipgen
632
637
  self.rem = rem
633
638
 
634
639
  self.s.settimeout(self.args.s_tbody or None)
@@ -717,6 +722,11 @@ class HttpCli(object):
717
722
  except Pebkac:
718
723
  return False
719
724
 
725
+ finally:
726
+ if self.dl_id:
727
+ self.conn.hsrv.dli.pop(self.dl_id, None)
728
+ self.conn.hsrv.dls.pop(self.dl_id, None)
729
+
720
730
  def dip(self) :
721
731
  if self.args.plain_ip:
722
732
  return self.ip.replace(":", ".")
@@ -787,11 +797,11 @@ class HttpCli(object):
787
797
 
788
798
  def k304(self) :
789
799
  k304 = self.cookies.get("k304")
790
- return (
791
- k304 == "y"
792
- or (self.args.k304 == 2 and k304 != "n")
793
- or ("; Trident/" in self.ua and not k304)
794
- )
800
+ return k304 == "y" or (self.args.k304 == 2 and k304 != "n")
801
+
802
+ def no304(self) :
803
+ no304 = self.cookies.get("no304")
804
+ return no304 == "y" or (self.args.no304 == 2 and no304 != "n")
795
805
 
796
806
  def _build_html_head(self, maybe_html , kv ) :
797
807
  html = str(maybe_html)
@@ -826,25 +836,28 @@ class HttpCli(object):
826
836
  ) :
827
837
  response = ["%s %s %s" % (self.http_ver, status, HTTPCODE[status])]
828
838
 
829
- if length is not None:
830
- response.append("Content-Length: " + unicode(length))
831
-
832
- if status == 304 and self.k304():
833
- self.keepalive = False
834
-
835
- # close if unknown length, otherwise take client's preference
836
- response.append("Connection: " + ("Keep-Alive" if self.keepalive else "Close"))
837
- response.append("Date: " + formatdate())
838
-
839
839
  # headers{} overrides anything set previously
840
840
  if headers:
841
841
  self.out_headers.update(headers)
842
842
 
843
- # default to utf8 html if no content-type is set
844
- if not mime:
845
- mime = self.out_headers.get("Content-Type") or "text/html; charset=utf-8"
843
+ if status == 304:
844
+ self.out_headers.pop("Content-Length", None)
845
+ self.out_headers.pop("Content-Type", None)
846
+ self.out_headerlist.clear()
847
+ if self.k304():
848
+ self.keepalive = False
849
+ else:
850
+ if length is not None:
851
+ response.append("Content-Length: " + unicode(length))
852
+
853
+ if mime:
854
+ self.out_headers["Content-Type"] = mime
855
+ elif "Content-Type" not in self.out_headers:
856
+ self.out_headers["Content-Type"] = "text/html; charset=utf-8"
846
857
 
847
- self.out_headers["Content-Type"] = mime
858
+ # close if unknown length, otherwise take client's preference
859
+ response.append(H_CONN_KEEPALIVE if self.keepalive else H_CONN_CLOSE)
860
+ response.append("Date: " + formatdate())
848
861
 
849
862
  for k, zs in list(self.out_headers.items()) + self.out_headerlist:
850
863
  response.append("%s: %s" % (k, zs))
@@ -858,6 +871,19 @@ class HttpCli(object):
858
871
  self.cbonk(self.conn.hsrv.gmal, zs, "cc_hdr", "Cc in out-hdr")
859
872
  raise Pebkac(999)
860
873
 
874
+ if self.args.ohead and self.do_log:
875
+ keys = self.args.ohead
876
+ if "*" in keys:
877
+ lines = response[1:]
878
+ else:
879
+ lines = []
880
+ for zs in response[1:]:
881
+ if zs.split(":")[0].lower() in keys:
882
+ lines.append(zs)
883
+ for zs in lines:
884
+ hk, hv = zs.split(": ")
885
+ self.log("[O] {}: \033[33m[{}]".format(hk, hv), 5)
886
+
861
887
  response.append("\r\n")
862
888
  try:
863
889
  self.s.sendall("\r\n".join(response).encode("utf-8"))
@@ -937,13 +963,14 @@ class HttpCli(object):
937
963
 
938
964
  lines = [
939
965
  "%s %s %s" % (self.http_ver or "HTTP/1.1", status, HTTPCODE[status]),
940
- "Connection: Close",
966
+ H_CONN_CLOSE,
941
967
  ]
942
968
 
943
969
  if body:
944
970
  lines.append("Content-Length: " + unicode(len(body)))
945
971
 
946
- self.s.sendall("\r\n".join(lines).encode("utf-8") + b"\r\n\r\n" + body)
972
+ lines.append("\r\n")
973
+ self.s.sendall("\r\n".join(lines).encode("utf-8") + body)
947
974
 
948
975
  def urlq(self, add , rm ) :
949
976
  """
@@ -1170,6 +1197,9 @@ class HttpCli(object):
1170
1197
  if "move" in self.uparam:
1171
1198
  return self.handle_mv()
1172
1199
 
1200
+ if "copy" in self.uparam:
1201
+ return self.handle_cp()
1202
+
1173
1203
  if not self.vpath and self.ouparam:
1174
1204
  if "reload" in self.uparam:
1175
1205
  return self.handle_reload()
@@ -1177,12 +1207,6 @@ class HttpCli(object):
1177
1207
  if "stack" in self.uparam:
1178
1208
  return self.tx_stack()
1179
1209
 
1180
- if "ups" in self.uparam:
1181
- return self.tx_ups()
1182
-
1183
- if "k304" in self.uparam:
1184
- return self.set_k304()
1185
-
1186
1210
  if "setck" in self.uparam:
1187
1211
  return self.setck()
1188
1212
 
@@ -1195,9 +1219,16 @@ class HttpCli(object):
1195
1219
  if "shares" in self.uparam:
1196
1220
  return self.tx_shares()
1197
1221
 
1222
+ if "dls" in self.uparam:
1223
+ return self.tx_dls()
1224
+
1198
1225
  if "h" in self.uparam:
1199
1226
  return self.tx_mounts()
1200
1227
 
1228
+ if "ups" in self.uparam:
1229
+ # vpath is used for share translation
1230
+ return self.tx_ups()
1231
+
1201
1232
  if "rss" in self.uparam:
1202
1233
  return self.tx_rss()
1203
1234
 
@@ -1426,6 +1457,7 @@ class HttpCli(object):
1426
1457
  not self.args.no_scandir,
1427
1458
  [[True, False]],
1428
1459
  lstat="davrt" not in vn.flags,
1460
+ throw=True,
1429
1461
  )
1430
1462
  if not self.can_read:
1431
1463
  vfs_ls = []
@@ -1763,6 +1795,9 @@ class HttpCli(object):
1763
1795
  if "move" in self.uparam:
1764
1796
  return self.handle_mv()
1765
1797
 
1798
+ if "copy" in self.uparam:
1799
+ return self.handle_cp()
1800
+
1766
1801
  if "delete" in self.uparam:
1767
1802
  return self.handle_rm([])
1768
1803
 
@@ -2380,6 +2415,15 @@ class HttpCli(object):
2380
2415
  if "purl" in ret:
2381
2416
  ret["purl"] = self.args.SR + ret["purl"]
2382
2417
 
2418
+ if self.args.shr and self.vpath.startswith(self.args.shr1):
2419
+ # strip common suffix (uploader's folder structure)
2420
+ vp_req, vp_vfs = vroots(self.vpath, vjoin(dbv.vpath, vrem))
2421
+ if not ret["purl"].startswith(vp_vfs):
2422
+ t = "share-mapping failed; req=[%s] dbv=[%s] vrem=[%s] n1=[%s] n2=[%s] purl=[%s]"
2423
+ zt = (self.vpath, dbv.vpath, vrem, vp_req, vp_vfs, ret["purl"])
2424
+ raise Pebkac(500, t % zt)
2425
+ ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :]
2426
+
2383
2427
  ret = json.dumps(ret)
2384
2428
  self.log(ret)
2385
2429
  self.reply(ret.encode("utf-8"), mime="application/json")
@@ -2472,7 +2516,11 @@ class HttpCli(object):
2472
2516
  chashes.append(siblings[n : n + clen])
2473
2517
 
2474
2518
  vfs, _ = self.asrv.vfs.get(self.vpath, self.uname, False, True)
2475
- ptop = (vfs.dbv or vfs).realpath
2519
+ ptop = vfs.get_dbv("")[0].realpath
2520
+ # if this is a share, then get_dbv has been overridden to return
2521
+ # the dbv (which does not exist as a property). And its realpath
2522
+ # could point into the middle of its origin vfs node, meaning it
2523
+ # is not necessarily registered with up2k, so get_dbv is crucial
2476
2524
 
2477
2525
  broker = self.conn.hsrv.broker
2478
2526
  x = broker.ask("up2k.handle_chunks", ptop, wark, chashes)
@@ -3378,25 +3426,29 @@ class HttpCli(object):
3378
3426
  self.reply(response.encode("utf-8"))
3379
3427
  return True
3380
3428
 
3381
- def _chk_lastmod(self, file_ts ) :
3429
+ def _chk_lastmod(self, file_ts ) :
3430
+ # ret: lastmod, do_send, can_range
3382
3431
  file_lastmod = formatdate(file_ts)
3383
- cli_lastmod = self.headers.get("if-modified-since")
3384
- if cli_lastmod:
3385
- try:
3386
- # some browser append "; length=573"
3387
- cli_lastmod = cli_lastmod.split(";")[0].strip()
3388
- cli_dt = parsedate(cli_lastmod)
3389
- cli_ts = calendar.timegm(cli_dt)
3390
- return file_lastmod, int(file_ts) > int(cli_ts)
3391
- except Exception as ex:
3392
- self.log(
3393
- "lastmod {}\nremote: [{}]\n local: [{}]".format(
3394
- repr(ex), cli_lastmod, file_lastmod
3395
- )
3396
- )
3397
- return file_lastmod, file_lastmod != cli_lastmod
3432
+ c_ifrange = self.headers.get("if-range")
3433
+ c_lastmod = self.headers.get("if-modified-since")
3434
+
3435
+ if not c_ifrange and not c_lastmod:
3436
+ return file_lastmod, True, True
3437
+
3438
+ if c_ifrange and c_ifrange != file_lastmod:
3439
+ t = "sending entire file due to If-Range; cli(%s) file(%s)"
3440
+ self.log(t % (c_ifrange, file_lastmod), 6)
3441
+ return file_lastmod, True, False
3442
+
3443
+ do_send = c_lastmod != file_lastmod
3444
+ if do_send and c_lastmod:
3445
+ t = "sending body due to If-Modified-Since cli(%s) file(%s)"
3446
+ self.log(t % (c_lastmod, file_lastmod), 6)
3447
+ elif not do_send and self.no304():
3448
+ do_send = True
3449
+ self.log("sending body due to no304")
3398
3450
 
3399
- return file_lastmod, True
3451
+ return file_lastmod, do_send, True
3400
3452
 
3401
3453
  def _use_dirkey(self, vn , ap ) :
3402
3454
  if self.can_read or not self.can_get:
@@ -3546,7 +3598,7 @@ class HttpCli(object):
3546
3598
  # if-modified
3547
3599
 
3548
3600
  if file_ts > 0:
3549
- file_lastmod, do_send = self._chk_lastmod(int(file_ts))
3601
+ file_lastmod, do_send, _ = self._chk_lastmod(int(file_ts))
3550
3602
  self.out_headers["Last-Modified"] = file_lastmod
3551
3603
  if not do_send:
3552
3604
  status = 304
@@ -3629,6 +3681,8 @@ class HttpCli(object):
3629
3681
  self.args.s_wr_sz,
3630
3682
  self.args.s_wr_slp,
3631
3683
  not self.args.no_poll,
3684
+ {},
3685
+ "",
3632
3686
  )
3633
3687
  res.close()
3634
3688
 
@@ -3708,7 +3762,7 @@ class HttpCli(object):
3708
3762
  #
3709
3763
  # if-modified
3710
3764
 
3711
- file_lastmod, do_send = self._chk_lastmod(int(file_ts))
3765
+ file_lastmod, do_send, can_range = self._chk_lastmod(int(file_ts))
3712
3766
  self.out_headers["Last-Modified"] = file_lastmod
3713
3767
  if not do_send:
3714
3768
  status = 304
@@ -3752,7 +3806,14 @@ class HttpCli(object):
3752
3806
 
3753
3807
  # let's not support 206 with compression
3754
3808
  # and multirange / multipart is also not-impl (mostly because calculating contentlength is a pain)
3755
- if do_send and not is_compressed and hrange and file_sz and "," not in hrange:
3809
+ if (
3810
+ do_send
3811
+ and not is_compressed
3812
+ and hrange
3813
+ and can_range
3814
+ and file_sz
3815
+ and "," not in hrange
3816
+ ):
3756
3817
  try:
3757
3818
  if not hrange.lower().startswith("bytes"):
3758
3819
  raise Exception()
@@ -3836,6 +3897,19 @@ class HttpCli(object):
3836
3897
  self.send_headers(length=upper - lower, status=status, mime=mime)
3837
3898
  return True
3838
3899
 
3900
+ dls = self.conn.hsrv.dls
3901
+ if upper - lower > 0x400000: # 4m
3902
+ now = time.time()
3903
+ self.dl_id = "%s:%s" % (self.ip, self.addr[1])
3904
+ dls[self.dl_id] = (now, 0)
3905
+ self.conn.hsrv.dli[self.dl_id] = (
3906
+ now,
3907
+ upper - lower,
3908
+ self.vn,
3909
+ self.vpath,
3910
+ self.uname,
3911
+ )
3912
+
3839
3913
  if ptop is not None:
3840
3914
  return self.tx_pipe(
3841
3915
  ptop, req_path, ap_data, job, lower, upper, status, mime, logmsg
@@ -3855,6 +3929,8 @@ class HttpCli(object):
3855
3929
  self.args.s_wr_sz,
3856
3930
  self.args.s_wr_slp,
3857
3931
  not self.args.no_poll,
3932
+ dls,
3933
+ self.dl_id,
3858
3934
  )
3859
3935
 
3860
3936
  if remains > 0:
@@ -4005,6 +4081,8 @@ class HttpCli(object):
4005
4081
  wr_sz,
4006
4082
  wr_slp,
4007
4083
  not self.args.no_poll,
4084
+ self.conn.hsrv.dls,
4085
+ self.dl_id,
4008
4086
  )
4009
4087
 
4010
4088
  spd = self._spd((upper - lower) - remains)
@@ -4090,6 +4168,18 @@ class HttpCli(object):
4090
4168
  self.log("transcoding to [{}]".format(cfmt))
4091
4169
  fgen = gfilter(fgen, self.thumbcli, self.uname, vpath, cfmt)
4092
4170
 
4171
+ now = time.time()
4172
+ self.dl_id = "%s:%s" % (self.ip, self.addr[1])
4173
+ self.conn.hsrv.dli[self.dl_id] = (
4174
+ now,
4175
+ 0,
4176
+ self.vn,
4177
+ "%s :%s" % (self.vpath, ext),
4178
+ self.uname,
4179
+ )
4180
+ dls = self.conn.hsrv.dls
4181
+ dls[self.dl_id] = (time.time(), 0)
4182
+
4093
4183
  bgen = packer(
4094
4184
  self.log,
4095
4185
  self.asrv,
@@ -4098,6 +4188,7 @@ class HttpCli(object):
4098
4188
  pre_crc="crc" in uarg,
4099
4189
  cmp=uarg if cancmp or uarg == "pax" else "",
4100
4190
  )
4191
+ n = 0
4101
4192
  bsent = 0
4102
4193
  for buf in bgen.gen():
4103
4194
  if not buf:
@@ -4111,6 +4202,11 @@ class HttpCli(object):
4111
4202
  bgen.stop()
4112
4203
  break
4113
4204
 
4205
+ n += 1
4206
+ if n >= 4:
4207
+ n = 0
4208
+ dls[self.dl_id] = (time.time(), bsent)
4209
+
4114
4210
  spd = self._spd(bsent)
4115
4211
  self.log("{}, {}".format(logmsg, spd))
4116
4212
  return True
@@ -4223,7 +4319,7 @@ class HttpCli(object):
4223
4319
  sz_md = len(lead) + len(fullfile)
4224
4320
 
4225
4321
  file_ts = int(max(ts_md, self.E.t0))
4226
- file_lastmod, do_send = self._chk_lastmod(file_ts)
4322
+ file_lastmod, do_send, _ = self._chk_lastmod(file_ts)
4227
4323
  self.out_headers["Last-Modified"] = file_lastmod
4228
4324
  self.out_headers.update(NO_CACHE)
4229
4325
  status = 200 if do_send else 304
@@ -4367,6 +4463,32 @@ class HttpCli(object):
4367
4463
  }
4368
4464
 
4369
4465
 
4466
+ dls = dl_list = []
4467
+ if self.conn.hsrv.tdls:
4468
+ zi = self.args.dl_list
4469
+ if zi == 2 or (zi == 1 and self.avol):
4470
+ dl_list = self.get_dls()
4471
+ for t0, t1, sent, sz, vp, dl_id, uname in dl_list:
4472
+ rem = sz - sent
4473
+ td = max(0.1, now - t0)
4474
+ rd, fn = vsplit(vp)
4475
+ if not rd:
4476
+ rd = "/"
4477
+ erd = quotep(rd)
4478
+ rds = rd.replace("/", " / ")
4479
+ spd = humansize(sent / td, True) + "/s"
4480
+ hsent = humansize(sent, True)
4481
+ idle = s2hms(now - t1, True)
4482
+ usr = "%s @%s" % (dl_id, uname) if dl_id else uname
4483
+ if sz and sent and td:
4484
+ eta = s2hms((sz - sent) / (sent / td), True)
4485
+ perc = int(100 * sent / sz)
4486
+ else:
4487
+ eta = perc = "--"
4488
+
4489
+ fn = html_escape(fn) if fn else self.conn.hsrv.iiam
4490
+ dls.append((perc, hsent, spd, eta, idle, usr, erd, rds, fn))
4491
+
4370
4492
  fmt = self.uparam.get("ls", "")
4371
4493
  if not fmt and (self.ua.startswith("curl/") or self.ua.startswith("fetch")):
4372
4494
  fmt = "v"
@@ -4388,6 +4510,12 @@ class HttpCli(object):
4388
4510
  txt += "\n%s" % (", ".join((str(x) for x in zt)),)
4389
4511
  txt += "\n"
4390
4512
 
4513
+ if dls:
4514
+ txt += "\n\nactive downloads:"
4515
+ for zt in dls:
4516
+ txt += "\n%s" % (", ".join((str(x) for x in zt)),)
4517
+ txt += "\n"
4518
+
4391
4519
  if rvol:
4392
4520
  txt += "\nyou can browse:"
4393
4521
  for v in rvol:
@@ -4409,8 +4537,9 @@ class HttpCli(object):
4409
4537
  rvol=rvol,
4410
4538
  wvol=wvol,
4411
4539
  avol=avol,
4412
- in_shr=self.args.shr and self.vpath.startswith(self.args.shr[1:]),
4540
+ in_shr=self.args.shr and self.vpath.startswith(self.args.shr1),
4413
4541
  vstate=vstate,
4542
+ dls=dls,
4414
4543
  ups=ups,
4415
4544
  scanning=vs["scanning"],
4416
4545
  hashq=vs["hashq"],
@@ -4419,7 +4548,9 @@ class HttpCli(object):
4419
4548
  dbwt=vs["dbwt"],
4420
4549
  url_suf=suf,
4421
4550
  k304=self.k304(),
4551
+ no304=self.no304(),
4422
4552
  k304vis=self.args.k304 > 0,
4553
+ no304vis=self.args.no304 > 0,
4423
4554
  ver=S_VERSION if self.args.ver else "",
4424
4555
  chpw=self.args.chpw and self.uname != "*",
4425
4556
  ahttps="" if self.is_https else "https://" + self.host + self.req,
@@ -4427,29 +4558,21 @@ class HttpCli(object):
4427
4558
  self.reply(html.encode("utf-8"))
4428
4559
  return True
4429
4560
 
4430
- def set_k304(self) :
4431
- v = self.uparam["k304"].lower()
4432
- if v in "yn":
4433
- dur = 86400 * 299
4434
- else:
4435
- dur = 0
4436
- v = "x"
4437
-
4438
- ck = gencookie("k304", v, self.args.R, False, dur)
4439
- self.out_headerlist.append(("Set-Cookie", ck))
4440
- self.redirect("", "?h#cc")
4441
- return True
4442
-
4443
4561
  def setck(self) :
4444
4562
  k, v = self.uparam["setck"].split("=", 1)
4445
- t = 0 if v == "" else 86400 * 299
4563
+ t = 0 if v in ("", "x") else 86400 * 299
4446
4564
  ck = gencookie(k, v, self.args.R, False, t)
4447
4565
  self.out_headerlist.append(("Set-Cookie", ck))
4448
- self.reply(b"o7\n")
4566
+ if "cc" in self.ouparam:
4567
+ self.redirect("", "?h#cc")
4568
+ else:
4569
+ self.reply(b"o7\n")
4449
4570
  return True
4450
4571
 
4451
4572
  def set_cfg_reset(self) :
4452
- for k in ("k304", "js", "idxh", "dots", "cppwd", "cppws"):
4573
+ for k in ALL_COOKIES:
4574
+ if k not in self.cookies:
4575
+ continue
4453
4576
  cookie = gencookie(k, "x", self.args.R, False)
4454
4577
  self.out_headerlist.append(("Set-Cookie", cookie))
4455
4578
 
@@ -4479,8 +4602,14 @@ class HttpCli(object):
4479
4602
 
4480
4603
  t = t.format(self.args.SR)
4481
4604
  qv = quotep(self.vpaths) + self.ourlq()
4482
- in_shr = self.args.shr and self.vpath.startswith(self.args.shr[1:])
4483
- html = self.j2s("splash", this=self, qvpath=qv, in_shr=in_shr, msg=t)
4605
+ html = self.j2s(
4606
+ "splash",
4607
+ this=self,
4608
+ qvpath=qv,
4609
+ msg=t,
4610
+ in_shr=self.args.shr and self.vpath.startswith(self.args.shr1),
4611
+ ahttps="" if self.is_https else "https://" + self.host + self.req,
4612
+ )
4484
4613
  self.reply(html.encode("utf-8"), status=rc)
4485
4614
  return True
4486
4615
 
@@ -4528,7 +4657,7 @@ class HttpCli(object):
4528
4657
  if self.args.no_reload:
4529
4658
  raise Pebkac(403, "the reload feature is disabled in server config")
4530
4659
 
4531
- x = self.conn.hsrv.broker.ask("reload")
4660
+ x = self.conn.hsrv.broker.ask("reload", True, True)
4532
4661
  return self.redirect("", "?h", x.get(), "return to", False)
4533
4662
 
4534
4663
  def tx_stack(self) :
@@ -4631,6 +4760,40 @@ class HttpCli(object):
4631
4760
  ret["a"] = dirs
4632
4761
  return ret
4633
4762
 
4763
+ def get_dls(self) :
4764
+ ret = []
4765
+ dls = self.conn.hsrv.tdls
4766
+ for dl_id, (t0, sz, vn, vp, uname) in self.conn.hsrv.tdli.items():
4767
+ t1, sent = dls[dl_id]
4768
+ if sent > 0x100000: # 1m; buffers 2~4
4769
+ sent -= 0x100000
4770
+ if self.uname not in vn.axs.uread:
4771
+ vp = ""
4772
+ elif self.uname not in vn.axs.udot and (vp.startswith(".") or "/." in vp):
4773
+ vp = ""
4774
+ if self.uname not in vn.axs.uadmin:
4775
+ dl_id = uname = ""
4776
+
4777
+ ret.append([t0, t1, sent, sz, vp, dl_id, uname])
4778
+ return ret
4779
+
4780
+ def tx_dls(self) :
4781
+ ret = [
4782
+ {
4783
+ "t0": x[0],
4784
+ "t1": x[1],
4785
+ "sent": x[2],
4786
+ "size": x[3],
4787
+ "path": x[4],
4788
+ "conn": x[5],
4789
+ "uname": x[6],
4790
+ }
4791
+ for x in self.get_dls()
4792
+ ]
4793
+ zs = json.dumps(ret, separators=(",\n", ": "))
4794
+ self.reply(zs.encode("utf-8", "replace"), mime="application/json")
4795
+ return True
4796
+
4634
4797
  def tx_ups(self) :
4635
4798
  idx = self.conn.get_u2idx()
4636
4799
  if not idx or not hasattr(idx, "p_end"):
@@ -4642,6 +4805,11 @@ class HttpCli(object):
4642
4805
  lm = "ups [{}]".format(filt)
4643
4806
  self.log(lm)
4644
4807
 
4808
+ if self.args.shr and self.vpath.startswith(self.args.shr1):
4809
+ shr_dbv, shr_vrem = self.vn.get_dbv(self.rem)
4810
+ else:
4811
+ shr_dbv = None
4812
+
4645
4813
  ret = []
4646
4814
  t0 = time.time()
4647
4815
  lim = time.time() - self.args.unpost
@@ -4662,7 +4830,12 @@ class HttpCli(object):
4662
4830
  else:
4663
4831
  allvols = list(self.asrv.vfs.all_vols.values())
4664
4832
 
4665
- allvols = [x for x in allvols if "e2d" in x.flags]
4833
+ allvols = [
4834
+ x
4835
+ for x in allvols
4836
+ if "e2d" in x.flags
4837
+ and ("*" in x.axs.uwrite or self.uname in x.axs.uwrite or x == shr_dbv)
4838
+ ]
4666
4839
 
4667
4840
  for vol in allvols:
4668
4841
  cur = idx.get_cur(vol)
@@ -4712,6 +4885,15 @@ class HttpCli(object):
4712
4885
 
4713
4886
  ret = ret[:2000]
4714
4887
 
4888
+ if shr_dbv:
4889
+ # translate vpaths from share-target to share-url
4890
+ # to satisfy access checks
4891
+ vp_shr, vp_vfs = vroots(self.vpath, vjoin(shr_dbv.vpath, shr_vrem))
4892
+ for v in ret:
4893
+ vp = v["vp"]
4894
+ if vp.startswith(vp_vfs):
4895
+ v["vp"] = vp_shr + vp[len(vp_vfs) :]
4896
+
4715
4897
  if self.is_vproxied:
4716
4898
  for v in ret:
4717
4899
  v["vp"] = self.args.SR + v["vp"]
@@ -4796,7 +4978,7 @@ class HttpCli(object):
4796
4978
 
4797
4979
  cur.connection.commit()
4798
4980
  if reload:
4799
- self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
4981
+ self.conn.hsrv.broker.ask("reload", False, False).get()
4800
4982
  self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
4801
4983
 
4802
4984
  self.redirect(self.args.SRS + "?shares")
@@ -4841,7 +5023,7 @@ class HttpCli(object):
4841
5023
  if m:
4842
5024
  raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],))
4843
5025
 
4844
- if vp.startswith(self.args.shr[1:]):
5026
+ if vp.startswith(self.args.shr1):
4845
5027
  raise Pebkac(400, "yo dawg...")
4846
5028
 
4847
5029
  cur = idx.get_shr()
@@ -4887,7 +5069,7 @@ class HttpCli(object):
4887
5069
  cur.execute(q, (skey, fn))
4888
5070
 
4889
5071
  cur.connection.commit()
4890
- self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
5072
+ self.conn.hsrv.broker.ask("reload", False, False).get()
4891
5073
  self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
4892
5074
 
4893
5075
  fn = quotep(fns[0]) if len(fns) == 1 else ""
@@ -4938,16 +5120,39 @@ class HttpCli(object):
4938
5120
  return self._mv(self.vpath, dst.lstrip("/"))
4939
5121
 
4940
5122
  def _mv(self, vsrc , vdst ) :
4941
- if not self.can_move:
4942
- raise Pebkac(403, "not allowed for user " + self.uname)
4943
-
4944
5123
  if self.args.no_mv:
4945
5124
  raise Pebkac(403, "the rename/move feature is disabled in server config")
4946
5125
 
5126
+ self.asrv.vfs.get(vsrc, self.uname, True, False, True)
5127
+ self.asrv.vfs.get(vdst, self.uname, False, True)
5128
+
4947
5129
  x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
4948
5130
  self.loud_reply(x.get(), status=201)
4949
5131
  return True
4950
5132
 
5133
+ def handle_cp(self) :
5134
+ # full path of new loc (incl filename)
5135
+ dst = self.uparam.get("copy")
5136
+
5137
+ if self.is_vproxied and dst and dst.startswith(self.args.SR):
5138
+ dst = dst[len(self.args.RS) :]
5139
+
5140
+ if not dst:
5141
+ raise Pebkac(400, "need dst vpath")
5142
+
5143
+ return self._cp(self.vpath, dst.lstrip("/"))
5144
+
5145
+ def _cp(self, vsrc , vdst ) :
5146
+ if self.args.no_cp:
5147
+ raise Pebkac(403, "the copy feature is disabled in server config")
5148
+
5149
+ self.asrv.vfs.get(vsrc, self.uname, True, False)
5150
+ self.asrv.vfs.get(vdst, self.uname, False, True)
5151
+
5152
+ x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
5153
+ self.loud_reply(x.get(), status=201)
5154
+ return True
5155
+
4951
5156
  def tx_ls(self, ls ) :
4952
5157
  dirs = ls["dirs"]
4953
5158
  files = ls["files"]
@@ -5377,6 +5582,7 @@ class HttpCli(object):
5377
5582
  not self.args.no_scandir,
5378
5583
  [[True, False], [False, True]],
5379
5584
  lstat="lt" in self.uparam,
5585
+ throw=True,
5380
5586
  )
5381
5587
  stats = {k: v for k, v in vfs_ls}
5382
5588
  ls_names = [x[0] for x in vfs_ls]