copyparty 1.18.1__tar.gz → 1.18.3__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 (126) hide show
  1. {copyparty-1.18.1 → copyparty-1.18.3}/PKG-INFO +3 -1
  2. {copyparty-1.18.1 → copyparty-1.18.3}/README.md +2 -0
  3. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/__main__.py +41 -1
  4. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/__version__.py +2 -2
  5. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/authsrv.py +75 -12
  6. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/bos/bos.py +18 -6
  7. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/cfg.py +6 -0
  8. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/ftpd.py +9 -4
  9. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/httpcli.py +36 -16
  10. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/smbd.py +2 -2
  11. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/svchub.py +5 -1
  12. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/tcpsrv.py +1 -1
  13. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/tftpd.py +6 -2
  14. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/th_srv.py +3 -2
  15. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/up2k.py +36 -11
  16. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/util.py +9 -2
  17. copyparty-1.18.3/copyparty/web/baguettebox.js.gz +0 -0
  18. copyparty-1.18.3/copyparty/web/browser.css.gz +0 -0
  19. copyparty-1.18.3/copyparty/web/browser.js.gz +0 -0
  20. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty.egg-info/PKG-INFO +3 -1
  21. copyparty-1.18.1/copyparty/web/baguettebox.js.gz +0 -0
  22. copyparty-1.18.1/copyparty/web/browser.css.gz +0 -0
  23. copyparty-1.18.1/copyparty/web/browser.js.gz +0 -0
  24. {copyparty-1.18.1 → copyparty-1.18.3}/LICENSE +0 -0
  25. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/__init__.py +0 -0
  26. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/bos/__init__.py +0 -0
  27. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/bos/path.py +0 -0
  28. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/broker_mp.py +0 -0
  29. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/broker_mpw.py +0 -0
  30. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/broker_thr.py +0 -0
  31. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/broker_util.py +0 -0
  32. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/cert.py +0 -0
  33. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/dxml.py +0 -0
  34. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/fsutil.py +0 -0
  35. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/httpconn.py +0 -0
  36. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/httpsrv.py +0 -0
  37. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/ico.py +0 -0
  38. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/mdns.py +0 -0
  39. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/metrics.py +0 -0
  40. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/mtag.py +0 -0
  41. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/multicast.py +0 -0
  42. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/pwhash.py +0 -0
  43. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/res/COPYING.txt +0 -0
  44. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/res/__init__.py +0 -0
  45. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/res/insecure.pem +0 -0
  46. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/ssdp.py +0 -0
  47. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/star.py +0 -0
  48. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/__init__.py +0 -0
  49. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/dnslib/__init__.py +0 -0
  50. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/dnslib/bimap.py +0 -0
  51. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/dnslib/bit.py +0 -0
  52. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/dnslib/buffer.py +0 -0
  53. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/dnslib/dns.py +0 -0
  54. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/dnslib/label.py +0 -0
  55. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/dnslib/lex.py +0 -0
  56. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/dnslib/ranges.py +0 -0
  57. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/ifaddr/__init__.py +0 -0
  58. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/ifaddr/_posix.py +0 -0
  59. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/ifaddr/_shared.py +0 -0
  60. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/ifaddr/_win32.py +0 -0
  61. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/qrcodegen.py +0 -0
  62. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/stolen/surrogateescape.py +0 -0
  63. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/sutil.py +0 -0
  64. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/szip.py +0 -0
  65. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/th_cli.py +0 -0
  66. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/u2idx.py +0 -0
  67. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/a/__init__.py +0 -0
  68. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/a/partyfuse.py +0 -0
  69. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/a/u2c.py +0 -0
  70. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/a/webdav-cfg.bat +0 -0
  71. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/browser.html +0 -0
  72. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/browser2.html +0 -0
  73. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/cf.html +0 -0
  74. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/dbg-audio.js.gz +0 -0
  75. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/dd/2.png +0 -0
  76. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/dd/3.png +0 -0
  77. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/dd/4.png +0 -0
  78. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/dd/5.png +0 -0
  79. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/dd/__init__.py +0 -0
  80. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/__init__.py +0 -0
  81. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/busy.mp3.gz +0 -0
  82. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/easymde.css.gz +0 -0
  83. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/easymde.js.gz +0 -0
  84. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/fuse.py +0 -0
  85. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/marked.js.gz +0 -0
  86. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/mini-fa.css.gz +0 -0
  87. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/mini-fa.woff +0 -0
  88. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/prism.css.gz +0 -0
  89. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/prism.js.gz +0 -0
  90. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/prismd.css.gz +0 -0
  91. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/scp.woff2 +0 -0
  92. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  93. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  94. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/idp.html +0 -0
  95. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/md.css.gz +0 -0
  96. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/md.html +0 -0
  97. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/md.js.gz +0 -0
  98. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/md2.css.gz +0 -0
  99. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/md2.js.gz +0 -0
  100. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/mde.css.gz +0 -0
  101. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/mde.html +0 -0
  102. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/mde.js.gz +0 -0
  103. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/msg.css.gz +0 -0
  104. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/msg.html +0 -0
  105. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/rups.css.gz +0 -0
  106. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/rups.html +0 -0
  107. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/rups.js.gz +0 -0
  108. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/shares.css.gz +0 -0
  109. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/shares.html +0 -0
  110. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/shares.js.gz +0 -0
  111. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/splash.css.gz +0 -0
  112. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/splash.html +0 -0
  113. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/splash.js.gz +0 -0
  114. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/svcs.html +0 -0
  115. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/svcs.js.gz +0 -0
  116. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/ui.css.gz +0 -0
  117. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/up2k.js.gz +0 -0
  118. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/util.js.gz +0 -0
  119. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty/web/w.hash.js.gz +0 -0
  120. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty.egg-info/SOURCES.txt +0 -0
  121. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty.egg-info/dependency_links.txt +0 -0
  122. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty.egg-info/entry_points.txt +0 -0
  123. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty.egg-info/requires.txt +0 -0
  124. {copyparty-1.18.1 → copyparty-1.18.3}/copyparty.egg-info/top_level.txt +0 -0
  125. {copyparty-1.18.1 → copyparty-1.18.3}/pyproject.toml +0 -0
  126. {copyparty-1.18.1 → copyparty-1.18.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.18.1
3
+ Version: 1.18.3
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
@@ -2422,8 +2422,10 @@ TLDR: yes
2422
2422
  | send message | yep | yep | yep | yep | yep | yep | yep | yep |
2423
2423
  | set sort order | - | yep | yep | yep | yep | yep | yep | yep |
2424
2424
  | zip selection | - | yep | yep | yep | yep | yep | yep | yep |
2425
+ | file search | - | yep | yep | yep | yep | yep | yep | yep |
2425
2426
  | file rename | - | yep | yep | yep | yep | yep | yep | yep |
2426
2427
  | file cut/paste | - | yep | yep | yep | yep | yep | yep | yep |
2428
+ | unpost uploads | - | - | yep | yep | yep | yep | yep | yep |
2427
2429
  | navpane | - | yep | yep | yep | yep | yep | yep | yep |
2428
2430
  | image viewer | - | yep | yep | yep | yep | yep | yep | yep |
2429
2431
  | video player | - | yep | yep | yep | yep | yep | yep | yep |
@@ -2364,8 +2364,10 @@ TLDR: yes
2364
2364
  | send message | yep | yep | yep | yep | yep | yep | yep | yep |
2365
2365
  | set sort order | - | yep | yep | yep | yep | yep | yep | yep |
2366
2366
  | zip selection | - | yep | yep | yep | yep | yep | yep | yep |
2367
+ | file search | - | yep | yep | yep | yep | yep | yep | yep |
2367
2368
  | file rename | - | yep | yep | yep | yep | yep | yep | yep |
2368
2369
  | file cut/paste | - | yep | yep | yep | yep | yep | yep | yep |
2370
+ | unpost uploads | - | - | yep | yep | yep | yep | yep | yep |
2369
2371
  | navpane | - | yep | yep | yep | yep | yep | yep | yep |
2370
2372
  | image viewer | - | yep | yep | yep | yep | yep | yep | yep |
2371
2373
  | video player | - | yep | yep | yep | yep | yep | yep | yep |
@@ -855,6 +855,43 @@ def get_sects():
855
855
  """
856
856
  ),
857
857
  ],
858
+ [
859
+ "chmod",
860
+ "file/folder permissions",
861
+ dedent(
862
+ """
863
+ global-option \033[33m--chmod-f\033[0m and volflag \033[33mchmod_f\033[0m specifies the unix-permission to use when creating a new file
864
+
865
+ similarly, \033[33m--chmod-d\033[0m and \033[33mchmod_d\033[0m sets the directory/folder perm
866
+
867
+ the value is a three-digit octal number such as 755, 750, 644, etc.
868
+
869
+ first digit = "User"; permission for the unix-user
870
+ second digit = "Group"; permission for the unix-group
871
+ third digit = "Other"; permission for all other users/groups
872
+
873
+ for files:
874
+ 0 = --- = no access
875
+ 1 = --x = can execute the file as a program
876
+ 2 = -w- = can write
877
+ 3 = -wx = can write and execute
878
+ 4 = r-- = can read
879
+ 5 = r-x = can read and execute
880
+ 6 = rw- = can read and write
881
+ 7 = rwx = can read, write, execute
882
+
883
+ for directories/folders:
884
+ 0 = --- = no access
885
+ 1 = --x = can read files in folder but not list contents
886
+ 2 = -w- = n/a
887
+ 3 = -wx = can create files but not list
888
+ 4 = r-- = can list, but not read/write
889
+ 5 = r-x = can list and read files
890
+ 6 = rw- = n/a
891
+ 7 = rwx = can read, write, list
892
+ """
893
+ ),
894
+ ],
858
895
  [
859
896
  "pwhash",
860
897
  "password hashing",
@@ -1005,6 +1042,8 @@ def add_upload(ap):
1005
1042
  ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without \033[33m-e2d\033[0m; roughly 1 MiB RAM per 600")
1006
1043
  ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems)")
1007
1044
  ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)")
1045
+ ap2.add_argument("--chmod-f", metavar="UGO", type=u, default="", help="unix file permissions to use when creating files; default is probably 644 (OS-decided), see --help-chmod. Examples: [\033[32m644\033[0m] = owner-RW + all-R, [\033[32m755\033[0m] = owner-RWX + all-RX, [\033[32m777\033[0m] = full-yolo (volflag=chmod_f)")
1046
+ ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see --help-chmod. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)")
1008
1047
  ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
1009
1048
  ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)")
1010
1049
  ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
@@ -1041,7 +1080,7 @@ def add_network(ap):
1041
1080
  ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [\033[32m/foo/bar\033[0m]")
1042
1081
  if ANYWIN:
1043
1082
  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")
1044
- else:
1083
+ elif not MACOS:
1045
1084
  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)")
1046
1085
  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")
1047
1086
  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")
@@ -1527,6 +1566,7 @@ def add_ui(ap, retry):
1527
1566
  ap2.add_argument("--lg-sba", metavar="TXT", type=u, default="", help="the value of the iframe 'allow' attribute for prologue/epilogue docs (volflag=lg_sba); see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#iframes")
1528
1567
  ap2.add_argument("--no-sb-md", action="store_true", help="don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md)")
1529
1568
  ap2.add_argument("--no-sb-lg", action="store_true", help="don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support")
1569
+ ap2.add_argument("--have-unlistc", action="store_true", help=argparse.SUPPRESS)
1530
1570
 
1531
1571
 
1532
1572
  def add_debug(ap):
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 18, 1)
3
+ VERSION = (1, 18, 3)
4
4
  CODENAME = "logtail"
5
- BUILD_DT = (2025, 7, 7)
5
+ BUILD_DT = (2025, 7, 21)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -113,6 +113,8 @@ class Lim(object):
113
113
 
114
114
  self.reg = None # up2k registry
115
115
 
116
+ self.chmod_d = 0o755
117
+
116
118
  self.nups = {} # num tracker
117
119
  self.bups = {} # byte tracker list
118
120
  self.bupc = {} # byte tracker cache
@@ -273,7 +275,7 @@ class Lim(object):
273
275
  if not dirs:
274
276
  # no branches yet; make one
275
277
  sub = os.path.join(path, "0")
276
- bos.mkdir(sub)
278
+ bos.mkdir(sub, self.chmod_d)
277
279
  else:
278
280
  # try newest branch only
279
281
  sub = os.path.join(path, str(dirs[-1]))
@@ -288,7 +290,7 @@ class Lim(object):
288
290
 
289
291
  # make a branch
290
292
  sub = os.path.join(path, str(dirs[-1] + 1))
291
- bos.mkdir(sub)
293
+ bos.mkdir(sub, self.chmod_d)
292
294
  ret = self.dive(sub, lvs - 1)
293
295
  if ret is None:
294
296
  raise Pebkac(500, "rotation bug")
@@ -365,6 +367,7 @@ class VFS(object):
365
367
  self.shr_src = None # source vfs+rem of a share
366
368
  self.shr_files = set() # filenames to include from shr_src
367
369
  self.shr_owner = "" # uname
370
+ self.shr_all_aps = []
368
371
  self.aread = {}
369
372
  self.awrite = {}
370
373
  self.amove = {}
@@ -384,7 +387,7 @@ class VFS(object):
384
387
  self.dbpath = self.histpath
385
388
  self.all_vols = {vpath: self} # flattened recursive
386
389
  self.all_nodes = {vpath: self} # also jumpvols/shares
387
- self.all_aps = [(rp, self)]
390
+ self.all_aps = [(rp, [self])]
388
391
  self.all_vps = [(vp, self)]
389
392
  else:
390
393
  self.histpath = self.dbpath = ""
@@ -417,7 +420,11 @@ class VFS(object):
417
420
  rp = self.realpath
418
421
  rp += "" if rp.endswith(os.sep) else os.sep
419
422
  vp = self.vpath + ("/" if self.vpath else "")
420
- aps.append((rp, self))
423
+ hit = next((x[1] for x in aps if x[0] == rp), None)
424
+ if hit:
425
+ hit.append(self)
426
+ else:
427
+ aps.append((rp, [self]))
421
428
  vps.append((vp, self))
422
429
 
423
430
  for v in self.nodes.values():
@@ -841,9 +848,11 @@ class VFS(object):
841
848
  return None
842
849
 
843
850
  if "xvol" in self.flags:
844
- for vap, vn in self.root.all_aps:
851
+ all_aps = self.shr_all_aps or self.root.all_aps
852
+
853
+ for vap, vns in all_aps:
845
854
  if aps.startswith(vap):
846
- return vn
855
+ return self if self in vns else vns[0]
847
856
 
848
857
  if self.log:
849
858
  self.log("vfs", "xvol: %r" % (ap,), 3)
@@ -915,6 +924,9 @@ class AuthSrv(object):
915
924
 
916
925
  yield prev, True
917
926
 
927
+ def vf0(self):
928
+ return {"d2d": True, "tcolor": self.args.tcolor}
929
+
918
930
  def idp_checkin(
919
931
  self, broker , uname , gname
920
932
  ) :
@@ -1620,13 +1632,12 @@ class AuthSrv(object):
1620
1632
  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"
1621
1633
  self.log(t, 1)
1622
1634
  axs = AXS()
1623
- vfs = VFS(self.log_func, absreal("."), "", "", axs, {})
1635
+ vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0())
1624
1636
  if not axs.uread:
1625
1637
  self.badcfg1 = True
1626
1638
  elif "" not in mount:
1627
1639
  # there's volumes but no root; make root inaccessible
1628
- zsd = {"d2d": True, "tcolor": self.args.tcolor}
1629
- vfs = VFS(self.log_func, "", "", "", AXS(), zsd)
1640
+ vfs = VFS(self.log_func, "", "", "", AXS(), self.vf0())
1630
1641
 
1631
1642
  maxdepth = 0
1632
1643
  for dst in sorted(mount.keys(), key=lambda x: (x.count("/"), len(x))):
@@ -1674,8 +1685,7 @@ class AuthSrv(object):
1674
1685
  shrs = enshare[1:]
1675
1686
  if enshare:
1676
1687
 
1677
- zsd = {"d2d": True, "tcolor": self.args.tcolor}
1678
- shv = VFS(self.log_func, "", shr, shr, AXS(), zsd)
1688
+ shv = VFS(self.log_func, "", shr, shr, AXS(), self.vf0())
1679
1689
 
1680
1690
  db_path = self.args.shr_db
1681
1691
  db = sqlite3.connect(db_path)
@@ -2053,8 +2063,11 @@ class AuthSrv(object):
2053
2063
  elif self.args.re_maxage:
2054
2064
  vol.flags["scan"] = self.args.re_maxage
2055
2065
 
2066
+ self.args.have_unlistc = False
2067
+
2056
2068
  all_mte = {}
2057
2069
  errors = False
2070
+ free_umask = False
2058
2071
  for vol in vfs.all_nodes.values():
2059
2072
  if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
2060
2073
  vol.flags["e2ds"] = True
@@ -2111,9 +2124,33 @@ class AuthSrv(object):
2111
2124
  t = 'volume "/%s" has invalid %stry [%s]'
2112
2125
  raise Exception(t % (vol.vpath, k, vol.flags.get(k + "try")))
2113
2126
 
2127
+ for k in ("chmod_d", "chmod_f"):
2128
+ is_d = k == "chmod_d"
2129
+ zs = vol.flags.get(k, "")
2130
+ if not zs and is_d:
2131
+ zs = "755"
2132
+ if not zs:
2133
+ vol.flags.pop(k, None)
2134
+ continue
2135
+ if not re.match("^[0-7]{3}$", zs):
2136
+ t = "config-option '%s' must be a three-digit octal value such as [755] or [644] but the value was [%s]"
2137
+ t = t % (k, zs)
2138
+ self.log(t, 1)
2139
+ raise Exception(t)
2140
+ zi = int(zs, 8)
2141
+ vol.flags[k] = zi
2142
+ if (is_d and zi != 0o755) or not is_d:
2143
+ free_umask = True
2144
+
2145
+ if vol.lim:
2146
+ vol.lim.chmod_d = vol.flags["chmod_d"]
2147
+
2114
2148
  if vol.flags.get("og"):
2115
2149
  self.args.uqe = True
2116
2150
 
2151
+ if "unlistcr" in vol.flags or "unlistcw" in vol.flags:
2152
+ self.args.have_unlistc = True
2153
+
2117
2154
  zs = str(vol.flags.get("tcolor", "")).lstrip("#")
2118
2155
  if len(zs) == 3: # fc5 => ffcc55
2119
2156
  vol.flags["tcolor"] = "".join([x * 2 for x in zs])
@@ -2337,6 +2374,10 @@ class AuthSrv(object):
2337
2374
  if errors:
2338
2375
  sys.exit(1)
2339
2376
 
2377
+ setattr(self.args, "free_umask", free_umask)
2378
+ if free_umask:
2379
+ os.umask(0)
2380
+
2340
2381
  vfs.bubble_flags()
2341
2382
 
2342
2383
  have_e2d = False
@@ -2539,6 +2580,28 @@ class AuthSrv(object):
2539
2580
  shn.shr_src = (s_vfs, s_rem)
2540
2581
  shn.realpath = s_vfs.canonical(s_rem)
2541
2582
 
2583
+ # root.all_aps doesn't include any shares, so make a copy where the
2584
+ # share appears in all abspaths it can provide (for example for chk_ap)
2585
+ ap = shn.realpath
2586
+ if not ap.endswith(os.sep):
2587
+ ap += os.sep
2588
+ shn.shr_all_aps = [(x, y[:]) for x, y in vfs.all_aps]
2589
+ exact = False
2590
+ for ap2, vns in shn.shr_all_aps:
2591
+ if ap == ap2:
2592
+ exact = True
2593
+ if ap2.startswith(ap):
2594
+ try:
2595
+ vp2 = vjoin(s_rem, ap2[len(ap) :])
2596
+ vn2, _ = s_vfs.get(vp2, "*", False, False)
2597
+ if vn2 == s_vfs or vn2.dbv == s_vfs:
2598
+ vns.append(shn)
2599
+ except:
2600
+ pass
2601
+ if not exact:
2602
+ shn.shr_all_aps.append((ap, [shn]))
2603
+ shn.shr_all_aps.sort(key=lambda x: len(x[0]), reverse=True)
2604
+
2542
2605
  if self.args.shr_v:
2543
2606
  t = "mapped %s share [%s] by [%s] => [%s] => [%s]"
2544
2607
  self.log(t % (s_pr, s_k, s_un, s_vp, shn.realpath))
@@ -2553,7 +2616,7 @@ class AuthSrv(object):
2553
2616
  continue # also fine
2554
2617
  for zs in svn.nodes.keys():
2555
2618
  # hide subvolume
2556
- vn.nodes[zs] = VFS(self.log_func, "", "", "", AXS(), {})
2619
+ vn.nodes[zs] = VFS(self.log_func, "", "", "", AXS(), self.vf0())
2557
2620
 
2558
2621
  cur2.close()
2559
2622
  cur.close()
@@ -22,14 +22,26 @@ def listdir(p = ".") :
22
22
 
23
23
 
24
24
  def makedirs(name , mode = 0o755, exist_ok = True) :
25
+ # os.makedirs does 777 for all but leaf; this does mode on all
26
+ todo = []
25
27
  bname = fsenc(name)
26
- try:
27
- os.makedirs(bname, mode)
28
- return True
29
- except:
30
- if not exist_ok or not os.path.isdir(bname):
31
- raise
28
+ while bname:
29
+ if os.path.isdir(bname):
30
+ break
31
+ todo.append(bname)
32
+ bname = os.path.dirname(bname)
33
+ if not todo:
34
+ if not exist_ok:
35
+ os.mkdir(bname) # to throw
32
36
  return False
37
+ for zb in todo[::-1]:
38
+ try:
39
+ os.mkdir(zb, mode)
40
+ except:
41
+ if os.path.isdir(zb):
42
+ continue
43
+ raise
44
+ return True
33
45
 
34
46
 
35
47
  def mkdir(p , mode = 0o755) :
@@ -78,6 +78,8 @@ def vf_vmap() :
78
78
  }
79
79
  for k in (
80
80
  "bup_ck",
81
+ "chmod_d",
82
+ "chmod_f",
81
83
  "dbd",
82
84
  "forget_ip",
83
85
  "hsortn",
@@ -169,6 +171,8 @@ flagcats = {
169
171
  "safededup": "verify on-disk data before using it for dedup",
170
172
  "noclone": "take dupe data from clients, even if available on HDD",
171
173
  "nodupe": "rejects existing files (instead of linking/cloning them)",
174
+ "chmod_d=755": "unix-permission for new dirs/folders",
175
+ "chmod_f=644": "unix-permission for new files",
172
176
  "sparse": "force use of sparse files, mainly for s3-backed storage",
173
177
  "nosparse": "deny use of sparse files, mainly for slow storage",
174
178
  "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
@@ -280,6 +284,8 @@ flagcats = {
280
284
  "nodirsz": "don't show total folder size",
281
285
  "robots": "allows indexing by search engines (default)",
282
286
  "norobots": "kindly asks search engines to leave",
287
+ "unlistcr": "don't list read-access in controlpanel",
288
+ "unlistcw": "don't list write-access in controlpanel",
283
289
  "no_sb_md": "disable js sandbox for markdown files",
284
290
  "no_sb_lg": "disable js sandbox for prologue/epilogue",
285
291
  "sb_md": "enable js sandbox for markdown files (default)",
@@ -225,7 +225,7 @@ class FtpFs(AbstractedFS):
225
225
  r = "r" in mode
226
226
  w = "w" in mode or "a" in mode or "+" in mode
227
227
 
228
- ap = self.rv2a(filename, r, w)[0]
228
+ ap, vfs, _ = self.rv2a(filename, r, w)
229
229
  self.validpath(ap)
230
230
  if w:
231
231
  try:
@@ -257,7 +257,11 @@ class FtpFs(AbstractedFS):
257
257
 
258
258
  wunlink(self.log, ap, VF_CAREFUL)
259
259
 
260
- return open(fsenc(ap), mode, self.args.iobuf)
260
+ ret = open(fsenc(ap), mode, self.args.iobuf)
261
+ if w and "chmod_f" in vfs.flags:
262
+ os.fchmod(ret.fileno(), vfs.flags["chmod_f"])
263
+
264
+ return ret
261
265
 
262
266
  def chdir(self, path ) :
263
267
  nwd = join(self.cwd, path)
@@ -288,8 +292,9 @@ class FtpFs(AbstractedFS):
288
292
  ) = avfs.can_access("", self.h.uname)
289
293
 
290
294
  def mkdir(self, path ) :
291
- ap = self.rv2a(path, w=True)[0]
292
- bos.makedirs(ap) # filezilla expects this
295
+ ap, vfs, _ = self.rv2a(path, w=True)
296
+ chmod = vfs.flags["chmod_d"]
297
+ bos.makedirs(ap, chmod) # filezilla expects this
293
298
 
294
299
  def listdir(self, path ) :
295
300
  vpath = join(self.cwd, path)
@@ -45,6 +45,7 @@ from .util import (
45
45
  APPLESAN_RE,
46
46
  BITNESS,
47
47
  DAV_ALLPROPS,
48
+ E_SCK_WR,
48
49
  FN_EMB,
49
50
  HAVE_SQLITE3,
50
51
  HTTPCODE,
@@ -1369,12 +1370,13 @@ class HttpCli(object):
1369
1370
  title = self.uparam.get("title") or self.vpath.split("/")[-1]
1370
1371
  etitle = html_escape(title, True, True)
1371
1372
 
1372
- baseurl = "%s://%s%s" % (
1373
+ baseurl = "%s://%s/" % (
1373
1374
  "https" if self.is_https else "http",
1374
1375
  self.host,
1375
- self.args.SRS,
1376
1376
  )
1377
- feed = "%s%s" % (baseurl, self.req[1:])
1377
+ feed = baseurl + self.req[1:]
1378
+ if self.is_vproxied:
1379
+ baseurl += self.args.RS
1378
1380
  efeed = html_escape(feed, True, True)
1379
1381
  edirlink = efeed.split("?")[0] + q_pw
1380
1382
 
@@ -1387,7 +1389,7 @@ class HttpCli(object):
1387
1389
  \t\t<title>%s</title>
1388
1390
  \t\t<description></description>
1389
1391
  \t\t<link>%s</link>
1390
- \t\t<generator>copyparty-1</generator>
1392
+ \t\t<generator>copyparty-2</generator>
1391
1393
  """
1392
1394
  % (efeed, etitle, edirlink)
1393
1395
  ]
@@ -2058,7 +2060,7 @@ class HttpCli(object):
2058
2060
  fdir, fn = os.path.split(fdir)
2059
2061
  rem, _ = vsplit(rem)
2060
2062
 
2061
- bos.makedirs(fdir)
2063
+ bos.makedirs(fdir, vfs.flags["chmod_d"])
2062
2064
 
2063
2065
  open_ka = {"fun": open}
2064
2066
  open_a = ["wb", self.args.iobuf]
@@ -2116,6 +2118,8 @@ class HttpCli(object):
2116
2118
  fn = vfs.flags["put_name2"].format(now=time.time(), cip=self.dip())
2117
2119
 
2118
2120
  params = {"suffix": suffix, "fdir": fdir}
2121
+ if "chmod_f" in vfs.flags:
2122
+ params["chmod"] = vfs.flags["chmod_f"]
2119
2123
  if self.args.nw:
2120
2124
  params = {}
2121
2125
  fn = os.devnull
@@ -2163,7 +2167,7 @@ class HttpCli(object):
2163
2167
  if self.args.nw:
2164
2168
  fn = os.devnull
2165
2169
  else:
2166
- bos.makedirs(fdir)
2170
+ bos.makedirs(fdir, vfs.flags["chmod_d"])
2167
2171
  path = os.path.join(fdir, fn)
2168
2172
  if not nameless:
2169
2173
  self.vpath = vjoin(self.vpath, fn)
@@ -2295,7 +2299,7 @@ class HttpCli(object):
2295
2299
  if self.args.hook_v:
2296
2300
  log_reloc(self.log, hr["reloc"], x, path, vp, fn, vfs, rem)
2297
2301
  fdir, self.vpath, fn, (vfs, rem) = x
2298
- bos.makedirs(fdir)
2302
+ bos.makedirs(fdir, vfs.flags["chmod_d"])
2299
2303
  path2 = os.path.join(fdir, fn)
2300
2304
  atomic_move(self.log, path, path2, vfs.flags)
2301
2305
  path = path2
@@ -2580,7 +2584,7 @@ class HttpCli(object):
2580
2584
  dst = vfs.canonical(rem)
2581
2585
  try:
2582
2586
  if not bos.path.isdir(dst):
2583
- bos.makedirs(dst)
2587
+ bos.makedirs(dst, vfs.flags["chmod_d"])
2584
2588
  except OSError as ex:
2585
2589
  self.log("makedirs failed %r" % (dst,))
2586
2590
  if not bos.path.isdir(dst):
@@ -3011,7 +3015,7 @@ class HttpCli(object):
3011
3015
  raise Pebkac(405, 'folder "/%s" already exists' % (vpath,))
3012
3016
 
3013
3017
  try:
3014
- bos.makedirs(fn)
3018
+ bos.makedirs(fn, vfs.flags["chmod_d"])
3015
3019
  except OSError as ex:
3016
3020
  if ex.errno == errno.EACCES:
3017
3021
  raise Pebkac(500, "the server OS denied write-access")
@@ -3052,6 +3056,8 @@ class HttpCli(object):
3052
3056
 
3053
3057
  with open(fsenc(fn), "wb") as f:
3054
3058
  f.write(b"`GRUNNUR`\n")
3059
+ if "chmod_f" in vfs.flags:
3060
+ os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3055
3061
 
3056
3062
  vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
3057
3063
  self.redirect(vpath, "?edit")
@@ -3125,7 +3131,7 @@ class HttpCli(object):
3125
3131
  )
3126
3132
  upload_vpath = "{}/{}".format(vfs.vpath, rem).strip("/")
3127
3133
  if not nullwrite:
3128
- bos.makedirs(fdir_base)
3134
+ bos.makedirs(fdir_base, vfs.flags["chmod_d"])
3129
3135
 
3130
3136
  rnd, lifetime, xbu, xau = self.upload_flags(vfs)
3131
3137
  zs = self.uparam.get("want") or self.headers.get("accept") or ""
@@ -3220,8 +3226,11 @@ class HttpCli(object):
3220
3226
  else:
3221
3227
  open_args["fdir"] = fdir
3222
3228
 
3229
+ if "chmod_f" in vfs.flags:
3230
+ open_args["chmod"] = vfs.flags["chmod_f"]
3231
+
3223
3232
  if p_file and not nullwrite:
3224
- bos.makedirs(fdir)
3233
+ bos.makedirs(fdir, vfs.flags["chmod_d"])
3225
3234
 
3226
3235
  # reserve destination filename
3227
3236
  f, fname = ren_open(fname, "wb", fdir=fdir, suffix=suffix)
@@ -3325,7 +3334,7 @@ class HttpCli(object):
3325
3334
  if nullwrite:
3326
3335
  fdir = ap2 = ""
3327
3336
  else:
3328
- bos.makedirs(fdir)
3337
+ bos.makedirs(fdir, vfs.flags["chmod_d"])
3329
3338
  atomic_move(self.log, abspath, ap2, vfs.flags)
3330
3339
  abspath = ap2
3331
3340
  sz = bos.path.getsize(abspath)
@@ -3446,6 +3455,8 @@ class HttpCli(object):
3446
3455
  ft = "{}:{}".format(self.ip, self.addr[1])
3447
3456
  ft = "{}\n{}\n{}\n".format(ft, msg.rstrip(), errmsg)
3448
3457
  f.write(ft.encode("utf-8"))
3458
+ if "chmod_f" in vfs.flags:
3459
+ os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3449
3460
  except Exception as ex:
3450
3461
  suf = "\nfailed to write the upload report: {}".format(ex)
3451
3462
 
@@ -3495,7 +3506,7 @@ class HttpCli(object):
3495
3506
  lim = vfs.get_dbv(rem)[0].lim
3496
3507
  if lim:
3497
3508
  fp, rp = lim.all(self.ip, rp, clen, vfs.realpath, fp, self.conn.hsrv.broker)
3498
- bos.makedirs(fp)
3509
+ bos.makedirs(fp, vfs.flags["chmod_d"])
3499
3510
 
3500
3511
  fp = os.path.join(fp, fn)
3501
3512
  rem = "{}/{}".format(rp, fn).strip("/")
@@ -3563,13 +3574,15 @@ class HttpCli(object):
3563
3574
  zs = ub64enc(zb).decode("ascii")[:24].lower()
3564
3575
  dp = "%s/md/%s/%s/%s" % (dbv.histpath, zs[:2], zs[2:4], zs)
3565
3576
  self.log("moving old version to %s/%s" % (dp, mfile2))
3566
- if bos.makedirs(dp):
3577
+ if bos.makedirs(dp, vfs.flags["chmod_d"]):
3567
3578
  with open(os.path.join(dp, "dir.txt"), "wb") as f:
3568
3579
  f.write(afsenc(vrd))
3580
+ if "chmod_f" in vfs.flags:
3581
+ os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3569
3582
  elif hist_cfg == "s":
3570
3583
  dp = os.path.join(mdir, ".hist")
3571
3584
  try:
3572
- bos.mkdir(dp)
3585
+ bos.mkdir(dp, vfs.flags["chmod_d"])
3573
3586
  hidedir(dp)
3574
3587
  except:
3575
3588
  pass
@@ -3607,6 +3620,8 @@ class HttpCli(object):
3607
3620
  wunlink(self.log, fp, vfs.flags)
3608
3621
 
3609
3622
  with open(fsenc(fp), "wb", self.args.iobuf) as f:
3623
+ if "chmod_f" in vfs.flags:
3624
+ os.fchmod(f.fileno(), vfs.flags["chmod_f"])
3610
3625
  sz, sha512, _ = hashcopy(p_data, f, None, 0, self.args.s_wr_slp)
3611
3626
 
3612
3627
  if lim:
@@ -4342,7 +4357,7 @@ class HttpCli(object):
4342
4357
  self.log("file deleted; disconnecting")
4343
4358
  break
4344
4359
  except IOError as ex:
4345
- if ex.errno not in (errno.EPIPE, errno.ESHUTDOWN, errno.EBADFD):
4360
+ if ex.errno not in E_SCK_WR:
4346
4361
  raise
4347
4362
  finally:
4348
4363
  if f:
@@ -4926,6 +4941,11 @@ class HttpCli(object):
4926
4941
  fn = html_escape(fn) if fn else self.conn.hsrv.iiam
4927
4942
  dls.append((perc, hsent, spd, eta, idle, usr, erd, rds, fn))
4928
4943
 
4944
+ if self.args.have_unlistc:
4945
+ allvols = self.asrv.vfs.all_vols
4946
+ rvol = [x for x in rvol if "unlistcr" not in allvols[x[1:-1]].flags]
4947
+ wvol = [x for x in wvol if "unlistcw" not in allvols[x[1:-1]].flags]
4948
+
4929
4949
  fmt = self.uparam.get("ls", "")
4930
4950
  if not fmt and (self.ua.startswith("curl/") or self.ua.startswith("fetch")):
4931
4951
  fmt = "v"
@@ -317,7 +317,7 @@ class SMB(object):
317
317
 
318
318
  self.hub.up2k.handle_mv(uname, "1.7.6.2", vp1, vp2)
319
319
  try:
320
- bos.makedirs(ap2)
320
+ bos.makedirs(ap2, vfs2.flags["chmod_d"])
321
321
  except:
322
322
  pass
323
323
 
@@ -331,7 +331,7 @@ class SMB(object):
331
331
  t = "blocked mkdir (no-write-acc %s): /%s @%s"
332
332
  yeet(t % (vfs.axs.uwrite, vpath, uname))
333
333
 
334
- return bos.mkdir(ap)
334
+ return bos.mkdir(ap, vfs.flags["chmod_d"])
335
335
 
336
336
  def _stat(self, vpath , *a , **ka ) :
337
337
  try:
@@ -21,6 +21,7 @@ from datetime import datetime
21
21
 
22
22
  from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode
23
23
  from .authsrv import BAD_CFG, AuthSrv
24
+ from .bos import bos
24
25
  from .cert import ensure_cert
25
26
  from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN
26
27
  from .pwhash import HAVE_ARGON2
@@ -1108,7 +1109,7 @@ class SvcHub(object):
1108
1109
 
1109
1110
  fn = sel_fn
1110
1111
  try:
1111
- os.makedirs(os.path.dirname(fn))
1112
+ bos.makedirs(os.path.dirname(fn))
1112
1113
  except:
1113
1114
  pass
1114
1115
 
@@ -1125,6 +1126,9 @@ class SvcHub(object):
1125
1126
 
1126
1127
  lh = codecs.open(fn, "w", encoding="utf-8", errors="replace")
1127
1128
 
1129
+ if getattr(self.args, "free_umask", False):
1130
+ os.fchmod(lh.fileno(), 0o644)
1131
+
1128
1132
  argv = [pybin] + self.argv
1129
1133
  if hasattr(shlex, "quote"):
1130
1134
  argv = [shlex.quote(x) for x in argv]
@@ -279,7 +279,7 @@ class TcpSrv(object):
279
279
  except:
280
280
  pass # will create another ipv4 socket instead
281
281
 
282
- if not ANYWIN and self.args.freebind:
282
+ if getattr(self.args, "freebind", False):
283
283
  srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1)
284
284
 
285
285
  try: