From 39814a0c426343bdd3b84b89edffd77d14ad8f9e Mon Sep 17 00:00:00 2001 From: galihap76 Date: Sun, 27 Feb 2022 20:42:43 +0700 Subject: [PATCH] update --- colorama/__init__.py | 6 + colorama/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 439 bytes colorama/__pycache__/ansi.cpython-39.pyc | Bin 0 -> 3224 bytes .../__pycache__/ansitowin32.cpython-39.pyc | Bin 0 -> 7690 bytes .../__pycache__/initialise.cpython-39.pyc | Bin 0 -> 1706 bytes colorama/__pycache__/win32.cpython-39.pyc | Bin 0 -> 3938 bytes colorama/__pycache__/winterm.cpython-39.pyc | Bin 0 -> 4660 bytes colorama/ansi.py | 102 ++ colorama/ansitowin32.py | 258 ++++ colorama/initialise.py | 80 ++ colorama/win32.py | 152 +++ colorama/winterm.py | 169 +++ main.py | 43 +- requests/__init__.py | 152 +++ requests/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 3899 bytes .../__pycache__/__version__.cpython-39.pyc | Bin 0 -> 551 bytes .../_internal_utils.cpython-39.pyc | Bin 0 -> 1298 bytes requests/__pycache__/adapters.cpython-39.pyc | Bin 0 -> 17036 bytes requests/__pycache__/api.cpython-39.pyc | Bin 0 -> 6715 bytes requests/__pycache__/auth.cpython-39.pyc | Bin 0 -> 8327 bytes requests/__pycache__/certs.cpython-39.pyc | Bin 0 -> 617 bytes requests/__pycache__/compat.cpython-39.pyc | Bin 0 -> 1817 bytes requests/__pycache__/cookies.cpython-39.pyc | Bin 0 -> 18818 bytes .../__pycache__/exceptions.cpython-39.pyc | Bin 0 -> 5642 bytes requests/__pycache__/help.cpython-39.pyc | Bin 0 -> 2869 bytes requests/__pycache__/hooks.cpython-39.pyc | Bin 0 -> 986 bytes requests/__pycache__/models.cpython-39.pyc | Bin 0 -> 24295 bytes requests/__pycache__/packages.cpython-39.pyc | Bin 0 -> 693 bytes requests/__pycache__/sessions.cpython-39.pyc | Bin 0 -> 19702 bytes .../__pycache__/status_codes.cpython-39.pyc | Bin 0 -> 4235 bytes .../__pycache__/structures.cpython-39.pyc | Bin 0 -> 4456 bytes requests/__pycache__/utils.cpython-39.pyc | Bin 0 -> 24271 bytes requests/__version__.py | 14 + requests/_internal_utils.py | 42 + requests/adapters.py | 538 +++++++++ requests/api.py | 159 +++ requests/auth.py | 305 +++++ requests/certs.py | 18 + requests/compat.py | 81 ++ requests/cookies.py | 549 +++++++++ requests/exceptions.py | 133 +++ requests/help.py | 135 +++ requests/hooks.py | 34 + requests/models.py | 973 +++++++++++++++ requests/packages.py | 26 + requests/sessions.py | 771 ++++++++++++ requests/status_codes.py | 123 ++ requests/structures.py | 105 ++ requests/utils.py | 1060 +++++++++++++++++ 49 files changed, 6027 insertions(+), 1 deletion(-) create mode 100644 colorama/__init__.py create mode 100644 colorama/__pycache__/__init__.cpython-39.pyc create mode 100644 colorama/__pycache__/ansi.cpython-39.pyc create mode 100644 colorama/__pycache__/ansitowin32.cpython-39.pyc create mode 100644 colorama/__pycache__/initialise.cpython-39.pyc create mode 100644 colorama/__pycache__/win32.cpython-39.pyc create mode 100644 colorama/__pycache__/winterm.cpython-39.pyc create mode 100644 colorama/ansi.py create mode 100644 colorama/ansitowin32.py create mode 100644 colorama/initialise.py create mode 100644 colorama/win32.py create mode 100644 colorama/winterm.py create mode 100644 requests/__init__.py create mode 100644 requests/__pycache__/__init__.cpython-39.pyc create mode 100644 requests/__pycache__/__version__.cpython-39.pyc create mode 100644 requests/__pycache__/_internal_utils.cpython-39.pyc create mode 100644 requests/__pycache__/adapters.cpython-39.pyc create mode 100644 requests/__pycache__/api.cpython-39.pyc create mode 100644 requests/__pycache__/auth.cpython-39.pyc create mode 100644 requests/__pycache__/certs.cpython-39.pyc create mode 100644 requests/__pycache__/compat.cpython-39.pyc create mode 100644 requests/__pycache__/cookies.cpython-39.pyc create mode 100644 requests/__pycache__/exceptions.cpython-39.pyc create mode 100644 requests/__pycache__/help.cpython-39.pyc create mode 100644 requests/__pycache__/hooks.cpython-39.pyc create mode 100644 requests/__pycache__/models.cpython-39.pyc create mode 100644 requests/__pycache__/packages.cpython-39.pyc create mode 100644 requests/__pycache__/sessions.cpython-39.pyc create mode 100644 requests/__pycache__/status_codes.cpython-39.pyc create mode 100644 requests/__pycache__/structures.cpython-39.pyc create mode 100644 requests/__pycache__/utils.cpython-39.pyc create mode 100644 requests/__version__.py create mode 100644 requests/_internal_utils.py create mode 100644 requests/adapters.py create mode 100644 requests/api.py create mode 100644 requests/auth.py create mode 100644 requests/certs.py create mode 100644 requests/compat.py create mode 100644 requests/cookies.py create mode 100644 requests/exceptions.py create mode 100644 requests/help.py create mode 100644 requests/hooks.py create mode 100644 requests/models.py create mode 100644 requests/packages.py create mode 100644 requests/sessions.py create mode 100644 requests/status_codes.py create mode 100644 requests/structures.py create mode 100644 requests/utils.py diff --git a/colorama/__init__.py b/colorama/__init__.py new file mode 100644 index 0000000..b149ed7 --- /dev/null +++ b/colorama/__init__.py @@ -0,0 +1,6 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from .initialise import init, deinit, reinit, colorama_text +from .ansi import Fore, Back, Style, Cursor +from .ansitowin32 import AnsiToWin32 + +__version__ = '0.4.4' diff --git a/colorama/__pycache__/__init__.cpython-39.pyc b/colorama/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f9484246e558205ce4ee447919fd5cffe2f3abd GIT binary patch literal 439 zcmYjNyH3L}6pa%neWVd;b!eD6KxMKO$mJ?c;X5%>+9{}u=5G#qysppI&!B8>>rF^P3T5{(H)5RX-= zGm_~Z>FGY{>j4@3#1l2tImyEvlTn>6qcVF(p;d`JmW}LuR*N0m9T$~R#&XSBC!RYf zyE0aIe9o%}pDw$t5C6)}wD2`PXY5f}Cr!iHYh?GJ6yS?jM|2YgIJydx*1V;> zZEN0fs!YWdZLL`aA&$1&Zf%-hnjcQ)GkPuWX_zA>ZIH!R!qGq3`;5Wzj7{6E?;mSZ QZ{vZDce73N$GK%d%p-QDnoi;f9gRGTe>KM3$80 zI;jl@DB%2%^yt5|*Pi$TddaCXOUsd8XyHqj*pEA$R5|gYjznZ%?K*?Jp$8 zhY7?i%y$f7&01o)dBC$AT@(>T^9P@khv(- z-qUY9fw}pfq{iEBRA8*~c z-Dw6-J7FN$t)b_?@VYGQD1*IiZ^r9~L3Ow*bEwM|0~g&CX7M|90F7`+4DF0D$Qv^T zjo8LgsV6OAV)fLu#Qm28O8tZ z#h*FuIRy7X9-Psa^?S_YZs>Ey`lT_IxW0t8he{jAn4UJxj#c%7KD#=mXc85{JTcJL zS(qVmnFdv+L6t$RL1h`#fCMpRx(2bTgH7Ry??h{pFzfjG4*&u}aX{v^&6{t?K6ytd z`ICf}(D$`{a-hG_g@G$!?&}BSz#!U7i;gnTtt6yD11{}QcwB_91F|Y;80PK`BXFnX$ve0gtfCeT?L2;2PDb&>WXN zIaT(xIAL{u1GY-iA2Usrh`zq^6>yGfV=wnXV|9Jy68h%iltr1RRhje48?CR$loiI5 zA@36E>KCg^Jy>4dsIAUd#x%xxp&VL0VqZe({KZPmxmx|pi(A$)eI;F^(eoyCl1(J#DNkw{L;CQB?C|js{k%LiiWJ zKL4gGUcABLpTa&~M)(W?!^hF19F63tgx^L`b@BmJ#e5EoFJXpw5U_}?#}~)vKFkvX z^LrL%xB_roIao6~REq(1V#3cq0kx5IEShrC)N*W^b~4mfAtRTzt}t21TIc~NaSSvr*4Z~ zIPPG=@;pE^g#c4)RZ^!DF*&!2xg?GGrAIQ^T3&rrYs$1rGq=`ko@*7FkGZaXtD~56 ze2UEjRgP{YnQ~n^@LiYViJoJv^9({70S{Xok3Sp_A2K<3`keVf{XA74P}#!KDN4FD zu|%OjAd)hntdz;RYS`y}r?h^Gf8+OckPwyu%!Eou!-C5;Y_pgt=898rC6ffNe*l&Z BbmIU3 literal 0 HcmV?d00001 diff --git a/colorama/__pycache__/ansitowin32.cpython-39.pyc b/colorama/__pycache__/ansitowin32.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..522fe9bad7e8bce58949983360fe7b86674e279f GIT binary patch literal 7690 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 VVl%0>XI@37FmKryyVm^j{{e3u0c8LH literal 0 HcmV?d00001 diff --git a/colorama/__pycache__/initialise.cpython-39.pyc b/colorama/__pycache__/initialise.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a6cca147e7c220739911e0cf54e8a5886b05cd6 GIT binary patch literal 1706 zcma)6ON$&g5SG-B>FIgw!- zyk2K~$?hSSF8)h>&B;F?mt34G&Cc$u!4Nf7Qb{GL`m3)bd%c)pdp`e1{@P;f zZ!$KUkH&W>#-dV8@q&$4Js)#ti?L8bd4)Ik$39078K8tHEtE+4vaJFYp0aVQc2%U> zr+nN|vFhMVSM$T3>Z;x;JK^KL>Z>iB-BR0X2m5WsAF<@d8@!nf-*D6kZ#v(XCLdK# z@^b(FK}JiPO`!1uiuo9o#XuJvKCsO46MiPn8HUFmvDbW!WWr?Reqf#Oir~)v#O}jTYj0Bxy|uFqra$+A;jx;Q49gjc*NbzD03!FVWTnm zxgY!;2TRZFS+-?$b5#1iCrEAArD2&L$CifTKB%sV-BC7$NPCYz|{+$_KBwx zVJTT0Zdng4pWp>bd=fZJpo3%RlIoM{k-FsSYUE-Jm8K~7aox}aaVjKU?cw-ZJXAAu zLWN@Ppdw1;NUFd1pP1yA+loEL%6URngnb#U`g^XHR&hgBF=^&(6$`#I4&YS+lQFB@1BL^;tK*+gk qH^OvGbv%f^nOAC2=x-^vu8tyPXC%5FhAo_UQ4rsXT3AI<)cOx*&17Ey literal 0 HcmV?d00001 diff --git a/colorama/__pycache__/win32.cpython-39.pyc b/colorama/__pycache__/win32.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d605ed6f53c057c98f2ab19d30dc8353ebe507f GIT binary patch literal 3938 zcmai1TXPf16`t<7Xe7(V=4y-=1jM^&i}eynYHPPB3h)J}Xl=^!#@Q(+Q)6|@mXI`} zXN0j!Jek*|@|IWjasNerN5AH2pObwFRdK%4Bg-Ah3{4S z=VAx>+;%LPlYX7=Z83{}!iJ0|a&N65FMLxMzPJ9$I>8+abMu%x7L2uvL9sm^jJGF( z39Rll&jgcCEKv|+Z!Iw}>k{f|!?_&HnEr&EI$+ye z&KUj`)EPe?%o@Je`~=+D0e5!5y&7CI+)41R4S3fEyi#!8@Ww$e$VoXtHTH1j^?~H| zf#i+grjf)RAIaVv$le^t{wkP*%%?cX+<-F|PK%-#2U4Gj32_E>L0WYtCdJfS7F-r* z#W{oGC*p#*h>@$}l9w&{YV1Va&8XWIQ5tQw;#$<&?5cQMMeSs>yO-|7ok4Z)>o4xz-GnZiNh6hCbfemf zXj>+mwYU{SI{Fe?-Mc&A-P8X4R@B}S(Sr$W`A-x}k(d5|Z564VDe4VdMsNRUku%hw zh$uVS-P%*KuJhGY^=fHPN$u6b+D@c&?)mDomGz~y6I)`EMaw=!NgjYmD`3JxjIh8K zTsn2cjG+#E(?iciokQ)>=(Zam5`+(1lUS3)Y zpRFvf?w?DHR;5O#1Yl}$XMSg1dt$fR*q3@z>@M_F607Giz-+`FJ+}KOO;uy7m&!!@ ziP~s%M7*odiQR9b*Nt|soej|J8d>@1qjnKx0VR0~B3R#QTFs1dv~AP2o78qta5O`6 zhWdu)K?_E%(B$>8i}0K>U)jHLYfjw$pMT}@n3}|{ntiX?AJi0@>MW6SG-$8KUF}q4 zJ=NahI8EcWcGnu)JE=MkHayu$WUGGcnTpJ;b72VV!tg3O3AvEPI4k1kuxVC&zklO& z)aM6NS701J3_DR2SgimC;5O@ ztfZZ-IBu0~?d?Pz(URJ!C0h-hOM11MOp+1p4cf&J?iqC(AsjE=#QenEH_)Cl|Ip#; zbBxtcPJDE5qdOTXh0_WC)=F6leL?L5R_BWrP66%9_R5K)bIhRQCsZzi^sPTm;OG_| z`}as;FF9qd?9j|?U*?+6-_dO6L*?`Lw`E7Z?y3i*p~(yTS4Yk>f8S)Z~(a^?-(PN~xNPEgA7Hm9msP3Y5;+SOJp@mE6 z;DA7cYz#|tvT|l$SQ>UdIETAM5%md9iypw!$s4F+R>gljL^vHGjIvpmmvaRl6 zx^^R^wj|U018pyzGPO;66MUqq`U>@WjdVKt2>Btpuh1d)`3oYb&zelI7c7}Y3z_`| zQzddI;)BDdsm7~l+RAWOBDGVziJI;kr*AuyIGv{VmBp=)nLbB9XZk!vvV;ZQG~ajo zHsJu@R+NE%B2q4>>!=i^0Y#3j2wHWA2&F@HiO4h&va!BAg4@a4pzLM9`Q4-I!HYz* zm!x_stEC8sES{)egm(HE+LNol#O4VlU|`G7Gsn4zbX8!JY?1?Ox)^H)d6K}F8x-RX zkI@;pg9CS9@AwayQ`_&HKL3GH(t76zhL)SUeWy<@W2au&_UkeRi+Xp-=3xZG&92H9@4mv(#U0`B*ymP+*~%XaRosMV9@T!xrqu79s3 zJKO1wc9S$xsX<4Nb$ab^S2fa9c78cR^S{#kK{;?t<%x^2Haz` z-XVAlF6VHv_=3X8yOI#a%KlFvjah|ZV-70f~)5A-Lt%OggOPdTBM0fL?}b) zJPn!$jasI`4L_y=*QDDl1N$hKGCegCV(X2x zB{%Tsa8qi1sLz?>a50j9!*Yp9i%hMcU1sa$&$D3uHM)3enkTp-5$)l1wpgiX4>yEp zwK5-`7@BHHsaWZ;XYFpRQqwUh&0HD=NJUv$Q5YzC@Mtg0-XkWT(Bnb-FQn?o*1fyB zIC^hoc6(?fBDHN)(%#cYD~pvSom05d@qa>=hvrDgdAzz>QNKkqvol4z`kFXbKC;J1 zq&*@rf?qu)*~t&E+E5i5&Q+c-Y*g3R%$;pQ!yq?;wDAG+?C|erPqGJ;YLeF=4zduQ z945Yyi}2d>k&&jk$0yJF+&T1YpQC2pe>^8^Il~q|Zebo)dGF;0GAH}lDxU8YbG}nJ J>lggOe*xGmg^>UN literal 0 HcmV?d00001 diff --git a/colorama/__pycache__/winterm.cpython-39.pyc b/colorama/__pycache__/winterm.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ad75efe49cc13462af27c494f24504c63528826 GIT binary patch literal 4660 zcmcgwTXWmS6~^L55TqnZRwBi595qgyFs+@|ZkxnS6J4#ik*SO%Hx-8+6yicM8Hi99 zU>s|pPG{=Zq<=wrtNT;>&>yg`ebNu;OP>0j1we|loX1kLXK!bLbNSBM1NjpZu7=+? zhyV3&zNTsap~=OciOC{bOoVHkh1wx&GN!F-T<69!jT?dfH=}7_ZgLBAOU+HpZC=8> zq~;cPwzYcsoZ&HbCaou(-rYNGOh$gRyNDM53?$H+jB8Duv!=oIrpb+_#m%P8t!9ba zO^277W$rXxUdEGM>3r4ctwdoY&T&=~^r&;4h(Uz(IJbz{L`ooydPO>(*Yghp&y%j_ z9Y(w#(zxn*fA0HXzGE$KEUkPY&E<_pYtmZZT3c&Kb7gm_A&srIRatttw7%BZS(5hd z+Q!D_SJDK_?sHANigzgT!=KvsTKzZ>aqGYjJCFUNmVb1_{lssDQQHq&Mt+D{x+ZHGZ?x23ZKQv3V(;c!e7No zmEYuZd>*3-{w}}DU&m;YzsIlfH!zyw*ZG^^ukjKLRhK0Ia0mWq6ZDE7jY;ekwD?ny zL_1}P*458g*GM(bOtn)h)w<>xVhX*DwGw&X{F@Zr&4w$pK?S-9#$H{Xh%{U16FF2ZfOo7(vbrR1b zXU7B?jn!C{IrL{8*K*t4F4$gf#A`ST!%$?6%bo?czt{foE@Hl0uwC2;~8^>X>EBk~V8?zc_w6h9gdtiOQ?Y_9b+F$uY~6WAOwvs4%F zr0d;UpQ}Jss{{k(Z1knI`o7uX_X7m|$TrGe`5BdLTDSib>pddjfi;|&&3V9<6 zQt*opiIwa!uHHvj`vmPr)uTtW$!W!p#seoR5MJg)if$AE3W{(}Cbl8!IGn|`lGDf| zL}jB(W&9Wi*3n|R!g3d|fnfYgfVI>Xz|ZMa*Wj|R?dL3gmv{311MX-$T%?) zc50?Z%Fc8|4dIA2>X!HbCsno<3mB=iytcI^=e7r_dnfod8RY03Qi9N;^pN&rzsGU^ zj%F=EYClBFvngd#8~47=j;{=K8b;(u^p6;xLXU1G$W8jd*oYWJ!-o_b&<-;*pP8wi z-9dD%K{d*O;!O})<4;!lB96qD5vr9=)RXKFlI_+rw}{^ok8_mojcFpQlwpg_M_|oe zR5d0k7t9vwTx5H>r%0dNQ|ivdZOfFGuF<^nApN?9@zcj@#)wBSUE2Xxv9iw|(C2$4R|3W&}ZR z5sO4VBSLQm6IocYZzX$rL4PuboX#E;@PW?bcA02rHpv(da45G(W(}9kvg20@i;VE9 z8)wArWt^s(Q5odl7$}2KKxPKXT@8bnso6DQ3ZnL`1_KrI63sCyb*(w=#0GHK*ScuZ z%1J4;QhQ!Iae5zNUB`L?@Hj9aLL9p`cCKT`z|Qw0I~I0KNc<7zQeCoP?sjOH%D zr=3>Lv|=rP!oRRr1+P`4kGh=|d+J+8kmrA33%FD%_!P;jK$TY%XtP+f`IGI=age!e zk(jCQl-A1T=GLmbdcPBfdCj=lPmcP@%41*nZG1{xvNuReSsGISC}#jL@x2rb0U*tG zyw{Oa?H~+e6nkE*3Q~C`Ti6TecJnfiN_SsGhbR^z!R2JekVIY}e0(L1ra4(vtNGI+ zC=ydeq?tKh^qM}eGM@`tMNBDbm{^}>jy_Bt7auc%A_}VkivEeokDw?+$2~%i!jMHF zMVJCg?B3@&N?Pz_2}L{YI6z8{J8<00ef^h9IPffRjH1R)trt*}uc?B}8b^ru9Tccn z$K#lCv_gUSf<`KsscOn%n{E-&AVPOl9^hh=7KZulOPX#I*&#CKB{wr~*y#m-$9B8` zGVqb|&kSe{#i^poKgTmgoDUC=TvL>h4Kx+sR5-zE>YB=uSC+VSrK_A8rt6(f5(Y)_ zX(cop7gv4!sGWb{u`g8ol!?`D9XB{qbVb%|$Lc9o_Nh|moveothV$k5Viq&}IiV}oWfeJ41&Q*{i-axJ%lKihR2 z$8}xDU2tC$#EDOJnUx^Yj`q4iJ5m1> 4) & 7 + self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND) + + def reset_all(self, on_stderr=None): + self.set_attrs(self._default) + self.set_console(attrs=self._default) + self._light = 0 + + def fore(self, fore=None, light=False, on_stderr=False): + if fore is None: + fore = self._default_fore + self._fore = fore + # Emulate LIGHT_EX with BRIGHT Style + if light: + self._light |= WinStyle.BRIGHT + else: + self._light &= ~WinStyle.BRIGHT + self.set_console(on_stderr=on_stderr) + + def back(self, back=None, light=False, on_stderr=False): + if back is None: + back = self._default_back + self._back = back + # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style + if light: + self._light |= WinStyle.BRIGHT_BACKGROUND + else: + self._light &= ~WinStyle.BRIGHT_BACKGROUND + self.set_console(on_stderr=on_stderr) + + def style(self, style=None, on_stderr=False): + if style is None: + style = self._default_style + self._style = style + self.set_console(on_stderr=on_stderr) + + def set_console(self, attrs=None, on_stderr=False): + if attrs is None: + attrs = self.get_attrs() + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleTextAttribute(handle, attrs) + + def get_position(self, handle): + position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition + # Because Windows coordinates are 0-based, + # and win32.SetConsoleCursorPosition expects 1-based. + position.X += 1 + position.Y += 1 + return position + + def set_cursor_position(self, position=None, on_stderr=False): + if position is None: + # I'm not currently tracking the position, so there is no default. + # position = self.get_position() + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleCursorPosition(handle, position) + + def cursor_adjust(self, x, y, on_stderr=False): + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + position = self.get_position(handle) + adjusted_position = (position.Y + y, position.X + x) + win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False) + + def erase_screen(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the screen. + # 1 should clear from the cursor to the beginning of the screen. + # 2 should clear the entire screen, and move cursor to (1,1) + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + # get the number of character cells in the current buffer + cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y + # get number of character cells before current cursor position + cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = cells_in_screen - cells_before_cursor + elif mode == 1: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_before_cursor + elif mode == 2: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_in_screen + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + if mode == 2: + # put the cursor where needed + win32.SetConsoleCursorPosition(handle, (1, 1)) + + def erase_line(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the line. + # 1 should clear from the cursor to the beginning of the line. + # 2 should clear the entire line. + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X + elif mode == 1: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwCursorPosition.X + elif mode == 2: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwSize.X + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + + def set_title(self, title): + win32.SetConsoleTitle(title) diff --git a/main.py b/main.py index b7f9ed3..83aa430 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -import phonenumbers, argparse +import phonenumbers, argparse, requests from phonenumbers import timezone, geocoder, carrier from cfonts import render from termcolor import colored @@ -6,6 +6,7 @@ parser = argparse.ArgumentParser() parser.add_argument('-n', '--number', type=str, help='do information gathering in phone numbers') +parser.add_argument('-g', '--github', type=str, help='do information gathering in account github') args = parser.parse_args() def Banner(): @@ -38,5 +39,45 @@ def Main(): print(colored("[-] WRONG COMMAND!", 'red')) print(colored("[!] Example You Must Enterred : +62xxxxx", 'yellow')) + elif args.github: + username = args.github + url = f'https://api.github.com/users/{username}' + response = requests.get(url) + if response.status_code == 200 and requests.codes.ok: + try: + data = response.json() + print(colored(f"[!] Fetching Github Username : {username}", 'yellow')) + print(colored(f"[+] Name : {data['name']}", 'green')) + print(colored(f"[+] Id : {data['id']}", 'green')) + print(colored(f"[+] Node Id : {data['node_id']}", 'green')) + print(colored(f"[+] Gravatar Id : {data['gravatar_id']}", 'green')) + print(colored(f"[+] Bio : {data['bio']}", 'green')) + print(colored(f"[+] Location : {data['location']}", 'green')) + print(colored(f"[+] Email : {data['email']}", 'green')) + print(colored(f"[+] Twitter Username : {data['twitter_username']}", 'green')) + print(colored(f"[+] Company : {data['company']}", 'green')) + print(colored(f"[+] Type : {data['type']}", 'green')) + print(colored(f"[+] Blog : {data['blog']}", 'green')) + print(colored(f"[+] Followers : {data['followers']}", 'green')) + print(colored(f"[+] Following : {data['following']}", 'green')) + print(colored(f"[+] Public Gists : {data['public_gists']}", 'green')) + print(colored(f"[+] Public Repos : {data['public_repos']}", 'green')) + print(colored(f"[+] Created At : {data['created_at']}", 'green')) + print(colored(f"[+] Updated At : {data['updated_at']}", 'green')) + print(colored(f"[+] Organizations : {data['organizations_url']}", 'green')) + print(colored(f"[+] Url : {data['url']}", 'green')) + print(colored(f"[+] Html Url : {data['html_url']}", 'green')) + print(colored(f"[+] Avatar Url : {data['avatar_url']}", 'green')) + print(colored(f"[+] Followers Url : {data['followers_url']}", 'green')) + print(colored(f"[+] Following Url : {data['following_url']} (You Must Copy Like This )--> https://api.github.com/users/{username}/following", 'green')) + print(colored(f"[+] Events Url : {data['events_url']} (You Must Copy Like This )--> https://api.github.com/users/{username}/events", 'green')) + print(colored(f"[+] Received Events Url : {data['received_events_url']} (You Must Copy Like This )--> https://api.github.com/users/{username}/received_events", 'green')) + print(colored(f"[+] Gists Url : {data['gists_url']} (You Must Copy Like This )--> https://api.github.com/users/{username}/gists", 'green')) + print(colored(f"[+] Starred Url : {data['starred_url']} (You Must Copy Like This )--> https://api.github.com/users/{username}/starred", 'green')) + print(colored(f"[+] Repos Url : {data['repos_url']}", 'green')) + print(colored(f"[+] Subscriptions Url : {data['subscriptions_url']}", 'green')) + except KeyboardInterrupt: + print(colored("Exit!", 'red')) + if __name__ == "__main__": Main() \ No newline at end of file diff --git a/requests/__init__.py b/requests/__init__.py new file mode 100644 index 0000000..53a5b42 --- /dev/null +++ b/requests/__init__.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- + +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP Library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. +Basic GET usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> b'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('https://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key1": "value1", + "key2": "value2" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at . + +:copyright: (c) 2017 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. +""" + +import urllib3 +import warnings +from .exceptions import RequestsDependencyWarning + +try: + from charset_normalizer import __version__ as charset_normalizer_version +except ImportError: + charset_normalizer_version = None + +try: + from chardet import __version__ as chardet_version +except ImportError: + chardet_version = None + +def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version): + urllib3_version = urllib3_version.split('.') + assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git. + + # Sometimes, urllib3 only reports its version as 16.1. + if len(urllib3_version) == 2: + urllib3_version.append('0') + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1, <= 1.26 + assert major == 1 + assert minor >= 21 + assert minor <= 26 + + # Check charset_normalizer for compatibility. + if chardet_version: + major, minor, patch = chardet_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet_version >= 3.0.2, < 5.0.0 + assert (3, 0, 2) <= (major, minor, patch) < (5, 0, 0) + elif charset_normalizer_version: + major, minor, patch = charset_normalizer_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # charset_normalizer >= 2.0.0 < 3.0.0 + assert (2, 0, 0) <= (major, minor, patch) < (3, 0, 0) + else: + raise Exception("You need either charset_normalizer or chardet installed") + +def _check_cryptography(cryptography_version): + # cryptography < 1.3.4 + try: + cryptography_version = list(map(int, cryptography_version.split('.'))) + except ValueError: + return + + if cryptography_version < [1, 3, 4]: + warning = 'Old version of cryptography ({}) may cause slowdown.'.format(cryptography_version) + warnings.warn(warning, RequestsDependencyWarning) + +# Check imported dependencies for compatibility. +try: + check_compatibility(urllib3.__version__, chardet_version, charset_normalizer_version) +except (AssertionError, ValueError): + warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported " + "version!".format(urllib3.__version__, chardet_version, charset_normalizer_version), + RequestsDependencyWarning) + +# Attempt to enable urllib3's fallback for SNI support +# if the standard library doesn't support SNI or the +# 'ssl' library isn't available. +try: + try: + import ssl + except ImportError: + ssl = None + + if not getattr(ssl, "HAS_SNI", False): + from urllib3.contrib import pyopenssl + pyopenssl.inject_into_urllib3() + + # Check cryptography version + from cryptography import __version__ as cryptography_version + _check_cryptography(cryptography_version) +except ImportError: + pass + +# urllib3's DependencyWarnings should be silenced. +from urllib3.exceptions import DependencyWarning +warnings.simplefilter('ignore', DependencyWarning) + +from .__version__ import __title__, __description__, __url__, __version__ +from .__version__ import __build__, __author__, __author_email__, __license__ +from .__version__ import __copyright__, __cake__ + +from . import utils +from . import packages +from .models import Request, Response, PreparedRequest +from .api import request, get, head, post, patch, put, delete, options +from .sessions import session, Session +from .status_codes import codes +from .exceptions import ( + RequestException, Timeout, URLRequired, + TooManyRedirects, HTTPError, ConnectionError, + FileModeWarning, ConnectTimeout, ReadTimeout, JSONDecodeError +) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +logging.getLogger(__name__).addHandler(NullHandler()) + +# FileModeWarnings go off per the default. +warnings.simplefilter('default', FileModeWarning, append=True) diff --git a/requests/__pycache__/__init__.cpython-39.pyc b/requests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9bd54af61862df0bf6f58859f35343ad301e0ab GIT binary patch literal 3899 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 literal 0 HcmV?d00001 diff --git a/requests/__pycache__/_internal_utils.cpython-39.pyc b/requests/__pycache__/_internal_utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea69c827d8234f0245ef24db2a55f4a1621657d0 GIT binary patch literal 1298 zcmZ8h&5j&35VpH}erR@A8^nnN@{y6qfHoHxp$HTL1VI$BL5NXH9gkf*ZPINwwtJUZ z4IE&ffF}q?E}VD^-k`6Xcm*UR%I;|tQX_j@o^n-QKHv>e5@`(GtRJr(pzRW-+=IMG7DR7Wev_TbHV(sg|O#4iUW-MW^l!IkFxR~_0a!NbTwE!D{u>q}q8cRYyjnyEaJha)*$7cG} zbpBUjk1+o4O00^`Ax*`W*Q(-e!P{1H&x_g!UKg!3Tgw|)v^!rJefYZb?(I8g#b;_$ zIOXAX%f(Z^1y_tReCq`nF56vy>PNA% z3&d9;BmYrmG%6Ui?>XXnpg>gIa)D>uGLOKoTHI&?%d9)xWA$PpZ=;0!>Q(Rg6{~&gTQ~%1B z!wv2SnZbQ)ja|@wJ~kE^9;V|EetNiYiSI5xu-9>SU|@Da6N)U*BWiEq?!yI*Wf!x4 vR+iF;vh1hB`;9sq6c;-QgvB$@l?$xeSabefV`Nvu-5^mH`p@RCrupf=8_0BW literal 0 HcmV?d00001 diff --git a/requests/__pycache__/adapters.cpython-39.pyc b/requests/__pycache__/adapters.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..186dca97832beb1fc7262b4b6de2c529bf957420 GIT binary patch literal 17036 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 Dbm7%7 literal 0 HcmV?d00001 diff --git a/requests/__pycache__/auth.cpython-39.pyc b/requests/__pycache__/auth.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca7f7ac6034861c73917d1fc8afdb9cc80e6a24d GIT binary patch literal 8327 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 T?^pU{5nH-jF)eQ2Nmt1qznjNg literal 0 HcmV?d00001 diff --git a/requests/__pycache__/compat.cpython-39.pyc b/requests/__pycache__/compat.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53bb5146e2e98d18765e12826616de5d4f610f38 GIT binary patch literal 1817 zcmZux>u%dN6qaOJw&h#m+?*^~y#V8t7*MV#r5ZJCkz3&{S_ikG+RC%5C*SV}!RL!%J(koj$hWHYz@j9yW25RsoYVrlN zz!%XXZ=n`nLQ8xZE%Oz$lA$WIRlbT=`5Ic|ZPe!LXq|7Mjcl#L-s78SbB2BkZ9#6I zz0Yrzhwj-H*+w72?>_0GZL))Q$VcP>*&P;8 z&uBvj?U8+S5dTTauN<)q+3W1^e`pNyciCJcY_KDP@J41$_A&niZDr_u_URnz&t9Wm z0Mr>$`0qvb`8)$aUx9l54J$3?^8<8XTd_Ic7OUpgjNLZ54-7#9AH#4LX5crp(O%@7 zTHU3~3ty0n36VlUV81H+$y&-J&i- z;uH%Bc|n`Rxn2VOETM6>EK}gA4T6Xa1s-*6-OR#*u~KQks8rzcjuXhjKr5drLF3W0 zldh}FPcUP6z<}g2PE&B}#_>cM&$V4W5fKq2dP+mp^+g?uBN|3DjaV?kTx;PPQ8dCc zqGZG(h;C7|j9xLqJP)F3L=6Ohsf0;#PRSV-U0bw`uOL>;`l=CYc~JA4)05{fC-RaANs~CB@i5VD z0H*e+4ASZ0Y(7GbF_wXZW@6+_CNc8g=-M?N?Ln~_9o)z{E5;3EZ>*dt5cF4;?J%p; zbwwb(_MT+L5lf~P%O<5KS(2P}c+OU=nA3UDp+55IxHET6{+4dld J*>8r)@E@#_{Rsd7 literal 0 HcmV?d00001 diff --git a/requests/__pycache__/cookies.cpython-39.pyc b/requests/__pycache__/cookies.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc4e695107e21f44c5cce226fa1e59ac4f3cc615 GIT binary patch literal 18818 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�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 literal 0 HcmV?d00001 diff --git a/requests/__pycache__/exceptions.cpython-39.pyc b/requests/__pycache__/exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8680ce5a14508800524bccd27785952dd34c1936 GIT binary patch literal 5642 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 IT+7w}15P*_1ONa4 literal 0 HcmV?d00001 diff --git a/requests/__pycache__/hooks.cpython-39.pyc b/requests/__pycache__/hooks.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3df5c2ecf9006e1e0c14a6ba4dfb590a938f33f2 GIT binary patch literal 986 zcmY+D&ubJh6vvbK)orBWFYqSuB5c7`EC`lT6nhX_T5)?SQ)kmjb~fzn&NRtZw#<5I z&mQz2Q1If>e^JfBWI zwPb_K+~n1$-gM{t4USG9a?xj#(qtED8~#;#vvZZ(#x{Plx3}HBI_kwNhW&Qs+Wy-h zY1bpdY8hf4y7}A^7~P(2aQzlSgX=^hN9ZfC`i9+1?HGt0p9Sg>kn`FWI;69xiJG8~ zfok|2f5Nl4iFq)=YPE@H!SQMnxGOn*z9n_dOg?~5i1|;0x~63{W@<{SF^Gi(?zEO7 z7qseA2e@NfSq6y9EZc?K2@7HD(tv4ZjA|z~9Ez!pJs`X6hRkiU$?yYHg|?BCEb#>W z$^xi4qxqqH%6-@VJqRdhVS*#@1h?=l@C2*7pzpRLyOd?T%Ck(hK=3U6(n4v6S^4^{ Sjf>xD)6v!rb95sKTgiX@IRvi& literal 0 HcmV?d00001 diff --git a/requests/__pycache__/models.cpython-39.pyc b/requests/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee56239b25b2640f16356114c258973ceea9d438 GIT binary patch literal 24295 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 literal 0 HcmV?d00001 diff --git a/requests/__pycache__/sessions.cpython-39.pyc b/requests/__pycache__/sessions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..588772de0cc4ea69c88786f5d4234c7106360aa1 GIT binary patch literal 19702 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 YirXmfw*Gcw+j;V=Uz8uKdX>rl1DzmE#sB~S literal 0 HcmV?d00001 diff --git a/requests/__pycache__/structures.cpython-39.pyc b/requests/__pycache__/structures.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d1f0d8fa531d350c78463209d83c3fb6129bd9d GIT binary patch literal 4456 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�`nEd2)0E&qXam~7~pWASyRTZ z+CR*LjM|7=^?Mv-3j0JB$~}gdt7x(qTCSJL|G=E}NdGW%h{=*K;pd;jU9#H`L^5+| zRT4vpMKX%YZxr9dXjsJ4^3HL_vBG`$2iz~=1Eerjvs_dL){NyUvY5sSr2Hcbpq;Tn zM-li90}HOdL>Z5v2l&^Blx1D_?wTAZ2?#qdIgN|VgOqocGWy%F%??7vM*1;L3J5qy zoeU308Gw4^`o9m0uB7yLqB_14M6ky}mc|!x=?)c+e6VF{NS3=`9`K*a zuVv%qFq^{6M~-3noc}^k)NdAxXC~NEl?TCYX1vDyd!qSnQ$f1X&b{)i7-IVu(|K}kZh9|gi!Z9QDY5C+yG93$i2g-tWSEsNorw!O{lGp40p9MyFve`rbn zO@SWR7ELu5>2AmNcgQow$}IaeNPoj!{plpSgI4228Vd#x8Q%Xl#y_)^hVGU zB^}rtWmv%kB`(qV3B`rV2iP8#5zr;XQgS33m9(-KoHa;4+M2n7Y8$R)0@oAc-g ziXV-vhB6M3KPl?*3HQQgPXdO;hc?7GKD;$f(yT{v6qU#{3j0(TpV3s9gffMRbmT}o zjTFw{GgJum9uCz!QcyOe(y_D*S(u4l9&h9U{UImSGV~WKQYB57BDe0KFQ^aB-zq*kS^91-}IW7Nuo<68s8u2ht~mbxd#%0tIoE zsDt&NaI(vqq*VEr&@j7t97_q7<%KEDy$B4QEjRjSNLKE&qsKg!=dpJT6 z^~;WkWiY-*%Jpy}^G$tr>MkRi57LgW%4evqBy~3>#yNomRIN#PcF}6md9v7PA+@PRM z3=V_6i{}$aKM&g)A)$T=bv)T>LKhE5t@3+BHVSOGL;003o`T!Y+dIME0mrY)AR2T7 zN-??!h!5bID`wc;gv!y!GpGweg^JP3_bH|HuJfb7I2V)5fX)BS)gjO029T zmEGlcQYaL6;e(+cMGjMe1V_Z-g=?JB~*teqaAj_w8;o_mzs@+ z2~vk1@EqDE%@3c2mHxu5Ycr(_mo81-m?=$8UAlhx>eTC-xOhY$msgG~4LFrq9FF$Y{}O!>+7b{Ono zgaKn?q^`4;0&adDxH-96GZIN8HafMo~b8p8*F=7(@L251G3`|mWQI;3laf0(Eg1sNirOoybqj* zTVK_xjpZor&oEg)gNSp zojHdB5|=TS65qRo?1%x+SPAHC!h(=*;e-GUe3Bn-i5zp(BeCK;38m67oZn#~ED32W zYm$NfT^{-v%ZT(|9JMJGu8_=LK-okVkdjL=fvSc(EhOvm7wgvj0^4OjsZ|n0Q zz9&{fD^~v&4j-X{UXo-66`D)<`Tq|cq(6sR0#zFP?0H69AbORmChkiJ4)Phz zddeuE$d%Yt$; zCvV{>{k~|kg41}cFI_F**m7hu~Ub!Fq!tF?sd&G$rHzMtDCHO@b~;{ zlne70ufJWoHFM>}@zS-)%~uyE!)$c#bT|Llsm-Nub#YSX(ZDcOYOG)@66`jH)A8th z0?v7ddpH5~iIqGY;B8~e>Z7V??K*=>O8D4yQWOoAi43<^azH%}EK*$XPZI`Xb|eo< z3h&ZlrLUtQJ*o*k9U1(DhH6Me5eXRY*$kZuYpJy~!@LmQ1$2(xG;)5Slj-EZu5vUR zAZmw7wA0DSI(FEgWN(gZ*h44T$w>NLm0ZgNa65Ez$VnpXYhWI$(nX5=^6Rq81wDh; zAo^fv=297eAD$T3ENgno5w=EfA*LlXNs*Ff(*T2&O+4e}I2t;dye^djk*rY#W+@3p zEDTG8WoFqpm#JcjkzQC~L`jUy`g}=Gtn*W%2?MZ`{B(ndM?7#b?%1I42ks5=pkx}Q zU39l5LJ1x2GGP?+=a{*OC%8W!%%fNx%wRyg*wolJ>ey9JJ~^thRGo)Iz{6tq-{km9 zObU?v2zHU;=3EZYycvCr_AQP!e%(JjvEhL}kBd0|1eRWKj3s!HFu~VEK~V74VTZK_GPsh_Eb-^?b(eyeaJ(qa zjv$3Lg>kwL;q~Zu<{ktK7{|{n+<;OmY@P>bO9CsmA`ML`ss|>5s3O_ULrf094uV2u zbf!5PeOM9Ia|%_u_V?u1$U3I!f}OkuW0j)H?i8m7(BDv0*WvHQNi9c)|NV$)bu7FU13 zZ=btObGIn{QIo!K0D*!B{57o60|>%WcIto(Jq5wBHVLdA7q)c3pMo+&hG|wd?B-k8-z{ByZ)P%l_Gx!+&P?l<`S7QB_#+$;N{Baswfr`71jv7j z*-2~z(NCW{!~+|{3m}uK!m7bPC5?*qlHn#$Y3M9AWC53(L3{aF-|6QD_J++;w^I^|13StEew~i#R8`G zbbAQX6jzVLyLhjNNepK__Mn@JCNd5tKbwZ@j)>UxYCow=5srickOyH)3J|{;;*NCgCw8woVQ~N;T`}i0F(Y+`^Q^*HK1^71-F_30KVS zNz$o1cuB@#cCRF);Hm{#wWMjThC!yQ)r;EA-M6A$PJ9WGF??(<>)^oX1wNIMRpH^z zSUbOTp>*-q)a7fFr5hJ!-UzqE`8Tdl&)hUX>Lecq(trfVPO1UfzCO>q%RCf$;I37& zN0aPa6AncKQ7<>3T58z2OH_+=ix%LCW;=-=dl;p`?B@ei5$J`aD4 zLpT7fwT%!Y>|XXCnaQ{+iP90jh6E9aH{SR<=oZ{CUy12dY(-+75L1Gva|m&!MZ?oq zS^iBP-r|9vT(Ft|3jIC4>pkYdI*<^yEu2Saa}!OUV>vNtWY1s|LhNXLj>O}y%7C`t|UXob6|4YKXy|2WOg*0&kkhI=U&NXvj@QXwq&2n?#;T{ z&*pY#w`K>k+q1jzZzP+`9^lTej+5P$-6?klv%64!NA_$kpWTytE;oRhUJ$=Y63>4s OyA39_WOhe(+y4PfPhaW) literal 0 HcmV?d00001 diff --git a/requests/__version__.py b/requests/__version__.py new file mode 100644 index 0000000..e973b03 --- /dev/null +++ b/requests/__version__.py @@ -0,0 +1,14 @@ +# .-. .-. .-. . . .-. .-. .-. .-. +# |( |- |.| | | |- `-. | `-. +# ' ' `-' `-`.`-' `-' `-' ' `-' + +__title__ = 'requests' +__description__ = 'Python HTTP for Humans.' +__url__ = 'https://requests.readthedocs.io' +__version__ = '2.27.1' +__build__ = 0x022701 +__author__ = 'Kenneth Reitz' +__author_email__ = 'me@kennethreitz.org' +__license__ = 'Apache 2.0' +__copyright__ = 'Copyright 2022 Kenneth Reitz' +__cake__ = u'\u2728 \U0001f370 \u2728' diff --git a/requests/_internal_utils.py b/requests/_internal_utils.py new file mode 100644 index 0000000..759d9a5 --- /dev/null +++ b/requests/_internal_utils.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +""" +requests._internal_utils +~~~~~~~~~~~~~~ + +Provides utility functions that are consumed internally by Requests +which depend on extremely few external helpers (such as compat) +""" + +from .compat import is_py2, builtin_str, str + + +def to_native_string(string, encoding='ascii'): + """Given a string object, regardless of type, returns a representation of + that string in the native string type, encoding and decoding where + necessary. This assumes ASCII unless told otherwise. + """ + if isinstance(string, builtin_str): + out = string + else: + if is_py2: + out = string.encode(encoding) + else: + out = string.decode(encoding) + + return out + + +def unicode_is_ascii(u_string): + """Determine if unicode string only contains ASCII characters. + + :param str u_string: unicode string to check. Must be unicode + and not Python 2 `str`. + :rtype: bool + """ + assert isinstance(u_string, str) + try: + u_string.encode('ascii') + return True + except UnicodeEncodeError: + return False diff --git a/requests/adapters.py b/requests/adapters.py new file mode 100644 index 0000000..fe22ff4 --- /dev/null +++ b/requests/adapters.py @@ -0,0 +1,538 @@ +# -*- coding: utf-8 -*- + +""" +requests.adapters +~~~~~~~~~~~~~~~~~ + +This module contains the transport adapters that Requests uses to define +and maintain connections. +""" + +import os.path +import socket + +from urllib3.poolmanager import PoolManager, proxy_from_url +from urllib3.response import HTTPResponse +from urllib3.util import parse_url +from urllib3.util import Timeout as TimeoutSauce +from urllib3.util.retry import Retry +from urllib3.exceptions import ClosedPoolError +from urllib3.exceptions import ConnectTimeoutError +from urllib3.exceptions import HTTPError as _HTTPError +from urllib3.exceptions import InvalidHeader as _InvalidHeader +from urllib3.exceptions import MaxRetryError +from urllib3.exceptions import NewConnectionError +from urllib3.exceptions import ProxyError as _ProxyError +from urllib3.exceptions import ProtocolError +from urllib3.exceptions import ReadTimeoutError +from urllib3.exceptions import SSLError as _SSLError +from urllib3.exceptions import ResponseError +from urllib3.exceptions import LocationValueError + +from .models import Response +from .compat import urlparse, basestring +from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths, + get_encoding_from_headers, prepend_scheme_if_needed, + get_auth_from_url, urldefragauth, select_proxy) +from .structures import CaseInsensitiveDict +from .cookies import extract_cookies_to_jar +from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError, + ProxyError, RetryError, InvalidSchema, InvalidProxyURL, + InvalidURL, InvalidHeader) +from .auth import _basic_auth_str + +try: + from urllib3.contrib.socks import SOCKSProxyManager +except ImportError: + def SOCKSProxyManager(*args, **kwargs): + raise InvalidSchema("Missing dependencies for SOCKS support.") + +DEFAULT_POOLBLOCK = False +DEFAULT_POOLSIZE = 10 +DEFAULT_RETRIES = 0 +DEFAULT_POOL_TIMEOUT = None + + +class BaseAdapter(object): + """The Base Transport Adapter""" + + def __init__(self): + super(BaseAdapter, self).__init__() + + def send(self, request, stream=False, timeout=None, verify=True, + cert=None, proxies=None): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + raise NotImplementedError + + def close(self): + """Cleans up adapter specific items.""" + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session ` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> a = requests.adapters.HTTPAdapter(max_retries=3) + >>> s.mount('http://', a) + """ + __attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize', + '_pool_block'] + + def __init__(self, pool_connections=DEFAULT_POOLSIZE, + pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES, + pool_block=DEFAULT_POOLBLOCK): + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super(HTTPAdapter, self).__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self): + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager(self._pool_connections, self._pool_maxsize, + block=self._pool_block) + + def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs): + """Initializes a urllib3 PoolManager. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, + block=block, strict=True, **pool_kwargs) + + def proxy_manager_for(self, proxy, **proxy_kwargs): + """Return urllib3 ProxyManager for the given proxy. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + :rtype: urllib3.ProxyManager + """ + if proxy in self.proxy_manager: + manager = self.proxy_manager[proxy] + elif proxy.lower().startswith('socks'): + username, password = get_auth_from_url(proxy) + manager = self.proxy_manager[proxy] = SOCKSProxyManager( + proxy, + username=username, + password=password, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs + ) + else: + proxy_headers = self.proxy_headers(proxy) + manager = self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs) + + return manager + + def cert_verify(self, conn, url, verify, cert): + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith('https') and verify: + + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH) + + if not cert_loc or not os.path.exists(cert_loc): + raise IOError("Could not find a suitable TLS CA certificate bundle, " + "invalid path: {}".format(cert_loc)) + + conn.cert_reqs = 'CERT_REQUIRED' + + if not os.path.isdir(cert_loc): + conn.ca_certs = cert_loc + else: + conn.ca_cert_dir = cert_loc + else: + conn.cert_reqs = 'CERT_NONE' + conn.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + conn.cert_file = cert + conn.key_file = None + if conn.cert_file and not os.path.exists(conn.cert_file): + raise IOError("Could not find the TLS certificate file, " + "invalid path: {}".format(conn.cert_file)) + if conn.key_file and not os.path.exists(conn.key_file): + raise IOError("Could not find the TLS key file, " + "invalid path: {}".format(conn.key_file)) + + def build_response(self, req, resp): + """Builds a :class:`Response ` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter ` + + :param req: The :class:`PreparedRequest ` used to generate the response. + :param resp: The urllib3 response object. + :rtype: requests.Response + """ + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, 'status', None) + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode('utf-8') + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def get_connection(self, url, proxies=None): + """Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + :rtype: urllib3.ConnectionPool + """ + proxy = select_proxy(url, proxies) + + if proxy: + proxy = prepend_scheme_if_needed(proxy, 'http') + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL("Please check proxy URL. It is malformed" + " and could be missing the host.") + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self): + """Disposes of any internal state. + + Currently, this closes the PoolManager and any active ProxyManager, + which closes any pooled connections. + """ + self.poolmanager.clear() + for proxy in self.proxy_manager.values(): + proxy.clear() + + def request_url(self, request, proxies): + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + :rtype: str + """ + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + + is_proxied_http_request = (proxy and scheme != 'https') + using_socks_proxy = False + if proxy: + proxy_scheme = urlparse(proxy).scheme.lower() + using_socks_proxy = proxy_scheme.startswith('socks') + + url = request.path_url + if is_proxied_http_request and not using_socks_proxy: + url = urldefragauth(request.url) + + return url + + def add_headers(self, request, **kwargs): + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter `. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy): + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The url of the proxy being used for this request. + :rtype: dict + """ + headers = {} + username, password = get_auth_from_url(proxy) + + if username: + headers['Proxy-Authorization'] = _basic_auth_str(username, + password) + + return headers + + def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple or urllib3 Timeout object + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + :rtype: requests.Response + """ + + try: + conn = self.get_connection(request.url, proxies) + except LocationValueError as e: + raise InvalidURL(e, request=request) + + self.cert_verify(conn, request.url, verify, cert) + url = self.request_url(request, proxies) + self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies) + + chunked = not (request.body is None or 'Content-Length' in request.headers) + + if isinstance(timeout, tuple): + try: + connect, read = timeout + timeout = TimeoutSauce(connect=connect, read=read) + except ValueError as e: + # this may raise a string formatting error. + err = ("Invalid timeout {}. Pass a (connect, read) " + "timeout tuple, or a single float to set " + "both timeouts to the same value".format(timeout)) + raise ValueError(err) + elif isinstance(timeout, TimeoutSauce): + pass + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + if not chunked: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout + ) + + # Send the request. + else: + if hasattr(conn, 'proxy_pool'): + conn = conn.proxy_pool + + low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) + + try: + skip_host = 'Host' in request.headers + low_conn.putrequest(request.method, + url, + skip_accept_encoding=True, + skip_host=skip_host) + + for header, value in request.headers.items(): + low_conn.putheader(header, value) + + low_conn.endheaders() + + for i in request.body: + low_conn.send(hex(len(i))[2:].encode('utf-8')) + low_conn.send(b'\r\n') + low_conn.send(i) + low_conn.send(b'\r\n') + low_conn.send(b'0\r\n\r\n') + + # Receive the response from the server + try: + # For Python 2.7, use buffering of HTTP responses + r = low_conn.getresponse(buffering=True) + except TypeError: + # For compatibility with Python 3.3+ + r = low_conn.getresponse() + + resp = HTTPResponse.from_httplib( + r, + pool=conn, + connection=low_conn, + preload_content=False, + decode_content=False + ) + except: + # If we hit any problems here, clean up the connection. + # Then, reraise so that we can handle the actual exception. + low_conn.close() + raise + + except (ProtocolError, socket.error) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + if isinstance(e.reason, _ProxyError): + raise ProxyError(e, request=request) + + if isinstance(e.reason, _SSLError): + # This branch is for urllib3 v1.22 and later. + raise SSLError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + # This branch is for urllib3 versions earlier than v1.22 + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + elif isinstance(e, _InvalidHeader): + raise InvalidHeader(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/requests/api.py b/requests/api.py new file mode 100644 index 0000000..4cba90e --- /dev/null +++ b/requests/api.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- + +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. +""" + +from . import sessions + + +def request(method, url, **kwargs): + """Constructs and sends a :class:`Request `. + + :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. + ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` + or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string + defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers + to add for the file. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response ` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'https://httpbin.org/get') + >>> req + + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get(url, params=None, **kwargs): + r"""Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('get', url, params=params, **kwargs) + + +def options(url, **kwargs): + r"""Sends an OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('options', url, **kwargs) + + +def head(url, **kwargs): + r"""Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. If + `allow_redirects` is not provided, it will be set to `False` (as + opposed to the default :meth:`request` behavior). + :return: :class:`Response ` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', False) + return request('head', url, **kwargs) + + +def post(url, data=None, json=None, **kwargs): + r"""Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('post', url, data=data, json=json, **kwargs) + + +def put(url, data=None, **kwargs): + r"""Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('put', url, data=data, **kwargs) + + +def patch(url, data=None, **kwargs): + r"""Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('patch', url, data=data, **kwargs) + + +def delete(url, **kwargs): + r"""Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('delete', url, **kwargs) diff --git a/requests/auth.py b/requests/auth.py new file mode 100644 index 0000000..eeface3 --- /dev/null +++ b/requests/auth.py @@ -0,0 +1,305 @@ +# -*- coding: utf-8 -*- + +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +import os +import re +import time +import hashlib +import threading +import warnings + +from base64 import b64encode + +from .compat import urlparse, str, basestring +from .cookies import extract_cookies_to_jar +from ._internal_utils import to_native_string +from .utils import parse_dict_header + +CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' +CONTENT_TYPE_MULTI_PART = 'multipart/form-data' + + +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + # "I want us to put a big-ol' comment on top of it that + # says that this behaviour is dumb but we need to preserve + # it because people are relying on it." + # - Lukasa + # + # These are here solely to maintain backwards compatibility + # for things like ints. This will be removed in 3.0.0. + if not isinstance(username, basestring): + warnings.warn( + "Non-string usernames will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(username), + category=DeprecationWarning, + ) + username = str(username) + + if not isinstance(password, basestring): + warnings.warn( + "Non-string passwords will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(type(password)), + category=DeprecationWarning, + ) + password = str(password) + # -- End Removal -- + + if isinstance(username, str): + username = username.encode('latin1') + + if isinstance(password, str): + password = password.encode('latin1') + + authstr = 'Basic ' + to_native_string( + b64encode(b':'.join((username, password))).strip() + ) + + return authstr + + +class AuthBase(object): + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError('Auth hooks must be callable.') + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + + def __eq__(self, other): + return all([ + self.username == getattr(other, 'username', None), + self.password == getattr(other, 'password', None) + ]) + + def __ne__(self, other): + return not self == other + + def __call__(self, r): + r.headers['Authorization'] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPProxyAuth(HTTPBasicAuth): + """Attaches HTTP Proxy Authentication to a given Request object.""" + + def __call__(self, r): + r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self): + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, 'init'): + self._thread_local.init = True + self._thread_local.last_nonce = '' + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method, url): + """ + :rtype: str + """ + + realm = self._thread_local.chal['realm'] + nonce = self._thread_local.chal['nonce'] + qop = self._thread_local.chal.get('qop') + algorithm = self._thread_local.chal.get('algorithm') + opaque = self._thread_local.chal.get('opaque') + hash_utf8 = None + + if algorithm is None: + _algorithm = 'MD5' + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': + def md5_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.md5(x).hexdigest() + hash_utf8 = md5_utf8 + elif _algorithm == 'SHA': + def sha_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha1(x).hexdigest() + hash_utf8 = sha_utf8 + elif _algorithm == 'SHA-256': + def sha256_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha256(x).hexdigest() + hash_utf8 = sha256_utf8 + elif _algorithm == 'SHA-512': + def sha512_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha512(x).hexdigest() + hash_utf8 = sha512_utf8 + + KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) + + if hash_utf8 is None: + return None + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: path is request-uri defined in RFC 2616 which should not be empty + path = p_parsed.path or "/" + if p_parsed.query: + path += '?' + p_parsed.query + + A1 = '%s:%s:%s' % (self.username, realm, self.password) + A2 = '%s:%s' % (method, path) + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + self._thread_local.nonce_count += 1 + else: + self._thread_local.nonce_count = 1 + ncvalue = '%08x' % self._thread_local.nonce_count + s = str(self._thread_local.nonce_count).encode('utf-8') + s += nonce.encode('utf-8') + s += time.ctime().encode('utf-8') + s += os.urandom(8) + + cnonce = (hashlib.sha1(s).hexdigest()[:16]) + if _algorithm == 'MD5-SESS': + HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) + + if not qop: + respdig = KD(HA1, "%s:%s" % (nonce, HA2)) + elif qop == 'auth' or 'auth' in qop.split(','): + noncebit = "%s:%s:%s:%s:%s" % ( + nonce, ncvalue, cnonce, 'auth', HA2 + ) + respdig = KD(HA1, noncebit) + else: + # XXX handle auth-int. + return None + + self._thread_local.last_nonce = nonce + + # XXX should the partial digests be encoded too? + base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ + 'response="%s"' % (self.username, realm, nonce, path, respdig) + if opaque: + base += ', opaque="%s"' % opaque + if algorithm: + base += ', algorithm="%s"' % algorithm + if entdig: + base += ', digest="%s"' % entdig + if qop: + base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) + + return 'Digest %s' % (base) + + def handle_redirect(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r, **kwargs): + """ + Takes the given response and tries digest-auth, if needed. + + :rtype: requests.Response + """ + + # If response is not 4xx, do not auth + # See https://github.com/psf/requests/issues/3772 + if not 400 <= r.status_code < 500: + self._thread_local.num_401_calls = 1 + return r + + if self._thread_local.pos is not None: + # Rewind the file position indicator of the body to where + # it was to resend the request. + r.request.body.seek(self._thread_local.pos) + s_auth = r.headers.get('www-authenticate', '') + + if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: + + self._thread_local.num_401_calls += 1 + pat = re.compile(r'digest ', flags=re.IGNORECASE) + self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + extract_cookies_to_jar(prep._cookies, r.request, r.raw) + prep.prepare_cookies(prep._cookies) + + prep.headers['Authorization'] = self.build_digest_header( + prep.method, prep.url) + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r): + # Initialize per-thread state, if needed + self.init_per_thread_state() + # If we have a saved nonce, skip the 401 + if self._thread_local.last_nonce: + r.headers['Authorization'] = self.build_digest_header(r.method, r.url) + try: + self._thread_local.pos = r.body.tell() + except AttributeError: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # file position of the previous body. Ensure it's set to + # None. + self._thread_local.pos = None + r.register_hook('response', self.handle_401) + r.register_hook('response', self.handle_redirect) + self._thread_local.num_401_calls = 1 + + return r + + def __eq__(self, other): + return all([ + self.username == getattr(other, 'username', None), + self.password == getattr(other, 'password', None) + ]) + + def __ne__(self, other): + return not self == other diff --git a/requests/certs.py b/requests/certs.py new file mode 100644 index 0000000..d1a378d --- /dev/null +++ b/requests/certs.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +requests.certs +~~~~~~~~~~~~~~ + +This module returns the preferred default CA certificate bundle. There is +only one — the one from the certifi package. + +If you are packaging Requests, e.g., for a Linux distribution or a managed +environment, you can change the definition of where() to return a separately +packaged CA bundle. +""" +from certifi import where + +if __name__ == '__main__': + print(where()) diff --git a/requests/compat.py b/requests/compat.py new file mode 100644 index 0000000..029ae62 --- /dev/null +++ b/requests/compat.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +""" +requests.compat +~~~~~~~~~~~~~~~ + +This module handles import compatibility issues between Python 2 and +Python 3. +""" + +try: + import chardet +except ImportError: + import charset_normalizer as chardet + +import sys + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = (_ver[0] == 2) + +#: Python 3.x? +is_py3 = (_ver[0] == 3) + +has_simplejson = False +try: + import simplejson as json + has_simplejson = True +except ImportError: + import json + +# --------- +# Specifics +# --------- + +if is_py2: + from urllib import ( + quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, + proxy_bypass, proxy_bypass_environment, getproxies_environment) + from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag + from urllib2 import parse_http_list + import cookielib + from Cookie import Morsel + from StringIO import StringIO + # Keep OrderedDict for backwards compatibility. + from collections import Callable, Mapping, MutableMapping, OrderedDict + + builtin_str = str + bytes = str + str = unicode + basestring = basestring + numeric_types = (int, long, float) + integer_types = (int, long) + JSONDecodeError = ValueError + +elif is_py3: + from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag + from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment + from http import cookiejar as cookielib + from http.cookies import Morsel + from io import StringIO + # Keep OrderedDict for backwards compatibility. + from collections import OrderedDict + from collections.abc import Callable, Mapping, MutableMapping + if has_simplejson: + from simplejson import JSONDecodeError + else: + from json import JSONDecodeError + + builtin_str = str + str = str + bytes = bytes + basestring = (str, bytes) + numeric_types = (int, float) + integer_types = (int,) diff --git a/requests/cookies.py b/requests/cookies.py new file mode 100644 index 0000000..56fccd9 --- /dev/null +++ b/requests/cookies.py @@ -0,0 +1,549 @@ +# -*- coding: utf-8 -*- + +""" +requests.cookies +~~~~~~~~~~~~~~~~ + +Compatibility code to be able to use `cookielib.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +import copy +import time +import calendar + +from ._internal_utils import to_native_string +from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping + +try: + import threading +except ImportError: + import dummy_threading as threading + + +class MockRequest(object): + """Wraps a `requests.Request` to mimic a `urllib2.Request`. + + The code in `cookielib.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + def __init__(self, request): + self._r = request + self._new_headers = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self): + return self.type + + def get_host(self): + return urlparse(self._r.url).netloc + + def get_origin_req_host(self): + return self.get_host() + + def get_full_url(self): + # Only return the response's URL if the user hadn't set the Host + # header + if not self._r.headers.get('Host'): + return self._r.url + # If they did set it, retrieve it and reconstruct the expected domain + host = to_native_string(self._r.headers['Host'], encoding='utf-8') + parsed = urlparse(self._r.url) + # Reconstruct the URL as we expect it + return urlunparse([ + parsed.scheme, host, parsed.path, parsed.params, parsed.query, + parsed.fragment + ]) + + def is_unverifiable(self): + return True + + def has_header(self, name): + return name in self._r.headers or name in self._new_headers + + def get_header(self, name, default=None): + return self._r.headers.get(name, self._new_headers.get(name, default)) + + def add_header(self, key, val): + """cookielib has no legitimate use for this method; add it back if you find one.""" + raise NotImplementedError("Cookie headers should be added with add_unredirected_header()") + + def add_unredirected_header(self, name, value): + self._new_headers[name] = value + + def get_new_headers(self): + return self._new_headers + + @property + def unverifiable(self): + return self.is_unverifiable() + + @property + def origin_req_host(self): + return self.get_origin_req_host() + + @property + def host(self): + return self.get_host() + + +class MockResponse(object): + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `cookielib` expects to see them. + """ + + def __init__(self, headers): + """Make a MockResponse for `cookielib` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self): + return self._headers + + def getheaders(self, name): + self._headers.getheaders(name) + + +def extract_cookies_to_jar(jar, request, response): + """Extract the cookies from the response into a CookieJar. + + :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, '_original_response') and + response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) + + +def get_cookie_header(jar, request): + """ + Produce an appropriate Cookie header string to be sent with `request`, or None. + + :rtype: str + """ + r = MockRequest(request) + jar.add_cookie_header(r) + return r.get_new_headers().get('Cookie') + + +def remove_cookie_by_name(cookiejar, name, domain=None, path=None): + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific. + """ + + +class RequestsCookieJar(cookielib.CookieJar, MutableMapping): + """Compatibility class; is a cookielib.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + + def get(self, name, default=None, domain=None, path=None): + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1). + """ + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set(self, name, value, **kwargs): + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + """ + # support client code that unsets cookies by assignment of a None value: + if value is None: + remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path')) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self): + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. + + .. seealso:: itervalues() and iteritems(). + """ + for cookie in iter(self): + yield cookie.name + + def keys(self): + """Dict-like keys() that returns a list of names of cookies from the + jar. + + .. seealso:: values() and items(). + """ + return list(self.iterkeys()) + + def itervalues(self): + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. + + .. seealso:: iterkeys() and iteritems(). + """ + for cookie in iter(self): + yield cookie.value + + def values(self): + """Dict-like values() that returns a list of values of cookies from the + jar. + + .. seealso:: keys() and items(). + """ + return list(self.itervalues()) + + def iteritems(self): + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. + + .. seealso:: iterkeys() and itervalues(). + """ + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self): + """Dict-like items() that returns a list of name-value tuples from the + jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a + vanilla python dict of key value pairs. + + .. seealso:: keys() and values(). + """ + return list(self.iteritems()) + + def list_domains(self): + """Utility method to list all the domains in the jar.""" + domains = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self): + """Utility method to list all the paths in the jar.""" + paths = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self): + """Returns True if there are multiple domains in the jar. + Returns False otherwise. + + :rtype: bool + """ + domains = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict(self, domain=None, path=None): + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements. + + :rtype: dict + """ + dictionary = {} + for cookie in iter(self): + if ( + (domain is None or cookie.domain == domain) and + (path is None or cookie.path == path) + ): + dictionary[cookie.name] = cookie.value + return dictionary + + def __contains__(self, name): + try: + return super(RequestsCookieJar, self).__contains__(name) + except CookieConflictError: + return True + + def __getitem__(self, name): + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1). + """ + return self._find_no_duplicates(name) + + def __setitem__(self, name, value): + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead. + """ + self.set(name, value) + + def __delitem__(self, name): + """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s + ``remove_cookie_by_name()``. + """ + remove_cookie_by_name(self, name) + + def set_cookie(self, cookie, *args, **kwargs): + if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'): + cookie.value = cookie.value.replace('\\"', '') + return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs) + + def update(self, other): + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super(RequestsCookieJar, self).update(other) + + def _find(self, name, domain=None, path=None): + """Requests uses this method internally to get cookie values. + + If there are conflicting cookies, _find arbitrarily chooses one. + See _find_no_duplicates if you want an exception thrown if there are + conflicting cookies. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :return: cookie.value + """ + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def _find_no_duplicates(self, name, domain=None, path=None): + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :raises KeyError: if cookie is not found + :raises CookieConflictError: if there are multiple cookies + that match name and optionally domain and path + :return: cookie.value + """ + toReturn = None + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if toReturn is not None: # if there are multiple cookies that meet passed in criteria + raise CookieConflictError('There are multiple cookies with name, %r' % (name)) + toReturn = cookie.value # we will eventually return this as long as no cookie conflict + + if toReturn: + return toReturn + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + state.pop('_cookies_lock') + return state + + def __setstate__(self, state): + """Unlike a normal CookieJar, this class is pickleable.""" + self.__dict__.update(state) + if '_cookies_lock' not in self.__dict__: + self._cookies_lock = threading.RLock() + + def copy(self): + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.set_policy(self.get_policy()) + new_cj.update(self) + return new_cj + + def get_policy(self): + """Return the CookiePolicy instance used.""" + return self._policy + + +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, 'copy'): + # We're dealing with an instance of RequestsCookieJar + return jar.copy() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name, value, **kwargs): + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + and sent on every request (this is sometimes called a "supercookie"). + """ + result = { + 'version': 0, + 'name': name, + 'value': value, + 'port': None, + 'domain': '', + 'path': '/', + 'secure': False, + 'expires': None, + 'discard': True, + 'comment': None, + 'comment_url': None, + 'rest': {'HttpOnly': None}, + 'rfc2109': False, + } + + badargs = set(kwargs) - set(result) + if badargs: + err = 'create_cookie() got unexpected keyword arguments: %s' + raise TypeError(err % list(badargs)) + + result.update(kwargs) + result['port_specified'] = bool(result['port']) + result['domain_specified'] = bool(result['domain']) + result['domain_initial_dot'] = result['domain'].startswith('.') + result['path_specified'] = bool(result['path']) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel): + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires = None + if morsel['max-age']: + try: + expires = int(time.time() + int(morsel['max-age'])) + except ValueError: + raise TypeError('max-age: %s must be integer' % morsel['max-age']) + elif morsel['expires']: + time_template = '%a, %d-%b-%Y %H:%M:%S GMT' + expires = calendar.timegm( + time.strptime(morsel['expires'], time_template) + ) + return create_cookie( + comment=morsel['comment'], + comment_url=bool(morsel['comment']), + discard=False, + domain=morsel['domain'], + expires=expires, + name=morsel.key, + path=morsel['path'], + port=None, + rest={'HttpOnly': morsel['httponly']}, + rfc2109=False, + secure=bool(morsel['secure']), + value=morsel.value, + version=morsel['version'] or 0, + ) + + +def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + :rtype: CookieJar + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies(cookiejar, cookies): + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + :rtype: CookieJar + """ + if not isinstance(cookiejar, cookielib.CookieJar): + raise ValueError('You can only merge into CookieJar') + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict( + cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + try: + cookiejar.update(cookies) + except AttributeError: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/requests/exceptions.py b/requests/exceptions.py new file mode 100644 index 0000000..7969763 --- /dev/null +++ b/requests/exceptions.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- + +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. +""" +from urllib3.exceptions import HTTPError as BaseHTTPError + +from .compat import JSONDecodeError as CompatJSONDecodeError + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request. + """ + + def __init__(self, *args, **kwargs): + """Initialize RequestException with `request` and `response` objects.""" + response = kwargs.pop('response', None) + self.response = response + self.request = kwargs.pop('request', None) + if (response is not None and not self.request and + hasattr(response, 'request')): + self.request = self.response.request + super(RequestException, self).__init__(*args, **kwargs) + + +class InvalidJSONError(RequestException): + """A JSON error occurred.""" + + +class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError): + """Couldn't decode the text into json""" + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL scheme (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """The URL scheme provided is either invalid or unsupported.""" + + +class InvalidURL(RequestException, ValueError): + """The URL provided was somehow invalid.""" + + +class InvalidHeader(RequestException, ValueError): + """The header value provided was somehow invalid.""" + + +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content.""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed.""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +class UnrewindableBodyError(RequestException): + """Requests encountered an error when trying to rewind a body.""" + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """A file was opened in text mode, but Requests determined its binary length.""" + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" diff --git a/requests/help.py b/requests/help.py new file mode 100644 index 0000000..4cd6389 --- /dev/null +++ b/requests/help.py @@ -0,0 +1,135 @@ +"""Module containing bug report helper(s).""" +from __future__ import print_function + +import json +import platform +import sys +import ssl + +import idna +import urllib3 + +from . import __version__ as requests_version + +try: + import charset_normalizer +except ImportError: + charset_normalizer = None + +try: + import chardet +except ImportError: + chardet = None + +try: + from urllib3.contrib import pyopenssl +except ImportError: + pyopenssl = None + OpenSSL = None + cryptography = None +else: + import OpenSSL + import cryptography + + +def _implementation(): + """Return a dict with the Python implementation and version. + + Provide both the name and the version of the Python implementation + currently running. For example, on CPython 2.7.5 it will return + {'name': 'CPython', 'version': '2.7.5'}. + + This function works best on CPython and PyPy: in particular, it probably + doesn't work for Jython or IronPython. Future investigation should be done + to work out the correct shape of the code for those platforms. + """ + implementation = platform.python_implementation() + + if implementation == 'CPython': + implementation_version = platform.python_version() + elif implementation == 'PyPy': + implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major, + sys.pypy_version_info.minor, + sys.pypy_version_info.micro) + if sys.pypy_version_info.releaselevel != 'final': + implementation_version = ''.join([ + implementation_version, sys.pypy_version_info.releaselevel + ]) + elif implementation == 'Jython': + implementation_version = platform.python_version() # Complete Guess + elif implementation == 'IronPython': + implementation_version = platform.python_version() # Complete Guess + else: + implementation_version = 'Unknown' + + return {'name': implementation, 'version': implementation_version} + + +def info(): + """Generate information for a bug report.""" + try: + platform_info = { + 'system': platform.system(), + 'release': platform.release(), + } + except IOError: + platform_info = { + 'system': 'Unknown', + 'release': 'Unknown', + } + + implementation_info = _implementation() + urllib3_info = {'version': urllib3.__version__} + charset_normalizer_info = {'version': None} + chardet_info = {'version': None} + if charset_normalizer: + charset_normalizer_info = {'version': charset_normalizer.__version__} + if chardet: + chardet_info = {'version': chardet.__version__} + + pyopenssl_info = { + 'version': None, + 'openssl_version': '', + } + if OpenSSL: + pyopenssl_info = { + 'version': OpenSSL.__version__, + 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER, + } + cryptography_info = { + 'version': getattr(cryptography, '__version__', ''), + } + idna_info = { + 'version': getattr(idna, '__version__', ''), + } + + system_ssl = ssl.OPENSSL_VERSION_NUMBER + system_ssl_info = { + 'version': '%x' % system_ssl if system_ssl is not None else '' + } + + return { + 'platform': platform_info, + 'implementation': implementation_info, + 'system_ssl': system_ssl_info, + 'using_pyopenssl': pyopenssl is not None, + 'using_charset_normalizer': chardet is None, + 'pyOpenSSL': pyopenssl_info, + 'urllib3': urllib3_info, + 'chardet': chardet_info, + 'charset_normalizer': charset_normalizer_info, + 'cryptography': cryptography_info, + 'idna': idna_info, + 'requests': { + 'version': requests_version, + }, + } + + +def main(): + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/requests/hooks.py b/requests/hooks.py new file mode 100644 index 0000000..7a51f21 --- /dev/null +++ b/requests/hooks.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. +""" +HOOKS = ['response'] + + +def default_hooks(): + return {event: [] for event in HOOKS} + +# TODO: response is the only one + + +def dispatch_hook(key, hooks, hook_data, **kwargs): + """Dispatches a hook dictionary on a given piece of data.""" + hooks = hooks or {} + hooks = hooks.get(key) + if hooks: + if hasattr(hooks, '__call__'): + hooks = [hooks] + for hook in hooks: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/requests/models.py b/requests/models.py new file mode 100644 index 0000000..dfbea85 --- /dev/null +++ b/requests/models.py @@ -0,0 +1,973 @@ +# -*- coding: utf-8 -*- + +""" +requests.models +~~~~~~~~~~~~~~~ + +This module contains the primary objects that power Requests. +""" + +import datetime +import sys + +# Import encoding now, to avoid implicit import later. +# Implicit import within threads may cause LookupError when standard library is in a ZIP, +# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. +import encodings.idna + +from urllib3.fields import RequestField +from urllib3.filepost import encode_multipart_formdata +from urllib3.util import parse_url +from urllib3.exceptions import ( + DecodeError, ReadTimeoutError, ProtocolError, LocationParseError) + +from io import UnsupportedOperation +from .hooks import default_hooks +from .structures import CaseInsensitiveDict + +from .auth import HTTPBasicAuth +from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar +from .exceptions import ( + HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, + ContentDecodingError, ConnectionError, StreamConsumedError, + InvalidJSONError) +from .exceptions import JSONDecodeError as RequestsJSONDecodeError +from ._internal_utils import to_native_string, unicode_is_ascii +from .utils import ( + guess_filename, get_auth_from_url, requote_uri, + stream_decode_response_unicode, to_key_val_list, parse_header_links, + iter_slices, guess_json_utf, super_len, check_header_validity) +from .compat import ( + Callable, Mapping, + cookielib, urlunparse, urlsplit, urlencode, str, bytes, + is_py2, chardet, builtin_str, basestring, JSONDecodeError) +from .compat import json as complexjson +from .status_codes import codes + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT = 30 +CONTENT_CHUNK_SIZE = 10 * 1024 +ITER_CHUNK_SIZE = 512 + + +class RequestEncodingMixin(object): + @property + def path_url(self): + """Build the path URL to use.""" + + url = [] + + p = urlsplit(self.url) + + path = p.path + if not path: + path = '/' + + url.append(path) + + query = p.query + if query: + url.append('?') + url.append(query) + + return ''.join(url) + + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, 'read'): + return data + elif hasattr(data, '__iter__'): + result = [] + for k, vs in to_key_val_list(data): + if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): + vs = [vs] + for v in vs: + if v is not None: + result.append( + (k.encode('utf-8') if isinstance(k, str) else k, + v.encode('utf-8') if isinstance(v, str) else v)) + return urlencode(result, doseq=True) + else: + return data + + @staticmethod + def _encode_files(files, data): + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + tuples. Order is retained if data is a list of tuples but arbitrary + if parameters are supplied as a dict. + The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) + or 4-tuples (filename, fileobj, contentype, custom_headers). + """ + if (not files): + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, '__iter__'): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + v = str(v) + + new_fields.append( + (field.decode('utf-8') if isinstance(field, bytes) else field, + v.encode('utf-8') if isinstance(v, str) else v)) + + for (k, v) in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + elif hasattr(fp, 'read'): + fdata = fp.read() + elif fp is None: + continue + else: + fdata = fp + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin(object): + def register_hook(self, event, hook): + """Properly register a hook.""" + + if event not in self.hooks: + raise ValueError('Unsupported event specified, with event name "%s"' % (event)) + + if isinstance(hook, Callable): + self.hooks[event].append(hook) + elif hasattr(hook, '__iter__'): + self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) + + def deregister_hook(self, event, hook): + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request ` object. + + Used to prepare a :class:`PreparedRequest `, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: URL parameters to append to the URL. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> req.prepare() + + """ + + def __init__(self, + method=None, url=None, headers=None, files=None, data=None, + params=None, auth=None, cookies=None, hooks=None, json=None): + + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for (k, v) in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self): + return '' % (self.method) + + def prepare(self): + """Constructs a :class:`PreparedRequest ` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest ` object, + containing the exact bytes that will be sent to the server. + + Instances are generated from a :class:`Request ` object, and + should not be instantiated manually; doing so may produce undesirable + effects. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> r = req.prepare() + >>> r + + + >>> s = requests.Session() + >>> s.send(r) + + """ + + def __init__(self): + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + #: integer denoting starting position of a readable file-like body. + self._body_position = None + + def prepare(self, + method=None, url=None, headers=None, files=None, data=None, + params=None, auth=None, cookies=None, hooks=None, json=None): + """Prepares the entire request with the given parameters.""" + + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self): + return '' % (self.method) + + def copy(self): + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + p._body_position = self._body_position + return p + + def prepare_method(self, method): + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + @staticmethod + def _get_idna_encoded_host(host): + import idna + + try: + host = idna.encode(host, uts46=True).decode('utf-8') + except idna.IDNAError: + raise UnicodeError + return host + + def prepare_url(self, url, params): + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/psf/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode('utf8') + else: + url = unicode(url) if is_py2 else str(url) + + # Remove leading whitespaces from url + url = url.lstrip() + + # Don't do any URL preparation for non-HTTP schemes like `mailto`, + # `data` etc to work around exceptions from `url_parse`, which + # handles RFC 3986 only. + if ':' in url and not url.lower().startswith('http'): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + raise InvalidURL(*e.args) + + if not scheme: + error = ("Invalid URL {0!r}: No scheme supplied. Perhaps you meant http://{0}?") + error = error.format(to_native_string(url, 'utf8')) + + raise MissingSchema(error) + + if not host: + raise InvalidURL("Invalid URL %r: No host supplied" % url) + + # In general, we want to try IDNA encoding the hostname if the string contains + # non-ASCII characters. This allows users to automatically get the correct IDNA + # behaviour. For strings containing only ASCII characters, we need to also verify + # it doesn't start with a wildcard (*), before allowing the unencoded hostname. + if not unicode_is_ascii(host): + try: + host = self._get_idna_encoded_host(host) + except UnicodeError: + raise InvalidURL('URL has an invalid label.') + elif host.startswith((u'*', u'.')): + raise InvalidURL('URL has an invalid label.') + + # Carefully reconstruct the network location + netloc = auth or '' + if netloc: + netloc += '@' + netloc += host + if port: + netloc += ':' + str(port) + + # Bare domains aren't valid URLs. + if not path: + path = '/' + + if is_py2: + if isinstance(scheme, str): + scheme = scheme.encode('utf-8') + if isinstance(netloc, str): + netloc = netloc.encode('utf-8') + if isinstance(path, str): + path = path.encode('utf-8') + if isinstance(query, str): + query = query.encode('utf-8') + if isinstance(fragment, str): + fragment = fragment.encode('utf-8') + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + enc_params = self._encode_params(params) + if enc_params: + if query: + query = '%s&%s' % (query, enc_params) + else: + query = enc_params + + url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) + self.url = url + + def prepare_headers(self, headers): + """Prepares the given HTTP headers.""" + + self.headers = CaseInsensitiveDict() + if headers: + for header in headers.items(): + # Raise exception on invalid header value. + check_header_validity(header) + name, value = header + self.headers[to_native_string(name)] = value + + def prepare_body(self, data, files, json=None): + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + + if not data and json is not None: + # urllib3 requires a bytes-like body. Python 2's json.dumps + # provides this natively, but Python 3 gives a Unicode string. + content_type = 'application/json' + + try: + body = complexjson.dumps(json, allow_nan=False) + except ValueError as ve: + raise InvalidJSONError(ve, request=self) + + if not isinstance(body, bytes): + body = body.encode('utf-8') + + is_stream = all([ + hasattr(data, '__iter__'), + not isinstance(data, (basestring, list, tuple, Mapping)) + ]) + + if is_stream: + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + body = data + + if getattr(body, 'tell', None) is not None: + # Record the current file position before reading. + # This will allow us to rewind a file in the event + # of a redirect. + try: + self._body_position = body.tell() + except (IOError, OSError): + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body + self._body_position = object() + + if files: + raise NotImplementedError('Streamed bodies and files are mutually exclusive.') + + if length: + self.headers['Content-Length'] = builtin_str(length) + else: + self.headers['Transfer-Encoding'] = 'chunked' + else: + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, data) + else: + if data: + body = self._encode_params(data) + if isinstance(data, basestring) or hasattr(data, 'read'): + content_type = None + else: + content_type = 'application/x-www-form-urlencoded' + + self.prepare_content_length(body) + + # Add content-type if it wasn't explicitly provided. + if content_type and ('content-type' not in self.headers): + self.headers['Content-Type'] = content_type + + self.body = body + + def prepare_content_length(self, body): + """Prepare Content-Length header based on request method and body""" + if body is not None: + length = super_len(body) + if length: + # If length exists, set it. Otherwise, we fallback + # to Transfer-Encoding: chunked. + self.headers['Content-Length'] = builtin_str(length) + elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: + # Set Content-Length to 0 for methods that can have a body + # but don't provide one. (i.e. not GET or HEAD) + self.headers['Content-Length'] = '0' + + def prepare_auth(self, auth, url=''): + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(self.url) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: + # special-case basic HTTP auth + auth = HTTPBasicAuth(*auth) + + # Allow auth to make its changes. + r = auth(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies(self, cookies): + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest ` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand. + """ + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers['Cookie'] = cookie_header + + def prepare_hooks(self, hooks): + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or [] + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response(object): + """The :class:`Response ` object, which contains a + server's response to an HTTP request. + """ + + __attrs__ = [ + '_content', 'status_code', 'headers', 'url', 'history', + 'encoding', 'reason', 'cookies', 'elapsed', 'request' + ] + + def __init__(self): + self._content = False + self._content_consumed = False + self._next = None + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + #: This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response ` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest ` object to which this + #: is a response. + self.request = None + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, '_content_consumed', True) + setattr(self, 'raw', None) + + def __repr__(self): + return '' % (self.status_code) + + def __bool__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __nonzero__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __iter__(self): + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self): + """Returns True if :attr:`status_code` is less than 400, False if not. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self): + """True if this Response is a well-formed HTTP redirect that could have + been processed automatically (by :meth:`Session.resolve_redirects`). + """ + return ('location' in self.headers and self.status_code in REDIRECT_STATI) + + @property + def is_permanent_redirect(self): + """True if this Response one of the permanent versions of redirect.""" + return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect)) + + @property + def next(self): + """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" + return self._next + + @property + def apparent_encoding(self): + """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" + return chardet.detect(self.content)['encoding'] + + def iter_content(self, chunk_size=1, decode_unicode=False): + """Iterates over the response data. When stream=True is set on the + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ + + def generate(): + # Special case for urllib3. + if hasattr(self.raw, 'stream'): + try: + for chunk in self.raw.stream(chunk_size, decode_content=True): + yield chunk + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + if self._content_consumed and isinstance(self._content, bool): + raise StreamConsumedError() + elif chunk_size is not None and not isinstance(chunk_size, int): + raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size)) + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None): + """Iterates over the response data, one line at a time. When + stream=True is set on the request, this avoids reading the + content at once into memory for large responses. + + .. note:: This method is not reentrant safe. + """ + + pending = None + + for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): + + if pending is not None: + chunk = pending + chunk + + if delimiter: + lines = chunk.split(delimiter) + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + for line in lines: + yield line + + if pending is not None: + yield pending + + @property + def content(self): + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + if self._content_consumed: + raise RuntimeError( + 'The content for this response was already consumed') + + if self.status_code == 0 or self.raw is None: + self._content = None + else: + self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b'' + + self._content_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content + + @property + def text(self): + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``charset_normalizer`` or ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return str('') + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + content = str(self.content, encoding, errors='replace') + except (LookupError, TypeError): + # A LookupError is raised if the encoding was not found which could + # indicate a misspelling or similar mistake. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors='replace') + + return content + + def json(self, **kwargs): + r"""Returns the json-encoded content of a response, if any. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + :raises requests.exceptions.JSONDecodeError: If the response body does not + contain valid json. + """ + + if not self.encoding and self.content and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using charset_normalizer to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + return complexjson.loads( + self.content.decode(encoding), **kwargs + ) + except UnicodeDecodeError: + # Wrong UTF codec detected; usually because it's not UTF-8 + # but some other 8-bit codec. This is an RFC violation, + # and the server didn't bother to tell us what codec *was* + # used. + pass + + try: + return complexjson.loads(self.text, **kwargs) + except JSONDecodeError as e: + # Catch JSON-related errors and raise as requests.JSONDecodeError + # This aliases json.JSONDecodeError and simplejson.JSONDecodeError + if is_py2: # e is a ValueError + raise RequestsJSONDecodeError(e.message) + else: + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + @property + def links(self): + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get('link') + + # l = MultiDict() + l = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get('rel') or link.get('url') + l[key] = link + + return l + + def raise_for_status(self): + """Raises :class:`HTTPError`, if one occurred.""" + + http_error_msg = '' + if isinstance(self.reason, bytes): + # We attempt to decode utf-8 first because some servers + # choose to localize their reason strings. If the string + # isn't utf-8, we fall back to iso-8859-1 for all other + # encodings. (See PR #3538) + try: + reason = self.reason.decode('utf-8') + except UnicodeDecodeError: + reason = self.reason.decode('iso-8859-1') + else: + reason = self.reason + + if 400 <= self.status_code < 500: + http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url) + + elif 500 <= self.status_code < 600: + http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self): + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + self.raw.close() + + release_conn = getattr(self.raw, 'release_conn', None) + if release_conn is not None: + release_conn() diff --git a/requests/packages.py b/requests/packages.py new file mode 100644 index 0000000..00196bf --- /dev/null +++ b/requests/packages.py @@ -0,0 +1,26 @@ +import sys + +try: + import chardet +except ImportError: + import charset_normalizer as chardet + import warnings + + warnings.filterwarnings('ignore', 'Trying to detect', module='charset_normalizer') + +# This code exists for backwards compatibility reasons. +# I don't like it either. Just look the other way. :) + +for package in ('urllib3', 'idna'): + locals()[package] = __import__(package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == package or mod.startswith(package + '.'): + sys.modules['requests.packages.' + mod] = sys.modules[mod] + +target = chardet.__name__ +for mod in list(sys.modules): + if mod == target or mod.startswith(target + '.'): + sys.modules['requests.packages.' + target.replace(target, 'chardet')] = sys.modules[mod] +# Kinda cool, though, right? diff --git a/requests/sessions.py b/requests/sessions.py new file mode 100644 index 0000000..3f59cab --- /dev/null +++ b/requests/sessions.py @@ -0,0 +1,771 @@ +# -*- coding: utf-8 -*- + +""" +requests.sessions +~~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). +""" +import os +import sys +import time +from datetime import timedelta +from collections import OrderedDict + +from .auth import _basic_auth_str +from .compat import cookielib, is_py3, urljoin, urlparse, Mapping +from .cookies import ( + cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) +from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT +from .hooks import default_hooks, dispatch_hook +from ._internal_utils import to_native_string +from .utils import to_key_val_list, default_headers, DEFAULT_PORTS +from .exceptions import ( + TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError) + +from .structures import CaseInsensitiveDict +from .adapters import HTTPAdapter + +from .utils import ( + requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies, + get_auth_from_url, rewind_body, resolve_proxies +) + +from .status_codes import codes + +# formerly defined here, reexposed here for backward compatibility +from .models import REDIRECT_STATI + +# Preferred clock, based on which one is more accurate on a given system. +if sys.platform == 'win32': + try: # Python 3.4+ + preferred_clock = time.perf_counter + except AttributeError: # Earlier than Python 3. + preferred_clock = time.clock +else: + preferred_clock = time.time + + +def merge_setting(request_setting, session_setting, dict_class=OrderedDict): + """Determines appropriate setting for a given request, taking into account + the explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and + isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) + merged_setting.update(to_key_val_list(request_setting)) + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + """Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get('response') == []: + return request_hooks + + if request_hooks is None or request_hooks.get('response') == []: + return session_hooks + + return merge_setting(request_hooks, session_hooks, dict_class) + + +class SessionRedirectMixin(object): + + def get_redirect_target(self, resp): + """Receives a Response. Returns a redirect URI or ``None``""" + # Due to the nature of how requests processes redirects this method will + # be called at least once upon the original response and at least twice + # on each subsequent redirect response (if any). + # If a custom mixin is used to handle this logic, it may be advantageous + # to cache the redirect location onto the response object as a private + # attribute. + if resp.is_redirect: + location = resp.headers['location'] + # Currently the underlying http module on py3 decode headers + # in latin1, but empirical evidence suggests that latin1 is very + # rarely used with non-ASCII characters in HTTP headers. + # It is more likely to get UTF8 header rather than latin1. + # This causes incorrect handling of UTF8 encoded location headers. + # To solve this, we re-encode the location in latin1. + if is_py3: + location = location.encode('latin1') + return to_native_string(location, 'utf8') + return None + + def should_strip_auth(self, old_url, new_url): + """Decide whether Authorization header should be removed when redirecting""" + old_parsed = urlparse(old_url) + new_parsed = urlparse(new_url) + if old_parsed.hostname != new_parsed.hostname: + return True + # Special case: allow http -> https redirect when using the standard + # ports. This isn't specified by RFC 7235, but is kept to avoid + # breaking backwards compatibility with older versions of requests + # that allowed any redirects on the same host. + if (old_parsed.scheme == 'http' and old_parsed.port in (80, None) + and new_parsed.scheme == 'https' and new_parsed.port in (443, None)): + return False + + # Handle default port usage corresponding to scheme. + changed_port = old_parsed.port != new_parsed.port + changed_scheme = old_parsed.scheme != new_parsed.scheme + default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) + if (not changed_scheme and old_parsed.port in default_port + and new_parsed.port in default_port): + return False + + # Standard case: root URI must match + return changed_port or changed_scheme + + def resolve_redirects(self, resp, req, stream=False, timeout=None, + verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs): + """Receives a Response. Returns a generator of Responses or Requests.""" + + hist = [] # keep track of history + + url = self.get_redirect_target(resp) + previous_fragment = urlparse(req.url).fragment + while url: + prepared_request = req.copy() + + # Update history and keep track of redirects. + # resp.history must ignore the original request in this loop + hist.append(resp) + resp.history = hist[1:] + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if len(resp.history) >= self.max_redirects: + raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp) + + # Release the connection back into the pool. + resp.close() + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith('//'): + parsed_rurl = urlparse(resp.url) + url = ':'.join([to_native_string(parsed_rurl.scheme), url]) + + # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) + parsed = urlparse(url) + if parsed.fragment == '' and previous_fragment: + parsed = parsed._replace(fragment=previous_fragment) + elif parsed.fragment: + previous_fragment = parsed.fragment + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + + self.rebuild_method(prepared_request, resp) + + # https://github.com/psf/requests/issues/1084 + if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): + # https://github.com/psf/requests/issues/3490 + purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding') + for header in purged_headers: + prepared_request.headers.pop(header, None) + prepared_request.body = None + + headers = prepared_request.headers + headers.pop('Cookie', None) + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) + merge_cookies(prepared_request._cookies, self.cookies) + prepared_request.prepare_cookies(prepared_request._cookies) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # A failed tell() sets `_body_position` to `object()`. This non-None + # value ensures `rewindable` will be True, allowing us to raise an + # UnrewindableBodyError, instead of hanging the connection. + rewindable = ( + prepared_request._body_position is not None and + ('Content-Length' in headers or 'Transfer-Encoding' in headers) + ) + + # Attempt to rewind consumed file-like object. + if rewindable: + rewind_body(prepared_request) + + # Override the original request. + req = prepared_request + + if yield_requests: + yield req + else: + + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + # extract redirect url, if any, for the next loop + url = self.get_redirect_target(resp) + yield resp + + def rebuild_auth(self, prepared_request, response): + """When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.url + + if 'Authorization' in headers and self.should_strip_auth(response.request.url, url): + # If we get redirected to a new host, we should strip out any + # authentication headers. + del headers['Authorization'] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + + def rebuild_proxies(self, prepared_request, proxies): + """This method re-evaluates the proxy configuration by considering the + environment variables. If we are redirected to a URL covered by + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + + :rtype: dict + """ + headers = prepared_request.headers + scheme = urlparse(prepared_request.url).scheme + new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env) + + if 'Proxy-Authorization' in headers: + del headers['Proxy-Authorization'] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + if username and password: + headers['Proxy-Authorization'] = _basic_auth_str(username, password) + + return new_proxies + + def rebuild_method(self, prepared_request, response): + """When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + method = prepared_request.method + + # https://tools.ietf.org/html/rfc7231#section-6.4.4 + if response.status_code == codes.see_other and method != 'HEAD': + method = 'GET' + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + if response.status_code == codes.found and method != 'HEAD': + method = 'GET' + + # Second, if a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in Issue 1704. + if response.status_code == codes.moved and method == 'POST': + method = 'GET' + + prepared_request.method = method + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('https://httpbin.org/get') + + + Or as a context manager:: + + >>> with requests.Session() as s: + ... s.get('https://httpbin.org/get') + + """ + + __attrs__ = [ + 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', + 'cert', 'adapters', 'stream', 'trust_env', + 'max_redirects', + ] + + def __init__(self): + + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request ` sent from this + #: :class:`Session `. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request `. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request `. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request `. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + #: Defaults to `True`, requiring requests to verify the TLS certificate at the + #: remote end. + #: If verify is set to `False`, requests will accept any TLS certificate + #: presented by the server, and will ignore hostname mismatches and/or + #: expired certificates, which will make your application vulnerable to + #: man-in-the-middle (MitM) attacks. + #: Only set this to `False` for testing. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar `, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount('https://', HTTPAdapter()) + self.mount('http://', HTTPAdapter()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def prepare_request(self, request): + """Constructs a :class:`PreparedRequest ` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request ` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + :rtype: requests.PreparedRequest + """ + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies) + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(request.url) + + p = PreparedRequest() + p.prepare( + method=request.method.upper(), + url=request.url, + files=request.files, + data=request.data, + json=request.json, + headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict), + params=merge_setting(request.params, self.params), + auth=merge_setting(auth, self.auth), + cookies=merged_cookies, + hooks=merge_hooks(request.hooks, self.hooks), + ) + return p + + def request(self, method, url, + params=None, data=None, headers=None, cookies=None, files=None, + auth=None, timeout=None, allow_redirects=True, proxies=None, + hooks=None, stream=None, verify=None, cert=None, json=None): + """Constructs a :class:`Request `, prepares it and sends it. + Returns :class:`Response ` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ + # Create the Request. + req = Request( + method=method.upper(), + url=url, + headers=headers, + files=files, + data=data or {}, + json=json, + params=params or {}, + auth=auth, + cookies=cookies, + hooks=hooks, + ) + prep = self.prepare_request(req) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + 'timeout': timeout, + 'allow_redirects': allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get(self, url, **kwargs): + r"""Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return self.request('GET', url, **kwargs) + + def options(self, url, **kwargs): + r"""Sends a OPTIONS request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return self.request('OPTIONS', url, **kwargs) + + def head(self, url, **kwargs): + r"""Sends a HEAD request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', False) + return self.request('HEAD', url, **kwargs) + + def post(self, url, data=None, json=None, **kwargs): + r"""Sends a POST request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('POST', url, data=data, json=json, **kwargs) + + def put(self, url, data=None, **kwargs): + r"""Sends a PUT request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('PUT', url, data=data, **kwargs) + + def patch(self, url, data=None, **kwargs): + r"""Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('PATCH', url, data=data, **kwargs) + + def delete(self, url, **kwargs): + r"""Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('DELETE', url, **kwargs) + + def send(self, request, **kwargs): + """Send a given PreparedRequest. + + :rtype: requests.Response + """ + # Set defaults that the hooks can utilize to ensure they always have + # the correct parameters to reproduce the previous request. + kwargs.setdefault('stream', self.stream) + kwargs.setdefault('verify', self.verify) + kwargs.setdefault('cert', self.cert) + if 'proxies' not in kwargs: + kwargs['proxies'] = resolve_proxies( + request, self.proxies, self.trust_env + ) + + # It's possible that users might accidentally send a Request object. + # Guard against that specific failure case. + if isinstance(request, Request): + raise ValueError('You can only send PreparedRequests.') + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop('allow_redirects', True) + stream = kwargs.get('stream') + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = preferred_clock() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + elapsed = preferred_clock() - start + r.elapsed = timedelta(seconds=elapsed) + + # Response manipulation hooks + r = dispatch_hook('response', hooks, r, **kwargs) + + # Persist cookies + if r.history: + + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Resolve redirects if allowed. + if allow_redirects: + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + history = [resp for resp in gen] + else: + history = [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + # If redirects aren't being followed, store the response on the Request for Response.next(). + if not allow_redirects: + try: + r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs)) + except StopIteration: + pass + + if not stream: + r.content + + return r + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """ + Check the environment and merge it with some settings. + + :rtype: dict + """ + # Gather clues from the surrounding environment. + if self.trust_env: + # Set environment's proxies. + no_proxy = proxies.get('no_proxy') if proxies is not None else None + env_proxies = get_environ_proxies(url, no_proxy=no_proxy) + for (k, v) in env_proxies.items(): + proxies.setdefault(k, v) + + # Look for requests environment configuration and be compatible + # with cURL. + if verify is True or verify is None: + verify = (os.environ.get('REQUESTS_CA_BUNDLE') or + os.environ.get('CURL_CA_BUNDLE')) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {'verify': verify, 'proxies': proxies, 'stream': stream, + 'cert': cert} + + def get_adapter(self, url): + """ + Returns the appropriate connection adapter for the given URL. + + :rtype: requests.adapters.BaseAdapter + """ + for (prefix, adapter) in self.adapters.items(): + + if url.lower().startswith(prefix.lower()): + return adapter + + # Nothing matches :-/ + raise InvalidSchema("No connection adapters were found for {!r}".format(url)) + + def close(self): + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix, adapter): + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by prefix length. + """ + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self): + state = {attr: getattr(self, attr, None) for attr in self.__attrs__} + return state + + def __setstate__(self, state): + for attr, value in state.items(): + setattr(self, attr, value) + + +def session(): + """ + Returns a :class:`Session` for context-management. + + .. deprecated:: 1.0.0 + + This method has been deprecated since version 1.0.0 and is only kept for + backwards compatibility. New code should use :class:`~requests.sessions.Session` + to create a session. This may be removed at a future date. + + :rtype: Session + """ + return Session() diff --git a/requests/status_codes.py b/requests/status_codes.py new file mode 100644 index 0000000..d80a7cd --- /dev/null +++ b/requests/status_codes.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- + +r""" +The ``codes`` object defines a mapping from common names for HTTP statuses +to their numerical codes, accessible either as attributes or as dictionary +items. + +Example:: + + >>> import requests + >>> requests.codes['temporary_redirect'] + 307 + >>> requests.codes.teapot + 418 + >>> requests.codes['\o/'] + 200 + +Some codes have multiple names, and both upper- and lower-case versions of +the names are allowed. For example, ``codes.ok``, ``codes.OK``, and +``codes.okay`` all correspond to the HTTP status code 200. +""" + +from .structures import LookupDict + +_codes = { + + # Informational. + 100: ('continue',), + 101: ('switching_protocols',), + 102: ('processing',), + 103: ('checkpoint',), + 122: ('uri_too_long', 'request_uri_too_long'), + 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'), + 201: ('created',), + 202: ('accepted',), + 203: ('non_authoritative_info', 'non_authoritative_information'), + 204: ('no_content',), + 205: ('reset_content', 'reset'), + 206: ('partial_content', 'partial'), + 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), + 208: ('already_reported',), + 226: ('im_used',), + + # Redirection. + 300: ('multiple_choices',), + 301: ('moved_permanently', 'moved', '\\o-'), + 302: ('found',), + 303: ('see_other', 'other'), + 304: ('not_modified',), + 305: ('use_proxy',), + 306: ('switch_proxy',), + 307: ('temporary_redirect', 'temporary_moved', 'temporary'), + 308: ('permanent_redirect', + 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 + + # Client Error. + 400: ('bad_request', 'bad'), + 401: ('unauthorized',), + 402: ('payment_required', 'payment'), + 403: ('forbidden',), + 404: ('not_found', '-o-'), + 405: ('method_not_allowed', 'not_allowed'), + 406: ('not_acceptable',), + 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), + 408: ('request_timeout', 'timeout'), + 409: ('conflict',), + 410: ('gone',), + 411: ('length_required',), + 412: ('precondition_failed', 'precondition'), + 413: ('request_entity_too_large',), + 414: ('request_uri_too_large',), + 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), + 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), + 417: ('expectation_failed',), + 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), + 421: ('misdirected_request',), + 422: ('unprocessable_entity', 'unprocessable'), + 423: ('locked',), + 424: ('failed_dependency', 'dependency'), + 425: ('unordered_collection', 'unordered'), + 426: ('upgrade_required', 'upgrade'), + 428: ('precondition_required', 'precondition'), + 429: ('too_many_requests', 'too_many'), + 431: ('header_fields_too_large', 'fields_too_large'), + 444: ('no_response', 'none'), + 449: ('retry_with', 'retry'), + 450: ('blocked_by_windows_parental_controls', 'parental_controls'), + 451: ('unavailable_for_legal_reasons', 'legal_reasons'), + 499: ('client_closed_request',), + + # Server Error. + 500: ('internal_server_error', 'server_error', '/o\\', '✗'), + 501: ('not_implemented',), + 502: ('bad_gateway',), + 503: ('service_unavailable', 'unavailable'), + 504: ('gateway_timeout',), + 505: ('http_version_not_supported', 'http_version'), + 506: ('variant_also_negotiates',), + 507: ('insufficient_storage',), + 509: ('bandwidth_limit_exceeded', 'bandwidth'), + 510: ('not_extended',), + 511: ('network_authentication_required', 'network_auth', 'network_authentication'), +} + +codes = LookupDict(name='status_codes') + +def _init(): + for code, titles in _codes.items(): + for title in titles: + setattr(codes, title, code) + if not title.startswith(('\\', '/')): + setattr(codes, title.upper(), code) + + def doc(code): + names = ', '.join('``%s``' % n for n in _codes[code]) + return '* %d: %s' % (code, names) + + global __doc__ + __doc__ = (__doc__ + '\n' + + '\n'.join(doc(code) for code in sorted(_codes)) + if __doc__ is not None else None) + +_init() diff --git a/requests/structures.py b/requests/structures.py new file mode 100644 index 0000000..8ee0ba7 --- /dev/null +++ b/requests/structures.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. +""" + +from collections import OrderedDict + +from .compat import Mapping, MutableMapping + + +class CaseInsensitiveDict(MutableMapping): + """A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + """ + + def __init__(self, data=None, **kwargs): + self._store = OrderedDict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ( + (lowerkey, keyval[1]) + for (lowerkey, keyval) + in self._store.items() + ) + + def __eq__(self, other): + if isinstance(other, Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return str(dict(self.items())) + + +class LookupDict(dict): + """Dictionary lookup object.""" + + def __init__(self, name=None): + self.name = name + super(LookupDict, self).__init__() + + def __repr__(self): + return '' % (self.name) + + def __getitem__(self, key): + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + def get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/requests/utils.py b/requests/utils.py new file mode 100644 index 0000000..153776c --- /dev/null +++ b/requests/utils.py @@ -0,0 +1,1060 @@ +# -*- coding: utf-8 -*- + +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. +""" + +import codecs +import contextlib +import io +import os +import re +import socket +import struct +import sys +import tempfile +import warnings +import zipfile +from collections import OrderedDict +from urllib3.util import make_headers +from urllib3.util import parse_url + +from .__version__ import __version__ +from . import certs +# to_native_string is unused here, but imported here for backwards compatibility +from ._internal_utils import to_native_string +from .compat import parse_http_list as _parse_list_header +from .compat import ( + quote, urlparse, bytes, str, unquote, getproxies, + proxy_bypass, urlunparse, basestring, integer_types, is_py3, + proxy_bypass_environment, getproxies_environment, Mapping) +from .cookies import cookiejar_from_dict +from .structures import CaseInsensitiveDict +from .exceptions import ( + InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError) + +NETRC_FILES = ('.netrc', '_netrc') + +DEFAULT_CA_BUNDLE_PATH = certs.where() + +DEFAULT_PORTS = {'http': 80, 'https': 443} + +# Ensure that ', ' is used to preserve previous delimiter behavior. +DEFAULT_ACCEPT_ENCODING = ", ".join( + re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"]) +) + + +if sys.platform == 'win32': + # provide a proxy_bypass version on Windows without DNS lookups + + def proxy_bypass_registry(host): + try: + if is_py3: + import winreg + else: + import _winreg as winreg + except ImportError: + return False + + try: + internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER, + r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') + # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it + proxyEnable = int(winreg.QueryValueEx(internetSettings, + 'ProxyEnable')[0]) + # ProxyOverride is almost always a string + proxyOverride = winreg.QueryValueEx(internetSettings, + 'ProxyOverride')[0] + except OSError: + return False + if not proxyEnable or not proxyOverride: + return False + + # make a check value list from the registry entry: replace the + # '' string by the localhost entry and the corresponding + # canonical entry. + proxyOverride = proxyOverride.split(';') + # now check if we match one of the registry values. + for test in proxyOverride: + if test == '': + if '.' not in host: + return True + test = test.replace(".", r"\.") # mask dots + test = test.replace("*", r".*") # change glob sequence + test = test.replace("?", r".") # change glob char + if re.match(test, host, re.I): + return True + return False + + def proxy_bypass(host): # noqa + """Return True, if the host should be bypassed. + + Checks proxy settings gathered from the environment, if specified, + or the registry. + """ + if getproxies_environment(): + return proxy_bypass_environment(host) + else: + return proxy_bypass_registry(host) + + +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, 'items'): + d = d.items() + + return d + + +def super_len(o): + total_length = None + current_position = 0 + + if hasattr(o, '__len__'): + total_length = len(o) + + elif hasattr(o, 'len'): + total_length = o.len + + elif hasattr(o, 'fileno'): + try: + fileno = o.fileno() + except (io.UnsupportedOperation, AttributeError): + # AttributeError is a surprising exception, seeing as how we've just checked + # that `hasattr(o, 'fileno')`. It happens for objects obtained via + # `Tarfile.extractfile()`, per issue 5229. + pass + else: + total_length = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + if 'b' not in o.mode: + warnings.warn(( + "Requests has determined the content-length for this " + "request using the binary size of the file: however, the " + "file has been opened in text mode (i.e. without the 'b' " + "flag in the mode). This may lead to an incorrect " + "content-length. In Requests 3.0, support will be removed " + "for files in text mode."), + FileModeWarning + ) + + if hasattr(o, 'tell'): + try: + current_position = o.tell() + except (OSError, IOError): + # This can happen in some weird situations, such as when the file + # is actually a special file descriptor like stdin. In this + # instance, we don't know what the length is, so set it to zero and + # let requests chunk it instead. + if total_length is not None: + current_position = total_length + else: + if hasattr(o, 'seek') and total_length is None: + # StringIO and BytesIO have seek but no usable fileno + try: + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + except (OSError, IOError): + total_length = 0 + + if total_length is None: + total_length = 0 + + return max(0, total_length - current_position) + + +def get_netrc_auth(url, raise_errors=False): + """Returns the Requests tuple auth for a given url from netrc.""" + + netrc_file = os.environ.get('NETRC') + if netrc_file is not None: + netrc_locations = (netrc_file,) + else: + netrc_locations = ('~/{}'.format(f) for f in NETRC_FILES) + + try: + from netrc import netrc, NetrcParseError + + netrc_path = None + + for f in netrc_locations: + try: + loc = os.path.expanduser(f) + except KeyError: + # os.path.expanduser can fail when $HOME is undefined and + # getpwuid fails. See https://bugs.python.org/issue20164 & + # https://github.com/psf/requests/issues/1846 + return + + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + + # Strip port numbers from netloc. This weird `if...encode`` dance is + # used for Python 3.2, which doesn't support unicode literals. + splitstr = b':' + if isinstance(url, str): + splitstr = splitstr.decode('ascii') + host = ri.netloc.split(splitstr)[0] + + try: + _netrc = netrc(netrc_path).authenticators(host) + if _netrc: + # Return with login / password + login_i = (0 if _netrc[0] else 1) + return (_netrc[login_i], _netrc[2]) + except (NetrcParseError, IOError): + # If there was a parsing error or a permissions issue reading the file, + # we'll just skip netrc auth unless explicitly asked to raise errors. + if raise_errors: + raise + + # App Engine hackiness. + except (ImportError, AttributeError): + pass + + +def guess_filename(obj): + """Tries to guess the filename of the given object.""" + name = getattr(obj, 'name', None) + if (name and isinstance(name, basestring) and name[0] != '<' and + name[-1] != '>'): + return os.path.basename(name) + + +def extract_zipped_paths(path): + """Replace nonexistent paths that look like they refer to a member of a zip + archive with the location of an extracted copy of the target, or else + just return the provided path unchanged. + """ + if os.path.exists(path): + # this is already a valid path, no need to do anything further + return path + + # find the first valid part of the provided path and treat that as a zip archive + # assume the rest of the path is the name of a member in the archive + archive, member = os.path.split(path) + while archive and not os.path.exists(archive): + archive, prefix = os.path.split(archive) + if not prefix: + # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split), + # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users + break + member = '/'.join([prefix, member]) + + if not zipfile.is_zipfile(archive): + return path + + zip_file = zipfile.ZipFile(archive) + if member not in zip_file.namelist(): + return path + + # we have a valid zip archive and a valid member of that archive + tmp = tempfile.gettempdir() + extracted_path = os.path.join(tmp, member.split('/')[-1]) + if not os.path.exists(extracted_path): + # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition + with atomic_open(extracted_path) as file_handler: + file_handler.write(zip_file.read(member)) + return extracted_path + + +@contextlib.contextmanager +def atomic_open(filename): + """Write a file to the disk in an atomic fashion""" + replacer = os.rename if sys.version_info[0] == 2 else os.replace + tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) + try: + with os.fdopen(tmp_descriptor, 'wb') as tmp_handler: + yield tmp_handler + replacer(tmp_name, filename) + except BaseException: + os.remove(tmp_name) + raise + + +def from_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + + :rtype: OrderedDict + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + return OrderedDict(value) + + +def to_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + + :rtype: list + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + if isinstance(value, Mapping): + value = value.items() + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + :rtype: list + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + :rtype: dict + """ + result = {} + for item in _parse_list_header(value): + if '=' not in item: + result[item] = None + continue + name, value = item.split('=', 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + :rtype: str + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != '\\\\': + return value.replace('\\\\', '\\').replace('\\"', '"') + return value + + +def dict_from_cookiejar(cj): + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + :rtype: dict + """ + + cookie_dict = {} + + for cookie in cj: + cookie_dict[cookie.name] = cookie.value + + return cookie_dict + + +def add_dict_to_cookiejar(cj, cookie_dict): + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + :rtype: CookieJar + """ + + return cookiejar_from_dict(cookie_dict, cj) + + +def get_encodings_from_content(content): + """Returns encodings from given content string. + + :param content: bytestring to extract encodings from. + """ + warnings.warn(( + 'In requests 3.0, get_encodings_from_content will be removed. For ' + 'more information, please see the discussion on issue #2266. (This' + ' warning should only appear once.)'), + DeprecationWarning) + + charset_re = re.compile(r']', flags=re.I) + pragma_re = re.compile(r']', flags=re.I) + xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') + + return (charset_re.findall(content) + + pragma_re.findall(content) + + xml_re.findall(content)) + + +def _parse_content_type_header(header): + """Returns content type and parameters from given header + + :param header: string + :return: tuple containing content type and dictionary of + parameters + """ + + tokens = header.split(';') + content_type, params = tokens[0].strip(), tokens[1:] + params_dict = {} + items_to_strip = "\"' " + + for param in params: + param = param.strip() + if param: + key, value = param, True + index_of_equals = param.find("=") + if index_of_equals != -1: + key = param[:index_of_equals].strip(items_to_strip) + value = param[index_of_equals + 1:].strip(items_to_strip) + params_dict[key.lower()] = value + return content_type, params_dict + + +def get_encoding_from_headers(headers): + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + :rtype: str + """ + + content_type = headers.get('content-type') + + if not content_type: + return None + + content_type, params = _parse_content_type_header(content_type) + + if 'charset' in params: + return params['charset'].strip("'\"") + + if 'text' in content_type: + return 'ISO-8859-1' + + if 'application/json' in content_type: + # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset + return 'utf-8' + + +def stream_decode_response_unicode(iterator, r): + """Stream decodes a iterator.""" + + if r.encoding is None: + for item in iterator: + yield item + return + + decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace') + for chunk in iterator: + rv = decoder.decode(chunk) + if rv: + yield rv + rv = decoder.decode(b'', final=True) + if rv: + yield rv + + +def iter_slices(string, slice_length): + """Iterate over slices of a string.""" + pos = 0 + if slice_length is None or slice_length <= 0: + slice_length = len(string) + while pos < len(string): + yield string[pos:pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r): + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + :rtype: str + """ + warnings.warn(( + 'In requests 3.0, get_unicode_from_response will be removed. For ' + 'more information, please see the discussion on issue #2266. (This' + ' warning should only appear once.)'), + DeprecationWarning) + + tried_encodings = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding, errors='replace') + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~") + + +def unquote_unreserved(uri): + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + + :rtype: str + """ + parts = uri.split('%') + for i in range(1, len(parts)): + h = parts[i][0:2] + if len(h) == 2 and h.isalnum(): + try: + c = chr(int(h, 16)) + except ValueError: + raise InvalidURL("Invalid percent-escape sequence: '%s'" % h) + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = '%' + parts[i] + else: + parts[i] = '%' + parts[i] + return ''.join(parts) + + +def requote_uri(uri): + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + + :rtype: str + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip, net): + """This function allows you to check if an IP belongs to a network subnet + + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + + :rtype: bool + """ + ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0] + netaddr, bits = net.split('/') + netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0] + network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask): + """Converts mask from /xx format to xxx.xxx.xxx.xxx + + Example: if mask is 24 function returns 255.255.255.0 + + :rtype: str + """ + bits = 0xffffffff ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack('>I', bits)) + + +def is_ipv4_address(string_ip): + """ + :rtype: bool + """ + try: + socket.inet_aton(string_ip) + except socket.error: + return False + return True + + +def is_valid_cidr(string_network): + """ + Very simple check of the cidr format in no_proxy variable. + + :rtype: bool + """ + if string_network.count('/') == 1: + try: + mask = int(string_network.split('/')[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split('/')[0]) + except socket.error: + return False + else: + return False + return True + + +@contextlib.contextmanager +def set_environ(env_name, value): + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + value_changed = value is not None + if value_changed: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value_changed: + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url, no_proxy): + """ + Returns whether we should bypass proxies or not. + + :rtype: bool + """ + # Prioritize lowercase environment variables over uppercase + # to keep a consistent behaviour with other http projects (curl, wget). + get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper()) + + # First check whether no_proxy is defined. If it is, check that the URL + # we're getting isn't in the no_proxy list. + no_proxy_arg = no_proxy + if no_proxy is None: + no_proxy = get_proxy('no_proxy') + parsed = urlparse(url) + + if parsed.hostname is None: + # URLs don't always have hostnames, e.g. file:/// urls. + return True + + if no_proxy: + # We need to check whether we match here. We need to see if we match + # the end of the hostname, both with and without the port. + no_proxy = ( + host for host in no_proxy.replace(' ', '').split(',') if host + ) + + if is_ipv4_address(parsed.hostname): + for proxy_ip in no_proxy: + if is_valid_cidr(proxy_ip): + if address_in_network(parsed.hostname, proxy_ip): + return True + elif parsed.hostname == proxy_ip: + # If no_proxy ip was defined in plain IP notation instead of cidr notation & + # matches the IP of the index + return True + else: + host_with_port = parsed.hostname + if parsed.port: + host_with_port += ':{}'.format(parsed.port) + + for host in no_proxy: + if parsed.hostname.endswith(host) or host_with_port.endswith(host): + # The URL does match something in no_proxy, so we don't want + # to apply the proxies on this URL. + return True + + with set_environ('no_proxy', no_proxy_arg): + # parsed.hostname can be `None` in cases such as a file URI. + try: + bypass = proxy_bypass(parsed.hostname) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + + +def get_environ_proxies(url, no_proxy=None): + """ + Return a dict of environment proxies. + + :rtype: dict + """ + if should_bypass_proxies(url, no_proxy=no_proxy): + return {} + else: + return getproxies() + + +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + if urlparts.hostname is None: + return proxies.get(urlparts.scheme, proxies.get('all')) + + proxy_keys = [ + urlparts.scheme + '://' + urlparts.hostname, + urlparts.scheme, + 'all://' + urlparts.hostname, + 'all', + ] + proxy = None + for proxy_key in proxy_keys: + if proxy_key in proxies: + proxy = proxies[proxy_key] + break + + return proxy + + +def resolve_proxies(request, proxies, trust_env=True): + """This method takes proxy information from a request and configuration + input to resolve a mapping of target proxies. This will consider settings + such a NO_PROXY to strip proxy configurations. + + :param request: Request or PreparedRequest + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + :param trust_env: Boolean declaring whether to trust environment configs + + :rtype: dict + """ + proxies = proxies if proxies is not None else {} + url = request.url + scheme = urlparse(url).scheme + no_proxy = proxies.get('no_proxy') + new_proxies = proxies.copy() + + if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy): + environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) + + proxy = environ_proxies.get(scheme, environ_proxies.get('all')) + + if proxy: + new_proxies.setdefault(scheme, proxy) + return new_proxies + + +def default_user_agent(name="python-requests"): + """ + Return a string representing the default user agent. + + :rtype: str + """ + return '%s/%s' % (name, __version__) + + +def default_headers(): + """ + :rtype: requests.structures.CaseInsensitiveDict + """ + return CaseInsensitiveDict({ + 'User-Agent': default_user_agent(), + 'Accept-Encoding': DEFAULT_ACCEPT_ENCODING, + 'Accept': '*/*', + 'Connection': 'keep-alive', + }) + + +def parse_header_links(value): + """Return a list of parsed link headers proxies. + + i.e. Link: ; rel=front; type="image/jpeg",; rel=back;type="image/jpeg" + + :rtype: list + """ + + links = [] + + replace_chars = ' \'"' + + value = value.strip(replace_chars) + if not value: + return links + + for val in re.split(', *<', value): + try: + url, params = val.split(';', 1) + except ValueError: + url, params = val, '' + + link = {'url': url.strip('<> \'"')} + + for param in params.split(';'): + try: + key, value = param.split('=') + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data): + """ + :rtype: str + """ + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # determine the encoding. Also detect a BOM, if present. + sample = data[:4] + if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): + return 'utf-32' # BOM included + if sample[:3] == codecs.BOM_UTF8: + return 'utf-8-sig' # BOM included, MS style (discouraged) + if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): + return 'utf-16' # BOM included + nullcount = sample.count(_null) + if nullcount == 0: + return 'utf-8' + if nullcount == 2: + if sample[::2] == _null2: # 1st and 3rd are null + return 'utf-16-be' + if sample[1::2] == _null2: # 2nd and 4th are null + return 'utf-16-le' + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return 'utf-32-be' + if sample[1:] == _null3: + return 'utf-32-le' + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url, new_scheme): + """Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument. + + :rtype: str + """ + parsed = parse_url(url) + scheme, auth, host, port, path, query, fragment = parsed + + # A defect in urlparse determines that there isn't a netloc present in some + # urls. We previously assumed parsing was overly cautious, and swapped the + # netloc and path. Due to a lack of tests on the original defect, this is + # maintained with parse_url for backwards compatibility. + netloc = parsed.netloc + if not netloc: + netloc, path = path, netloc + + if auth: + # parse_url doesn't provide the netloc with auth + # so we'll add it ourselves. + netloc = '@'.join([auth, netloc]) + if scheme is None: + scheme = new_scheme + if path is None: + path = '' + + return urlunparse((scheme, netloc, path, '', query, fragment)) + + +def get_auth_from_url(url): + """Given a url with authentication components, extract them into a tuple of + username,password. + + :rtype: (str,str) + """ + parsed = urlparse(url) + + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): + auth = ('', '') + + return auth + + +# Moved outside of function to avoid recompile every call +_CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$') +_CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$') + + +def check_header_validity(header): + """Verifies that header value is a string which doesn't contain + leading whitespace or return characters. This prevents unintended + header injection. + + :param header: tuple, in the format (name, value). + """ + name, value = header + + if isinstance(value, bytes): + pat = _CLEAN_HEADER_REGEX_BYTE + else: + pat = _CLEAN_HEADER_REGEX_STR + try: + if not pat.match(value): + raise InvalidHeader("Invalid return character or leading space in header: %s" % name) + except TypeError: + raise InvalidHeader("Value for header {%s: %s} must be of type str or " + "bytes, not %s" % (name, value, type(value))) + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part. + + :rtype: str + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit('@', 1)[-1] + + return urlunparse((scheme, netloc, path, params, query, '')) + + +def rewind_body(prepared_request): + """Move file pointer back to its recorded starting position + so it can be read again on redirect. + """ + body_seek = getattr(prepared_request.body, 'seek', None) + if body_seek is not None and isinstance(prepared_request._body_position, integer_types): + try: + body_seek(prepared_request._body_position) + except (IOError, OSError): + raise UnrewindableBodyError("An error occurred when rewinding request " + "body for redirect.") + else: + raise UnrewindableBodyError("Unable to rewind request body for redirect.")