http-2.9.8.tm 112 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604
  1. # http.tcl --
  2. #
  3. # Client-side HTTP for GET, POST, and HEAD commands. These routines can
  4. # be used in untrusted code that uses the Safesock security policy.
  5. # These procedures use a callback interface to avoid using vwait, which
  6. # is not defined in the safe base.
  7. #
  8. # See the file "license.terms" for information on usage and redistribution of
  9. # this file, and for a DISCLAIMER OF ALL WARRANTIES.
  10. package require Tcl 8.6-
  11. # Keep this in sync with pkgIndex.tcl and with the install directories in
  12. # Makefiles
  13. package provide http 2.9.8
  14. namespace eval http {
  15. # Allow resourcing to not clobber existing data
  16. variable http
  17. if {![info exists http]} {
  18. array set http {
  19. -accept */*
  20. -pipeline 1
  21. -postfresh 0
  22. -proxyhost {}
  23. -proxyport {}
  24. -proxyfilter http::ProxyRequired
  25. -repost 0
  26. -urlencoding utf-8
  27. -zip 1
  28. }
  29. # We need a useragent string of this style or various servers will
  30. # refuse to send us compressed content even when we ask for it. This
  31. # follows the de-facto layout of user-agent strings in current browsers.
  32. # Safe interpreters do not have ::tcl_platform(os) or
  33. # ::tcl_platform(osVersion).
  34. if {[interp issafe]} {
  35. set http(-useragent) "Mozilla/5.0\
  36. (Windows; U;\
  37. Windows NT 10.0)\
  38. http/[package provide http] Tcl/[package provide Tcl]"
  39. } else {
  40. set http(-useragent) "Mozilla/5.0\
  41. ([string totitle $::tcl_platform(platform)]; U;\
  42. $::tcl_platform(os) $::tcl_platform(osVersion))\
  43. http/[package provide http] Tcl/[package provide Tcl]"
  44. }
  45. }
  46. proc init {} {
  47. # Set up the map for quoting chars. RFC3986 Section 2.3 say percent
  48. # encode all except: "... percent-encoded octets in the ranges of
  49. # ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), period
  50. # (%2E), underscore (%5F), or tilde (%7E) should not be created by URI
  51. # producers ..."
  52. for {set i 0} {$i <= 256} {incr i} {
  53. set c [format %c $i]
  54. if {![string match {[-._~a-zA-Z0-9]} $c]} {
  55. set map($c) %[format %.2X $i]
  56. }
  57. }
  58. # These are handled specially
  59. set map(\n) %0D%0A
  60. variable formMap [array get map]
  61. # Create a map for HTTP/1.1 open sockets
  62. variable socketMapping
  63. variable socketRdState
  64. variable socketWrState
  65. variable socketRdQueue
  66. variable socketWrQueue
  67. variable socketClosing
  68. variable socketPlayCmd
  69. if {[info exists socketMapping]} {
  70. # Close open sockets on re-init. Do not permit retries.
  71. foreach {url sock} [array get socketMapping] {
  72. unset -nocomplain socketClosing($url)
  73. unset -nocomplain socketPlayCmd($url)
  74. CloseSocket $sock
  75. }
  76. }
  77. # CloseSocket should have unset the socket* arrays, one element at
  78. # a time. Now unset anything that was overlooked.
  79. # Traces on "unset socketRdState(*)" will call CancelReadPipeline and
  80. # cancel any queued responses.
  81. # Traces on "unset socketWrState(*)" will call CancelWritePipeline and
  82. # cancel any queued requests.
  83. array unset socketMapping
  84. array unset socketRdState
  85. array unset socketWrState
  86. array unset socketRdQueue
  87. array unset socketWrQueue
  88. array unset socketClosing
  89. array unset socketPlayCmd
  90. array set socketMapping {}
  91. array set socketRdState {}
  92. array set socketWrState {}
  93. array set socketRdQueue {}
  94. array set socketWrQueue {}
  95. array set socketClosing {}
  96. array set socketPlayCmd {}
  97. }
  98. init
  99. variable urlTypes
  100. if {![info exists urlTypes]} {
  101. set urlTypes(http) [list 80 ::socket]
  102. }
  103. variable encodings [string tolower [encoding names]]
  104. # This can be changed, but iso8859-1 is the RFC standard.
  105. variable defaultCharset
  106. if {![info exists defaultCharset]} {
  107. set defaultCharset "iso8859-1"
  108. }
  109. # Force RFC 3986 strictness in geturl url verification?
  110. variable strict
  111. if {![info exists strict]} {
  112. set strict 1
  113. }
  114. # Let user control default keepalive for compatibility
  115. variable defaultKeepalive
  116. if {![info exists defaultKeepalive]} {
  117. set defaultKeepalive 0
  118. }
  119. namespace export geturl config reset wait formatQuery quoteString
  120. namespace export register unregister registerError
  121. # - Useful, but not exported: data, size, status, code, cleanup, error,
  122. # meta, ncode, mapReply, init. Comments suggest that "init" can be used
  123. # for re-initialisation, although the command is undocumented.
  124. # - Not exported, probably should be upper-case initial letter as part
  125. # of the internals: getTextLine, make-transformation-chunked.
  126. }
  127. # http::Log --
  128. #
  129. # Debugging output -- define this to observe HTTP/1.1 socket usage.
  130. # Should echo any args received.
  131. #
  132. # Arguments:
  133. # msg Message to output
  134. #
  135. if {[info command http::Log] eq {}} {proc http::Log {args} {}}
  136. # http::register --
  137. #
  138. # See documentation for details.
  139. #
  140. # Arguments:
  141. # proto URL protocol prefix, e.g. https
  142. # port Default port for protocol
  143. # command Command to use to create socket
  144. # Results:
  145. # list of port and command that was registered.
  146. proc http::register {proto port command} {
  147. variable urlTypes
  148. set urlTypes([string tolower $proto]) [list $port $command]
  149. }
  150. # http::unregister --
  151. #
  152. # Unregisters URL protocol handler
  153. #
  154. # Arguments:
  155. # proto URL protocol prefix, e.g. https
  156. # Results:
  157. # list of port and command that was unregistered.
  158. proc http::unregister {proto} {
  159. variable urlTypes
  160. set lower [string tolower $proto]
  161. if {![info exists urlTypes($lower)]} {
  162. return -code error "unsupported url type \"$proto\""
  163. }
  164. set old $urlTypes($lower)
  165. unset urlTypes($lower)
  166. return $old
  167. }
  168. # http::config --
  169. #
  170. # See documentation for details.
  171. #
  172. # Arguments:
  173. # args Options parsed by the procedure.
  174. # Results:
  175. # TODO
  176. proc http::config {args} {
  177. variable http
  178. set options [lsort [array names http -*]]
  179. set usage [join $options ", "]
  180. if {[llength $args] == 0} {
  181. set result {}
  182. foreach name $options {
  183. lappend result $name $http($name)
  184. }
  185. return $result
  186. }
  187. set options [string map {- ""} $options]
  188. set pat ^-(?:[join $options |])$
  189. if {[llength $args] == 1} {
  190. set flag [lindex $args 0]
  191. if {![regexp -- $pat $flag]} {
  192. return -code error "Unknown option $flag, must be: $usage"
  193. }
  194. return $http($flag)
  195. } else {
  196. foreach {flag value} $args {
  197. if {![regexp -- $pat $flag]} {
  198. return -code error "Unknown option $flag, must be: $usage"
  199. }
  200. set http($flag) $value
  201. }
  202. }
  203. }
  204. # http::Finish --
  205. #
  206. # Clean up the socket and eval close time callbacks
  207. #
  208. # Arguments:
  209. # token Connection token.
  210. # errormsg (optional) If set, forces status to error.
  211. # skipCB (optional) If set, don't call the -command callback. This
  212. # is useful when geturl wants to throw an exception instead
  213. # of calling the callback. That way, the same error isn't
  214. # reported to two places.
  215. #
  216. # Side Effects:
  217. # May close the socket.
  218. proc http::Finish {token {errormsg ""} {skipCB 0}} {
  219. variable socketMapping
  220. variable socketRdState
  221. variable socketWrState
  222. variable socketRdQueue
  223. variable socketWrQueue
  224. variable socketClosing
  225. variable socketPlayCmd
  226. variable $token
  227. upvar 0 $token state
  228. global errorInfo errorCode
  229. set closeQueue 0
  230. if {$errormsg ne ""} {
  231. set state(error) [list $errormsg $errorInfo $errorCode]
  232. set state(status) "error"
  233. }
  234. if {[info commands ${token}EventCoroutine] ne {}} {
  235. rename ${token}EventCoroutine {}
  236. }
  237. # Is this an upgrade request/response?
  238. set upgradeResponse \
  239. [expr { [info exists state(upgradeRequest)] && $state(upgradeRequest)
  240. && [info exists state(http)] && [ncode $token] eq {101}
  241. && [info exists state(connection)] && "upgrade" in $state(connection)
  242. && [info exists state(upgrade)] && "" ne $state(upgrade)}]
  243. if { ($state(status) eq "timeout")
  244. || ($state(status) eq "error")
  245. || ($state(status) eq "eof")
  246. } {
  247. set closeQueue 1
  248. set connId $state(socketinfo)
  249. set sock $state(sock)
  250. CloseSocket $state(sock) $token
  251. } elseif {$upgradeResponse} {
  252. # Special handling for an upgrade request/response.
  253. # - geturl ensures that this is not a "persistent" socket used for
  254. # multiple HTTP requests, so a call to KeepSocket is not needed.
  255. # - Leave socket open, so a call to CloseSocket is not needed either.
  256. # - Remove fileevent bindings. The caller will set its own bindings.
  257. # - THE CALLER MUST PROCESS THE UPGRADED SOCKET IN THE CALLBACK COMMAND
  258. # PASSED TO http::geturl AS -command callback.
  259. catch {fileevent $state(sock) readable {}}
  260. catch {fileevent $state(sock) writable {}}
  261. } elseif {
  262. ([info exists state(-keepalive)] && !$state(-keepalive))
  263. || ([info exists state(connection)] && ("close" in $state(connection)))
  264. } {
  265. set closeQueue 1
  266. set connId $state(socketinfo)
  267. set sock $state(sock)
  268. CloseSocket $state(sock) $token
  269. } elseif {
  270. ([info exists state(-keepalive)] && $state(-keepalive))
  271. && ([info exists state(connection)] && ("close" ni $state(connection)))
  272. } {
  273. KeepSocket $token
  274. }
  275. if {[info exists state(after)]} {
  276. after cancel $state(after)
  277. unset state(after)
  278. }
  279. if {[info exists state(-command)] && (!$skipCB)
  280. && (![info exists state(done-command-cb)])} {
  281. set state(done-command-cb) yes
  282. if {[catch {eval $state(-command) {$token}} err] && $errormsg eq ""} {
  283. set state(error) [list $err $errorInfo $errorCode]
  284. set state(status) error
  285. }
  286. }
  287. if { $closeQueue
  288. && [info exists socketMapping($connId)]
  289. && ($socketMapping($connId) eq $sock)
  290. } {
  291. http::CloseQueuedQueries $connId $token
  292. }
  293. }
  294. # http::KeepSocket -
  295. #
  296. # Keep a socket in the persistent sockets table and connect it to its next
  297. # queued task if possible. Otherwise leave it idle and ready for its next
  298. # use.
  299. #
  300. # If $socketClosing(*), then ("close" in $state(connection)) and therefore
  301. # this command will not be called by Finish.
  302. #
  303. # Arguments:
  304. # token Connection token.
  305. proc http::KeepSocket {token} {
  306. variable http
  307. variable socketMapping
  308. variable socketRdState
  309. variable socketWrState
  310. variable socketRdQueue
  311. variable socketWrQueue
  312. variable socketClosing
  313. variable socketPlayCmd
  314. variable $token
  315. upvar 0 $token state
  316. set tk [namespace tail $token]
  317. # Keep this socket open for another request ("Keep-Alive").
  318. # React if the server half-closes the socket.
  319. # Discussion is in http::geturl.
  320. catch {fileevent $state(sock) readable [list http::CheckEof $state(sock)]}
  321. # The line below should not be changed in production code.
  322. # It is edited by the test suite.
  323. set TEST_EOF 0
  324. if {$TEST_EOF} {
  325. # ONLY for testing reaction to server eof.
  326. # No server timeouts will be caught.
  327. catch {fileevent $state(sock) readable {}}
  328. }
  329. if { [info exists state(socketinfo)]
  330. && [info exists socketMapping($state(socketinfo))]
  331. } {
  332. set connId $state(socketinfo)
  333. # The value "Rready" is set only here.
  334. set socketRdState($connId) Rready
  335. if { $state(-pipeline)
  336. && [info exists socketRdQueue($connId)]
  337. && [llength $socketRdQueue($connId)]
  338. } {
  339. # The usual case for pipelined responses - if another response is
  340. # queued, arrange to read it.
  341. set token3 [lindex $socketRdQueue($connId) 0]
  342. set socketRdQueue($connId) [lrange $socketRdQueue($connId) 1 end]
  343. variable $token3
  344. upvar 0 $token3 state3
  345. set tk2 [namespace tail $token3]
  346. #Log pipelined, GRANT read access to $token3 in KeepSocket
  347. set socketRdState($connId) $token3
  348. ReceiveResponse $token3
  349. # Other pipelined cases.
  350. # - The test above ensures that, for the pipelined cases in the two
  351. # tests below, the read queue is empty.
  352. # - In those two tests, check whether the next write will be
  353. # nonpipeline.
  354. } elseif {
  355. $state(-pipeline)
  356. && [info exists socketWrState($connId)]
  357. && ($socketWrState($connId) eq "peNding")
  358. && [info exists socketWrQueue($connId)]
  359. && [llength $socketWrQueue($connId)]
  360. && (![set token3 [lindex $socketWrQueue($connId) 0]
  361. set ${token3}(-pipeline)
  362. ]
  363. )
  364. } {
  365. # This case:
  366. # - Now it the time to run the "pending" request.
  367. # - The next token in the write queue is nonpipeline, and
  368. # socketWrState has been marked "pending" (in
  369. # http::NextPipelinedWrite or http::geturl) so a new pipelined
  370. # request cannot jump the queue.
  371. #
  372. # Tests:
  373. # - In this case the read queue (tested above) is empty and this
  374. # "pending" write token is in front of the rest of the write
  375. # queue.
  376. # - The write state is not Wready and therefore appears to be busy,
  377. # but because it is "pending" we know that it is reserved for the
  378. # first item in the write queue, a non-pipelined request that is
  379. # waiting for the read queue to empty. That has now happened: so
  380. # give that request read and write access.
  381. variable $token3
  382. set conn [set ${token3}(tmpConnArgs)]
  383. #Log nonpipeline, GRANT r/w access to $token3 in KeepSocket
  384. set socketRdState($connId) $token3
  385. set socketWrState($connId) $token3
  386. set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end]
  387. # Connect does its own fconfigure.
  388. fileevent $state(sock) writable [list http::Connect $token3 {*}$conn]
  389. #Log ---- $state(sock) << conn to $token3 for HTTP request (c)
  390. } elseif {
  391. $state(-pipeline)
  392. && [info exists socketWrState($connId)]
  393. && ($socketWrState($connId) eq "peNding")
  394. } {
  395. # Should not come here. The second block in the previous "elseif"
  396. # test should be tautologous (but was needed in an earlier
  397. # implementation) and will be removed after testing.
  398. # If we get here, the value "pending" was assigned in error.
  399. # This error would block the queue for ever.
  400. Log ^X$tk <<<<< Error in queueing of requests >>>>> - token $token
  401. } elseif {
  402. $state(-pipeline)
  403. && [info exists socketWrState($connId)]
  404. && ($socketWrState($connId) eq "Wready")
  405. && [info exists socketWrQueue($connId)]
  406. && [llength $socketWrQueue($connId)]
  407. && (![set token3 [lindex $socketWrQueue($connId) 0]
  408. set ${token3}(-pipeline)
  409. ]
  410. )
  411. } {
  412. # This case:
  413. # - The next token in the write queue is nonpipeline, and
  414. # socketWrState is Wready. Get the next event from socketWrQueue.
  415. # Tests:
  416. # - In this case the read state (tested above) is Rready and the
  417. # write state (tested here) is Wready - there is no "pending"
  418. # request.
  419. # Code:
  420. # - The code is the same as the code below for the nonpipelined
  421. # case with a queued request.
  422. variable $token3
  423. set conn [set ${token3}(tmpConnArgs)]
  424. #Log nonpipeline, GRANT r/w access to $token3 in KeepSocket
  425. set socketRdState($connId) $token3
  426. set socketWrState($connId) $token3
  427. set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end]
  428. # Connect does its own fconfigure.
  429. fileevent $state(sock) writable [list http::Connect $token3 {*}$conn]
  430. #Log ---- $state(sock) << conn to $token3 for HTTP request (c)
  431. } elseif {
  432. (!$state(-pipeline))
  433. && [info exists socketWrQueue($connId)]
  434. && [llength $socketWrQueue($connId)]
  435. && ("close" ni $state(connection))
  436. } {
  437. # If not pipelined, (socketRdState eq Rready) tells us that we are
  438. # ready for the next write - there is no need to check
  439. # socketWrState. Write the next request, if one is waiting.
  440. # If the next request is pipelined, it receives premature read
  441. # access to the socket. This is not a problem.
  442. set token3 [lindex $socketWrQueue($connId) 0]
  443. variable $token3
  444. set conn [set ${token3}(tmpConnArgs)]
  445. #Log nonpipeline, GRANT r/w access to $token3 in KeepSocket
  446. set socketRdState($connId) $token3
  447. set socketWrState($connId) $token3
  448. set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end]
  449. # Connect does its own fconfigure.
  450. fileevent $state(sock) writable [list http::Connect $token3 {*}$conn]
  451. #Log ---- $state(sock) << conn to $token3 for HTTP request (d)
  452. } elseif {(!$state(-pipeline))} {
  453. set socketWrState($connId) Wready
  454. # Rready and Wready and idle: nothing to do.
  455. }
  456. } else {
  457. CloseSocket $state(sock) $token
  458. # There is no socketMapping($state(socketinfo)), so it does not matter
  459. # that CloseQueuedQueries is not called.
  460. }
  461. }
  462. # http::CheckEof -
  463. #
  464. # Read from a socket and close it if eof.
  465. # The command is bound to "fileevent readable" on an idle socket, and
  466. # "eof" is the only event that should trigger the binding, occurring when
  467. # the server times out and half-closes the socket.
  468. #
  469. # A read is necessary so that [eof] gives a meaningful result.
  470. # Any bytes sent are junk (or a bug).
  471. proc http::CheckEof {sock} {
  472. set junk [read $sock]
  473. set n [string length $junk]
  474. if {$n} {
  475. Log "WARNING: $n bytes received but no HTTP request sent"
  476. }
  477. if {[catch {eof $sock} res] || $res} {
  478. # The server has half-closed the socket.
  479. # If a new write has started, its transaction will fail and
  480. # will then be error-handled.
  481. CloseSocket $sock
  482. }
  483. }
  484. # http::CloseSocket -
  485. #
  486. # Close a socket and remove it from the persistent sockets table. If
  487. # possible an http token is included here but when we are called from a
  488. # fileevent on remote closure we need to find the correct entry - hence
  489. # the "else" block of the first "if" command.
  490. proc http::CloseSocket {s {token {}}} {
  491. variable socketMapping
  492. variable socketRdState
  493. variable socketWrState
  494. variable socketRdQueue
  495. variable socketWrQueue
  496. variable socketClosing
  497. variable socketPlayCmd
  498. set tk [namespace tail $token]
  499. catch {fileevent $s readable {}}
  500. set connId {}
  501. if {$token ne ""} {
  502. variable $token
  503. upvar 0 $token state
  504. if {[info exists state(socketinfo)]} {
  505. set connId $state(socketinfo)
  506. }
  507. } else {
  508. set map [array get socketMapping]
  509. set ndx [lsearch -exact $map $s]
  510. if {$ndx >= 0} {
  511. incr ndx -1
  512. set connId [lindex $map $ndx]
  513. }
  514. }
  515. if { ($connId ne {})
  516. && [info exists socketMapping($connId)]
  517. && ($socketMapping($connId) eq $s)
  518. } {
  519. Log "Closing connection $connId (sock $socketMapping($connId))"
  520. if {[catch {close $socketMapping($connId)} err]} {
  521. Log "Error closing connection: $err"
  522. }
  523. if {$token eq {}} {
  524. # Cases with a non-empty token are handled by Finish, so the tokens
  525. # are finished in connection order.
  526. http::CloseQueuedQueries $connId
  527. }
  528. } else {
  529. Log "Closing socket $s (no connection info)"
  530. if {[catch {close $s} err]} {
  531. Log "Error closing socket: $err"
  532. }
  533. }
  534. }
  535. # http::CloseQueuedQueries
  536. #
  537. # connId - identifier "domain:port" for the connection
  538. # token - (optional) used only for logging
  539. #
  540. # Called from http::CloseSocket and http::Finish, after a connection is closed,
  541. # to clear the read and write queues if this has not already been done.
  542. proc http::CloseQueuedQueries {connId {token {}}} {
  543. variable socketMapping
  544. variable socketRdState
  545. variable socketWrState
  546. variable socketRdQueue
  547. variable socketWrQueue
  548. variable socketClosing
  549. variable socketPlayCmd
  550. if {![info exists socketMapping($connId)]} {
  551. # Command has already been called.
  552. # Don't come here again - especially recursively.
  553. return
  554. }
  555. # Used only for logging.
  556. if {$token eq {}} {
  557. set tk {}
  558. } else {
  559. set tk [namespace tail $token]
  560. }
  561. if { [info exists socketPlayCmd($connId)]
  562. && ($socketPlayCmd($connId) ne {ReplayIfClose Wready {} {}})
  563. } {
  564. # Before unsetting, there is some unfinished business.
  565. # - If the server sent "Connection: close", we have stored the command
  566. # for retrying any queued requests in socketPlayCmd, so copy that
  567. # value for execution below. socketClosing(*) was also set.
  568. # - Also clear the queues to prevent calls to Finish that would set the
  569. # state for the requests that will be retried to "finished with error
  570. # status".
  571. set unfinished $socketPlayCmd($connId)
  572. set socketRdQueue($connId) {}
  573. set socketWrQueue($connId) {}
  574. } else {
  575. set unfinished {}
  576. }
  577. Unset $connId
  578. if {$unfinished ne {}} {
  579. Log ^R$tk Any unfinished transactions (excluding $token) failed \
  580. - token $token
  581. {*}$unfinished
  582. }
  583. }
  584. # http::Unset
  585. #
  586. # The trace on "unset socketRdState(*)" will call CancelReadPipeline
  587. # and cancel any queued responses.
  588. # The trace on "unset socketWrState(*)" will call CancelWritePipeline
  589. # and cancel any queued requests.
  590. proc http::Unset {connId} {
  591. variable socketMapping
  592. variable socketRdState
  593. variable socketWrState
  594. variable socketRdQueue
  595. variable socketWrQueue
  596. variable socketClosing
  597. variable socketPlayCmd
  598. unset socketMapping($connId)
  599. unset socketRdState($connId)
  600. unset socketWrState($connId)
  601. unset -nocomplain socketRdQueue($connId)
  602. unset -nocomplain socketWrQueue($connId)
  603. unset -nocomplain socketClosing($connId)
  604. unset -nocomplain socketPlayCmd($connId)
  605. }
  606. # http::reset --
  607. #
  608. # See documentation for details.
  609. #
  610. # Arguments:
  611. # token Connection token.
  612. # why Status info.
  613. #
  614. # Side Effects:
  615. # See Finish
  616. proc http::reset {token {why reset}} {
  617. variable $token
  618. upvar 0 $token state
  619. set state(status) $why
  620. catch {fileevent $state(sock) readable {}}
  621. catch {fileevent $state(sock) writable {}}
  622. Finish $token
  623. if {[info exists state(error)]} {
  624. set errorlist $state(error)
  625. unset state
  626. eval ::error $errorlist
  627. }
  628. }
  629. # http::geturl --
  630. #
  631. # Establishes a connection to a remote url via http.
  632. #
  633. # Arguments:
  634. # url The http URL to goget.
  635. # args Option value pairs. Valid options include:
  636. # -blocksize, -validate, -headers, -timeout
  637. # Results:
  638. # Returns a token for this connection. This token is the name of an
  639. # array that the caller should unset to garbage collect the state.
  640. proc http::geturl {url args} {
  641. variable http
  642. variable urlTypes
  643. variable defaultCharset
  644. variable defaultKeepalive
  645. variable strict
  646. # Initialize the state variable, an array. We'll return the name of this
  647. # array as the token for the transaction.
  648. if {![info exists http(uid)]} {
  649. set http(uid) 0
  650. }
  651. set token [namespace current]::[incr http(uid)]
  652. ##Log Starting http::geturl - token $token
  653. variable $token
  654. upvar 0 $token state
  655. set tk [namespace tail $token]
  656. reset $token
  657. Log ^A$tk URL $url - token $token
  658. # Process command options.
  659. array set state {
  660. -binary false
  661. -blocksize 8192
  662. -queryblocksize 8192
  663. -validate 0
  664. -headers {}
  665. -timeout 0
  666. -type application/x-www-form-urlencoded
  667. -queryprogress {}
  668. -protocol 1.1
  669. binary 0
  670. state created
  671. meta {}
  672. method {}
  673. coding {}
  674. currentsize 0
  675. totalsize 0
  676. querylength 0
  677. queryoffset 0
  678. type text/html
  679. body {}
  680. status ""
  681. http ""
  682. connection keep-alive
  683. }
  684. set state(-keepalive) $defaultKeepalive
  685. set state(-strict) $strict
  686. # These flags have their types verified [Bug 811170]
  687. array set type {
  688. -binary boolean
  689. -blocksize integer
  690. -queryblocksize integer
  691. -strict boolean
  692. -timeout integer
  693. -validate boolean
  694. -headers list
  695. }
  696. set state(charset) $defaultCharset
  697. set options {
  698. -binary -blocksize -channel -command -handler -headers -keepalive
  699. -method -myaddr -progress -protocol -query -queryblocksize
  700. -querychannel -queryprogress -strict -timeout -type -validate
  701. }
  702. set usage [join [lsort $options] ", "]
  703. set options [string map {- ""} $options]
  704. set pat ^-(?:[join $options |])$
  705. foreach {flag value} $args {
  706. if {[regexp -- $pat $flag]} {
  707. # Validate numbers
  708. if { [info exists type($flag)]
  709. && (![string is $type($flag) -strict $value])
  710. } {
  711. unset $token
  712. return -code error \
  713. "Bad value for $flag ($value), must be $type($flag)"
  714. }
  715. if {($flag eq "-headers") && ([llength $value] % 2 != 0)} {
  716. unset $token
  717. return -code error \
  718. "Bad value for $flag ($value), number of list elements must be even"
  719. }
  720. set state($flag) $value
  721. } else {
  722. unset $token
  723. return -code error "Unknown option $flag, can be: $usage"
  724. }
  725. }
  726. # Make sure -query and -querychannel aren't both specified
  727. set isQueryChannel [info exists state(-querychannel)]
  728. set isQuery [info exists state(-query)]
  729. if {$isQuery && $isQueryChannel} {
  730. unset $token
  731. return -code error "Can't combine -query and -querychannel options!"
  732. }
  733. # Validate URL, determine the server host and port, and check proxy case
  734. # Recognize user:pass@host URLs also, although we do not do anything with
  735. # that info yet.
  736. # URLs have basically four parts.
  737. # First, before the colon, is the protocol scheme (e.g. http)
  738. # Second, for HTTP-like protocols, is the authority
  739. # The authority is preceded by // and lasts up to (but not including)
  740. # the following / or ? and it identifies up to four parts, of which
  741. # only one, the host, is required (if an authority is present at all).
  742. # All other parts of the authority (user name, password, port number)
  743. # are optional.
  744. # Third is the resource name, which is split into two parts at a ?
  745. # The first part (from the single "/" up to "?") is the path, and the
  746. # second part (from that "?" up to "#") is the query. *HOWEVER*, we do
  747. # not need to separate them; we send the whole lot to the server.
  748. # Both, path and query are allowed to be missing, including their
  749. # delimiting character.
  750. # Fourth is the fragment identifier, which is everything after the first
  751. # "#" in the URL. The fragment identifier MUST NOT be sent to the server
  752. # and indeed, we don't bother to validate it (it could be an error to
  753. # pass it in here, but it's cheap to strip).
  754. #
  755. # An example of a URL that has all the parts:
  756. #
  757. # http://jschmoe:xyzzy@www.bogus.net:8000/foo/bar.tml?q=foo#changes
  758. #
  759. # The "http" is the protocol, the user is "jschmoe", the password is
  760. # "xyzzy", the host is "www.bogus.net", the port is "8000", the path is
  761. # "/foo/bar.tml", the query is "q=foo", and the fragment is "changes".
  762. #
  763. # Note that the RE actually combines the user and password parts, as
  764. # recommended in RFC 3986. Indeed, that RFC states that putting passwords
  765. # in URLs is a Really Bad Idea, something with which I would agree utterly.
  766. #
  767. # From a validation perspective, we need to ensure that the parts of the
  768. # URL that are going to the server are correctly encoded. This is only
  769. # done if $state(-strict) is true (inherited from $::http::strict).
  770. set URLmatcher {(?x) # this is _expanded_ syntax
  771. ^
  772. (?: (\w+) : ) ? # <protocol scheme>
  773. (?: //
  774. (?:
  775. (
  776. [^@/\#?]+ # <userinfo part of authority>
  777. ) @
  778. )?
  779. ( # <host part of authority>
  780. [^/:\#?]+ | # host name or IPv4 address
  781. \[ [^/\#?]+ \] # IPv6 address in square brackets
  782. )
  783. (?: : (\d+) )? # <port part of authority>
  784. )?
  785. ( [/\?] [^\#]*)? # <path> (including query)
  786. (?: \# (.*) )? # <fragment>
  787. $
  788. }
  789. # Phase one: parse
  790. if {![regexp -- $URLmatcher $url -> proto user host port srvurl]} {
  791. unset $token
  792. return -code error "Unsupported URL: $url"
  793. }
  794. # Phase two: validate
  795. set host [string trim $host {[]}]; # strip square brackets from IPv6 address
  796. if {$host eq ""} {
  797. # Caller has to provide a host name; we do not have a "default host"
  798. # that would enable us to handle relative URLs.
  799. unset $token
  800. return -code error "Missing host part: $url"
  801. # Note that we don't check the hostname for validity here; if it's
  802. # invalid, we'll simply fail to resolve it later on.
  803. }
  804. if {$port ne "" && $port > 65535} {
  805. unset $token
  806. return -code error "Invalid port number: $port"
  807. }
  808. # The user identification and resource identification parts of the URL can
  809. # have encoded characters in them; take care!
  810. if {$user ne ""} {
  811. # Check for validity according to RFC 3986, Appendix A
  812. set validityRE {(?xi)
  813. ^
  814. (?: [-\w.~!$&'()*+,;=:] | %[0-9a-f][0-9a-f] )+
  815. $
  816. }
  817. if {$state(-strict) && ![regexp -- $validityRE $user]} {
  818. unset $token
  819. # Provide a better error message in this error case
  820. if {[regexp {(?i)%(?![0-9a-f][0-9a-f]).?.?} $user bad]} {
  821. return -code error \
  822. "Illegal encoding character usage \"$bad\" in URL user"
  823. }
  824. return -code error "Illegal characters in URL user"
  825. }
  826. }
  827. if {$srvurl ne ""} {
  828. # RFC 3986 allows empty paths (not even a /), but servers
  829. # return 400 if the path in the HTTP request doesn't start
  830. # with / , so add it here if needed.
  831. if {[string index $srvurl 0] ne "/"} {
  832. set srvurl /$srvurl
  833. }
  834. # Check for validity according to RFC 3986, Appendix A
  835. set validityRE {(?xi)
  836. ^
  837. # Path part (already must start with / character)
  838. (?: [-\w.~!$&'()*+,;=:@/] | %[0-9a-f][0-9a-f] )*
  839. # Query part (optional, permits ? characters)
  840. (?: \? (?: [-\w.~!$&'()*+,;=:@/?] | %[0-9a-f][0-9a-f] )* )?
  841. $
  842. }
  843. if {$state(-strict) && ![regexp -- $validityRE $srvurl]} {
  844. unset $token
  845. # Provide a better error message in this error case
  846. if {[regexp {(?i)%(?![0-9a-f][0-9a-f])..} $srvurl bad]} {
  847. return -code error \
  848. "Illegal encoding character usage \"$bad\" in URL path"
  849. }
  850. return -code error "Illegal characters in URL path"
  851. }
  852. if {![regexp {^[^?#]+} $srvurl state(path)]} {
  853. set state(path) /
  854. }
  855. } else {
  856. set srvurl /
  857. set state(path) /
  858. }
  859. if {$proto eq ""} {
  860. set proto http
  861. }
  862. set lower [string tolower $proto]
  863. if {![info exists urlTypes($lower)]} {
  864. unset $token
  865. return -code error "Unsupported URL type \"$proto\""
  866. }
  867. set defport [lindex $urlTypes($lower) 0]
  868. set defcmd [lindex $urlTypes($lower) 1]
  869. if {$port eq ""} {
  870. set port $defport
  871. }
  872. if {![catch {$http(-proxyfilter) $host} proxy]} {
  873. set phost [lindex $proxy 0]
  874. set pport [lindex $proxy 1]
  875. }
  876. # OK, now reassemble into a full URL
  877. set url ${proto}://
  878. if {$user ne ""} {
  879. append url $user
  880. append url @
  881. }
  882. append url $host
  883. if {$port != $defport} {
  884. append url : $port
  885. }
  886. append url $srvurl
  887. # Don't append the fragment!
  888. set state(url) $url
  889. set sockopts [list -async]
  890. # If we are using the proxy, we must pass in the full URL that includes
  891. # the server name.
  892. if {[info exists phost] && ($phost ne "")} {
  893. set srvurl $url
  894. set targetAddr [list $phost $pport]
  895. } else {
  896. set targetAddr [list $host $port]
  897. }
  898. # Proxy connections aren't shared among different hosts.
  899. set state(socketinfo) $host:$port
  900. # Save the accept types at this point to prevent a race condition. [Bug
  901. # c11a51c482]
  902. set state(accept-types) $http(-accept)
  903. # Check whether this is an Upgrade request.
  904. set connectionValues [SplitCommaSeparatedFieldValue \
  905. [GetFieldValue $state(-headers) Connection]]
  906. set connectionValues [string tolower $connectionValues]
  907. set upgradeValues [SplitCommaSeparatedFieldValue \
  908. [GetFieldValue $state(-headers) Upgrade]]
  909. set state(upgradeRequest) [expr { "upgrade" in $connectionValues
  910. && [llength $upgradeValues] >= 1}]
  911. if {$isQuery || $isQueryChannel} {
  912. # It's a POST.
  913. # A client wishing to send a non-idempotent request SHOULD wait to send
  914. # that request until it has received the response status for the
  915. # previous request.
  916. if {$http(-postfresh)} {
  917. # Override -keepalive for a POST. Use a new connection, and thus
  918. # avoid the small risk of a race against server timeout.
  919. set state(-keepalive) 0
  920. } else {
  921. # Allow -keepalive but do not -pipeline - wait for the previous
  922. # transaction to finish.
  923. # There is a small risk of a race against server timeout.
  924. set state(-pipeline) 0
  925. }
  926. } elseif {$state(upgradeRequest)} {
  927. # It's an upgrade request. Method must be GET (untested).
  928. # Force -keepalive to 0 so the connection is not made over a persistent
  929. # socket, i.e. one used for multiple HTTP requests.
  930. set state(-keepalive) 0
  931. } else {
  932. # It's a non-upgrade GET or HEAD.
  933. set state(-pipeline) $http(-pipeline)
  934. }
  935. # We cannot handle chunked encodings with -handler, so force HTTP/1.0
  936. # until we can manage this.
  937. if {[info exists state(-handler)]} {
  938. set state(-protocol) 1.0
  939. }
  940. # RFC 7320 A.1 - HTTP/1.0 Keep-Alive is problematic. We do not support it.
  941. if {$state(-protocol) eq "1.0"} {
  942. set state(connection) close
  943. set state(-keepalive) 0
  944. }
  945. # See if we are supposed to use a previously opened channel.
  946. # - In principle, ANY call to http::geturl could use a previously opened
  947. # channel if it is available - the "Connection: keep-alive" header is a
  948. # request to leave the channel open AFTER completion of this call.
  949. # - In fact, we try to use an existing channel only if -keepalive 1 -- this
  950. # means that at most one channel is left open for each value of
  951. # $state(socketinfo). This property simplifies the mapping of open
  952. # channels.
  953. set reusing 0
  954. set alreadyQueued 0
  955. if {$state(-keepalive)} {
  956. variable socketMapping
  957. variable socketRdState
  958. variable socketWrState
  959. variable socketRdQueue
  960. variable socketWrQueue
  961. variable socketClosing
  962. variable socketPlayCmd
  963. if {[info exists socketMapping($state(socketinfo))]} {
  964. # - If the connection is idle, it has a "fileevent readable" binding
  965. # to http::CheckEof, in case the server times out and half-closes
  966. # the socket (http::CheckEof closes the other half).
  967. # - We leave this binding in place until just before the last
  968. # puts+flush in http::Connected (GET/HEAD) or http::Write (POST),
  969. # after which the HTTP response might be generated.
  970. if { [info exists socketClosing($state(socketinfo))]
  971. && $socketClosing($state(socketinfo))
  972. } {
  973. # socketClosing(*) is set because the server has sent a
  974. # "Connection: close" header.
  975. # Do not use the persistent socket again.
  976. # Since we have only one persistent socket per server, and the
  977. # old socket is not yet dead, add the request to the write queue
  978. # of the dying socket, which will be replayed by ReplayIfClose.
  979. # Also add it to socketWrQueue(*) which is used only if an error
  980. # causes a call to Finish.
  981. set reusing 1
  982. set sock $socketMapping($state(socketinfo))
  983. Log "reusing socket $sock for $state(socketinfo) - token $token"
  984. set alreadyQueued 1
  985. lassign $socketPlayCmd($state(socketinfo)) com0 com1 com2 com3
  986. lappend com3 $token
  987. set socketPlayCmd($state(socketinfo)) [list $com0 $com1 $com2 $com3]
  988. lappend socketWrQueue($state(socketinfo)) $token
  989. } elseif {[catch {fconfigure $socketMapping($state(socketinfo))}]} {
  990. # FIXME Is it still possible for this code to be executed? If
  991. # so, this could be another place to call TestForReplay,
  992. # rather than discarding the queued transactions.
  993. Log "WARNING: socket for $state(socketinfo) was closed\
  994. - token $token"
  995. Log "WARNING - if testing, pay special attention to this\
  996. case (GH) which is seldom executed - token $token"
  997. # This will call CancelReadPipeline, CancelWritePipeline, and
  998. # cancel any queued requests, responses.
  999. Unset $state(socketinfo)
  1000. } else {
  1001. # Use the persistent socket.
  1002. # The socket may not be ready to write: an earlier request might
  1003. # still be still writing (in the pipelined case) or
  1004. # writing/reading (in the nonpipeline case). This possibility
  1005. # is handled by socketWrQueue later in this command.
  1006. set reusing 1
  1007. set sock $socketMapping($state(socketinfo))
  1008. Log "reusing socket $sock for $state(socketinfo) - token $token"
  1009. }
  1010. # Do not automatically close the connection socket.
  1011. set state(connection) keep-alive
  1012. }
  1013. }
  1014. if {$reusing} {
  1015. # Define state(tmpState) and state(tmpOpenCmd) for use
  1016. # by http::ReplayIfDead if the persistent connection has died.
  1017. set state(tmpState) [array get state]
  1018. # Pass -myaddr directly to the socket command
  1019. if {[info exists state(-myaddr)]} {
  1020. lappend sockopts -myaddr $state(-myaddr)
  1021. }
  1022. set state(tmpOpenCmd) [list {*}$defcmd {*}$sockopts {*}$targetAddr]
  1023. }
  1024. set state(reusing) $reusing
  1025. # Excluding ReplayIfDead and the decision whether to call it, there are four
  1026. # places outside http::geturl where state(reusing) is used:
  1027. # - Connected - if reusing and not pipelined, start the state(-timeout)
  1028. # timeout (when writing).
  1029. # - DoneRequest - if reusing and pipelined, send the next pipelined write
  1030. # - Event - if reusing and pipelined, start the state(-timeout)
  1031. # timeout (when reading).
  1032. # - Event - if (not reusing) and pipelined, send the next pipelined
  1033. # write
  1034. # See comments above re the start of this timeout in other cases.
  1035. if {(!$state(reusing)) && ($state(-timeout) > 0)} {
  1036. set state(after) [after $state(-timeout) \
  1037. [list http::reset $token timeout]]
  1038. }
  1039. if {![info exists sock]} {
  1040. # Pass -myaddr directly to the socket command
  1041. if {[info exists state(-myaddr)]} {
  1042. lappend sockopts -myaddr $state(-myaddr)
  1043. }
  1044. set pre [clock milliseconds]
  1045. ##Log pre socket opened, - token $token
  1046. ##Log [concat $defcmd $sockopts $targetAddr] - token $token
  1047. if {[catch {eval $defcmd $sockopts $targetAddr} sock errdict]} {
  1048. # Something went wrong while trying to establish the connection.
  1049. # Clean up after events and such, but DON'T call the command
  1050. # callback (if available) because we're going to throw an
  1051. # exception from here instead.
  1052. set state(sock) NONE
  1053. Finish $token $sock 1
  1054. cleanup $token
  1055. dict unset errdict -level
  1056. return -options $errdict $sock
  1057. } else {
  1058. # Initialisation of a new socket.
  1059. ##Log post socket opened, - token $token
  1060. ##Log socket opened, now fconfigure - token $token
  1061. set delay [expr {[clock milliseconds] - $pre}]
  1062. if {$delay > 3000} {
  1063. Log socket delay $delay - token $token
  1064. }
  1065. fconfigure $sock -translation {auto crlf} \
  1066. -buffersize $state(-blocksize)
  1067. ##Log socket opened, DONE fconfigure - token $token
  1068. }
  1069. }
  1070. # Command [socket] is called with -async, but takes 5s to 5.1s to return,
  1071. # with probability of order 1 in 10,000. This may be a bizarre scheduling
  1072. # issue with my (KJN's) system (Fedora Linux).
  1073. # This does not cause a problem (unless the request times out when this
  1074. # command returns).
  1075. set state(sock) $sock
  1076. Log "Using $sock for $state(socketinfo) - token $token" \
  1077. [expr {$state(-keepalive)?"keepalive":""}]
  1078. if { $state(-keepalive)
  1079. && (![info exists socketMapping($state(socketinfo))])
  1080. } {
  1081. # Freshly-opened socket that we would like to become persistent.
  1082. set socketMapping($state(socketinfo)) $sock
  1083. if {![info exists socketRdState($state(socketinfo))]} {
  1084. set socketRdState($state(socketinfo)) {}
  1085. set varName ::http::socketRdState($state(socketinfo))
  1086. trace add variable $varName unset ::http::CancelReadPipeline
  1087. }
  1088. if {![info exists socketWrState($state(socketinfo))]} {
  1089. set socketWrState($state(socketinfo)) {}
  1090. set varName ::http::socketWrState($state(socketinfo))
  1091. trace add variable $varName unset ::http::CancelWritePipeline
  1092. }
  1093. if {$state(-pipeline)} {
  1094. #Log new, init for pipelined, GRANT write access to $token in geturl
  1095. # Also grant premature read access to the socket. This is OK.
  1096. set socketRdState($state(socketinfo)) $token
  1097. set socketWrState($state(socketinfo)) $token
  1098. } else {
  1099. # socketWrState is not used by this non-pipelined transaction.
  1100. # We cannot leave it as "Wready" because the next call to
  1101. # http::geturl with a pipelined transaction would conclude that the
  1102. # socket is available for writing.
  1103. #Log new, init for nonpipeline, GRANT r/w access to $token in geturl
  1104. set socketRdState($state(socketinfo)) $token
  1105. set socketWrState($state(socketinfo)) $token
  1106. }
  1107. set socketRdQueue($state(socketinfo)) {}
  1108. set socketWrQueue($state(socketinfo)) {}
  1109. set socketClosing($state(socketinfo)) 0
  1110. set socketPlayCmd($state(socketinfo)) {ReplayIfClose Wready {} {}}
  1111. }
  1112. if {![info exists phost]} {
  1113. set phost ""
  1114. }
  1115. if {$reusing} {
  1116. # For use by http::ReplayIfDead if the persistent connection has died.
  1117. # Also used by NextPipelinedWrite.
  1118. set state(tmpConnArgs) [list $proto $phost $srvurl]
  1119. }
  1120. # The element socketWrState($connId) has a value which is either the name of
  1121. # the token that is permitted to write to the socket, or "Wready" if no
  1122. # token is permitted to write.
  1123. #
  1124. # The code that sets the value to Wready immediately calls
  1125. # http::NextPipelinedWrite, which examines socketWrQueue($connId) and
  1126. # processes the next request in the queue, if there is one. The value
  1127. # Wready is not found when the interpreter is in the event loop unless the
  1128. # socket is idle.
  1129. #
  1130. # The element socketRdState($connId) has a value which is either the name of
  1131. # the token that is permitted to read from the socket, or "Rready" if no
  1132. # token is permitted to read.
  1133. #
  1134. # The code that sets the value to Rready then examines
  1135. # socketRdQueue($connId) and processes the next request in the queue, if
  1136. # there is one. The value Rready is not found when the interpreter is in
  1137. # the event loop unless the socket is idle.
  1138. if {$alreadyQueued} {
  1139. # A write may or may not be in progress. There is no need to set
  1140. # socketWrState to prevent another call stealing write access - all
  1141. # subsequent calls on this socket will come here because the socket
  1142. # will close after the current read, and its
  1143. # socketClosing($connId) is 1.
  1144. ##Log "HTTP request for token $token is queued"
  1145. } elseif { $reusing
  1146. && $state(-pipeline)
  1147. && ($socketWrState($state(socketinfo)) ne "Wready")
  1148. } {
  1149. ##Log "HTTP request for token $token is queued for pipelined use"
  1150. lappend socketWrQueue($state(socketinfo)) $token
  1151. } elseif { $reusing
  1152. && (!$state(-pipeline))
  1153. && ($socketWrState($state(socketinfo)) ne "Wready")
  1154. } {
  1155. # A write is queued or in progress. Lappend to the write queue.
  1156. ##Log "HTTP request for token $token is queued for nonpipeline use"
  1157. lappend socketWrQueue($state(socketinfo)) $token
  1158. } elseif { $reusing
  1159. && (!$state(-pipeline))
  1160. && ($socketWrState($state(socketinfo)) eq "Wready")
  1161. && ($socketRdState($state(socketinfo)) ne "Rready")
  1162. } {
  1163. # A read is queued or in progress, but not a write. Cannot start the
  1164. # nonpipeline transaction, but must set socketWrState to prevent a
  1165. # pipelined request jumping the queue.
  1166. ##Log "HTTP request for token $token is queued for nonpipeline use"
  1167. #Log re-use nonpipeline, GRANT delayed write access to $token in geturl
  1168. set socketWrState($state(socketinfo)) peNding
  1169. lappend socketWrQueue($state(socketinfo)) $token
  1170. } else {
  1171. if {$reusing && $state(-pipeline)} {
  1172. #Log re-use pipelined, GRANT write access to $token in geturl
  1173. set socketWrState($state(socketinfo)) $token
  1174. } elseif {$reusing} {
  1175. # Cf tests above - both are ready.
  1176. #Log re-use nonpipeline, GRANT r/w access to $token in geturl
  1177. set socketRdState($state(socketinfo)) $token
  1178. set socketWrState($state(socketinfo)) $token
  1179. }
  1180. # All (!$reusing) cases come here, and also some $reusing cases if the
  1181. # connection is ready.
  1182. #Log ---- $state(socketinfo) << conn to $token for HTTP request (a)
  1183. # Connect does its own fconfigure.
  1184. fileevent $sock writable \
  1185. [list http::Connect $token $proto $phost $srvurl]
  1186. }
  1187. # Wait for the connection to complete.
  1188. if {![info exists state(-command)]} {
  1189. # geturl does EVERYTHING asynchronously, so if the user
  1190. # calls it synchronously, we just do a wait here.
  1191. http::wait $token
  1192. if {![info exists state]} {
  1193. # If we timed out then Finish has been called and the users
  1194. # command callback may have cleaned up the token. If so we end up
  1195. # here with nothing left to do.
  1196. return $token
  1197. } elseif {$state(status) eq "error"} {
  1198. # Something went wrong while trying to establish the connection.
  1199. # Clean up after events and such, but DON'T call the command
  1200. # callback (if available) because we're going to throw an
  1201. # exception from here instead.
  1202. set err [lindex $state(error) 0]
  1203. cleanup $token
  1204. return -code error $err
  1205. }
  1206. }
  1207. ##Log Leaving http::geturl - token $token
  1208. return $token
  1209. }
  1210. # http::Connected --
  1211. #
  1212. # Callback used when the connection to the HTTP server is actually
  1213. # established.
  1214. #
  1215. # Arguments:
  1216. # token State token.
  1217. # proto What protocol (http, https, etc.) was used to connect.
  1218. # phost Are we using keep-alive? Non-empty if yes.
  1219. # srvurl Service-local URL that we're requesting
  1220. # Results:
  1221. # None.
  1222. proc http::Connected {token proto phost srvurl} {
  1223. variable http
  1224. variable urlTypes
  1225. variable socketMapping
  1226. variable socketRdState
  1227. variable socketWrState
  1228. variable socketRdQueue
  1229. variable socketWrQueue
  1230. variable socketClosing
  1231. variable socketPlayCmd
  1232. variable $token
  1233. upvar 0 $token state
  1234. set tk [namespace tail $token]
  1235. if {$state(reusing) && (!$state(-pipeline)) && ($state(-timeout) > 0)} {
  1236. set state(after) [after $state(-timeout) \
  1237. [list http::reset $token timeout]]
  1238. }
  1239. # Set back the variables needed here.
  1240. set sock $state(sock)
  1241. set isQueryChannel [info exists state(-querychannel)]
  1242. set isQuery [info exists state(-query)]
  1243. regexp {^(.+):([^:]+)$} $state(socketinfo) {} host port
  1244. set lower [string tolower $proto]
  1245. set defport [lindex $urlTypes($lower) 0]
  1246. # Send data in cr-lf format, but accept any line terminators.
  1247. # Initialisation to {auto *} now done in geturl, KeepSocket and DoneRequest.
  1248. # We are concerned here with the request (write) not the response (read).
  1249. lassign [fconfigure $sock -translation] trRead trWrite
  1250. fconfigure $sock -translation [list $trRead crlf] \
  1251. -buffersize $state(-blocksize)
  1252. # The following is disallowed in safe interpreters, but the socket is
  1253. # already in non-blocking mode in that case.
  1254. catch {fconfigure $sock -blocking off}
  1255. set how GET
  1256. if {$isQuery} {
  1257. set state(querylength) [string length $state(-query)]
  1258. if {$state(querylength) > 0} {
  1259. set how POST
  1260. set contDone 0
  1261. } else {
  1262. # There's no query data.
  1263. unset state(-query)
  1264. set isQuery 0
  1265. }
  1266. } elseif {$state(-validate)} {
  1267. set how HEAD
  1268. } elseif {$isQueryChannel} {
  1269. set how POST
  1270. # The query channel must be blocking for the async Write to
  1271. # work properly.
  1272. fconfigure $state(-querychannel) -blocking 1 -translation binary
  1273. set contDone 0
  1274. }
  1275. if {[info exists state(-method)] && ($state(-method) ne "")} {
  1276. set how $state(-method)
  1277. }
  1278. set accept_types_seen 0
  1279. Log ^B$tk begin sending request - token $token
  1280. if {[catch {
  1281. set state(method) $how
  1282. puts $sock "$how $srvurl HTTP/$state(-protocol)"
  1283. set hostValue [GetFieldValue $state(-headers) Host]
  1284. if {$hostValue ne {}} {
  1285. # Allow Host spoofing. [Bug 928154]
  1286. regexp {^[^:]+} $hostValue state(host)
  1287. puts $sock "Host: $hostValue"
  1288. } elseif {$port == $defport} {
  1289. # Don't add port in this case, to handle broken servers. [Bug
  1290. # #504508]
  1291. set state(host) $host
  1292. puts $sock "Host: $host"
  1293. } else {
  1294. set state(host) $host
  1295. puts $sock "Host: $host:$port"
  1296. }
  1297. puts $sock "User-Agent: $http(-useragent)"
  1298. if {($state(-protocol) > 1.0) && $state(-keepalive)} {
  1299. # Send this header, because a 1.1 server is not compelled to treat
  1300. # this as the default.
  1301. puts $sock "Connection: keep-alive"
  1302. }
  1303. if {($state(-protocol) > 1.0) && !$state(-keepalive)} {
  1304. puts $sock "Connection: close" ;# RFC2616 sec 8.1.2.1
  1305. }
  1306. if {($state(-protocol) < 1.1)} {
  1307. # RFC7230 A.1
  1308. # Some server implementations of HTTP/1.0 have a faulty
  1309. # implementation of RFC 2068 Keep-Alive.
  1310. # Don't leave this to chance.
  1311. # For HTTP/1.0 we have already "set state(connection) close"
  1312. # and "state(-keepalive) 0".
  1313. puts $sock "Connection: close"
  1314. }
  1315. # RFC7230 A.1 - "clients are encouraged not to send the
  1316. # Proxy-Connection header field in any requests"
  1317. set accept_encoding_seen 0
  1318. set content_type_seen 0
  1319. foreach {key value} $state(-headers) {
  1320. set value [string map [list \n "" \r ""] $value]
  1321. set key [string map {" " -} [string trim $key]]
  1322. if {[string equal -nocase $key "host"]} {
  1323. continue
  1324. }
  1325. if {[string equal -nocase $key "accept-encoding"]} {
  1326. set accept_encoding_seen 1
  1327. }
  1328. if {[string equal -nocase $key "accept"]} {
  1329. set accept_types_seen 1
  1330. }
  1331. if {[string equal -nocase $key "content-type"]} {
  1332. set content_type_seen 1
  1333. }
  1334. if {[string equal -nocase $key "content-length"]} {
  1335. set contDone 1
  1336. set state(querylength) $value
  1337. }
  1338. if {[string length $key]} {
  1339. puts $sock "$key: $value"
  1340. }
  1341. }
  1342. # Allow overriding the Accept header on a per-connection basis. Useful
  1343. # for working with REST services. [Bug c11a51c482]
  1344. if {!$accept_types_seen} {
  1345. puts $sock "Accept: $state(accept-types)"
  1346. }
  1347. if { (!$accept_encoding_seen)
  1348. && (![info exists state(-handler)])
  1349. && $http(-zip)
  1350. } {
  1351. puts $sock "Accept-Encoding: gzip,deflate,compress"
  1352. }
  1353. if {$isQueryChannel && ($state(querylength) == 0)} {
  1354. # Try to determine size of data in channel. If we cannot seek, the
  1355. # surrounding catch will trap us
  1356. set start [tell $state(-querychannel)]
  1357. seek $state(-querychannel) 0 end
  1358. set state(querylength) \
  1359. [expr {[tell $state(-querychannel)] - $start}]
  1360. seek $state(-querychannel) $start
  1361. }
  1362. # Flush the request header and set up the fileevent that will either
  1363. # push the POST data or read the response.
  1364. #
  1365. # fileevent note:
  1366. #
  1367. # It is possible to have both the read and write fileevents active at
  1368. # this point. The only scenario it seems to affect is a server that
  1369. # closes the connection without reading the POST data. (e.g., early
  1370. # versions TclHttpd in various error cases). Depending on the
  1371. # platform, the client may or may not be able to get the response from
  1372. # the server because of the error it will get trying to write the post
  1373. # data. Having both fileevents active changes the timing and the
  1374. # behavior, but no two platforms (among Solaris, Linux, and NT) behave
  1375. # the same, and none behave all that well in any case. Servers should
  1376. # always read their POST data if they expect the client to read their
  1377. # response.
  1378. if {$isQuery || $isQueryChannel} {
  1379. # POST method.
  1380. if {!$content_type_seen} {
  1381. puts $sock "Content-Type: $state(-type)"
  1382. }
  1383. if {!$contDone} {
  1384. puts $sock "Content-Length: $state(querylength)"
  1385. }
  1386. puts $sock ""
  1387. flush $sock
  1388. # Flush flushes the error in the https case with a bad handshake:
  1389. # else the socket never becomes writable again, and hangs until
  1390. # timeout (if any).
  1391. lassign [fconfigure $sock -translation] trRead trWrite
  1392. fconfigure $sock -translation [list $trRead binary]
  1393. fileevent $sock writable [list http::Write $token]
  1394. # The http::Write command decides when to make the socket readable,
  1395. # using the same test as the GET/HEAD case below.
  1396. } else {
  1397. # GET or HEAD method.
  1398. if { (![catch {fileevent $sock readable} binding])
  1399. && ($binding eq [list http::CheckEof $sock])
  1400. } {
  1401. # Remove the "fileevent readable" binding of an idle persistent
  1402. # socket to http::CheckEof. We can no longer treat bytes
  1403. # received as junk. The server might still time out and
  1404. # half-close the socket if it has not yet received the first
  1405. # "puts".
  1406. fileevent $sock readable {}
  1407. }
  1408. puts $sock ""
  1409. flush $sock
  1410. Log ^C$tk end sending request - token $token
  1411. # End of writing (GET/HEAD methods). The request has been sent.
  1412. DoneRequest $token
  1413. }
  1414. } err]} {
  1415. # The socket probably was never connected, OR the connection dropped
  1416. # later, OR https handshake error, which may be discovered as late as
  1417. # the "flush" command above...
  1418. Log "WARNING - if testing, pay special attention to this\
  1419. case (GI) which is seldom executed - token $token"
  1420. if {[info exists state(reusing)] && $state(reusing)} {
  1421. # The socket was closed at the server end, and closed at
  1422. # this end by http::CheckEof.
  1423. if {[TestForReplay $token write $err a]} {
  1424. return
  1425. } else {
  1426. Finish $token {failed to re-use socket}
  1427. }
  1428. # else:
  1429. # This is NOT a persistent socket that has been closed since its
  1430. # last use.
  1431. # If any other requests are in flight or pipelined/queued, they will
  1432. # be discarded.
  1433. } elseif {$state(status) eq ""} {
  1434. # ...https handshake errors come here.
  1435. set msg [registerError $sock]
  1436. registerError $sock {}
  1437. if {$msg eq {}} {
  1438. set msg {failed to use socket}
  1439. }
  1440. Finish $token $msg
  1441. } elseif {$state(status) ne "error"} {
  1442. Finish $token $err
  1443. }
  1444. }
  1445. }
  1446. # http::registerError
  1447. #
  1448. # Called (for example when processing TclTLS activity) to register
  1449. # an error for a connection on a specific socket. This helps
  1450. # http::Connected to deliver meaningful error messages, e.g. when a TLS
  1451. # certificate fails verification.
  1452. #
  1453. # Usage: http::registerError socket ?newValue?
  1454. #
  1455. # "set" semantics, except that a "get" (a call without a new value) for a
  1456. # non-existent socket returns {}, not an error.
  1457. proc http::registerError {sock args} {
  1458. variable registeredErrors
  1459. if { ([llength $args] == 0)
  1460. && (![info exists registeredErrors($sock)])
  1461. } {
  1462. return
  1463. } elseif { ([llength $args] == 1)
  1464. && ([lindex $args 0] eq {})
  1465. } {
  1466. unset -nocomplain registeredErrors($sock)
  1467. return
  1468. }
  1469. set registeredErrors($sock) {*}$args
  1470. }
  1471. # http::DoneRequest --
  1472. #
  1473. # Command called when a request has been sent. It will arrange the
  1474. # next request and/or response as appropriate.
  1475. #
  1476. # If this command is called when $socketClosing(*), the request $token
  1477. # that calls it must be pipelined and destined to fail.
  1478. proc http::DoneRequest {token} {
  1479. variable http
  1480. variable socketMapping
  1481. variable socketRdState
  1482. variable socketWrState
  1483. variable socketRdQueue
  1484. variable socketWrQueue
  1485. variable socketClosing
  1486. variable socketPlayCmd
  1487. variable $token
  1488. upvar 0 $token state
  1489. set tk [namespace tail $token]
  1490. set sock $state(sock)
  1491. # If pipelined, connect the next HTTP request to the socket.
  1492. if {$state(reusing) && $state(-pipeline)} {
  1493. # Enable next token (if any) to write.
  1494. # The value "Wready" is set only here, and
  1495. # in http::Event after reading the response-headers of a
  1496. # non-reusing transaction.
  1497. # Previous value is $token. It cannot be pending.
  1498. set socketWrState($state(socketinfo)) Wready
  1499. # Now ready to write the next pipelined request (if any).
  1500. http::NextPipelinedWrite $token
  1501. } else {
  1502. # If pipelined, this is the first transaction on this socket. We wait
  1503. # for the response headers to discover whether the connection is
  1504. # persistent. (If this is not done and the connection is not
  1505. # persistent, we SHOULD retry and then MUST NOT pipeline before knowing
  1506. # that we have a persistent connection
  1507. # (rfc2616 8.1.2.2)).
  1508. }
  1509. # Connect to receive the response, unless the socket is pipelined
  1510. # and another response is being sent.
  1511. # This code block is separate from the code below because there are
  1512. # cases where socketRdState already has the value $token.
  1513. if { $state(-keepalive)
  1514. && $state(-pipeline)
  1515. && [info exists socketRdState($state(socketinfo))]
  1516. && ($socketRdState($state(socketinfo)) eq "Rready")
  1517. } {
  1518. #Log pipelined, GRANT read access to $token in Connected
  1519. set socketRdState($state(socketinfo)) $token
  1520. }
  1521. if { $state(-keepalive)
  1522. && $state(-pipeline)
  1523. && [info exists socketRdState($state(socketinfo))]
  1524. && ($socketRdState($state(socketinfo)) ne $token)
  1525. } {
  1526. # Do not read from the socket until it is ready.
  1527. ##Log "HTTP response for token $token is queued for pipelined use"
  1528. # If $socketClosing(*), then the caller will be a pipelined write and
  1529. # execution will come here.
  1530. # This token has already been recorded as "in flight" for writing.
  1531. # When the socket is closed, the read queue will be cleared in
  1532. # CloseQueuedQueries and so the "lappend" here has no effect.
  1533. lappend socketRdQueue($state(socketinfo)) $token
  1534. } else {
  1535. # In the pipelined case, connection for reading depends on the
  1536. # value of socketRdState.
  1537. # In the nonpipeline case, connection for reading always occurs.
  1538. ReceiveResponse $token
  1539. }
  1540. }
  1541. # http::ReceiveResponse
  1542. #
  1543. # Connects token to its socket for reading.
  1544. proc http::ReceiveResponse {token} {
  1545. variable $token
  1546. upvar 0 $token state
  1547. set tk [namespace tail $token]
  1548. set sock $state(sock)
  1549. #Log ---- $state(socketinfo) >> conn to $token for HTTP response
  1550. lassign [fconfigure $sock -translation] trRead trWrite
  1551. fconfigure $sock -translation [list auto $trWrite] \
  1552. -buffersize $state(-blocksize)
  1553. Log ^D$tk begin receiving response - token $token
  1554. coroutine ${token}EventCoroutine http::Event $sock $token
  1555. if {[info exists state(-handler)] || [info exists state(-progress)]} {
  1556. fileevent $sock readable [list http::EventGateway $sock $token]
  1557. } else {
  1558. fileevent $sock readable ${token}EventCoroutine
  1559. }
  1560. return
  1561. }
  1562. # http::EventGateway
  1563. #
  1564. # Bug [c2dc1da315].
  1565. # - Recursive launch of the coroutine can occur if a -handler or -progress
  1566. # callback is used, and the callback command enters the event loop.
  1567. # - To prevent this, the fileevent "binding" is disabled while the
  1568. # coroutine is in flight.
  1569. # - If a recursive call occurs despite these precautions, it is not
  1570. # trapped and discarded here, because it is better to report it as a
  1571. # bug.
  1572. # - Although this solution is believed to be sufficiently general, it is
  1573. # used only if -handler or -progress is specified. In other cases,
  1574. # the coroutine is called directly.
  1575. proc http::EventGateway {sock token} {
  1576. variable $token
  1577. upvar 0 $token state
  1578. fileevent $sock readable {}
  1579. catch {${token}EventCoroutine} res opts
  1580. if {[info commands ${token}EventCoroutine] ne {}} {
  1581. # The coroutine can be deleted by completion (a non-yield return), by
  1582. # http::Finish (when there is a premature end to the transaction), by
  1583. # http::reset or http::cleanup, or if the caller set option -channel
  1584. # but not option -handler: in the last case reading from the socket is
  1585. # now managed by commands ::http::Copy*, http::ReceiveChunked, and
  1586. # http::make-transformation-chunked.
  1587. #
  1588. # Catch in case the coroutine has closed the socket.
  1589. catch {fileevent $sock readable [list http::EventGateway $sock $token]}
  1590. }
  1591. # If there was an error, re-throw it.
  1592. return -options $opts $res
  1593. }
  1594. # http::NextPipelinedWrite
  1595. #
  1596. # - Connecting a socket to a token for writing is done by this command and by
  1597. # command KeepSocket.
  1598. # - If another request has a pipelined write scheduled for $token's socket,
  1599. # and if the socket is ready to accept it, connect the write and update
  1600. # the queue accordingly.
  1601. # - This command is called from http::DoneRequest and http::Event,
  1602. # IF $state(-pipeline) AND (the current transfer has reached the point at
  1603. # which the socket is ready for the next request to be written).
  1604. # - This command is called when a token has write access and is pipelined and
  1605. # keep-alive, and sets socketWrState to Wready.
  1606. # - The command need not consider the case where socketWrState is set to a token
  1607. # that does not yet have write access. Such a token is waiting for Rready,
  1608. # and the assignment of the connection to the token will be done elsewhere (in
  1609. # http::KeepSocket).
  1610. # - This command cannot be called after socketWrState has been set to a
  1611. # "pending" token value (that is then overwritten by the caller), because that
  1612. # value is set by this command when it is called by an earlier token when it
  1613. # relinquishes its write access, and the pending token is always the next in
  1614. # line to write.
  1615. proc http::NextPipelinedWrite {token} {
  1616. variable http
  1617. variable socketRdState
  1618. variable socketWrState
  1619. variable socketWrQueue
  1620. variable socketClosing
  1621. variable $token
  1622. upvar 0 $token state
  1623. set connId $state(socketinfo)
  1624. if { [info exists socketClosing($connId)]
  1625. && $socketClosing($connId)
  1626. } {
  1627. # socketClosing(*) is set because the server has sent a
  1628. # "Connection: close" header.
  1629. # Behave as if the queues are empty - so do nothing.
  1630. } elseif { $state(-pipeline)
  1631. && [info exists socketWrState($connId)]
  1632. && ($socketWrState($connId) eq "Wready")
  1633. && [info exists socketWrQueue($connId)]
  1634. && [llength $socketWrQueue($connId)]
  1635. && ([set token2 [lindex $socketWrQueue($connId) 0]
  1636. set ${token2}(-pipeline)
  1637. ]
  1638. )
  1639. } {
  1640. # - The usual case for a pipelined connection, ready for a new request.
  1641. #Log pipelined, GRANT write access to $token2 in NextPipelinedWrite
  1642. set conn [set ${token2}(tmpConnArgs)]
  1643. set socketWrState($connId) $token2
  1644. set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end]
  1645. # Connect does its own fconfigure.
  1646. fileevent $state(sock) writable [list http::Connect $token2 {*}$conn]
  1647. #Log ---- $connId << conn to $token2 for HTTP request (b)
  1648. # In the tests below, the next request will be nonpipeline.
  1649. } elseif { $state(-pipeline)
  1650. && [info exists socketWrState($connId)]
  1651. && ($socketWrState($connId) eq "Wready")
  1652. && [info exists socketWrQueue($connId)]
  1653. && [llength $socketWrQueue($connId)]
  1654. && (![ set token3 [lindex $socketWrQueue($connId) 0]
  1655. set ${token3}(-pipeline)
  1656. ]
  1657. )
  1658. && [info exists socketRdState($connId)]
  1659. && ($socketRdState($connId) eq "Rready")
  1660. } {
  1661. # The case in which the next request will be non-pipelined, and the read
  1662. # and write queues is ready: which is the condition for a non-pipelined
  1663. # write.
  1664. variable $token3
  1665. upvar 0 $token3 state3
  1666. set conn [set ${token3}(tmpConnArgs)]
  1667. #Log nonpipeline, GRANT r/w access to $token3 in NextPipelinedWrite
  1668. set socketRdState($connId) $token3
  1669. set socketWrState($connId) $token3
  1670. set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end]
  1671. # Connect does its own fconfigure.
  1672. fileevent $state(sock) writable [list http::Connect $token3 {*}$conn]
  1673. #Log ---- $state(sock) << conn to $token3 for HTTP request (c)
  1674. } elseif { $state(-pipeline)
  1675. && [info exists socketWrState($connId)]
  1676. && ($socketWrState($connId) eq "Wready")
  1677. && [info exists socketWrQueue($connId)]
  1678. && [llength $socketWrQueue($connId)]
  1679. && (![set token2 [lindex $socketWrQueue($connId) 0]
  1680. set ${token2}(-pipeline)
  1681. ]
  1682. )
  1683. } {
  1684. # - The case in which the next request will be non-pipelined, but the
  1685. # read queue is NOT ready.
  1686. # - A read is queued or in progress, but not a write. Cannot start the
  1687. # nonpipeline transaction, but must set socketWrState to prevent a new
  1688. # pipelined request (in http::geturl) jumping the queue.
  1689. # - Because socketWrState($connId) is not set to Wready, the assignment
  1690. # of the connection to $token2 will be done elsewhere - by command
  1691. # http::KeepSocket when $socketRdState($connId) is set to "Rready".
  1692. #Log re-use nonpipeline, GRANT delayed write access to $token in NextP..
  1693. set socketWrState($connId) peNding
  1694. }
  1695. }
  1696. # http::CancelReadPipeline
  1697. #
  1698. # Cancel pipelined responses on a closing "Keep-Alive" socket.
  1699. #
  1700. # - Called by a variable trace on "unset socketRdState($connId)".
  1701. # - The variable relates to a Keep-Alive socket, which has been closed.
  1702. # - Cancels all pipelined responses. The requests have been sent,
  1703. # the responses have not yet been received.
  1704. # - This is a hard cancel that ends each transaction with error status,
  1705. # and closes the connection. Do not use it if you want to replay failed
  1706. # transactions.
  1707. # - N.B. Always delete ::http::socketRdState($connId) before deleting
  1708. # ::http::socketRdQueue($connId), or this command will do nothing.
  1709. #
  1710. # Arguments
  1711. # As for a trace command on a variable.
  1712. proc http::CancelReadPipeline {name1 connId op} {
  1713. variable socketRdQueue
  1714. ##Log CancelReadPipeline $name1 $connId $op
  1715. if {[info exists socketRdQueue($connId)]} {
  1716. set msg {the connection was closed by CancelReadPipeline}
  1717. foreach token $socketRdQueue($connId) {
  1718. set tk [namespace tail $token]
  1719. Log ^X$tk end of response "($msg)" - token $token
  1720. set ${token}(status) eof
  1721. Finish $token ;#$msg
  1722. }
  1723. set socketRdQueue($connId) {}
  1724. }
  1725. }
  1726. # http::CancelWritePipeline
  1727. #
  1728. # Cancel queued events on a closing "Keep-Alive" socket.
  1729. #
  1730. # - Called by a variable trace on "unset socketWrState($connId)".
  1731. # - The variable relates to a Keep-Alive socket, which has been closed.
  1732. # - In pipelined or nonpipeline case: cancels all queued requests. The
  1733. # requests have not yet been sent, the responses are not due.
  1734. # - This is a hard cancel that ends each transaction with error status,
  1735. # and closes the connection. Do not use it if you want to replay failed
  1736. # transactions.
  1737. # - N.B. Always delete ::http::socketWrState($connId) before deleting
  1738. # ::http::socketWrQueue($connId), or this command will do nothing.
  1739. #
  1740. # Arguments
  1741. # As for a trace command on a variable.
  1742. proc http::CancelWritePipeline {name1 connId op} {
  1743. variable socketWrQueue
  1744. ##Log CancelWritePipeline $name1 $connId $op
  1745. if {[info exists socketWrQueue($connId)]} {
  1746. set msg {the connection was closed by CancelWritePipeline}
  1747. foreach token $socketWrQueue($connId) {
  1748. set tk [namespace tail $token]
  1749. Log ^X$tk end of response "($msg)" - token $token
  1750. set ${token}(status) eof
  1751. Finish $token ;#$msg
  1752. }
  1753. set socketWrQueue($connId) {}
  1754. }
  1755. }
  1756. # http::ReplayIfDead --
  1757. #
  1758. # - A query on a re-used persistent socket failed at the earliest opportunity,
  1759. # because the socket had been closed by the server. Keep the token, tidy up,
  1760. # and try to connect on a fresh socket.
  1761. # - The connection is monitored for eof by the command http::CheckEof. Thus
  1762. # http::ReplayIfDead is needed only when a server event (half-closing an
  1763. # apparently idle connection), and a client event (sending a request) occur at
  1764. # almost the same time, and neither client nor server detects the other's
  1765. # action before performing its own (an "asynchronous close event").
  1766. # - To simplify testing of http::ReplayIfDead, set TEST_EOF 1 in
  1767. # http::KeepSocket, and then http::ReplayIfDead will be called if http::geturl
  1768. # is called at any time after the server timeout.
  1769. #
  1770. # Arguments:
  1771. # token Connection token.
  1772. #
  1773. # Side Effects:
  1774. # Use the same token, but try to open a new socket.
  1775. proc http::ReplayIfDead {tokenArg doing} {
  1776. variable socketMapping
  1777. variable socketRdState
  1778. variable socketWrState
  1779. variable socketRdQueue
  1780. variable socketWrQueue
  1781. variable socketClosing
  1782. variable socketPlayCmd
  1783. variable $tokenArg
  1784. upvar 0 $tokenArg stateArg
  1785. Log running http::ReplayIfDead for $tokenArg $doing
  1786. # 1. Merge the tokens for transactions in flight, the read (response) queue,
  1787. # and the write (request) queue.
  1788. set InFlightR {}
  1789. set InFlightW {}
  1790. # Obtain the tokens for transactions in flight.
  1791. if {$stateArg(-pipeline)} {
  1792. # Two transactions may be in flight. The "read" transaction was first.
  1793. # It is unlikely that the server would close the socket if a response
  1794. # was pending; however, an earlier request (as well as the present
  1795. # request) may have been sent and ignored if the socket was half-closed
  1796. # by the server.
  1797. if { [info exists socketRdState($stateArg(socketinfo))]
  1798. && ($socketRdState($stateArg(socketinfo)) ne "Rready")
  1799. } {
  1800. lappend InFlightR $socketRdState($stateArg(socketinfo))
  1801. } elseif {($doing eq "read")} {
  1802. lappend InFlightR $tokenArg
  1803. }
  1804. if { [info exists socketWrState($stateArg(socketinfo))]
  1805. && $socketWrState($stateArg(socketinfo)) ni {Wready peNding}
  1806. } {
  1807. lappend InFlightW $socketWrState($stateArg(socketinfo))
  1808. } elseif {($doing eq "write")} {
  1809. lappend InFlightW $tokenArg
  1810. }
  1811. # Report any inconsistency of $tokenArg with socket*state.
  1812. if { ($doing eq "read")
  1813. && [info exists socketRdState($stateArg(socketinfo))]
  1814. && ($tokenArg ne $socketRdState($stateArg(socketinfo)))
  1815. } {
  1816. Log WARNING - ReplayIfDead pipelined tokenArg $tokenArg $doing \
  1817. ne socketRdState($stateArg(socketinfo)) \
  1818. $socketRdState($stateArg(socketinfo))
  1819. } elseif {
  1820. ($doing eq "write")
  1821. && [info exists socketWrState($stateArg(socketinfo))]
  1822. && ($tokenArg ne $socketWrState($stateArg(socketinfo)))
  1823. } {
  1824. Log WARNING - ReplayIfDead pipelined tokenArg $tokenArg $doing \
  1825. ne socketWrState($stateArg(socketinfo)) \
  1826. $socketWrState($stateArg(socketinfo))
  1827. }
  1828. } else {
  1829. # One transaction should be in flight.
  1830. # socketRdState, socketWrQueue are used.
  1831. # socketRdQueue should be empty.
  1832. # Report any inconsistency of $tokenArg with socket*state.
  1833. if {$tokenArg ne $socketRdState($stateArg(socketinfo))} {
  1834. Log WARNING - ReplayIfDead nonpipeline tokenArg $tokenArg $doing \
  1835. ne socketRdState($stateArg(socketinfo)) \
  1836. $socketRdState($stateArg(socketinfo))
  1837. }
  1838. # Report the inconsistency that socketRdQueue is non-empty.
  1839. if { [info exists socketRdQueue($stateArg(socketinfo))]
  1840. && ($socketRdQueue($stateArg(socketinfo)) ne {})
  1841. } {
  1842. Log WARNING - ReplayIfDead nonpipeline tokenArg $tokenArg $doing \
  1843. has read queue socketRdQueue($stateArg(socketinfo)) \
  1844. $socketRdQueue($stateArg(socketinfo)) ne {}
  1845. }
  1846. lappend InFlightW $socketRdState($stateArg(socketinfo))
  1847. set socketRdQueue($stateArg(socketinfo)) {}
  1848. }
  1849. set newQueue {}
  1850. lappend newQueue {*}$InFlightR
  1851. lappend newQueue {*}$socketRdQueue($stateArg(socketinfo))
  1852. lappend newQueue {*}$InFlightW
  1853. lappend newQueue {*}$socketWrQueue($stateArg(socketinfo))
  1854. # 2. Tidy up tokenArg. This is a cut-down form of Finish/CloseSocket.
  1855. # Do not change state(status).
  1856. # No need to after cancel stateArg(after) - either this is done in
  1857. # ReplayCore/ReInit, or Finish is called.
  1858. catch {close $stateArg(sock)}
  1859. # 2a. Tidy the tokens in the queues - this is done in ReplayCore/ReInit.
  1860. # - Transactions, if any, that are awaiting responses cannot be completed.
  1861. # They are listed for re-sending in newQueue.
  1862. # - All tokens are preserved for re-use by ReplayCore, and their variables
  1863. # will be re-initialised by calls to ReInit.
  1864. # - The relevant element of socketMapping, socketRdState, socketWrState,
  1865. # socketRdQueue, socketWrQueue, socketClosing, socketPlayCmd will be set
  1866. # to new values in ReplayCore.
  1867. ReplayCore $newQueue
  1868. }
  1869. # http::ReplayIfClose --
  1870. #
  1871. # A request on a socket that was previously "Connection: keep-alive" has
  1872. # received a "Connection: close" response header. The server supplies
  1873. # that response correctly, but any later requests already queued on this
  1874. # connection will be lost when the socket closes.
  1875. #
  1876. # This command takes arguments that represent the socketWrState,
  1877. # socketRdQueue and socketWrQueue for this connection. The socketRdState
  1878. # is not needed because the server responds in full to the request that
  1879. # received the "Connection: close" response header.
  1880. #
  1881. # Existing request tokens $token (::http::$n) are preserved. The caller
  1882. # will be unaware that the request was processed this way.
  1883. proc http::ReplayIfClose {Wstate Rqueue Wqueue} {
  1884. Log running http::ReplayIfClose for $Wstate $Rqueue $Wqueue
  1885. if {$Wstate in $Rqueue || $Wstate in $Wqueue} {
  1886. Log WARNING duplicate token in http::ReplayIfClose - token $Wstate
  1887. set Wstate Wready
  1888. }
  1889. # 1. Create newQueue
  1890. set InFlightW {}
  1891. if {$Wstate ni {Wready peNding}} {
  1892. lappend InFlightW $Wstate
  1893. }
  1894. set newQueue {}
  1895. lappend newQueue {*}$Rqueue
  1896. lappend newQueue {*}$InFlightW
  1897. lappend newQueue {*}$Wqueue
  1898. # 2. Cleanup - none needed, done by the caller.
  1899. ReplayCore $newQueue
  1900. }
  1901. # http::ReInit --
  1902. #
  1903. # Command to restore a token's state to a condition that
  1904. # makes it ready to replay a request.
  1905. #
  1906. # Command http::geturl stores extra state in state(tmp*) so
  1907. # we don't need to do the argument processing again.
  1908. #
  1909. # The caller must:
  1910. # - Set state(reusing) and state(sock) to their new values after calling
  1911. # this command.
  1912. # - Unset state(tmpState), state(tmpOpenCmd) if future calls to ReplayCore
  1913. # or ReInit are inappropriate for this token. Typically only one retry
  1914. # is allowed.
  1915. # The caller may also unset state(tmpConnArgs) if this value (and the
  1916. # token) will be used immediately. The value is needed by tokens that
  1917. # will be stored in a queue.
  1918. #
  1919. # Arguments:
  1920. # token Connection token.
  1921. #
  1922. # Return Value: (boolean) true iff the re-initialisation was successful.
  1923. proc http::ReInit {token} {
  1924. variable $token
  1925. upvar 0 $token state
  1926. if {!(
  1927. [info exists state(tmpState)]
  1928. && [info exists state(tmpOpenCmd)]
  1929. && [info exists state(tmpConnArgs)]
  1930. )
  1931. } {
  1932. Log FAILED in http::ReInit via ReplayCore - NO tmp vars for $token
  1933. return 0
  1934. }
  1935. if {[info exists state(after)]} {
  1936. after cancel $state(after)
  1937. unset state(after)
  1938. }
  1939. # Don't alter state(status) - this would trigger http::wait if it is in use.
  1940. set tmpState $state(tmpState)
  1941. set tmpOpenCmd $state(tmpOpenCmd)
  1942. set tmpConnArgs $state(tmpConnArgs)
  1943. foreach name [array names state] {
  1944. if {$name ne "status"} {
  1945. unset state($name)
  1946. }
  1947. }
  1948. # Don't alter state(status).
  1949. # Restore state(tmp*) - the caller may decide to unset them.
  1950. # Restore state(tmpConnArgs) which is needed for connection.
  1951. # state(tmpState), state(tmpOpenCmd) are needed only for retries.
  1952. dict unset tmpState status
  1953. array set state $tmpState
  1954. set state(tmpState) $tmpState
  1955. set state(tmpOpenCmd) $tmpOpenCmd
  1956. set state(tmpConnArgs) $tmpConnArgs
  1957. return 1
  1958. }
  1959. # http::ReplayCore --
  1960. #
  1961. # Command to replay a list of requests, using existing connection tokens.
  1962. #
  1963. # Abstracted from http::geturl which stores extra state in state(tmp*) so
  1964. # we don't need to do the argument processing again.
  1965. #
  1966. # Arguments:
  1967. # newQueue List of connection tokens.
  1968. #
  1969. # Side Effects:
  1970. # Use existing tokens, but try to open a new socket.
  1971. proc http::ReplayCore {newQueue} {
  1972. variable socketMapping
  1973. variable socketRdState
  1974. variable socketWrState
  1975. variable socketRdQueue
  1976. variable socketWrQueue
  1977. variable socketClosing
  1978. variable socketPlayCmd
  1979. if {[llength $newQueue] == 0} {
  1980. # Nothing to do.
  1981. return
  1982. }
  1983. ##Log running ReplayCore for {*}$newQueue
  1984. set newToken [lindex $newQueue 0]
  1985. set newQueue [lrange $newQueue 1 end]
  1986. # 3. Use newToken, and restore its values of state(*). Do not restore
  1987. # elements tmp* - we try again only once.
  1988. set token $newToken
  1989. variable $token
  1990. upvar 0 $token state
  1991. if {![ReInit $token]} {
  1992. Log FAILED in http::ReplayCore - NO tmp vars
  1993. Finish $token {cannot send this request again}
  1994. return
  1995. }
  1996. set tmpState $state(tmpState)
  1997. set tmpOpenCmd $state(tmpOpenCmd)
  1998. set tmpConnArgs $state(tmpConnArgs)
  1999. unset state(tmpState)
  2000. unset state(tmpOpenCmd)
  2001. unset state(tmpConnArgs)
  2002. set state(reusing) 0
  2003. if {$state(-timeout) > 0} {
  2004. set resetCmd [list http::reset $token timeout]
  2005. set state(after) [after $state(-timeout) $resetCmd]
  2006. }
  2007. set pre [clock milliseconds]
  2008. ##Log pre socket opened, - token $token
  2009. ##Log $tmpOpenCmd - token $token
  2010. # 4. Open a socket.
  2011. if {[catch {eval $tmpOpenCmd} sock]} {
  2012. # Something went wrong while trying to establish the connection.
  2013. Log FAILED - $sock
  2014. set state(sock) NONE
  2015. Finish $token $sock
  2016. return
  2017. }
  2018. ##Log post socket opened, - token $token
  2019. set delay [expr {[clock milliseconds] - $pre}]
  2020. if {$delay > 3000} {
  2021. Log socket delay $delay - token $token
  2022. }
  2023. # Command [socket] is called with -async, but takes 5s to 5.1s to return,
  2024. # with probability of order 1 in 10,000. This may be a bizarre scheduling
  2025. # issue with my (KJN's) system (Fedora Linux).
  2026. # This does not cause a problem (unless the request times out when this
  2027. # command returns).
  2028. # 5. Configure the persistent socket data.
  2029. if {$state(-keepalive)} {
  2030. set socketMapping($state(socketinfo)) $sock
  2031. if {![info exists socketRdState($state(socketinfo))]} {
  2032. set socketRdState($state(socketinfo)) {}
  2033. set varName ::http::socketRdState($state(socketinfo))
  2034. trace add variable $varName unset ::http::CancelReadPipeline
  2035. }
  2036. if {![info exists socketWrState($state(socketinfo))]} {
  2037. set socketWrState($state(socketinfo)) {}
  2038. set varName ::http::socketWrState($state(socketinfo))
  2039. trace add variable $varName unset ::http::CancelWritePipeline
  2040. }
  2041. if {$state(-pipeline)} {
  2042. #Log new, init for pipelined, GRANT write acc to $token ReplayCore
  2043. set socketRdState($state(socketinfo)) $token
  2044. set socketWrState($state(socketinfo)) $token
  2045. } else {
  2046. #Log new, init for nonpipeline, GRANT r/w acc to $token ReplayCore
  2047. set socketRdState($state(socketinfo)) $token
  2048. set socketWrState($state(socketinfo)) $token
  2049. }
  2050. set socketRdQueue($state(socketinfo)) {}
  2051. set socketWrQueue($state(socketinfo)) $newQueue
  2052. set socketClosing($state(socketinfo)) 0
  2053. set socketPlayCmd($state(socketinfo)) {ReplayIfClose Wready {} {}}
  2054. }
  2055. ##Log pre newQueue ReInit, - token $token
  2056. # 6. Configure sockets in the queue.
  2057. foreach tok $newQueue {
  2058. if {[ReInit $tok]} {
  2059. set ${tok}(reusing) 1
  2060. set ${tok}(sock) $sock
  2061. } else {
  2062. set ${tok}(reusing) 1
  2063. set ${tok}(sock) NONE
  2064. Finish $token {cannot send this request again}
  2065. }
  2066. }
  2067. # 7. Configure the socket for newToken to send a request.
  2068. set state(sock) $sock
  2069. Log "Using $sock for $state(socketinfo) - token $token" \
  2070. [expr {$state(-keepalive)?"keepalive":""}]
  2071. # Initialisation of a new socket.
  2072. ##Log socket opened, now fconfigure - token $token
  2073. fconfigure $sock -translation {auto crlf} -buffersize $state(-blocksize)
  2074. ##Log socket opened, DONE fconfigure - token $token
  2075. # Connect does its own fconfigure.
  2076. fileevent $sock writable [list http::Connect $token {*}$tmpConnArgs]
  2077. #Log ---- $sock << conn to $token for HTTP request (e)
  2078. }
  2079. # Data access functions:
  2080. # Data - the URL data
  2081. # Status - the transaction status: ok, reset, eof, timeout, error
  2082. # Code - the HTTP transaction code, e.g., 200
  2083. # Size - the size of the URL data
  2084. proc http::data {token} {
  2085. variable $token
  2086. upvar 0 $token state
  2087. return $state(body)
  2088. }
  2089. proc http::status {token} {
  2090. if {![info exists $token]} {
  2091. return "error"
  2092. }
  2093. variable $token
  2094. upvar 0 $token state
  2095. return $state(status)
  2096. }
  2097. proc http::code {token} {
  2098. variable $token
  2099. upvar 0 $token state
  2100. return $state(http)
  2101. }
  2102. proc http::ncode {token} {
  2103. variable $token
  2104. upvar 0 $token state
  2105. if {[regexp {[0-9]{3}} $state(http) numeric_code]} {
  2106. return $numeric_code
  2107. } else {
  2108. return $state(http)
  2109. }
  2110. }
  2111. proc http::size {token} {
  2112. variable $token
  2113. upvar 0 $token state
  2114. return $state(currentsize)
  2115. }
  2116. proc http::meta {token} {
  2117. variable $token
  2118. upvar 0 $token state
  2119. return $state(meta)
  2120. }
  2121. proc http::error {token} {
  2122. variable $token
  2123. upvar 0 $token state
  2124. if {[info exists state(error)]} {
  2125. return $state(error)
  2126. }
  2127. return ""
  2128. }
  2129. # http::cleanup
  2130. #
  2131. # Garbage collect the state associated with a transaction
  2132. #
  2133. # Arguments
  2134. # token The token returned from http::geturl
  2135. #
  2136. # Side Effects
  2137. # Unsets the state array.
  2138. proc http::cleanup {token} {
  2139. variable $token
  2140. upvar 0 $token state
  2141. if {[info commands ${token}EventCoroutine] ne {}} {
  2142. rename ${token}EventCoroutine {}
  2143. }
  2144. if {[info exists state(after)]} {
  2145. after cancel $state(after)
  2146. unset state(after)
  2147. }
  2148. if {[info exists state]} {
  2149. unset state
  2150. }
  2151. }
  2152. # http::Connect
  2153. #
  2154. # This callback is made when an asynchronous connection completes.
  2155. #
  2156. # Arguments
  2157. # token The token returned from http::geturl
  2158. #
  2159. # Side Effects
  2160. # Sets the status of the connection, which unblocks
  2161. # the waiting geturl call
  2162. proc http::Connect {token proto phost srvurl} {
  2163. variable $token
  2164. upvar 0 $token state
  2165. set tk [namespace tail $token]
  2166. set err "due to unexpected EOF"
  2167. if {
  2168. [eof $state(sock)] ||
  2169. [set err [fconfigure $state(sock) -error]] ne ""
  2170. } {
  2171. Log "WARNING - if testing, pay special attention to this\
  2172. case (GJ) which is seldom executed - token $token"
  2173. if {[info exists state(reusing)] && $state(reusing)} {
  2174. # The socket was closed at the server end, and closed at
  2175. # this end by http::CheckEof.
  2176. if {[TestForReplay $token write $err b]} {
  2177. return
  2178. }
  2179. # else:
  2180. # This is NOT a persistent socket that has been closed since its
  2181. # last use.
  2182. # If any other requests are in flight or pipelined/queued, they will
  2183. # be discarded.
  2184. }
  2185. Finish $token "connect failed $err"
  2186. } else {
  2187. set state(state) connecting
  2188. fileevent $state(sock) writable {}
  2189. ::http::Connected $token $proto $phost $srvurl
  2190. }
  2191. }
  2192. # http::Write
  2193. #
  2194. # Write POST query data to the socket
  2195. #
  2196. # Arguments
  2197. # token The token for the connection
  2198. #
  2199. # Side Effects
  2200. # Write the socket and handle callbacks.
  2201. proc http::Write {token} {
  2202. variable http
  2203. variable socketMapping
  2204. variable socketRdState
  2205. variable socketWrState
  2206. variable socketRdQueue
  2207. variable socketWrQueue
  2208. variable socketClosing
  2209. variable socketPlayCmd
  2210. variable $token
  2211. upvar 0 $token state
  2212. set tk [namespace tail $token]
  2213. set sock $state(sock)
  2214. # Output a block. Tcl will buffer this if the socket blocks
  2215. set done 0
  2216. if {[catch {
  2217. # Catch I/O errors on dead sockets
  2218. if {[info exists state(-query)]} {
  2219. # Chop up large query strings so queryprogress callback can give
  2220. # smooth feedback.
  2221. if { $state(queryoffset) + $state(-queryblocksize)
  2222. >= $state(querylength)
  2223. } {
  2224. # This will be the last puts for the request-body.
  2225. if { (![catch {fileevent $sock readable} binding])
  2226. && ($binding eq [list http::CheckEof $sock])
  2227. } {
  2228. # Remove the "fileevent readable" binding of an idle
  2229. # persistent socket to http::CheckEof. We can no longer
  2230. # treat bytes received as junk. The server might still time
  2231. # out and half-close the socket if it has not yet received
  2232. # the first "puts".
  2233. fileevent $sock readable {}
  2234. }
  2235. }
  2236. puts -nonewline $sock \
  2237. [string range $state(-query) $state(queryoffset) \
  2238. [expr {$state(queryoffset) + $state(-queryblocksize) - 1}]]
  2239. incr state(queryoffset) $state(-queryblocksize)
  2240. if {$state(queryoffset) >= $state(querylength)} {
  2241. set state(queryoffset) $state(querylength)
  2242. set done 1
  2243. }
  2244. } else {
  2245. # Copy blocks from the query channel
  2246. set outStr [read $state(-querychannel) $state(-queryblocksize)]
  2247. if {[eof $state(-querychannel)]} {
  2248. # This will be the last puts for the request-body.
  2249. if { (![catch {fileevent $sock readable} binding])
  2250. && ($binding eq [list http::CheckEof $sock])
  2251. } {
  2252. # Remove the "fileevent readable" binding of an idle
  2253. # persistent socket to http::CheckEof. We can no longer
  2254. # treat bytes received as junk. The server might still time
  2255. # out and half-close the socket if it has not yet received
  2256. # the first "puts".
  2257. fileevent $sock readable {}
  2258. }
  2259. }
  2260. puts -nonewline $sock $outStr
  2261. incr state(queryoffset) [string length $outStr]
  2262. if {[eof $state(-querychannel)]} {
  2263. set done 1
  2264. }
  2265. }
  2266. } err]} {
  2267. # Do not call Finish here, but instead let the read half of the socket
  2268. # process whatever server reply there is to get.
  2269. set state(posterror) $err
  2270. set done 1
  2271. }
  2272. if {$done} {
  2273. catch {flush $sock}
  2274. fileevent $sock writable {}
  2275. Log ^C$tk end sending request - token $token
  2276. # End of writing (POST method). The request has been sent.
  2277. DoneRequest $token
  2278. }
  2279. # Callback to the client after we've completely handled everything.
  2280. if {[string length $state(-queryprogress)]} {
  2281. eval $state(-queryprogress) \
  2282. [list $token $state(querylength) $state(queryoffset)]
  2283. }
  2284. }
  2285. # http::Event
  2286. #
  2287. # Handle input on the socket. This command is the core of
  2288. # the coroutine commands ${token}EventCoroutine that are
  2289. # bound to "fileevent $sock readable" and process input.
  2290. #
  2291. # Arguments
  2292. # sock The socket receiving input.
  2293. # token The token returned from http::geturl
  2294. #
  2295. # Side Effects
  2296. # Read the socket and handle callbacks.
  2297. proc http::Event {sock token} {
  2298. variable http
  2299. variable socketMapping
  2300. variable socketRdState
  2301. variable socketWrState
  2302. variable socketRdQueue
  2303. variable socketWrQueue
  2304. variable socketClosing
  2305. variable socketPlayCmd
  2306. variable $token
  2307. upvar 0 $token state
  2308. set tk [namespace tail $token]
  2309. while 1 {
  2310. yield
  2311. ##Log Event call - token $token
  2312. if {![info exists state]} {
  2313. Log "Event $sock with invalid token '$token' - remote close?"
  2314. if {![eof $sock]} {
  2315. if {[set d [read $sock]] ne ""} {
  2316. Log "WARNING: additional data left on closed socket\
  2317. - token $token"
  2318. }
  2319. }
  2320. Log ^X$tk end of response (token error) - token $token
  2321. CloseSocket $sock
  2322. return
  2323. }
  2324. if {$state(state) eq "connecting"} {
  2325. ##Log - connecting - token $token
  2326. if { $state(reusing)
  2327. && $state(-pipeline)
  2328. && ($state(-timeout) > 0)
  2329. && (![info exists state(after)])
  2330. } {
  2331. set state(after) [after $state(-timeout) \
  2332. [list http::reset $token timeout]]
  2333. }
  2334. if {[catch {gets $sock state(http)} nsl]} {
  2335. Log "WARNING - if testing, pay special attention to this\
  2336. case (GK) which is seldom executed - token $token"
  2337. if {[info exists state(reusing)] && $state(reusing)} {
  2338. # The socket was closed at the server end, and closed at
  2339. # this end by http::CheckEof.
  2340. if {[TestForReplay $token read $nsl c]} {
  2341. return
  2342. }
  2343. # else:
  2344. # This is NOT a persistent socket that has been closed since
  2345. # its last use.
  2346. # If any other requests are in flight or pipelined/queued,
  2347. # they will be discarded.
  2348. } else {
  2349. Log ^X$tk end of response (error) - token $token
  2350. Finish $token $nsl
  2351. return
  2352. }
  2353. } elseif {$nsl >= 0} {
  2354. ##Log - connecting 1 - token $token
  2355. set state(state) "header"
  2356. } elseif { [eof $sock]
  2357. && [info exists state(reusing)]
  2358. && $state(reusing)
  2359. } {
  2360. # The socket was closed at the server end, and we didn't notice.
  2361. # This is the first read - where the closure is usually first
  2362. # detected.
  2363. if {[TestForReplay $token read {} d]} {
  2364. return
  2365. }
  2366. # else:
  2367. # This is NOT a persistent socket that has been closed since its
  2368. # last use.
  2369. # If any other requests are in flight or pipelined/queued, they
  2370. # will be discarded.
  2371. }
  2372. } elseif {$state(state) eq "header"} {
  2373. if {[catch {gets $sock line} nhl]} {
  2374. ##Log header failed - token $token
  2375. Log ^X$tk end of response (error) - token $token
  2376. Finish $token $nhl
  2377. return
  2378. } elseif {$nhl == 0} {
  2379. ##Log header done - token $token
  2380. Log ^E$tk end of response headers - token $token
  2381. # We have now read all headers
  2382. # We ignore HTTP/1.1 100 Continue returns. RFC2616 sec 8.2.3
  2383. if { ($state(http) == "")
  2384. || ([regexp {^\S+\s(\d+)} $state(http) {} x] && $x == 100)
  2385. } {
  2386. set state(state) "connecting"
  2387. continue
  2388. # This was a "return" in the pre-coroutine code.
  2389. }
  2390. if { ([info exists state(connection)])
  2391. && ([info exists socketMapping($state(socketinfo))])
  2392. && ("keep-alive" in $state(connection))
  2393. && ($state(-keepalive))
  2394. && (!$state(reusing))
  2395. && ($state(-pipeline))
  2396. } {
  2397. # Response headers received for first request on a
  2398. # persistent socket. Now ready for pipelined writes (if
  2399. # any).
  2400. # Previous value is $token. It cannot be "pending".
  2401. set socketWrState($state(socketinfo)) Wready
  2402. http::NextPipelinedWrite $token
  2403. }
  2404. # Once a "close" has been signaled, the client MUST NOT send any
  2405. # more requests on that connection.
  2406. #
  2407. # If either the client or the server sends the "close" token in
  2408. # the Connection header, that request becomes the last one for
  2409. # the connection.
  2410. if { ([info exists state(connection)])
  2411. && ([info exists socketMapping($state(socketinfo))])
  2412. && ("close" in $state(connection))
  2413. && ($state(-keepalive))
  2414. } {
  2415. # The server warns that it will close the socket after this
  2416. # response.
  2417. ##Log WARNING - socket will close after response for $token
  2418. # Prepare data for a call to ReplayIfClose.
  2419. if { ($socketRdQueue($state(socketinfo)) ne {})
  2420. || ($socketWrQueue($state(socketinfo)) ne {})
  2421. || ($socketWrState($state(socketinfo)) ni
  2422. [list Wready peNding $token])
  2423. } {
  2424. set InFlightW $socketWrState($state(socketinfo))
  2425. if {$InFlightW in [list Wready peNding $token]} {
  2426. set InFlightW Wready
  2427. } else {
  2428. set msg "token ${InFlightW} is InFlightW"
  2429. ##Log $msg - token $token
  2430. }
  2431. set socketPlayCmd($state(socketinfo)) \
  2432. [list ReplayIfClose $InFlightW \
  2433. $socketRdQueue($state(socketinfo)) \
  2434. $socketWrQueue($state(socketinfo))]
  2435. # - All tokens are preserved for re-use by ReplayCore.
  2436. # - Queues are preserved in case of Finish with error,
  2437. # but are not used for anything else because
  2438. # socketClosing(*) is set below.
  2439. # - Cancel the state(after) timeout events.
  2440. foreach tokenVal $socketRdQueue($state(socketinfo)) {
  2441. if {[info exists ${tokenVal}(after)]} {
  2442. after cancel [set ${tokenVal}(after)]
  2443. unset ${tokenVal}(after)
  2444. }
  2445. }
  2446. } else {
  2447. set socketPlayCmd($state(socketinfo)) \
  2448. {ReplayIfClose Wready {} {}}
  2449. }
  2450. # Do not allow further connections on this socket.
  2451. set socketClosing($state(socketinfo)) 1
  2452. }
  2453. set state(state) body
  2454. # According to
  2455. # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection
  2456. # any comma-separated "Connection:" list implies keep-alive, but I
  2457. # don't see this in the RFC so we'll play safe and
  2458. # scan any list for "close".
  2459. # Done here to support combining duplicate header field's values.
  2460. if { [info exists state(connection)]
  2461. && ("close" ni $state(connection))
  2462. && ("keep-alive" ni $state(connection))
  2463. } {
  2464. lappend state(connection) "keep-alive"
  2465. }
  2466. # If doing a HEAD, then we won't get any body
  2467. if {$state(-validate)} {
  2468. Log ^F$tk end of response for HEAD request - token $token
  2469. set state(state) complete
  2470. Eot $token
  2471. return
  2472. }
  2473. # - For non-chunked transfer we may have no body - in this case
  2474. # we may get no further file event if the connection doesn't
  2475. # close and no more data is sent. We can tell and must finish
  2476. # up now - not later - the alternative would be to wait until
  2477. # the server times out.
  2478. # - In this case, the server has NOT told the client it will
  2479. # close the connection, AND it has NOT indicated the resource
  2480. # length EITHER by setting the Content-Length (totalsize) OR
  2481. # by using chunked Transfer-Encoding.
  2482. # - Do not worry here about the case (Connection: close) because
  2483. # the server should close the connection.
  2484. # - IF (NOT Connection: close) AND (NOT chunked encoding) AND
  2485. # (totalsize == 0).
  2486. if { (!( [info exists state(connection)]
  2487. && ("close" in $state(connection))
  2488. )
  2489. )
  2490. && (![info exists state(transfer)])
  2491. && ($state(totalsize) == 0)
  2492. } {
  2493. set msg {body size is 0 and no events likely - complete}
  2494. Log "$msg - token $token"
  2495. set msg {(length unknown, set to 0)}
  2496. Log ^F$tk end of response body {*}$msg - token $token
  2497. set state(state) complete
  2498. Eot $token
  2499. return
  2500. }
  2501. # We have to use binary translation to count bytes properly.
  2502. lassign [fconfigure $sock -translation] trRead trWrite
  2503. fconfigure $sock -translation [list binary $trWrite]
  2504. if {
  2505. $state(-binary) || [IsBinaryContentType $state(type)]
  2506. } {
  2507. # Turn off conversions for non-text data.
  2508. set state(binary) 1
  2509. }
  2510. if {[info exists state(-channel)]} {
  2511. if {$state(binary) || [llength [ContentEncoding $token]]} {
  2512. fconfigure $state(-channel) -translation binary
  2513. }
  2514. if {![info exists state(-handler)]} {
  2515. # Initiate a sequence of background fcopies.
  2516. fileevent $sock readable {}
  2517. rename ${token}EventCoroutine {}
  2518. CopyStart $sock $token
  2519. return
  2520. }
  2521. }
  2522. } elseif {$nhl > 0} {
  2523. # Process header lines.
  2524. ##Log header - token $token - $line
  2525. if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} {
  2526. switch -- [string tolower $key] {
  2527. content-type {
  2528. set state(type) [string trim [string tolower $value]]
  2529. # Grab the optional charset information.
  2530. if {[regexp -nocase \
  2531. {charset\s*=\s*\"((?:[^""]|\\\")*)\"} \
  2532. $state(type) -> cs]} {
  2533. set state(charset) [string map {{\"} \"} $cs]
  2534. } else {
  2535. regexp -nocase {charset\s*=\s*(\S+?);?} \
  2536. $state(type) -> state(charset)
  2537. }
  2538. }
  2539. content-length {
  2540. set state(totalsize) [string trim $value]
  2541. }
  2542. content-encoding {
  2543. set state(coding) [string trim $value]
  2544. }
  2545. transfer-encoding {
  2546. set state(transfer) \
  2547. [string trim [string tolower $value]]
  2548. }
  2549. proxy-connection -
  2550. connection {
  2551. # RFC 7230 Section 6.1 states that a comma-separated
  2552. # list is an acceptable value.
  2553. foreach el [SplitCommaSeparatedFieldValue $value] {
  2554. lappend state(connection) [string tolower $el]
  2555. }
  2556. }
  2557. upgrade {
  2558. set state(upgrade) [string trim $value]
  2559. }
  2560. }
  2561. lappend state(meta) $key [string trim $value]
  2562. }
  2563. }
  2564. } else {
  2565. # Now reading body
  2566. ##Log body - token $token
  2567. if {[catch {
  2568. if {[info exists state(-handler)]} {
  2569. set n [eval $state(-handler) [list $sock $token]]
  2570. ##Log handler $n - token $token
  2571. # N.B. the protocol has been set to 1.0 because the -handler
  2572. # logic is not expected to handle chunked encoding.
  2573. # FIXME Allow -handler with 1.1 on dechunked stacked chan.
  2574. if {$state(totalsize) == 0} {
  2575. # We know the transfer is complete only when the server
  2576. # closes the connection - i.e. eof is not an error.
  2577. set state(state) complete
  2578. }
  2579. if {![string is integer -strict $n]} {
  2580. if 1 {
  2581. # Do not tolerate bad -handler - fail with error
  2582. # status.
  2583. set msg {the -handler command for http::geturl must\
  2584. return an integer (the number of bytes\
  2585. read)}
  2586. Log ^X$tk end of response (handler error) -\
  2587. token $token
  2588. Eot $token $msg
  2589. } else {
  2590. # Tolerate the bad -handler, and continue. The
  2591. # penalty:
  2592. # (a) Because the handler returns nonsense, we know
  2593. # the transfer is complete only when the server
  2594. # closes the connection - i.e. eof is not an
  2595. # error.
  2596. # (b) http::size will not be accurate.
  2597. # (c) The transaction is already downgraded to 1.0
  2598. # to avoid chunked transfer encoding. It MUST
  2599. # also be forced to "Connection: close" or the
  2600. # HTTP/1.0 equivalent; or it MUST fail (as
  2601. # above) if the server sends
  2602. # "Connection: keep-alive" or the HTTP/1.0
  2603. # equivalent.
  2604. set n 0
  2605. set state(state) complete
  2606. }
  2607. }
  2608. } elseif {[info exists state(transfer_final)]} {
  2609. # This code forgives EOF in place of the final CRLF.
  2610. set line [getTextLine $sock]
  2611. set n [string length $line]
  2612. set state(state) complete
  2613. if {$n > 0} {
  2614. # - HTTP trailers (late response headers) are permitted
  2615. # by Chunked Transfer-Encoding, and can be safely
  2616. # ignored.
  2617. # - Do not count these bytes in the total received for
  2618. # the response body.
  2619. Log "trailer of $n bytes after final chunk -\
  2620. token $token"
  2621. append state(transfer_final) $line
  2622. set n 0
  2623. } else {
  2624. Log ^F$tk end of response body (chunked) - token $token
  2625. Log "final chunk part - token $token"
  2626. Eot $token
  2627. }
  2628. } elseif { [info exists state(transfer)]
  2629. && ($state(transfer) eq "chunked")
  2630. } {
  2631. ##Log chunked - token $token
  2632. set size 0
  2633. set hexLenChunk [getTextLine $sock]
  2634. #set ntl [string length $hexLenChunk]
  2635. if {[string trim $hexLenChunk] ne ""} {
  2636. scan $hexLenChunk %x size
  2637. if {$size != 0} {
  2638. ##Log chunk-measure $size - token $token
  2639. set chunk [BlockingRead $sock $size]
  2640. set n [string length $chunk]
  2641. if {$n >= 0} {
  2642. append state(body) $chunk
  2643. incr state(log_size) [string length $chunk]
  2644. ##Log chunk $n cumul $state(log_size) -\
  2645. token $token
  2646. }
  2647. if {$size != [string length $chunk]} {
  2648. Log "WARNING: mis-sized chunk:\
  2649. was [string length $chunk], should be\
  2650. $size - token $token"
  2651. set n 0
  2652. set state(connection) close
  2653. Log ^X$tk end of response (chunk error) \
  2654. - token $token
  2655. set msg {error in chunked encoding - fetch\
  2656. terminated}
  2657. Eot $token $msg
  2658. }
  2659. # CRLF that follows chunk.
  2660. # If eof, this is handled at the end of this proc.
  2661. getTextLine $sock
  2662. } else {
  2663. set n 0
  2664. set state(transfer_final) {}
  2665. }
  2666. } else {
  2667. # Line expected to hold chunk length is empty, or eof.
  2668. ##Log bad-chunk-measure - token $token
  2669. set n 0
  2670. set state(connection) close
  2671. Log ^X$tk end of response (chunk error) - token $token
  2672. Eot $token {error in chunked encoding -\
  2673. fetch terminated}
  2674. }
  2675. } else {
  2676. ##Log unchunked - token $token
  2677. if {$state(totalsize) == 0} {
  2678. # We know the transfer is complete only when the server
  2679. # closes the connection.
  2680. set state(state) complete
  2681. set reqSize $state(-blocksize)
  2682. } else {
  2683. # Ask for the whole of the unserved response-body.
  2684. # This works around a problem with a tls::socket - for
  2685. # https in keep-alive mode, and a request for
  2686. # $state(-blocksize) bytes, the last part of the
  2687. # resource does not get read until the server times out.
  2688. set reqSize [expr { $state(totalsize)
  2689. - $state(currentsize)}]
  2690. # The workaround fails if reqSize is
  2691. # capped at $state(-blocksize).
  2692. # set reqSize [expr {min($reqSize, $state(-blocksize))}]
  2693. }
  2694. set c $state(currentsize)
  2695. set t $state(totalsize)
  2696. ##Log non-chunk currentsize $c of totalsize $t -\
  2697. token $token
  2698. set block [read $sock $reqSize]
  2699. set n [string length $block]
  2700. if {$n >= 0} {
  2701. append state(body) $block
  2702. ##Log non-chunk [string length $state(body)] -\
  2703. token $token
  2704. }
  2705. }
  2706. # This calculation uses n from the -handler, chunked, or
  2707. # unchunked case as appropriate.
  2708. if {[info exists state]} {
  2709. if {$n >= 0} {
  2710. incr state(currentsize) $n
  2711. set c $state(currentsize)
  2712. set t $state(totalsize)
  2713. ##Log another $n currentsize $c totalsize $t -\
  2714. token $token
  2715. }
  2716. # If Content-Length - check for end of data.
  2717. if {
  2718. ($state(totalsize) > 0)
  2719. && ($state(currentsize) >= $state(totalsize))
  2720. } {
  2721. Log ^F$tk end of response body (unchunked) -\
  2722. token $token
  2723. set state(state) complete
  2724. Eot $token
  2725. }
  2726. }
  2727. } err]} {
  2728. Log ^X$tk end of response (error ${err}) - token $token
  2729. Finish $token $err
  2730. return
  2731. } else {
  2732. if {[info exists state(-progress)]} {
  2733. eval $state(-progress) \
  2734. [list $token $state(totalsize) $state(currentsize)]
  2735. }
  2736. }
  2737. }
  2738. # catch as an Eot above may have closed the socket already
  2739. # $state(state) may be connecting, header, body, or complete
  2740. if {![set cc [catch {eof $sock} eof]] && $eof} {
  2741. ##Log eof - token $token
  2742. if {[info exists $token]} {
  2743. set state(connection) close
  2744. if {$state(state) eq "complete"} {
  2745. # This includes all cases in which the transaction
  2746. # can be completed by eof.
  2747. # The value "complete" is set only in http::Event, and it is
  2748. # used only in the test above.
  2749. Log ^F$tk end of response body (unchunked, eof) -\
  2750. token $token
  2751. Eot $token
  2752. } else {
  2753. # Premature eof.
  2754. Log ^X$tk end of response (unexpected eof) - token $token
  2755. Eot $token eof
  2756. }
  2757. } else {
  2758. # open connection closed on a token that has been cleaned up.
  2759. Log ^X$tk end of response (token error) - token $token
  2760. CloseSocket $sock
  2761. }
  2762. } elseif {$cc} {
  2763. return
  2764. }
  2765. }
  2766. }
  2767. # http::TestForReplay
  2768. #
  2769. # Command called if eof is discovered when a socket is first used for a
  2770. # new transaction. Typically this occurs if a persistent socket is used
  2771. # after a period of idleness and the server has half-closed the socket.
  2772. #
  2773. # token - the connection token returned by http::geturl
  2774. # doing - "read" or "write"
  2775. # err - error message, if any
  2776. # caller - code to identify the caller - used only in logging
  2777. #
  2778. # Return Value: boolean, true iff the command calls http::ReplayIfDead.
  2779. proc http::TestForReplay {token doing err caller} {
  2780. variable http
  2781. variable $token
  2782. upvar 0 $token state
  2783. set tk [namespace tail $token]
  2784. if {$doing eq "read"} {
  2785. set code Q
  2786. set action response
  2787. set ing reading
  2788. } else {
  2789. set code P
  2790. set action request
  2791. set ing writing
  2792. }
  2793. if {$err eq {}} {
  2794. set err "detect eof when $ing (server timed out?)"
  2795. }
  2796. if {$state(method) eq "POST" && !$http(-repost)} {
  2797. # No Replay.
  2798. # The present transaction will end when Finish is called.
  2799. # That call to Finish will abort any other transactions
  2800. # currently in the write queue.
  2801. # For calls from http::Event this occurs when execution
  2802. # reaches the code block at the end of that proc.
  2803. set msg {no retry for POST with http::config -repost 0}
  2804. Log reusing socket failed "($caller)" - $msg - token $token
  2805. Log error - $err - token $token
  2806. Log ^X$tk end of $action (error) - token $token
  2807. return 0
  2808. } else {
  2809. # Replay.
  2810. set msg {try a new socket}
  2811. Log reusing socket failed "($caller)" - $msg - token $token
  2812. Log error - $err - token $token
  2813. Log ^$code$tk Any unfinished (incl this one) failed - token $token
  2814. ReplayIfDead $token $doing
  2815. return 1
  2816. }
  2817. }
  2818. # http::IsBinaryContentType --
  2819. #
  2820. # Determine if the content-type means that we should definitely transfer
  2821. # the data as binary. [Bug 838e99a76d]
  2822. #
  2823. # Arguments
  2824. # type The content-type of the data.
  2825. #
  2826. # Results:
  2827. # Boolean, true if we definitely should be binary.
  2828. proc http::IsBinaryContentType {type} {
  2829. lassign [split [string tolower $type] "/;"] major minor
  2830. if {$major eq "text"} {
  2831. return false
  2832. }
  2833. # There's a bunch of XML-as-application-format things about. See RFC 3023
  2834. # and so on.
  2835. if {$major eq "application"} {
  2836. set minor [string trimright $minor]
  2837. if {$minor in {"json" "xml" "xml-external-parsed-entity" "xml-dtd"}} {
  2838. return false
  2839. }
  2840. }
  2841. # Not just application/foobar+xml but also image/svg+xml, so let us not
  2842. # restrict things for now...
  2843. if {[string match "*+xml" $minor]} {
  2844. return false
  2845. }
  2846. return true
  2847. }
  2848. # http::getTextLine --
  2849. #
  2850. # Get one line with the stream in crlf mode.
  2851. # Used if Transfer-Encoding is chunked.
  2852. # Empty line is not distinguished from eof. The caller must
  2853. # be able to handle this.
  2854. #
  2855. # Arguments
  2856. # sock The socket receiving input.
  2857. #
  2858. # Results:
  2859. # The line of text, without trailing newline
  2860. proc http::getTextLine {sock} {
  2861. set tr [fconfigure $sock -translation]
  2862. lassign $tr trRead trWrite
  2863. fconfigure $sock -translation [list crlf $trWrite]
  2864. set r [BlockingGets $sock]
  2865. fconfigure $sock -translation $tr
  2866. return $r
  2867. }
  2868. # http::BlockingRead
  2869. #
  2870. # Replacement for a blocking read.
  2871. # The caller must be a coroutine.
  2872. proc http::BlockingRead {sock size} {
  2873. if {$size < 1} {
  2874. return
  2875. }
  2876. set result {}
  2877. while 1 {
  2878. set need [expr {$size - [string length $result]}]
  2879. set block [read $sock $need]
  2880. set eof [eof $sock]
  2881. append result $block
  2882. if {[string length $result] >= $size || $eof} {
  2883. return $result
  2884. } else {
  2885. yield
  2886. }
  2887. }
  2888. }
  2889. # http::BlockingGets
  2890. #
  2891. # Replacement for a blocking gets.
  2892. # The caller must be a coroutine.
  2893. # Empty line is not distinguished from eof. The caller must
  2894. # be able to handle this.
  2895. proc http::BlockingGets {sock} {
  2896. while 1 {
  2897. set count [gets $sock line]
  2898. set eof [eof $sock]
  2899. if {$count >= 0 || $eof} {
  2900. return $line
  2901. } else {
  2902. yield
  2903. }
  2904. }
  2905. }
  2906. # http::CopyStart
  2907. #
  2908. # Error handling wrapper around fcopy
  2909. #
  2910. # Arguments
  2911. # sock The socket to copy from
  2912. # token The token returned from http::geturl
  2913. #
  2914. # Side Effects
  2915. # This closes the connection upon error
  2916. proc http::CopyStart {sock token {initial 1}} {
  2917. upvar #0 $token state
  2918. if {[info exists state(transfer)] && $state(transfer) eq "chunked"} {
  2919. foreach coding [ContentEncoding $token] {
  2920. lappend state(zlib) [zlib stream $coding]
  2921. }
  2922. make-transformation-chunked $sock [namespace code [list CopyChunk $token]]
  2923. } else {
  2924. if {$initial} {
  2925. foreach coding [ContentEncoding $token] {
  2926. zlib push $coding $sock
  2927. }
  2928. }
  2929. if {[catch {
  2930. # FIXME Keep-Alive on https tls::socket with unchunked transfer
  2931. # hangs until the server times out. A workaround is possible, as for
  2932. # the case without -channel, but it does not use the neat "fcopy"
  2933. # solution.
  2934. fcopy $sock $state(-channel) -size $state(-blocksize) -command \
  2935. [list http::CopyDone $token]
  2936. } err]} {
  2937. Finish $token $err
  2938. }
  2939. }
  2940. }
  2941. proc http::CopyChunk {token chunk} {
  2942. upvar 0 $token state
  2943. if {[set count [string length $chunk]]} {
  2944. incr state(currentsize) $count
  2945. if {[info exists state(zlib)]} {
  2946. foreach stream $state(zlib) {
  2947. set chunk [$stream add $chunk]
  2948. }
  2949. }
  2950. puts -nonewline $state(-channel) $chunk
  2951. if {[info exists state(-progress)]} {
  2952. eval [linsert $state(-progress) end \
  2953. $token $state(totalsize) $state(currentsize)]
  2954. }
  2955. } else {
  2956. Log "CopyChunk Finish - token $token"
  2957. if {[info exists state(zlib)]} {
  2958. set excess ""
  2959. foreach stream $state(zlib) {
  2960. catch {set excess [$stream add -finalize $excess]}
  2961. }
  2962. puts -nonewline $state(-channel) $excess
  2963. foreach stream $state(zlib) { $stream close }
  2964. unset state(zlib)
  2965. }
  2966. Eot $token ;# FIX ME: pipelining.
  2967. }
  2968. }
  2969. # http::CopyDone
  2970. #
  2971. # fcopy completion callback
  2972. #
  2973. # Arguments
  2974. # token The token returned from http::geturl
  2975. # count The amount transferred
  2976. #
  2977. # Side Effects
  2978. # Invokes callbacks
  2979. proc http::CopyDone {token count {error {}}} {
  2980. variable $token
  2981. upvar 0 $token state
  2982. set sock $state(sock)
  2983. incr state(currentsize) $count
  2984. if {[info exists state(-progress)]} {
  2985. eval $state(-progress) \
  2986. [list $token $state(totalsize) $state(currentsize)]
  2987. }
  2988. # At this point the token may have been reset.
  2989. if {[string length $error]} {
  2990. Finish $token $error
  2991. } elseif {[catch {eof $sock} iseof] || $iseof} {
  2992. Eot $token
  2993. } else {
  2994. CopyStart $sock $token 0
  2995. }
  2996. }
  2997. # http::Eot
  2998. #
  2999. # Called when either:
  3000. # a. An eof condition is detected on the socket.
  3001. # b. The client decides that the response is complete.
  3002. # c. The client detects an inconsistency and aborts the transaction.
  3003. #
  3004. # Does:
  3005. # 1. Set state(status)
  3006. # 2. Reverse any Content-Encoding
  3007. # 3. Convert charset encoding and line ends if necessary
  3008. # 4. Call http::Finish
  3009. #
  3010. # Arguments
  3011. # token The token returned from http::geturl
  3012. # force (previously) optional, has no effect
  3013. # reason - "eof" means premature EOF (not EOF as the natural end of
  3014. # the response)
  3015. # - "" means completion of response, with or without EOF
  3016. # - anything else describes an error condition other than
  3017. # premature EOF.
  3018. #
  3019. # Side Effects
  3020. # Clean up the socket
  3021. proc http::Eot {token {reason {}}} {
  3022. variable $token
  3023. upvar 0 $token state
  3024. if {$reason eq "eof"} {
  3025. # Premature eof.
  3026. set state(status) eof
  3027. set reason {}
  3028. } elseif {$reason ne ""} {
  3029. # Abort the transaction.
  3030. set state(status) $reason
  3031. } else {
  3032. # The response is complete.
  3033. set state(status) ok
  3034. }
  3035. if {[string length $state(body)] > 0} {
  3036. if {[catch {
  3037. foreach coding [ContentEncoding $token] {
  3038. set state(body) [zlib $coding $state(body)]
  3039. }
  3040. } err]} {
  3041. Log "error doing decompression for token $token: $err"
  3042. Finish $token $err
  3043. return
  3044. }
  3045. if {!$state(binary)} {
  3046. # If we are getting text, set the incoming channel's encoding
  3047. # correctly. iso8859-1 is the RFC default, but this could be any
  3048. # IANA charset. However, we only know how to convert what we have
  3049. # encodings for.
  3050. set enc [CharsetToEncoding $state(charset)]
  3051. if {$enc ne "binary"} {
  3052. set state(body) [encoding convertfrom $enc $state(body)]
  3053. }
  3054. # Translate text line endings.
  3055. set state(body) [string map {\r\n \n \r \n} $state(body)]
  3056. }
  3057. }
  3058. Finish $token $reason
  3059. }
  3060. # http::wait --
  3061. #
  3062. # See documentation for details.
  3063. #
  3064. # Arguments:
  3065. # token Connection token.
  3066. #
  3067. # Results:
  3068. # The status after the wait.
  3069. proc http::wait {token} {
  3070. variable $token
  3071. upvar 0 $token state
  3072. if {![info exists state(status)] || $state(status) eq ""} {
  3073. # We must wait on the original variable name, not the upvar alias
  3074. vwait ${token}(status)
  3075. }
  3076. return [status $token]
  3077. }
  3078. # http::formatQuery --
  3079. #
  3080. # See documentation for details. Call http::formatQuery with an even
  3081. # number of arguments, where the first is a name, the second is a value,
  3082. # the third is another name, and so on.
  3083. #
  3084. # Arguments:
  3085. # args A list of name-value pairs.
  3086. #
  3087. # Results:
  3088. # TODO
  3089. proc http::formatQuery {args} {
  3090. if {[llength $args] % 2} {
  3091. return \
  3092. -code error \
  3093. -errorcode [list HTTP BADARGCNT $args] \
  3094. {Incorrect number of arguments, must be an even number.}
  3095. }
  3096. set result ""
  3097. set sep ""
  3098. foreach i $args {
  3099. append result $sep [mapReply $i]
  3100. if {$sep eq "="} {
  3101. set sep &
  3102. } else {
  3103. set sep =
  3104. }
  3105. }
  3106. return $result
  3107. }
  3108. # http::mapReply --
  3109. #
  3110. # Do x-www-urlencoded character mapping
  3111. #
  3112. # Arguments:
  3113. # string The string the needs to be encoded
  3114. #
  3115. # Results:
  3116. # The encoded string
  3117. proc http::mapReply {string} {
  3118. variable http
  3119. variable formMap
  3120. # The spec says: "non-alphanumeric characters are replaced by '%HH'". Use
  3121. # a pre-computed map and [string map] to do the conversion (much faster
  3122. # than [regsub]/[subst]). [Bug 1020491]
  3123. if {$http(-urlencoding) ne ""} {
  3124. set string [encoding convertto $http(-urlencoding) $string]
  3125. return [string map $formMap $string]
  3126. }
  3127. set converted [string map $formMap $string]
  3128. if {[string match "*\[\u0100-\uffff\]*" $converted]} {
  3129. regexp "\[\u0100-\uffff\]" $converted badChar
  3130. # Return this error message for maximum compatibility... :^/
  3131. return -code error \
  3132. "can't read \"formMap($badChar)\": no such element in array"
  3133. }
  3134. return $converted
  3135. }
  3136. interp alias {} http::quoteString {} http::mapReply
  3137. # http::ProxyRequired --
  3138. # Default proxy filter.
  3139. #
  3140. # Arguments:
  3141. # host The destination host
  3142. #
  3143. # Results:
  3144. # The current proxy settings
  3145. proc http::ProxyRequired {host} {
  3146. variable http
  3147. if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} {
  3148. if {
  3149. ![info exists http(-proxyport)] ||
  3150. ![string length $http(-proxyport)]
  3151. } {
  3152. set http(-proxyport) 8080
  3153. }
  3154. return [list $http(-proxyhost) $http(-proxyport)]
  3155. }
  3156. }
  3157. # http::CharsetToEncoding --
  3158. #
  3159. # Tries to map a given IANA charset to a tcl encoding. If no encoding
  3160. # can be found, returns binary.
  3161. #
  3162. proc http::CharsetToEncoding {charset} {
  3163. variable encodings
  3164. set charset [string tolower $charset]
  3165. if {[regexp {iso-?8859-([0-9]+)} $charset -> num]} {
  3166. set encoding "iso8859-$num"
  3167. } elseif {[regexp {iso-?2022-(jp|kr)} $charset -> ext]} {
  3168. set encoding "iso2022-$ext"
  3169. } elseif {[regexp {shift[-_]?jis} $charset]} {
  3170. set encoding "shiftjis"
  3171. } elseif {[regexp {(?:windows|cp)-?([0-9]+)} $charset -> num]} {
  3172. set encoding "cp$num"
  3173. } elseif {$charset eq "us-ascii"} {
  3174. set encoding "ascii"
  3175. } elseif {[regexp {(?:iso-?)?lat(?:in)?-?([0-9]+)} $charset -> num]} {
  3176. switch -- $num {
  3177. 5 {set encoding "iso8859-9"}
  3178. 1 - 2 - 3 {
  3179. set encoding "iso8859-$num"
  3180. }
  3181. default {
  3182. set encoding "binary"
  3183. }
  3184. }
  3185. } else {
  3186. # other charset, like euc-xx, utf-8,... may directly map to encoding
  3187. set encoding $charset
  3188. }
  3189. set idx [lsearch -exact $encodings $encoding]
  3190. if {$idx >= 0} {
  3191. return $encoding
  3192. } else {
  3193. return "binary"
  3194. }
  3195. }
  3196. # Return the list of content-encoding transformations we need to do in order.
  3197. proc http::ContentEncoding {token} {
  3198. upvar 0 $token state
  3199. set r {}
  3200. if {[info exists state(coding)]} {
  3201. foreach coding [split $state(coding) ,] {
  3202. switch -exact -- $coding {
  3203. deflate { lappend r inflate }
  3204. gzip - x-gzip { lappend r gunzip }
  3205. compress - x-compress { lappend r decompress }
  3206. identity {}
  3207. br {
  3208. return -code error\
  3209. "content-encoding \"br\" not implemented"
  3210. }
  3211. default {
  3212. Log "unknown content-encoding \"$coding\" ignored"
  3213. }
  3214. }
  3215. }
  3216. }
  3217. return $r
  3218. }
  3219. proc http::ReceiveChunked {chan command} {
  3220. set data ""
  3221. set size -1
  3222. yield
  3223. while {1} {
  3224. chan configure $chan -translation {crlf binary}
  3225. while {[gets $chan line] < 1} { yield }
  3226. chan configure $chan -translation {binary binary}
  3227. if {[scan $line %x size] != 1} {
  3228. return -code error "invalid size: \"$line\""
  3229. }
  3230. set chunk ""
  3231. while {$size && ![chan eof $chan]} {
  3232. set part [chan read $chan $size]
  3233. incr size -[string length $part]
  3234. append chunk $part
  3235. }
  3236. if {[catch {
  3237. uplevel #0 [linsert $command end $chunk]
  3238. }]} {
  3239. http::Log "Error in callback: $::errorInfo"
  3240. }
  3241. if {[string length $chunk] == 0} {
  3242. # channel might have been closed in the callback
  3243. catch {chan event $chan readable {}}
  3244. return
  3245. }
  3246. }
  3247. }
  3248. # http::SplitCommaSeparatedFieldValue --
  3249. # Return the individual values of a comma-separated field value.
  3250. #
  3251. # Arguments:
  3252. # fieldValue Comma-separated header field value.
  3253. #
  3254. # Results:
  3255. # List of values.
  3256. proc http::SplitCommaSeparatedFieldValue {fieldValue} {
  3257. set r {}
  3258. foreach el [split $fieldValue ,] {
  3259. lappend r [string trim $el]
  3260. }
  3261. return $r
  3262. }
  3263. # http::GetFieldValue --
  3264. # Return the value of a header field.
  3265. #
  3266. # Arguments:
  3267. # headers Headers key-value list
  3268. # fieldName Name of header field whose value to return.
  3269. #
  3270. # Results:
  3271. # The value of the fieldName header field
  3272. #
  3273. # Field names are matched case-insensitively (RFC 7230 Section 3.2).
  3274. #
  3275. # If the field is present multiple times, it is assumed that the field is
  3276. # defined as a comma-separated list and the values are combined (by separating
  3277. # them with commas, see RFC 7230 Section 3.2.2) and returned at once.
  3278. proc http::GetFieldValue {headers fieldName} {
  3279. set r {}
  3280. foreach {field value} $headers {
  3281. if {[string equal -nocase $fieldName $field]} {
  3282. if {$r eq {}} {
  3283. set r $value
  3284. } else {
  3285. append r ", $value"
  3286. }
  3287. }
  3288. }
  3289. return $r
  3290. }
  3291. proc http::make-transformation-chunked {chan command} {
  3292. coroutine [namespace current]::dechunk$chan ::http::ReceiveChunked $chan $command
  3293. chan event $chan readable [namespace current]::dechunk$chan
  3294. }
  3295. # Local variables:
  3296. # indent-tabs-mode: t
  3297. # End: