copyparty 1.18.8__tar.gz → 1.18.10__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 (130) hide show
  1. {copyparty-1.18.8 → copyparty-1.18.10}/PKG-INFO +30 -2
  2. {copyparty-1.18.8 → copyparty-1.18.10}/README.md +22 -1
  3. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/__init__.py +0 -4
  4. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/__main__.py +13 -5
  5. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/__version__.py +2 -2
  6. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/authsrv.py +10 -3
  7. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/httpcli.py +85 -35
  8. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/httpsrv.py +2 -1
  9. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/mtag.py +5 -1
  10. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/svchub.py +2 -0
  11. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/tcpsrv.py +2 -3
  12. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/th_cli.py +2 -2
  13. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/th_srv.py +68 -2
  14. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/up2k.py +27 -21
  15. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/util.py +15 -0
  16. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/a/u2c.py +3 -1
  17. copyparty-1.18.10/copyparty/web/browser.css.gz +0 -0
  18. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/browser.html +1 -1
  19. copyparty-1.18.10/copyparty/web/browser.js.gz +0 -0
  20. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/rups.html +1 -0
  21. copyparty-1.18.10/copyparty/web/rups.js.gz +0 -0
  22. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/shares.html +1 -0
  23. copyparty-1.18.10/copyparty/web/shares.js.gz +0 -0
  24. copyparty-1.18.10/copyparty/web/splash.js.gz +0 -0
  25. copyparty-1.18.10/copyparty/web/up2k.js.gz +0 -0
  26. copyparty-1.18.10/copyparty/web/util.js.gz +0 -0
  27. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty.egg-info/PKG-INFO +30 -2
  28. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty.egg-info/SOURCES.txt +0 -5
  29. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty.egg-info/requires.txt +8 -0
  30. {copyparty-1.18.8 → copyparty-1.18.10}/pyproject.toml +8 -1
  31. copyparty-1.18.8/copyparty/web/browser.css.gz +0 -0
  32. copyparty-1.18.8/copyparty/web/browser.js.gz +0 -0
  33. copyparty-1.18.8/copyparty/web/dd/2.png +0 -0
  34. copyparty-1.18.8/copyparty/web/dd/3.png +0 -0
  35. copyparty-1.18.8/copyparty/web/dd/4.png +0 -0
  36. copyparty-1.18.8/copyparty/web/dd/5.png +0 -0
  37. copyparty-1.18.8/copyparty/web/deps/__init__.py +0 -0
  38. copyparty-1.18.8/copyparty/web/rups.js.gz +0 -0
  39. copyparty-1.18.8/copyparty/web/shares.js.gz +0 -0
  40. copyparty-1.18.8/copyparty/web/splash.js.gz +0 -0
  41. copyparty-1.18.8/copyparty/web/up2k.js.gz +0 -0
  42. copyparty-1.18.8/copyparty/web/util.js.gz +0 -0
  43. {copyparty-1.18.8 → copyparty-1.18.10}/LICENSE +0 -0
  44. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/bos/__init__.py +0 -0
  45. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/bos/bos.py +0 -0
  46. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/bos/path.py +0 -0
  47. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/broker_mp.py +0 -0
  48. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/broker_mpw.py +0 -0
  49. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/broker_thr.py +0 -0
  50. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/broker_util.py +0 -0
  51. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/cert.py +0 -0
  52. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/cfg.py +0 -0
  53. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/dxml.py +0 -0
  54. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/fsutil.py +0 -0
  55. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/ftpd.py +0 -0
  56. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/httpconn.py +0 -0
  57. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/ico.py +0 -0
  58. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/mdns.py +0 -0
  59. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/metrics.py +0 -0
  60. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/multicast.py +0 -0
  61. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/pwhash.py +0 -0
  62. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/res/COPYING.txt +0 -0
  63. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/res/__init__.py +0 -0
  64. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/res/insecure.pem +0 -0
  65. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/smbd.py +0 -0
  66. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/ssdp.py +0 -0
  67. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/star.py +0 -0
  68. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/__init__.py +0 -0
  69. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/dnslib/__init__.py +0 -0
  70. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/dnslib/bimap.py +0 -0
  71. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/dnslib/bit.py +0 -0
  72. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/dnslib/buffer.py +0 -0
  73. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/dnslib/dns.py +0 -0
  74. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/dnslib/label.py +0 -0
  75. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/dnslib/lex.py +0 -0
  76. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/dnslib/ranges.py +0 -0
  77. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/ifaddr/__init__.py +0 -0
  78. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/ifaddr/_posix.py +0 -0
  79. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/ifaddr/_shared.py +0 -0
  80. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/ifaddr/_win32.py +0 -0
  81. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/qrcodegen.py +0 -0
  82. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/stolen/surrogateescape.py +0 -0
  83. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/sutil.py +0 -0
  84. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/szip.py +0 -0
  85. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/tftpd.py +0 -0
  86. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/u2idx.py +0 -0
  87. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/a/__init__.py +0 -0
  88. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/a/partyfuse.py +0 -0
  89. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/a/webdav-cfg.bat +0 -0
  90. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/baguettebox.js.gz +0 -0
  91. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/browser2.html +0 -0
  92. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/cf.html +0 -0
  93. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/dbg-audio.js.gz +0 -0
  94. {copyparty-1.18.8/copyparty/web/dd → copyparty-1.18.10/copyparty/web/deps}/__init__.py +0 -0
  95. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/busy.mp3.gz +0 -0
  96. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/easymde.css.gz +0 -0
  97. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/easymde.js.gz +0 -0
  98. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/fuse.py +0 -0
  99. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/marked.js.gz +0 -0
  100. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/mini-fa.css.gz +0 -0
  101. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/mini-fa.woff +0 -0
  102. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/prism.css.gz +0 -0
  103. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/prism.js.gz +0 -0
  104. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/prismd.css.gz +0 -0
  105. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/scp.woff2 +0 -0
  106. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  107. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  108. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/idp.html +0 -0
  109. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/md.css.gz +0 -0
  110. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/md.html +0 -0
  111. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/md.js.gz +0 -0
  112. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/md2.css.gz +0 -0
  113. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/md2.js.gz +0 -0
  114. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/mde.css.gz +0 -0
  115. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/mde.html +0 -0
  116. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/mde.js.gz +0 -0
  117. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/msg.css.gz +0 -0
  118. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/msg.html +0 -0
  119. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/rups.css.gz +0 -0
  120. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/shares.css.gz +0 -0
  121. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/splash.css.gz +0 -0
  122. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/splash.html +0 -0
  123. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/svcs.html +0 -0
  124. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/svcs.js.gz +0 -0
  125. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/ui.css.gz +0 -0
  126. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty/web/w.hash.js.gz +0 -0
  127. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty.egg-info/dependency_links.txt +0 -0
  128. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty.egg-info/entry_points.txt +0 -0
  129. {copyparty-1.18.8 → copyparty-1.18.10}/copyparty.egg-info/top_level.txt +0 -0
  130. {copyparty-1.18.8 → copyparty-1.18.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.18.8
3
+ Version: 1.18.10
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,6 +37,13 @@ Requires-Python: >=3.3
37
37
  Description-Content-Type: text/markdown
38
38
  License-File: LICENSE
39
39
  Requires-Dist: Jinja2
40
+ Provides-Extra: all
41
+ Requires-Dist: argon2-cffi; extra == "all"
42
+ Requires-Dist: partftpy>=0.4.0; extra == "all"
43
+ Requires-Dist: Pillow; extra == "all"
44
+ Requires-Dist: pyftpdlib; extra == "all"
45
+ Requires-Dist: pyopenssl; extra == "all"
46
+ Requires-Dist: pyzmq; extra == "all"
40
47
  Provides-Extra: thumbnails
41
48
  Requires-Dist: Pillow; extra == "thumbnails"
42
49
  Provides-Extra: thumbnails2
@@ -205,11 +212,14 @@ made in Norway 🇳🇴
205
212
 
206
213
  just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
207
214
 
215
+ > ℹ️ the sfx is a [self-extractor](https://github.com/9001/copyparty/issues/270) which unpacks an embedded `tar.gz` into `$TEMP` -- if this looks too scary, you can use the [zipapp](#zipapp) which has slightly worse performance
216
+
208
217
  * or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
209
218
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
210
219
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
211
220
  * or if you are on android, [install copyparty in termux](#install-on-android)
212
221
  * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
222
+ * or if you have [uv](https://docs.astral.sh/uv/) installed, run `uv tool run copyparty`
213
223
  * or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
214
224
  * or if your OS is dead, give the [bootable flashdrive / cd-rom](https://a.ocv.me/pub/stuff/edcd001/enterprise-edition/) a spin
215
225
  * or if you don't trust copyparty yet and want to isolate it a little, then...
@@ -570,12 +580,17 @@ anyone trying to bruteforce a password gets banned according to `--ban-pw`; defa
570
580
 
571
581
  and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
572
582
 
583
+ * you can also `PRTY_CONFIG=foobar.conf python copyparty-sfx.py` (convenient in docker etc)
584
+
573
585
  ```yaml
574
586
  [accounts]
575
587
  u1: p1 # create account "u1" with password "p1"
576
588
  u2: p2 # (note that comments must have
577
589
  u3: p3 # two spaces before the # sign)
578
590
 
591
+ [groups]
592
+ g1: u1, u2 # create a group
593
+
579
594
  [/] # this URL will be mapped to...
580
595
  /srv # ...this folder on the server filesystem
581
596
  accs:
@@ -585,6 +600,7 @@ and if you want to use config files instead of commandline args (good!) then her
585
600
  /mnt/music # which is mapped to this folder
586
601
  accs:
587
602
  r: u1, u2 # only these accounts can read,
603
+ r: @g1 # (exactly the same, just with a group instead)
588
604
  rw: u3 # and only u3 can read-write
589
605
 
590
606
  [/inc]
@@ -1141,6 +1157,9 @@ open the `[🎺]` media-player-settings tab to configure it,
1141
1157
  * `[awo]` is `opus` in a `weba` file, good for iPhones (iOS 17.5 and newer) but Apple is still fixing some state-confusion bugs as of iOS 18.2.1
1142
1158
  * `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the most part
1143
1159
  * `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere
1160
+ * `[flac]` -- lossless but compressed, for LAN and/or fiber playback on electrostatic headphones
1161
+ * `[wav]` -- lossless and uncompressed, for LAN and/or fiber playback on electrostatic headphones connected to very old equipment
1162
+ * `flac` and `wav` must be enabled with `--allow-flac` / `--allow-wav` to allow spending the disk space
1144
1163
  * "tint" reduces the contrast of the playback bar
1145
1164
 
1146
1165
 
@@ -1482,6 +1501,8 @@ note that this disables hotlinking because the opengraph spec demands it; to sne
1482
1501
 
1483
1502
  you can also hotlink files regardless by appending `?raw` to the url
1484
1503
 
1504
+ > WARNING: if you plan to use WebDAV, then `--og-ua` / `og_ua` must be configured
1505
+
1485
1506
  if you want to entirely replace the copyparty response with your own jinja2 template, give the template filepath to `--og-tpl` or volflag `og_tpl` (all members of `HttpCli` are available through the `this` object)
1486
1507
 
1487
1508
 
@@ -1938,6 +1959,8 @@ you can disable the built-in password-based login system, and instead replace it
1938
1959
 
1939
1960
  * the regular config-defined users will be used as a fallback for requests which don't include a valid (trusted) IdP username header
1940
1961
 
1962
+ * if your IdP-server is slow, consider `--idp-cookie` and let requests with the cookie `cppws` bypass the IdP; experimental sessions-based feature added for a party
1963
+
1941
1964
  some popular identity providers are [Authelia](https://www.authelia.com/) (config-file based) and [authentik](https://goauthentik.io/) (GUI-based, more complex)
1942
1965
 
1943
1966
  there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik) which is hopefully a good starting point (alternatively see [./docs/idp.md](./docs/idp.md) if you're the DIY type)
@@ -2486,6 +2509,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
2486
2509
  | **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
2487
2510
  | **sony psp** 5.50 | can browse, upload/mkdir/msg (thx dwarf) [screenshot](https://github.com/user-attachments/assets/9d21f020-1110-4652-abeb-6fc09c533d4f) |
2488
2511
  | **nintendo 3ds** | can browse, upload, view thumbnails (thx bnjmn) |
2512
+ | **Nintendo Wii (Opera 9.0 "Internet Channel")** | can browse, can't upload or download (no local storage), can view images - works best with `?b=u`, default view broken |
2489
2513
 
2490
2514
  <p align="center"><img src="https://github.com/user-attachments/assets/88deab3d-6cad-4017-8841-2f041472b853" /></p>
2491
2515
 
@@ -2756,6 +2780,10 @@ optionally also specify `--ah-cli` to enter an interactive mode where it will ha
2756
2780
 
2757
2781
  the default configs take about 0.4 sec and 256 MiB RAM to process a new password on a decent laptop
2758
2782
 
2783
+ when generating hashes using `--ah-cli` for docker or systemd services, make sure it is using the same `--ah-salt` by:
2784
+ * inspecting the generated salt using `--show-ah-salt` in copyparty service configuration
2785
+ * setting the same `--ah-salt` in both environments
2786
+
2759
2787
 
2760
2788
  ## https
2761
2789
 
@@ -2900,7 +2928,7 @@ then again, if you are already into downloading shady binaries from the internet
2900
2928
 
2901
2929
  ## zipapp
2902
2930
 
2903
- another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but it does not unpack any temporary files to disk, so it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails
2931
+ another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but, unlike the sfx, it is a completely normal zipfile which does not unpack any temporary files to disk, so it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails
2904
2932
 
2905
2933
  run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails
2906
2934
 
@@ -147,11 +147,14 @@ made in Norway 🇳🇴
147
147
 
148
148
  just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
149
149
 
150
+ > ℹ️ the sfx is a [self-extractor](https://github.com/9001/copyparty/issues/270) which unpacks an embedded `tar.gz` into `$TEMP` -- if this looks too scary, you can use the [zipapp](#zipapp) which has slightly worse performance
151
+
150
152
  * or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
151
153
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
152
154
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
153
155
  * or if you are on android, [install copyparty in termux](#install-on-android)
154
156
  * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
157
+ * or if you have [uv](https://docs.astral.sh/uv/) installed, run `uv tool run copyparty`
155
158
  * or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
156
159
  * or if your OS is dead, give the [bootable flashdrive / cd-rom](https://a.ocv.me/pub/stuff/edcd001/enterprise-edition/) a spin
157
160
  * or if you don't trust copyparty yet and want to isolate it a little, then...
@@ -512,12 +515,17 @@ anyone trying to bruteforce a password gets banned according to `--ban-pw`; defa
512
515
 
513
516
  and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
514
517
 
518
+ * you can also `PRTY_CONFIG=foobar.conf python copyparty-sfx.py` (convenient in docker etc)
519
+
515
520
  ```yaml
516
521
  [accounts]
517
522
  u1: p1 # create account "u1" with password "p1"
518
523
  u2: p2 # (note that comments must have
519
524
  u3: p3 # two spaces before the # sign)
520
525
 
526
+ [groups]
527
+ g1: u1, u2 # create a group
528
+
521
529
  [/] # this URL will be mapped to...
522
530
  /srv # ...this folder on the server filesystem
523
531
  accs:
@@ -527,6 +535,7 @@ and if you want to use config files instead of commandline args (good!) then her
527
535
  /mnt/music # which is mapped to this folder
528
536
  accs:
529
537
  r: u1, u2 # only these accounts can read,
538
+ r: @g1 # (exactly the same, just with a group instead)
530
539
  rw: u3 # and only u3 can read-write
531
540
 
532
541
  [/inc]
@@ -1083,6 +1092,9 @@ open the `[🎺]` media-player-settings tab to configure it,
1083
1092
  * `[awo]` is `opus` in a `weba` file, good for iPhones (iOS 17.5 and newer) but Apple is still fixing some state-confusion bugs as of iOS 18.2.1
1084
1093
  * `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the most part
1085
1094
  * `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere
1095
+ * `[flac]` -- lossless but compressed, for LAN and/or fiber playback on electrostatic headphones
1096
+ * `[wav]` -- lossless and uncompressed, for LAN and/or fiber playback on electrostatic headphones connected to very old equipment
1097
+ * `flac` and `wav` must be enabled with `--allow-flac` / `--allow-wav` to allow spending the disk space
1086
1098
  * "tint" reduces the contrast of the playback bar
1087
1099
 
1088
1100
 
@@ -1424,6 +1436,8 @@ note that this disables hotlinking because the opengraph spec demands it; to sne
1424
1436
 
1425
1437
  you can also hotlink files regardless by appending `?raw` to the url
1426
1438
 
1439
+ > WARNING: if you plan to use WebDAV, then `--og-ua` / `og_ua` must be configured
1440
+
1427
1441
  if you want to entirely replace the copyparty response with your own jinja2 template, give the template filepath to `--og-tpl` or volflag `og_tpl` (all members of `HttpCli` are available through the `this` object)
1428
1442
 
1429
1443
 
@@ -1880,6 +1894,8 @@ you can disable the built-in password-based login system, and instead replace it
1880
1894
 
1881
1895
  * the regular config-defined users will be used as a fallback for requests which don't include a valid (trusted) IdP username header
1882
1896
 
1897
+ * if your IdP-server is slow, consider `--idp-cookie` and let requests with the cookie `cppws` bypass the IdP; experimental sessions-based feature added for a party
1898
+
1883
1899
  some popular identity providers are [Authelia](https://www.authelia.com/) (config-file based) and [authentik](https://goauthentik.io/) (GUI-based, more complex)
1884
1900
 
1885
1901
  there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik) which is hopefully a good starting point (alternatively see [./docs/idp.md](./docs/idp.md) if you're the DIY type)
@@ -2428,6 +2444,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
2428
2444
  | **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
2429
2445
  | **sony psp** 5.50 | can browse, upload/mkdir/msg (thx dwarf) [screenshot](https://github.com/user-attachments/assets/9d21f020-1110-4652-abeb-6fc09c533d4f) |
2430
2446
  | **nintendo 3ds** | can browse, upload, view thumbnails (thx bnjmn) |
2447
+ | **Nintendo Wii (Opera 9.0 "Internet Channel")** | can browse, can't upload or download (no local storage), can view images - works best with `?b=u`, default view broken |
2431
2448
 
2432
2449
  <p align="center"><img src="https://github.com/user-attachments/assets/88deab3d-6cad-4017-8841-2f041472b853" /></p>
2433
2450
 
@@ -2698,6 +2715,10 @@ optionally also specify `--ah-cli` to enter an interactive mode where it will ha
2698
2715
 
2699
2716
  the default configs take about 0.4 sec and 256 MiB RAM to process a new password on a decent laptop
2700
2717
 
2718
+ when generating hashes using `--ah-cli` for docker or systemd services, make sure it is using the same `--ah-salt` by:
2719
+ * inspecting the generated salt using `--show-ah-salt` in copyparty service configuration
2720
+ * setting the same `--ah-salt` in both environments
2721
+
2701
2722
 
2702
2723
  ## https
2703
2724
 
@@ -2842,7 +2863,7 @@ then again, if you are already into downloading shady binaries from the internet
2842
2863
 
2843
2864
  ## zipapp
2844
2865
 
2845
- another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but it does not unpack any temporary files to disk, so it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails
2866
+ another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but, unlike the sfx, it is a completely normal zipfile which does not unpack any temporary files to disk, so it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails
2846
2867
 
2847
2868
  run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails
2848
2869
 
@@ -60,10 +60,6 @@ web/browser.js
60
60
  web/browser2.html
61
61
  web/cf.html
62
62
  web/copyparty.gif
63
- web/dd/2.png
64
- web/dd/3.png
65
- web/dd/4.png
66
- web/dd/5.png
67
63
  web/deps/busy.mp3
68
64
  web/deps/easymde.css
69
65
  web/deps/easymde.js
@@ -87,6 +87,10 @@ u = unicode
87
87
  printed = []
88
88
  zsid = uuid.uuid4().urn[4:]
89
89
 
90
+ CFG_DEF = [os.environ.get("PRTY_CONFIG", "")]
91
+ if not CFG_DEF[0]:
92
+ CFG_DEF.pop()
93
+
90
94
 
91
95
  class RiceFormatter(argparse.HelpFormatter):
92
96
  def __init__(self, *args , **kwargs ) :
@@ -566,7 +570,7 @@ def get_sects():
566
570
 
567
571
  --grp takes groupname:username1,username2,...
568
572
  and groupnames can be used instead of usernames in -v
569
- by prefixing the groupname with %
573
+ by prefixing the groupname with @
570
574
 
571
575
  list of permissions:
572
576
  "r" (read): list folder contents, download files
@@ -982,7 +986,7 @@ def build_flags_desc():
982
986
 
983
987
  def add_general(ap, nc, srvname):
984
988
  ap2 = ap.add_argument_group('general options')
985
- ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file")
989
+ ap2.add_argument("-c", metavar="PATH", type=u, default=CFG_DEF, action="append", help="add config file")
986
990
  ap2.add_argument("-nc", metavar="NUM", type=int, default=nc, help="max num clients")
987
991
  ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all")
988
992
  ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]")
@@ -1101,7 +1105,7 @@ def add_tls(ap, cert_path):
1101
1105
  ap2 = ap.add_argument_group('SSL/TLS options')
1102
1106
  ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
1103
1107
  ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
1104
- ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to TLS certificate")
1108
+ ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain")
1105
1109
  ap2.add_argument("--ssl-ver", metavar="LIST", type=u, default="", help="set allowed ssl/tls versions; [\033[32mhelp\033[0m] shows available versions; default is what your python version considers safe")
1106
1110
  ap2.add_argument("--ciphers", metavar="LIST", type=u, default="", help="set allowed ssl/tls ciphers; [\033[32mhelp\033[0m] shows available ciphers")
1107
1111
  ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
@@ -1138,6 +1142,7 @@ def add_auth(ap):
1138
1142
  ap2.add_argument("--idp-db", metavar="PATH", type=u, default=idp_db, help="where to store the known IdP users/groups (if you run multiple copyparty instances, make sure they use different DBs)")
1139
1143
  ap2.add_argument("--idp-store", metavar="N", type=int, default=1, help="how to use \033[33m--idp-db\033[0m; [\033[32m0\033[0m] = entirely disable, [\033[32m1\033[0m] = write-only (effectively disabled), [\033[32m2\033[0m] = remember users, [\033[32m3\033[0m] = remember users and groups.\nNOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP")
1140
1144
  ap2.add_argument("--idp-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to use /?idp (the cache management UI)")
1145
+ ap2.add_argument("--idp-cookie", metavar="S", type=int, default=0, help="generate a session-token for IdP users which is written to cookie \033[33mcppws\033[0m (or \033[33mcppwd\033[0m if plaintext), to reduce the load on the IdP server, lifetime \033[33mS\033[0m seconds.\n └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next restart with \033[33m--ses-db\033[0m wiped)")
1141
1146
  ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
1142
1147
  ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
1143
1148
  ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)")
@@ -1435,6 +1440,8 @@ def add_transcoding(ap):
1435
1440
  ap2 = ap.add_argument_group('transcoding options')
1436
1441
  ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable")
1437
1442
  ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable")
1443
+ ap2.add_argument("--allow-wav", action="store_true", help="allow transcoding to wav (lossless, uncompressed)")
1444
+ ap2.add_argument("--allow-flac", action="store_true", help="allow transcoding to flac (lossless, compressed)")
1438
1445
  ap2.add_argument("--no-caf", action="store_true", help="disable transcoding to caf-opus (affects iOS v12~v17), will use mp3 instead")
1439
1446
  ap2.add_argument("--no-owa", action="store_true", help="disable transcoding to webm-opus (iOS v18 and later), will use mp3 instead")
1440
1447
  ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
@@ -1540,6 +1547,7 @@ def add_ui(ap, retry):
1540
1547
  ap2 = ap.add_argument_group('ui options')
1541
1548
  ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
1542
1549
  ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
1550
+ ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC")
1543
1551
  ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor chi\033[0m")
1544
1552
  ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..7)")
1545
1553
  ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
@@ -1552,7 +1560,7 @@ def add_ui(ap, retry):
1552
1560
  ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
1553
1561
  ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
1554
1562
  ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
1555
- ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
1563
+ ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS)
1556
1564
  ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]")
1557
1565
  ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
1558
1566
  ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
@@ -1777,7 +1785,7 @@ def main(argv = None) :
1777
1785
  argv[idx] = nk + ov
1778
1786
  time.sleep(2)
1779
1787
 
1780
- da = len(argv) == 1
1788
+ da = len(argv) == 1 and not CFG_DEF
1781
1789
  try:
1782
1790
  if da:
1783
1791
  argv.extend(["--qr"])
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 18, 8)
3
+ VERSION = (1, 18, 10)
4
4
  CODENAME = "logtail"
5
- BUILD_DT = (2025, 7, 31)
5
+ BUILD_DT = (2025, 8, 4)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -1705,7 +1705,8 @@ class AuthSrv(object):
1705
1705
  t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
1706
1706
  self.log(t, 1)
1707
1707
  axs = AXS()
1708
- vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0())
1708
+ zvf = {"tcolor": self.args.tcolor}
1709
+ vfs = VFS(self.log_func, absreal("."), "", "", axs, zvf)
1709
1710
  if not axs.uread:
1710
1711
  self.badcfg1 = True
1711
1712
  elif "" not in mount:
@@ -2742,6 +2743,8 @@ class AuthSrv(object):
2742
2743
  "s_name": self.args.bname,
2743
2744
  "have_up2k_idx": "e2d" in vf,
2744
2745
  "have_acode": not self.args.no_acode,
2746
+ "have_c2flac": self.args.allow_flac,
2747
+ "have_c2wav": self.args.allow_wav,
2745
2748
  "have_shr": self.args.shr,
2746
2749
  "have_zip": not self.args.no_zip,
2747
2750
  "have_mv": not self.args.no_mv,
@@ -2766,6 +2769,7 @@ class AuthSrv(object):
2766
2769
  "dth3x": vf["th3x"],
2767
2770
  "dvol": self.args.au_vol,
2768
2771
  "idxh": int(self.args.ih),
2772
+ "dutc": not self.args.localtime,
2769
2773
  "themes": self.args.themes,
2770
2774
  "turbolvl": self.args.turbo,
2771
2775
  "nosubtle": self.args.nosubtle,
@@ -2853,7 +2857,10 @@ class AuthSrv(object):
2853
2857
 
2854
2858
  n = []
2855
2859
  q = "insert into us values (?,?,?)"
2856
- for uname in self.acct:
2860
+ accs = list(self.acct)
2861
+ if self.args.idp_h_usr and self.args.idp_cookie:
2862
+ accs.extend(self.idp_accs.keys())
2863
+ for uname in accs:
2857
2864
  if uname not in ases:
2858
2865
  sid = ub64enc(os.urandom(blen)).decode("ascii")
2859
2866
  cur.execute(q, (uname, sid, int(time.time())))
@@ -3444,7 +3451,7 @@ def expand_config_file(
3444
3451
  ipath += " -> " + fp
3445
3452
  ret.append("#\033[36m opening cfg file{}\033[0m".format(ipath))
3446
3453
 
3447
- cfg_lines = read_utf8(log, fp, True).split("\n")
3454
+ cfg_lines = read_utf8(log, fp, True).replace("\t", " ").split("\n")
3448
3455
  if True: # diff-golf
3449
3456
  for oln in [x.rstrip() for x in cfg_lines]:
3450
3457
  ln = oln.split(" #")[0].strip()
@@ -107,6 +107,7 @@ from .util import (
107
107
  sendfile_py,
108
108
  set_fperms,
109
109
  stat_resource,
110
+ str_anchor,
110
111
  ub64dec,
111
112
  ub64enc,
112
113
  ujoin,
@@ -652,6 +653,9 @@ class HttpCli(object):
652
653
  self.pw = ""
653
654
  self.uname = idp_usr
654
655
  self.html_head += "<script>var is_idp=1</script>\n"
656
+ zs = self.asrv.ases.get(idp_usr)
657
+ if zs:
658
+ self.set_idp_cookie(zs)
655
659
  else:
656
660
  self.log("unknown username: %r" % (idp_usr,), 1)
657
661
 
@@ -906,7 +910,7 @@ class HttpCli(object):
906
910
  if status == 304:
907
911
  self.out_headers.pop("Content-Length", None)
908
912
  self.out_headers.pop("Content-Type", None)
909
- self.out_headerlist.clear()
913
+ self.out_headerlist[:] = []
910
914
  if self.k304():
911
915
  self.keepalive = False
912
916
  else:
@@ -1190,15 +1194,6 @@ class HttpCli(object):
1190
1194
  self.reply(b"ssdp is disabled in server config", 404)
1191
1195
  return False
1192
1196
 
1193
- if self.vpath.startswith(".cpr/dd/") and self.args.mpmc:
1194
- if self.args.mpmc == ".":
1195
- raise Pebkac(404)
1196
-
1197
- loc = self.args.mpmc.rstrip("/") + self.vpath[self.vpath.rfind("/") :]
1198
- h = {"Location": loc, "Cache-Control": "max-age=39"}
1199
- self.reply(b"", 301, headers=h)
1200
- return True
1201
-
1202
1197
  if self.vpath == ".cpr/metrics":
1203
1198
  return self.conn.hsrv.metrics.tx(self)
1204
1199
 
@@ -2074,16 +2069,16 @@ class HttpCli(object):
2074
2069
  rnd, lifetime, xbu, xau = self.upload_flags(vfs)
2075
2070
  lim = vfs.get_dbv(rem)[0].lim
2076
2071
  fdir = vfs.canonical(rem)
2077
- if lim:
2078
- fdir, rem = lim.all(
2079
- self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
2080
- )
2081
-
2082
2072
  fn = None
2083
2073
  if rem and not self.trailing_slash and not bos.path.isdir(fdir):
2084
2074
  fdir, fn = os.path.split(fdir)
2085
2075
  rem, _ = vsplit(rem)
2086
2076
 
2077
+ if lim:
2078
+ fdir, rem = lim.all(
2079
+ self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
2080
+ )
2081
+
2087
2082
  bos.makedirs(fdir, vf=vfs.flags)
2088
2083
 
2089
2084
  open_ka = {"fun": open}
@@ -2928,7 +2923,7 @@ class HttpCli(object):
2928
2923
  msg = "new password OK"
2929
2924
 
2930
2925
  redir = (self.args.SRS + "?h") if ok else ""
2931
- h2 = '<a href="' + self.args.SRS + '?h">ack</a>'
2926
+ h2 = '<a href="' + self.args.SRS + '?h">continue</a>'
2932
2927
  html = self.j2s("msg", h1=msg, h2=h2, redir=redir)
2933
2928
  self.reply(html.encode("utf-8"))
2934
2929
  return True
@@ -2956,7 +2951,8 @@ class HttpCli(object):
2956
2951
  dst += "_=1#" + html_escape(uhash, True, True)
2957
2952
 
2958
2953
  _, msg = self.get_pwd_cookie(pwd)
2959
- html = self.j2s("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
2954
+ h2 = '<a href="' + dst + '">continue</a>'
2955
+ html = self.j2s("msg", h1=msg, h2=h2, redir=dst)
2960
2956
  self.reply(html.encode("utf-8"))
2961
2957
  return True
2962
2958
 
@@ -2969,7 +2965,7 @@ class HttpCli(object):
2969
2965
  self.get_pwd_cookie("x")
2970
2966
 
2971
2967
  dst = self.args.SRS + "?h"
2972
- h2 = '<a href="' + dst + '">ack</a>'
2968
+ h2 = '<a href="' + dst + '">continue</a>'
2973
2969
  html = self.j2s("msg", h1="ok bye", h2=h2, redir=dst)
2974
2970
  self.reply(html.encode("utf-8"))
2975
2971
  return True
@@ -3022,6 +3018,19 @@ class HttpCli(object):
3022
3018
 
3023
3019
  return dur > 0, msg
3024
3020
 
3021
+ def set_idp_cookie(self, ases) :
3022
+ k = "cppws" if self.is_https else "cppwd"
3023
+ ck = gencookie(
3024
+ k,
3025
+ ases,
3026
+ self.args.R,
3027
+ self.args.cookie_lax,
3028
+ self.is_https,
3029
+ self.args.idp_cookie,
3030
+ "; HttpOnly",
3031
+ )
3032
+ self.out_headers["Set-Cookie"] = ck
3033
+
3025
3034
  def handle_mkdir(self) :
3026
3035
  new_dir = self.parser.require("name", 512)
3027
3036
  self.parser.drop()
@@ -3093,6 +3102,20 @@ class HttpCli(object):
3093
3102
  if "fperms" in vfs.flags:
3094
3103
  set_fperms(f, vfs.flags)
3095
3104
 
3105
+ dbv, vrem = vfs.get_dbv(rem)
3106
+ self.conn.hsrv.broker.say(
3107
+ "up2k.hash_file",
3108
+ dbv.realpath,
3109
+ dbv.vpath,
3110
+ dbv.flags,
3111
+ vrem,
3112
+ sanitized,
3113
+ self.ip,
3114
+ bos.stat(fn).st_mtime,
3115
+ self.uname,
3116
+ True,
3117
+ )
3118
+
3096
3119
  vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
3097
3120
  self.redirect(vpath, "?edit")
3098
3121
  return True
@@ -4646,7 +4669,7 @@ class HttpCli(object):
4646
4669
  # for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
4647
4670
  cfmt = ""
4648
4671
  if self.thumbcli and not self.args.no_bacode:
4649
- for zs in ("opus", "mp3", "w", "j", "p"):
4672
+ for zs in ("opus", "mp3", "flac", "wav", "w", "j", "p"):
4650
4673
  if zs in self.ouparam or uarg == zs:
4651
4674
  cfmt = zs
4652
4675
 
@@ -5331,15 +5354,16 @@ class HttpCli(object):
5331
5354
  raise Pebkac(500, "sqlite3 not found on server; unpost is disabled")
5332
5355
  raise Pebkac(500, "server busy, cannot unpost; please retry in a bit")
5333
5356
 
5334
- zs = self.uparam.get("filter") or ""
5335
- filt = re.compile(zs, re.I) if zs else None
5336
- lm = "ups %r" % (zs,)
5357
+ sfilt = self.uparam.get("filter") or ""
5358
+ nfi, vfi = str_anchor(sfilt)
5359
+ lm = "ups %d%r" % (nfi, sfilt)
5337
5360
 
5338
5361
  if self.args.shr and self.vpath.startswith(self.args.shr1):
5339
5362
  shr_dbv, shr_vrem = self.vn.get_dbv(self.rem)
5340
5363
  else:
5341
5364
  shr_dbv = None
5342
5365
 
5366
+ wret = {}
5343
5367
  ret = []
5344
5368
  t0 = time.time()
5345
5369
  lim = time.time() - self.args.unpost
@@ -5361,7 +5385,13 @@ class HttpCli(object):
5361
5385
  x = self.conn.hsrv.broker.ask(
5362
5386
  "up2k.get_unfinished_by_user", self.uname, "" if bad_xff else self.ip
5363
5387
  )
5364
- uret = x.get()
5388
+ zdsa = x.get()
5389
+ uret = []
5390
+ if "timeout" in zdsa:
5391
+ wret["nou"] = 1
5392
+ else:
5393
+ uret = zdsa["f"]
5394
+ nu = len(uret)
5365
5395
 
5366
5396
  if not self.args.unpost:
5367
5397
  allvols = []
@@ -5386,8 +5416,14 @@ class HttpCli(object):
5386
5416
  q = "select sz, rd, fn, at from up where ip=? and at>? order by at desc"
5387
5417
  for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
5388
5418
  vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
5389
- if filt and not filt.search(vp):
5390
- continue
5419
+ if nfi == 0 or (nfi == 1 and vfi in vp):
5420
+ pass
5421
+ elif nfi == 2:
5422
+ if not vp.startswith(vfi):
5423
+ continue
5424
+ elif nfi == 3:
5425
+ if not vp.endswith(vfi):
5426
+ continue
5391
5427
 
5392
5428
  n -= 1
5393
5429
  if not n:
@@ -5407,6 +5443,8 @@ class HttpCli(object):
5407
5443
 
5408
5444
  if len(ret) > 2000:
5409
5445
  ret = ret[:2000]
5446
+ if len(ret) >= 2000:
5447
+ wret["oc"] = 1
5410
5448
 
5411
5449
  for rv in ret:
5412
5450
  rv["vp"] = quotep(rv["vp"])
@@ -5426,6 +5464,13 @@ class HttpCli(object):
5426
5464
  )
5427
5465
  rv["vp"] += "?k=" + fk[:nfk]
5428
5466
 
5467
+ if not allvols:
5468
+ wret["noc"] = 1
5469
+ ret = []
5470
+
5471
+ nc = len(ret)
5472
+ ret = uret + ret
5473
+
5429
5474
  if shr_dbv:
5430
5475
  # translate vpaths from share-target to share-url
5431
5476
  # to satisfy access checks
@@ -5439,12 +5484,11 @@ class HttpCli(object):
5439
5484
  for v in ret:
5440
5485
  v["vp"] = self.args.SR + v["vp"]
5441
5486
 
5442
- if not allvols:
5443
- ret = [{"kinshi": 1}]
5444
-
5445
- jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, separators=(",\n", ": ")))
5446
- zi = len(uret.split('\n"pd":')) - 1
5447
- self.log("%s #%d+%d %.2fsec" % (lm, zi, len(ret), time.time() - t0))
5487
+ wret["f"] = ret
5488
+ wret["nu"] = nu
5489
+ wret["nc"] = nc
5490
+ jtxt = json.dumps(wret, separators=(",\n", ": "))
5491
+ self.log("%s #%d+%d %.2fsec" % (lm, nu, nc, time.time() - t0))
5448
5492
  self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
5449
5493
  return True
5450
5494
 
@@ -5459,8 +5503,8 @@ class HttpCli(object):
5459
5503
  raise Pebkac(500, "server busy, cannot list recent uploads; please retry")
5460
5504
 
5461
5505
  sfilt = self.uparam.get("filter") or ""
5462
- filt = re.compile(sfilt, re.I) if sfilt else None
5463
- lm = "ru %r" % (sfilt,)
5506
+ nfi, vfi = str_anchor(sfilt)
5507
+ lm = "ru %d%r" % (nfi, sfilt)
5464
5508
  self.log(lm)
5465
5509
 
5466
5510
  ret = []
@@ -5495,8 +5539,14 @@ class HttpCli(object):
5495
5539
  q = "select sz, rd, fn, ip, at from up where at>0 order by at desc"
5496
5540
  for sz, rd, fn, ip, at in cur.execute(q):
5497
5541
  vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
5498
- if filt and not filt.search(vp):
5499
- continue
5542
+ if nfi == 0 or (nfi == 1 and vfi in vp):
5543
+ pass
5544
+ elif nfi == 2:
5545
+ if not vp.startswith(vfi):
5546
+ continue
5547
+ elif nfi == 3:
5548
+ if not vp.endswith(vfi):
5549
+ continue
5500
5550
 
5501
5551
  if not dots and "/." in vp:
5502
5552
  continue
@@ -319,7 +319,8 @@ class HttpSrv(object):
319
319
  spins = 0
320
320
  while self.ncli >= self.nclimax:
321
321
  if not spins:
322
- self.log(self.name, "at connection limit; waiting", 3)
322
+ t = "at connection limit (global-option 'nc'); waiting"
323
+ self.log(self.name, t, 3)
323
324
 
324
325
  spins += 1
325
326
  time.sleep(0.1)