copyparty 1.15.2__py3-none-any.whl → 1.15.4__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/web/a/u2c.py CHANGED
@@ -1,34 +1,35 @@
1
1
  #!/usr/bin/env python3
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
- S_VERSION = "1.24"
5
- S_BUILD_DT = "2024-09-05"
4
+ S_VERSION = "2.1"
5
+ S_BUILD_DT = "2024-09-23"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
9
9
  2021, ed <irc.rizon.net>, MIT-Licensed
10
10
  https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py
11
11
 
12
- - dependencies: requests
12
+ - dependencies: no
13
13
  - supports python 2.6, 2.7, and 3.3 through 3.12
14
14
  - if something breaks just try again and it'll autoresume
15
15
  """
16
16
 
17
- import re
18
- import os
19
- import sys
20
- import stat
21
- import math
22
- import time
23
- import json
24
17
  import atexit
25
- import signal
26
- import socket
27
18
  import base64
19
+ import binascii
20
+ import datetime
28
21
  import hashlib
22
+ import json
23
+ import math
24
+ import os
29
25
  import platform
26
+ import re
27
+ import signal
28
+ import socket
29
+ import stat
30
+ import sys
30
31
  import threading
31
- import datetime
32
+ import time
32
33
 
33
34
  EXE = bool(getattr(sys, "frozen", False))
34
35
 
@@ -39,32 +40,12 @@ except:
39
40
  print(m)
40
41
  raise
41
42
 
42
- try:
43
- import requests
44
43
 
45
- req_ses = requests.Session()
46
- except ImportError as ex:
47
- if "-" in sys.argv or "-h" in sys.argv:
48
- m = ""
49
- elif EXE:
50
- raise
51
- elif sys.version_info > (2, 7):
52
- m = "\nERROR: need 'requests'{0}; please run this command:\n {1} -m pip install --user requests\n"
53
- else:
54
- m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
55
- m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
56
- m = "\n ERROR: need these{0}:\n" + "\n".join(m) + "\n"
57
- m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n"
58
-
59
- if m:
60
- t = " when not running with '-h' or url '-'"
61
- print(m.format(t, sys.executable), "\nspecifically,", ex)
62
- sys.exit(1)
63
-
64
-
65
- # from copyparty/__init__.py
66
44
  PY2 = sys.version_info < (3,)
45
+ PY27 = sys.version_info > (2, 7) and PY2
46
+ PY37 = sys.version_info > (3, 7)
67
47
  if PY2:
48
+ import httplib as http_client
68
49
  from Queue import Queue
69
50
  from urllib import quote, unquote
70
51
  from urlparse import urlsplit, urlunsplit
@@ -72,11 +53,13 @@ if PY2:
72
53
  sys.dont_write_bytecode = True
73
54
  bytes = str
74
55
  else:
75
- from queue import Queue
76
- from urllib.parse import unquote_to_bytes as unquote
77
56
  from urllib.parse import quote_from_bytes as quote
57
+ from urllib.parse import unquote_to_bytes as unquote
78
58
  from urllib.parse import urlsplit, urlunsplit
79
59
 
60
+ import http.client as http_client
61
+ from queue import Queue
62
+
80
63
  unicode = str
81
64
 
82
65
  VT100 = platform.system() != "Windows"
@@ -100,6 +83,22 @@ except:
100
83
  UTC = _UTC()
101
84
 
102
85
 
86
+ try:
87
+ _b64etl = bytes.maketrans(b"+/", b"-_")
88
+
89
+ def ub64enc(bs):
90
+ x = binascii.b2a_base64(bs, newline=False)
91
+ return x.translate(_b64etl)
92
+
93
+ ub64enc(b"a")
94
+ except:
95
+ ub64enc = base64.urlsafe_b64encode
96
+
97
+
98
+ class BadAuth(Exception):
99
+ pass
100
+
101
+
103
102
  class Daemon(threading.Thread):
104
103
  def __init__(self, target, name=None, a=None):
105
104
  threading.Thread.__init__(self, name=name)
@@ -117,6 +116,108 @@ class Daemon(threading.Thread):
117
116
  self.fun(*self.a)
118
117
 
119
118
 
119
+ class HSQueue(Queue):
120
+ def _init(self, maxsize):
121
+ from collections import deque
122
+
123
+ self.q = deque()
124
+
125
+ def _qsize(self):
126
+ return len(self.q)
127
+
128
+ def _put(self, item):
129
+ if item and item.nhs:
130
+ self.q.appendleft(item)
131
+ else:
132
+ self.q.append(item)
133
+
134
+ def _get(self):
135
+ return self.q.popleft()
136
+
137
+
138
+ class HCli(object):
139
+ def __init__(self, ar):
140
+ self.ar = ar
141
+ url = urlsplit(ar.url)
142
+ tls = url.scheme.lower() == "https"
143
+ try:
144
+ addr, port = url.netloc.split(":")
145
+ except:
146
+ addr = url.netloc
147
+ port = 443 if tls else 80
148
+
149
+ self.addr = addr
150
+ self.port = int(port)
151
+ self.tls = tls
152
+ self.verify = ar.te or not ar.td
153
+ self.conns = []
154
+ if tls:
155
+ import ssl
156
+
157
+ if not self.verify:
158
+ self.ctx = ssl._create_unverified_context()
159
+ elif self.verify is True:
160
+ self.ctx = None
161
+ else:
162
+ self.ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
163
+ self.ctx.load_verify_locations(self.verify)
164
+
165
+ self.base_hdrs = {
166
+ "Accept": "*/*",
167
+ "Connection": "keep-alive",
168
+ "Host": url.netloc,
169
+ "Origin": self.ar.burl,
170
+ "User-Agent": "u2c/%s" % (S_VERSION,),
171
+ }
172
+
173
+ def _connect(self):
174
+ args = {}
175
+ if PY37:
176
+ args["blocksize"] = 1048576
177
+
178
+ if not self.tls:
179
+ C = http_client.HTTPConnection
180
+ else:
181
+ C = http_client.HTTPSConnection
182
+ if self.ctx:
183
+ args = {"context": self.ctx}
184
+
185
+ return C(self.addr, self.port, timeout=999, **args)
186
+
187
+ def req(self, meth, vpath, hdrs, body=None, ctype=None):
188
+ hdrs.update(self.base_hdrs)
189
+ if self.ar.a:
190
+ hdrs["PW"] = self.ar.a
191
+ if ctype:
192
+ hdrs["Content-Type"] = ctype
193
+ if meth == "POST" and CLEN not in hdrs:
194
+ hdrs[CLEN] = (
195
+ 0 if not body else body.len if hasattr(body, "len") else len(body)
196
+ )
197
+
198
+ c = self.conns.pop() if self.conns else self._connect()
199
+ try:
200
+ c.request(meth, vpath, body, hdrs)
201
+ if PY27:
202
+ rsp = c.getresponse(buffering=True)
203
+ else:
204
+ rsp = c.getresponse()
205
+
206
+ data = rsp.read()
207
+ self.conns.append(c)
208
+ return rsp.status, data.decode("utf-8")
209
+ except:
210
+ c.close()
211
+ raise
212
+
213
+
214
+ MJ = "application/json"
215
+ MO = "application/octet-stream"
216
+ CLEN = "Content-Length"
217
+
218
+ web = None # type: HCli
219
+
220
+
120
221
  class File(object):
121
222
  """an up2k upload task; represents a single file"""
122
223
 
@@ -149,9 +250,6 @@ class File(object):
149
250
  self.up_c = 0 # type: int
150
251
  self.cd = 0 # type: int
151
252
 
152
- # t = "size({}) lmod({}) top({}) rel({}) abs({}) name({})\n"
153
- # eprint(t.format(self.size, self.lmod, self.top, self.rel, self.abs, self.name))
154
-
155
253
 
156
254
  class FileSlice(object):
157
255
  """file-like object providing a fixed window into a file"""
@@ -284,8 +382,7 @@ class MTHash(object):
284
382
  chunk_rem -= len(buf)
285
383
  ofs += len(buf)
286
384
 
287
- digest = hashobj.digest()[:33]
288
- digest = base64.urlsafe_b64encode(digest).decode("utf-8")
385
+ digest = ub64enc(hashobj.digest()[:33]).decode("utf-8")
289
386
  return nch, digest, ofs0, chunk_sz
290
387
 
291
388
 
@@ -329,7 +426,9 @@ def termsize():
329
426
 
330
427
  def ioctl_GWINSZ(fd):
331
428
  try:
332
- import fcntl, termios, struct
429
+ import fcntl
430
+ import struct
431
+ import termios
333
432
 
334
433
  r = struct.unpack(b"hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, b"AAAA"))
335
434
  return r[::-1]
@@ -387,8 +486,8 @@ class CTermsize(object):
387
486
  eprint("\033[s\033[r\033[u")
388
487
  else:
389
488
  self.g = 1 + self.h - margin
390
- t = "{0}\033[{1}A".format("\n" * margin, margin)
391
- eprint("{0}\033[s\033[1;{1}r\033[u".format(t, self.g - 1))
489
+ t = "%s\033[%dA" % ("\n" * margin, margin)
490
+ eprint("%s\033[s\033[1;%dr\033[u" % (t, self.g - 1))
392
491
 
393
492
 
394
493
  ss = CTermsize()
@@ -405,14 +504,14 @@ def undns(url):
405
504
  except KeyboardInterrupt:
406
505
  raise
407
506
  except:
408
- t = "\n\033[31mfailed to resolve upload destination host;\033[0m\ngai={0}\n"
409
- eprint(t.format(repr(gai)))
507
+ t = "\n\033[31mfailed to resolve upload destination host;\033[0m\ngai=%r\n"
508
+ eprint(t % (gai,))
410
509
  raise
411
510
 
412
511
  if usp.port:
413
- hn = "{0}:{1}".format(hn, usp.port)
512
+ hn = "%s:%s" % (hn, usp.port)
414
513
  if usp.username or usp.password:
415
- hn = "{0}:{1}@{2}".format(usp.username, usp.password, hn)
514
+ hn = "%s:%s@%s" % (usp.username, usp.password, hn)
416
515
 
417
516
  usp = usp._replace(netloc=hn)
418
517
  url = urlunsplit(usp)
@@ -447,7 +546,7 @@ else:
447
546
  statdir = _lsd
448
547
 
449
548
 
450
- def walkdir(err, top, seen):
549
+ def walkdir(err, top, excl, seen):
451
550
  """recursive statdir"""
452
551
  atop = os.path.abspath(os.path.realpath(top))
453
552
  if atop in seen:
@@ -456,10 +555,12 @@ def walkdir(err, top, seen):
456
555
 
457
556
  seen = seen[:] + [atop]
458
557
  for ap, inf in sorted(statdir(err, top)):
558
+ if excl.match(ap):
559
+ continue
459
560
  yield ap, inf
460
561
  if stat.S_ISDIR(inf.st_mode):
461
562
  try:
462
- for x in walkdir(err, ap, seen):
563
+ for x in walkdir(err, ap, excl, seen):
463
564
  yield x
464
565
  except Exception as ex:
465
566
  err.append((ap, str(ex)))
@@ -499,9 +600,7 @@ def walkdirs(err, tops, excl):
499
600
  yield stop, dn, os.stat(stop)
500
601
 
501
602
  if isdir:
502
- for ap, inf in walkdir(err, top, []):
503
- if ptn.match(ap):
504
- continue
603
+ for ap, inf in walkdir(err, top, ptn, []):
505
604
  yield stop, ap[len(stop) :].lstrip(sep), inf
506
605
  else:
507
606
  d, n = top.rsplit(sep, 1)
@@ -577,8 +676,7 @@ def get_hashlist(file, pcb, mth):
577
676
  hashobj.update(buf)
578
677
  chunk_rem -= len(buf)
579
678
 
580
- digest = hashobj.digest()[:33]
581
- digest = base64.urlsafe_b64encode(digest).decode("utf-8")
679
+ digest = ub64enc(hashobj.digest()[:33]).decode("utf-8")
582
680
 
583
681
  ret.append([digest, file_ofs, chunk_sz])
584
682
  file_ofs += chunk_sz
@@ -603,9 +701,6 @@ def handshake(ar, file, search):
603
701
  otherwise, a list of chunks to upload
604
702
  """
605
703
 
606
- url = ar.url
607
- pw = ar.a
608
-
609
704
  req = {
610
705
  "hash": [x[0] for x in file.cids],
611
706
  "name": file.name,
@@ -620,28 +715,26 @@ def handshake(ar, file, search):
620
715
  if ar.ow:
621
716
  req["replace"] = True
622
717
 
623
- headers = {"Content-Type": "text/plain"} # <=1.5.1 compat
624
- if pw:
625
- headers["Cookie"] = "=".join(["cppwd", pw])
626
-
627
718
  file.recheck = False
628
719
  if file.url:
629
720
  url = file.url
630
- elif b"/" in file.rel:
631
- url += quotep(file.rel.rsplit(b"/", 1)[0]).decode("utf-8", "replace")
721
+ else:
722
+ if b"/" in file.rel:
723
+ url = quotep(file.rel.rsplit(b"/", 1)[0]).decode("utf-8", "replace")
724
+ else:
725
+ url = ""
726
+ url = ar.vtop + url
632
727
 
633
728
  while True:
634
729
  sc = 600
635
730
  txt = ""
636
731
  try:
637
732
  zs = json.dumps(req, separators=(",\n", ": "))
638
- r = req_ses.post(url, headers=headers, data=zs)
639
- sc = r.status_code
640
- txt = r.text
733
+ sc, txt = web.req("POST", url, {}, zs.encode("utf-8"), MJ)
641
734
  if sc < 400:
642
735
  break
643
736
 
644
- raise Exception("http {0}: {1}".format(sc, txt))
737
+ raise Exception("http %d: %s" % (sc, txt))
645
738
 
646
739
  except Exception as ex:
647
740
  em = str(ex).split("SSLError(")[-1].split("\nURL: ")[0].strip()
@@ -655,35 +748,30 @@ def handshake(ar, file, search):
655
748
  return [], False
656
749
  elif sc == 409 or "<pre>upload rejected, file already exists" in txt:
657
750
  return [], False
658
- elif "<pre>you don't have " in txt:
659
- raise
751
+ elif sc == 403:
752
+ print("\nERROR: login required, or wrong password:\n%s" % (txt,))
753
+ raise BadAuth()
660
754
 
661
- eprint("handshake failed, retrying: {0}\n {1}\n\n".format(file.name, em))
755
+ eprint("handshake failed, retrying: %s\n %s\n\n" % (file.name, em))
662
756
  time.sleep(ar.cd)
663
757
 
664
758
  try:
665
- r = r.json()
759
+ r = json.loads(txt)
666
760
  except:
667
- raise Exception(r.text)
761
+ raise Exception(txt)
668
762
 
669
763
  if search:
670
764
  return r["hits"], False
671
765
 
672
- try:
673
- pre, url = url.split("://")
674
- pre += "://"
675
- except:
676
- pre = ""
677
-
678
- file.url = pre + url.split("/")[0] + r["purl"]
766
+ file.url = r["purl"]
679
767
  file.name = r["name"]
680
768
  file.wark = r["wark"]
681
769
 
682
770
  return r["hash"], r["sprs"]
683
771
 
684
772
 
685
- def upload(fsl, pw, stats):
686
- # type: (FileSlice, str, str) -> None
773
+ def upload(fsl, stats):
774
+ # type: (FileSlice, str) -> None
687
775
  """upload a range of file data, defined by one or more `cid` (chunk-hash)"""
688
776
 
689
777
  ctxt = fsl.cids[0]
@@ -696,20 +784,15 @@ def upload(fsl, pw, stats):
696
784
  headers = {
697
785
  "X-Up2k-Hash": ctxt,
698
786
  "X-Up2k-Wark": fsl.file.wark,
699
- "Content-Type": "application/octet-stream",
700
787
  }
701
788
 
702
789
  if stats:
703
790
  headers["X-Up2k-Stat"] = stats
704
791
 
705
- if pw:
706
- headers["Cookie"] = "=".join(["cppwd", pw])
707
-
708
792
  try:
709
- r = req_ses.post(fsl.file.url, headers=headers, data=fsl)
793
+ sc, txt = web.req("POST", fsl.file.url, headers, fsl, MO)
710
794
 
711
- if r.status_code == 400:
712
- txt = r.text
795
+ if sc == 400:
713
796
  if (
714
797
  "already being written" in txt
715
798
  or "already got that" in txt
@@ -717,10 +800,8 @@ def upload(fsl, pw, stats):
717
800
  ):
718
801
  fsl.file.nojoin = 1
719
802
 
720
- if not r:
721
- raise Exception(repr(r))
722
-
723
- _ = r.content
803
+ if sc >= 400:
804
+ raise Exception("http %s: %s" % (sc, txt))
724
805
  finally:
725
806
  fsl.f.close()
726
807
 
@@ -733,7 +814,7 @@ class Ctl(object):
733
814
 
734
815
  def _scan(self):
735
816
  ar = self.ar
736
- eprint("\nscanning {0} locations\n".format(len(ar.files)))
817
+ eprint("\nscanning %d locations\n" % (len(ar.files),))
737
818
  nfiles = 0
738
819
  nbytes = 0
739
820
  err = []
@@ -745,14 +826,14 @@ class Ctl(object):
745
826
  nbytes += inf.st_size
746
827
 
747
828
  if err:
748
- eprint("\n# failed to access {0} paths:\n".format(len(err)))
829
+ eprint("\n# failed to access %d paths:\n" % (len(err),))
749
830
  for ap, msg in err:
750
831
  if ar.v:
751
- eprint("{0}\n `-{1}\n\n".format(ap.decode("utf-8", "replace"), msg))
832
+ eprint("%s\n `-%s\n\n" % (ap.decode("utf-8", "replace"), msg))
752
833
  else:
753
834
  eprint(ap.decode("utf-8", "replace") + "\n")
754
835
 
755
- eprint("^ failed to access those {0} paths ^\n\n".format(len(err)))
836
+ eprint("^ failed to access those %d paths ^\n\n" % (len(err),))
756
837
 
757
838
  if not ar.v:
758
839
  eprint("hint: set -v for detailed error messages\n")
@@ -761,11 +842,12 @@ class Ctl(object):
761
842
  eprint("hint: aborting because --ok is not set\n")
762
843
  return
763
844
 
764
- eprint("found {0} files, {1}\n\n".format(nfiles, humansize(nbytes)))
845
+ eprint("found %d files, %s\n\n" % (nfiles, humansize(nbytes)))
765
846
  return nfiles, nbytes
766
847
 
767
848
  def __init__(self, ar, stats=None):
768
849
  self.ok = False
850
+ self.panik = 0
769
851
  self.errs = 0
770
852
  self.ar = ar
771
853
  self.stats = stats or self._scan()
@@ -773,13 +855,6 @@ class Ctl(object):
773
855
  return
774
856
 
775
857
  self.nfiles, self.nbytes = self.stats
776
-
777
- if ar.td:
778
- requests.packages.urllib3.disable_warnings()
779
- req_ses.verify = False
780
- if ar.te:
781
- req_ses.verify = ar.te
782
-
783
858
  self.filegen = walkdirs([], ar.files, ar.x)
784
859
  self.recheck = [] # type: list[File]
785
860
 
@@ -808,7 +883,7 @@ class Ctl(object):
808
883
  self.exit_cond = threading.Condition()
809
884
  self.uploader_alive = ar.j
810
885
  self.handshaker_alive = ar.j
811
- self.q_handshake = Queue() # type: Queue[File]
886
+ self.q_handshake = HSQueue() # type: Queue[File]
812
887
  self.q_upload = Queue() # type: Queue[FileSlice]
813
888
 
814
889
  self.st_hash = [None, "(idle, starting...)"] # type: tuple[File, int]
@@ -823,24 +898,29 @@ class Ctl(object):
823
898
  def _safe(self):
824
899
  """minimal basic slow boring fallback codepath"""
825
900
  search = self.ar.s
826
- for nf, (top, rel, inf) in enumerate(self.filegen):
901
+ nf = 0
902
+ for top, rel, inf in self.filegen:
827
903
  if stat.S_ISDIR(inf.st_mode) or not rel:
828
904
  continue
829
905
 
906
+ nf += 1
830
907
  file = File(top, rel, inf.st_size, inf.st_mtime)
831
908
  upath = file.abs.decode("utf-8", "replace")
832
909
 
833
- print("{0} {1}\n hash...".format(self.nfiles - nf, upath))
910
+ print("%d %s\n hash..." % (self.nfiles - nf, upath))
834
911
  get_hashlist(file, None, None)
835
912
 
836
- burl = self.ar.url[:12] + self.ar.url[8:].split("/")[0] + "/"
837
913
  while True:
838
914
  print(" hs...")
839
- hs, _ = handshake(self.ar, file, search)
915
+ try:
916
+ hs, _ = handshake(self.ar, file, search)
917
+ except BadAuth:
918
+ sys.exit(1)
919
+
840
920
  if search:
841
921
  if hs:
842
922
  for hit in hs:
843
- print(" found: {0}{1}".format(burl, hit["rp"]))
923
+ print(" found: %s/%s" % (self.ar.burl, hit["rp"]))
844
924
  else:
845
925
  print(" NOT found")
846
926
  break
@@ -849,13 +929,13 @@ class Ctl(object):
849
929
  if not hs:
850
930
  break
851
931
 
852
- print("{0} {1}".format(self.nfiles - nf, upath))
932
+ print("%d %s" % (self.nfiles - nf, upath))
853
933
  ncs = len(hs)
854
934
  for nc, cid in enumerate(hs):
855
- print(" {0} up {1}".format(ncs - nc, cid))
856
- stats = "{0}/0/0/{1}".format(nf, self.nfiles - nf)
935
+ print(" %d up %s" % (ncs - nc, cid))
936
+ stats = "%d/0/0/%d" % (nf, self.nfiles - nf)
857
937
  fslice = FileSlice(file, [cid])
858
- upload(fslice, self.ar.a, stats)
938
+ upload(fslice, stats)
859
939
 
860
940
  print(" ok!")
861
941
  if file.recheck:
@@ -866,7 +946,7 @@ class Ctl(object):
866
946
 
867
947
  eprint("finalizing %d duplicate files\n" % (len(self.recheck),))
868
948
  for file in self.recheck:
869
- handshake(self.ar, file, search)
949
+ handshake(self.ar, file, False)
870
950
 
871
951
  def _fancy(self):
872
952
  if VT100 and not self.ar.ns:
@@ -881,6 +961,8 @@ class Ctl(object):
881
961
  while True:
882
962
  with self.exit_cond:
883
963
  self.exit_cond.wait(0.07)
964
+ if self.panik:
965
+ sys.exit(1)
884
966
  with self.mutex:
885
967
  if not self.handshaker_alive and not self.uploader_alive:
886
968
  break
@@ -889,15 +971,15 @@ class Ctl(object):
889
971
 
890
972
  if VT100 and not self.ar.ns:
891
973
  maxlen = ss.w - len(str(self.nfiles)) - 14
892
- txt = "\033[s\033[{0}H".format(ss.g)
974
+ txt = "\033[s\033[%dH" % (ss.g,)
893
975
  for y, k, st, f in [
894
976
  [0, "hash", st_hash, self.hash_f],
895
977
  [1, "send", st_up, self.up_f],
896
978
  ]:
897
- txt += "\033[{0}H{1}:".format(ss.g + y, k)
979
+ txt += "\033[%dH%s:" % (ss.g + y, k)
898
980
  file, arg = st
899
981
  if not file:
900
- txt += " {0}\033[K".format(arg)
982
+ txt += " %s\033[K" % (arg,)
901
983
  else:
902
984
  if y:
903
985
  p = 100 * file.up_b / file.size
@@ -906,12 +988,11 @@ class Ctl(object):
906
988
 
907
989
  name = file.abs.decode("utf-8", "replace")[-maxlen:]
908
990
  if "/" in name:
909
- name = "\033[36m{0}\033[0m/{1}".format(*name.rsplit("/", 1))
991
+ name = "\033[36m%s\033[0m/%s" % tuple(name.rsplit("/", 1))
910
992
 
911
- t = "{0:6.1f}% {1} {2}\033[K"
912
- txt += t.format(p, self.nfiles - f, name)
993
+ txt += "%6.1f%% %d %s\033[K" % (p, self.nfiles - f, name)
913
994
 
914
- txt += "\033[{0}H ".format(ss.g + 2)
995
+ txt += "\033[%dH " % (ss.g + 2,)
915
996
  else:
916
997
  txt = " "
917
998
 
@@ -929,7 +1010,7 @@ class Ctl(object):
929
1010
  nleft = self.nfiles - self.up_f
930
1011
  tail = "\033[K\033[u" if VT100 and not self.ar.ns else "\r"
931
1012
 
932
- t = "{0} eta @ {1}/s, {2}, {3}# left".format(self.eta, spd, sleft, nleft)
1013
+ t = "%s eta @ %s/s, %s, %d# left\033[K" % (self.eta, spd, sleft, nleft)
933
1014
  eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail))
934
1015
 
935
1016
  if self.hash_b and self.at_hash:
@@ -965,20 +1046,18 @@ class Ctl(object):
965
1046
  srd = rd.decode("utf-8", "replace").replace("\\", "/")
966
1047
  if prd != rd:
967
1048
  prd = rd
968
- headers = {}
969
- if self.ar.a:
970
- headers["Cookie"] = "=".join(["cppwd", self.ar.a])
971
-
972
1049
  ls = {}
973
1050
  try:
974
1051
  print(" ls ~{0}".format(srd))
975
- zb = self.ar.url.encode("utf-8")
976
- zb += quotep(rd.replace(b"\\", b"/"))
977
- r = req_ses.get(zb + b"?ls&lt&dots", headers=headers)
978
- if not r:
979
- raise Exception("HTTP {0}".format(r.status_code))
1052
+ zt = (
1053
+ self.ar.vtop,
1054
+ quotep(rd.replace(b"\\", b"/")).decode("utf-8", "replace"),
1055
+ )
1056
+ sc, txt = web.req("GET", "%s%s?ls&lt&dots" % zt, {})
1057
+ if sc >= 400:
1058
+ raise Exception("http %s" % (sc,))
980
1059
 
981
- j = r.json()
1060
+ j = json.loads(txt)
982
1061
  for f in j["dirs"] + j["files"]:
983
1062
  rfn = f["href"].split("?")[0].rstrip("/")
984
1063
  ls[unquote(rfn.encode("utf-8", "replace"))] = f
@@ -1001,14 +1080,17 @@ class Ctl(object):
1001
1080
  req = locs
1002
1081
  while req:
1003
1082
  print("DELETING ~%s/#%s" % (srd, len(req)))
1004
- r = req_ses.post(self.ar.url + "?delete", json=req)
1005
- if r.status_code == 413 and "json 2big" in r.text:
1083
+ body = json.dumps(req).encode("utf-8")
1084
+ sc, txt = web.req(
1085
+ "POST", self.ar.url + "?delete", {}, body, MJ
1086
+ )
1087
+ if sc == 413 and "json 2big" in txt:
1006
1088
  print(" (delete request too big; slicing...)")
1007
1089
  req = req[: len(req) // 2]
1008
1090
  continue
1009
- elif not r:
1010
- t = "delete request failed: %r %s"
1011
- raise Exception(t % (r, r.text))
1091
+ elif sc >= 400:
1092
+ t = "delete request failed: %s %s"
1093
+ raise Exception(t % (sc, txt))
1012
1094
  break
1013
1095
  locs = locs[len(req) :]
1014
1096
 
@@ -1056,7 +1138,7 @@ class Ctl(object):
1056
1138
  if self.ar.wlist:
1057
1139
  zsl = [self.ar.wsalt, str(file.size)] + [x[0] for x in file.kchunks]
1058
1140
  zb = hashlib.sha512("\n".join(zsl).encode("utf-8")).digest()[:33]
1059
- wark = base64.urlsafe_b64encode(zb).decode("utf-8")
1141
+ wark = ub64enc(zb).decode("utf-8")
1060
1142
  vp = file.rel.decode("utf-8")
1061
1143
  if self.ar.jw:
1062
1144
  print("%s %s" % (wark, vp))
@@ -1087,7 +1169,6 @@ class Ctl(object):
1087
1169
 
1088
1170
  def handshaker(self):
1089
1171
  search = self.ar.s
1090
- burl = self.ar.url[:8] + self.ar.url[8:].split("/")[0] + "/"
1091
1172
  while True:
1092
1173
  file = self.q_handshake.get()
1093
1174
  if not file:
@@ -1109,12 +1190,16 @@ class Ctl(object):
1109
1190
  while time.time() < file.cd:
1110
1191
  time.sleep(0.1)
1111
1192
 
1112
- hs, sprs = handshake(self.ar, file, search)
1193
+ try:
1194
+ hs, sprs = handshake(self.ar, file, search)
1195
+ except BadAuth:
1196
+ self.panik = 1
1197
+ break
1198
+
1113
1199
  if search:
1114
1200
  if hs:
1115
1201
  for hit in hs:
1116
- t = "found: {0}\n {1}{2}"
1117
- print(t.format(upath, burl, hit["rp"]))
1202
+ print("found: %s\n %s/%s" % (upath, self.ar.burl, hit["rp"]))
1118
1203
  else:
1119
1204
  print("NOT found: {0}".format(upath))
1120
1205
 
@@ -1236,7 +1321,7 @@ class Ctl(object):
1236
1321
  )
1237
1322
 
1238
1323
  try:
1239
- upload(fsl, self.ar.a, stats)
1324
+ upload(fsl, stats)
1240
1325
  except Exception as ex:
1241
1326
  t = "upload failed, retrying: %s #%s+%d (%s)\n"
1242
1327
  eprint(t % (file.name, cids[0][:8], len(cids) - 1, ex))
@@ -1270,14 +1355,17 @@ class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFor
1270
1355
 
1271
1356
 
1272
1357
  def main():
1358
+ global web
1359
+
1273
1360
  time.strptime("19970815", "%Y%m%d") # python#7980
1361
+ "".encode("idna") # python#29288
1274
1362
  if not VT100:
1275
1363
  os.system("rem") # enables colors
1276
1364
 
1277
1365
  cores = (os.cpu_count() if hasattr(os, "cpu_count") else 0) or 2
1278
1366
  hcores = min(cores, 3) # 4% faster than 4+ on py3.9 @ r5-4500U
1279
1367
 
1280
- ver = "{0}, v{1}".format(S_BUILD_DT, S_VERSION)
1368
+ ver = "{0} v{1} https://youtu.be/BIcOO6TLKaY".format(S_BUILD_DT, S_VERSION)
1281
1369
  if "--version" in sys.argv:
1282
1370
  print(ver)
1283
1371
  return
@@ -1285,7 +1373,7 @@ def main():
1285
1373
  sys.argv = [x for x in sys.argv if x != "--ws"]
1286
1374
 
1287
1375
  # fmt: off
1288
- ap = app = argparse.ArgumentParser(formatter_class=APF, description="copyparty up2k uploader / filesearch tool, " + ver, epilog="""
1376
+ ap = app = argparse.ArgumentParser(formatter_class=APF, description="copyparty up2k uploader / filesearch tool " + ver, epilog="""
1289
1377
  NOTE:
1290
1378
  source file/folder selection uses rsync syntax, meaning that:
1291
1379
  "foo" uploads the entire folder to URL/foo/
@@ -1366,13 +1454,20 @@ source file/folder selection uses rsync syntax, meaning that:
1366
1454
  for x in ar.files
1367
1455
  ]
1368
1456
 
1369
- ar.url = ar.url.rstrip("/") + "/"
1370
- if "://" not in ar.url:
1371
- ar.url = "http://" + ar.url
1457
+ # urlsplit needs scheme;
1458
+ zs = ar.url.rstrip("/") + "/"
1459
+ if "://" not in zs:
1460
+ zs = "http://" + zs
1461
+ ar.url = zs
1462
+
1463
+ url = urlsplit(zs)
1464
+ ar.burl = "%s://%s" % (url.scheme, url.netloc)
1465
+ ar.vtop = url.path
1372
1466
 
1373
1467
  if "https://" in ar.url.lower():
1374
1468
  try:
1375
- import ssl, zipfile
1469
+ import ssl
1470
+ import zipfile
1376
1471
  except:
1377
1472
  t = "ERROR: https is not available for some reason; please use http"
1378
1473
  print("\n\n %s\n\n" % (t,))
@@ -1397,6 +1492,7 @@ source file/folder selection uses rsync syntax, meaning that:
1397
1492
  if ar.cls:
1398
1493
  eprint("\033[H\033[2J\033[3J", end="")
1399
1494
 
1495
+ web = HCli(ar)
1400
1496
  ctl = Ctl(ar)
1401
1497
 
1402
1498
  if ar.dr and not ar.drd and ctl.ok: