copyparty 1.13.6__py3-none-any.whl → 1.13.8__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.
Files changed (42) hide show
  1. copyparty/__main__.py +25 -7
  2. copyparty/__version__.py +2 -2
  3. copyparty/authsrv.py +9 -6
  4. copyparty/cert.py +1 -1
  5. copyparty/fsutil.py +3 -3
  6. copyparty/ftpd.py +15 -2
  7. copyparty/httpcli.py +224 -81
  8. copyparty/httpconn.py +3 -0
  9. copyparty/httpsrv.py +38 -11
  10. copyparty/ico.py +1 -1
  11. copyparty/mtag.py +15 -6
  12. copyparty/pwhash.py +10 -0
  13. copyparty/smbd.py +20 -2
  14. copyparty/ssdp.py +3 -3
  15. copyparty/stolen/dnslib/dns.py +6 -0
  16. copyparty/stolen/ifaddr/__init__.py +15 -1
  17. copyparty/stolen/ifaddr/_shared.py +1 -0
  18. copyparty/stolen/qrcodegen.py +6 -0
  19. copyparty/sutil.py +1 -1
  20. copyparty/svchub.py +72 -3
  21. copyparty/szip.py +1 -3
  22. copyparty/tcpsrv.py +63 -8
  23. copyparty/tftpd.py +30 -4
  24. copyparty/th_srv.py +22 -1
  25. copyparty/u2idx.py +4 -1
  26. copyparty/up2k.py +221 -93
  27. copyparty/util.py +166 -31
  28. copyparty/web/a/u2c.py +10 -3
  29. copyparty/web/browser.css.gz +0 -0
  30. copyparty/web/browser2.html +0 -1
  31. copyparty/web/md.html +3 -0
  32. copyparty/web/mde.html +3 -0
  33. copyparty/web/msg.html +3 -0
  34. copyparty/web/splash.html +3 -0
  35. copyparty/web/svcs.html +3 -0
  36. copyparty/web/up2k.js.gz +0 -0
  37. {copyparty-1.13.6.dist-info → copyparty-1.13.8.dist-info}/METADATA +64 -14
  38. {copyparty-1.13.6.dist-info → copyparty-1.13.8.dist-info}/RECORD +42 -42
  39. {copyparty-1.13.6.dist-info → copyparty-1.13.8.dist-info}/LICENSE +0 -0
  40. {copyparty-1.13.6.dist-info → copyparty-1.13.8.dist-info}/WHEEL +0 -0
  41. {copyparty-1.13.6.dist-info → copyparty-1.13.8.dist-info}/entry_points.txt +0 -0
  42. {copyparty-1.13.6.dist-info → copyparty-1.13.8.dist-info}/top_level.txt +0 -0
copyparty/util.py CHANGED
@@ -26,7 +26,6 @@ import threading
26
26
  import time
27
27
  import traceback
28
28
  from collections import Counter
29
- from email.utils import formatdate
30
29
 
31
30
  from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
32
31
  from queue import Queue
@@ -60,6 +59,10 @@ except:
60
59
  UTC = _UTC()
61
60
 
62
61
 
62
+ if PY2:
63
+ range = xrange # type: ignore
64
+
65
+
63
66
  if sys.version_info >= (3, 7) or (
64
67
  sys.version_info >= (3, 6) and platform.python_implementation() == "CPython"
65
68
  ):
@@ -99,6 +102,9 @@ except:
99
102
  pass
100
103
 
101
104
  try:
105
+ if os.environ.get("PRTY_NO_SQLITE"):
106
+ raise Exception()
107
+
102
108
  HAVE_SQLITE3 = True
103
109
  import sqlite3
104
110
 
@@ -107,6 +113,9 @@ except:
107
113
  HAVE_SQLITE3 = False
108
114
 
109
115
  try:
116
+ if os.environ.get("PRTY_NO_PSUTIL"):
117
+ raise Exception()
118
+
110
119
  HAVE_PSUTIL = True
111
120
  import psutil
112
121
  except:
@@ -116,10 +125,15 @@ if TYPE_CHECKING:
116
125
  import magic
117
126
 
118
127
  from .authsrv import VFS
128
+ from .broker_util import BrokerCli
129
+ from .up2k import Up2k
119
130
 
120
131
  FAKE_MP = False
121
132
 
122
133
  try:
134
+ if os.environ.get("PRTY_NO_MP"):
135
+ raise ImportError()
136
+
123
137
  import multiprocessing as mp
124
138
 
125
139
  # import multiprocessing.dummy as mp
@@ -138,6 +152,9 @@ else:
138
152
 
139
153
 
140
154
  try:
155
+ if os.environ.get("PRTY_NO_IPV6"):
156
+ raise Exception()
157
+
141
158
  socket.inet_pton(socket.AF_INET6, "::1")
142
159
  HAVE_IPV6 = True
143
160
  except:
@@ -773,7 +790,7 @@ class CachedSet(object):
773
790
 
774
791
  c = self.c = {k: v for k, v in self.c.items() if now - v < self.maxage}
775
792
  try:
776
- self.oldest = c[min(c, key=c.get)]
793
+ self.oldest = c[min(c, key=c.get)] # type: ignore
777
794
  except:
778
795
  self.oldest = now
779
796
 
@@ -1800,10 +1817,21 @@ def gen_filekey_dbg(
1800
1817
  return ret
1801
1818
 
1802
1819
 
1820
+ WKDAYS = "Mon Tue Wed Thu Fri Sat Sun".split()
1821
+ MONTHS = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split()
1822
+ RFC2822 = "%s, %02d %s %04d %02d:%02d:%02d GMT"
1823
+
1824
+
1825
+ def formatdate(ts = None) :
1826
+ # gmtime ~= datetime.fromtimestamp(ts, UTC).timetuple()
1827
+ y, mo, d, h, mi, s, wd, _, _ = time.gmtime(ts)
1828
+ return RFC2822 % (WKDAYS[wd], d, MONTHS[mo - 1], y, h, mi, s)
1829
+
1830
+
1803
1831
  def gencookie(k , v , r , tls , dur = 0, txt = "") :
1804
1832
  v = v.replace("%", "%25").replace(";", "%3B")
1805
1833
  if dur:
1806
- exp = formatdate(time.time() + dur, usegmt=True)
1834
+ exp = formatdate(time.time() + dur)
1807
1835
  else:
1808
1836
  exp = "Fri, 15 Aug 1997 01:00:00 GMT"
1809
1837
 
@@ -1818,12 +1846,10 @@ def humansize(sz , terse = False) :
1818
1846
 
1819
1847
  sz /= 1024.0
1820
1848
 
1821
- ret = " ".join([str(sz)[:4].rstrip("."), unit])
1822
-
1823
- if not terse:
1824
- return ret
1825
-
1826
- return ret.replace("iB", "").replace(" ", "")
1849
+ if terse:
1850
+ return "%s%s" % (str(sz)[:4].rstrip("."), unit[:1])
1851
+ else:
1852
+ return "%s %s" % (str(sz)[:4].rstrip("."), unit)
1827
1853
 
1828
1854
 
1829
1855
  def unhumanize(sz ) :
@@ -1875,7 +1901,7 @@ def uncyg(path ) :
1875
1901
  def undot(path ) :
1876
1902
  ret = []
1877
1903
  for node in path.split("/"):
1878
- if node in ["", "."]:
1904
+ if node == "." or not node:
1879
1905
  continue
1880
1906
 
1881
1907
  if node == "..":
@@ -2028,7 +2054,7 @@ def _quotep2(txt ) :
2028
2054
  """url quoter which deals with bytes correctly"""
2029
2055
  btxt = w8enc(txt)
2030
2056
  quot = quote(btxt, safe=b"/")
2031
- return w8dec(quot.replace(b" ", b"+"))
2057
+ return w8dec(quot.replace(b" ", b"+")) # type: ignore
2032
2058
 
2033
2059
 
2034
2060
  def _quotep3(txt ) :
@@ -2072,6 +2098,72 @@ def ujoin(rd , fn ) :
2072
2098
  return rd or fn
2073
2099
 
2074
2100
 
2101
+ def log_reloc(
2102
+ log ,
2103
+ re ,
2104
+ pm ,
2105
+ ap ,
2106
+ vp ,
2107
+ fn ,
2108
+ vn ,
2109
+ rem ,
2110
+ ) :
2111
+ nap, nvp, nfn, (nvn, nrem) = pm
2112
+ t = "reloc %s:\nold ap [%s]\nnew ap [%s\033[36m/%s\033[0m]\nold vp [%s]\nnew vp [%s\033[36m/%s\033[0m]\nold fn [%s]\nnew fn [%s]\nold vfs [%s]\nnew vfs [%s]\nold rem [%s]\nnew rem [%s]"
2113
+ log(t % (re, ap, nap, nfn, vp, nvp, nfn, fn, nfn, vn.vpath, nvn.vpath, rem, nrem))
2114
+
2115
+
2116
+ def pathmod(
2117
+ vfs , ap , vp , mod
2118
+ ) :
2119
+ # vfs: authsrv.vfs
2120
+ # ap: original abspath to a file
2121
+ # vp: original urlpath to a file
2122
+ # mod: modification (ap/vp/fn)
2123
+
2124
+ nvp = "\n" # new vpath
2125
+ ap = os.path.dirname(ap)
2126
+ vp, fn = vsplit(vp)
2127
+ if mod.get("fn"):
2128
+ fn = mod["fn"]
2129
+ nvp = vp
2130
+
2131
+ for ref, k in ((ap, "ap"), (vp, "vp")):
2132
+ if k not in mod:
2133
+ continue
2134
+
2135
+ ms = mod[k].replace(os.sep, "/")
2136
+ if ms.startswith("/"):
2137
+ np = ms
2138
+ elif k == "vp":
2139
+ np = undot(vjoin(ref, ms))
2140
+ else:
2141
+ np = os.path.abspath(os.path.join(ref, ms))
2142
+
2143
+ if k == "vp":
2144
+ nvp = np.lstrip("/")
2145
+ continue
2146
+
2147
+ # try to map abspath to vpath
2148
+ np = np.replace("/", os.sep)
2149
+ for vn_ap, vn in vfs.all_aps:
2150
+ if not np.startswith(vn_ap):
2151
+ continue
2152
+ zs = np[len(vn_ap) :].replace(os.sep, "/")
2153
+ nvp = vjoin(vn.vpath, zs)
2154
+ break
2155
+
2156
+ if nvp == "\n":
2157
+ return None
2158
+
2159
+ vn, rem = vfs.get(nvp, "*", False, False)
2160
+ if not vn.realpath:
2161
+ raise Exception("unmapped vfs")
2162
+
2163
+ ap = vn.canonical(rem)
2164
+ return ap, nvp, fn, (vn, rem)
2165
+
2166
+
2075
2167
  def _w8dec2(txt ) :
2076
2168
  """decodes filesystem-bytes to wtf8"""
2077
2169
  return surrogateescape.decodefilename(txt)
@@ -2688,30 +2780,30 @@ def rmdirs_up(top , stop ) :
2688
2780
 
2689
2781
  def unescape_cookie(orig ) :
2690
2782
  # mw=idk; doot=qwe%2Crty%3Basd+fgh%2Bjkl%25zxc%26vbn # qwe,rty;asd fgh+jkl%zxc&vbn
2691
- ret = ""
2783
+ ret = []
2692
2784
  esc = ""
2693
2785
  for ch in orig:
2694
2786
  if ch == "%":
2695
- if len(esc) > 0:
2696
- ret += esc
2787
+ if esc:
2788
+ ret.append(esc)
2697
2789
  esc = ch
2698
2790
 
2699
- elif len(esc) > 0:
2791
+ elif esc:
2700
2792
  esc += ch
2701
2793
  if len(esc) == 3:
2702
2794
  try:
2703
- ret += chr(int(esc[1:], 16))
2795
+ ret.append(chr(int(esc[1:], 16)))
2704
2796
  except:
2705
- ret += esc
2797
+ ret.append(esc)
2706
2798
  esc = ""
2707
2799
 
2708
2800
  else:
2709
- ret += ch
2801
+ ret.append(ch)
2710
2802
 
2711
- if len(esc) > 0:
2712
- ret += esc
2803
+ if esc:
2804
+ ret.append(esc)
2713
2805
 
2714
- return ret
2806
+ return "".join(ret)
2715
2807
 
2716
2808
 
2717
2809
  def guess_mime(url , fallback = "application/octet-stream") :
@@ -3085,6 +3177,7 @@ def runihook(
3085
3177
 
3086
3178
  def _runhook(
3087
3179
  log ,
3180
+ src ,
3088
3181
  cmd ,
3089
3182
  ap ,
3090
3183
  vp ,
@@ -3096,14 +3189,16 @@ def _runhook(
3096
3189
  ip ,
3097
3190
  at ,
3098
3191
  txt ,
3099
- ) :
3192
+ ) :
3193
+ ret = {"rc": 0}
3100
3194
  areq, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
3101
3195
  if areq:
3102
3196
  for ch in areq:
3103
3197
  if ch not in perms:
3104
3198
  t = "user %s not allowed to run hook %s; need perms %s, have %s"
3105
- log(t % (uname, cmd, areq, perms))
3106
- return True # fallthrough to next hook
3199
+ if log:
3200
+ log(t % (uname, cmd, areq, perms))
3201
+ return ret # fallthrough to next hook
3107
3202
  if jtxt:
3108
3203
  ja = {
3109
3204
  "ap": ap,
@@ -3115,6 +3210,7 @@ def _runhook(
3115
3210
  "host": host,
3116
3211
  "user": uname,
3117
3212
  "perms": perms,
3213
+ "src": src,
3118
3214
  "txt": txt,
3119
3215
  }
3120
3216
  arg = json.dumps(ja)
@@ -3133,18 +3229,34 @@ def _runhook(
3133
3229
  else:
3134
3230
  rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
3135
3231
  if chk and rc:
3232
+ ret["rc"] = rc
3136
3233
  retchk(rc, bcmd, err, log, 5)
3137
- return False
3234
+ else:
3235
+ try:
3236
+ ret = json.loads(v)
3237
+ except:
3238
+ ret = {}
3239
+
3240
+ try:
3241
+ if "stdout" not in ret:
3242
+ ret["stdout"] = v
3243
+ if "rc" not in ret:
3244
+ ret["rc"] = rc
3245
+ except:
3246
+ ret = {"rc": rc, "stdout": v}
3138
3247
 
3139
3248
  wait -= time.time() - t0
3140
3249
  if wait > 0:
3141
3250
  time.sleep(wait)
3142
3251
 
3143
- return True
3252
+ return ret
3144
3253
 
3145
3254
 
3146
3255
  def runhook(
3147
3256
  log ,
3257
+ broker ,
3258
+ up2k ,
3259
+ src ,
3148
3260
  cmds ,
3149
3261
  ap ,
3150
3262
  vp ,
@@ -3156,19 +3268,42 @@ def runhook(
3156
3268
  ip ,
3157
3269
  at ,
3158
3270
  txt ,
3159
- ) :
3271
+ ) :
3272
+ assert broker or up2k
3273
+ asrv = (broker or up2k).asrv
3274
+ args = (broker or up2k).args
3160
3275
  vp = vp.replace("\\", "/")
3276
+ ret = {"rc": 0}
3161
3277
  for cmd in cmds:
3162
3278
  try:
3163
- if not _runhook(log, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt):
3164
- return False
3279
+ hr = _runhook(
3280
+ log, src, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt
3281
+ )
3282
+ if log and args.hook_v:
3283
+ log("hook(%s) [%s] => \033[32m%s" % (src, cmd, hr), 6)
3284
+ if not hr:
3285
+ return {}
3286
+ for k, v in hr.items():
3287
+ if k in ("idx", "del") and v:
3288
+ if broker:
3289
+ broker.say("up2k.hook_fx", k, v, vp)
3290
+ else:
3291
+ up2k.fx_backlog.append((k, v, vp))
3292
+ elif k == "reloc" and v:
3293
+ # idk, just take the last one ig
3294
+ ret["reloc"] = v
3295
+ elif k in ret:
3296
+ if k == "rc" and v:
3297
+ ret[k] = v
3298
+ else:
3299
+ ret[k] = v
3165
3300
  except Exception as ex:
3166
3301
  (log or print)("hook: {}".format(ex))
3167
3302
  if ",c," in "," + cmd:
3168
- return False
3303
+ return {}
3169
3304
  break
3170
3305
 
3171
- return True
3306
+ return ret
3172
3307
 
3173
3308
 
3174
3309
  def loadpy(ap , hot ) :
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.21"
5
- S_BUILD_DT = "2024-07-26"
4
+ S_VERSION = "1.22"
5
+ S_BUILD_DT = "2024-08-08"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -660,8 +660,15 @@ def upload(fsl, pw, stats):
660
660
  # type: (FileSlice, str, str) -> None
661
661
  """upload a range of file data, defined by one or more `cid` (chunk-hash)"""
662
662
 
663
+ ctxt = fsl.cids[0]
664
+ if len(fsl.cids) > 1:
665
+ n = 192 // len(fsl.cids)
666
+ n = 9 if n > 9 else 2 if n < 2 else n
667
+ zsl = [zs[:n] for zs in fsl.cids[1:]]
668
+ ctxt += ",%d,%s" % (n, "".join(zsl))
669
+
663
670
  headers = {
664
- "X-Up2k-Hash": ",".join(fsl.cids),
671
+ "X-Up2k-Hash": ctxt,
665
672
  "X-Up2k-Wark": fsl.file.wark,
666
673
  "Content-Type": "application/octet-stream",
667
674
  }
Binary file
@@ -11,7 +11,6 @@
11
11
  td{border:1px solid #999;border-width:1px 1px 0 0;padding:0 5px}
12
12
  a{display:block}
13
13
  </style>
14
- {{ html_head }}
15
14
  </head>
16
15
 
17
16
  <body>
copyparty/web/md.html CHANGED
@@ -159,5 +159,8 @@ try { l.light = drk? 0:1; } catch (ex) { }
159
159
  {%- if edit %}
160
160
  <script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
161
161
  {%- endif %}
162
+ {%- if js %}
163
+ <script src="{{ js }}_={{ ts }}"></script>
164
+ {%- endif %}
162
165
  </body></html>
163
166
 
copyparty/web/mde.html CHANGED
@@ -53,5 +53,8 @@ try { l.light = drk? 0:1; } catch (ex) { }
53
53
  <script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
54
54
  <script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
55
55
  <script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
56
+ {%- if js %}
57
+ <script src="{{ js }}_={{ ts }}"></script>
58
+ {%- endif %}
56
59
  </body></html>
57
60
 
copyparty/web/msg.html CHANGED
@@ -46,6 +46,9 @@
46
46
  }, 1000);
47
47
  </script>
48
48
  {%- endif %}
49
+ {%- if js %}
50
+ <script src="{{ js }}_={{ ts }}"></script>
51
+ {%- endif %}
49
52
  </body>
50
53
 
51
54
  </html>
copyparty/web/splash.html CHANGED
@@ -119,6 +119,9 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme
119
119
  </script>
120
120
  <script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
121
121
  <script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
122
+ {%- if js %}
123
+ <script src="{{ js }}_={{ ts }}"></script>
124
+ {%- endif %}
122
125
  </body>
123
126
  </html>
124
127
 
copyparty/web/svcs.html CHANGED
@@ -245,6 +245,9 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ args.theme }}";
245
245
  </script>
246
246
  <script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
247
247
  <script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
248
+ {%- if js %}
249
+ <script src="{{ js }}_={{ ts }}"></script>
250
+ {%- endif %}
248
251
  </body>
249
252
  </html>
250
253
 
copyparty/web/up2k.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.13.6
3
+ Version: 1.13.8
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
@@ -37,20 +37,20 @@ Description-Content-Type: text/markdown
37
37
  License-File: LICENSE
38
38
  Requires-Dist: Jinja2
39
39
  Provides-Extra: audiotags
40
- Requires-Dist: mutagen ; extra == 'audiotags'
40
+ Requires-Dist: mutagen; extra == "audiotags"
41
41
  Provides-Extra: ftpd
42
- Requires-Dist: pyftpdlib ; extra == 'ftpd'
42
+ Requires-Dist: pyftpdlib; extra == "ftpd"
43
43
  Provides-Extra: ftps
44
- Requires-Dist: pyftpdlib ; extra == 'ftps'
45
- Requires-Dist: pyopenssl ; extra == 'ftps'
44
+ Requires-Dist: pyftpdlib; extra == "ftps"
45
+ Requires-Dist: pyopenssl; extra == "ftps"
46
46
  Provides-Extra: pwhash
47
- Requires-Dist: argon2-cffi ; extra == 'pwhash'
47
+ Requires-Dist: argon2-cffi; extra == "pwhash"
48
48
  Provides-Extra: tftpd
49
- Requires-Dist: partftpy >=0.4.0 ; extra == 'tftpd'
49
+ Requires-Dist: partftpy>=0.4.0; extra == "tftpd"
50
50
  Provides-Extra: thumbnails
51
- Requires-Dist: Pillow ; extra == 'thumbnails'
51
+ Requires-Dist: Pillow; extra == "thumbnails"
52
52
  Provides-Extra: thumbnails2
53
- Requires-Dist: pyvips ; extra == 'thumbnails2'
53
+ Requires-Dist: pyvips; extra == "thumbnails2"
54
54
 
55
55
  # 💾🎉 copyparty
56
56
 
@@ -139,6 +139,7 @@ turn almost any device into a file server with resumable uploads/downloads using
139
139
  * [prometheus](#prometheus) - metrics/stats can be enabled
140
140
  * [other extremely specific features](#other-extremely-specific-features) - you'll never find a use for these
141
141
  * [custom mimetypes](#custom-mimetypes) - change the association of a file extension
142
+ * [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
142
143
  * [packages](#packages) - the party might be closer than you think
143
144
  * [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
144
145
  * [fedora package](#fedora-package) - does not exist yet
@@ -165,6 +166,7 @@ turn almost any device into a file server with resumable uploads/downloads using
165
166
  * [HTTP API](#HTTP-API) - see [devnotes](./docs/devnotes.md#http-api)
166
167
  * [dependencies](#dependencies) - mandatory deps
167
168
  * [optional dependencies](#optional-dependencies) - install these to enable bonus features
169
+ * [dependency chickenbits](#dependency-chickenbits) - prevent loading an optional dependency
168
170
  * [optional gpl stuff](#optional-gpl-stuff)
169
171
  * [sfx](#sfx) - the self-contained "binary" (recommended!)
170
172
  * [copyparty.exe](#copypartyexe) - download [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) (win8+) or [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) (win7+)
@@ -178,7 +180,7 @@ turn almost any device into a file server with resumable uploads/downloads using
178
180
 
179
181
  just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
180
182
 
181
- * or install through pypi: `python3 -m pip install --user -U copyparty`
183
+ * or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
182
184
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
183
185
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
184
186
  * or if you are on android, [install copyparty in termux](#install-on-android)
@@ -248,7 +250,7 @@ firewall-cmd --reload
248
250
  also see [comparison to similar software](./docs/versus.md)
249
251
 
250
252
  * backend stuff
251
- * ☑ IPv6
253
+ * ☑ IPv6 + unix-sockets
252
254
  * ☑ [multiprocessing](#performance) (actual multithreading)
253
255
  * ☑ volumes (mountpoints)
254
256
  * ☑ [accounts](#accounts-and-volumes)
@@ -633,9 +635,6 @@ images with the following names (see `--th-covers`) become the thumbnail of the
633
635
  * the order is significant, so if both `cover.png` and `folder.jpg` exist in a folder, it will pick the first matching `--th-covers` entry (`folder.jpg`)
634
636
  * and, if you enable [file indexing](#file-indexing), it will also try those names as dotfiles (`.folder.jpg` and so), and then fallback on the first picture in the folder (if it has any pictures at all)
635
637
 
636
- in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
637
- * indicated by the audio files having the ▶ icon instead of 💾
638
-
639
638
  enabling `multiselect` lets you click files to select them, and then shift-click another file for range-select
640
639
  * `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking
641
640
  * the `sel` option can be made default globally with `--gsel` or per-volume with volflag `gsel`
@@ -1514,6 +1513,8 @@ some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatical
1514
1513
  * **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now
1515
1514
  * depending on server/client, HTTP/1.1 can also be 5x faster than HTTP/2
1516
1515
 
1516
+ for improved security (and a tiny performance boost) consider listening on a unix-socket with `-i /tmp/party.sock` instead of `-i 127.0.0.1`
1517
+
1517
1518
  example webserver configs:
1518
1519
 
1519
1520
  * [nginx config](contrib/nginx/copyparty.conf) -- entire domain/subdomain
@@ -1614,6 +1615,23 @@ in a config-file, this is the same as:
1614
1615
  run copyparty with `--mimes` to list all the default mappings
1615
1616
 
1616
1617
 
1618
+ ### feature chickenbits
1619
+
1620
+ buggy feature? rip it out by setting any of the following environment variables to disable its associated bell or whistle,
1621
+
1622
+ | env-var | what it does |
1623
+ | -------------------- | ------------ |
1624
+ | `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
1625
+ | `PRTY_NO_IPV6` | disable some ipv6 support (should not be necessary since windows 2000) |
1626
+ | `PRTY_NO_LZMA` | disable streaming xz compression of incoming uploads |
1627
+ | `PRTY_NO_MP` | disable all use of the python `multiprocessing` module (actual multithreading, cpu-count for parsers/thumbnailers) |
1628
+ | `PRTY_NO_SQLITE` | disable all database-related functionality (file indexing, metadata indexing, most file deduplication logic) |
1629
+ | `PRTY_NO_TLS` | disable native HTTPS support; if you still want to accept HTTPS connections then TLS must now be terminated by a reverse-proxy |
1630
+ | `PRTY_NO_TPOKE` | disable systemd-tmpfilesd avoider |
1631
+
1632
+ example: `PRTY_NO_IFADDR=1 python3 copyparty-sfx.py`
1633
+
1634
+
1617
1635
  # packages
1618
1636
 
1619
1637
  the party might be closer than you think
@@ -1936,6 +1954,7 @@ some notes on hardening
1936
1954
  * cors doesn't work right otherwise
1937
1955
  * if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml`
1938
1956
  * this returns html documents as plaintext, and also disables markdown rendering
1957
+ * when running behind a reverse-proxy, listen on a unix-socket with `-i /tmp/party.sock` instead of `-i 127.0.0.1` for tighter access control (plus you get a tiny performance boost for free)
1939
1958
 
1940
1959
  safety profiles:
1941
1960
 
@@ -2100,6 +2119,37 @@ enable [smb](#smb-server) support (**not** recommended):
2100
2119
  `pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
2101
2120
 
2102
2121
 
2122
+ ### dependency chickenbits
2123
+
2124
+ prevent loading an optional dependency , for example if:
2125
+
2126
+ * you have an incompatible version installed and it causes problems
2127
+ * you just don't want copyparty to use it, maybe to save ram
2128
+
2129
+ set any of the following environment variables to disable its associated optional feature,
2130
+
2131
+ | env-var | what it does |
2132
+ | -------------------- | ------------ |
2133
+ | `PRTY_NO_ARGON2` | disable argon2-cffi password hashing |
2134
+ | `PRTY_NO_CFSSL` | never attempt to generate self-signed certificates using [cfssl](https://github.com/cloudflare/cfssl) |
2135
+ | `PRTY_NO_FFMPEG` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips |
2136
+ | `PRTY_NO_FFPROBE` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips, **metadata-scanning** must be handled by mutagen |
2137
+ | `PRTY_NO_MUTAGEN` | do not use [mutagen](https://pypi.org/project/mutagen/) for reading metadata from media files; will fallback to ffprobe |
2138
+ | `PRTY_NO_PIL` | disable all [Pillow](https://pypi.org/project/pillow/)-based thumbnail support; will fallback to libvips or ffmpeg |
2139
+ | `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
2140
+ | `PRTY_NO_PIL_AVIF` | disable 3rd-party Pillow plugin for [AVIF support](https://pypi.org/project/pillow-avif-plugin/) |
2141
+ | `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pyheif-pillow-opener/) |
2142
+ | `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
2143
+ | `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
2144
+ | `PRTY_NO_VIPS` | disable all [libvips](https://pypi.org/project/pyvips/)-based thumbnail support; will fallback to Pillow or ffmpeg |
2145
+
2146
+ example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py`
2147
+
2148
+ * `PRTY_NO_PIL` saves ram
2149
+ * `PRTY_NO_VIPS` saves ram and startup time
2150
+ * python2.7 on windows: `PRTY_NO_FFMPEG` + `PRTY_NO_FFPROBE` saves startup time
2151
+
2152
+
2103
2153
  ## optional gpl stuff
2104
2154
 
2105
2155
  some bundled tools have copyleft dependencies, see [./bin/#mtag](bin/#mtag)