Skip to content

Commit c09bbfd

Browse files
author
Kazuki Suzuki Przyborowski
committed
Update pyfoxfile.py
1 parent 1ae341c commit c09bbfd

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

pyfoxfile.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,24 @@ def _parse_net_url(url):
876876
)
877877
return parts, opts
878878

879+
def _rewrite_url_without_auth(url):
880+
u = urlparse(url)
881+
netloc = u.hostname or ''
882+
if u.port:
883+
netloc += ':' + str(u.port)
884+
rebuilt = urlunparse((u.scheme, netloc, u.path, u.params, u.query, u.fragment))
885+
# username/password may be percent-encoded in URL; unquote them
886+
usr = unquote(u.username) if u.username else ''
887+
pwd = unquote(u.password) if u.password else ''
888+
return rebuilt, usr, pwd
889+
890+
def _guess_filename(url, filename):
891+
if filename:
892+
return filename
893+
path = urlparse(url).path or ''
894+
base = os.path.basename(path)
895+
return base or 'OutFile.'+__file_format_extension__
896+
879897
def DetectTarBombFoxFileArray(listarrayfiles,
880898
top_file_ratio_threshold=0.6,
881899
min_members_for_ratio=4,
@@ -9562,6 +9580,184 @@ def download_file_from_http_file(url, headers=None, usehttp=__use_http_lib__):
95629580
return httpfile
95639581

95649582

9583+
def upload_file_to_http_file(
9584+
fileobj,
9585+
url,
9586+
method="POST", # "POST" or "PUT"
9587+
headers=None,
9588+
form=None, # dict of extra form fields → triggers multipart/form-data
9589+
field_name="file", # form field name for the file content
9590+
filename=None, # defaults to basename of URL path
9591+
content_type="application/octet-stream",
9592+
usehttp=__use_http_lib__, # 'requests' | 'httpx' | 'mechanize' | anything → urllib fallback
9593+
):
9594+
"""
9595+
Py2+Py3 compatible HTTP/HTTPS upload.
9596+
9597+
- If `form` is provided (dict), uses multipart/form-data:
9598+
* text fields from `form`
9599+
* file part named by `field_name` with given `filename` and `content_type`
9600+
- If `form` is None, uploads raw body as POST/PUT with Content-Type.
9601+
- Returns True on HTTP 2xx, else False.
9602+
"""
9603+
if headers is None:
9604+
headers = {}
9605+
method = (method or "POST").upper()
9606+
9607+
rebuilt_url, username, password = _rewrite_url_without_auth(url)
9608+
filename = _guess_filename(url, filename)
9609+
9610+
# rewind if possible
9611+
try:
9612+
fileobj.seek(0)
9613+
except Exception:
9614+
pass
9615+
9616+
# ========== 1) requests (Py2+Py3) ==========
9617+
if usehttp == 'requests' and haverequests:
9618+
import requests
9619+
9620+
auth = (username, password) if (username or password) else None
9621+
9622+
if form is not None:
9623+
# multipart/form-data
9624+
files = {field_name: (filename, fileobj, content_type)}
9625+
data = form or {}
9626+
resp = requests.request(method, rebuilt_url, headers=headers, auth=auth,
9627+
files=files, data=data, timeout=(5, 120))
9628+
else:
9629+
# raw body
9630+
hdrs = {'Content-Type': content_type}
9631+
hdrs.update(headers)
9632+
# best-effort content-length (helps some servers)
9633+
if hasattr(fileobj, 'seek') and hasattr(fileobj, 'tell'):
9634+
try:
9635+
cur = fileobj.tell()
9636+
fileobj.seek(0, io.SEEK_END if hasattr(io, 'SEEK_END') else 2)
9637+
size = fileobj.tell() - cur
9638+
fileobj.seek(cur)
9639+
hdrs.setdefault('Content-Length', str(size))
9640+
except Exception:
9641+
pass
9642+
resp = requests.request(method, rebuilt_url, headers=hdrs, auth=auth,
9643+
data=fileobj, timeout=(5, 300))
9644+
9645+
return (200 <= resp.status_code < 300)
9646+
9647+
# ========== 2) httpx (Py3 only) ==========
9648+
if usehttp == 'httpx' and havehttpx and not PY2:
9649+
import httpx
9650+
auth = (username, password) if (username or password) else None
9651+
9652+
with httpx.Client(follow_redirects=True, timeout=60) as client:
9653+
if form is not None:
9654+
files = {field_name: (filename, fileobj, content_type)}
9655+
data = form or {}
9656+
resp = client.request(method, rebuilt_url, headers=headers, auth=auth,
9657+
files=files, data=data)
9658+
else:
9659+
hdrs = {'Content-Type': content_type}
9660+
hdrs.update(headers)
9661+
resp = client.request(method, rebuilt_url, headers=hdrs, auth=auth,
9662+
content=fileobj)
9663+
return (200 <= resp.status_code < 300)
9664+
9665+
# ========== 3) mechanize (forms) → prefer requests if available ==========
9666+
if usehttp == 'mechanize' and havemechanize:
9667+
# mechanize is great for HTML forms, but file upload requires form discovery.
9668+
# For a generic upload helper, prefer requests. If not available, fall through.
9669+
try:
9670+
import requests # noqa
9671+
# delegate to requests path to ensure robust multipart handling
9672+
return upload_file_to_http_file(
9673+
fileobj, url, method=method, headers=headers,
9674+
form=(form or {}), field_name=field_name,
9675+
filename=filename, content_type=content_type,
9676+
usehttp='requests'
9677+
)
9678+
except Exception:
9679+
pass # fall through to urllib
9680+
9681+
# ========== 4) urllib fallback (Py2+Py3) ==========
9682+
# multipart builder (no f-strings)
9683+
boundary = ('----pyuploader-%s' % uuid.uuid4().hex)
9684+
9685+
if form is not None:
9686+
# Build multipart body to a temp file-like (your MkTempFile())
9687+
buf = MkTempFile()
9688+
9689+
def _w(s):
9690+
buf.write(_to_bytes(s))
9691+
9692+
# text fields
9693+
if form:
9694+
for k, v in form.items():
9695+
_w('--' + boundary + '\r\n')
9696+
_w('Content-Disposition: form-data; name="%s"\r\n\r\n' % k)
9697+
_w('' if v is None else (v if isinstance(v, (str, bytes)) else str(v)))
9698+
_w('\r\n')
9699+
9700+
# file field
9701+
_w('--' + boundary + '\r\n')
9702+
_w('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (field_name, filename))
9703+
_w('Content-Type: %s\r\n\r\n' % content_type)
9704+
9705+
try:
9706+
fileobj.seek(0)
9707+
except Exception:
9708+
pass
9709+
shutil.copyfileobj(fileobj, buf)
9710+
9711+
_w('\r\n')
9712+
_w('--' + boundary + '--\r\n')
9713+
9714+
buf.seek(0)
9715+
data = buf.read()
9716+
hdrs = {'Content-Type': 'multipart/form-data; boundary=%s' % boundary}
9717+
hdrs.update(headers)
9718+
req = Request(rebuilt_url, data=data)
9719+
# method override for Py3; Py2 Request ignores 'method' kw
9720+
if not PY2:
9721+
req.method = method # type: ignore[attr-defined]
9722+
else:
9723+
# raw body
9724+
try:
9725+
fileobj.seek(0)
9726+
except Exception:
9727+
pass
9728+
data = fileobj.read()
9729+
hdrs = {'Content-Type': content_type}
9730+
hdrs.update(headers)
9731+
req = Request(rebuilt_url, data=data)
9732+
if not PY2:
9733+
req.method = method # type: ignore[attr-defined]
9734+
9735+
for k, v in hdrs.items():
9736+
req.add_header(k, v)
9737+
9738+
# Basic auth if present
9739+
if username or password:
9740+
pwd_mgr = HTTPPasswordMgrWithDefaultRealm()
9741+
pwd_mgr.add_password(None, rebuilt_url, username, password)
9742+
opener = build_opener(HTTPBasicAuthHandler(pwd_mgr))
9743+
else:
9744+
opener = build_opener()
9745+
9746+
# Py2 OpenerDirector.open takes timeout since 2.6; to be safe, avoid passing if it explodes
9747+
try:
9748+
resp = opener.open(req, timeout=60)
9749+
except TypeError:
9750+
resp = opener.open(req)
9751+
9752+
# Status code compat
9753+
code = getattr(resp, 'status', None) or getattr(resp, 'code', None) or 0
9754+
try:
9755+
resp.close()
9756+
except Exception:
9757+
pass
9758+
return (200 <= int(code) < 300)
9759+
9760+
95659761
def download_file_from_http_string(url, headers=geturls_headers_pyfile_python_alt, usehttp=__use_http_lib__):
95669762
httpfile = download_file_from_http_file(url, headers, usehttp)
95679763
httpout = httpfile.read()

0 commit comments

Comments
 (0)