copyparty 1.14.0__py3-none-any.whl → 1.14.2__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/__main__.py CHANGED
@@ -969,8 +969,8 @@ def add_fs(ap):
969
969
  def add_share(ap):
970
970
  db_path = os.path.join(E.cfg, "shares.db")
971
971
  ap2 = ap.add_argument_group('share-url options')
972
- ap2.add_argument("--shr", metavar="URL", default="", help="base url for shared files, for example [\033[32m/share\033[0m] (must be a toplevel subfolder)")
973
- ap2.add_argument("--shr-db", metavar="PATH", default=db_path, help="database to store shares in")
972
+ ap2.add_argument("--shr", metavar="DIR", default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]")
973
+ ap2.add_argument("--shr-db", metavar="FILE", default=db_path, help="database to store shares in")
974
974
  ap2.add_argument("--shr-adm", metavar="U,U", default="", help="comma-separated list of users allowed to view/delete any share")
975
975
  ap2.add_argument("--shr-v", action="store_true", help="debug")
976
976
 
@@ -1406,7 +1406,7 @@ def add_ui(ap, retry):
1406
1406
  ap2 = ap.add_argument_group('ui options')
1407
1407
  ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
1408
1408
  ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
1409
- ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor\033[0m")
1409
+ ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor chi\033[0m")
1410
1410
  ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..7)")
1411
1411
  ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
1412
1412
  ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 14, 0)
3
+ VERSION = (1, 14, 2)
4
4
  CODENAME = "one step forward"
5
- BUILD_DT = (2024, 8, 18)
5
+ BUILD_DT = (2024, 8, 23)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
copyparty/authsrv.py CHANGED
@@ -35,6 +35,7 @@ from .util import (
35
35
  odfusion,
36
36
  relchk,
37
37
  statdir,
38
+ ub64enc,
38
39
  uncyg,
39
40
  undot,
40
41
  unhumanize,
@@ -337,6 +338,7 @@ class VFS(object):
337
338
  self.dbv = None # closest full/non-jump parent
338
339
  self.lim = None # upload limits; only set for dbv
339
340
  self.shr_src = None # source vfs+rem of a share
341
+ self.shr_files = set() # filenames to include from shr_src
340
342
  self.aread = {}
341
343
  self.awrite = {}
342
344
  self.amove = {}
@@ -362,6 +364,7 @@ class VFS(object):
362
364
  self.all_vps = []
363
365
 
364
366
  self.get_dbv = self._get_dbv
367
+ self.ls = self._ls
365
368
 
366
369
  def __repr__(self) :
367
370
  return "VFS(%s)" % (
@@ -558,7 +561,26 @@ class VFS(object):
558
561
  ad, fn = os.path.split(ap)
559
562
  return os.path.join(absreal(ad), fn)
560
563
 
561
- def ls(
564
+ def _ls_nope(
565
+ self, *a, **ka
566
+ ) :
567
+ raise Pebkac(500, "nope.avi")
568
+
569
+ def _ls_shr(
570
+ self,
571
+ rem ,
572
+ uname ,
573
+ scandir ,
574
+ permsets ,
575
+ lstat = False,
576
+ ) :
577
+ """replaces _ls for certain shares (single-file, or file selection)"""
578
+ vn, rem = self.shr_src # type: ignore
579
+ abspath, real, _ = vn.ls(rem, "\n", scandir, permsets, lstat)
580
+ real = [x for x in real if os.path.basename(x[0]) in self.shr_files]
581
+ return abspath, real, {}
582
+
583
+ def _ls(
562
584
  self,
563
585
  rem ,
564
586
  uname ,
@@ -1501,14 +1523,14 @@ class AuthSrv(object):
1501
1523
  import sqlite3
1502
1524
 
1503
1525
  shv = VFS(self.log_func, "", shr, AXS(), {"d2d": True})
1504
- par = vfs.all_vols[""]
1505
1526
 
1506
1527
  db_path = self.args.shr_db
1507
1528
  db = sqlite3.connect(db_path)
1508
1529
  cur = db.cursor()
1530
+ cur2 = db.cursor()
1509
1531
  now = time.time()
1510
1532
  for row in cur.execute("select * from sh"):
1511
- s_k, s_pw, s_vp, s_pr, s_st, s_un, s_t0, s_t1 = row
1533
+ s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1 = row
1512
1534
  if s_t1 and s_t1 < now:
1513
1535
  continue
1514
1536
 
@@ -1517,7 +1539,10 @@ class AuthSrv(object):
1517
1539
  self.log(t % (s_pr, s_k, s_un, s_vp))
1518
1540
 
1519
1541
  if s_pw:
1520
- sun = "s_%s" % (s_k,)
1542
+ # gotta reuse the "account" for all shares with this pw,
1543
+ # so do a light scramble as this appears in the web-ui
1544
+ zs = ub64enc(hashlib.sha512(s_pw.encode("utf-8")).digest())[4:16]
1545
+ sun = "s_%s" % (zs.decode("utf-8"),)
1521
1546
  acct[sun] = s_pw
1522
1547
  else:
1523
1548
  sun = "*"
@@ -1532,13 +1557,14 @@ class AuthSrv(object):
1532
1557
  # don't know the abspath yet + wanna ensure the user
1533
1558
  # still has the privs they granted, so nullmap it
1534
1559
  shv.nodes[s_k] = VFS(
1535
- self.log_func, "", "%s/%s" % (shr, s_k), s_axs, par.flags.copy()
1560
+ self.log_func, "", "%s/%s" % (shr, s_k), s_axs, shv.flags.copy()
1536
1561
  )
1537
1562
 
1538
1563
  vfs.nodes[shr] = vfs.all_vols[shr] = shv
1539
1564
  for vol in shv.nodes.values():
1540
1565
  vfs.all_vols[vol.vpath] = vol
1541
1566
  vol.get_dbv = vol._get_share_src
1567
+ vol.ls = vol._ls_nope
1542
1568
 
1543
1569
  zss = set(acct)
1544
1570
  zss.update(self.idp_accs)
@@ -2048,6 +2074,9 @@ class AuthSrv(object):
2048
2074
  if not self.warn_anonwrite or verbosity < 5:
2049
2075
  break
2050
2076
 
2077
+ if enshare and (zv.vpath == shr or zv.vpath.startswith(shrs)):
2078
+ continue
2079
+
2051
2080
  t += '\n\033[36m"/{}" \033[33m{}\033[0m'.format(zv.vpath, zv.realpath)
2052
2081
  for txt, attr in [
2053
2082
  [" read", "uread"],
@@ -2154,10 +2183,9 @@ class AuthSrv(object):
2154
2183
  if x != shr and not x.startswith(shrs)
2155
2184
  }
2156
2185
 
2157
- assert cur # type: ignore
2158
- assert shv # type: ignore
2186
+ assert db and cur and cur2 and shv # type: ignore
2159
2187
  for row in cur.execute("select * from sh"):
2160
- s_k, s_pw, s_vp, s_pr, s_st, s_un, s_t0, s_t1 = row
2188
+ s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1 = row
2161
2189
  shn = shv.nodes.get(s_k, None)
2162
2190
  if not shn:
2163
2191
  continue
@@ -2172,6 +2200,17 @@ class AuthSrv(object):
2172
2200
  shv.nodes.pop(s_k)
2173
2201
  continue
2174
2202
 
2203
+ fns = []
2204
+ if s_nf:
2205
+ q = "select vp from sf where k = ?"
2206
+ for (s_fn,) in cur2.execute(q, (s_k,)):
2207
+ fns.append(s_fn)
2208
+
2209
+ shn.shr_files = set(fns)
2210
+ shn.ls = shn._ls_shr
2211
+ else:
2212
+ shn.ls = shn._ls
2213
+
2175
2214
  shn.shr_src = (s_vfs, s_rem)
2176
2215
  shn.realpath = s_vfs.canonical(s_rem)
2177
2216
 
@@ -2191,6 +2230,10 @@ class AuthSrv(object):
2191
2230
  # hide subvolume
2192
2231
  vn.nodes[zs] = VFS(self.log_func, "", "", AXS(), {})
2193
2232
 
2233
+ cur2.close()
2234
+ cur.close()
2235
+ db.close()
2236
+
2194
2237
  def chpw(self, broker , uname, pw) :
2195
2238
  if not self.args.chpw:
2196
2239
  return False, "feature disabled in server config"
copyparty/httpcli.py CHANGED
@@ -3262,7 +3262,8 @@ class HttpCli(object):
3262
3262
  raise Exception("not found in registry")
3263
3263
  self.pipes.set(req_path, job)
3264
3264
  except Exception as ex:
3265
- self.log("will not pipe [%s]; %s" % (ap_data, ex), 6)
3265
+ if getattr(ex, "errno", 0) != errno.ENOENT:
3266
+ self.log("will not pipe [%s]; %s" % (ap_data, ex), 6)
3266
3267
  ptop = None
3267
3268
 
3268
3269
  #
@@ -3954,6 +3955,7 @@ class HttpCli(object):
3954
3955
  rvol=rvol,
3955
3956
  wvol=wvol,
3956
3957
  avol=avol,
3958
+ in_shr=self.args.shr and self.vpath.startswith(self.args.shr[1:]),
3957
3959
  vstate=vstate,
3958
3960
  scanning=vs["scanning"],
3959
3961
  hashq=vs["hashq"],
@@ -4002,10 +4004,10 @@ class HttpCli(object):
4002
4004
  def tx_404(self, is_403 = False) :
4003
4005
  rc = 404
4004
4006
  if self.args.vague_403:
4005
- t = '<h1 id="n">404 not found &nbsp;┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try logging in or <a href="{}/?h">go home</a></p>'
4006
- pt = "404 not found ┐( ´ -`)┌ (or maybe you don't have access -- try logging in)"
4007
+ t = '<h1 id="n">404 not found &nbsp;┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try a password or <a href="{}/?h">go home</a></p>'
4008
+ pt = "404 not found ┐( ´ -`)┌ (or maybe you don't have access -- try a password)"
4007
4009
  elif is_403:
4008
- t = '<h1 id="p">403 forbiddena &nbsp;~┻━┻</h1><p id="q">you\'ll have to log in or <a href="{}/?h">go home</a></p>'
4010
+ t = '<h1 id="p">403 forbiddena &nbsp;~┻━┻</h1><p id="q">use a password or <a href="{}/?h">go home</a></p>'
4009
4011
  pt = "403 forbiddena ~┻━┻ (you'll have to log in)"
4010
4012
  rc = 403
4011
4013
  else:
@@ -4022,7 +4024,8 @@ class HttpCli(object):
4022
4024
 
4023
4025
  t = t.format(self.args.SR)
4024
4026
  qv = quotep(self.vpaths) + self.ourlq()
4025
- html = self.j2s("splash", this=self, qvpath=qv, msg=t)
4027
+ in_shr = self.args.shr and self.vpath.startswith(self.args.shr[1:])
4028
+ html = self.j2s("splash", this=self, qvpath=qv, in_shr=in_shr, msg=t)
4026
4029
  self.reply(html.encode("utf-8"), status=rc)
4027
4030
  return True
4028
4031
 
@@ -4340,11 +4343,31 @@ class HttpCli(object):
4340
4343
  self.log("handle_share: " + json.dumps(req, indent=4))
4341
4344
 
4342
4345
  skey = req["k"]
4343
- vp = req["vp"].strip("/")
4346
+ vps = req["vp"]
4347
+ fns = []
4348
+ if len(vps) == 1:
4349
+ vp = vps[0]
4350
+ if not vp.endswith("/"):
4351
+ vp, zs = vp.rsplit("/", 1)
4352
+ fns = [zs]
4353
+ else:
4354
+ for zs in vps:
4355
+ if zs.endswith("/"):
4356
+ t = "you cannot select more than one folder, or mix flies and folders in one selection"
4357
+ raise Pebkac(400, t)
4358
+ vp = vps[0].rsplit("/", 1)[0]
4359
+ for zs in vps:
4360
+ vp2, fn = zs.rsplit("/", 1)
4361
+ fns.append(fn)
4362
+ if vp != vp2:
4363
+ t = "mismatching base paths in selection:\n [%s]\n [%s]"
4364
+ raise Pebkac(400, t % (vp, vp2))
4365
+
4366
+ vp = vp.strip("/")
4344
4367
  if self.is_vproxied and (vp == self.args.R or vp.startswith(self.args.RS)):
4345
4368
  vp = vp[len(self.args.RS) :]
4346
4369
 
4347
- m = re.search(r"([^0-9a-zA-Z_\.-]|\.\.|^\.)", skey)
4370
+ m = re.search(r"([^0-9a-zA-Z_-])", skey)
4348
4371
  if m:
4349
4372
  raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],))
4350
4373
 
@@ -4371,29 +4394,41 @@ class HttpCli(object):
4371
4394
  except:
4372
4395
  raise Pebkac(400, "you dont have all the perms you tried to grant")
4373
4396
 
4374
- ap = vfs.canonical(rem)
4375
- st = bos.stat(ap)
4376
- ist = 2 if stat.S_ISDIR(st.st_mode) else 1
4397
+ ap, reals, _ = vfs.ls(
4398
+ rem, self.uname, not self.args.no_scandir, [[s_rd, s_wr, s_mv, s_del]]
4399
+ )
4400
+ rfns = set([x[0] for x in reals])
4401
+ for fn in fns:
4402
+ if fn not in rfns:
4403
+ raise Pebkac(400, "selected file not found on disk: [%s]" % (fn,))
4377
4404
 
4378
4405
  pw = req.get("pw") or ""
4379
4406
  now = int(time.time())
4380
4407
  sexp = req["exp"]
4381
- exp = now + int(sexp) * 60 if sexp else 0
4408
+ exp = int(sexp) if sexp else 0
4409
+ exp = now + exp * 60 if exp else 0
4382
4410
  pr = "".join(zc for zc, zb in zip("rwmd", (s_rd, s_wr, s_mv, s_del)) if zb)
4383
4411
 
4384
4412
  q = "insert into sh values (?,?,?,?,?,?,?,?)"
4385
- cur.execute(q, (skey, pw, vp, pr, ist, self.uname, now, exp))
4386
- cur.connection.commit()
4413
+ cur.execute(q, (skey, pw, vp, pr, len(fns), self.uname, now, exp))
4414
+
4415
+ q = "insert into sf values (?,?)"
4416
+ for fn in fns:
4417
+ cur.execute(q, (skey, fn))
4387
4418
 
4419
+ cur.connection.commit()
4388
4420
  self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
4389
4421
  self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
4390
4422
 
4391
- surl = "%s://%s%s%s%s" % (
4423
+ fn = quotep(fns[0]) if len(fns) == 1 else ""
4424
+
4425
+ surl = "created share: %s://%s%s%s%s/%s" % (
4392
4426
  "https" if self.is_https else "http",
4393
4427
  self.host,
4394
4428
  self.args.SR,
4395
4429
  self.args.shr,
4396
4430
  skey,
4431
+ fn,
4397
4432
  )
4398
4433
  self.loud_reply(surl, status=201)
4399
4434
  return True
copyparty/svchub.py CHANGED
@@ -370,11 +370,18 @@ class SvcHub(object):
370
370
 
371
371
  import sqlite3
372
372
 
373
- al.shr = "/%s/" % (al.shr.strip("/"))
373
+ al.shr = al.shr.strip("/")
374
+ if "/" in al.shr or not al.shr:
375
+ t = "config error: --shr must be the name of a virtual toplevel directory to put shares inside"
376
+ self.log("root", t, 1)
377
+ raise Exception(t)
378
+
379
+ al.shr = "/%s/" % (al.shr,)
374
380
 
375
381
  create = True
382
+ modified = False
376
383
  db_path = self.args.shr_db
377
- self.log("root", "initializing shares-db %s" % (db_path,))
384
+ self.log("root", "opening shares-db %s" % (db_path,))
378
385
  for n in range(2):
379
386
  try:
380
387
  db = sqlite3.connect(db_path)
@@ -400,18 +407,43 @@ class SvcHub(object):
400
407
  pass
401
408
  os.unlink(db_path)
402
409
 
410
+ sch1 = [
411
+ r"create table kv (k text, v int)",
412
+ r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int)",
413
+ # sharekey, password, src, perms, numFiles, owner, created, expires
414
+ ]
415
+ sch2 = [
416
+ r"create table sf (k text, vp text)",
417
+ r"create index sf_k on sf(k)",
418
+ r"create index sh_k on sh(k)",
419
+ r"create index sh_t1 on sh(t1)",
420
+ ]
421
+
403
422
  assert db # type: ignore
404
423
  assert cur # type: ignore
405
424
  if create:
425
+ dver = 2
426
+ modified = True
427
+ for cmd in sch1 + sch2:
428
+ cur.execute(cmd)
429
+ self.log("root", "created new shares-db")
430
+ else:
431
+ (dver,) = cur.execute("select v from kv where k = 'sver'").fetchall()[0]
432
+
433
+ if dver == 1:
434
+ modified = True
435
+ for cmd in sch2:
436
+ cur.execute(cmd)
437
+ cur.execute("update sh set st = 0")
438
+ self.log("root", "shares-db schema upgrade ok")
439
+
440
+ if modified:
406
441
  for cmd in [
407
- # sharekey, password, src, perms, type, owner, created, expires
408
- r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int)",
409
- r"create table kv (k text, v int)",
410
- r"insert into kv values ('sver', {})".format(1),
442
+ r"delete from kv where k = 'sver'",
443
+ r"insert into kv values ('sver', %d)" % (2,),
411
444
  ]:
412
445
  cur.execute(cmd)
413
446
  db.commit()
414
- self.log("root", "created new shares-db")
415
447
 
416
448
  cur.close()
417
449
  db.close()
copyparty/up2k.py CHANGED
@@ -457,7 +457,8 @@ class Up2k(object):
457
457
  # important; not deferred by db_act
458
458
  timeout = self._check_lifetimes()
459
459
  try:
460
- timeout = min(self._check_shares(), timeout)
460
+ if self.args.shr:
461
+ timeout = min(self._check_shares(), timeout)
461
462
  except Exception as ex:
462
463
  t = "could not check for expiring shares: %r"
463
464
  self.log(t % (ex,), 1)
@@ -576,8 +577,8 @@ class Up2k(object):
576
577
  rm = [x[0] for x in cur.execute(q, (now,))]
577
578
  if rm:
578
579
  self.log("forgetting expired shares %s" % (rm,))
579
- q = "delete from sh where k=?"
580
- cur.executemany(q, [(x,) for x in rm])
580
+ cur.executemany("delete from sh where k=?", [(x,) for x in rm])
581
+ cur.executemany("delete from sf where k=?", [(x,) for x in rm])
581
582
  db.commit()
582
583
  Daemon(self.hub._reload_blocking, "sharedrop", (False, False))
583
584
 
copyparty/web/a/u2c.py CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
- S_VERSION = "1.22"
5
- S_BUILD_DT = "2024-08-08"
4
+ S_VERSION = "1.23"
5
+ S_BUILD_DT = "2024-08-22"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -1236,7 +1236,7 @@ source file/folder selection uses rsync syntax, meaning that:
1236
1236
  ap.add_argument("-v", action="store_true", help="verbose")
1237
1237
  ap.add_argument("-a", metavar="PASSWD", help="password or $filepath")
1238
1238
  ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
1239
- ap.add_argument("-x", type=unicode, metavar="REGEX", default="", help="skip file if filesystem-abspath matches REGEX, example: '.*/\\.hist/.*'")
1239
+ ap.add_argument("-x", type=unicode, metavar="REGEX", action="append", help="skip file if filesystem-abspath matches REGEX (option can be repeated), example: '.*/\\.hist/.*'")
1240
1240
  ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
1241
1241
  ap.add_argument("--touch", action="store_true", help="if last-modified timestamps differ, push local to server (need write+delete perms)")
1242
1242
  ap.add_argument("--ow", action="store_true", help="overwrite existing files instead of autorenaming")
@@ -1283,6 +1283,8 @@ source file/folder selection uses rsync syntax, meaning that:
1283
1283
  if ar.dr:
1284
1284
  ar.ow = True
1285
1285
 
1286
+ ar.x = "|".join(ar.x or [])
1287
+
1286
1288
  for k in "dl dr drd".split():
1287
1289
  errs = []
1288
1290
  if ar.safe and getattr(ar, k):
Binary file
Binary file
Binary file
copyparty/web/shares.html CHANGED
@@ -15,25 +15,25 @@
15
15
  <body>
16
16
  <div id="wrap">
17
17
  <a id="a" href="{{ r }}/?shares" class="af">refresh</a>
18
- <a id="a" href="{{ r }}/?h" class="af">controlpanel</a>
18
+ <a id="a" href="{{ r }}/?h" class="af">control-panel</a>
19
19
 
20
20
  <span>axs = perms (read,write,move,delet)</span>
21
- <span>st 1=file 2=dir</span>
21
+ <span>nf = numFiles (0=dir)</span>
22
22
  <span>min/hrs = time left</span>
23
23
 
24
- <table><tr>
24
+ <table id="tab"><thead><tr>
25
25
  <th>delete</th>
26
26
  <th>sharekey</th>
27
27
  <th>pw</th>
28
28
  <th>source</th>
29
29
  <th>axs</th>
30
- <th>st</th>
30
+ <th>nf</th>
31
31
  <th>user</th>
32
32
  <th>created</th>
33
33
  <th>expires</th>
34
34
  <th>min</th>
35
35
  <th>hrs</th>
36
- </tr>
36
+ </tr></thead><tbody>
37
37
  {% for k, pw, vp, pr, st, un, t0, t1 in rows %}
38
38
  <tr>
39
39
  <td><a href="#" k="{{ k }}">delete</a></td>
@@ -45,11 +45,11 @@
45
45
  <td>{{ un|e }}</td>
46
46
  <td>{{ t0 }}</td>
47
47
  <td>{{ t1 }}</td>
48
- <td>{{ (t1 - now) // 60 if t1 else "never" }}</td>
49
- <td>{{ (t1 - now) // 3600 if t1 else "never" }}</td>
48
+ <td>{{ ((t1 - now) / 60) | round(1) if t1 else "inf" }}</td>
49
+ <td>{{ ((t1 - now) / 3600) | round(1) if t1 else "inf" }}</td>
50
50
  </tr>
51
51
  {% endfor %}
52
- </table>
52
+ </tbody></table>
53
53
  {% if not rows %}
54
54
  (you don't have any active shares btw)
55
55
  {% endif %}
Binary file
Binary file
copyparty/web/splash.html CHANGED
@@ -14,6 +14,7 @@
14
14
 
15
15
  <body>
16
16
  <div id="wrap">
17
+ {%- if not in_shr %}
17
18
  <a id="a" href="{{ r }}/?h" class="af">refresh</a>
18
19
  <a id="v" href="{{ r }}/?hc" class="af">connect</a>
19
20
 
@@ -23,6 +24,7 @@
23
24
  <a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
24
25
  <p><span id="m">welcome back,</span> <strong>{{ this.uname|e }}</strong></p>
25
26
  {%- endif %}
27
+ {%- endif %}
26
28
 
27
29
  {%- if msg %}
28
30
  <div id="msg">
@@ -76,6 +78,37 @@
76
78
  </ul>
77
79
  {%- endif %}
78
80
 
81
+ {%- if in_shr %}
82
+ <h1 id="z">unlock this share:</h1>
83
+ <div>
84
+ <form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
85
+ <input type="hidden" id="la" name="act" value="login" />
86
+ <input type="password" id="lp" name="cppwd" placeholder=" password" />
87
+ <input type="hidden" name="uhash" id="uhash" value="x" />
88
+ <input type="submit" id="ls" value="Unlock" />
89
+ {% if ahttps %}
90
+ <a id="w" href="{{ ahttps }}">switch to https</a>
91
+ {% endif %}
92
+ </form>
93
+ </div>
94
+ {%- else %}
95
+ <h1 id="l">login for more:</h1>
96
+ <div>
97
+ <form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
98
+ <input type="hidden" id="la" name="act" value="login" />
99
+ <input type="password" id="lp" name="cppwd" placeholder=" password" />
100
+ <input type="hidden" name="uhash" id="uhash" value="x" />
101
+ <input type="submit" id="ls" value="Login" />
102
+ {% if chpw %}
103
+ <a id="x" href="#">change password</a>
104
+ {% endif %}
105
+ {% if ahttps %}
106
+ <a id="w" href="{{ ahttps }}">switch to https</a>
107
+ {% endif %}
108
+ </form>
109
+ </div>
110
+ {%- endif %}
111
+
79
112
  <h1 id="cc">other stuff:</h1>
80
113
  <ul>
81
114
  {%- if this.uname != '*' and this.args.shr %}
@@ -94,21 +127,6 @@
94
127
  <li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
95
128
  </ul>
96
129
 
97
- <h1 id="l">login for more:</h1>
98
- <div>
99
- <form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
100
- <input type="hidden" id="la" name="act" value="login" />
101
- <input type="password" id="lp" name="cppwd" placeholder=" password" />
102
- <input type="hidden" name="uhash" id="uhash" value="x" />
103
- <input type="submit" id="ls" value="Login" />
104
- {% if chpw %}
105
- <a id="x" href="#">change password</a>
106
- {% endif %}
107
- {% if ahttps %}
108
- <a id="w" href="{{ ahttps }}">switch to https</a>
109
- {% endif %}
110
- </form>
111
- </div>
112
130
  </div>
113
131
  <a href="#" id="repl">π</a>
114
132
  {%- if not this.args.nb %}
Binary file
copyparty/web/util.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.14.0
3
+ Version: 1.14.2
4
4
  Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
5
5
  Author-email: ed <copyparty@ocv.me>
6
6
  License: MIT
@@ -804,14 +804,16 @@ you can move files across browser tabs (cut in one tab, paste in another)
804
804
 
805
805
  share a file or folder by creating a temporary link
806
806
 
807
- when enabled in the server settings (`--shr`), click the bottom-right `share` button to share the folder you're currently in, or select a file first to share only that file
807
+ when enabled in the server settings (`--shr`), click the bottom-right `share` button to share the folder you're currently in, or alternatively:
808
+ * select a folder first to share that folder instead
809
+ * select one or more files to share only those files
808
810
 
809
811
  this feature was made with [identity providers](#identity-providers) in mind -- configure your reverseproxy to skip the IdP's access-control for a given URL prefix and use that to safely share specific files/folders sans the usual auth checks
810
812
 
811
813
  when creating a share, the creator can choose any of the following options:
812
814
 
813
815
  * password-protection
814
- * expire after a certain time
816
+ * expire after a certain time; `0` or blank means infinite
815
817
  * allow visitors to upload (if the user who creates the share has write-access)
816
818
 
817
819
  semi-intentional limitations:
@@ -822,10 +824,15 @@ semi-intentional limitations:
822
824
  * when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit
823
825
  * browsers wouldn't be able to resume a broken download unless the requester's IP gets allowlisted for X minutes (ref. tricky)
824
826
 
825
- the links are created inside a specific toplevel folder which must be specified with server-config `--shr`, for example `--shr /share/` (this also enables the feature)
827
+ specify `--shr /foobar` to enable this feature; a toplevel virtual folder named `foobar` is then created, and that's where all the shares will be served from
828
+
829
+ * you can name it whatever, `foobar` is just an example
830
+ * if you're using config files, put `shr: /foobar` inside the `[global]` section instead
826
831
 
827
832
  users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server
828
833
 
834
+ **security note:** using this feature does not mean that you can skip the [accounts and volumes](#accounts-and-volumes) section -- you still need to restrict access to volumes that you do not intend to share with unauthenticated users! it is not sufficient to use rules in the reverseproxy to restrict access to just the `/share` folder.
835
+
829
836
 
830
837
  ## batch rename
831
838
 
@@ -1,7 +1,7 @@
1
1
  copyparty/__init__.py,sha256=fUINM1abqDGzCCH_JcXdOnLdKOV-SrTI2Xo2QgQW2P4,1703
2
- copyparty/__main__.py,sha256=JBSVuRhMEnI_LMdRe5VLs09Li7WRJFcLlYGStcu0F90,107832
3
- copyparty/__version__.py,sha256=WaooTD72_XSmTadut-eIUgjzkuntfRYhU0umcNxzvXw,258
4
- copyparty/authsrv.py,sha256=5LEz9yBB4PGEgbVsnJne-XevAsHbolFP-yfanOsDsUs,94305
2
+ copyparty/__main__.py,sha256=T9fipgzSsr9hc5ApvEpoWMe0CPoYpm1jOE2kNznV9Fg,107828
3
+ copyparty/__version__.py,sha256=wDXrZl0rzt2I3ONTXVvCQwVk8Dh7I819X_mTUcCs4bI,258
4
+ copyparty/authsrv.py,sha256=jWXTjZLT8cGymfa9wBwGJqyBIi80aXcvzAMgI74G8iA,95750
5
5
  copyparty/broker_mp.py,sha256=YFe1S6Zziht8Qc__dCLj_ff8z0DDny9lqk_Mi5ajsJk,3868
6
6
  copyparty/broker_mpw.py,sha256=4ZI7bJYOwUibeAJVv9_FPGNmHrr9eOtkj_Kz0JEppTU,3197
7
7
  copyparty/broker_thr.py,sha256=eKr--HJGig5zqvNGwH9UoBG9Nvi9mT2axrRmJwknd0s,1759
@@ -11,7 +11,7 @@ copyparty/cfg.py,sha256=i8-bjWgbguQooxiA172RcptqR_SEOwDHJ4cqldrZ8oQ,9792
11
11
  copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
12
12
  copyparty/fsutil.py,sha256=hnEHgySI43-XJJKbI8n6t1A6oVHzR_nYdsBcAwtreBk,4610
13
13
  copyparty/ftpd.py,sha256=1vD-KTy07xfEEEk1dx37pUYModpNO2gIhVXvFUr205M,17497
14
- copyparty/httpcli.py,sha256=xk8ZjiZkb3Cko-_A95bgFgm68rpHrmASLwqRmlOss1U,180620
14
+ copyparty/httpcli.py,sha256=nlSmnZ6ejFzUGdfyWZ1v2C9ycuDdhDArXL3MZJcxYBI,181984
15
15
  copyparty/httpconn.py,sha256=mwIDup85cBowIfJOse8rla5bqTz7nf-ChgfR-5-V0JM,6938
16
16
  copyparty/httpsrv.py,sha256=8_1Ivg3eco7HJDjqL_rUB58IOUaUnoXGhO62bOMXLBk,17242
17
17
  copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
@@ -24,14 +24,14 @@ copyparty/smbd.py,sha256=8zkC9BjVtGiKXMLajbdakxoKeFzACdM75SW0_SvqXJA,14490
24
24
  copyparty/ssdp.py,sha256=8iyF5sqIjATJLWcAtnJa8eadHosOn0CP4ywltzJ7bVY,7023
25
25
  copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
26
26
  copyparty/sutil.py,sha256=JTMrQwcWH85hXB_cKG206eDZ967WZDGaP00AWvl_gB0,3214
27
- copyparty/svchub.py,sha256=eAvlDGhKAXUYxAHd03lHY-iNat32Bbt1rZHZwF46Kzg,37367
27
+ copyparty/svchub.py,sha256=fiH7d8UXiW_LC7m2MU0AtS6KWwy49QXbSp4gIoyT2SM,38364
28
28
  copyparty/szip.py,sha256=tor4yjdHhEL4Ox-Xg7-cuUFrMO0IwQD29aRX5Cp8MYs,8605
29
29
  copyparty/tcpsrv.py,sha256=jM_Za64O8LEMfMrU4irJluIJZrU494e2b759r_KhaUQ,19881
30
30
  copyparty/tftpd.py,sha256=i1-oZ05DJq2_nDOW3g3PfTkMoUCr2lAcDYFMWArwtKA,13568
31
31
  copyparty/th_cli.py,sha256=o6FMkerYvAXS455z3DUossVztu_nzFlYSQhs6qN6Jt8,4636
32
32
  copyparty/th_srv.py,sha256=27IftjIXUQzRRiUytt-CgXkybEoP3HHHoXaDAvxEmLo,29217
33
33
  copyparty/u2idx.py,sha256=t4mzjj2GDrkjIHt0RM68y1EgT5qOBoz6mkYgjMbqA38,13526
34
- copyparty/up2k.py,sha256=YclDPGVM_DvRXvZya8mtr2XBzYdBsUM5Ox41X-p0PaI,153007
34
+ copyparty/up2k.py,sha256=HNn5EzOM-pExt3waU-0n6_tqGweI--QAO4gKhNSACsU,153107
35
35
  copyparty/util.py,sha256=S7FuQBPbl2FX7ULIH9VNgiB7Z_rceqTJssXz4SGErwA,88625
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
@@ -55,9 +55,9 @@ copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUu
55
55
  copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
57
  copyparty/web/baguettebox.js.gz,sha256=4dS8-r4si84ca71l98672ahnRI86Aq95MU-bc5knykk,7962
58
- copyparty/web/browser.css.gz,sha256=tE4SKTvB2l6t91Mb_bhadKbV0QMLrYbAcVE-JQ7MPUk,11494
58
+ copyparty/web/browser.css.gz,sha256=PoW_IIwFigZaMo3atpPU0o05Jj5Flbsm1bhW_KfcX-U,11491
59
59
  copyparty/web/browser.html,sha256=vvfWiu_aOFRar8u5lridMRKQSPF4R0YkA41zrsh82Qs,4878
60
- copyparty/web/browser.js.gz,sha256=lF2coe19rlOIwgDZlcqdnascDjqCJDVzVxtyZopQE2Y,71016
60
+ copyparty/web/browser.js.gz,sha256=0eqpm-iVRYZc-A9rpNNj8I1S_cV90nBb8VOWq-Lk6Zc,80814
61
61
  copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
62
62
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
63
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
@@ -71,21 +71,21 @@ copyparty/web/mde.html,sha256=ImBhQAaEUCke2M85QU_fl4X2XQExRLcEzgCEN8RNe9o,1759
71
71
  copyparty/web/mde.js.gz,sha256=kN2eUSvr4mFuksfK4-4LimJmWdwsao39Sea2lWtu8L0,2224
72
72
  copyparty/web/msg.css.gz,sha256=u90fXYAVrMD-jqwf5XFVC1ptSpSHZUe8Mez6PX101P8,300
73
73
  copyparty/web/msg.html,sha256=w9CM3hkLLGJX9fWEaG4gSbTOPe2GcPqW8BpSCDiFzOI,977
74
- copyparty/web/shares.css.gz,sha256=4OqsDr0aby14LPy61nU6JdEsgDyBw3TMQiClLvOPFds,477
75
- copyparty/web/shares.html,sha256=sogrhA6HCzG413qSrHar8-RFSocuBxACTWArO_HOP28,2208
76
- copyparty/web/shares.js.gz,sha256=PYOZ2jjzDTy_g7lali1kozUjUoI-97LsjXxKUzoThiU,319
77
- copyparty/web/splash.css.gz,sha256=lCleyzwvc5KdkFtuLhImeOeRZKYi_ISiLppCDcmRK7Y,1023
78
- copyparty/web/splash.html,sha256=FDCcWzO8E_mm_D4EaCWgeMyFSkMTAnGxWwPh9dn1e4Y,4221
79
- copyparty/web/splash.js.gz,sha256=RUH5mxrYtCAQ4eLsOUfLl0ryHa1f6-kf_I2j8FitMB0,1840
74
+ copyparty/web/shares.css.gz,sha256=m-nRqTGPiU3ohZxvGaROzFr98F_jmohQnjieqEAyjBo,496
75
+ copyparty/web/shares.html,sha256=j3nXy0cWBXHx0kx-NsFwxbtCSRbRowGy5DhF_n6y7iQ,2276
76
+ copyparty/web/shares.js.gz,sha256=St2Ep6md8XhpmJhxcppYjHCER5Q1GmIdfHtIM3x4iWU,514
77
+ copyparty/web/splash.css.gz,sha256=4DOtEKBWyaDKel7fdnwvnc9FrKlkht-ec7R2nRlruPU,1023
78
+ copyparty/web/splash.html,sha256=dAo4KXKmXUMGcIwetZkFtVxk-mCMNkscD36BxLwRdow,4804
79
+ copyparty/web/splash.js.gz,sha256=0HMVU21vpeA4Jbi6cGsPtKTwhdtT8-puVOQOiQAHGIc,2592
80
80
  copyparty/web/svcs.html,sha256=v0C3cOFWXYlvp3GEifz1Qj0W3MD8JANT3WTON05GZ9o,11797
81
81
  copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
82
82
  copyparty/web/ui.css.gz,sha256=GnR_PxnZGcNs2IJnb5hFffnhlW3cUHkPad3tNIm-7DQ,2637
83
83
  copyparty/web/up2k.js.gz,sha256=KufMtRViAZQo2rVj67iEWbdPxlVeXW85emRYVJoY3aA,22946
84
- copyparty/web/util.js.gz,sha256=9LeqbO0j_Z4pEXH2pl9FxWxv5eG7d-SE997AFGboK74,14682
84
+ copyparty/web/util.js.gz,sha256=dPuhXEBJ_T-d2tYUUufGTUul4FYIbuh6GQmtK7iBkEo,14682
85
85
  copyparty/web/w.hash.js.gz,sha256=7wP9EZQNXQxwZnCCFUVsi_-6TM9PLZJeZ9krutXRRj8,1060
86
86
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
87
  copyparty/web/a/partyfuse.py,sha256=MuRkaSuYsdfWfBFMOkbPwDXqSvNTw3sd7QhhlKCDZ8I,32311
88
- copyparty/web/a/u2c.py,sha256=eJlcAN70lKeh46eNq9ae7CO-kgA8oGrMfiOBJivzCXk,42419
88
+ copyparty/web/a/u2c.py,sha256=WG9njRxY9g9xjO93-fas9Wo-AM8vtrWrUTwpJ5Afmvk,42482
89
89
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
90
90
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
91
91
  copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
@@ -105,9 +105,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
105
105
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
106
106
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
107
107
  copyparty/web/deps/sha512.hw.js.gz,sha256=vqoXeracj-99Z5MfY3jK2N4WiSzYQdfjy0RnUlQDhSU,8110
108
- copyparty-1.14.0.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
109
- copyparty-1.14.0.dist-info/METADATA,sha256=LDXOH7TctyfP7bNEY9BtHEVrU2zk0MPEJdhauMb09TM,130962
110
- copyparty-1.14.0.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
111
- copyparty-1.14.0.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
112
- copyparty-1.14.0.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
113
- copyparty-1.14.0.dist-info/RECORD,,
108
+ copyparty-1.14.2.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
109
+ copyparty-1.14.2.dist-info/METADATA,sha256=fDuptsXfw2sOmf6nms6NIZRX5cgtutScqpw_UA0DqmE,131543
110
+ copyparty-1.14.2.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
111
+ copyparty-1.14.2.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
112
+ copyparty-1.14.2.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
113
+ copyparty-1.14.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.2.0)
2
+ Generator: setuptools (73.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5