copyparty 1.14.4__py3-none-any.whl → 1.15.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/__main__.py CHANGED
@@ -986,9 +986,10 @@ def add_upload(ap):
986
986
  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")
987
987
  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)")
988
988
  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, ...)")
989
- ap2.add_argument("--hardlink", action="store_true", help="prefer hardlinks instead of symlinks when possible (within same filesystem) (volflag=hardlink)")
990
- ap2.add_argument("--never-symlink", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=neversymlink)")
991
- ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead (volflag=copydupes)")
989
+ ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
990
+ 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)")
991
+ ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
992
+ ap2.add_argument("--hardlink-only", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly)")
992
993
  ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
993
994
  ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually")
994
995
  ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash")
@@ -1060,6 +1061,7 @@ def add_cert(ap, cert_path):
1060
1061
 
1061
1062
 
1062
1063
  def add_auth(ap):
1064
+ ses_db = os.path.join(E.cfg, "sessions.db")
1063
1065
  ap2 = ap.add_argument_group('IdP / identity provider / user authentication options')
1064
1066
  ap2.add_argument("--idp-h-usr", metavar="HN", type=u, default="", help="bypass the copyparty authentication checks and assume the request-header \033[33mHN\033[0m contains the username of the requesting user (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy")
1065
1067
  ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
@@ -1067,6 +1069,9 @@ def add_auth(ap):
1067
1069
  ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")
1068
1070
  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")
1069
1071
  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")
1072
+ 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)")
1073
+ ap2.add_argument("--ses-len", metavar="CHARS", type=int, default=20, help="session key length; default is 120 bits ((20//4)*4*6)")
1074
+ ap2.add_argument("--no-ses", action="store_true", help="disable sessions; use plaintext passwords in cookies")
1070
1075
 
1071
1076
 
1072
1077
  def add_chpw(ap):
@@ -1279,6 +1284,7 @@ def add_logging(ap):
1279
1284
  ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
1280
1285
  ap2.add_argument("--no-logflush", action="store_true", help="don't flush the logfile after each write; tiny bit faster")
1281
1286
  ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
1287
+ ap2.add_argument("--log-utc", action="store_true", help="do not use local timezone; assume the TZ env-var is UTC (tiny bit faster)")
1282
1288
  ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals")
1283
1289
  ap2.add_argument("--log-badpwd", metavar="N", type=int, default=1, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed")
1284
1290
  ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
@@ -1337,7 +1343,7 @@ def add_transcoding(ap):
1337
1343
  def add_db_general(ap, hcores):
1338
1344
  noidx = APPLESAN_TXT if MACOS else ""
1339
1345
  ap2 = ap.add_argument_group('general db options')
1340
- ap2.add_argument("-e2d", action="store_true", help="enable up2k database, making files searchable + enables upload deduplication")
1346
+ ap2.add_argument("-e2d", action="store_true", help="enable up2k database; this enables file search, upload-undo, improves deduplication")
1341
1347
  ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets \033[33m-e2d\033[0m")
1342
1348
  ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets \033[33m-e2ds\033[0m")
1343
1349
  ap2.add_argument("-e2v", action="store_true", help="verify file integrity; rehash all files and compare with db")
@@ -1350,7 +1356,7 @@ def add_db_general(ap, hcores):
1350
1356
  ap2.add_argument("--re-dhash", action="store_true", help="force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary)")
1351
1357
  ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something (volflag=noforget)")
1352
1358
  ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see \033[33m--help-dbd\033[0m (volflag=dbd)")
1353
- ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (volflag=xlink)")
1359
+ ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (probably buggy, not recommended) (volflag=xlink)")
1354
1360
  ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing")
1355
1361
  ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="rescan filesystem for changes every \033[33mSEC\033[0m seconds; 0=off (volflag=scan)")
1356
1362
  ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
@@ -1463,6 +1469,7 @@ def run_argparse(
1463
1469
  ) :
1464
1470
  ap = argparse.ArgumentParser(
1465
1471
  formatter_class=formatter,
1472
+ usage=argparse.SUPPRESS,
1466
1473
  prog="copyparty",
1467
1474
  description="http file sharing hub v{} ({})".format(S_VERSION, S_BUILD_DT),
1468
1475
  )
@@ -1613,6 +1620,7 @@ def main(argv = None, rsrc = None) :
1613
1620
  ("--hdr-au-usr", "--idp-h-usr"),
1614
1621
  ("--idp-h-sep", "--idp-gsep"),
1615
1622
  ("--th-no-crop", "--th-crop=n"),
1623
+ ("--never-symlink", "--hardlink-only"),
1616
1624
  ]
1617
1625
  for dk, nk in deprecated:
1618
1626
  idx = -1
@@ -1637,7 +1645,7 @@ def main(argv = None, rsrc = None) :
1637
1645
  argv.extend(["--qr"])
1638
1646
  if ANYWIN or not os.geteuid():
1639
1647
  # win10 allows symlinks if admin; can be unexpected
1640
- argv.extend(["-p80,443,3923", "--ign-ebind", "--no-dedup"])
1648
+ argv.extend(["-p80,443,3923", "--ign-ebind"])
1641
1649
  except:
1642
1650
  pass
1643
1651
 
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 14, 4)
4
- CODENAME = "one step forward"
5
- BUILD_DT = (2024, 9, 2)
3
+ VERSION = (1, 15, 1)
4
+ CODENAME = "fill the drives"
5
+ BUILD_DT = (2024, 9, 9)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
copyparty/authsrv.py CHANGED
@@ -833,8 +833,10 @@ class AuthSrv(object):
833
833
 
834
834
  # fwd-decl
835
835
  self.vfs = VFS(log_func, "", "", AXS(), {})
836
- self.acct = {}
837
- self.iacct = {}
836
+ self.acct = {} # uname->pw
837
+ self.iacct = {} # pw->uname
838
+ self.ases = {} # uname->session
839
+ self.sesa = {} # session->uname
838
840
  self.defpw = {}
839
841
  self.grps = {}
840
842
  self.re_pwd = None
@@ -1884,6 +1886,11 @@ class AuthSrv(object):
1884
1886
  if len(zs) == 3: # fc5 => ffcc55
1885
1887
  vol.flags["tcolor"] = "".join([x * 2 for x in zs])
1886
1888
 
1889
+ if vol.flags.get("neversymlink"):
1890
+ vol.flags["hardlinkonly"] = True # was renamed
1891
+ if vol.flags.get("hardlinkonly"):
1892
+ vol.flags["hardlink"] = True
1893
+
1887
1894
  for k1, k2 in IMPLICATIONS:
1888
1895
  if k1 in vol.flags:
1889
1896
  vol.flags[k2] = True
@@ -1988,9 +1995,6 @@ class AuthSrv(object):
1988
1995
  for x in drop:
1989
1996
  vol.flags.pop(x)
1990
1997
 
1991
- if vol.flags.get("neversymlink") and not vol.flags.get("hardlink"):
1992
- vol.flags["copydupes"] = True
1993
-
1994
1998
  # verify tags mentioned by -mt[mp] are used by -mte
1995
1999
  local_mtp = {}
1996
2000
  local_only_mtp = {}
@@ -2069,6 +2073,8 @@ class AuthSrv(object):
2069
2073
 
2070
2074
  have_e2d = False
2071
2075
  have_e2t = False
2076
+ have_dedup = False
2077
+ unsafe_dedup = []
2072
2078
  t = "volumes and permissions:\n"
2073
2079
  for zv in vfs.all_vols.values():
2074
2080
  if not self.warn_anonwrite or verbosity < 5:
@@ -2101,6 +2107,11 @@ class AuthSrv(object):
2101
2107
  if "e2t" in zv.flags:
2102
2108
  have_e2t = True
2103
2109
 
2110
+ if "dedup" in zv.flags:
2111
+ have_dedup = True
2112
+ if "e2d" not in zv.flags and "hardlink" not in zv.flags:
2113
+ unsafe_dedup.append("/" + zv.vpath)
2114
+
2104
2115
  t += "\n"
2105
2116
 
2106
2117
  if self.warn_anonwrite and verbosity > 4:
@@ -2113,10 +2124,17 @@ class AuthSrv(object):
2113
2124
  self.log("\n\033[{}\033[0m\n".format(t))
2114
2125
 
2115
2126
  if not have_e2t:
2116
- t = "hint: argument -e2ts enables multimedia indexing (artist/title/...)"
2127
+ t = "hint: enable multimedia indexing (artist/title/...) with argument -e2ts"
2117
2128
  self.log(t, 6)
2118
2129
  else:
2119
- t = "hint: argument -e2dsa enables searching, upload-undo, and better deduplication"
2130
+ t = "hint: enable searching and upload-undo with argument -e2dsa"
2131
+ self.log(t, 6)
2132
+
2133
+ if unsafe_dedup:
2134
+ t = "WARNING: symlink-based deduplication is enabled for some volumes, but without indexing. Please enable -e2dsa and/or --hardlink to avoid problems when moving/renaming files. Affected volumes: %s"
2135
+ self.log(t % (", ".join(unsafe_dedup)), 3)
2136
+ elif not have_dedup:
2137
+ t = "hint: enable upload deduplication with --dedup (but see readme for consequences)"
2120
2138
  self.log(t, 6)
2121
2139
 
2122
2140
  zv, _ = vfs.get("/", "*", False, False)
@@ -2158,8 +2176,11 @@ class AuthSrv(object):
2158
2176
  self.grps = grps
2159
2177
  self.iacct = {v: k for k, v in acct.items()}
2160
2178
 
2179
+ self.load_sessions()
2180
+
2161
2181
  self.re_pwd = None
2162
2182
  pwds = [re.escape(x) for x in self.iacct.keys()]
2183
+ pwds.extend(list(self.sesa))
2163
2184
  if pwds:
2164
2185
  if self.ah.on:
2165
2186
  zs = r"(\[H\] pw:.*|[?&]pw=)([^&]+)"
@@ -2234,6 +2255,72 @@ class AuthSrv(object):
2234
2255
  cur.close()
2235
2256
  db.close()
2236
2257
 
2258
+ def load_sessions(self, quiet=False) :
2259
+ # mutex me
2260
+ if self.args.no_ses:
2261
+ self.ases = {}
2262
+ self.sesa = {}
2263
+ return
2264
+
2265
+ import sqlite3
2266
+
2267
+ ases = {}
2268
+ blen = (self.args.ses_len // 4) * 4 # 3 bytes in 4 chars
2269
+ blen = (blen * 3) // 4 # bytes needed for ses_len chars
2270
+
2271
+ db = sqlite3.connect(self.args.ses_db)
2272
+ cur = db.cursor()
2273
+
2274
+ for uname, sid in cur.execute("select un, si from us"):
2275
+ if uname in self.acct:
2276
+ ases[uname] = sid
2277
+
2278
+ n = []
2279
+ q = "insert into us values (?,?,?)"
2280
+ for uname in self.acct:
2281
+ if uname not in ases:
2282
+ sid = ub64enc(os.urandom(blen)).decode("utf-8")
2283
+ cur.execute(q, (uname, sid, int(time.time())))
2284
+ ases[uname] = sid
2285
+ n.append(uname)
2286
+
2287
+ if n:
2288
+ db.commit()
2289
+
2290
+ cur.close()
2291
+ db.close()
2292
+
2293
+ self.ases = ases
2294
+ self.sesa = {v: k for k, v in ases.items()}
2295
+ if n and not quiet:
2296
+ t = ", ".join(n[:3])
2297
+ if len(n) > 3:
2298
+ t += "..."
2299
+ self.log("added %d new sessions (%s)" % (len(n), t))
2300
+
2301
+ def forget_session(self, broker , uname ) :
2302
+ with self.mutex:
2303
+ self._forget_session(uname)
2304
+
2305
+ if broker:
2306
+ broker.ask("_reload_sessions").get()
2307
+
2308
+ def _forget_session(self, uname ) :
2309
+ if self.args.no_ses:
2310
+ return
2311
+
2312
+ import sqlite3
2313
+
2314
+ db = sqlite3.connect(self.args.ses_db)
2315
+ cur = db.cursor()
2316
+ cur.execute("delete from us where un = ?", (uname,))
2317
+ db.commit()
2318
+ cur.close()
2319
+ db.close()
2320
+
2321
+ self.sesa.pop(self.ases.get(uname, ""), "")
2322
+ self.ases.pop(uname, "")
2323
+
2237
2324
  def chpw(self, broker , uname, pw) :
2238
2325
  if not self.args.chpw:
2239
2326
  return False, "feature disabled in server config"
@@ -2253,7 +2340,7 @@ class AuthSrv(object):
2253
2340
  if hpw == self.acct[uname]:
2254
2341
  return False, "that's already your password my dude"
2255
2342
 
2256
- if hpw in self.iacct:
2343
+ if hpw in self.iacct or hpw in self.sesa:
2257
2344
  return False, "password is taken"
2258
2345
 
2259
2346
  with self.mutex:
copyparty/broker_mp.py CHANGED
@@ -72,6 +72,10 @@ class BrokerMp(object):
72
72
  for _, proc in enumerate(self.procs):
73
73
  proc.q_pend.put((0, "reload", []))
74
74
 
75
+ def reload_sessions(self) :
76
+ for _, proc in enumerate(self.procs):
77
+ proc.q_pend.put((0, "reload_sessions", []))
78
+
75
79
  def collector(self, proc ) :
76
80
  """receive message from hub in other process"""
77
81
  while True:
copyparty/broker_mpw.py CHANGED
@@ -88,6 +88,10 @@ class MpWorker(BrokerCli):
88
88
  self.asrv.reload()
89
89
  self.logw("mpw.asrv reloaded")
90
90
 
91
+ elif dest == "reload_sessions":
92
+ with self.asrv.mutex:
93
+ self.asrv.load_sessions()
94
+
91
95
  elif dest == "listen":
92
96
  self.httpsrv.listen(args[0], args[1])
93
97
 
copyparty/broker_thr.py CHANGED
@@ -30,6 +30,7 @@ class BrokerThr(BrokerCli):
30
30
  self.iphash = HMaccas(os.path.join(self.args.E.cfg, "iphash"), 8)
31
31
  self.httpsrv = HttpSrv(self, None)
32
32
  self.reload = self.noop
33
+ self.reload_sessions = self.noop
33
34
 
34
35
  def shutdown(self) :
35
36
  # self.log("broker", "shutting down")
copyparty/cfg.py CHANGED
@@ -12,8 +12,7 @@ def vf_bmap() :
12
12
  "dav_auth": "davauth",
13
13
  "dav_rt": "davrt",
14
14
  "ed": "dots",
15
- "never_symlink": "neversymlink",
16
- "no_dedup": "copydupes",
15
+ "hardlink_only": "hardlinkonly",
17
16
  "no_dupe": "nodupe",
18
17
  "no_forget": "noforget",
19
18
  "no_pipe": "nopipe",
@@ -23,6 +22,7 @@ def vf_bmap() :
23
22
  "no_athumb": "dathumb",
24
23
  }
25
24
  for k in (
25
+ "dedup",
26
26
  "dotsrch",
27
27
  "e2d",
28
28
  "e2ds",
@@ -58,6 +58,7 @@ def vf_vmap() :
58
58
  "no_hash": "nohash",
59
59
  "no_idx": "noidx",
60
60
  "re_maxage": "scan",
61
+ "safe_dedup": "safededup",
61
62
  "th_convt": "convt",
62
63
  "th_size": "thsize",
63
64
  "th_crop": "crop",
@@ -129,10 +130,11 @@ permdescs = {
129
130
 
130
131
  flagcats = {
131
132
  "uploads, general": {
133
+ "dedup": "enable symlink-based file deduplication",
134
+ "hardlink": "enable hardlink-based file deduplication,\nwith fallback on symlinks when that is impossible",
135
+ "hardlinkonly": "dedup with hardlink only, never symlink;\nmake a full copy if hardlink is impossible",
136
+ "safededup": "verify on-disk data before using it for dedup",
132
137
  "nodupe": "rejects existing files (instead of symlinking them)",
133
- "hardlink": "does dedup with hardlinks instead of symlinks",
134
- "neversymlink": "disables symlink fallback; full copy instead",
135
- "copydupes": "disables dedup, always saves full copies of dupes",
136
138
  "sparse": "force use of sparse files, mainly for s3-backed storage",
137
139
  "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
138
140
  "nosub": "forces all uploads into the top folder of the vfs",
@@ -159,7 +161,7 @@ flagcats = {
159
161
  "lifetime=3600": "uploads are deleted after 1 hour",
160
162
  },
161
163
  "database, general": {
162
- "e2d": "enable database; makes files searchable + enables upload dedup",
164
+ "e2d": "enable database; makes files searchable + enables upload-undo",
163
165
  "e2ds": "scan writable folders for new files on startup; also sets -e2d",
164
166
  "e2dsa": "scans all folders for new files on startup; also sets -e2d",
165
167
  "e2t": "enable multimedia indexing; makes it possible to search for tags",
@@ -177,7 +179,7 @@ flagcats = {
177
179
  "noforget": "don't forget files when deleted from disk",
178
180
  "fat32": "avoid excessive reindexing on android sdcardfs",
179
181
  "dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
180
- "xlink": "cross-volume dupe detection / linking",
182
+ "xlink": "cross-volume dupe detection / linking (dangerous)",
181
183
  "xdev": "do not descend into other filesystems",
182
184
  "xvol": "do not follow symlinks leaving the volume root",
183
185
  "dotsrch": "show dotfiles in search results",
copyparty/httpcli.py CHANGED
@@ -201,7 +201,8 @@ class HttpCli(object):
201
201
 
202
202
  def unpwd(self, m ) :
203
203
  a, b, c = m.groups()
204
- return "%s\033[7m %s \033[27m%s" % (a, self.asrv.iacct[b], c)
204
+ uname = self.asrv.iacct.get(b) or self.asrv.sesa.get(b)
205
+ return "%s\033[7m %s \033[27m%s" % (a, uname, c)
205
206
 
206
207
  def _check_nonfatal(self, ex , post ) :
207
208
  if post:
@@ -500,6 +501,8 @@ class HttpCli(object):
500
501
  zs = base64.b64decode(zb).decode("utf-8")
501
502
  # try "pwd", "x:pwd", "pwd:x"
502
503
  for bauth in [zs] + zs.split(":", 1)[::-1]:
504
+ if bauth in self.asrv.sesa:
505
+ break
503
506
  hpw = self.asrv.ah.hash(bauth)
504
507
  if self.asrv.iacct.get(hpw):
505
508
  break
@@ -561,7 +564,11 @@ class HttpCli(object):
561
564
  self.uname = "*"
562
565
  else:
563
566
  self.pw = uparam.get("pw") or self.headers.get("pw") or bauth or cookie_pw
564
- self.uname = self.asrv.iacct.get(self.asrv.ah.hash(self.pw)) or "*"
567
+ self.uname = (
568
+ self.asrv.sesa.get(self.pw)
569
+ or self.asrv.iacct.get(self.asrv.ah.hash(self.pw))
570
+ or "*"
571
+ )
565
572
 
566
573
  self.rvol = self.asrv.vfs.aread[self.uname]
567
574
  self.wvol = self.asrv.vfs.awrite[self.uname]
@@ -2084,6 +2091,9 @@ class HttpCli(object):
2084
2091
  if act == "chpw":
2085
2092
  return self.handle_chpw()
2086
2093
 
2094
+ if act == "logout":
2095
+ return self.handle_logout()
2096
+
2087
2097
  raise Pebkac(422, 'invalid action "{}"'.format(act))
2088
2098
 
2089
2099
  def handle_zip_post(self) :
@@ -2405,7 +2415,8 @@ class HttpCli(object):
2405
2415
  msg = "new password OK"
2406
2416
 
2407
2417
  redir = (self.args.SRS + "?h") if ok else ""
2408
- html = self.j2s("msg", h1=msg, h2='<a href="/?h">ack</a>', redir=redir)
2418
+ h2 = '<a href="' + self.args.SRS + '?h">ack</a>'
2419
+ html = self.j2s("msg", h1=msg, h2=h2, redir=redir)
2409
2420
  self.reply(html.encode("utf-8"))
2410
2421
  return True
2411
2422
 
@@ -2418,9 +2429,8 @@ class HttpCli(object):
2418
2429
  uhash = ""
2419
2430
  self.parser.drop()
2420
2431
 
2421
- self.out_headerlist = [
2422
- x for x in self.out_headerlist if x[0] != "Set-Cookie" or "cppw" != x[1][:4]
2423
- ]
2432
+ if not pwd:
2433
+ raise Pebkac(422, "password cannot be blank")
2424
2434
 
2425
2435
  dst = self.args.SRS
2426
2436
  if self.vpath:
@@ -2438,9 +2448,27 @@ class HttpCli(object):
2438
2448
  self.reply(html.encode("utf-8"))
2439
2449
  return True
2440
2450
 
2451
+ def handle_logout(self) :
2452
+ assert self.parser
2453
+ self.parser.drop()
2454
+
2455
+ self.log("logout " + self.uname)
2456
+ self.asrv.forget_session(self.conn.hsrv.broker, self.uname)
2457
+ self.get_pwd_cookie("x")
2458
+
2459
+ dst = self.args.SRS + "?h"
2460
+ h2 = '<a href="' + dst + '">ack</a>'
2461
+ html = self.j2s("msg", h1="ok bye", h2=h2, redir=dst)
2462
+ self.reply(html.encode("utf-8"))
2463
+ return True
2464
+
2441
2465
  def get_pwd_cookie(self, pwd ) :
2442
- hpwd = self.asrv.ah.hash(pwd)
2443
- uname = self.asrv.iacct.get(hpwd)
2466
+ uname = self.asrv.sesa.get(pwd)
2467
+ if not uname:
2468
+ hpwd = self.asrv.ah.hash(pwd)
2469
+ uname = self.asrv.iacct.get(hpwd)
2470
+ if uname:
2471
+ pwd = self.asrv.ases.get(uname) or pwd
2444
2472
  if uname:
2445
2473
  msg = "hi " + uname
2446
2474
  dur = int(60 * 60 * self.args.logout)
@@ -2452,8 +2480,9 @@ class HttpCli(object):
2452
2480
  zb = hashlib.sha512(pwd.encode("utf-8", "replace")).digest()
2453
2481
  logpwd = "%" + base64.b64encode(zb[:12]).decode("utf-8")
2454
2482
 
2455
- self.log("invalid password: {}".format(logpwd), 3)
2456
- self.cbonk(self.conn.hsrv.gpwd, pwd, "pw", "invalid passwords")
2483
+ if pwd != "x":
2484
+ self.log("invalid password: {}".format(logpwd), 3)
2485
+ self.cbonk(self.conn.hsrv.gpwd, pwd, "pw", "invalid passwords")
2457
2486
 
2458
2487
  msg = "naw dude"
2459
2488
  pwd = "x" # nosec
@@ -2465,10 +2494,11 @@ class HttpCli(object):
2465
2494
  for k in ("cppwd", "cppws") if self.is_https else ("cppwd",):
2466
2495
  ck = gencookie(k, pwd, self.args.R, False)
2467
2496
  self.out_headerlist.append(("Set-Cookie", ck))
2497
+ self.out_headers.pop("Set-Cookie", None) # drop keepalive
2468
2498
  else:
2469
2499
  k = "cppws" if self.is_https else "cppwd"
2470
2500
  ck = gencookie(k, pwd, self.args.R, self.is_https, dur, "; HttpOnly")
2471
- self.out_headerlist.append(("Set-Cookie", ck))
2501
+ self.out_headers["Set-Cookie"] = ck
2472
2502
 
2473
2503
  return dur > 0, msg
2474
2504