PyCatFile 0.22.2__py3-none-any.whl → 0.23.0__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.
- {pycatfile-0.22.2.data → pycatfile-0.23.0.data}/scripts/catfile.py +1 -1
- {pycatfile-0.22.2.dist-info → pycatfile-0.23.0.dist-info}/METADATA +1 -1
- pycatfile-0.23.0.dist-info/RECORD +10 -0
- pycatfile.py +1606 -222
- pycatfile-0.22.2.dist-info/RECORD +0 -10
- {pycatfile-0.22.2.data → pycatfile-0.23.0.data}/scripts/catneofile.py +0 -0
- {pycatfile-0.22.2.data → pycatfile-0.23.0.data}/scripts/neocatfile.py +0 -0
- {pycatfile-0.22.2.dist-info → pycatfile-0.23.0.dist-info}/WHEEL +0 -0
- {pycatfile-0.22.2.dist-info → pycatfile-0.23.0.dist-info}/licenses/LICENSE +0 -0
- {pycatfile-0.22.2.dist-info → pycatfile-0.23.0.dist-info}/top_level.txt +0 -0
- {pycatfile-0.22.2.dist-info → pycatfile-0.23.0.dist-info}/zip-safe +0 -0
pycatfile.py
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
Copyright 2018-2024 Game Maker 2k - http://intdb.sourceforge.net/
|
|
15
15
|
Copyright 2018-2024 Kazuki Przyborowski - https://github.com/KazukiPrzyborowski
|
|
16
16
|
|
|
17
|
-
$FileInfo: pycatfile.py - Last Update:
|
|
17
|
+
$FileInfo: pycatfile.py - Last Update: 10/1/2025 Ver. 0.23.0 RC 1 - Author: cooldude2k $
|
|
18
18
|
'''
|
|
19
19
|
|
|
20
20
|
from __future__ import absolute_import, division, print_function, unicode_literals, generators, with_statement, nested_scopes
|
|
@@ -27,8 +27,8 @@ import stat
|
|
|
27
27
|
import zlib
|
|
28
28
|
import base64
|
|
29
29
|
import shutil
|
|
30
|
-
import struct
|
|
31
30
|
import socket
|
|
31
|
+
import struct
|
|
32
32
|
import hashlib
|
|
33
33
|
import inspect
|
|
34
34
|
import datetime
|
|
@@ -79,6 +79,7 @@ try:
|
|
|
79
79
|
except NameError:
|
|
80
80
|
basestring = str
|
|
81
81
|
|
|
82
|
+
PY2 = (sys.version_info[0] == 2)
|
|
82
83
|
try:
|
|
83
84
|
unicode # Py2
|
|
84
85
|
except NameError: # Py3
|
|
@@ -271,8 +272,8 @@ def get_default_threads():
|
|
|
271
272
|
|
|
272
273
|
|
|
273
274
|
__use_pysftp__ = False
|
|
274
|
-
__upload_proto_support__ = "^(ftp|ftps|sftp|scp)://"
|
|
275
|
-
__download_proto_support__ = "^(http|https|ftp|ftps|sftp|scp)://"
|
|
275
|
+
__upload_proto_support__ = "^(ftp|ftps|sftp|scp|tcp|udp)://"
|
|
276
|
+
__download_proto_support__ = "^(http|https|ftp|ftps|sftp|scp|tcp|udp)://"
|
|
276
277
|
if(not havepysftp):
|
|
277
278
|
__use_pysftp__ = False
|
|
278
279
|
__use_http_lib__ = "httpx"
|
|
@@ -392,12 +393,12 @@ __file_format_extension__ = __file_format_multi_dict__[__file_format_default__][
|
|
|
392
393
|
__file_format_dict__ = __file_format_multi_dict__[__file_format_default__]
|
|
393
394
|
__project__ = __program_name__
|
|
394
395
|
__project_url__ = "https://github.com/GameMaker2k/PyCatFile"
|
|
395
|
-
__version_info__ = (0,
|
|
396
|
-
__version_date_info__ = (2025,
|
|
396
|
+
__version_info__ = (0, 23, 0, "RC 1", 1)
|
|
397
|
+
__version_date_info__ = (2025, 10, 1, "RC 1", 1)
|
|
397
398
|
__version_date__ = str(__version_date_info__[0]) + "." + str(
|
|
398
399
|
__version_date_info__[1]).zfill(2) + "." + str(__version_date_info__[2]).zfill(2)
|
|
399
400
|
__revision__ = __version_info__[3]
|
|
400
|
-
__revision_id__ = "$Id:
|
|
401
|
+
__revision_id__ = "$Id: c566041792a64657ff1e9e2819f10744433f7a11 $"
|
|
401
402
|
if(__version_info__[4] is not None):
|
|
402
403
|
__version_date_plusrc__ = __version_date__ + \
|
|
403
404
|
"-" + str(__version_date_info__[4])
|
|
@@ -409,15 +410,67 @@ if(__version_info__[3] is not None):
|
|
|
409
410
|
if(__version_info__[3] is None):
|
|
410
411
|
__version__ = str(__version_info__[0]) + "." + str(__version_info__[1]) + "." + str(__version_info__[2])
|
|
411
412
|
|
|
413
|
+
# ===== Module-level type code table & helpers (reuse anywhere) =====
|
|
414
|
+
|
|
415
|
+
FT = {
|
|
416
|
+
"FILE": 0,
|
|
417
|
+
"HARDLINK": 1,
|
|
418
|
+
"SYMLINK": 2,
|
|
419
|
+
"CHAR": 3,
|
|
420
|
+
"BLOCK": 4,
|
|
421
|
+
"DIR": 5,
|
|
422
|
+
"FIFO": 6,
|
|
423
|
+
"CONTAGIOUS": 7, # treated like regular file
|
|
424
|
+
"SOCK": 8,
|
|
425
|
+
"DOOR": 9,
|
|
426
|
+
"PORT": 10,
|
|
427
|
+
"WHT": 11,
|
|
428
|
+
"SPARSE": 12,
|
|
429
|
+
"JUNCTION": 13,
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
BASE_CATEGORY_BY_CODE = {
|
|
433
|
+
0: "files",
|
|
434
|
+
1: "hardlinks",
|
|
435
|
+
2: "symlinks",
|
|
436
|
+
3: "characters",
|
|
437
|
+
4: "blocks",
|
|
438
|
+
5: "directories",
|
|
439
|
+
6: "fifos",
|
|
440
|
+
7: "files", # contagious treated as file
|
|
441
|
+
8: "sockets",
|
|
442
|
+
9: "doors",
|
|
443
|
+
10: "ports",
|
|
444
|
+
11: "whiteouts",
|
|
445
|
+
12: "sparsefiles",
|
|
446
|
+
13: "junctions",
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
# Union categories defined by which base codes should populate them.
|
|
450
|
+
UNION_RULES = [
|
|
451
|
+
("links", set([FT["HARDLINK"], FT["SYMLINK"]])),
|
|
452
|
+
("devices", set([FT["CHAR"], FT["BLOCK"]])),
|
|
453
|
+
]
|
|
454
|
+
|
|
455
|
+
# Deterministic category order (handy for consistent output/printing).
|
|
456
|
+
CATEGORY_ORDER = [
|
|
457
|
+
"files", "hardlinks", "symlinks", "character", "block",
|
|
458
|
+
"directories", "fifo", "sockets", "doors", "ports",
|
|
459
|
+
"whiteouts", "sparsefiles", "junctions", "links", "devices"
|
|
460
|
+
]
|
|
461
|
+
|
|
412
462
|
# Robust bitness detection
|
|
413
463
|
# Works on Py2 & Py3, all platforms
|
|
464
|
+
|
|
465
|
+
# Python interpreter bitness
|
|
466
|
+
PyBitness = "64" if struct.calcsize("P") * 8 == 64 else ("64" if sys.maxsize > 2**32 else "32")
|
|
467
|
+
|
|
468
|
+
# Operating system bitness
|
|
414
469
|
try:
|
|
415
|
-
|
|
416
|
-
PyBitness = "64" if struct.calcsize("P") * 8 == 64 else "32"
|
|
470
|
+
OSBitness = platform.architecture()[0].replace("bit", "")
|
|
417
471
|
except Exception:
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
PyBitness = "64" if m.endswith("64") else "32"
|
|
472
|
+
m = platform.machine().lower()
|
|
473
|
+
OSBitness = "64" if "64" in m else "32"
|
|
421
474
|
|
|
422
475
|
geturls_ua_pyfile_python = "Mozilla/5.0 (compatible; {proname}/{prover}; +{prourl})".format(
|
|
423
476
|
proname=__project__, prover=__version__, prourl=__project_url__)
|
|
@@ -658,6 +711,415 @@ def _resolves_outside(base_rel, target_rel):
|
|
|
658
711
|
return True
|
|
659
712
|
|
|
660
713
|
|
|
714
|
+
def _to_bytes(data):
|
|
715
|
+
if data is None:
|
|
716
|
+
return b""
|
|
717
|
+
if isinstance(data, bytes):
|
|
718
|
+
return data
|
|
719
|
+
if isinstance(data, unicode):
|
|
720
|
+
return data.encode("utf-8")
|
|
721
|
+
try:
|
|
722
|
+
return bytes(data)
|
|
723
|
+
except Exception:
|
|
724
|
+
return (u"%s" % data).encode("utf-8")
|
|
725
|
+
|
|
726
|
+
def _to_text(b):
|
|
727
|
+
if isinstance(b, bytes):
|
|
728
|
+
return b.decode("utf-8", "replace")
|
|
729
|
+
return b
|
|
730
|
+
|
|
731
|
+
# ---------- TLS helpers (TCP only) ----------
|
|
732
|
+
def _ssl_available():
|
|
733
|
+
try:
|
|
734
|
+
import ssl # noqa
|
|
735
|
+
return True
|
|
736
|
+
except Exception:
|
|
737
|
+
return False
|
|
738
|
+
|
|
739
|
+
def _build_ssl_context(server_side=False, verify=True, ca_file=None, certfile=None, keyfile=None):
|
|
740
|
+
import ssl
|
|
741
|
+
create_ctx = getattr(ssl, "create_default_context", None)
|
|
742
|
+
SSLContext = getattr(ssl, "SSLContext", None)
|
|
743
|
+
Purpose = getattr(ssl, "Purpose", None)
|
|
744
|
+
if create_ctx and Purpose:
|
|
745
|
+
ctx = create_ctx(ssl.Purpose.CLIENT_AUTH if server_side else ssl.Purpose.SERVER_AUTH)
|
|
746
|
+
elif SSLContext:
|
|
747
|
+
ctx = SSLContext(getattr(ssl, "PROTOCOL_TLS", getattr(ssl, "PROTOCOL_SSLv23")))
|
|
748
|
+
else:
|
|
749
|
+
return None
|
|
750
|
+
|
|
751
|
+
if hasattr(ctx, "check_hostname") and not server_side:
|
|
752
|
+
ctx.check_hostname = bool(verify)
|
|
753
|
+
|
|
754
|
+
if verify:
|
|
755
|
+
if hasattr(ctx, "verify_mode"):
|
|
756
|
+
ctx.verify_mode = getattr(ssl, "CERT_REQUIRED", 2)
|
|
757
|
+
if ca_file:
|
|
758
|
+
try: ctx.load_verify_locations(cafile=ca_file)
|
|
759
|
+
except Exception: pass
|
|
760
|
+
else:
|
|
761
|
+
load_default_certs = getattr(ctx, "load_default_certs", None)
|
|
762
|
+
if load_default_certs: load_default_certs()
|
|
763
|
+
else:
|
|
764
|
+
if hasattr(ctx, "verify_mode"):
|
|
765
|
+
ctx.verify_mode = getattr(ssl, "CERT_NONE", 0)
|
|
766
|
+
if hasattr(ctx, "check_hostname"):
|
|
767
|
+
ctx.check_hostname = False
|
|
768
|
+
|
|
769
|
+
if certfile:
|
|
770
|
+
ctx.load_cert_chain(certfile=certfile, keyfile=keyfile or None)
|
|
771
|
+
|
|
772
|
+
try:
|
|
773
|
+
ctx.set_ciphers("HIGH:!aNULL:!MD5:!RC4")
|
|
774
|
+
except Exception:
|
|
775
|
+
pass
|
|
776
|
+
return ctx
|
|
777
|
+
|
|
778
|
+
def _ssl_wrap_socket(sock, server_side=False, server_hostname=None,
|
|
779
|
+
verify=True, ca_file=None, certfile=None, keyfile=None):
|
|
780
|
+
import ssl
|
|
781
|
+
ctx = _build_ssl_context(server_side, verify, ca_file, certfile, keyfile)
|
|
782
|
+
if ctx is not None:
|
|
783
|
+
kwargs = {}
|
|
784
|
+
if not server_side and getattr(ssl, "HAS_SNI", False) and server_hostname:
|
|
785
|
+
kwargs["server_hostname"] = server_hostname
|
|
786
|
+
return ctx.wrap_socket(sock, server_side=server_side, **kwargs)
|
|
787
|
+
# Very old Python fallback
|
|
788
|
+
kwargs = {
|
|
789
|
+
"ssl_version": getattr(ssl, "PROTOCOL_TLS", getattr(ssl, "PROTOCOL_SSLv23")),
|
|
790
|
+
"certfile": certfile or None,
|
|
791
|
+
"keyfile": keyfile or None,
|
|
792
|
+
"cert_reqs": (getattr(ssl, "CERT_REQUIRED", 2) if (verify and ca_file) else getattr(ssl, "CERT_NONE", 0)),
|
|
793
|
+
}
|
|
794
|
+
if verify and ca_file:
|
|
795
|
+
kwargs["ca_certs"] = ca_file
|
|
796
|
+
return ssl.wrap_socket(sock, **kwargs)
|
|
797
|
+
|
|
798
|
+
# ---------- IPv6 / multi-A dialer + keepalive ----------
|
|
799
|
+
def _enable_keepalive(s, idle=60, intvl=15, cnt=4):
|
|
800
|
+
try:
|
|
801
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
802
|
+
if hasattr(socket, 'TCP_KEEPIDLE'):
|
|
803
|
+
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, idle)
|
|
804
|
+
if hasattr(socket, 'TCP_KEEPINTVL'):
|
|
805
|
+
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, intvl)
|
|
806
|
+
if hasattr(socket, 'TCP_KEEPCNT'):
|
|
807
|
+
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, cnt)
|
|
808
|
+
except Exception:
|
|
809
|
+
pass
|
|
810
|
+
|
|
811
|
+
def _connect_stream(host, port, timeout):
|
|
812
|
+
err = None
|
|
813
|
+
for fam, st, proto, _, sa in socket.getaddrinfo(host, int(port), 0, socket.SOCK_STREAM):
|
|
814
|
+
try:
|
|
815
|
+
s = socket.socket(fam, st, proto)
|
|
816
|
+
if timeout is not None:
|
|
817
|
+
s.settimeout(timeout)
|
|
818
|
+
try: s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
819
|
+
except Exception: pass
|
|
820
|
+
s.connect(sa)
|
|
821
|
+
_enable_keepalive(s)
|
|
822
|
+
return s
|
|
823
|
+
except Exception as e:
|
|
824
|
+
err = e
|
|
825
|
+
try: s.close()
|
|
826
|
+
except Exception: pass
|
|
827
|
+
if err: raise err
|
|
828
|
+
raise RuntimeError("no usable address")
|
|
829
|
+
|
|
830
|
+
# ---------- Auth: AF1 (HMAC) + legacy fallback ----------
|
|
831
|
+
# AF1: single ASCII line ending with '\n':
|
|
832
|
+
# AF1 ts=<unix> user=<b64url> nonce=<b64url_12B> scope=<b64url> alg=sha256 mac=<hex>\n
|
|
833
|
+
def _b64url_encode(b):
|
|
834
|
+
s = base64.urlsafe_b64encode(b)
|
|
835
|
+
return _to_text(s.rstrip(b'='))
|
|
836
|
+
|
|
837
|
+
def _b64url_decode(s):
|
|
838
|
+
s = _to_bytes(s)
|
|
839
|
+
pad = b'=' * ((4 - (len(s) % 4)) % 4)
|
|
840
|
+
return base64.urlsafe_b64decode(s + pad)
|
|
841
|
+
|
|
842
|
+
def _auth_msg(ts_int, user_utf8, nonce_bytes, scope_utf8, length_str, sha_hex):
|
|
843
|
+
# canonical message for MAC: v1|ts|user|nonce_b64|scope|len|sha
|
|
844
|
+
return _to_bytes("v1|%d|%s|%s|%s|%s|%s" % (
|
|
845
|
+
ts_int,
|
|
846
|
+
_to_text(user_utf8),
|
|
847
|
+
_b64url_encode(nonce_bytes),
|
|
848
|
+
_to_text(scope_utf8),
|
|
849
|
+
length_str if length_str is not None else "",
|
|
850
|
+
sha_hex if sha_hex is not None else "",
|
|
851
|
+
))
|
|
852
|
+
|
|
853
|
+
def build_auth_blob_v1(user, secret, scope=u"", now=None, length=None, sha_hex=None):
|
|
854
|
+
"""
|
|
855
|
+
user: text; secret: text/bytes (HMAC key)
|
|
856
|
+
scope: optional text (e.g., path)
|
|
857
|
+
length: int or None (payload bytes)
|
|
858
|
+
sha_hex: ascii hex SHA-256 of payload (optional)
|
|
859
|
+
"""
|
|
860
|
+
ts = int(time.time() if now is None else now)
|
|
861
|
+
user_b = _to_bytes(user or u"")
|
|
862
|
+
scope_b = _to_bytes(scope or u"")
|
|
863
|
+
key_b = _to_bytes(secret or u"")
|
|
864
|
+
nonce = os.urandom(12)
|
|
865
|
+
|
|
866
|
+
length_str = (str(int(length)) if (length is not None and int(length) >= 0) else "")
|
|
867
|
+
sha_hex = (sha_hex or None)
|
|
868
|
+
mac = hmac.new(
|
|
869
|
+
key_b,
|
|
870
|
+
_auth_msg(ts, user_b, nonce, scope_b, length_str, sha_hex),
|
|
871
|
+
hashlib.sha256
|
|
872
|
+
).hexdigest()
|
|
873
|
+
|
|
874
|
+
line = "AF1 ts=%d user=%s nonce=%s scope=%s len=%s sha=%s alg=sha256 mac=%s\n" % (
|
|
875
|
+
ts,
|
|
876
|
+
_b64url_encode(user_b),
|
|
877
|
+
_b64url_encode(nonce),
|
|
878
|
+
_b64url_encode(scope_b),
|
|
879
|
+
length_str,
|
|
880
|
+
(sha_hex or ""),
|
|
881
|
+
mac,
|
|
882
|
+
)
|
|
883
|
+
return _to_bytes(line)
|
|
884
|
+
|
|
885
|
+
from collections import deque
|
|
886
|
+
class _NonceCache(object):
|
|
887
|
+
def __init__(self, max_items=10000, ttl_seconds=600):
|
|
888
|
+
self.max_items = int(max_items); self.ttl = int(ttl_seconds)
|
|
889
|
+
self.q = deque(); self.s = set()
|
|
890
|
+
def seen(self, nonce_b64, now_ts):
|
|
891
|
+
# evict old / over-capacity
|
|
892
|
+
while self.q and (now_ts - self.q[0][0] > self.ttl or len(self.q) > self.max_items):
|
|
893
|
+
_, n = self.q.popleft(); self.s.discard(n)
|
|
894
|
+
if nonce_b64 in self.s: return True
|
|
895
|
+
self.s.add(nonce_b64); self.q.append((now_ts, nonce_b64))
|
|
896
|
+
return False
|
|
897
|
+
|
|
898
|
+
_NONCES = _NonceCache()
|
|
899
|
+
|
|
900
|
+
def verify_auth_blob_v1(blob_bytes, expected_user=None, secret=None,
|
|
901
|
+
max_skew=600, expect_scope=None):
|
|
902
|
+
"""
|
|
903
|
+
Returns (ok_bool, user_text, scope_text, reason_text, length_or_None, sha_hex_or_None)
|
|
904
|
+
"""
|
|
905
|
+
try:
|
|
906
|
+
line = _to_text(blob_bytes).strip()
|
|
907
|
+
if not line.startswith("AF1 "):
|
|
908
|
+
return (False, None, None, "bad magic", None, None)
|
|
909
|
+
kv = {}
|
|
910
|
+
for tok in line.split()[1:]:
|
|
911
|
+
if '=' in tok:
|
|
912
|
+
k, v = tok.split('=', 1); kv[k] = v
|
|
913
|
+
|
|
914
|
+
for req in ("ts","user","nonce","mac","alg"):
|
|
915
|
+
if req not in kv:
|
|
916
|
+
return (False, None, None, "missing %s" % req, None, None)
|
|
917
|
+
if kv["alg"].lower() != "sha256":
|
|
918
|
+
return (False, None, None, "alg", None, None)
|
|
919
|
+
|
|
920
|
+
ts = int(kv["ts"])
|
|
921
|
+
userb = _b64url_decode(kv["user"])
|
|
922
|
+
nonce_b64 = kv["nonce"]; nonce = _b64url_decode(nonce_b64)
|
|
923
|
+
scopeb = _b64url_decode(kv.get("scope","")) if kv.get("scope") else b""
|
|
924
|
+
length_str = kv.get("len","")
|
|
925
|
+
sha_hex = kv.get("sha","") or None
|
|
926
|
+
mac = kv["mac"]
|
|
927
|
+
|
|
928
|
+
now = int(time.time())
|
|
929
|
+
if abs(now - ts) > int(max_skew):
|
|
930
|
+
return (False, None, None, "skew", None, None)
|
|
931
|
+
|
|
932
|
+
if _NONCES.seen(nonce_b64, now):
|
|
933
|
+
return (False, None, None, "replay", None, None)
|
|
934
|
+
|
|
935
|
+
if expected_user is not None and _to_bytes(expected_user) != userb:
|
|
936
|
+
return (False, None, None, "user", None, None)
|
|
937
|
+
|
|
938
|
+
calc = hmac.new(
|
|
939
|
+
_to_bytes(secret or u""),
|
|
940
|
+
_auth_msg(ts, userb, nonce, scopeb, length_str, sha_hex),
|
|
941
|
+
hashlib.sha256
|
|
942
|
+
).hexdigest()
|
|
943
|
+
if not hmac.compare_digest(calc, mac):
|
|
944
|
+
return (False, None, None, "mac", None, None)
|
|
945
|
+
|
|
946
|
+
if expect_scope is not None and _to_bytes(expect_scope) != scopeb:
|
|
947
|
+
return (False, None, None, "scope", None, None)
|
|
948
|
+
|
|
949
|
+
length = int(length_str) if (length_str and length_str.isdigit()) else None
|
|
950
|
+
return (True, _to_text(userb), _to_text(scopeb), "ok", length, sha_hex)
|
|
951
|
+
except Exception as e:
|
|
952
|
+
return (False, None, None, "exc:%s" % e, None, None)
|
|
953
|
+
|
|
954
|
+
# Legacy blob (kept for backward compatibility)
|
|
955
|
+
_MAGIC = b"AUTH\0"; _OK = b"OK"; _NO = b"NO"
|
|
956
|
+
|
|
957
|
+
def _build_auth_blob_legacy(user, pw):
|
|
958
|
+
return _MAGIC + _to_bytes(user) + b"\0" + _to_bytes(pw) + b"\0"
|
|
959
|
+
|
|
960
|
+
def _parse_auth_blob_legacy(data):
|
|
961
|
+
if not data.startswith(_MAGIC):
|
|
962
|
+
return (None, None)
|
|
963
|
+
rest = data[len(_MAGIC):]
|
|
964
|
+
try:
|
|
965
|
+
user, rest = rest.split(b"\0", 1)
|
|
966
|
+
pw, _tail = rest.split(b"\0", 1)
|
|
967
|
+
return (user, pw)
|
|
968
|
+
except Exception:
|
|
969
|
+
return (None, None)
|
|
970
|
+
|
|
971
|
+
# ---------- URL helpers ----------
|
|
972
|
+
def _qflag(qs, key, default=False):
|
|
973
|
+
v = qs.get(key, [None])[0]
|
|
974
|
+
if v is None: return bool(default)
|
|
975
|
+
return _to_text(v).lower() in ("1", "true", "yes", "on")
|
|
976
|
+
|
|
977
|
+
def _qnum(qs, key, default=None, cast=float):
|
|
978
|
+
v = qs.get(key, [None])[0]
|
|
979
|
+
if v is None or v == "": return default
|
|
980
|
+
try: return cast(v)
|
|
981
|
+
except Exception: return default
|
|
982
|
+
|
|
983
|
+
def _qstr(qs, key, default=None):
|
|
984
|
+
v = qs.get(key, [None])[0]
|
|
985
|
+
if v is None: return default
|
|
986
|
+
return v
|
|
987
|
+
|
|
988
|
+
def _parse_net_url(url):
|
|
989
|
+
"""
|
|
990
|
+
Parse tcp:// / udp:// URL and extract transport options.
|
|
991
|
+
Returns (parts, opts)
|
|
992
|
+
"""
|
|
993
|
+
parts = urlparse(url)
|
|
994
|
+
qs = parse_qs(parts.query or "")
|
|
995
|
+
|
|
996
|
+
proto = parts.scheme.lower()
|
|
997
|
+
if proto not in ("tcp", "udp"):
|
|
998
|
+
raise ValueError("Only tcp:// or udp:// supported here")
|
|
999
|
+
|
|
1000
|
+
user = unquote(parts.username) if parts.username else None
|
|
1001
|
+
pw = unquote(parts.password) if parts.password else None
|
|
1002
|
+
|
|
1003
|
+
use_ssl = _qflag(qs, "ssl", False) if proto == "tcp" else False
|
|
1004
|
+
ssl_verify = _qflag(qs, "verify", True)
|
|
1005
|
+
ssl_ca_file = _qstr(qs, "ca", None)
|
|
1006
|
+
ssl_cert = _qstr(qs, "cert", None)
|
|
1007
|
+
ssl_key = _qstr(qs, "key", None)
|
|
1008
|
+
|
|
1009
|
+
timeout = _qnum(qs, "timeout", None, float)
|
|
1010
|
+
total_timeout = _qnum(qs, "total_timeout", None, float)
|
|
1011
|
+
chunk_size = int(_qnum(qs, "chunk", 65536, float))
|
|
1012
|
+
|
|
1013
|
+
force_auth = _qflag(qs, "auth", False)
|
|
1014
|
+
want_sha = _qflag(qs, "sha", True) # <— NEW: default compute sha
|
|
1015
|
+
|
|
1016
|
+
opts = dict(
|
|
1017
|
+
proto=proto,
|
|
1018
|
+
host=parts.hostname or "127.0.0.1",
|
|
1019
|
+
port=int(parts.port or 0),
|
|
1020
|
+
|
|
1021
|
+
user=user, pw=pw, force_auth=force_auth,
|
|
1022
|
+
|
|
1023
|
+
use_ssl=use_ssl, ssl_verify=ssl_verify,
|
|
1024
|
+
ssl_ca_file=ssl_ca_file, ssl_certfile=ssl_cert, ssl_keyfile=ssl_key,
|
|
1025
|
+
|
|
1026
|
+
timeout=timeout, total_timeout=total_timeout, chunk_size=chunk_size,
|
|
1027
|
+
|
|
1028
|
+
server_hostname=parts.hostname or None,
|
|
1029
|
+
|
|
1030
|
+
# new option
|
|
1031
|
+
want_sha=want_sha,
|
|
1032
|
+
|
|
1033
|
+
# convenience (used as scope in AF1)
|
|
1034
|
+
path=(parts.path or u""),
|
|
1035
|
+
)
|
|
1036
|
+
return parts, opts
|
|
1037
|
+
|
|
1038
|
+
def _rewrite_url_without_auth(url):
|
|
1039
|
+
u = urlparse(url)
|
|
1040
|
+
netloc = u.hostname or ''
|
|
1041
|
+
if u.port:
|
|
1042
|
+
netloc += ':' + str(u.port)
|
|
1043
|
+
rebuilt = urlunparse((u.scheme, netloc, u.path, u.params, u.query, u.fragment))
|
|
1044
|
+
usr = unquote(u.username) if u.username else ''
|
|
1045
|
+
pwd = unquote(u.password) if u.password else ''
|
|
1046
|
+
return rebuilt, usr, pwd
|
|
1047
|
+
|
|
1048
|
+
def _guess_filename(url, filename):
|
|
1049
|
+
if filename:
|
|
1050
|
+
return filename
|
|
1051
|
+
path = urlparse(url).path or ''
|
|
1052
|
+
base = os.path.basename(path)
|
|
1053
|
+
return base or 'OutFile.'+__file_format_extension__
|
|
1054
|
+
|
|
1055
|
+
# ---- progress + rate limiting helpers ----
|
|
1056
|
+
try:
|
|
1057
|
+
monotonic = time.monotonic # Py3
|
|
1058
|
+
except Exception:
|
|
1059
|
+
# Py2 fallback: time.time() is good enough for coarse throttling
|
|
1060
|
+
monotonic = time.time
|
|
1061
|
+
|
|
1062
|
+
def _progress_tick(now_bytes, total_bytes, last_ts, last_bytes, rate_limit_bps, min_interval=0.1):
|
|
1063
|
+
"""
|
|
1064
|
+
Returns (sleep_seconds, new_last_ts, new_last_bytes).
|
|
1065
|
+
- If rate_limit_bps is set, computes how long to sleep to keep average <= limit.
|
|
1066
|
+
- Also enforces a minimum interval between progress callbacks (handled by caller).
|
|
1067
|
+
"""
|
|
1068
|
+
now = monotonic()
|
|
1069
|
+
elapsed = max(1e-9, now - last_ts)
|
|
1070
|
+
# Desired time to have elapsed for the given rate:
|
|
1071
|
+
desired = (now_bytes - last_bytes) / float(rate_limit_bps) if rate_limit_bps else 0.0
|
|
1072
|
+
extra = desired - elapsed
|
|
1073
|
+
return (max(0.0, extra), now, now_bytes)
|
|
1074
|
+
|
|
1075
|
+
def _discover_len_and_reset(fobj):
|
|
1076
|
+
"""
|
|
1077
|
+
Try hard to get total length and restore original position.
|
|
1078
|
+
Returns (length_or_None, start_pos_or_None).
|
|
1079
|
+
Works with seekable files and BytesIO; leaves stream position unchanged.
|
|
1080
|
+
"""
|
|
1081
|
+
# Generic seek/tell
|
|
1082
|
+
try:
|
|
1083
|
+
pos0 = fobj.tell()
|
|
1084
|
+
fobj.seek(0, os.SEEK_END)
|
|
1085
|
+
end = fobj.tell()
|
|
1086
|
+
fobj.seek(pos0, os.SEEK_SET)
|
|
1087
|
+
if end is not None and pos0 is not None and end >= pos0:
|
|
1088
|
+
return (end - pos0, pos0)
|
|
1089
|
+
except Exception:
|
|
1090
|
+
pass
|
|
1091
|
+
|
|
1092
|
+
# BytesIO fast path
|
|
1093
|
+
try:
|
|
1094
|
+
getvalue = getattr(fobj, "getvalue", None)
|
|
1095
|
+
if callable(getvalue):
|
|
1096
|
+
buf = getvalue()
|
|
1097
|
+
L = len(buf)
|
|
1098
|
+
try:
|
|
1099
|
+
pos0 = fobj.tell()
|
|
1100
|
+
except Exception:
|
|
1101
|
+
pos0 = 0
|
|
1102
|
+
return (max(0, L - pos0), pos0)
|
|
1103
|
+
except Exception:
|
|
1104
|
+
pass
|
|
1105
|
+
|
|
1106
|
+
# Memoryview/getbuffer
|
|
1107
|
+
try:
|
|
1108
|
+
getbuffer = getattr(fobj, "getbuffer", None)
|
|
1109
|
+
if callable(getbuffer):
|
|
1110
|
+
mv = getbuffer()
|
|
1111
|
+
L = len(mv)
|
|
1112
|
+
try:
|
|
1113
|
+
pos0 = fobj.tell()
|
|
1114
|
+
except Exception:
|
|
1115
|
+
pos0 = 0
|
|
1116
|
+
return (max(0, L - pos0), pos0)
|
|
1117
|
+
except Exception:
|
|
1118
|
+
pass
|
|
1119
|
+
|
|
1120
|
+
return (None, None)
|
|
1121
|
+
|
|
1122
|
+
|
|
661
1123
|
def DetectTarBombCatFileArray(listarrayfiles,
|
|
662
1124
|
top_file_ratio_threshold=0.6,
|
|
663
1125
|
min_members_for_ratio=4,
|
|
@@ -2782,6 +3244,7 @@ def ReadFileDataWithContent(fp, filestart=0, listonly=False, uncompress=True, sk
|
|
|
2782
3244
|
break
|
|
2783
3245
|
flist.append(HeaderOut)
|
|
2784
3246
|
countnum = countnum + 1
|
|
3247
|
+
outlist.update({'fp': fp})
|
|
2785
3248
|
return flist
|
|
2786
3249
|
|
|
2787
3250
|
|
|
@@ -2848,7 +3311,7 @@ def ReadFileDataWithContentToArray(fp, filestart=0, seekstart=0, seekend=0, list
|
|
|
2848
3311
|
return False
|
|
2849
3312
|
formversions = re.search('(.*?)(\\d+)', formstring).groups()
|
|
2850
3313
|
fcompresstype = ""
|
|
2851
|
-
outlist = {'fnumfiles': fnumfiles, 'fformat': formversions[0], 'fcompression': fcompresstype, 'fencoding': fhencoding, 'fversion': formversions[1], 'fostype': fostype, 'fheadersize': fheadsize, 'fsize': CatSizeEnd, 'fnumfields': fnumfields + 2, 'fformatspecs': formatspecs, 'fchecksumtype': fprechecksumtype, 'fheaderchecksum': fprechecksum, 'frawheader': [formstring] + inheader, 'fextrafields': fnumextrafields, 'fextrafieldsize': fnumextrafieldsize, 'fextradata': fextrafieldslist, 'ffilelist': []}
|
|
3314
|
+
outlist = {'fnumfiles': fnumfiles, 'ffilestart': filestart, 'fformat': formversions[0], 'fcompression': fcompresstype, 'fencoding': fhencoding, 'fversion': formversions[1], 'fostype': fostype, 'fheadersize': fheadsize, 'fsize': CatSizeEnd, 'fnumfields': fnumfields + 2, 'fformatspecs': formatspecs, 'fchecksumtype': fprechecksumtype, 'fheaderchecksum': fprechecksum, 'frawheader': [formstring] + inheader, 'fextrafields': fnumextrafields, 'fextrafieldsize': fnumextrafieldsize, 'fextradata': fextrafieldslist, 'ffilelist': []}
|
|
2852
3315
|
if (seekstart < 0) or (seekstart > fnumfiles):
|
|
2853
3316
|
seekstart = 0
|
|
2854
3317
|
if (seekend == 0) or (seekend > fnumfiles) or (seekend < seekstart):
|
|
@@ -3291,6 +3754,41 @@ def ReadInMultipleFilesWithContentToArray(infile, fmttype="auto", filestart=0, s
|
|
|
3291
3754
|
return ReadInMultipleFileWithContentToArray(infile, fmttype, filestart, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend)
|
|
3292
3755
|
|
|
3293
3756
|
|
|
3757
|
+
def ReadInStackedFileWithContentToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False):
|
|
3758
|
+
outretval = []
|
|
3759
|
+
outstartfile = filestart
|
|
3760
|
+
outfsize = float('inf')
|
|
3761
|
+
while True:
|
|
3762
|
+
if outstartfile >= outfsize: # stop when function signals False
|
|
3763
|
+
break
|
|
3764
|
+
outarray = CatFileToArray(infile, fmttype, outstartfile, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend, True)
|
|
3765
|
+
outfsize = outarray['fsize']
|
|
3766
|
+
if outarray is False: # stop when function signals False
|
|
3767
|
+
break
|
|
3768
|
+
infile = outarray['fp']
|
|
3769
|
+
outstartfile = infile.tell()
|
|
3770
|
+
outretval.append(outarray)
|
|
3771
|
+
return outretval
|
|
3772
|
+
|
|
3773
|
+
|
|
3774
|
+
def ReadInStackedFilesWithContentToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False):
|
|
3775
|
+
return ReadInStackedFileWithContentToArray(infile, fmttype, filestart, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend)
|
|
3776
|
+
|
|
3777
|
+
|
|
3778
|
+
def ReadInMultipleStackedFileWithContentToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False):
|
|
3779
|
+
if(isinstance(infile, (list, tuple, ))):
|
|
3780
|
+
pass
|
|
3781
|
+
else:
|
|
3782
|
+
infile = [infile]
|
|
3783
|
+
outretval = {}
|
|
3784
|
+
for curfname in infile:
|
|
3785
|
+
outretval[curfname] = ReadInStackedFileWithContentToArray(curfname, fmttype, filestart, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend)
|
|
3786
|
+
return outretval
|
|
3787
|
+
|
|
3788
|
+
def ReadInMultipleStackedFilesWithContentToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False):
|
|
3789
|
+
return ReadInMultipleStackedFileWithContentToArray(infile, fmttype, filestart, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend)
|
|
3790
|
+
|
|
3791
|
+
|
|
3294
3792
|
def ReadInFileWithContentToList(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False):
|
|
3295
3793
|
if(IsNestedDict(formatspecs) and fmttype!="auto" and fmttype in formatspecs):
|
|
3296
3794
|
formatspecs = formatspecs[fmttype]
|
|
@@ -3472,7 +3970,7 @@ def ReadInMultipleFileWithContentToList(infile, fmttype="auto", filestart=0, see
|
|
|
3472
3970
|
infile = [infile]
|
|
3473
3971
|
outretval = {}
|
|
3474
3972
|
for curfname in infile:
|
|
3475
|
-
|
|
3973
|
+
outretval[curfname] = ReadInFileWithContentToList(curfname, fmttype, filestart, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend)
|
|
3476
3974
|
return outretval
|
|
3477
3975
|
|
|
3478
3976
|
def ReadInMultipleFilesWithContentToList(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False):
|
|
@@ -4041,12 +4539,6 @@ def AppendFilesWithContent(infiles, fp, dirlistfromtxt=False, filevalues=[], ext
|
|
|
4041
4539
|
fcsize, fuid, funame, fgid, fgname, fcurfid, fcurinode, flinkcount, fdev, fdev_minor, fdev_major, "+"+str(len(formatspecs['format_delimiter']))]
|
|
4042
4540
|
AppendFileHeaderWithContent(
|
|
4043
4541
|
fp, tmpoutlist, extradata, jsondata, fcontents.read(), [checksumtype[1], checksumtype[2], checksumtype[3]], formatspecs)
|
|
4044
|
-
if(numfiles > 0):
|
|
4045
|
-
try:
|
|
4046
|
-
fp.write(AppendNullBytes(
|
|
4047
|
-
["0", "0"], formatspecs['format_delimiter']))
|
|
4048
|
-
except OSError:
|
|
4049
|
-
return False
|
|
4050
4542
|
fp.seek(0, 0)
|
|
4051
4543
|
return fp
|
|
4052
4544
|
|
|
@@ -4110,12 +4602,6 @@ def AppendListsWithContent(inlist, fp, dirlistfromtxt=False, filevalues=[], extr
|
|
|
4110
4602
|
fcontents.seek(0, 0)
|
|
4111
4603
|
AppendFileHeaderWithContent(
|
|
4112
4604
|
fp, tmpoutlist, extradata, jsondata, fcontents.read(), [checksumtype[1], checksumtype[2], checksumtype[3]], formatspecs)
|
|
4113
|
-
if(numfiles > 0):
|
|
4114
|
-
try:
|
|
4115
|
-
fp.write(AppendNullBytes(
|
|
4116
|
-
["0", "0"], formatspecs['format_delimiter']))
|
|
4117
|
-
except OSError:
|
|
4118
|
-
return False
|
|
4119
4605
|
return fp
|
|
4120
4606
|
|
|
4121
4607
|
|
|
@@ -5508,12 +5994,6 @@ def PackCatFile(infiles, outfile, dirlistfromtxt=False, fmttype="auto", compress
|
|
|
5508
5994
|
AppendFileHeaderWithContent(
|
|
5509
5995
|
fp, tmpoutlist, extradata, jsondata, fcontents.read(), [checksumtype[1], checksumtype[2], checksumtype[3]], formatspecs)
|
|
5510
5996
|
fcontents.close()
|
|
5511
|
-
if(numfiles > 0):
|
|
5512
|
-
try:
|
|
5513
|
-
fp.write(AppendNullBytes(
|
|
5514
|
-
["0", "0"], formatspecs['format_delimiter']))
|
|
5515
|
-
except OSError:
|
|
5516
|
-
return False
|
|
5517
5997
|
if(outfile == "-" or outfile is None or hasattr(outfile, "read") or hasattr(outfile, "write")):
|
|
5518
5998
|
fp = CompressOpenFileAlt(
|
|
5519
5999
|
fp, compression, compressionlevel, compressionuselist, formatspecs)
|
|
@@ -5809,12 +6289,6 @@ def PackCatFileFromTarFile(infile, outfile, fmttype="auto", compression="auto",
|
|
|
5809
6289
|
AppendFileHeaderWithContent(
|
|
5810
6290
|
fp, tmpoutlist, extradata, jsondata, fcontents.read(), [checksumtype[1], checksumtype[2], checksumtype[3]], formatspecs)
|
|
5811
6291
|
fcontents.close()
|
|
5812
|
-
if(numfiles > 0):
|
|
5813
|
-
try:
|
|
5814
|
-
fp.write(AppendNullBytes(
|
|
5815
|
-
["0", "0"], formatspecs['format_delimiter']))
|
|
5816
|
-
except OSError:
|
|
5817
|
-
return False
|
|
5818
6292
|
if(outfile == "-" or outfile is None or hasattr(outfile, "read") or hasattr(outfile, "write")):
|
|
5819
6293
|
fp = CompressOpenFileAlt(
|
|
5820
6294
|
fp, compression, compressionlevel, compressionuselist, formatspecs)
|
|
@@ -6103,12 +6577,6 @@ def PackCatFileFromZipFile(infile, outfile, fmttype="auto", compression="auto",
|
|
|
6103
6577
|
AppendFileHeaderWithContent(
|
|
6104
6578
|
fp, tmpoutlist, extradata, jsondata, fcontents.read(), [checksumtype[1], checksumtype[2], checksumtype[3]], formatspecs)
|
|
6105
6579
|
fcontents.close()
|
|
6106
|
-
if(numfiles > 0):
|
|
6107
|
-
try:
|
|
6108
|
-
fp.write(AppendNullBytes(
|
|
6109
|
-
["0", "0"], formatspecs['format_delimiter']))
|
|
6110
|
-
except OSError:
|
|
6111
|
-
return False
|
|
6112
6580
|
if(outfile == "-" or outfile is None or hasattr(outfile, "read") or hasattr(outfile, "write")):
|
|
6113
6581
|
fp = CompressOpenFileAlt(
|
|
6114
6582
|
fp, compression, compressionlevel, compressionuselist, formatspecs)
|
|
@@ -6423,12 +6891,6 @@ if(rarfile_support):
|
|
|
6423
6891
|
AppendFileHeaderWithContent(
|
|
6424
6892
|
fp, tmpoutlist, extradata, jsondata, fcontents.read(), [checksumtype[1], checksumtype[2], checksumtype[3]], formatspecs)
|
|
6425
6893
|
fcontents.close()
|
|
6426
|
-
if(numfiles > 0):
|
|
6427
|
-
try:
|
|
6428
|
-
fp.write(AppendNullBytes(
|
|
6429
|
-
["0", "0"], formatspecs['format_delimiter']))
|
|
6430
|
-
except OSError:
|
|
6431
|
-
return False
|
|
6432
6894
|
if(outfile == "-" or outfile is None or hasattr(outfile, "read") or hasattr(outfile, "write")):
|
|
6433
6895
|
fp = CompressOpenFileAlt(
|
|
6434
6896
|
fp, compression, compressionlevel, compressionuselist, formatspecs)
|
|
@@ -6677,12 +7139,6 @@ if(py7zr_support):
|
|
|
6677
7139
|
AppendFileHeaderWithContent(
|
|
6678
7140
|
fp, tmpoutlist, extradata, jsondata, fcontents.read(), [checksumtype[1], checksumtype[2], checksumtype[3]], formatspecs)
|
|
6679
7141
|
fcontents.close()
|
|
6680
|
-
if(numfiles > 0):
|
|
6681
|
-
try:
|
|
6682
|
-
fp.write(AppendNullBytes(
|
|
6683
|
-
["0", "0"], formatspecs['format_delimiter']))
|
|
6684
|
-
except OSError:
|
|
6685
|
-
return False
|
|
6686
7142
|
if(outfile == "-" or outfile is None or hasattr(outfile, "read") or hasattr(outfile, "write")):
|
|
6687
7143
|
fp = CompressOpenFileAlt(
|
|
6688
7144
|
fp, compression, compressionlevel, compressionuselist, formatspecs)
|
|
@@ -7072,24 +7528,73 @@ def CatFileValidate(infile, fmttype="auto", filestart=0, formatspecs=__file_form
|
|
|
7072
7528
|
return False
|
|
7073
7529
|
|
|
7074
7530
|
|
|
7075
|
-
def CatFileValidateFile(infile, fmttype="auto", formatspecs=__file_format_multi_dict__, verbose=False, returnfp=False):
|
|
7076
|
-
return CatFileValidate(infile, fmttype, formatspecs, verbose, returnfp)
|
|
7531
|
+
def CatFileValidateFile(infile, fmttype="auto", filestart=0, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, returnfp=False):
|
|
7532
|
+
return CatFileValidate(infile, fmttype, filestart, formatspecs, seektoend, verbose, returnfp)
|
|
7533
|
+
|
|
7534
|
+
|
|
7535
|
+
def CatFileValidateMultiple(infile, fmttype="auto", filestart=0, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, returnfp=False):
|
|
7536
|
+
if(isinstance(infile, (list, tuple, ))):
|
|
7537
|
+
pass
|
|
7538
|
+
else:
|
|
7539
|
+
infile = [infile]
|
|
7540
|
+
outretval = True
|
|
7541
|
+
for curfname in infile:
|
|
7542
|
+
curretfile = CatFileValidate(curfname, fmttype, filestart, formatspecs, seektoend, verbose, returnfp)
|
|
7543
|
+
if(not curretfile):
|
|
7544
|
+
outretval = False
|
|
7545
|
+
return outretval
|
|
7546
|
+
|
|
7547
|
+
def CatFileValidateMultipleFiles(infile, fmttype="auto", filestart=0, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, returnfp=False):
|
|
7548
|
+
return CatFileValidateMultiple(infile, fmttype, filestart, formatspecs, seektoend, verbose, returnfp)
|
|
7549
|
+
|
|
7550
|
+
|
|
7551
|
+
def StackedCatFileValidate(infile, fmttype="auto", filestart=0, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, returnfp=False):
|
|
7552
|
+
outretval = []
|
|
7553
|
+
outstartfile = filestart
|
|
7554
|
+
outfsize = float('inf')
|
|
7555
|
+
while True:
|
|
7556
|
+
if outstartfile >= outfsize: # stop when function signals False
|
|
7557
|
+
break
|
|
7558
|
+
is_valid_file = CatFileValidate(infile, fmttype, filestart, formatspecs, seektoend, verbose, True)
|
|
7559
|
+
if is_valid_file is False: # stop when function signals False
|
|
7560
|
+
outretval.append(is_valid_file)
|
|
7561
|
+
else:
|
|
7562
|
+
outretval.append(True)
|
|
7563
|
+
infile = is_valid_file
|
|
7564
|
+
outstartfile = infile.tell()
|
|
7565
|
+
try:
|
|
7566
|
+
infile.seek(0, 2)
|
|
7567
|
+
except OSError:
|
|
7568
|
+
SeekToEndOfFile(infile)
|
|
7569
|
+
except ValueError:
|
|
7570
|
+
SeekToEndOfFile(infile)
|
|
7571
|
+
outfsize = infile.tell()
|
|
7572
|
+
infile.seek(outstartfile, 0)
|
|
7573
|
+
if(returnfp):
|
|
7574
|
+
return infile
|
|
7575
|
+
else:
|
|
7576
|
+
infile.close()
|
|
7577
|
+
return outretval
|
|
7578
|
+
|
|
7077
7579
|
|
|
7580
|
+
def StackedCatFileValidateFile(infile, fmttype="auto", filestart=0, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, returnfp=False):
|
|
7581
|
+
return StackedCatFileValidate(infile, fmttype, filestart, formatspecs, seektoend, verbose, returnfp)
|
|
7078
7582
|
|
|
7079
|
-
|
|
7583
|
+
|
|
7584
|
+
def StackedCatFileValidateMultiple(infile, fmttype="auto", filestart=0, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, returnfp=False):
|
|
7080
7585
|
if(isinstance(infile, (list, tuple, ))):
|
|
7081
7586
|
pass
|
|
7082
7587
|
else:
|
|
7083
7588
|
infile = [infile]
|
|
7084
7589
|
outretval = True
|
|
7085
7590
|
for curfname in infile:
|
|
7086
|
-
curretfile =
|
|
7591
|
+
curretfile = StackedCatFileValidate(curfname, fmttype, filestart, formatspecs, seektoend, verbose, returnfp)
|
|
7087
7592
|
if(not curretfile):
|
|
7088
7593
|
outretval = False
|
|
7089
7594
|
return outretval
|
|
7090
7595
|
|
|
7091
|
-
def
|
|
7092
|
-
return
|
|
7596
|
+
def StackedCatFileValidateMultipleFiles(infile, fmttype="auto", filestart=0, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, returnfp=False):
|
|
7597
|
+
return StackedCatFileValidateMultiple(infile, fmttype, filestart, formatspecs, seektoend, verbose, returnfp)
|
|
7093
7598
|
|
|
7094
7599
|
def CatFileToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, returnfp=False):
|
|
7095
7600
|
if(IsNestedDict(formatspecs) and fmttype!="auto" and fmttype in formatspecs):
|
|
@@ -7102,20 +7607,20 @@ def CatFileToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0,
|
|
|
7102
7607
|
fp = infile
|
|
7103
7608
|
fp.seek(filestart, 0)
|
|
7104
7609
|
fp = UncompressFileAlt(fp, formatspecs, filestart)
|
|
7105
|
-
|
|
7106
|
-
if(IsNestedDict(formatspecs) and
|
|
7107
|
-
formatspecs = formatspecs[
|
|
7108
|
-
if(
|
|
7610
|
+
compresscheck = CheckCompressionSubType(fp, formatspecs, filestart, True)
|
|
7611
|
+
if(IsNestedDict(formatspecs) and compresscheck in formatspecs):
|
|
7612
|
+
formatspecs = formatspecs[compresscheck]
|
|
7613
|
+
if(compresscheck == "tarfile" and TarFileCheck(infile)):
|
|
7109
7614
|
return TarFileToArray(infile, 0, 0, listonly, contentasfile, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7110
|
-
elif(
|
|
7615
|
+
elif(compresscheck == "zipfile" and zipfile.is_zipfile(infile)):
|
|
7111
7616
|
return ZipFileToArray(infile, 0, 0, listonly, contentasfile, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7112
|
-
elif(rarfile_support and
|
|
7617
|
+
elif(rarfile_support and compresscheck == "rarfile" and (rarfile.is_rarfile(infile) or rarfile.is_rarfile_sfx(infile))):
|
|
7113
7618
|
return RarFileToArray(infile, 0, 0, listonly, contentasfile, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7114
|
-
elif(py7zr_support and
|
|
7619
|
+
elif(py7zr_support and compresscheck == "7zipfile" and py7zr.is_7zfile(infile)):
|
|
7115
7620
|
return SevenZipFileToArray(infile, 0, 0, listonly, contentasfile, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7116
|
-
elif(IsSingleDict(formatspecs) and
|
|
7621
|
+
elif(IsSingleDict(formatspecs) and compresscheck != formatspecs['format_magic']):
|
|
7117
7622
|
return False
|
|
7118
|
-
elif(IsNestedDict(formatspecs) and
|
|
7623
|
+
elif(IsNestedDict(formatspecs) and compresscheck not in formatspecs):
|
|
7119
7624
|
return False
|
|
7120
7625
|
if(not fp):
|
|
7121
7626
|
return False
|
|
@@ -7128,9 +7633,9 @@ def CatFileToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0,
|
|
|
7128
7633
|
shutil.copyfileobj(sys.stdin, fp)
|
|
7129
7634
|
fp.seek(filestart, 0)
|
|
7130
7635
|
fp = UncompressFileAlt(fp, formatspecs, filestart)
|
|
7131
|
-
|
|
7132
|
-
if(IsNestedDict(formatspecs) and
|
|
7133
|
-
formatspecs = formatspecs[
|
|
7636
|
+
compresscheck = CheckCompressionSubType(fp, formatspecs, filestart, True)
|
|
7637
|
+
if(IsNestedDict(formatspecs) and compresscheck in formatspecs):
|
|
7638
|
+
formatspecs = formatspecs[compresscheck]
|
|
7134
7639
|
if(not fp):
|
|
7135
7640
|
return False
|
|
7136
7641
|
fp.seek(filestart, 0)
|
|
@@ -7157,20 +7662,20 @@ def CatFileToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0,
|
|
|
7157
7662
|
fp.seek(filestart, 0)
|
|
7158
7663
|
else:
|
|
7159
7664
|
infile = RemoveWindowsPath(infile)
|
|
7160
|
-
|
|
7161
|
-
if(IsNestedDict(formatspecs) and
|
|
7162
|
-
formatspecs = formatspecs[
|
|
7163
|
-
if(
|
|
7665
|
+
compresscheck = CheckCompressionSubType(infile, formatspecs, filestart, True)
|
|
7666
|
+
if(IsNestedDict(formatspecs) and compresscheck in formatspecs):
|
|
7667
|
+
formatspecs = formatspecs[compresscheck]
|
|
7668
|
+
if(compresscheck == "tarfile" and TarFileCheck(infile)):
|
|
7164
7669
|
return TarFileToArray(infile, 0, 0, listonly, contentasfile, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7165
|
-
elif(
|
|
7670
|
+
elif(compresscheck == "zipfile" and zipfile.is_zipfile(infile)):
|
|
7166
7671
|
return ZipFileToArray(infile, 0, 0, listonly, contentasfile, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7167
|
-
elif(rarfile_support and
|
|
7672
|
+
elif(rarfile_support and compresscheck == "rarfile" and (rarfile.is_rarfile(infile) or rarfile.is_rarfile_sfx(infile))):
|
|
7168
7673
|
return RarFileToArray(infile, 0, 0, listonly, contentasfile, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7169
|
-
elif(py7zr_support and
|
|
7674
|
+
elif(py7zr_support and compresscheck == "7zipfile" and py7zr.is_7zfile(infile)):
|
|
7170
7675
|
return SevenZipFileToArray(infile, 0, 0, listonly, contentasfile, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7171
|
-
elif(IsSingleDict(formatspecs) and
|
|
7676
|
+
elif(IsSingleDict(formatspecs) and compresscheck != formatspecs['format_magic']):
|
|
7172
7677
|
return False
|
|
7173
|
-
elif(IsNestedDict(formatspecs) and
|
|
7678
|
+
elif(IsNestedDict(formatspecs) and compresscheck not in formatspecs):
|
|
7174
7679
|
return False
|
|
7175
7680
|
compresscheck = CheckCompressionType(infile, formatspecs, filestart, True)
|
|
7176
7681
|
if(not compresscheck):
|
|
@@ -7263,7 +7768,7 @@ def CatFileToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0,
|
|
|
7263
7768
|
fcompresstype = compresscheck
|
|
7264
7769
|
if(fcompresstype==formatspecs['format_magic']):
|
|
7265
7770
|
fcompresstype = ""
|
|
7266
|
-
outlist = {'fnumfiles': fnumfiles, 'fformat': formversions[0], 'fcompression': fcompresstype, 'fencoding': fhencoding, 'fversion': formversions[1], 'fostype': fostype, 'fheadersize': fheadsize, 'fsize': CatSizeEnd, 'fnumfields': fnumfields + 2, 'fformatspecs': formatspecs, 'fchecksumtype': fprechecksumtype, 'fheaderchecksum': fprechecksum, 'frawheader': [formstring] + inheader, 'fextrafields': fnumextrafields, 'fextrafieldsize': fnumextrafieldsize, 'fextradata': fextrafieldslist, 'ffilelist': []}
|
|
7771
|
+
outlist = {'fnumfiles': fnumfiles, 'ffilestart': filestart, 'fformat': formversions[0], 'fcompression': fcompresstype, 'fencoding': fhencoding, 'fversion': formversions[1], 'fostype': fostype, 'fheadersize': fheadsize, 'fsize': CatSizeEnd, 'fnumfields': fnumfields + 2, 'fformatspecs': formatspecs, 'fchecksumtype': fprechecksumtype, 'fheaderchecksum': fprechecksum, 'frawheader': [formstring] + inheader, 'fextrafields': fnumextrafields, 'fextrafieldsize': fnumextrafieldsize, 'fextradata': fextrafieldslist, 'ffilelist': []}
|
|
7267
7772
|
if (seekstart < 0) or (seekstart > fnumfiles):
|
|
7268
7773
|
seekstart = 0
|
|
7269
7774
|
if (seekend == 0) or (seekend > fnumfiles) or (seekend < seekstart):
|
|
@@ -7549,6 +8054,7 @@ def CatFileToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0,
|
|
|
7549
8054
|
outlist.update({'fp': fp})
|
|
7550
8055
|
else:
|
|
7551
8056
|
fp.close()
|
|
8057
|
+
outlist.update({'fp': None})
|
|
7552
8058
|
return outlist
|
|
7553
8059
|
|
|
7554
8060
|
|
|
@@ -7559,13 +8065,48 @@ def MultipleCatFileToArray(infile, fmttype="auto", filestart=0, seekstart=0, see
|
|
|
7559
8065
|
infile = [infile]
|
|
7560
8066
|
outretval = {}
|
|
7561
8067
|
for curfname in infile:
|
|
7562
|
-
|
|
8068
|
+
outretval[curfname] = CatFileToArray(curfname, fmttype, filestart, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7563
8069
|
return outretval
|
|
7564
8070
|
|
|
7565
8071
|
def MultipleCatFilesToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, returnfp=False):
|
|
7566
8072
|
return MultipleCatFileToArray(infile, fmttype, filestart, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7567
8073
|
|
|
7568
8074
|
|
|
8075
|
+
def StackedCatFileToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, returnfp=False):
|
|
8076
|
+
outretval = []
|
|
8077
|
+
outstartfile = filestart
|
|
8078
|
+
outfsize = float('inf')
|
|
8079
|
+
while True:
|
|
8080
|
+
if outstartfile >= outfsize: # stop when function signals False
|
|
8081
|
+
break
|
|
8082
|
+
outarray = CatFileToArray(infile, fmttype, outstartfile, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend, True)
|
|
8083
|
+
outfsize = outarray['fsize']
|
|
8084
|
+
if outarray is False: # stop when function signals False
|
|
8085
|
+
break
|
|
8086
|
+
infile = outarray['fp']
|
|
8087
|
+
outstartfile = infile.tell()
|
|
8088
|
+
if(not returnfp):
|
|
8089
|
+
outarray.update({"fp": None})
|
|
8090
|
+
outretval.append(outarray)
|
|
8091
|
+
if(not returnfp):
|
|
8092
|
+
infile.close()
|
|
8093
|
+
return outretval
|
|
8094
|
+
|
|
8095
|
+
|
|
8096
|
+
def MultipleStackedCatFileToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, returnfp=False):
|
|
8097
|
+
if(isinstance(infile, (list, tuple, ))):
|
|
8098
|
+
pass
|
|
8099
|
+
else:
|
|
8100
|
+
infile = [infile]
|
|
8101
|
+
outretval = {}
|
|
8102
|
+
for curfname in infile:
|
|
8103
|
+
outretval[curfname] = StackedCatFileToArray(curfname, fmttype, filestart, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend, returnfp)
|
|
8104
|
+
return outretval
|
|
8105
|
+
|
|
8106
|
+
def MultipleStackedCatFilesToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, returnfp=False):
|
|
8107
|
+
return MultipleStackedCatFileToArray(infile, fmttype, filestart, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend, returnfp)
|
|
8108
|
+
|
|
8109
|
+
|
|
7569
8110
|
def CatFileStringToArray(instr, filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, returnfp=False):
|
|
7570
8111
|
checkcompressfile = CheckCompressionSubType(infile, formatspecs, filestart, True)
|
|
7571
8112
|
if(IsNestedDict(formatspecs) and checkcompressfile in formatspecs):
|
|
@@ -7651,74 +8192,126 @@ def ListDirToArray(infiles, dirlistfromtxt=False, fmttype=__file_format_default_
|
|
|
7651
8192
|
outarray = MkTempFile()
|
|
7652
8193
|
packform = PackCatFile(infiles, outarray, dirlistfromtxt, fmttype, compression, compresswholefile,
|
|
7653
8194
|
compressionlevel, followlink, checksumtype, extradata, formatspecs, verbose, True)
|
|
7654
|
-
listarrayfiles = CatFileToArray(outarray, "auto", filestart, seekstart, seekend, listonly, True, skipchecksum, formatspecs, seektoend, returnfp)
|
|
8195
|
+
listarrayfiles = CatFileToArray(outarray, "auto", filestart, seekstart, seekend, listonly, True, True, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7655
8196
|
return listarrayfiles
|
|
7656
8197
|
|
|
7657
8198
|
|
|
8199
|
+
# ===== Function (keeps inarray schema; returns entries + indexes) =====
|
|
8200
|
+
|
|
7658
8201
|
def CatFileArrayToArrayIndex(inarray, returnfp=False):
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
8202
|
+
"""
|
|
8203
|
+
Build a bidirectional index over an archive listing while preserving the
|
|
8204
|
+
input 'inarray' as-is. Python 2/3 compatible, no external deps.
|
|
8205
|
+
|
|
8206
|
+
Input (unchanged contract):
|
|
8207
|
+
inarray: dict with at least:
|
|
8208
|
+
- 'ffilelist': list of dicts: {'fname': <str>, 'fid': <any>, 'ftype': <int>}
|
|
8209
|
+
- 'fnumfiles': int (expected count)
|
|
8210
|
+
- optional 'fp': any (passed through if returnfp=True)
|
|
8211
|
+
|
|
8212
|
+
Output structure:
|
|
8213
|
+
{
|
|
8214
|
+
'list': inarray, # alias to original input (not copied)
|
|
8215
|
+
'fp': inarray.get('fp') or None,
|
|
8216
|
+
'entries': { fid: {'name': fname, 'type': ftype} },
|
|
8217
|
+
'indexes': {
|
|
8218
|
+
'by_name': { fname: fid },
|
|
8219
|
+
'by_type': {
|
|
8220
|
+
<category>: {
|
|
8221
|
+
'by_name': { fname: fid },
|
|
8222
|
+
'by_id': { fid: fname },
|
|
8223
|
+
'count': <int>
|
|
8224
|
+
}, ...
|
|
8225
|
+
}
|
|
8226
|
+
},
|
|
8227
|
+
'counts': {
|
|
8228
|
+
'total': <int>,
|
|
8229
|
+
'by_type': { <category>: <int>, ... }
|
|
8230
|
+
},
|
|
8231
|
+
'unknown_types': { <ftype_int>: [fname, ...] }
|
|
8232
|
+
}
|
|
8233
|
+
"""
|
|
8234
|
+
if not isinstance(inarray, dict):
|
|
7662
8235
|
return False
|
|
7663
|
-
if
|
|
8236
|
+
if not inarray:
|
|
7664
8237
|
return False
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
8238
|
+
|
|
8239
|
+
# Buckets for categories
|
|
8240
|
+
def _bucket():
|
|
8241
|
+
return {"by_name": {}, "by_id": {}, "count": 0}
|
|
8242
|
+
|
|
8243
|
+
by_type = {}
|
|
8244
|
+
for cat in CATEGORY_ORDER:
|
|
8245
|
+
by_type[cat] = _bucket()
|
|
8246
|
+
|
|
8247
|
+
out = {
|
|
8248
|
+
"list": inarray,
|
|
8249
|
+
"fp": inarray.get("fp") if returnfp else None,
|
|
8250
|
+
"entries": {},
|
|
8251
|
+
"indexes": {
|
|
8252
|
+
"by_name": {},
|
|
8253
|
+
"by_type": by_type,
|
|
8254
|
+
},
|
|
8255
|
+
"counts": {"total": 0, "by_type": {}},
|
|
8256
|
+
"unknown_types": {},
|
|
8257
|
+
}
|
|
8258
|
+
|
|
8259
|
+
ffilelist = inarray.get("ffilelist") or []
|
|
8260
|
+
try:
|
|
8261
|
+
fnumfiles = int(inarray.get("fnumfiles", len(ffilelist)))
|
|
8262
|
+
except Exception:
|
|
8263
|
+
fnumfiles = len(ffilelist)
|
|
8264
|
+
|
|
8265
|
+
# Process only what's present
|
|
8266
|
+
total = min(len(ffilelist), fnumfiles)
|
|
8267
|
+
|
|
8268
|
+
def _add(cat, name, fid):
|
|
8269
|
+
b = by_type[cat]
|
|
8270
|
+
b["by_name"][name] = fid
|
|
8271
|
+
b["by_id"][fid] = name
|
|
8272
|
+
# Count is number of unique names in this category
|
|
8273
|
+
b["count"] = len(b["by_name"])
|
|
8274
|
+
|
|
8275
|
+
i = 0
|
|
8276
|
+
while i < total:
|
|
8277
|
+
e = ffilelist[i]
|
|
8278
|
+
name = e.get("fname")
|
|
8279
|
+
fid = e.get("fid")
|
|
8280
|
+
t = e.get("ftype")
|
|
8281
|
+
|
|
8282
|
+
if name is None or fid is None or t is None:
|
|
8283
|
+
i += 1
|
|
8284
|
+
continue
|
|
8285
|
+
|
|
8286
|
+
# Store canonical entry once, keyed by fid
|
|
8287
|
+
out["entries"][fid] = {"name": name, "type": t}
|
|
8288
|
+
|
|
8289
|
+
# Global reverse index for fast name -> id
|
|
8290
|
+
out["indexes"]["by_name"][name] = fid
|
|
8291
|
+
|
|
8292
|
+
# Base category
|
|
8293
|
+
base_cat = BASE_CATEGORY_BY_CODE.get(t)
|
|
8294
|
+
if base_cat is not None:
|
|
8295
|
+
_add(base_cat, name, fid)
|
|
8296
|
+
else:
|
|
8297
|
+
# Track unknown codes for visibility/forward-compat
|
|
8298
|
+
lst = out["unknown_types"].setdefault(t, [])
|
|
8299
|
+
if name not in lst:
|
|
8300
|
+
lst.append(name)
|
|
8301
|
+
|
|
8302
|
+
# Union categories
|
|
8303
|
+
for union_name, code_set in UNION_RULES:
|
|
8304
|
+
if t in code_set:
|
|
8305
|
+
_add(union_name, name, fid)
|
|
8306
|
+
|
|
8307
|
+
i += 1
|
|
8308
|
+
|
|
8309
|
+
# Counts
|
|
8310
|
+
out["counts"]["total"] = total
|
|
8311
|
+
for cat in CATEGORY_ORDER:
|
|
8312
|
+
out["counts"]["by_type"][cat] = by_type[cat]["count"]
|
|
8313
|
+
|
|
8314
|
+
return out
|
|
7722
8315
|
|
|
7723
8316
|
|
|
7724
8317
|
def RePackCatFile(infile, outfile, fmttype="auto", compression="auto", compresswholefile=True, compressionlevel=None, compressionuselist=compressionlistalt, followlink=False, filestart=0, seekstart=0, seekend=0, checksumtype=["crc32", "crc32", "crc32", "crc32"], skipchecksum=False, extradata=[], jsondata={}, formatspecs=__file_format_dict__, seektoend=False, verbose=False, returnfp=False):
|
|
@@ -7727,7 +8320,7 @@ def RePackCatFile(infile, outfile, fmttype="auto", compression="auto", compressw
|
|
|
7727
8320
|
else:
|
|
7728
8321
|
if(infile != "-" and not isinstance(infile, bytes) and not hasattr(infile, "read") and not hasattr(infile, "write")):
|
|
7729
8322
|
infile = RemoveWindowsPath(infile)
|
|
7730
|
-
listarrayfiles = CatFileToArray(infile, "auto", filestart, seekstart, seekend, False, True, skipchecksum, formatspecs, seektoend, returnfp)
|
|
8323
|
+
listarrayfiles = CatFileToArray(infile, "auto", filestart, seekstart, seekend, False, True, True, skipchecksum, formatspecs, seektoend, returnfp)
|
|
7731
8324
|
if(IsNestedDict(formatspecs) and fmttype in formatspecs):
|
|
7732
8325
|
formatspecs = formatspecs[fmttype]
|
|
7733
8326
|
elif(IsNestedDict(formatspecs) and fmttype not in formatspecs):
|
|
@@ -7840,11 +8433,11 @@ def RePackCatFile(infile, outfile, fmttype="auto", compression="auto", compressw
|
|
|
7840
8433
|
fdev_major = format(
|
|
7841
8434
|
int(listarrayfiles['ffilelist'][reallcfi]['fmajor']), 'x').lower()
|
|
7842
8435
|
fseeknextfile = listarrayfiles['ffilelist'][reallcfi]['fseeknextfile']
|
|
7843
|
-
if(len(listarrayfiles['ffilelist'][reallcfi]['
|
|
8436
|
+
if(len(listarrayfiles['ffilelist'][reallcfi]['fextradata']) > listarrayfiles['ffilelist'][reallcfi]['fextrafields'] and len(listarrayfiles['ffilelist'][reallcfi]['fextradata']) > 0):
|
|
7844
8437
|
listarrayfiles['ffilelist'][reallcfi]['fextrafields'] = len(
|
|
7845
|
-
listarrayfiles['ffilelist'][reallcfi]['
|
|
8438
|
+
listarrayfiles['ffilelist'][reallcfi]['fextradata'])
|
|
7846
8439
|
if(not followlink and len(extradata) <= 0):
|
|
7847
|
-
extradata = listarrayfiles['ffilelist'][reallcfi]['
|
|
8440
|
+
extradata = listarrayfiles['ffilelist'][reallcfi]['fextradata']
|
|
7848
8441
|
if(not followlink and len(jsondata) <= 0):
|
|
7849
8442
|
jsondata = listarrayfiles['ffilelist'][reallcfi]['fjsondata']
|
|
7850
8443
|
fcontents = listarrayfiles['ffilelist'][reallcfi]['fcontents']
|
|
@@ -7923,10 +8516,10 @@ def RePackCatFile(infile, outfile, fmttype="auto", compression="auto", compressw
|
|
|
7923
8516
|
fdev_minor = format(int(flinkinfo['fminor']), 'x').lower()
|
|
7924
8517
|
fdev_major = format(int(flinkinfo['fmajor']), 'x').lower()
|
|
7925
8518
|
fseeknextfile = flinkinfo['fseeknextfile']
|
|
7926
|
-
if(len(flinkinfo['
|
|
7927
|
-
flinkinfo['fextrafields'] = len(flinkinfo['
|
|
8519
|
+
if(len(flinkinfo['fextradata']) > flinkinfo['fextrafields'] and len(flinkinfo['fextradata']) > 0):
|
|
8520
|
+
flinkinfo['fextrafields'] = len(flinkinfo['fextradata'])
|
|
7928
8521
|
if(len(extradata) < 0):
|
|
7929
|
-
extradata = flinkinfo['
|
|
8522
|
+
extradata = flinkinfo['fextradata']
|
|
7930
8523
|
if(len(jsondata) < 0):
|
|
7931
8524
|
extradata = flinkinfo['fjsondata']
|
|
7932
8525
|
fcontents = flinkinfo['fcontents']
|
|
@@ -7958,12 +8551,6 @@ def RePackCatFile(infile, outfile, fmttype="auto", compression="auto", compressw
|
|
|
7958
8551
|
fcontents.close()
|
|
7959
8552
|
lcfi = lcfi + 1
|
|
7960
8553
|
reallcfi = reallcfi + 1
|
|
7961
|
-
if(lcfx > 0):
|
|
7962
|
-
try:
|
|
7963
|
-
fp.write(AppendNullBytes(
|
|
7964
|
-
["0", "0"], formatspecs['format_delimiter']))
|
|
7965
|
-
except OSError:
|
|
7966
|
-
return False
|
|
7967
8554
|
if(outfile == "-" or outfile is None or hasattr(outfile, "read") or hasattr(outfile, "write")):
|
|
7968
8555
|
fp = CompressOpenFileAlt(
|
|
7969
8556
|
fp, compression, compressionlevel, compressionuselist, formatspecs)
|
|
@@ -8027,7 +8614,7 @@ def UnPackCatFile(infile, outdir=None, followlink=False, filestart=0, seekstart=
|
|
|
8027
8614
|
else:
|
|
8028
8615
|
if(infile != "-" and not hasattr(infile, "read") and not hasattr(infile, "write") and not (sys.version_info[0] >= 3 and isinstance(infile, bytes))):
|
|
8029
8616
|
infile = RemoveWindowsPath(infile)
|
|
8030
|
-
listarrayfiles = CatFileToArray(infile, "auto", filestart, seekstart, seekend, False, True, skipchecksum, formatspecs, seektoend, returnfp)
|
|
8617
|
+
listarrayfiles = CatFileToArray(infile, "auto", filestart, seekstart, seekend, False, True, True, skipchecksum, formatspecs, seektoend, returnfp)
|
|
8031
8618
|
if(not listarrayfiles):
|
|
8032
8619
|
return False
|
|
8033
8620
|
lenlist = len(listarrayfiles['ffilelist'])
|
|
@@ -8275,9 +8862,9 @@ def UnPackCatFile(infile, outdir=None, followlink=False, filestart=0, seekstart=
|
|
|
8275
8862
|
return True
|
|
8276
8863
|
|
|
8277
8864
|
|
|
8278
|
-
def UnPackCatFileString(instr, outdir=None, followlink=False, seekstart=0, seekend=0, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, returnfp=False):
|
|
8865
|
+
def UnPackCatFileString(instr, outdir=None, followlink=False, filestart=0, seekstart=0, seekend=0, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, returnfp=False):
|
|
8279
8866
|
fp = MkTempFile(instr)
|
|
8280
|
-
listarrayfiles = UnPackCatFile(fp, outdir, followlink, seekstart, seekend, skipchecksum, formatspecs, seektoend, verbose, returnfp)
|
|
8867
|
+
listarrayfiles = UnPackCatFile(fp, outdir, followlink, filestart, seekstart, seekend, skipchecksum, formatspecs, seektoend, verbose, returnfp)
|
|
8281
8868
|
return listarrayfiles
|
|
8282
8869
|
|
|
8283
8870
|
def ftype_to_str(ftype):
|
|
@@ -8348,10 +8935,60 @@ def CatFileListFiles(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0
|
|
|
8348
8935
|
return True
|
|
8349
8936
|
|
|
8350
8937
|
|
|
8351
|
-
def
|
|
8938
|
+
def MultipleCatFileListFiles(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, returnfp=False):
|
|
8939
|
+
if(isinstance(infile, (list, tuple, ))):
|
|
8940
|
+
pass
|
|
8941
|
+
else:
|
|
8942
|
+
infile = [infile]
|
|
8943
|
+
outretval = {}
|
|
8944
|
+
for curfname in infile:
|
|
8945
|
+
outretval[curfname] = CatFileListFiles(infile, fmttype, filestart, seekstart, seekend, skipchecksum, formatspecs, seektoend, verbose, newstyle, returnfp)
|
|
8946
|
+
return outretval
|
|
8947
|
+
|
|
8948
|
+
|
|
8949
|
+
def StackedCatFileListFiles(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, newstyle=False, returnfp=False):
|
|
8950
|
+
outretval = []
|
|
8951
|
+
outstartfile = filestart
|
|
8952
|
+
outfsize = float('inf')
|
|
8953
|
+
while True:
|
|
8954
|
+
if outstartfile >= outfsize: # stop when function signals False
|
|
8955
|
+
break
|
|
8956
|
+
list_file_retu = ArchiveFileListFiles(infile, fmttype, outstartfile, seekstart, seekend, skipchecksum, formatspecs, seektoend, verbose, newstyle, True)
|
|
8957
|
+
if list_file_retu is False: # stop when function signals False
|
|
8958
|
+
outretval.append(list_file_retu)
|
|
8959
|
+
else:
|
|
8960
|
+
outretval.append(True)
|
|
8961
|
+
infile = list_file_retu
|
|
8962
|
+
outstartfile = infile.tell()
|
|
8963
|
+
try:
|
|
8964
|
+
infile.seek(0, 2)
|
|
8965
|
+
except OSError:
|
|
8966
|
+
SeekToEndOfFile(infile)
|
|
8967
|
+
except ValueError:
|
|
8968
|
+
SeekToEndOfFile(infile)
|
|
8969
|
+
outfsize = infile.tell()
|
|
8970
|
+
infile.seek(outstartfile, 0)
|
|
8971
|
+
if(returnfp):
|
|
8972
|
+
return infile
|
|
8973
|
+
else:
|
|
8974
|
+
infile.close()
|
|
8975
|
+
return outretval
|
|
8976
|
+
|
|
8977
|
+
|
|
8978
|
+
def MultipleStackedCatFileListFiles(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, returnfp=False):
|
|
8979
|
+
if(isinstance(infile, (list, tuple, ))):
|
|
8980
|
+
pass
|
|
8981
|
+
else:
|
|
8982
|
+
infile = [infile]
|
|
8983
|
+
outretval = {}
|
|
8984
|
+
for curfname in infile:
|
|
8985
|
+
outretval[curfname] = StackedArchiveListFiles(curfname, fmttype, filestart, seekstart, seekend, listonly, contentasfile, uncompress, skipchecksum, formatspecs, seektoend, returnfp)
|
|
8986
|
+
return outretval
|
|
8987
|
+
|
|
8988
|
+
|
|
8989
|
+
def CatFileStringListFiles(instr, filestart=0, seekstart=0, seekend=0, skipchecksum=False, formatspecs=__file_format_multi_dict__, seektoend=False, verbose=False, newstyle=False, returnfp=False):
|
|
8352
8990
|
fp = MkTempFile(instr)
|
|
8353
|
-
listarrayfiles = CatFileListFiles(
|
|
8354
|
-
instr, seekstart, seekend, skipchecksum, formatspecs, seektoend, verbose, newstyle, returnfp)
|
|
8991
|
+
listarrayfiles = CatFileListFiles(instr, "auto", filestart, seekstart, seekend, skipchecksum, formatspecs, seektoend, verbose, newstyle, returnfp)
|
|
8355
8992
|
return listarrayfiles
|
|
8356
8993
|
|
|
8357
8994
|
|
|
@@ -8943,11 +9580,11 @@ def download_file_from_ftp_file(url):
|
|
|
8943
9580
|
file_name = os.path.basename(unquote(urlparts.path))
|
|
8944
9581
|
file_dir = os.path.dirname(unquote(urlparts.path))
|
|
8945
9582
|
if(urlparts.username is not None):
|
|
8946
|
-
ftp_username = urlparts.username
|
|
9583
|
+
ftp_username = unquote(urlparts.username)
|
|
8947
9584
|
else:
|
|
8948
9585
|
ftp_username = "anonymous"
|
|
8949
9586
|
if(urlparts.password is not None):
|
|
8950
|
-
ftp_password = urlparts.password
|
|
9587
|
+
ftp_password = unquote(urlparts.password)
|
|
8951
9588
|
elif(urlparts.password is None and urlparts.username == "anonymous"):
|
|
8952
9589
|
ftp_password = "anonymous"
|
|
8953
9590
|
else:
|
|
@@ -8958,13 +9595,6 @@ def download_file_from_ftp_file(url):
|
|
|
8958
9595
|
ftp = FTP_TLS()
|
|
8959
9596
|
else:
|
|
8960
9597
|
return False
|
|
8961
|
-
if(urlparts.scheme == "sftp" or urlparts.scheme == "scp"):
|
|
8962
|
-
if(__use_pysftp__):
|
|
8963
|
-
return download_file_from_pysftp_file(url)
|
|
8964
|
-
else:
|
|
8965
|
-
return download_file_from_sftp_file(url)
|
|
8966
|
-
elif(urlparts.scheme == "http" or urlparts.scheme == "https"):
|
|
8967
|
-
return download_file_from_http_file(url)
|
|
8968
9598
|
ftp_port = urlparts.port
|
|
8969
9599
|
if(urlparts.port is None):
|
|
8970
9600
|
ftp_port = 21
|
|
@@ -9041,11 +9671,11 @@ def upload_file_to_ftp_file(ftpfile, url):
|
|
|
9041
9671
|
file_name = os.path.basename(unquote(urlparts.path))
|
|
9042
9672
|
file_dir = os.path.dirname(unquote(urlparts.path))
|
|
9043
9673
|
if(urlparts.username is not None):
|
|
9044
|
-
ftp_username = urlparts.username
|
|
9674
|
+
ftp_username = unquote(urlparts.username)
|
|
9045
9675
|
else:
|
|
9046
9676
|
ftp_username = "anonymous"
|
|
9047
9677
|
if(urlparts.password is not None):
|
|
9048
|
-
ftp_password = urlparts.password
|
|
9678
|
+
ftp_password = unquote(urlparts.password)
|
|
9049
9679
|
elif(urlparts.password is None and urlparts.username == "anonymous"):
|
|
9050
9680
|
ftp_password = "anonymous"
|
|
9051
9681
|
else:
|
|
@@ -9056,13 +9686,6 @@ def upload_file_to_ftp_file(ftpfile, url):
|
|
|
9056
9686
|
ftp = FTP_TLS()
|
|
9057
9687
|
else:
|
|
9058
9688
|
return False
|
|
9059
|
-
if(urlparts.scheme == "sftp" or urlparts.scheme == "scp"):
|
|
9060
|
-
if(__use_pysftp__):
|
|
9061
|
-
return upload_file_to_pysftp_file(url)
|
|
9062
|
-
else:
|
|
9063
|
-
return upload_file_to_sftp_file(url)
|
|
9064
|
-
elif(urlparts.scheme == "http" or urlparts.scheme == "https"):
|
|
9065
|
-
return False
|
|
9066
9689
|
ftp_port = urlparts.port
|
|
9067
9690
|
if(urlparts.port is None):
|
|
9068
9691
|
ftp_port = 21
|
|
@@ -9160,8 +9783,8 @@ def download_file_from_http_file(url, headers=None, usehttp=__use_http_lib__):
|
|
|
9160
9783
|
if headers is None:
|
|
9161
9784
|
headers = {}
|
|
9162
9785
|
urlparts = urlparse(url)
|
|
9163
|
-
username = urlparts.username
|
|
9164
|
-
password = urlparts.password
|
|
9786
|
+
username = unquote(urlparts.username)
|
|
9787
|
+
password = unquote(urlparts.password)
|
|
9165
9788
|
|
|
9166
9789
|
# Rebuild URL without username and password
|
|
9167
9790
|
netloc = urlparts.hostname or ''
|
|
@@ -9170,15 +9793,6 @@ def download_file_from_http_file(url, headers=None, usehttp=__use_http_lib__):
|
|
|
9170
9793
|
rebuilt_url = urlunparse((urlparts.scheme, netloc, urlparts.path,
|
|
9171
9794
|
urlparts.params, urlparts.query, urlparts.fragment))
|
|
9172
9795
|
|
|
9173
|
-
# Handle SFTP/FTP
|
|
9174
|
-
if urlparts.scheme == "sftp" or urlparts.scheme == "scp":
|
|
9175
|
-
if __use_pysftp__:
|
|
9176
|
-
return download_file_from_pysftp_file(url)
|
|
9177
|
-
else:
|
|
9178
|
-
return download_file_from_sftp_file(url)
|
|
9179
|
-
elif urlparts.scheme == "ftp" or urlparts.scheme == "ftps":
|
|
9180
|
-
return download_file_from_ftp_file(url)
|
|
9181
|
-
|
|
9182
9796
|
# Create a temporary file object
|
|
9183
9797
|
httpfile = MkTempFile()
|
|
9184
9798
|
|
|
@@ -9242,6 +9856,184 @@ def download_file_from_http_file(url, headers=None, usehttp=__use_http_lib__):
|
|
|
9242
9856
|
return httpfile
|
|
9243
9857
|
|
|
9244
9858
|
|
|
9859
|
+
def upload_file_to_http_file(
|
|
9860
|
+
fileobj,
|
|
9861
|
+
url,
|
|
9862
|
+
method="POST", # "POST" or "PUT"
|
|
9863
|
+
headers=None,
|
|
9864
|
+
form=None, # dict of extra form fields → triggers multipart/form-data
|
|
9865
|
+
field_name="file", # form field name for the file content
|
|
9866
|
+
filename=None, # defaults to basename of URL path
|
|
9867
|
+
content_type="application/octet-stream",
|
|
9868
|
+
usehttp=__use_http_lib__, # 'requests' | 'httpx' | 'mechanize' | anything → urllib fallback
|
|
9869
|
+
):
|
|
9870
|
+
"""
|
|
9871
|
+
Py2+Py3 compatible HTTP/HTTPS upload.
|
|
9872
|
+
|
|
9873
|
+
- If `form` is provided (dict), uses multipart/form-data:
|
|
9874
|
+
* text fields from `form`
|
|
9875
|
+
* file part named by `field_name` with given `filename` and `content_type`
|
|
9876
|
+
- If `form` is None, uploads raw body as POST/PUT with Content-Type.
|
|
9877
|
+
- Returns True on HTTP 2xx, else False.
|
|
9878
|
+
"""
|
|
9879
|
+
if headers is None:
|
|
9880
|
+
headers = {}
|
|
9881
|
+
method = (method or "POST").upper()
|
|
9882
|
+
|
|
9883
|
+
rebuilt_url, username, password = _rewrite_url_without_auth(url)
|
|
9884
|
+
filename = _guess_filename(url, filename)
|
|
9885
|
+
|
|
9886
|
+
# rewind if possible
|
|
9887
|
+
try:
|
|
9888
|
+
fileobj.seek(0)
|
|
9889
|
+
except Exception:
|
|
9890
|
+
pass
|
|
9891
|
+
|
|
9892
|
+
# ========== 1) requests (Py2+Py3) ==========
|
|
9893
|
+
if usehttp == 'requests' and haverequests:
|
|
9894
|
+
import requests
|
|
9895
|
+
|
|
9896
|
+
auth = (username, password) if (username or password) else None
|
|
9897
|
+
|
|
9898
|
+
if form is not None:
|
|
9899
|
+
# multipart/form-data
|
|
9900
|
+
files = {field_name: (filename, fileobj, content_type)}
|
|
9901
|
+
data = form or {}
|
|
9902
|
+
resp = requests.request(method, rebuilt_url, headers=headers, auth=auth,
|
|
9903
|
+
files=files, data=data, timeout=(5, 120))
|
|
9904
|
+
else:
|
|
9905
|
+
# raw body
|
|
9906
|
+
hdrs = {'Content-Type': content_type}
|
|
9907
|
+
hdrs.update(headers)
|
|
9908
|
+
# best-effort content-length (helps some servers)
|
|
9909
|
+
if hasattr(fileobj, 'seek') and hasattr(fileobj, 'tell'):
|
|
9910
|
+
try:
|
|
9911
|
+
cur = fileobj.tell()
|
|
9912
|
+
fileobj.seek(0, io.SEEK_END if hasattr(io, 'SEEK_END') else 2)
|
|
9913
|
+
size = fileobj.tell() - cur
|
|
9914
|
+
fileobj.seek(cur)
|
|
9915
|
+
hdrs.setdefault('Content-Length', str(size))
|
|
9916
|
+
except Exception:
|
|
9917
|
+
pass
|
|
9918
|
+
resp = requests.request(method, rebuilt_url, headers=hdrs, auth=auth,
|
|
9919
|
+
data=fileobj, timeout=(5, 300))
|
|
9920
|
+
|
|
9921
|
+
return (200 <= resp.status_code < 300)
|
|
9922
|
+
|
|
9923
|
+
# ========== 2) httpx (Py3 only) ==========
|
|
9924
|
+
if usehttp == 'httpx' and havehttpx and not PY2:
|
|
9925
|
+
import httpx
|
|
9926
|
+
auth = (username, password) if (username or password) else None
|
|
9927
|
+
|
|
9928
|
+
with httpx.Client(follow_redirects=True, timeout=60) as client:
|
|
9929
|
+
if form is not None:
|
|
9930
|
+
files = {field_name: (filename, fileobj, content_type)}
|
|
9931
|
+
data = form or {}
|
|
9932
|
+
resp = client.request(method, rebuilt_url, headers=headers, auth=auth,
|
|
9933
|
+
files=files, data=data)
|
|
9934
|
+
else:
|
|
9935
|
+
hdrs = {'Content-Type': content_type}
|
|
9936
|
+
hdrs.update(headers)
|
|
9937
|
+
resp = client.request(method, rebuilt_url, headers=hdrs, auth=auth,
|
|
9938
|
+
content=fileobj)
|
|
9939
|
+
return (200 <= resp.status_code < 300)
|
|
9940
|
+
|
|
9941
|
+
# ========== 3) mechanize (forms) → prefer requests if available ==========
|
|
9942
|
+
if usehttp == 'mechanize' and havemechanize:
|
|
9943
|
+
# mechanize is great for HTML forms, but file upload requires form discovery.
|
|
9944
|
+
# For a generic upload helper, prefer requests. If not available, fall through.
|
|
9945
|
+
try:
|
|
9946
|
+
import requests # noqa
|
|
9947
|
+
# delegate to requests path to ensure robust multipart handling
|
|
9948
|
+
return upload_file_to_http_file(
|
|
9949
|
+
fileobj, url, method=method, headers=headers,
|
|
9950
|
+
form=(form or {}), field_name=field_name,
|
|
9951
|
+
filename=filename, content_type=content_type,
|
|
9952
|
+
usehttp='requests'
|
|
9953
|
+
)
|
|
9954
|
+
except Exception:
|
|
9955
|
+
pass # fall through to urllib
|
|
9956
|
+
|
|
9957
|
+
# ========== 4) urllib fallback (Py2+Py3) ==========
|
|
9958
|
+
# multipart builder (no f-strings)
|
|
9959
|
+
boundary = ('----pyuploader-%s' % uuid.uuid4().hex)
|
|
9960
|
+
|
|
9961
|
+
if form is not None:
|
|
9962
|
+
# Build multipart body to a temp file-like (your MkTempFile())
|
|
9963
|
+
buf = MkTempFile()
|
|
9964
|
+
|
|
9965
|
+
def _w(s):
|
|
9966
|
+
buf.write(_to_bytes(s))
|
|
9967
|
+
|
|
9968
|
+
# text fields
|
|
9969
|
+
if form:
|
|
9970
|
+
for k, v in form.items():
|
|
9971
|
+
_w('--' + boundary + '\r\n')
|
|
9972
|
+
_w('Content-Disposition: form-data; name="%s"\r\n\r\n' % k)
|
|
9973
|
+
_w('' if v is None else (v if isinstance(v, (str, bytes)) else str(v)))
|
|
9974
|
+
_w('\r\n')
|
|
9975
|
+
|
|
9976
|
+
# file field
|
|
9977
|
+
_w('--' + boundary + '\r\n')
|
|
9978
|
+
_w('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (field_name, filename))
|
|
9979
|
+
_w('Content-Type: %s\r\n\r\n' % content_type)
|
|
9980
|
+
|
|
9981
|
+
try:
|
|
9982
|
+
fileobj.seek(0)
|
|
9983
|
+
except Exception:
|
|
9984
|
+
pass
|
|
9985
|
+
shutil.copyfileobj(fileobj, buf)
|
|
9986
|
+
|
|
9987
|
+
_w('\r\n')
|
|
9988
|
+
_w('--' + boundary + '--\r\n')
|
|
9989
|
+
|
|
9990
|
+
buf.seek(0)
|
|
9991
|
+
data = buf.read()
|
|
9992
|
+
hdrs = {'Content-Type': 'multipart/form-data; boundary=%s' % boundary}
|
|
9993
|
+
hdrs.update(headers)
|
|
9994
|
+
req = Request(rebuilt_url, data=data)
|
|
9995
|
+
# method override for Py3; Py2 Request ignores 'method' kw
|
|
9996
|
+
if not PY2:
|
|
9997
|
+
req.method = method # type: ignore[attr-defined]
|
|
9998
|
+
else:
|
|
9999
|
+
# raw body
|
|
10000
|
+
try:
|
|
10001
|
+
fileobj.seek(0)
|
|
10002
|
+
except Exception:
|
|
10003
|
+
pass
|
|
10004
|
+
data = fileobj.read()
|
|
10005
|
+
hdrs = {'Content-Type': content_type}
|
|
10006
|
+
hdrs.update(headers)
|
|
10007
|
+
req = Request(rebuilt_url, data=data)
|
|
10008
|
+
if not PY2:
|
|
10009
|
+
req.method = method # type: ignore[attr-defined]
|
|
10010
|
+
|
|
10011
|
+
for k, v in hdrs.items():
|
|
10012
|
+
req.add_header(k, v)
|
|
10013
|
+
|
|
10014
|
+
# Basic auth if present
|
|
10015
|
+
if username or password:
|
|
10016
|
+
pwd_mgr = HTTPPasswordMgrWithDefaultRealm()
|
|
10017
|
+
pwd_mgr.add_password(None, rebuilt_url, username, password)
|
|
10018
|
+
opener = build_opener(HTTPBasicAuthHandler(pwd_mgr))
|
|
10019
|
+
else:
|
|
10020
|
+
opener = build_opener()
|
|
10021
|
+
|
|
10022
|
+
# Py2 OpenerDirector.open takes timeout since 2.6; to be safe, avoid passing if it explodes
|
|
10023
|
+
try:
|
|
10024
|
+
resp = opener.open(req, timeout=60)
|
|
10025
|
+
except TypeError:
|
|
10026
|
+
resp = opener.open(req)
|
|
10027
|
+
|
|
10028
|
+
# Status code compat
|
|
10029
|
+
code = getattr(resp, 'status', None) or getattr(resp, 'code', None) or 0
|
|
10030
|
+
try:
|
|
10031
|
+
resp.close()
|
|
10032
|
+
except Exception:
|
|
10033
|
+
pass
|
|
10034
|
+
return (200 <= int(code) < 300)
|
|
10035
|
+
|
|
10036
|
+
|
|
9245
10037
|
def download_file_from_http_string(url, headers=geturls_headers_pyfile_python_alt, usehttp=__use_http_lib__):
|
|
9246
10038
|
httpfile = download_file_from_http_file(url, headers, usehttp)
|
|
9247
10039
|
httpout = httpfile.read()
|
|
@@ -9260,19 +10052,15 @@ if(haveparamiko):
|
|
|
9260
10052
|
else:
|
|
9261
10053
|
sftp_port = urlparts.port
|
|
9262
10054
|
if(urlparts.username is not None):
|
|
9263
|
-
sftp_username = urlparts.username
|
|
10055
|
+
sftp_username = unquote(urlparts.username)
|
|
9264
10056
|
else:
|
|
9265
10057
|
sftp_username = "anonymous"
|
|
9266
10058
|
if(urlparts.password is not None):
|
|
9267
|
-
sftp_password = urlparts.password
|
|
10059
|
+
sftp_password = unquote(urlparts.password)
|
|
9268
10060
|
elif(urlparts.password is None and urlparts.username == "anonymous"):
|
|
9269
10061
|
sftp_password = "anonymous"
|
|
9270
10062
|
else:
|
|
9271
10063
|
sftp_password = ""
|
|
9272
|
-
if(urlparts.scheme == "ftp"):
|
|
9273
|
-
return download_file_from_ftp_file(url)
|
|
9274
|
-
elif(urlparts.scheme == "http" or urlparts.scheme == "https"):
|
|
9275
|
-
return download_file_from_http_file(url)
|
|
9276
10064
|
if(urlparts.scheme != "sftp" and urlparts.scheme != "scp"):
|
|
9277
10065
|
return False
|
|
9278
10066
|
ssh = paramiko.SSHClient()
|
|
@@ -9280,7 +10068,7 @@ if(haveparamiko):
|
|
|
9280
10068
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
9281
10069
|
try:
|
|
9282
10070
|
ssh.connect(urlparts.hostname, port=sftp_port,
|
|
9283
|
-
username=sftp_username, password=
|
|
10071
|
+
username=sftp_username, password=sftp_password)
|
|
9284
10072
|
except paramiko.ssh_exception.SSHException:
|
|
9285
10073
|
return False
|
|
9286
10074
|
except socket.gaierror:
|
|
@@ -9321,19 +10109,15 @@ if(haveparamiko):
|
|
|
9321
10109
|
else:
|
|
9322
10110
|
sftp_port = urlparts.port
|
|
9323
10111
|
if(urlparts.username is not None):
|
|
9324
|
-
sftp_username = urlparts.username
|
|
10112
|
+
sftp_username = unquote(urlparts.username)
|
|
9325
10113
|
else:
|
|
9326
10114
|
sftp_username = "anonymous"
|
|
9327
10115
|
if(urlparts.password is not None):
|
|
9328
|
-
sftp_password = urlparts.password
|
|
10116
|
+
sftp_password = unquote(urlparts.password)
|
|
9329
10117
|
elif(urlparts.password is None and urlparts.username == "anonymous"):
|
|
9330
10118
|
sftp_password = "anonymous"
|
|
9331
10119
|
else:
|
|
9332
10120
|
sftp_password = ""
|
|
9333
|
-
if(urlparts.scheme == "ftp"):
|
|
9334
|
-
return upload_file_to_ftp_file(sftpfile, url)
|
|
9335
|
-
elif(urlparts.scheme == "http" or urlparts.scheme == "https"):
|
|
9336
|
-
return False
|
|
9337
10121
|
if(urlparts.scheme != "sftp" and urlparts.scheme != "scp"):
|
|
9338
10122
|
return False
|
|
9339
10123
|
ssh = paramiko.SSHClient()
|
|
@@ -9382,19 +10166,15 @@ if(havepysftp):
|
|
|
9382
10166
|
else:
|
|
9383
10167
|
sftp_port = urlparts.port
|
|
9384
10168
|
if(urlparts.username is not None):
|
|
9385
|
-
sftp_username = urlparts.username
|
|
10169
|
+
sftp_username = unquote(urlparts.username)
|
|
9386
10170
|
else:
|
|
9387
10171
|
sftp_username = "anonymous"
|
|
9388
10172
|
if(urlparts.password is not None):
|
|
9389
|
-
sftp_password = urlparts.password
|
|
10173
|
+
sftp_password = unquote(urlparts.password)
|
|
9390
10174
|
elif(urlparts.password is None and urlparts.username == "anonymous"):
|
|
9391
10175
|
sftp_password = "anonymous"
|
|
9392
10176
|
else:
|
|
9393
10177
|
sftp_password = ""
|
|
9394
|
-
if(urlparts.scheme == "ftp"):
|
|
9395
|
-
return download_file_from_ftp_file(url)
|
|
9396
|
-
elif(urlparts.scheme == "http" or urlparts.scheme == "https"):
|
|
9397
|
-
return download_file_from_http_file(url)
|
|
9398
10178
|
if(urlparts.scheme != "sftp" and urlparts.scheme != "scp"):
|
|
9399
10179
|
return False
|
|
9400
10180
|
try:
|
|
@@ -9439,19 +10219,15 @@ if(havepysftp):
|
|
|
9439
10219
|
else:
|
|
9440
10220
|
sftp_port = urlparts.port
|
|
9441
10221
|
if(urlparts.username is not None):
|
|
9442
|
-
sftp_username = urlparts.username
|
|
10222
|
+
sftp_username = unquote(urlparts.username)
|
|
9443
10223
|
else:
|
|
9444
10224
|
sftp_username = "anonymous"
|
|
9445
10225
|
if(urlparts.password is not None):
|
|
9446
|
-
sftp_password = urlparts.password
|
|
10226
|
+
sftp_password = unquote(urlparts.password)
|
|
9447
10227
|
elif(urlparts.password is None and urlparts.username == "anonymous"):
|
|
9448
10228
|
sftp_password = "anonymous"
|
|
9449
10229
|
else:
|
|
9450
10230
|
sftp_password = ""
|
|
9451
|
-
if(urlparts.scheme == "ftp"):
|
|
9452
|
-
return upload_file_to_ftp_file(sftpfile, url)
|
|
9453
|
-
elif(urlparts.scheme == "http" or urlparts.scheme == "https"):
|
|
9454
|
-
return False
|
|
9455
10231
|
if(urlparts.scheme != "sftp" and urlparts.scheme != "scp"):
|
|
9456
10232
|
return False
|
|
9457
10233
|
try:
|
|
@@ -9497,6 +10273,13 @@ def download_file_from_internet_file(url, headers=geturls_headers_pyfile_python_
|
|
|
9497
10273
|
return download_file_from_pysftp_file(url)
|
|
9498
10274
|
else:
|
|
9499
10275
|
return download_file_from_sftp_file(url)
|
|
10276
|
+
elif(urlparts.scheme == "tcp" or urlparts.scheme == "udp"):
|
|
10277
|
+
outfile = MkTempFile()
|
|
10278
|
+
returnval = recv_via_url(outfile, url, recv_to_fileobj)
|
|
10279
|
+
if(not returnval):
|
|
10280
|
+
return False
|
|
10281
|
+
outfile.seek(0, 0)
|
|
10282
|
+
return outfile
|
|
9500
10283
|
else:
|
|
9501
10284
|
return False
|
|
9502
10285
|
return False
|
|
@@ -9549,6 +10332,12 @@ def upload_file_to_internet_file(ifp, url):
|
|
|
9549
10332
|
return upload_file_to_pysftp_file(ifp, url)
|
|
9550
10333
|
else:
|
|
9551
10334
|
return upload_file_to_sftp_file(ifp, url)
|
|
10335
|
+
elif(urlparts.scheme == "tcp" or urlparts.scheme == "udp"):
|
|
10336
|
+
ifp.seek(0, 0)
|
|
10337
|
+
returnval = send_via_url(ifp, url, send_from_fileobj)
|
|
10338
|
+
if(not returnval):
|
|
10339
|
+
return False
|
|
10340
|
+
return returnval
|
|
9552
10341
|
else:
|
|
9553
10342
|
return False
|
|
9554
10343
|
return False
|
|
@@ -9557,7 +10346,7 @@ def upload_file_to_internet_file(ifp, url):
|
|
|
9557
10346
|
def upload_file_to_internet_compress_file(ifp, url, compression="auto", compressionlevel=None, compressionuselist=compressionlistalt, formatspecs=__file_format_dict__):
|
|
9558
10347
|
fp = CompressOpenFileAlt(
|
|
9559
10348
|
fp, compression, compressionlevel, compressionuselist, formatspecs)
|
|
9560
|
-
if(not
|
|
10349
|
+
if(not archivefileout):
|
|
9561
10350
|
return False
|
|
9562
10351
|
fp.seek(0, 0)
|
|
9563
10352
|
return upload_file_to_internet_file(fp, outfile)
|
|
@@ -9583,7 +10372,602 @@ def upload_file_to_internet_compress_string(ifp, url, compression="auto", compre
|
|
|
9583
10372
|
internetfileo = MkTempFile(ifp)
|
|
9584
10373
|
fp = CompressOpenFileAlt(
|
|
9585
10374
|
internetfileo, compression, compressionlevel, compressionuselist, formatspecs)
|
|
9586
|
-
if(not
|
|
10375
|
+
if(not archivefileout):
|
|
9587
10376
|
return False
|
|
9588
10377
|
fp.seek(0, 0)
|
|
9589
10378
|
return upload_file_to_internet_file(fp, outfile)
|
|
10379
|
+
|
|
10380
|
+
|
|
10381
|
+
# ---------- Core: send / recv ----------
|
|
10382
|
+
def send_from_fileobj(fileobj, host, port, proto="tcp", timeout=None,
|
|
10383
|
+
chunk_size=65536,
|
|
10384
|
+
use_ssl=False, ssl_verify=True, ssl_ca_file=None,
|
|
10385
|
+
ssl_certfile=None, ssl_keyfile=None, server_hostname=None,
|
|
10386
|
+
auth_user=None, auth_pass=None, auth_scope=u"",
|
|
10387
|
+
on_progress=None, rate_limit_bps=None, want_sha=True):
|
|
10388
|
+
"""
|
|
10389
|
+
Send fileobj contents to (host, port) via TCP or UDP.
|
|
10390
|
+
|
|
10391
|
+
UDP behavior:
|
|
10392
|
+
- Computes total length and sha256 when possible.
|
|
10393
|
+
- Sends: AF1 (if auth) + 'LEN <n> [<sha>]\\n' + payload
|
|
10394
|
+
- If length unknown: stream payload, then 'HASH <sha>\\n' (if enabled), then 'DONE\\n'.
|
|
10395
|
+
- Uses small datagrams (<=1200B) to avoid fragmentation.
|
|
10396
|
+
"""
|
|
10397
|
+
proto = (proto or "tcp").lower()
|
|
10398
|
+
total = 0
|
|
10399
|
+
port = int(port)
|
|
10400
|
+
if proto not in ("tcp", "udp"):
|
|
10401
|
+
raise ValueError("proto must be 'tcp' or 'udp'")
|
|
10402
|
+
|
|
10403
|
+
# ---------------- UDP ----------------
|
|
10404
|
+
if proto == "udp":
|
|
10405
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
10406
|
+
try:
|
|
10407
|
+
if timeout is not None:
|
|
10408
|
+
sock.settimeout(timeout)
|
|
10409
|
+
|
|
10410
|
+
# connect UDP for convenience
|
|
10411
|
+
try:
|
|
10412
|
+
sock.connect((host, port))
|
|
10413
|
+
connected = True
|
|
10414
|
+
except Exception:
|
|
10415
|
+
connected = False
|
|
10416
|
+
|
|
10417
|
+
# length + optional sha
|
|
10418
|
+
total_bytes, start_pos = _discover_len_and_reset(fileobj)
|
|
10419
|
+
|
|
10420
|
+
sha_hex = None
|
|
10421
|
+
if want_sha and total_bytes is not None:
|
|
10422
|
+
import hashlib
|
|
10423
|
+
h = hashlib.sha256()
|
|
10424
|
+
try:
|
|
10425
|
+
cur = fileobj.tell()
|
|
10426
|
+
except Exception:
|
|
10427
|
+
cur = None
|
|
10428
|
+
if start_pos is not None:
|
|
10429
|
+
try: fileobj.seek(start_pos, os.SEEK_SET)
|
|
10430
|
+
except Exception: pass
|
|
10431
|
+
_HSZ = 1024 * 1024
|
|
10432
|
+
while True:
|
|
10433
|
+
blk = fileobj.read(_HSZ)
|
|
10434
|
+
if not blk: break
|
|
10435
|
+
h.update(_to_bytes(blk))
|
|
10436
|
+
sha_hex = h.hexdigest()
|
|
10437
|
+
if start_pos is not None:
|
|
10438
|
+
try: fileobj.seek(start_pos, os.SEEK_SET)
|
|
10439
|
+
except Exception: pass
|
|
10440
|
+
elif cur is not None:
|
|
10441
|
+
try: fileobj.seek(cur, os.SEEK_SET)
|
|
10442
|
+
except Exception: pass
|
|
10443
|
+
|
|
10444
|
+
# optional AF1 (also carries len/sha, but we'll still send LEN for robustness)
|
|
10445
|
+
if auth_user is not None or auth_pass is not None:
|
|
10446
|
+
try:
|
|
10447
|
+
blob = build_auth_blob_v1(
|
|
10448
|
+
auth_user or u"", auth_pass or u"",
|
|
10449
|
+
scope=auth_scope, length=total_bytes, sha_hex=(sha_hex if want_sha else None)
|
|
10450
|
+
)
|
|
10451
|
+
except Exception:
|
|
10452
|
+
blob = _build_auth_blob_legacy(auth_user or b"", auth_pass or b"")
|
|
10453
|
+
if connected:
|
|
10454
|
+
sock.send(blob)
|
|
10455
|
+
# You may ignore the ack in UDP; keep try/except minimal
|
|
10456
|
+
try:
|
|
10457
|
+
resp = sock.recv(16)
|
|
10458
|
+
if resp != _OK:
|
|
10459
|
+
raise RuntimeError("UDP auth failed")
|
|
10460
|
+
except Exception:
|
|
10461
|
+
pass
|
|
10462
|
+
else:
|
|
10463
|
+
sock.sendto(blob, (host, port))
|
|
10464
|
+
try:
|
|
10465
|
+
resp, _ = sock.recvfrom(16)
|
|
10466
|
+
if resp != _OK:
|
|
10467
|
+
raise RuntimeError("UDP auth failed")
|
|
10468
|
+
except Exception:
|
|
10469
|
+
pass
|
|
10470
|
+
|
|
10471
|
+
# ALWAYS send LEN when length is known
|
|
10472
|
+
if total_bytes is not None:
|
|
10473
|
+
preface = b"LEN " + str(int(total_bytes)).encode("ascii")
|
|
10474
|
+
if want_sha and sha_hex:
|
|
10475
|
+
preface += b" " + sha_hex.encode("ascii")
|
|
10476
|
+
preface += b"\n"
|
|
10477
|
+
if connected: sock.send(preface)
|
|
10478
|
+
else: sock.sendto(preface, (host, port))
|
|
10479
|
+
|
|
10480
|
+
# payload stream
|
|
10481
|
+
UDP_PAYLOAD_MAX = 1200
|
|
10482
|
+
effective_chunk = min(int(chunk_size or 65536), UDP_PAYLOAD_MAX)
|
|
10483
|
+
|
|
10484
|
+
sent_so_far = 0
|
|
10485
|
+
last_cb_ts = monotonic()
|
|
10486
|
+
last_rate_ts = last_cb_ts
|
|
10487
|
+
last_rate_bytes = 0
|
|
10488
|
+
|
|
10489
|
+
rolling_h = None
|
|
10490
|
+
if want_sha and total_bytes is None:
|
|
10491
|
+
try:
|
|
10492
|
+
import hashlib
|
|
10493
|
+
rolling_h = hashlib.sha256()
|
|
10494
|
+
except Exception:
|
|
10495
|
+
rolling_h = None
|
|
10496
|
+
|
|
10497
|
+
while True:
|
|
10498
|
+
chunk = fileobj.read(effective_chunk)
|
|
10499
|
+
if not chunk:
|
|
10500
|
+
break
|
|
10501
|
+
b = _to_bytes(chunk)
|
|
10502
|
+
if rolling_h is not None:
|
|
10503
|
+
rolling_h.update(b)
|
|
10504
|
+
n = (sock.send(b) if connected else sock.sendto(b, (host, port)))
|
|
10505
|
+
total += n
|
|
10506
|
+
sent_so_far += n
|
|
10507
|
+
|
|
10508
|
+
if rate_limit_bps:
|
|
10509
|
+
sleep_s, last_rate_ts, last_rate_bytes = _progress_tick(
|
|
10510
|
+
sent_so_far, total_bytes, last_rate_ts, last_rate_bytes, rate_limit_bps
|
|
10511
|
+
)
|
|
10512
|
+
if sleep_s > 0.0:
|
|
10513
|
+
time.sleep(min(sleep_s, 0.25))
|
|
10514
|
+
|
|
10515
|
+
if on_progress and (monotonic() - last_cb_ts) >= 0.1:
|
|
10516
|
+
try: on_progress(sent_so_far, total_bytes)
|
|
10517
|
+
except Exception: pass
|
|
10518
|
+
last_cb_ts = monotonic()
|
|
10519
|
+
|
|
10520
|
+
# unknown-length trailers
|
|
10521
|
+
if total_bytes is None:
|
|
10522
|
+
if rolling_h is not None:
|
|
10523
|
+
try:
|
|
10524
|
+
th = rolling_h.hexdigest().encode("ascii")
|
|
10525
|
+
(sock.send(b"HASH " + th + b"\n") if connected
|
|
10526
|
+
else sock.sendto(b"HASH " + th + b"\n", (host, port)))
|
|
10527
|
+
except Exception:
|
|
10528
|
+
pass
|
|
10529
|
+
try:
|
|
10530
|
+
(sock.send(b"DONE\n") if connected else sock.sendto(b"DONE\n", (host, port)))
|
|
10531
|
+
except Exception:
|
|
10532
|
+
pass
|
|
10533
|
+
|
|
10534
|
+
finally:
|
|
10535
|
+
try: sock.close()
|
|
10536
|
+
except Exception: pass
|
|
10537
|
+
return total
|
|
10538
|
+
|
|
10539
|
+
# ---------------- TCP ----------------
|
|
10540
|
+
sock = _connect_stream(host, port, timeout)
|
|
10541
|
+
try:
|
|
10542
|
+
if use_ssl:
|
|
10543
|
+
if not _ssl_available():
|
|
10544
|
+
raise RuntimeError("SSL requested but 'ssl' module unavailable.")
|
|
10545
|
+
sock = _ssl_wrap_socket(sock, server_side=False,
|
|
10546
|
+
server_hostname=(server_hostname or host),
|
|
10547
|
+
verify=ssl_verify, ca_file=ssl_ca_file,
|
|
10548
|
+
certfile=ssl_certfile, keyfile=ssl_keyfile)
|
|
10549
|
+
|
|
10550
|
+
total_bytes, start_pos = _discover_len_and_reset(fileobj)
|
|
10551
|
+
sha_hex = None
|
|
10552
|
+
if want_sha and total_bytes is not None:
|
|
10553
|
+
try:
|
|
10554
|
+
import hashlib
|
|
10555
|
+
h = hashlib.sha256()
|
|
10556
|
+
cur = fileobj.tell()
|
|
10557
|
+
if start_pos is not None:
|
|
10558
|
+
fileobj.seek(start_pos, os.SEEK_SET)
|
|
10559
|
+
_HSZ = 1024 * 1024
|
|
10560
|
+
while True:
|
|
10561
|
+
blk = fileobj.read(_HSZ)
|
|
10562
|
+
if not blk: break
|
|
10563
|
+
h.update(_to_bytes(blk))
|
|
10564
|
+
sha_hex = h.hexdigest()
|
|
10565
|
+
fileobj.seek(cur, os.SEEK_SET)
|
|
10566
|
+
except Exception:
|
|
10567
|
+
sha_hex = None
|
|
10568
|
+
|
|
10569
|
+
if auth_user is not None or auth_pass is not None:
|
|
10570
|
+
try:
|
|
10571
|
+
blob = build_auth_blob_v1(
|
|
10572
|
+
auth_user or u"", auth_pass or u"",
|
|
10573
|
+
scope=auth_scope, length=total_bytes, sha_hex=(sha_hex if want_sha else None)
|
|
10574
|
+
)
|
|
10575
|
+
except Exception:
|
|
10576
|
+
blob = _build_auth_blob_legacy(auth_user or b"", auth_pass or b"")
|
|
10577
|
+
sock.sendall(blob)
|
|
10578
|
+
try:
|
|
10579
|
+
resp = sock.recv(16)
|
|
10580
|
+
if resp != _OK:
|
|
10581
|
+
raise RuntimeError("TCP auth failed")
|
|
10582
|
+
except Exception:
|
|
10583
|
+
pass
|
|
10584
|
+
|
|
10585
|
+
sent_so_far = 0
|
|
10586
|
+
last_cb_ts = monotonic()
|
|
10587
|
+
last_rate_ts = last_cb_ts
|
|
10588
|
+
last_rate_bytes = 0
|
|
10589
|
+
|
|
10590
|
+
use_sendfile = hasattr(sock, "sendfile") and hasattr(fileobj, "read")
|
|
10591
|
+
if use_sendfile:
|
|
10592
|
+
try:
|
|
10593
|
+
sent = sock.sendfile(fileobj)
|
|
10594
|
+
if isinstance(sent, int):
|
|
10595
|
+
total += sent
|
|
10596
|
+
sent_so_far += sent
|
|
10597
|
+
if on_progress:
|
|
10598
|
+
try: on_progress(sent_so_far, total_bytes)
|
|
10599
|
+
except Exception: pass
|
|
10600
|
+
else:
|
|
10601
|
+
raise RuntimeError("sendfile returned unexpected type")
|
|
10602
|
+
except Exception:
|
|
10603
|
+
while True:
|
|
10604
|
+
chunk = fileobj.read(chunk_size)
|
|
10605
|
+
if not chunk: break
|
|
10606
|
+
view = memoryview(_to_bytes(chunk))
|
|
10607
|
+
while view:
|
|
10608
|
+
n = sock.send(view); total += n; sent_so_far += n; view = view[n:]
|
|
10609
|
+
if rate_limit_bps:
|
|
10610
|
+
sleep_s, last_rate_ts, last_rate_bytes = _progress_tick(
|
|
10611
|
+
sent_so_far, total_bytes, last_rate_ts, last_rate_bytes, rate_limit_bps
|
|
10612
|
+
)
|
|
10613
|
+
if sleep_s > 0.0:
|
|
10614
|
+
time.sleep(min(sleep_s, 0.25))
|
|
10615
|
+
if on_progress and (monotonic() - last_cb_ts) >= 0.1:
|
|
10616
|
+
try: on_progress(sent_so_far, total_bytes)
|
|
10617
|
+
except Exception: pass
|
|
10618
|
+
last_cb_ts = monotonic()
|
|
10619
|
+
else:
|
|
10620
|
+
while True:
|
|
10621
|
+
chunk = fileobj.read(chunk_size)
|
|
10622
|
+
if not chunk: break
|
|
10623
|
+
view = memoryview(_to_bytes(chunk))
|
|
10624
|
+
while view:
|
|
10625
|
+
n = sock.send(view); total += n; sent_so_far += n; view = view[n:]
|
|
10626
|
+
if rate_limit_bps:
|
|
10627
|
+
sleep_s, last_rate_ts, last_rate_bytes = _progress_tick(
|
|
10628
|
+
sent_so_far, total_bytes, last_rate_ts, last_rate_bytes, rate_limit_bps
|
|
10629
|
+
)
|
|
10630
|
+
if sleep_s > 0.0:
|
|
10631
|
+
time.sleep(min(sleep_s, 0.25))
|
|
10632
|
+
if on_progress and (monotonic() - last_cb_ts) >= 0.1:
|
|
10633
|
+
try: on_progress(sent_so_far, total_bytes)
|
|
10634
|
+
except Exception: pass
|
|
10635
|
+
last_cb_ts = monotonic()
|
|
10636
|
+
finally:
|
|
10637
|
+
try: sock.shutdown(socket.SHUT_WR)
|
|
10638
|
+
except Exception: pass
|
|
10639
|
+
try: sock.close()
|
|
10640
|
+
except Exception: pass
|
|
10641
|
+
return total
|
|
10642
|
+
|
|
10643
|
+
def recv_to_fileobj(fileobj, host="", port=0, proto="tcp", timeout=None,
|
|
10644
|
+
max_bytes=None, chunk_size=65536, backlog=1,
|
|
10645
|
+
use_ssl=False, ssl_verify=True, ssl_ca_file=None,
|
|
10646
|
+
ssl_certfile=None, ssl_keyfile=None,
|
|
10647
|
+
require_auth=False, expected_user=None, expected_pass=None,
|
|
10648
|
+
total_timeout=None, expect_scope=None,
|
|
10649
|
+
on_progress=None, rate_limit_bps=None):
|
|
10650
|
+
"""
|
|
10651
|
+
Receive bytes into fileobj over TCP/UDP.
|
|
10652
|
+
|
|
10653
|
+
UDP specifics:
|
|
10654
|
+
* Accepts 'LEN <n> [<sha>]\\n' and 'HASH <sha>\\n' control frames (unauth) or AF1 with len/sha.
|
|
10655
|
+
* If length unknown, accepts final 'DONE\\n' to end cleanly.
|
|
10656
|
+
"""
|
|
10657
|
+
proto = (proto or "tcp").lower()
|
|
10658
|
+
port = int(port)
|
|
10659
|
+
total = 0
|
|
10660
|
+
|
|
10661
|
+
start_ts = time.time()
|
|
10662
|
+
def _time_left():
|
|
10663
|
+
if total_timeout is None:
|
|
10664
|
+
return None
|
|
10665
|
+
left = total_timeout - (time.time() - start_ts)
|
|
10666
|
+
return 0.0 if left <= 0 else left
|
|
10667
|
+
def _set_effective_timeout(socklike, base_timeout):
|
|
10668
|
+
left = _time_left()
|
|
10669
|
+
if left == 0.0:
|
|
10670
|
+
return False
|
|
10671
|
+
eff = base_timeout
|
|
10672
|
+
if left is not None:
|
|
10673
|
+
eff = left if eff is None else min(eff, left)
|
|
10674
|
+
if eff is not None:
|
|
10675
|
+
try:
|
|
10676
|
+
socklike.settimeout(eff)
|
|
10677
|
+
except Exception:
|
|
10678
|
+
pass
|
|
10679
|
+
return True
|
|
10680
|
+
|
|
10681
|
+
if proto not in ("tcp", "udp"):
|
|
10682
|
+
raise ValueError("proto must be 'tcp' or 'udp'")
|
|
10683
|
+
|
|
10684
|
+
# ---------------- UDP server ----------------
|
|
10685
|
+
if proto == "udp":
|
|
10686
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
10687
|
+
authed_addr = None
|
|
10688
|
+
expected_len = None
|
|
10689
|
+
expected_sha = None
|
|
10690
|
+
|
|
10691
|
+
try:
|
|
10692
|
+
sock.bind(("", port))
|
|
10693
|
+
if timeout is None:
|
|
10694
|
+
try: sock.settimeout(10.0)
|
|
10695
|
+
except Exception: pass
|
|
10696
|
+
|
|
10697
|
+
recvd_so_far = 0
|
|
10698
|
+
last_cb_ts = monotonic()
|
|
10699
|
+
last_rate_ts = last_cb_ts
|
|
10700
|
+
last_rate_bytes = 0
|
|
10701
|
+
|
|
10702
|
+
while True:
|
|
10703
|
+
if _time_left() == 0.0:
|
|
10704
|
+
if expected_len is not None and total < expected_len:
|
|
10705
|
+
raise RuntimeError("UDP receive aborted by total_timeout before full payload received")
|
|
10706
|
+
break
|
|
10707
|
+
if (max_bytes is not None) and (total >= max_bytes):
|
|
10708
|
+
break
|
|
10709
|
+
|
|
10710
|
+
if not _set_effective_timeout(sock, timeout):
|
|
10711
|
+
if expected_len is not None and total < expected_len:
|
|
10712
|
+
raise RuntimeError("UDP receive timed out before full payload received")
|
|
10713
|
+
if expected_len is None and total > 0:
|
|
10714
|
+
raise RuntimeError("UDP receive timed out with unknown length; partial data")
|
|
10715
|
+
if expected_len is None and total == 0:
|
|
10716
|
+
raise RuntimeError("UDP receive: no packets received before timeout (is the sender running?)")
|
|
10717
|
+
break
|
|
10718
|
+
|
|
10719
|
+
try:
|
|
10720
|
+
data, addr = sock.recvfrom(chunk_size)
|
|
10721
|
+
except socket.timeout:
|
|
10722
|
+
if expected_len is not None and total < expected_len:
|
|
10723
|
+
raise RuntimeError("UDP receive idle-timeout before full payload received")
|
|
10724
|
+
if expected_len is None and total > 0:
|
|
10725
|
+
raise RuntimeError("UDP receive idle-timeout with unknown length; partial data")
|
|
10726
|
+
if expected_len is None and total == 0:
|
|
10727
|
+
raise RuntimeError("UDP receive: no packets received before timeout (is the sender running?)")
|
|
10728
|
+
break
|
|
10729
|
+
|
|
10730
|
+
if not data:
|
|
10731
|
+
continue
|
|
10732
|
+
|
|
10733
|
+
# (0) Control frames FIRST: LEN / HASH / DONE
|
|
10734
|
+
if data.startswith(b"LEN ") and expected_len is None:
|
|
10735
|
+
try:
|
|
10736
|
+
parts = data.strip().split()
|
|
10737
|
+
n = int(parts[1])
|
|
10738
|
+
expected_len = (None if n < 0 else n)
|
|
10739
|
+
if len(parts) >= 3:
|
|
10740
|
+
expected_sha = parts[2].decode("ascii")
|
|
10741
|
+
except Exception:
|
|
10742
|
+
expected_len = None
|
|
10743
|
+
expected_sha = None
|
|
10744
|
+
continue
|
|
10745
|
+
|
|
10746
|
+
if data.startswith(b"HASH "):
|
|
10747
|
+
try:
|
|
10748
|
+
expected_sha = data.strip().split()[1].decode("ascii")
|
|
10749
|
+
except Exception:
|
|
10750
|
+
expected_sha = None
|
|
10751
|
+
continue
|
|
10752
|
+
|
|
10753
|
+
if data == b"DONE\n":
|
|
10754
|
+
break
|
|
10755
|
+
|
|
10756
|
+
# (1) Auth (AF1 preferred; legacy fallback)
|
|
10757
|
+
if authed_addr is None and require_auth:
|
|
10758
|
+
ok = False
|
|
10759
|
+
v_ok, v_user, v_scope, _r, v_len, v_sha = verify_auth_blob_v1(
|
|
10760
|
+
data, expected_user=expected_user, secret=expected_pass,
|
|
10761
|
+
max_skew=600, expect_scope=expect_scope
|
|
10762
|
+
)
|
|
10763
|
+
if v_ok:
|
|
10764
|
+
ok = True
|
|
10765
|
+
if expected_len is None:
|
|
10766
|
+
expected_len = v_len
|
|
10767
|
+
if expected_sha is None:
|
|
10768
|
+
expected_sha = v_sha
|
|
10769
|
+
else:
|
|
10770
|
+
user, pw = _parse_auth_blob_legacy(data)
|
|
10771
|
+
ok = (user is not None and
|
|
10772
|
+
(expected_user is None or user == _to_bytes(expected_user)) and
|
|
10773
|
+
(expected_pass is None or pw == _to_bytes(expected_pass)))
|
|
10774
|
+
try:
|
|
10775
|
+
sock.sendto((_OK if ok else _NO), addr)
|
|
10776
|
+
except Exception:
|
|
10777
|
+
pass
|
|
10778
|
+
if ok:
|
|
10779
|
+
authed_addr = addr
|
|
10780
|
+
continue
|
|
10781
|
+
|
|
10782
|
+
if require_auth and addr != authed_addr:
|
|
10783
|
+
continue
|
|
10784
|
+
|
|
10785
|
+
# (2) Payload
|
|
10786
|
+
fileobj.write(data)
|
|
10787
|
+
try: fileobj.flush()
|
|
10788
|
+
except Exception: pass
|
|
10789
|
+
total += len(data)
|
|
10790
|
+
recvd_so_far += len(data)
|
|
10791
|
+
|
|
10792
|
+
if rate_limit_bps:
|
|
10793
|
+
sleep_s, last_rate_ts, last_rate_bytes = _progress_tick(
|
|
10794
|
+
recvd_so_far, expected_len, last_rate_ts, last_rate_bytes, rate_limit_bps
|
|
10795
|
+
)
|
|
10796
|
+
if sleep_s > 0.0:
|
|
10797
|
+
time.sleep(min(sleep_s, 0.25))
|
|
10798
|
+
|
|
10799
|
+
if on_progress and (monotonic() - last_cb_ts) >= 0.1:
|
|
10800
|
+
try: on_progress(recvd_so_far, expected_len)
|
|
10801
|
+
except Exception: pass
|
|
10802
|
+
last_cb_ts = monotonic()
|
|
10803
|
+
|
|
10804
|
+
if expected_len is not None and total >= expected_len:
|
|
10805
|
+
break
|
|
10806
|
+
|
|
10807
|
+
# Post-conditions
|
|
10808
|
+
if expected_len is not None and total != expected_len:
|
|
10809
|
+
raise RuntimeError("UDP receive incomplete: got %d of %s bytes" % (total, expected_len))
|
|
10810
|
+
|
|
10811
|
+
if expected_sha:
|
|
10812
|
+
import hashlib
|
|
10813
|
+
try:
|
|
10814
|
+
cur = fileobj.tell(); fileobj.seek(0)
|
|
10815
|
+
except Exception:
|
|
10816
|
+
cur = None
|
|
10817
|
+
h = hashlib.sha256(); _HSZ = 1024 * 1024
|
|
10818
|
+
while True:
|
|
10819
|
+
blk = fileobj.read(_HSZ)
|
|
10820
|
+
if not blk: break
|
|
10821
|
+
h.update(_to_bytes(blk))
|
|
10822
|
+
got = h.hexdigest()
|
|
10823
|
+
if cur is not None:
|
|
10824
|
+
try: fileobj.seek(cur)
|
|
10825
|
+
except Exception: pass
|
|
10826
|
+
if got != expected_sha:
|
|
10827
|
+
raise RuntimeError("UDP checksum mismatch: got %s expected %s" % (got, expected_sha))
|
|
10828
|
+
|
|
10829
|
+
finally:
|
|
10830
|
+
try: sock.close()
|
|
10831
|
+
except Exception: pass
|
|
10832
|
+
return total
|
|
10833
|
+
|
|
10834
|
+
# ---------------- TCP server ----------------
|
|
10835
|
+
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
10836
|
+
try:
|
|
10837
|
+
try: srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
10838
|
+
except Exception: pass
|
|
10839
|
+
srv.bind((host or "", port))
|
|
10840
|
+
srv.listen(int(backlog) if backlog else 1)
|
|
10841
|
+
|
|
10842
|
+
if not _set_effective_timeout(srv, timeout):
|
|
10843
|
+
return 0
|
|
10844
|
+
try:
|
|
10845
|
+
conn, _peer = srv.accept()
|
|
10846
|
+
except socket.timeout:
|
|
10847
|
+
return 0
|
|
10848
|
+
|
|
10849
|
+
if use_ssl:
|
|
10850
|
+
if not _ssl_available():
|
|
10851
|
+
try: conn.close()
|
|
10852
|
+
except Exception: pass
|
|
10853
|
+
raise RuntimeError("SSL requested but 'ssl' module unavailable.")
|
|
10854
|
+
if not ssl_certfile:
|
|
10855
|
+
try: conn.close()
|
|
10856
|
+
except Exception: pass
|
|
10857
|
+
raise ValueError("TLS server requires ssl_certfile (and usually ssl_keyfile).")
|
|
10858
|
+
conn = _ssl_wrap_socket(conn, server_side=True, server_hostname=None,
|
|
10859
|
+
verify=ssl_verify, ca_file=ssl_ca_file,
|
|
10860
|
+
certfile=ssl_certfile, keyfile=ssl_keyfile)
|
|
10861
|
+
|
|
10862
|
+
recvd_so_far = 0
|
|
10863
|
+
last_cb_ts = monotonic()
|
|
10864
|
+
last_rate_ts = last_cb_ts
|
|
10865
|
+
last_rate_bytes = 0
|
|
10866
|
+
|
|
10867
|
+
try:
|
|
10868
|
+
if require_auth:
|
|
10869
|
+
if not _set_effective_timeout(conn, timeout):
|
|
10870
|
+
return 0
|
|
10871
|
+
try:
|
|
10872
|
+
preface = conn.recv(2048)
|
|
10873
|
+
except socket.timeout:
|
|
10874
|
+
try: conn.sendall(_NO)
|
|
10875
|
+
except Exception: pass
|
|
10876
|
+
return 0
|
|
10877
|
+
|
|
10878
|
+
ok = False
|
|
10879
|
+
v_ok, v_user, v_scope, _r, v_len, v_sha = verify_auth_blob_v1(
|
|
10880
|
+
preface or b"", expected_user=expected_user, secret=expected_pass,
|
|
10881
|
+
max_skew=600, expect_scope=expect_scope
|
|
10882
|
+
)
|
|
10883
|
+
if v_ok:
|
|
10884
|
+
ok = True
|
|
10885
|
+
else:
|
|
10886
|
+
user, pw = _parse_auth_blob_legacy(preface or b"")
|
|
10887
|
+
ok = (user is not None and
|
|
10888
|
+
(expected_user is None or user == _to_bytes(expected_user)) and
|
|
10889
|
+
(expected_pass is None or pw == _to_bytes(expected_pass)))
|
|
10890
|
+
|
|
10891
|
+
try: conn.sendall(_OK if ok else _NO)
|
|
10892
|
+
except Exception: pass
|
|
10893
|
+
if not ok:
|
|
10894
|
+
return 0
|
|
10895
|
+
|
|
10896
|
+
while True:
|
|
10897
|
+
if _time_left() == 0.0: break
|
|
10898
|
+
if (max_bytes is not None) and (total >= max_bytes): break
|
|
10899
|
+
|
|
10900
|
+
if not _set_effective_timeout(conn, timeout):
|
|
10901
|
+
break
|
|
10902
|
+
try:
|
|
10903
|
+
data = conn.recv(chunk_size)
|
|
10904
|
+
except socket.timeout:
|
|
10905
|
+
break
|
|
10906
|
+
if not data:
|
|
10907
|
+
break
|
|
10908
|
+
|
|
10909
|
+
fileobj.write(data)
|
|
10910
|
+
try: fileobj.flush()
|
|
10911
|
+
except Exception: pass
|
|
10912
|
+
total += len(data)
|
|
10913
|
+
recvd_so_far += len(data)
|
|
10914
|
+
|
|
10915
|
+
if rate_limit_bps:
|
|
10916
|
+
sleep_s, last_rate_ts, last_rate_bytes = _progress_tick(
|
|
10917
|
+
recvd_so_far, max_bytes, last_rate_ts, last_rate_bytes, rate_limit_bps
|
|
10918
|
+
)
|
|
10919
|
+
if sleep_s > 0.0:
|
|
10920
|
+
time.sleep(min(sleep_s, 0.25))
|
|
10921
|
+
|
|
10922
|
+
if on_progress and (monotonic() - last_cb_ts) >= 0.1:
|
|
10923
|
+
try: on_progress(recvd_so_far, max_bytes)
|
|
10924
|
+
except Exception: pass
|
|
10925
|
+
last_cb_ts = monotonic()
|
|
10926
|
+
finally:
|
|
10927
|
+
try: conn.shutdown(socket.SHUT_RD)
|
|
10928
|
+
except Exception: pass
|
|
10929
|
+
try: conn.close()
|
|
10930
|
+
except Exception: pass
|
|
10931
|
+
finally:
|
|
10932
|
+
try: srv.close()
|
|
10933
|
+
except Exception: pass
|
|
10934
|
+
|
|
10935
|
+
return total
|
|
10936
|
+
|
|
10937
|
+
# ---------- URL drivers ----------
|
|
10938
|
+
def send_via_url(fileobj, url, send_from_fileobj_func=send_from_fileobj):
|
|
10939
|
+
"""
|
|
10940
|
+
Use URL options to drive the sender. Returns bytes sent.
|
|
10941
|
+
"""
|
|
10942
|
+
parts, o = _parse_net_url(url)
|
|
10943
|
+
use_auth = (o["user"] is not None and o["pw"] is not None) or o["force_auth"]
|
|
10944
|
+
return send_from_fileobj_func(
|
|
10945
|
+
fileobj,
|
|
10946
|
+
o["host"], o["port"], proto=o["proto"],
|
|
10947
|
+
timeout=o["timeout"], chunk_size=o["chunk_size"],
|
|
10948
|
+
use_ssl=o["use_ssl"], ssl_verify=o["ssl_verify"],
|
|
10949
|
+
ssl_ca_file=o["ssl_ca_file"], ssl_certfile=o["ssl_certfile"], ssl_keyfile=o["ssl_keyfile"],
|
|
10950
|
+
server_hostname=o["server_hostname"],
|
|
10951
|
+
auth_user=(o["user"] if use_auth else None),
|
|
10952
|
+
auth_pass=(o["pw"] if use_auth else None),
|
|
10953
|
+
auth_scope=o.get("path", u""),
|
|
10954
|
+
want_sha=o["want_sha"], # <— pass through
|
|
10955
|
+
)
|
|
10956
|
+
|
|
10957
|
+
def recv_via_url(fileobj, url, recv_to_fileobj_func=recv_to_fileobj):
|
|
10958
|
+
"""
|
|
10959
|
+
Use URL options to drive the receiver. Returns bytes received.
|
|
10960
|
+
"""
|
|
10961
|
+
parts, o = _parse_net_url(url)
|
|
10962
|
+
require_auth = (o["user"] is not None and o["pw"] is not None) or o["force_auth"]
|
|
10963
|
+
return recv_to_fileobj_func(
|
|
10964
|
+
fileobj,
|
|
10965
|
+
o["host"], o["port"], proto=o["proto"],
|
|
10966
|
+
timeout=o["timeout"], total_timeout=o["total_timeout"],
|
|
10967
|
+
chunk_size=o["chunk_size"],
|
|
10968
|
+
use_ssl=o["use_ssl"], ssl_verify=o["ssl_verify"],
|
|
10969
|
+
ssl_ca_file=o["ssl_ca_file"], ssl_certfile=o["ssl_certfile"], ssl_keyfile=o["ssl_keyfile"],
|
|
10970
|
+
require_auth=require_auth,
|
|
10971
|
+
expected_user=o["user"], expected_pass=o["pw"],
|
|
10972
|
+
expect_scope=o.get("path", u""),
|
|
10973
|
+
)
|