copyparty 1.16.0__tar.gz → 1.16.2__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 (127) hide show
  1. {copyparty-1.16.0 → copyparty-1.16.2}/PKG-INFO +1 -1
  2. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/__main__.py +7 -5
  3. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/__version__.py +2 -2
  4. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/authsrv.py +98 -14
  5. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/cfg.py +1 -0
  6. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/httpcli.py +12 -47
  7. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/httpsrv.py +2 -2
  8. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/mtag.py +29 -0
  9. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/pwhash.py +10 -8
  10. copyparty-1.16.2/copyparty/res/COPYING.txt +95 -0
  11. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/u2idx.py +9 -0
  12. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/up2k.py +5 -0
  13. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/util.py +22 -5
  14. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/a/u2c.py +2 -0
  15. copyparty-1.16.2/copyparty/web/baguettebox.js.gz +0 -0
  16. copyparty-1.16.2/copyparty/web/browser.css.gz +0 -0
  17. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/browser.html +2 -3
  18. copyparty-1.16.2/copyparty/web/browser.js.gz +0 -0
  19. copyparty-1.16.2/copyparty/web/shares.js.gz +0 -0
  20. copyparty-1.16.2/copyparty/web/splash.css.gz +0 -0
  21. copyparty-1.16.2/copyparty/web/ui.css.gz +0 -0
  22. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/up2k.js.gz +0 -0
  23. copyparty-1.16.2/copyparty/web/util.js.gz +0 -0
  24. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty.egg-info/PKG-INFO +1 -1
  25. copyparty-1.16.0/copyparty/res/COPYING.txt +0 -159
  26. copyparty-1.16.0/copyparty/web/baguettebox.js.gz +0 -0
  27. copyparty-1.16.0/copyparty/web/browser.css.gz +0 -0
  28. copyparty-1.16.0/copyparty/web/browser.js.gz +0 -0
  29. copyparty-1.16.0/copyparty/web/shares.js.gz +0 -0
  30. copyparty-1.16.0/copyparty/web/splash.css.gz +0 -0
  31. copyparty-1.16.0/copyparty/web/ui.css.gz +0 -0
  32. copyparty-1.16.0/copyparty/web/util.js.gz +0 -0
  33. {copyparty-1.16.0 → copyparty-1.16.2}/LICENSE +0 -0
  34. {copyparty-1.16.0 → copyparty-1.16.2}/README.md +0 -0
  35. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/__init__.py +0 -0
  36. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/bos/__init__.py +0 -0
  37. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/bos/bos.py +0 -0
  38. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/bos/path.py +0 -0
  39. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/broker_mp.py +0 -0
  40. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/broker_mpw.py +0 -0
  41. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/broker_thr.py +0 -0
  42. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/broker_util.py +0 -0
  43. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/cert.py +0 -0
  44. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/dxml.py +0 -0
  45. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/fsutil.py +0 -0
  46. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/ftpd.py +0 -0
  47. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/httpconn.py +0 -0
  48. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/ico.py +0 -0
  49. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/mdns.py +0 -0
  50. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/metrics.py +0 -0
  51. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/multicast.py +0 -0
  52. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/res/__init__.py +0 -0
  53. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/res/insecure.pem +0 -0
  54. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/smbd.py +0 -0
  55. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/ssdp.py +0 -0
  56. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/star.py +0 -0
  57. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/__init__.py +0 -0
  58. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/dnslib/__init__.py +0 -0
  59. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/dnslib/bimap.py +0 -0
  60. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/dnslib/bit.py +0 -0
  61. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/dnslib/buffer.py +0 -0
  62. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/dnslib/dns.py +0 -0
  63. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/dnslib/label.py +0 -0
  64. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/dnslib/lex.py +0 -0
  65. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/dnslib/ranges.py +0 -0
  66. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/ifaddr/__init__.py +0 -0
  67. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/ifaddr/_posix.py +0 -0
  68. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/ifaddr/_shared.py +0 -0
  69. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/ifaddr/_win32.py +0 -0
  70. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/qrcodegen.py +0 -0
  71. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/stolen/surrogateescape.py +0 -0
  72. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/sutil.py +0 -0
  73. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/svchub.py +0 -0
  74. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/szip.py +0 -0
  75. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/tcpsrv.py +0 -0
  76. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/tftpd.py +0 -0
  77. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/th_cli.py +0 -0
  78. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/th_srv.py +0 -0
  79. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/a/__init__.py +0 -0
  80. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/a/partyfuse.py +0 -0
  81. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/a/webdav-cfg.bat +0 -0
  82. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/browser2.html +0 -0
  83. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/cf.html +0 -0
  84. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/dbg-audio.js.gz +0 -0
  85. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/dd/2.png +0 -0
  86. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/dd/3.png +0 -0
  87. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/dd/4.png +0 -0
  88. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/dd/5.png +0 -0
  89. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/dd/__init__.py +0 -0
  90. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/__init__.py +0 -0
  91. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/busy.mp3.gz +0 -0
  92. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/easymde.css.gz +0 -0
  93. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/easymde.js.gz +0 -0
  94. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/fuse.py +0 -0
  95. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/marked.js.gz +0 -0
  96. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/mini-fa.css.gz +0 -0
  97. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/mini-fa.woff +0 -0
  98. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/prism.css.gz +0 -0
  99. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/prism.js.gz +0 -0
  100. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/prismd.css.gz +0 -0
  101. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/scp.woff2 +0 -0
  102. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  103. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  104. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/md.css.gz +0 -0
  105. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/md.html +0 -0
  106. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/md.js.gz +0 -0
  107. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/md2.css.gz +0 -0
  108. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/md2.js.gz +0 -0
  109. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/mde.css.gz +0 -0
  110. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/mde.html +0 -0
  111. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/mde.js.gz +0 -0
  112. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/msg.css.gz +0 -0
  113. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/msg.html +0 -0
  114. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/shares.css.gz +0 -0
  115. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/shares.html +0 -0
  116. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/splash.html +0 -0
  117. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/splash.js.gz +0 -0
  118. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/svcs.html +0 -0
  119. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/svcs.js.gz +0 -0
  120. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty/web/w.hash.js.gz +0 -0
  121. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty.egg-info/SOURCES.txt +0 -0
  122. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty.egg-info/dependency_links.txt +0 -0
  123. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty.egg-info/entry_points.txt +0 -0
  124. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty.egg-info/requires.txt +0 -0
  125. {copyparty-1.16.0 → copyparty-1.16.2}/copyparty.egg-info/top_level.txt +0 -0
  126. {copyparty-1.16.0 → copyparty-1.16.2}/pyproject.toml +0 -0
  127. {copyparty-1.16.0 → copyparty-1.16.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.16.0
3
+ Version: 1.16.2
4
4
  Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
5
5
  Author-email: ed <copyparty@ocv.me>
6
6
  License: MIT
@@ -870,8 +870,9 @@ def get_sects():
870
870
  use argon2id with timecost 3, 256 MiB, 4 threads, version 19 (0x13/v1.3)
871
871
 
872
872
  \033[36m--ah-alg scrypt\033[0m # which is the same as:
873
- \033[36m--ah-alg scrypt,13,2,8,4\033[0m
874
- use scrypt with cost 2**13, 2 iterations, blocksize 8, 4 threads
873
+ \033[36m--ah-alg scrypt,13,2,8,4,32\033[0m
874
+ use scrypt with cost 2**13, 2 iterations, blocksize 8, 4 threads,
875
+ and allow using up to 32 MiB RAM (ram=cost*blksz roughly)
875
876
 
876
877
  \033[36m--ah-alg sha2\033[0m # which is the same as:
877
878
  \033[36m--ah-alg sha2,424242\033[0m
@@ -1343,12 +1344,12 @@ def add_thumbnail(ap):
1343
1344
  # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
1344
1345
  # https://github.com/libvips/libvips
1345
1346
  # ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
1346
- ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,dcx,dds,dib,emf,eps,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
1347
+ ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
1347
1348
  ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
1348
- ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
1349
+ ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
1349
1350
  ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
1350
1351
  ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
1351
- ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz", help="audio formats to decompress before passing to ffmpeg")
1352
+ ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz", help="audio/image formats to decompress before passing to ffmpeg")
1352
1353
 
1353
1354
 
1354
1355
  def add_transcoding(ap):
@@ -1448,6 +1449,7 @@ def add_ui(ap, retry):
1448
1449
  ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
1449
1450
  ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
1450
1451
  ap2.add_argument("--sort", metavar="C,C,C", type=u, default="href", help="default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: \033[32mhref -href ext sz ts tags/Album tags/.tn\033[0m (volflag=sort)")
1452
+ ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)")
1451
1453
  ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
1452
1454
  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")
1453
1455
  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])")
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 16, 0)
3
+ VERSION = (1, 16, 2)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2024, 11, 10)
5
+ BUILD_DT = (2024, 11, 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)
@@ -360,18 +360,21 @@ class VFS(object):
360
360
  self.ahtml = {}
361
361
  self.aadmin = {}
362
362
  self.adot = {}
363
- self.all_vols = {}
363
+ self.js_ls = {}
364
+ self.js_htm = ""
364
365
 
365
366
  if realpath:
366
367
  rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
367
368
  vp = vpath + ("/" if vpath else "")
368
369
  self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
369
370
  self.all_vols = {vpath: self} # flattened recursive
371
+ self.all_nodes = {vpath: self} # also jumpvols
370
372
  self.all_aps = [(rp, self)]
371
373
  self.all_vps = [(vp, self)]
372
374
  else:
373
375
  self.histpath = ""
374
376
  self.all_vols = {}
377
+ self.all_nodes = {}
375
378
  self.all_aps = []
376
379
  self.all_vps = []
377
380
 
@@ -389,9 +392,11 @@ class VFS(object):
389
392
  def get_all_vols(
390
393
  self,
391
394
  vols ,
395
+ nodes ,
392
396
  aps ,
393
397
  vps ,
394
398
  ) :
399
+ nodes[self.vpath] = self
395
400
  if self.realpath:
396
401
  vols[self.vpath] = self
397
402
  rp = self.realpath
@@ -401,7 +406,7 @@ class VFS(object):
401
406
  vps.append((vp, self))
402
407
 
403
408
  for v in self.nodes.values():
404
- v.get_all_vols(vols, aps, vps)
409
+ v.get_all_vols(vols, nodes, aps, vps)
405
410
 
406
411
  def add(self, src , dst ) :
407
412
  """get existing, or add new path to the vfs"""
@@ -1528,10 +1533,11 @@ class AuthSrv(object):
1528
1533
 
1529
1534
  assert vfs # type: ignore
1530
1535
  vfs.all_vols = {}
1536
+ vfs.all_nodes = {}
1531
1537
  vfs.all_aps = []
1532
1538
  vfs.all_vps = []
1533
- vfs.get_all_vols(vfs.all_vols, vfs.all_aps, vfs.all_vps)
1534
- for vol in vfs.all_vols.values():
1539
+ vfs.get_all_vols(vfs.all_vols, vfs.all_nodes, vfs.all_aps, vfs.all_vps)
1540
+ for vol in vfs.all_nodes.values():
1535
1541
  vol.all_aps.sort(key=lambda x: len(x[0]), reverse=True)
1536
1542
  vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
1537
1543
  vol.root = vfs
@@ -1582,7 +1588,7 @@ class AuthSrv(object):
1582
1588
 
1583
1589
  vfs.nodes[shr] = vfs.all_vols[shr] = shv
1584
1590
  for vol in shv.nodes.values():
1585
- vfs.all_vols[vol.vpath] = vol
1591
+ vfs.all_vols[vol.vpath] = vfs.all_nodes[vol.vpath] = vol
1586
1592
  vol.get_dbv = vol._get_share_src
1587
1593
  vol.ls = vol._ls_nope
1588
1594
 
@@ -1725,7 +1731,19 @@ class AuthSrv(object):
1725
1731
 
1726
1732
  self.log("\n\n".join(ta) + "\n", c=3)
1727
1733
 
1728
- vfs.histtab = {zv.realpath: zv.histpath for zv in vfs.all_vols.values()}
1734
+ rhisttab = {}
1735
+ vfs.histtab = {}
1736
+ for zv in vfs.all_vols.values():
1737
+ histp = zv.histpath
1738
+ is_shr = shr and zv.vpath.split("/")[0] == shr
1739
+ if histp and not is_shr and histp in rhisttab:
1740
+ zv2 = rhisttab[histp]
1741
+ t = "invalid config; multiple volumes share the same histpath (database location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
1742
+ t = t % (histp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
1743
+ self.log(t, 1)
1744
+ raise Exception(t)
1745
+ rhisttab[histp] = zv
1746
+ vfs.histtab[zv.realpath] = histp
1729
1747
 
1730
1748
  for vol in vfs.all_vols.values():
1731
1749
  lim = Lim(self.log_func)
@@ -1784,12 +1802,12 @@ class AuthSrv(object):
1784
1802
  vol.lim = lim
1785
1803
 
1786
1804
  if self.args.no_robots:
1787
- for vol in vfs.all_vols.values():
1805
+ for vol in vfs.all_nodes.values():
1788
1806
  # volflag "robots" overrides global "norobots", allowing indexing by search engines for this vol
1789
1807
  if not vol.flags.get("robots"):
1790
1808
  vol.flags["norobots"] = True
1791
1809
 
1792
- for vol in vfs.all_vols.values():
1810
+ for vol in vfs.all_nodes.values():
1793
1811
  if self.args.no_vthumb:
1794
1812
  vol.flags["dvthumb"] = True
1795
1813
  if self.args.no_athumb:
@@ -1801,7 +1819,7 @@ class AuthSrv(object):
1801
1819
  vol.flags["dithumb"] = True
1802
1820
 
1803
1821
  have_fk = False
1804
- for vol in vfs.all_vols.values():
1822
+ for vol in vfs.all_nodes.values():
1805
1823
  fk = vol.flags.get("fk")
1806
1824
  fka = vol.flags.get("fka")
1807
1825
  if fka and not fk:
@@ -1833,7 +1851,7 @@ class AuthSrv(object):
1833
1851
  zs = os.path.join(E.cfg, "fk-salt.txt")
1834
1852
  self.log(t % (fk_len, 16, zs), 3)
1835
1853
 
1836
- for vol in vfs.all_vols.values():
1854
+ for vol in vfs.all_nodes.values():
1837
1855
  if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags:
1838
1856
  vol.flags["gz"] = False # def.pk
1839
1857
 
@@ -1844,7 +1862,7 @@ class AuthSrv(object):
1844
1862
 
1845
1863
  all_mte = {}
1846
1864
  errors = False
1847
- for vol in vfs.all_vols.values():
1865
+ for vol in vfs.all_nodes.values():
1848
1866
  if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
1849
1867
  vol.flags["e2ds"] = True
1850
1868
 
@@ -2062,7 +2080,7 @@ class AuthSrv(object):
2062
2080
  errors = True
2063
2081
 
2064
2082
  have_daw = False
2065
- for vol in vfs.all_vols.values():
2083
+ for vol in vfs.all_nodes.values():
2066
2084
  daw = vol.flags.get("daw") or self.args.daw
2067
2085
  if daw:
2068
2086
  vol.flags["daw"] = True
@@ -2077,13 +2095,12 @@ class AuthSrv(object):
2077
2095
  self.log("--smb can only be used when --ah-alg is none", 1)
2078
2096
  errors = True
2079
2097
 
2080
- for vol in vfs.all_vols.values():
2098
+ for vol in vfs.all_nodes.values():
2081
2099
  for k in list(vol.flags.keys()):
2082
2100
  if re.match("^-[^-]+$", k):
2083
2101
  vol.flags.pop(k[1:], None)
2084
2102
  vol.flags.pop(k)
2085
2103
 
2086
- for vol in vfs.all_vols.values():
2087
2104
  if vol.flags.get("dots"):
2088
2105
  for name in vol.axs.uread:
2089
2106
  vol.axs.udot.add(name)
@@ -2225,6 +2242,11 @@ class AuthSrv(object):
2225
2242
  for x, y in vfs.all_vols.items()
2226
2243
  if x != shr and not x.startswith(shrs)
2227
2244
  }
2245
+ vfs.all_nodes = {
2246
+ x: y
2247
+ for x, y in vfs.all_nodes.items()
2248
+ if x != shr and not x.startswith(shrs)
2249
+ }
2228
2250
 
2229
2251
  assert db and cur and cur2 and shv # type: ignore
2230
2252
  for row in cur.execute("select * from sh"):
@@ -2277,6 +2299,68 @@ class AuthSrv(object):
2277
2299
  cur.close()
2278
2300
  db.close()
2279
2301
 
2302
+ self.js_ls = {}
2303
+ self.js_htm = {}
2304
+ for vn in self.vfs.all_nodes.values():
2305
+ vf = vn.flags
2306
+ vn.js_ls = {
2307
+ "idx": "e2d" in vf,
2308
+ "itag": "e2t" in vf,
2309
+ "dnsort": "nsort" in vf,
2310
+ "dsort": vf["sort"],
2311
+ "dcrop": vf["crop"],
2312
+ "dth3x": vf["th3x"],
2313
+ "u2ts": vf["u2ts"],
2314
+ "frand": bool(vf.get("rand")),
2315
+ "lifetime": vf.get("lifetime") or 0,
2316
+ "unlist": vf.get("unlist") or "",
2317
+ }
2318
+ js_htm = {
2319
+ "s_name": self.args.bname,
2320
+ "have_up2k_idx": "e2d" in vf,
2321
+ "have_acode": not self.args.no_acode,
2322
+ "have_shr": self.args.shr,
2323
+ "have_zip": not self.args.no_zip,
2324
+ "have_mv": not self.args.no_mv,
2325
+ "have_del": not self.args.no_del,
2326
+ "have_unpost": int(self.args.unpost),
2327
+ "have_emp": self.args.emp,
2328
+ "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
2329
+ "txt_ext": self.args.textfiles.replace(",", " "),
2330
+ "def_hcols": list(vf.get("mth") or []),
2331
+ "unlist0": vf.get("unlist") or "",
2332
+ "dgrid": "grid" in vf,
2333
+ "dgsel": "gsel" in vf,
2334
+ "dnsort": "nsort" in vf,
2335
+ "dsort": vf["sort"],
2336
+ "dcrop": vf["crop"],
2337
+ "dth3x": vf["th3x"],
2338
+ "dvol": self.args.au_vol,
2339
+ "idxh": int(self.args.ih),
2340
+ "themes": self.args.themes,
2341
+ "turbolvl": self.args.turbo,
2342
+ "u2j": self.args.u2j,
2343
+ "u2sz": self.args.u2sz,
2344
+ "u2ts": vf["u2ts"],
2345
+ "frand": bool(vf.get("rand")),
2346
+ "lifetime": vn.js_ls["lifetime"],
2347
+ "u2sort": self.args.u2sort,
2348
+ }
2349
+ vn.js_htm = json.dumps(js_htm)
2350
+
2351
+ vols = list(vfs.all_nodes.values())
2352
+ if enshare:
2353
+ vols.append(shv)
2354
+ vols.extend(list(shv.nodes.values()))
2355
+
2356
+ for vol in vols:
2357
+ dbv = vol.get_dbv("")[0]
2358
+ vol.js_ls = vol.js_ls or dbv.js_ls or {}
2359
+ vol.js_htm = vol.js_htm or dbv.js_htm or "{}"
2360
+
2361
+ zs = str(vol.flags.get("tcolor") or self.args.tcolor)
2362
+ vol.flags["tcolor"] = zs.lstrip("#")
2363
+
2280
2364
  def load_sessions(self, quiet=False) :
2281
2365
  # mutex me
2282
2366
  if self.args.no_ses:
@@ -42,6 +42,7 @@ def vf_bmap() :
42
42
  "magic",
43
43
  "no_sb_md",
44
44
  "no_sb_lg",
45
+ "nsort",
45
46
  "og",
46
47
  "og_no_head",
47
48
  "og_s_title",
@@ -243,7 +243,6 @@ class HttpCli(object):
243
243
  ka["ts"] = self.conn.hsrv.cachebuster()
244
244
  ka["lang"] = self.args.lang
245
245
  ka["favico"] = self.args.favico
246
- ka["s_name"] = self.args.bname
247
246
  ka["s_doctitle"] = self.args.doctitle
248
247
  ka["tcolor"] = self.vn.flags["tcolor"]
249
248
 
@@ -700,7 +699,7 @@ class HttpCli(object):
700
699
 
701
700
  if pex.code != 404 or self.do_log:
702
701
  self.log(
703
- "%s\033[0m, %s" % (msg, self.vpath),
702
+ "http%d: %s\033[0m, %s" % (pex.code, msg, self.vpath),
704
703
  6 if em.startswith("client d/c ") else 3,
705
704
  )
706
705
 
@@ -1409,12 +1408,13 @@ class HttpCli(object):
1409
1408
  vst = os.stat_result((16877, -1, -1, 1, 1000, 1000, 8, zi, zi, zi))
1410
1409
 
1411
1410
  try:
1412
- topdir = {"vp": "", "st": bos.stat(tap)}
1411
+ st = bos.stat(tap)
1413
1412
  except OSError as ex:
1414
1413
  if ex.errno not in (errno.ENOENT, errno.ENOTDIR):
1415
1414
  raise
1416
1415
  raise Pebkac(404)
1417
1416
 
1417
+ topdir = {"vp": "", "st": st}
1418
1418
  fgen = []
1419
1419
 
1420
1420
  depth = self.headers.get("depth", "infinity").lower()
@@ -1450,6 +1450,12 @@ class HttpCli(object):
1450
1450
  wrap=False,
1451
1451
  )
1452
1452
 
1453
+ elif depth == "0" or not stat.S_ISDIR(st.st_mode):
1454
+ # propfind on a file; return as topdir
1455
+ if not self.can_read and not self.can_get:
1456
+ self.log("inaccessible: [%s]" % (self.vpath,))
1457
+ raise Pebkac(401, "authenticate")
1458
+
1453
1459
  elif depth == "1":
1454
1460
  _, vfs_ls, vfs_virt = vn.ls(
1455
1461
  rem,
@@ -1468,9 +1474,6 @@ class HttpCli(object):
1468
1474
  fgen = [{"vp": vp, "st": st} for vp, st in vfs_ls]
1469
1475
  fgen += [{"vp": v, "st": vst} for v in vfs_virt]
1470
1476
 
1471
- elif depth == "0":
1472
- pass
1473
-
1474
1477
  else:
1475
1478
  t = "invalid depth value '{}' (must be either '0' or '1'{})"
1476
1479
  t2 = " or 'infinity'" if self.args.dav_inf else ""
@@ -5474,61 +5477,28 @@ class HttpCli(object):
5474
5477
  is_js = False
5475
5478
 
5476
5479
  vf = vn.flags
5477
- unlist = vf.get("unlist", "")
5478
5480
  ls_ret = {
5479
5481
  "dirs": [],
5480
5482
  "files": [],
5481
5483
  "taglist": [],
5482
5484
  "srvinf": srv_infot,
5483
5485
  "acct": self.uname,
5484
- "idx": e2d,
5485
- "itag": e2t,
5486
- "dsort": vf["sort"],
5487
- "dcrop": vf["crop"],
5488
- "dth3x": vf["th3x"],
5489
- "u2ts": vf["u2ts"],
5490
- "lifetime": vn.flags.get("lifetime") or 0,
5491
- "frand": bool(vn.flags.get("rand")),
5492
- "unlist": unlist,
5493
5486
  "perms": perms,
5487
+ "cfg": vn.js_ls,
5494
5488
  }
5495
5489
  cgv = {
5496
5490
  "ls0": None,
5497
5491
  "acct": self.uname,
5498
5492
  "perms": perms,
5499
- "u2ts": vf["u2ts"],
5500
- "lifetime": ls_ret["lifetime"],
5501
- "frand": bool(vn.flags.get("rand")),
5502
- "def_hcols": [],
5503
- "have_emp": self.args.emp,
5504
- "have_up2k_idx": e2d,
5505
- "have_acode": (not self.args.no_acode),
5506
- "have_mv": (not self.args.no_mv),
5507
- "have_del": (not self.args.no_del),
5508
- "have_zip": (not self.args.no_zip),
5509
- "have_shr": self.args.shr,
5510
- "have_unpost": int(self.args.unpost),
5511
- "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
5512
- "dgrid": "grid" in vf,
5513
- "dgsel": "gsel" in vf,
5514
- "dsort": vf["sort"],
5515
- "dcrop": vf["crop"],
5516
- "dth3x": vf["th3x"],
5517
- "dvol": self.args.au_vol,
5518
- "themes": self.args.themes,
5519
- "turbolvl": self.args.turbo,
5520
- "u2j": self.args.u2j,
5521
- "u2sz": self.args.u2sz,
5522
- "idxh": int(self.args.ih),
5523
- "u2sort": self.args.u2sort,
5524
5493
  }
5525
5494
  j2a = {
5495
+ "cgv1": vn.js_htm,
5526
5496
  "cgv": cgv,
5527
5497
  "vpnodes": vpnodes,
5528
5498
  "files": [],
5529
5499
  "ls0": None,
5530
5500
  "taglist": [],
5531
- "have_tags_idx": e2t,
5501
+ "have_tags_idx": int(e2t),
5532
5502
  "have_b_u": (self.can_write and self.uparam.get("b") == "u"),
5533
5503
  "sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
5534
5504
  "url_suf": url_suf,
@@ -5899,17 +5869,12 @@ class HttpCli(object):
5899
5869
  "dirs": dirs,
5900
5870
  "files": files,
5901
5871
  "taglist": taglist,
5902
- "unlist": unlist,
5903
5872
  }
5904
5873
  j2a["files"] = []
5905
5874
  else:
5906
5875
  j2a["files"] = dirs + files
5907
5876
 
5908
5877
  j2a["taglist"] = taglist
5909
- j2a["txt_ext"] = self.args.textfiles.replace(",", " ")
5910
-
5911
- if "mth" in vn.flags:
5912
- j2a["def_hcols"] = list(vn.flags["mth"])
5913
5878
 
5914
5879
  if add_og and "raw" not in self.uparam:
5915
5880
  j2a["this"] = self
@@ -132,7 +132,7 @@ class HttpSrv(object):
132
132
  dls = {} # state
133
133
  self.dli = self.tdli = dli
134
134
  self.dls = self.tdls = dls
135
- self.iiam = '<img src="%s.cpr/iiam.gif" />' % (self.args.SRS,)
135
+ self.iiam = '<img src="%s.cpr/iiam.gif?cache=i" />' % (self.args.SRS,)
136
136
 
137
137
  self.bound = set()
138
138
  self.name = "hsrv" + nsuf
@@ -607,7 +607,7 @@ class HttpSrv(object):
607
607
  """
608
608
  dli = {}
609
609
  for k, (a, b, c, d, e) in sdli.items():
610
- vn = self.asrv.vfs.all_vols[c]
610
+ vn = self.asrv.vfs.all_nodes[c]
611
611
  dli[k] = (a, b, vn, d, e)
612
612
 
613
613
  self.tdli = dli
@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
4
4
  import argparse
5
5
  import json
6
6
  import os
7
+ import re
7
8
  import shutil
8
9
  import subprocess as sp
9
10
  import sys
@@ -56,6 +57,9 @@ def have_ff(scmd ) :
56
57
  HAVE_FFMPEG = not os.environ.get("PRTY_NO_FFMPEG") and have_ff("ffmpeg")
57
58
  HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe")
58
59
 
60
+ CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif".split())
61
+ CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b")
62
+
59
63
 
60
64
  class MParser(object):
61
65
  def __init__(self, cmdline ) :
@@ -120,6 +124,7 @@ def au_unpk(
120
124
  log , fmt_map , abspath , vn = None
121
125
  ) :
122
126
  ret = ""
127
+ maxsz = 1024 * 1024 * 64
123
128
  try:
124
129
  ext = abspath.split(".")[-1].lower()
125
130
  au, pk = fmt_map[ext].split(".")
@@ -142,17 +147,41 @@ def au_unpk(
142
147
  zf = zipfile.ZipFile(abspath, "r")
143
148
  zil = zf.infolist()
144
149
  zil = [x for x in zil if x.filename.lower().split(".")[-1] == au]
150
+ if not zil:
151
+ raise Exception("no audio inside zip")
145
152
  fi = zf.open(zil[0])
146
153
 
154
+ elif pk == "cbz":
155
+ import zipfile
156
+
157
+ zf = zipfile.ZipFile(abspath, "r")
158
+ znil = [(x.filename.lower(), x) for x in zf.infolist()]
159
+ nf = len(znil)
160
+ znil = [x for x in znil if x[0].split(".")[-1] in CBZ_PICS]
161
+ znil = [x for x in znil if "cover" in x[0]] or znil
162
+ znil = [x for x in znil if CBZ_01.search(x[0])] or znil
163
+ t = "cbz: %d files, %d hits" % (nf, len(znil))
164
+ if znil:
165
+ t += ", using " + znil[0][1].filename
166
+ log(t)
167
+ if not znil:
168
+ raise Exception("no images inside cbz")
169
+ fi = zf.open(znil[0][1])
170
+
147
171
  else:
148
172
  raise Exception("unknown compression %s" % (pk,))
149
173
 
174
+ fsz = 0
150
175
  with os.fdopen(fd, "wb") as fo:
151
176
  while True:
152
177
  buf = fi.read(32768)
153
178
  if not buf:
154
179
  break
155
180
 
181
+ fsz += len(buf)
182
+ if fsz > maxsz:
183
+ raise Exception("zipbomb defused")
184
+
156
185
  fo.write(buf)
157
186
 
158
187
  return ret
@@ -24,17 +24,13 @@ class PWHash(object):
24
24
  def __init__(self, args ):
25
25
  self.args = args
26
26
 
27
- try:
28
- alg, ac = args.ah_alg.split(",")
29
- except:
30
- alg = args.ah_alg
31
- ac = {}
32
-
27
+ zsl = args.ah_alg.split(",")
28
+ alg = zsl[0]
33
29
  if alg == "none":
34
30
  alg = ""
35
31
 
36
32
  self.alg = alg
37
- self.ac = ac
33
+ self.ac = zsl[1:]
38
34
  if not alg:
39
35
  self.on = False
40
36
  self.hash = unicode
@@ -90,17 +86,23 @@ class PWHash(object):
90
86
  its = 2
91
87
  blksz = 8
92
88
  para = 4
89
+ ramcap = 0 # openssl 1.1 = 32 MiB
93
90
  try:
94
91
  cost = 2 << int(self.ac[0])
95
92
  its = int(self.ac[1])
96
93
  blksz = int(self.ac[2])
97
94
  para = int(self.ac[3])
95
+ ramcap = int(self.ac[4]) * 1024 * 1024
98
96
  except:
99
97
  pass
100
98
 
99
+ cfg = {"salt": self.salt, "n": cost, "r": blksz, "p": para, "dklen": 24}
100
+ if ramcap:
101
+ cfg["maxmem"] = ramcap
102
+
101
103
  ret = plain.encode("utf-8")
102
104
  for _ in range(its):
103
- ret = hashlib.scrypt(ret, salt=self.salt, n=cost, r=blksz, p=para, dklen=24)
105
+ ret = hashlib.scrypt(ret, **cfg)
104
106
 
105
107
  return "+" + base64.urlsafe_b64encode(ret).decode("utf-8")
106
108
 
@@ -0,0 +1,95 @@
1
+ --- server-side --- software ---
2
+
3
+ https://github.com/9001/copyparty/
4
+ Copyright (c) 2019 ed
5
+ License: MIT
6
+
7
+ https://github.com/pallets/jinja/
8
+ Copyright (c) 2007 Pallets
9
+ License: BSD 3-Clause
10
+
11
+ https://github.com/pallets/markupsafe/
12
+ Copyright (c) 2010 Pallets
13
+ License: BSD 3-Clause
14
+
15
+ https://github.com/paulc/dnslib/
16
+ Copyright (c) 2010-2017 Paul Chakravarti
17
+ License: BSD 2-Clause
18
+
19
+ https://github.com/pydron/ifaddr/
20
+ Copyright (c) 2014 Stefan C. Mueller
21
+ License: BSD-2-Clause
22
+
23
+ https://github.com/giampaolo/pyftpdlib/
24
+ Copyright (c) 2007 Giampaolo Rodola
25
+ License: MIT
26
+
27
+ https://github.com/9001/partftpy
28
+ Copyright (c) 2010-2021 Michael P. Soulier
29
+ License: MIT
30
+
31
+ https://github.com/nayuki/QR-Code-generator/
32
+ Copyright (c) Project Nayuki
33
+ License: MIT
34
+
35
+ https://github.com/ahupp/python-magic/
36
+ Copyright (c) 2001-2014 Adam Hupp
37
+ License: MIT
38
+
39
+ --- client-side --- software ---
40
+
41
+ https://github.com/Daninet/hash-wasm/
42
+ Copyright (c) 2020 Dani Biró
43
+ License: MIT
44
+
45
+ https://github.com/openpgpjs/asmcrypto.js/
46
+ Copyright (c) 2013 Artem S Vybornov
47
+ License: MIT
48
+
49
+ https://github.com/feimosi/baguetteBox.js/
50
+ Copyright (c) 2017 Marek Grzybek
51
+ License: MIT
52
+
53
+ https://github.com/markedjs/marked/
54
+ Copyright (c) 2018+, MarkedJS
55
+ Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
56
+ License: MIT
57
+
58
+ https://github.com/codemirror/codemirror5/
59
+ Copyright (c) 2017 Marijn Haverbeke <marijnh@gmail.com> and others
60
+ License: MIT
61
+
62
+ https://github.com/Ionaru/easy-markdown-editor/
63
+ Copyright (c) 2015 Sparksuite, Inc.
64
+ Copyright (c) 2017 Jeroen Akkerman.
65
+ License: MIT
66
+
67
+ --- client-side --- fonts ---
68
+
69
+ https://github.com/adobe-fonts/source-code-pro/
70
+ Copyright (c) 2010-2019 Adobe
71
+ License: SIL OFL 1.1
72
+
73
+ https://github.com/FortAwesome/Font-Awesome/
74
+ Copyright (c) 2022 Fonticons, Inc.
75
+ License: SIL OFL 1.1
76
+
77
+
78
+ --- MIT License ---
79
+
80
+
81
+
82
+ --- ISC License ---
83
+
84
+
85
+
86
+ --- BSD 2-Clause License ---
87
+
88
+
89
+
90
+ --- BSD 3-Clause License ---
91
+
92
+
93
+
94
+ --- SIL Open Font License v1.1 ---
95
+