copyparty 1.16.17__tar.gz → 1.16.18__tar.gz

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 (122) hide show
  1. {copyparty-1.16.17 → copyparty-1.16.18}/PKG-INFO +4 -3
  2. {copyparty-1.16.17 → copyparty-1.16.18}/README.md +1 -1
  3. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/__main__.py +6 -0
  4. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/__version__.py +2 -2
  5. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/cfg.py +2 -0
  6. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/ftpd.py +11 -0
  7. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/httpcli.py +19 -1
  8. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/mtag.py +1 -2
  9. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/svchub.py +3 -2
  10. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/szip.py +1 -2
  11. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/tcpsrv.py +18 -0
  12. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/tftpd.py +29 -8
  13. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/up2k.py +5 -3
  14. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/util.py +30 -3
  15. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty.egg-info/PKG-INFO +4 -3
  16. {copyparty-1.16.17 → copyparty-1.16.18}/LICENSE +0 -0
  17. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/__init__.py +0 -0
  18. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/authsrv.py +0 -0
  19. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/bos/__init__.py +0 -0
  20. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/bos/bos.py +0 -0
  21. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/bos/path.py +0 -0
  22. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/broker_mp.py +0 -0
  23. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/broker_mpw.py +0 -0
  24. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/broker_thr.py +0 -0
  25. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/broker_util.py +0 -0
  26. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/cert.py +0 -0
  27. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/dxml.py +0 -0
  28. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/fsutil.py +0 -0
  29. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/httpconn.py +0 -0
  30. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/httpsrv.py +0 -0
  31. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/ico.py +0 -0
  32. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/mdns.py +0 -0
  33. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/metrics.py +0 -0
  34. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/multicast.py +0 -0
  35. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/pwhash.py +0 -0
  36. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/res/COPYING.txt +0 -0
  37. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/res/__init__.py +0 -0
  38. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/res/insecure.pem +0 -0
  39. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/smbd.py +0 -0
  40. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/ssdp.py +0 -0
  41. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/star.py +0 -0
  42. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/__init__.py +0 -0
  43. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/dnslib/__init__.py +0 -0
  44. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/dnslib/bimap.py +0 -0
  45. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/dnslib/bit.py +0 -0
  46. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/dnslib/buffer.py +0 -0
  47. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/dnslib/dns.py +0 -0
  48. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/dnslib/label.py +0 -0
  49. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/dnslib/lex.py +0 -0
  50. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/dnslib/ranges.py +0 -0
  51. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/ifaddr/__init__.py +0 -0
  52. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/ifaddr/_posix.py +0 -0
  53. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/ifaddr/_shared.py +0 -0
  54. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/ifaddr/_win32.py +0 -0
  55. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/qrcodegen.py +0 -0
  56. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/stolen/surrogateescape.py +0 -0
  57. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/sutil.py +0 -0
  58. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/th_cli.py +0 -0
  59. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/th_srv.py +0 -0
  60. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/u2idx.py +0 -0
  61. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/a/__init__.py +0 -0
  62. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/a/partyfuse.py +0 -0
  63. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/a/u2c.py +0 -0
  64. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/a/webdav-cfg.bat +0 -0
  65. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/baguettebox.js.gz +0 -0
  66. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/browser.css.gz +0 -0
  67. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/browser.html +0 -0
  68. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/browser.js.gz +0 -0
  69. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/browser2.html +0 -0
  70. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/cf.html +0 -0
  71. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/dbg-audio.js.gz +0 -0
  72. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/dd/2.png +0 -0
  73. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/dd/3.png +0 -0
  74. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/dd/4.png +0 -0
  75. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/dd/5.png +0 -0
  76. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/dd/__init__.py +0 -0
  77. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/__init__.py +0 -0
  78. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/busy.mp3.gz +0 -0
  79. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/easymde.css.gz +0 -0
  80. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/easymde.js.gz +0 -0
  81. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/fuse.py +0 -0
  82. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/marked.js.gz +0 -0
  83. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/mini-fa.css.gz +0 -0
  84. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/mini-fa.woff +0 -0
  85. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/prism.css.gz +0 -0
  86. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/prism.js.gz +0 -0
  87. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/prismd.css.gz +0 -0
  88. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/scp.woff2 +0 -0
  89. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  90. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  91. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/md.css.gz +0 -0
  92. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/md.html +0 -0
  93. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/md.js.gz +0 -0
  94. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/md2.css.gz +0 -0
  95. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/md2.js.gz +0 -0
  96. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/mde.css.gz +0 -0
  97. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/mde.html +0 -0
  98. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/mde.js.gz +0 -0
  99. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/msg.css.gz +0 -0
  100. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/msg.html +0 -0
  101. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/rups.css.gz +0 -0
  102. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/rups.html +0 -0
  103. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/rups.js.gz +0 -0
  104. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/shares.css.gz +0 -0
  105. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/shares.html +0 -0
  106. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/shares.js.gz +0 -0
  107. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/splash.css.gz +0 -0
  108. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/splash.html +0 -0
  109. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/splash.js.gz +0 -0
  110. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/svcs.html +0 -0
  111. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/svcs.js.gz +0 -0
  112. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/ui.css.gz +0 -0
  113. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/up2k.js.gz +0 -0
  114. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/util.js.gz +0 -0
  115. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty/web/w.hash.js.gz +0 -0
  116. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty.egg-info/SOURCES.txt +0 -0
  117. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty.egg-info/dependency_links.txt +0 -0
  118. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty.egg-info/entry_points.txt +0 -0
  119. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty.egg-info/requires.txt +0 -0
  120. {copyparty-1.16.17 → copyparty-1.16.18}/copyparty.egg-info/top_level.txt +0 -0
  121. {copyparty-1.16.17 → copyparty-1.16.18}/pyproject.toml +0 -0
  122. {copyparty-1.16.17 → copyparty-1.16.18}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.16.17
3
+ Version: 1.16.18
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
@@ -54,6 +54,7 @@ Provides-Extra: pwhash
54
54
  Requires-Dist: argon2-cffi; extra == "pwhash"
55
55
  Provides-Extra: zeromq
56
56
  Requires-Dist: pyzmq; extra == "zeromq"
57
+ Dynamic: license-file
57
58
 
58
59
  <img src="https://github.com/9001/copyparty/raw/hovudstraum/docs/logo.svg" width="250" align="right"/>
59
60
 
@@ -157,7 +158,7 @@ turn almost any device into a file server with resumable uploads/downloads using
157
158
  * [custom mimetypes](#custom-mimetypes) - change the association of a file extension
158
159
  * [GDPR compliance](#GDPR-compliance) - imagine using copyparty professionally...
159
160
  * [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
160
- * [feature beefybits](#feature-beefybits) - force-enable incompatible features
161
+ * [feature beefybits](#feature-beefybits) - force-enable features with known issues on your OS/env
161
162
  * [packages](#packages) - the party might be closer than you think
162
163
  * [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
163
164
  * [fedora package](#fedora-package) - does not exist yet
@@ -100,7 +100,7 @@ turn almost any device into a file server with resumable uploads/downloads using
100
100
  * [custom mimetypes](#custom-mimetypes) - change the association of a file extension
101
101
  * [GDPR compliance](#GDPR-compliance) - imagine using copyparty professionally...
102
102
  * [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
103
- * [feature beefybits](#feature-beefybits) - force-enable incompatible features
103
+ * [feature beefybits](#feature-beefybits) - force-enable features with known issues on your OS/env
104
104
  * [packages](#packages) - the party might be closer than you think
105
105
  * [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
106
106
  * [fedora package](#fedora-package) - does not exist yet
@@ -40,6 +40,7 @@ from .cfg import flagcats, onedash
40
40
  from .svchub import SvcHub
41
41
  from .util import (
42
42
  APPLESAN_TXT,
43
+ BAD_BOTS,
43
44
  DEF_EXP,
44
45
  DEF_MTE,
45
46
  DEF_MTH,
@@ -1021,6 +1022,8 @@ def add_network(ap):
1021
1022
  ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances")
1022
1023
  else:
1023
1024
  ap2.add_argument("--freebind", action="store_true", help="allow listening on IPs which do not yet exist, for example if the network interfaces haven't finished going up. Only makes sense for IPs other than '0.0.0.0', '127.0.0.1', '::', and '::1'. May require running as root (unless net.ipv6.ip_nonlocal_bind)")
1025
+ ap2.add_argument("--wr-h-eps", metavar="PATH", type=u, default="", help="write list of listening-on ip:port to textfile at \033[33mPATH\033[0m when http-servers have started")
1026
+ ap2.add_argument("--wr-h-aon", metavar="PATH", type=u, default="", help="write list of accessible-on ip:port to textfile at \033[33mPATH\033[0m when http-servers have started")
1024
1027
  ap2.add_argument("--s-thead", metavar="SEC", type=int, default=120, help="socket timeout (read request header)")
1025
1028
  ap2.add_argument("--s-tbody", metavar="SEC", type=float, default=128.0, help="socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2%% speed boost")
1026
1029
  ap2.add_argument("--s-rd-sz", metavar="B", type=int, default=256*1024, help="socket read size in bytes (indirectly affects filesystem writes; recommendation: keep equal-to or lower-than \033[33m--iobuf\033[0m)")
@@ -1214,6 +1217,7 @@ def add_yolo(ap):
1214
1217
  ap2 = ap.add_argument_group('yolo options')
1215
1218
  ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests")
1216
1219
  ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET")
1220
+ ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)")
1217
1221
 
1218
1222
 
1219
1223
  def add_optouts(ap):
@@ -1233,6 +1237,7 @@ def add_optouts(ap):
1233
1237
  ap2.add_argument("--zipmaxt", metavar="TXT", type=u, default="", help="custom errormessage when download size exceeds max (volflag=zipmaxt)")
1234
1238
  ap2.add_argument("--zipmaxu", action="store_true", help="authenticated users bypass the zip size limit (volflag=zipmaxu)")
1235
1239
  ap2.add_argument("--zip-who", metavar="LVL", type=int, default=3, help="who can download as zip/tar? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=authenticated-with-read-access, [\033[32m3\033[0m]=everyone-with-read-access (volflag=zip_who)\n\033[1;31mWARNING:\033[0m if a nested volume has a more restrictive value than a parent volume, then this will be \033[33mignored\033[0m if the download is initiated from the parent, more lenient volume")
1240
+ ap2.add_argument("--ua-nozip", metavar="PTN", type=u, default=BAD_BOTS, help="regex of user-agents to reject from download-as-zip/tar; disable with [\033[32mno\033[0m] or blank")
1236
1241
  ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar; same as \033[33m--zip-who=0\033[0m")
1237
1242
  ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
1238
1243
  ap2.add_argument("--no-lifetime", action="store_true", help="do not allow clients (or server config) to schedule an upload to be deleted after a given time")
@@ -1423,6 +1428,7 @@ def add_txt(ap):
1423
1428
  ap2.add_argument("--exp", action="store_true", help="enable textfile expansion -- replace {{self.ip}} and such; see \033[33m--help-exp\033[0m (volflag=exp)")
1424
1429
  ap2.add_argument("--exp-md", metavar="V,V,V", type=u, default=DEF_EXP, help="comma/space-separated list of placeholders to expand in markdown files; add/remove stuff on the default list with +hdr_foo or /vf.scan (volflag=exp_md)")
1425
1430
  ap2.add_argument("--exp-lg", metavar="V,V,V", type=u, default=DEF_EXP, help="comma/space-separated list of placeholders to expand in prologue/epilogue files (volflag=exp_lg)")
1431
+ ap2.add_argument("--ua-nodoc", metavar="PTN", type=u, default=BAD_BOTS, help="regex of user-agents to reject from viewing documents through ?doc=[...]; disable with [\033[32mno\033[0m] or blank")
1426
1432
 
1427
1433
 
1428
1434
  def add_og(ap):
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 16, 17)
3
+ VERSION = (1, 16, 18)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2025, 3, 16)
5
+ BUILD_DT = (2025, 3, 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)
@@ -52,6 +52,7 @@ def vf_bmap() :
52
52
  "og_s_title",
53
53
  "rand",
54
54
  "rss",
55
+ "wo_up_readme",
55
56
  "xdev",
56
57
  "xlink",
57
58
  "xvol",
@@ -173,6 +174,7 @@ flagcats = {
173
174
  "vmaxb=1g": "total volume size max 1 GiB (suffixes: b, k, m, g, t)",
174
175
  "vmaxn=4k": "max 4096 files in volume (suffixes: b, k, m, g, t)",
175
176
  "medialinks": "return medialinks for non-up2k uploads (not hotlinks)",
177
+ "wo_up_readme": "write-only users can upload logues without getting renamed",
176
178
  "rand": "force randomized filenames, 9 chars long by default",
177
179
  "nrand=N": "randomized filenames are N chars long",
178
180
  "u2ow=N": "overwrite existing files? 0=no 1=if-older 2=always",
@@ -19,6 +19,7 @@ from .__init__ import PY2, TYPE_CHECKING
19
19
  from .authsrv import VFS
20
20
  from .bos import bos
21
21
  from .util import (
22
+ FN_EMB,
22
23
  VF_CAREFUL,
23
24
  Daemon,
24
25
  ODict,
@@ -166,6 +167,16 @@ class FtpFs(AbstractedFS):
166
167
  fn = sanitize_fn(fn or "", "")
167
168
  vpath = vjoin(rd, fn)
168
169
  vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
170
+ if (
171
+ w
172
+ and fn.lower() in FN_EMB
173
+ and self.h.uname not in vfs.axs.uread
174
+ and "wo_up_readme" not in vfs.flags
175
+ ):
176
+ fn = "_wo_" + fn
177
+ vpath = vjoin(rd, fn)
178
+ vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
179
+
169
180
  if not vfs.realpath:
170
181
  t = "No filesystem mounted at [{}]"
171
182
  raise FSE(t.format(vpath))
@@ -4,7 +4,6 @@ from __future__ import print_function, unicode_literals
4
4
  import argparse # typechk
5
5
  import copy
6
6
  import errno
7
- import gzip
8
7
  import hashlib
9
8
  import itertools
10
9
  import json
@@ -46,6 +45,7 @@ from .util import (
46
45
  APPLESAN_RE,
47
46
  BITNESS,
48
47
  DAV_ALLPROPS,
48
+ FN_EMB,
49
49
  HAVE_SQLITE3,
50
50
  HTTPCODE,
51
51
  META_NOBOTS,
@@ -69,6 +69,7 @@ from .util import (
69
69
  get_df,
70
70
  get_spd,
71
71
  guess_mime,
72
+ gzip,
72
73
  gzip_file_orig_sz,
73
74
  gzip_orig_sz,
74
75
  has_resource,
@@ -2539,6 +2540,16 @@ class HttpCli(object):
2539
2540
  vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
2540
2541
  dbv, vrem = vfs.get_dbv(rem)
2541
2542
 
2543
+ name = sanitize_fn(name, "")
2544
+ if (
2545
+ not self.can_read
2546
+ and self.can_write
2547
+ and name.lower() in FN_EMB
2548
+ and "wo_up_readme" not in dbv.flags
2549
+ ):
2550
+ name = "_wo_" + name
2551
+
2552
+ body["name"] = name
2542
2553
  body["vtop"] = dbv.vpath
2543
2554
  body["ptop"] = dbv.realpath
2544
2555
  body["prel"] = vrem
@@ -3778,6 +3789,9 @@ class HttpCli(object):
3778
3789
  return "download-as-zip/tar is admin-only on this server"
3779
3790
  elif lvl <= 2 and self.uname in ("", "*"):
3780
3791
  return "you must be authenticated to download-as-zip/tar on this server"
3792
+ elif self.args.ua_nozip and self.args.ua_nozip.search(self.ua):
3793
+ t = "this URL contains no valuable information for bots/crawlers"
3794
+ raise Pebkac(403, t)
3781
3795
  return ""
3782
3796
 
3783
3797
  def tx_res(self, req_path ) :
@@ -6257,6 +6271,10 @@ class HttpCli(object):
6257
6271
 
6258
6272
  doc = self.uparam.get("doc") if self.can_read else None
6259
6273
  if doc:
6274
+ zp = self.args.ua_nodoc
6275
+ if zp and zp.search(self.ua):
6276
+ t = "this URL contains no valuable information for bots/crawlers"
6277
+ raise Pebkac(403, t)
6260
6278
  j2a["docname"] = doc
6261
6279
  doctxt = None
6262
6280
  dfn = lnames.get(doc.lower())
@@ -18,6 +18,7 @@ from .util import (
18
18
  REKOBO_LKEY,
19
19
  VF_CAREFUL,
20
20
  fsenc,
21
+ gzip,
21
22
  min_ex,
22
23
  pybin,
23
24
  retchk,
@@ -132,8 +133,6 @@ def au_unpk(
132
133
  fd, ret = tempfile.mkstemp("." + au)
133
134
 
134
135
  if pk == "gz":
135
- import gzip
136
-
137
136
  fi = gzip.GzipFile(abspath, mode="rb")
138
137
 
139
138
  elif pk == "xz":
@@ -3,7 +3,6 @@ from __future__ import print_function, unicode_literals
3
3
 
4
4
  import argparse
5
5
  import errno
6
- import gzip
7
6
  import logging
8
7
  import os
9
8
  import re
@@ -57,6 +56,7 @@ from .util import (
57
56
  ansi_re,
58
57
  build_netmap,
59
58
  expat_ver,
59
+ gzip,
60
60
  load_ipu,
61
61
  min_ex,
62
62
  mp,
@@ -759,7 +759,8 @@ class SvcHub(object):
759
759
  vs = os.path.expandvars(os.path.expanduser(vs))
760
760
  setattr(al, k, vs)
761
761
 
762
- for k in "dav_ua1 sus_urls nonsus_urls".split(" "):
762
+ zs = "dav_ua1 sus_urls nonsus_urls ua_nodoc ua_nozip"
763
+ for k in zs.split(" "):
763
764
  vs = getattr(al, k)
764
765
  if not vs or vs == "no":
765
766
  setattr(al, k, None)
@@ -4,12 +4,11 @@ from __future__ import print_function, unicode_literals
4
4
  import calendar
5
5
  import stat
6
6
  import time
7
- import zlib
8
7
 
9
8
  from .authsrv import AuthSrv
10
9
  from .bos import bos
11
10
  from .sutil import StreamArc, errdesc
12
- from .util import min_ex, sanitize_fn, spack, sunpack, yieldfile
11
+ from .util import min_ex, sanitize_fn, spack, sunpack, yieldfile, zlib
13
12
 
14
13
  def dostime2unix(buf ) :
15
14
  t, d = sunpack(b"<HH", buf)
@@ -148,9 +148,15 @@ class TcpSrv(object):
148
148
  if just_ll or self.args.ll:
149
149
  ll_ok.add(ip.split("/")[0])
150
150
 
151
+ listening_on = []
152
+ for ip, ports in sorted(ok.items()):
153
+ for port in sorted(ports):
154
+ listening_on.append("%s %s" % (ip, port))
155
+
151
156
  qr1 = {}
152
157
  qr2 = {}
153
158
  msgs = []
159
+ accessible_on = []
154
160
  title_tab = {}
155
161
  title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")]
156
162
  t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
@@ -166,6 +172,10 @@ class TcpSrv(object):
166
172
  ):
167
173
  continue
168
174
 
175
+ zs = "%s %s" % (ip, port)
176
+ if zs not in accessible_on:
177
+ accessible_on.append(zs)
178
+
169
179
  proto = " http"
170
180
  if self.args.http_only:
171
181
  pass
@@ -216,6 +226,14 @@ class TcpSrv(object):
216
226
  else:
217
227
  print("\n", end="")
218
228
 
229
+ for fn, ls in (
230
+ (self.args.wr_h_eps, listening_on),
231
+ (self.args.wr_h_aon, accessible_on),
232
+ ):
233
+ if fn:
234
+ with open(fn, "wb") as f:
235
+ f.write(("\n".join(ls)).encode("utf-8"))
236
+
219
237
  if self.args.qr or self.args.qrs:
220
238
  self.qr = self._qr(qr1, qr2)
221
239
 
@@ -36,7 +36,19 @@ from partftpy.TftpShared import TftpException
36
36
  from .__init__ import EXE, PY2, TYPE_CHECKING
37
37
  from .authsrv import VFS
38
38
  from .bos import bos
39
- from .util import UTC, BytesIO, Daemon, ODict, exclude_dotfiles, min_ex, runhook, undot
39
+ from .util import (
40
+ FN_EMB,
41
+ UTC,
42
+ BytesIO,
43
+ Daemon,
44
+ ODict,
45
+ exclude_dotfiles,
46
+ min_ex,
47
+ runhook,
48
+ undot,
49
+ vjoin,
50
+ vsplit,
51
+ )
40
52
 
41
53
  if TYPE_CHECKING:
42
54
  from .svchub import SvcHub
@@ -241,16 +253,25 @@ class Tftpd(object):
241
253
  for srv in srvs:
242
254
  srv.stop()
243
255
 
244
- def _v2a(self, caller , vpath , perms , *a ) :
256
+ def _v2a(
257
+ self, caller , vpath , perms , *a
258
+ ) :
245
259
  vpath = vpath.replace("\\", "/").lstrip("/")
246
260
  if not perms:
247
261
  perms = [True, True]
248
262
 
249
263
  debug('%s("%s", %s) %s\033[K\033[0m', caller, vpath, str(a), perms)
250
264
  vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
265
+ if perms[1] and "*" not in vfs.axs.uread and "wo_up_readme" not in vfs.flags:
266
+ zs, fn = vsplit(vpath)
267
+ if fn.lower() in FN_EMB:
268
+ vpath = vjoin(zs, "_wo_" + fn)
269
+ vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
270
+
251
271
  if not vfs.realpath:
252
272
  raise Exception("unmapped vfs")
253
- return vfs, vfs.canonical(rem)
273
+
274
+ return vfs, vpath, vfs.canonical(rem)
254
275
 
255
276
  def _ls(self, vpath , raddress , rport , force=False) :
256
277
  # generate file listing if vpath is dir.txt and return as file object
@@ -328,7 +349,7 @@ class Tftpd(object):
328
349
  else:
329
350
  raise Exception("bad mode %s" % (mode,))
330
351
 
331
- vfs, ap = self._v2a("open", vpath, [rd, wr])
352
+ vfs, vpath, ap = self._v2a("open", vpath, [rd, wr])
332
353
  if wr:
333
354
  if "*" not in vfs.axs.uwrite:
334
355
  yeet("blocked write; folder not world-writable: /%s" % (vpath,))
@@ -365,7 +386,7 @@ class Tftpd(object):
365
386
  return open(ap, mode, *a, **ka)
366
387
 
367
388
  def _mkdir(self, vpath , *a) :
368
- vfs, ap = self._v2a("mkdir", vpath, [])
389
+ vfs, _, ap = self._v2a("mkdir", vpath, [False, True])
369
390
  if "*" not in vfs.axs.uwrite:
370
391
  yeet("blocked mkdir; folder not world-writable: /%s" % (vpath,))
371
392
 
@@ -373,7 +394,7 @@ class Tftpd(object):
373
394
 
374
395
  def _unlink(self, vpath ) :
375
396
  # return bos.unlink(self._v2a("stat", vpath, *a)[1])
376
- vfs, ap = self._v2a("delete", vpath, [True, False, False, True])
397
+ vfs, _, ap = self._v2a("delete", vpath, [True, False, False, True])
377
398
 
378
399
  try:
379
400
  inf = bos.stat(ap)
@@ -397,7 +418,7 @@ class Tftpd(object):
397
418
 
398
419
  def _p_exists(self, vpath ) :
399
420
  try:
400
- ap = self._v2a("p.exists", vpath, [False, False])[1]
421
+ ap = self._v2a("p.exists", vpath, [False, False])[2]
401
422
  bos.stat(ap)
402
423
  return True
403
424
  except:
@@ -405,7 +426,7 @@ class Tftpd(object):
405
426
 
406
427
  def _p_isdir(self, vpath ) :
407
428
  try:
408
- st = bos.stat(self._v2a("p.isdir", vpath, [False, False])[1])
429
+ st = bos.stat(self._v2a("p.isdir", vpath, [False, False])[2])
409
430
  ret = stat.S_ISDIR(st.st_mode)
410
431
  return ret
411
432
  except:
@@ -2,7 +2,6 @@
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
4
  import errno
5
- import gzip
6
5
  import hashlib
7
6
  import json
8
7
  import math
@@ -42,6 +41,7 @@ from .util import (
42
41
  fsenc,
43
42
  gen_filekey,
44
43
  gen_filekey_dbg,
44
+ gzip,
45
45
  hidedir,
46
46
  humansize,
47
47
  min_ex,
@@ -2903,7 +2903,6 @@ class Up2k(object):
2903
2903
  if ptop not in self.registry:
2904
2904
  raise Pebkac(410, "location unavailable")
2905
2905
 
2906
- cj["name"] = sanitize_fn(cj["name"], "")
2907
2906
  cj["poke"] = now = self.db_act = self.vol_act[ptop] = time.time()
2908
2907
  wark = dwark = self._get_wark(cj)
2909
2908
  job = None
@@ -3220,6 +3219,7 @@ class Up2k(object):
3220
3219
  job["ptop"] = vfs.realpath
3221
3220
  job["vtop"] = vfs.vpath
3222
3221
  job["prel"] = rem
3222
+ job["name"] = sanitize_fn(job["name"], "")
3223
3223
  if zvfs.vpath != vfs.vpath:
3224
3224
  # print(json.dumps(job, sort_keys=True, indent=4))
3225
3225
  job["hash"] = cj["hash"]
@@ -3692,8 +3692,9 @@ class Up2k(object):
3692
3692
  if self.idx_wark(vflags, *z2):
3693
3693
  del self.registry[ptop][wark]
3694
3694
  else:
3695
- for k in "host tnam busy sprs poke t0c".split():
3695
+ for k in "host tnam busy sprs poke".split():
3696
3696
  del job[k]
3697
+ job.pop("t0c", None)
3697
3698
  job["t0"] = int(job["t0"])
3698
3699
  job["hash"] = []
3699
3700
  job["done"] = 1
@@ -4972,6 +4973,7 @@ class Up2k(object):
4972
4973
  job["ptop"] = vfs.realpath
4973
4974
  job["vtop"] = vfs.vpath
4974
4975
  job["prel"] = rem
4976
+ job["name"] = sanitize_fn(job["name"], "")
4975
4977
  if zvfs.vpath != vfs.vpath:
4976
4978
  self.log("xbu reloc2:%d..." % (depth,), 6)
4977
4979
  return self._handle_json(job, depth + 1)
@@ -31,6 +31,17 @@ from collections import Counter
31
31
  from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
32
32
  from queue import Queue
33
33
 
34
+ try:
35
+ from zlib_ng import gzip_ng as gzip
36
+ from zlib_ng import zlib_ng as zlib
37
+
38
+ sys.modules["gzip"] = gzip
39
+ # sys.modules["zlib"] = zlib
40
+ # `- somehow makes tarfile 3% slower with default malloc, and barely faster with mimalloc
41
+ except:
42
+ import gzip
43
+ import zlib
44
+
34
45
  from .__init__ import (
35
46
  ANYWIN,
36
47
  EXE,
@@ -213,6 +224,9 @@ SYMTIME = PY36 and os.utime in os.supports_follow_symlinks
213
224
 
214
225
  META_NOBOTS = '<meta name="robots" content="noindex, nofollow">\n'
215
226
 
227
+ # smart enough to understand javascript while also ignoring rel="nofollow"
228
+ BAD_BOTS = r"Barkrowler|bingbot|BLEXBot|Googlebot|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot"
229
+
216
230
  FFMPEG_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z"
217
231
 
218
232
  URL_PRJ = "https://github.com/9001/copyparty"
@@ -427,6 +441,8 @@ UNHUMANIZE_UNITS = {
427
441
 
428
442
  VF_CAREFUL = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
429
443
 
444
+ FN_EMB = set([".prologue.html", ".epilogue.html", "readme.md", "preadme.md"])
445
+
430
446
 
431
447
  def read_ram() :
432
448
  a = b = 0
@@ -1369,8 +1385,6 @@ def stackmon(fp , ival , suffix ) :
1369
1385
  buf = st.encode("utf-8", "replace")
1370
1386
 
1371
1387
  if fp.endswith(".gz"):
1372
- import gzip
1373
-
1374
1388
  # 2459b 2304b 2241b 2202b 2194b 2191b lv3..8
1375
1389
  # 0.06s 0.08s 0.11s 0.13s 0.16s 0.19s
1376
1390
  buf = gzip.compress(buf, compresslevel=6)
@@ -3958,9 +3972,22 @@ class WrongPostKey(Pebkac):
3958
3972
  self.datagen = datagen
3959
3973
 
3960
3974
 
3961
- _ = (mp, BytesIO, quote, unquote, SQLITE_VER, JINJA_VER, PYFTPD_VER, PARTFTPY_VER)
3975
+ _ = (
3976
+ gzip,
3977
+ mp,
3978
+ zlib,
3979
+ BytesIO,
3980
+ quote,
3981
+ unquote,
3982
+ SQLITE_VER,
3983
+ JINJA_VER,
3984
+ PYFTPD_VER,
3985
+ PARTFTPY_VER,
3986
+ )
3962
3987
  __all__ = [
3988
+ "gzip",
3963
3989
  "mp",
3990
+ "zlib",
3964
3991
  "BytesIO",
3965
3992
  "quote",
3966
3993
  "unquote",
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.16.17
3
+ Version: 1.16.18
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
@@ -54,6 +54,7 @@ Provides-Extra: pwhash
54
54
  Requires-Dist: argon2-cffi; extra == "pwhash"
55
55
  Provides-Extra: zeromq
56
56
  Requires-Dist: pyzmq; extra == "zeromq"
57
+ Dynamic: license-file
57
58
 
58
59
  <img src="https://github.com/9001/copyparty/raw/hovudstraum/docs/logo.svg" width="250" align="right"/>
59
60
 
@@ -157,7 +158,7 @@ turn almost any device into a file server with resumable uploads/downloads using
157
158
  * [custom mimetypes](#custom-mimetypes) - change the association of a file extension
158
159
  * [GDPR compliance](#GDPR-compliance) - imagine using copyparty professionally...
159
160
  * [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
160
- * [feature beefybits](#feature-beefybits) - force-enable incompatible features
161
+ * [feature beefybits](#feature-beefybits) - force-enable features with known issues on your OS/env
161
162
  * [packages](#packages) - the party might be closer than you think
162
163
  * [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
163
164
  * [fedora package](#fedora-package) - does not exist yet
File without changes
File without changes
File without changes