@@ -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+
879897def 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+
95659761def 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