http-2.8.9.tm 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541
  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.8.9
  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. -proxyhost {}
  21. -proxyport {}
  22. -proxyfilter http::ProxyRequired
  23. -urlencoding utf-8
  24. }
  25. # We need a useragent string of this style or various servers will refuse to
  26. # send us compressed content even when we ask for it. This follows the
  27. # de-facto layout of user-agent strings in current browsers.
  28. set http(-useragent) "Mozilla/5.0\
  29. ([string totitle $::tcl_platform(platform)]; U;\
  30. $::tcl_platform(os) $::tcl_platform(osVersion))\
  31. http/[package provide http] Tcl/[package provide Tcl]"
  32. }
  33. proc init {} {
  34. # Set up the map for quoting chars. RFC3986 Section 2.3 say percent
  35. # encode all except: "... percent-encoded octets in the ranges of
  36. # ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), period
  37. # (%2E), underscore (%5F), or tilde (%7E) should not be created by URI
  38. # producers ..."
  39. for {set i 0} {$i <= 256} {incr i} {
  40. set c [format %c $i]
  41. if {![string match {[-._~a-zA-Z0-9]} $c]} {
  42. set map($c) %[format %.2X $i]
  43. }
  44. }
  45. # These are handled specially
  46. set map(\n) %0D%0A
  47. variable formMap [array get map]
  48. # Create a map for HTTP/1.1 open sockets
  49. variable socketmap
  50. if {[info exists socketmap]} {
  51. # Close but don't remove open sockets on re-init
  52. foreach {url sock} [array get socketmap] {
  53. catch {close $sock}
  54. }
  55. }
  56. array set socketmap {}
  57. }
  58. init
  59. variable urlTypes
  60. if {![info exists urlTypes]} {
  61. set urlTypes(http) [list 80 ::socket]
  62. }
  63. variable encodings [string tolower [encoding names]]
  64. # This can be changed, but iso8859-1 is the RFC standard.
  65. variable defaultCharset
  66. if {![info exists defaultCharset]} {
  67. set defaultCharset "iso8859-1"
  68. }
  69. # Force RFC 3986 strictness in geturl url verification?
  70. variable strict
  71. if {![info exists strict]} {
  72. set strict 1
  73. }
  74. # Let user control default keepalive for compatibility
  75. variable defaultKeepalive
  76. if {![info exists defaultKeepalive]} {
  77. set defaultKeepalive 0
  78. }
  79. namespace export geturl config reset wait formatQuery register unregister
  80. # Useful, but not exported: data size status code
  81. }
  82. # http::Log --
  83. #
  84. # Debugging output -- define this to observe HTTP/1.1 socket usage.
  85. # Should echo any args received.
  86. #
  87. # Arguments:
  88. # msg Message to output
  89. #
  90. if {[info command http::Log] eq {}} {proc http::Log {args} {}}
  91. # http::register --
  92. #
  93. # See documentation for details.
  94. #
  95. # Arguments:
  96. # proto URL protocol prefix, e.g. https
  97. # port Default port for protocol
  98. # command Command to use to create socket
  99. # Results:
  100. # list of port and command that was registered.
  101. proc http::register {proto port command} {
  102. variable urlTypes
  103. set urlTypes([string tolower $proto]) [list $port $command]
  104. }
  105. # http::unregister --
  106. #
  107. # Unregisters URL protocol handler
  108. #
  109. # Arguments:
  110. # proto URL protocol prefix, e.g. https
  111. # Results:
  112. # list of port and command that was unregistered.
  113. proc http::unregister {proto} {
  114. variable urlTypes
  115. set lower [string tolower $proto]
  116. if {![info exists urlTypes($lower)]} {
  117. return -code error "unsupported url type \"$proto\""
  118. }
  119. set old $urlTypes($lower)
  120. unset urlTypes($lower)
  121. return $old
  122. }
  123. # http::config --
  124. #
  125. # See documentation for details.
  126. #
  127. # Arguments:
  128. # args Options parsed by the procedure.
  129. # Results:
  130. # TODO
  131. proc http::config {args} {
  132. variable http
  133. set options [lsort [array names http -*]]
  134. set usage [join $options ", "]
  135. if {[llength $args] == 0} {
  136. set result {}
  137. foreach name $options {
  138. lappend result $name $http($name)
  139. }
  140. return $result
  141. }
  142. set options [string map {- ""} $options]
  143. set pat ^-(?:[join $options |])$
  144. if {[llength $args] == 1} {
  145. set flag [lindex $args 0]
  146. if {![regexp -- $pat $flag]} {
  147. return -code error "Unknown option $flag, must be: $usage"
  148. }
  149. return $http($flag)
  150. } else {
  151. foreach {flag value} $args {
  152. if {![regexp -- $pat $flag]} {
  153. return -code error "Unknown option $flag, must be: $usage"
  154. }
  155. set http($flag) $value
  156. }
  157. }
  158. }
  159. # http::Finish --
  160. #
  161. # Clean up the socket and eval close time callbacks
  162. #
  163. # Arguments:
  164. # token Connection token.
  165. # errormsg (optional) If set, forces status to error.
  166. # skipCB (optional) If set, don't call the -command callback. This
  167. # is useful when geturl wants to throw an exception instead
  168. # of calling the callback. That way, the same error isn't
  169. # reported to two places.
  170. #
  171. # Side Effects:
  172. # Closes the socket
  173. proc http::Finish {token {errormsg ""} {skipCB 0}} {
  174. variable $token
  175. upvar 0 $token state
  176. global errorInfo errorCode
  177. if {$errormsg ne ""} {
  178. set state(error) [list $errormsg $errorInfo $errorCode]
  179. set state(status) "error"
  180. }
  181. if {
  182. ($state(status) eq "timeout") || ($state(status) eq "error") ||
  183. ([info exists state(connection)] && ($state(connection) eq "close"))
  184. } {
  185. CloseSocket $state(sock) $token
  186. }
  187. if {[info exists state(after)]} {
  188. after cancel $state(after)
  189. }
  190. if {[info exists state(-command)] && !$skipCB
  191. && ![info exists state(done-command-cb)]} {
  192. set state(done-command-cb) yes
  193. if {[catch {eval $state(-command) {$token}} err] && $errormsg eq ""} {
  194. set state(error) [list $err $errorInfo $errorCode]
  195. set state(status) error
  196. }
  197. }
  198. }
  199. # http::CloseSocket -
  200. #
  201. # Close a socket and remove it from the persistent sockets table. If
  202. # possible an http token is included here but when we are called from a
  203. # fileevent on remote closure we need to find the correct entry - hence
  204. # the second section.
  205. proc ::http::CloseSocket {s {token {}}} {
  206. variable socketmap
  207. catch {fileevent $s readable {}}
  208. set conn_id {}
  209. if {$token ne ""} {
  210. variable $token
  211. upvar 0 $token state
  212. if {[info exists state(socketinfo)]} {
  213. set conn_id $state(socketinfo)
  214. }
  215. } else {
  216. set map [array get socketmap]
  217. set ndx [lsearch -exact $map $s]
  218. if {$ndx != -1} {
  219. incr ndx -1
  220. set conn_id [lindex $map $ndx]
  221. }
  222. }
  223. if {$conn_id eq {} || ![info exists socketmap($conn_id)]} {
  224. Log "Closing socket $s (no connection info)"
  225. if {[catch {close $s} err]} {
  226. Log "Error: $err"
  227. }
  228. } else {
  229. if {[info exists socketmap($conn_id)]} {
  230. Log "Closing connection $conn_id (sock $socketmap($conn_id))"
  231. if {[catch {close $socketmap($conn_id)} err]} {
  232. Log "Error: $err"
  233. }
  234. unset socketmap($conn_id)
  235. } else {
  236. Log "Cannot close connection $conn_id - no socket in socket map"
  237. }
  238. }
  239. }
  240. # http::reset --
  241. #
  242. # See documentation for details.
  243. #
  244. # Arguments:
  245. # token Connection token.
  246. # why Status info.
  247. #
  248. # Side Effects:
  249. # See Finish
  250. proc http::reset {token {why reset}} {
  251. variable $token
  252. upvar 0 $token state
  253. set state(status) $why
  254. catch {fileevent $state(sock) readable {}}
  255. catch {fileevent $state(sock) writable {}}
  256. Finish $token
  257. if {[info exists state(error)]} {
  258. set errorlist $state(error)
  259. unset state
  260. eval ::error $errorlist
  261. }
  262. }
  263. # http::geturl --
  264. #
  265. # Establishes a connection to a remote url via http.
  266. #
  267. # Arguments:
  268. # url The http URL to goget.
  269. # args Option value pairs. Valid options include:
  270. # -blocksize, -validate, -headers, -timeout
  271. # Results:
  272. # Returns a token for this connection. This token is the name of an
  273. # array that the caller should unset to garbage collect the state.
  274. proc http::geturl {url args} {
  275. variable http
  276. variable urlTypes
  277. variable defaultCharset
  278. variable defaultKeepalive
  279. variable strict
  280. # Initialize the state variable, an array. We'll return the name of this
  281. # array as the token for the transaction.
  282. if {![info exists http(uid)]} {
  283. set http(uid) 0
  284. }
  285. set token [namespace current]::[incr http(uid)]
  286. variable $token
  287. upvar 0 $token state
  288. reset $token
  289. # Process command options.
  290. array set state {
  291. -binary false
  292. -blocksize 8192
  293. -queryblocksize 8192
  294. -validate 0
  295. -headers {}
  296. -timeout 0
  297. -type application/x-www-form-urlencoded
  298. -queryprogress {}
  299. -protocol 1.1
  300. binary 0
  301. state connecting
  302. meta {}
  303. coding {}
  304. currentsize 0
  305. totalsize 0
  306. querylength 0
  307. queryoffset 0
  308. type text/html
  309. body {}
  310. status ""
  311. http ""
  312. connection close
  313. }
  314. set state(-keepalive) $defaultKeepalive
  315. set state(-strict) $strict
  316. # These flags have their types verified [Bug 811170]
  317. array set type {
  318. -binary boolean
  319. -blocksize integer
  320. -queryblocksize integer
  321. -strict boolean
  322. -timeout integer
  323. -validate boolean
  324. }
  325. set state(charset) $defaultCharset
  326. set options {
  327. -binary -blocksize -channel -command -handler -headers -keepalive
  328. -method -myaddr -progress -protocol -query -queryblocksize
  329. -querychannel -queryprogress -strict -timeout -type -validate
  330. }
  331. set usage [join [lsort $options] ", "]
  332. set options [string map {- ""} $options]
  333. set pat ^-(?:[join $options |])$
  334. foreach {flag value} $args {
  335. if {[regexp -- $pat $flag]} {
  336. # Validate numbers
  337. if {
  338. [info exists type($flag)] &&
  339. ![string is $type($flag) -strict $value]
  340. } {
  341. unset $token
  342. return -code error \
  343. "Bad value for $flag ($value), must be $type($flag)"
  344. }
  345. set state($flag) $value
  346. } else {
  347. unset $token
  348. return -code error "Unknown option $flag, can be: $usage"
  349. }
  350. }
  351. # Make sure -query and -querychannel aren't both specified
  352. set isQueryChannel [info exists state(-querychannel)]
  353. set isQuery [info exists state(-query)]
  354. if {$isQuery && $isQueryChannel} {
  355. unset $token
  356. return -code error "Can't combine -query and -querychannel options!"
  357. }
  358. # Validate URL, determine the server host and port, and check proxy case
  359. # Recognize user:pass@host URLs also, although we do not do anything with
  360. # that info yet.
  361. # URLs have basically four parts.
  362. # First, before the colon, is the protocol scheme (e.g. http)
  363. # Second, for HTTP-like protocols, is the authority
  364. # The authority is preceded by // and lasts up to (but not including)
  365. # the following / or ? and it identifies up to four parts, of which
  366. # only one, the host, is required (if an authority is present at all).
  367. # All other parts of the authority (user name, password, port number)
  368. # are optional.
  369. # Third is the resource name, which is split into two parts at a ?
  370. # The first part (from the single "/" up to "?") is the path, and the
  371. # second part (from that "?" up to "#") is the query. *HOWEVER*, we do
  372. # not need to separate them; we send the whole lot to the server.
  373. # Both, path and query are allowed to be missing, including their
  374. # delimiting character.
  375. # Fourth is the fragment identifier, which is everything after the first
  376. # "#" in the URL. The fragment identifier MUST NOT be sent to the server
  377. # and indeed, we don't bother to validate it (it could be an error to
  378. # pass it in here, but it's cheap to strip).
  379. #
  380. # An example of a URL that has all the parts:
  381. #
  382. # http://jschmoe:xyzzy@www.bogus.net:8000/foo/bar.tml?q=foo#changes
  383. #
  384. # The "http" is the protocol, the user is "jschmoe", the password is
  385. # "xyzzy", the host is "www.bogus.net", the port is "8000", the path is
  386. # "/foo/bar.tml", the query is "q=foo", and the fragment is "changes".
  387. #
  388. # Note that the RE actually combines the user and password parts, as
  389. # recommended in RFC 3986. Indeed, that RFC states that putting passwords
  390. # in URLs is a Really Bad Idea, something with which I would agree utterly.
  391. #
  392. # From a validation perspective, we need to ensure that the parts of the
  393. # URL that are going to the server are correctly encoded. This is only
  394. # done if $state(-strict) is true (inherited from $::http::strict).
  395. set URLmatcher {(?x) # this is _expanded_ syntax
  396. ^
  397. (?: (\w+) : ) ? # <protocol scheme>
  398. (?: //
  399. (?:
  400. (
  401. [^@/\#?]+ # <userinfo part of authority>
  402. ) @
  403. )?
  404. ( # <host part of authority>
  405. [^/:\#?]+ | # host name or IPv4 address
  406. \[ [^/\#?]+ \] # IPv6 address in square brackets
  407. )
  408. (?: : (\d+) )? # <port part of authority>
  409. )?
  410. ( [/\?] [^\#]*)? # <path> (including query)
  411. (?: \# (.*) )? # <fragment>
  412. $
  413. }
  414. # Phase one: parse
  415. if {![regexp -- $URLmatcher $url -> proto user host port srvurl]} {
  416. unset $token
  417. return -code error "Unsupported URL: $url"
  418. }
  419. # Phase two: validate
  420. set host [string trim $host {[]}]; # strip square brackets from IPv6 address
  421. if {$host eq ""} {
  422. # Caller has to provide a host name; we do not have a "default host"
  423. # that would enable us to handle relative URLs.
  424. unset $token
  425. return -code error "Missing host part: $url"
  426. # Note that we don't check the hostname for validity here; if it's
  427. # invalid, we'll simply fail to resolve it later on.
  428. }
  429. if {$port ne "" && $port > 65535} {
  430. unset $token
  431. return -code error "Invalid port number: $port"
  432. }
  433. # The user identification and resource identification parts of the URL can
  434. # have encoded characters in them; take care!
  435. if {$user ne ""} {
  436. # Check for validity according to RFC 3986, Appendix A
  437. set validityRE {(?xi)
  438. ^
  439. (?: [-\w.~!$&'()*+,;=:] | %[0-9a-f][0-9a-f] )+
  440. $
  441. }
  442. if {$state(-strict) && ![regexp -- $validityRE $user]} {
  443. unset $token
  444. # Provide a better error message in this error case
  445. if {[regexp {(?i)%(?![0-9a-f][0-9a-f]).?.?} $user bad]} {
  446. return -code error \
  447. "Illegal encoding character usage \"$bad\" in URL user"
  448. }
  449. return -code error "Illegal characters in URL user"
  450. }
  451. }
  452. if {$srvurl ne ""} {
  453. # RFC 3986 allows empty paths (not even a /), but servers
  454. # return 400 if the path in the HTTP request doesn't start
  455. # with / , so add it here if needed.
  456. if {[string index $srvurl 0] ne "/"} {
  457. set srvurl /$srvurl
  458. }
  459. # Check for validity according to RFC 3986, Appendix A
  460. set validityRE {(?xi)
  461. ^
  462. # Path part (already must start with / character)
  463. (?: [-\w.~!$&'()*+,;=:@/] | %[0-9a-f][0-9a-f] )*
  464. # Query part (optional, permits ? characters)
  465. (?: \? (?: [-\w.~!$&'()*+,;=:@/?] | %[0-9a-f][0-9a-f] )* )?
  466. $
  467. }
  468. if {$state(-strict) && ![regexp -- $validityRE $srvurl]} {
  469. unset $token
  470. # Provide a better error message in this error case
  471. if {[regexp {(?i)%(?![0-9a-f][0-9a-f])..} $srvurl bad]} {
  472. return -code error \
  473. "Illegal encoding character usage \"$bad\" in URL path"
  474. }
  475. return -code error "Illegal characters in URL path"
  476. }
  477. } else {
  478. set srvurl /
  479. }
  480. if {$proto eq ""} {
  481. set proto http
  482. }
  483. set lower [string tolower $proto]
  484. if {![info exists urlTypes($lower)]} {
  485. unset $token
  486. return -code error "Unsupported URL type \"$proto\""
  487. }
  488. set defport [lindex $urlTypes($lower) 0]
  489. set defcmd [lindex $urlTypes($lower) 1]
  490. if {$port eq ""} {
  491. set port $defport
  492. }
  493. if {![catch {$http(-proxyfilter) $host} proxy]} {
  494. set phost [lindex $proxy 0]
  495. set pport [lindex $proxy 1]
  496. }
  497. # OK, now reassemble into a full URL
  498. set url ${proto}://
  499. if {$user ne ""} {
  500. append url $user
  501. append url @
  502. }
  503. append url $host
  504. if {$port != $defport} {
  505. append url : $port
  506. }
  507. append url $srvurl
  508. # Don't append the fragment!
  509. set state(url) $url
  510. # If a timeout is specified we set up the after event and arrange for an
  511. # asynchronous socket connection.
  512. set sockopts [list -async]
  513. if {$state(-timeout) > 0} {
  514. set state(after) [after $state(-timeout) \
  515. [list http::reset $token timeout]]
  516. }
  517. # If we are using the proxy, we must pass in the full URL that includes
  518. # the server name.
  519. if {[info exists phost] && ($phost ne "")} {
  520. set srvurl $url
  521. set targetAddr [list $phost $pport]
  522. } else {
  523. set targetAddr [list $host $port]
  524. }
  525. # Proxy connections aren't shared among different hosts.
  526. set state(socketinfo) $host:$port
  527. # Save the accept types at this point to prevent a race condition. [Bug
  528. # c11a51c482]
  529. set state(accept-types) $http(-accept)
  530. # See if we are supposed to use a previously opened channel.
  531. if {$state(-keepalive)} {
  532. variable socketmap
  533. if {[info exists socketmap($state(socketinfo))]} {
  534. if {[catch {fconfigure $socketmap($state(socketinfo))}]} {
  535. Log "WARNING: socket for $state(socketinfo) was closed"
  536. unset socketmap($state(socketinfo))
  537. } else {
  538. set sock $socketmap($state(socketinfo))
  539. Log "reusing socket $sock for $state(socketinfo)"
  540. catch {fileevent $sock writable {}}
  541. catch {fileevent $sock readable {}}
  542. }
  543. }
  544. # don't automatically close this connection socket
  545. set state(connection) {}
  546. }
  547. if {![info exists sock]} {
  548. # Pass -myaddr directly to the socket command
  549. if {[info exists state(-myaddr)]} {
  550. lappend sockopts -myaddr $state(-myaddr)
  551. }
  552. if {[catch {eval $defcmd $sockopts $targetAddr} sock]} {
  553. # something went wrong while trying to establish the connection.
  554. # Clean up after events and such, but DON'T call the command
  555. # callback (if available) because we're going to throw an
  556. # exception from here instead.
  557. set state(sock) $sock
  558. Finish $token "" 1
  559. cleanup $token
  560. return -code error $sock
  561. }
  562. }
  563. set state(sock) $sock
  564. Log "Using $sock for $state(socketinfo)" \
  565. [expr {$state(-keepalive)?"keepalive":""}]
  566. if {$state(-keepalive)} {
  567. set socketmap($state(socketinfo)) $sock
  568. }
  569. if {![info exists phost]} {
  570. set phost ""
  571. }
  572. fileevent $sock writable [list http::Connect $token $proto $phost $srvurl]
  573. # Wait for the connection to complete.
  574. if {![info exists state(-command)]} {
  575. # geturl does EVERYTHING asynchronously, so if the user
  576. # calls it synchronously, we just do a wait here.
  577. http::wait $token
  578. if {![info exists state]} {
  579. # If we timed out then Finish has been called and the users
  580. # command callback may have cleaned up the token. If so we end up
  581. # here with nothing left to do.
  582. return $token
  583. } elseif {$state(status) eq "error"} {
  584. # Something went wrong while trying to establish the connection.
  585. # Clean up after events and such, but DON'T call the command
  586. # callback (if available) because we're going to throw an
  587. # exception from here instead.
  588. set err [lindex $state(error) 0]
  589. cleanup $token
  590. return -code error $err
  591. }
  592. }
  593. return $token
  594. }
  595. # http::Connected --
  596. #
  597. # Callback used when the connection to the HTTP server is actually
  598. # established.
  599. #
  600. # Arguments:
  601. # token State token.
  602. # proto What protocol (http, https, etc.) was used to connect.
  603. # phost Are we using keep-alive? Non-empty if yes.
  604. # srvurl Service-local URL that we're requesting
  605. # Results:
  606. # None.
  607. proc http::Connected {token proto phost srvurl} {
  608. variable http
  609. variable urlTypes
  610. variable $token
  611. upvar 0 $token state
  612. # Set back the variables needed here
  613. set sock $state(sock)
  614. set isQueryChannel [info exists state(-querychannel)]
  615. set isQuery [info exists state(-query)]
  616. set host [lindex [split $state(socketinfo) :] 0]
  617. set port [lindex [split $state(socketinfo) :] 1]
  618. set lower [string tolower $proto]
  619. set defport [lindex $urlTypes($lower) 0]
  620. # Send data in cr-lf format, but accept any line terminators
  621. fconfigure $sock -translation {auto crlf} -buffersize $state(-blocksize)
  622. # The following is disallowed in safe interpreters, but the socket is
  623. # already in non-blocking mode in that case.
  624. catch {fconfigure $sock -blocking off}
  625. set how GET
  626. if {$isQuery} {
  627. set state(querylength) [string length $state(-query)]
  628. if {$state(querylength) > 0} {
  629. set how POST
  630. set contDone 0
  631. } else {
  632. # There's no query data.
  633. unset state(-query)
  634. set isQuery 0
  635. }
  636. } elseif {$state(-validate)} {
  637. set how HEAD
  638. } elseif {$isQueryChannel} {
  639. set how POST
  640. # The query channel must be blocking for the async Write to
  641. # work properly.
  642. fconfigure $state(-querychannel) -blocking 1 -translation binary
  643. set contDone 0
  644. }
  645. if {[info exists state(-method)] && $state(-method) ne ""} {
  646. set how $state(-method)
  647. }
  648. # We cannot handle chunked encodings with -handler, so force HTTP/1.0
  649. # until we can manage this.
  650. if {[info exists state(-handler)]} {
  651. set state(-protocol) 1.0
  652. }
  653. set accept_types_seen 0
  654. if {[catch {
  655. puts $sock "$how $srvurl HTTP/$state(-protocol)"
  656. if {[dict exists $state(-headers) Host]} {
  657. # Allow Host spoofing. [Bug 928154]
  658. puts $sock "Host: [dict get $state(-headers) Host]"
  659. } elseif {$port == $defport} {
  660. # Don't add port in this case, to handle broken servers. [Bug
  661. # #504508]
  662. puts $sock "Host: $host"
  663. } else {
  664. puts $sock "Host: $host:$port"
  665. }
  666. puts $sock "User-Agent: $http(-useragent)"
  667. if {$state(-protocol) == 1.0 && $state(-keepalive)} {
  668. puts $sock "Connection: keep-alive"
  669. }
  670. if {$state(-protocol) > 1.0 && !$state(-keepalive)} {
  671. puts $sock "Connection: close" ;# RFC2616 sec 8.1.2.1
  672. }
  673. if {[info exists phost] && ($phost ne "") && $state(-keepalive)} {
  674. puts $sock "Proxy-Connection: Keep-Alive"
  675. }
  676. set accept_encoding_seen 0
  677. set content_type_seen 0
  678. dict for {key value} $state(-headers) {
  679. set value [string map [list \n "" \r ""] $value]
  680. set key [string map {" " -} [string trim $key]]
  681. if {[string equal -nocase $key "host"]} {
  682. continue
  683. }
  684. if {[string equal -nocase $key "accept-encoding"]} {
  685. set accept_encoding_seen 1
  686. }
  687. if {[string equal -nocase $key "accept"]} {
  688. set accept_types_seen 1
  689. }
  690. if {[string equal -nocase $key "content-type"]} {
  691. set content_type_seen 1
  692. }
  693. if {[string equal -nocase $key "content-length"]} {
  694. set contDone 1
  695. set state(querylength) $value
  696. }
  697. if {[string length $key]} {
  698. puts $sock "$key: $value"
  699. }
  700. }
  701. # Allow overriding the Accept header on a per-connection basis. Useful
  702. # for working with REST services. [Bug c11a51c482]
  703. if {!$accept_types_seen} {
  704. puts $sock "Accept: $state(accept-types)"
  705. }
  706. if {!$accept_encoding_seen && ![info exists state(-handler)]} {
  707. puts $sock "Accept-Encoding: gzip,deflate,compress"
  708. }
  709. if {$isQueryChannel && $state(querylength) == 0} {
  710. # Try to determine size of data in channel. If we cannot seek, the
  711. # surrounding catch will trap us
  712. set start [tell $state(-querychannel)]
  713. seek $state(-querychannel) 0 end
  714. set state(querylength) \
  715. [expr {[tell $state(-querychannel)] - $start}]
  716. seek $state(-querychannel) $start
  717. }
  718. # Flush the request header and set up the fileevent that will either
  719. # push the POST data or read the response.
  720. #
  721. # fileevent note:
  722. #
  723. # It is possible to have both the read and write fileevents active at
  724. # this point. The only scenario it seems to affect is a server that
  725. # closes the connection without reading the POST data. (e.g., early
  726. # versions TclHttpd in various error cases). Depending on the
  727. # platform, the client may or may not be able to get the response from
  728. # the server because of the error it will get trying to write the post
  729. # data. Having both fileevents active changes the timing and the
  730. # behavior, but no two platforms (among Solaris, Linux, and NT) behave
  731. # the same, and none behave all that well in any case. Servers should
  732. # always read their POST data if they expect the client to read their
  733. # response.
  734. if {$isQuery || $isQueryChannel} {
  735. if {!$content_type_seen} {
  736. puts $sock "Content-Type: $state(-type)"
  737. }
  738. if {!$contDone} {
  739. puts $sock "Content-Length: $state(querylength)"
  740. }
  741. puts $sock ""
  742. fconfigure $sock -translation {auto binary}
  743. fileevent $sock writable [list http::Write $token]
  744. } else {
  745. puts $sock ""
  746. flush $sock
  747. fileevent $sock readable [list http::Event $sock $token]
  748. }
  749. } err]} {
  750. # The socket probably was never connected, or the connection dropped
  751. # later.
  752. # if state(status) is error, it means someone's already called Finish
  753. # to do the above-described clean up.
  754. if {$state(status) ne "error"} {
  755. Finish $token $err
  756. }
  757. }
  758. }
  759. # Data access functions:
  760. # Data - the URL data
  761. # Status - the transaction status: ok, reset, eof, timeout
  762. # Code - the HTTP transaction code, e.g., 200
  763. # Size - the size of the URL data
  764. proc http::data {token} {
  765. variable $token
  766. upvar 0 $token state
  767. return $state(body)
  768. }
  769. proc http::status {token} {
  770. if {![info exists $token]} {
  771. return "error"
  772. }
  773. variable $token
  774. upvar 0 $token state
  775. return $state(status)
  776. }
  777. proc http::code {token} {
  778. variable $token
  779. upvar 0 $token state
  780. return $state(http)
  781. }
  782. proc http::ncode {token} {
  783. variable $token
  784. upvar 0 $token state
  785. if {[regexp {[0-9]{3}} $state(http) numeric_code]} {
  786. return $numeric_code
  787. } else {
  788. return $state(http)
  789. }
  790. }
  791. proc http::size {token} {
  792. variable $token
  793. upvar 0 $token state
  794. return $state(currentsize)
  795. }
  796. proc http::meta {token} {
  797. variable $token
  798. upvar 0 $token state
  799. return $state(meta)
  800. }
  801. proc http::error {token} {
  802. variable $token
  803. upvar 0 $token state
  804. if {[info exists state(error)]} {
  805. return $state(error)
  806. }
  807. return ""
  808. }
  809. # http::cleanup
  810. #
  811. # Garbage collect the state associated with a transaction
  812. #
  813. # Arguments
  814. # token The token returned from http::geturl
  815. #
  816. # Side Effects
  817. # unsets the state array
  818. proc http::cleanup {token} {
  819. variable $token
  820. upvar 0 $token state
  821. if {[info exists state]} {
  822. unset state
  823. }
  824. }
  825. # http::Connect
  826. #
  827. # This callback is made when an asyncronous connection completes.
  828. #
  829. # Arguments
  830. # token The token returned from http::geturl
  831. #
  832. # Side Effects
  833. # Sets the status of the connection, which unblocks
  834. # the waiting geturl call
  835. proc http::Connect {token proto phost srvurl} {
  836. variable $token
  837. upvar 0 $token state
  838. set err "due to unexpected EOF"
  839. if {
  840. [eof $state(sock)] ||
  841. [set err [fconfigure $state(sock) -error]] ne ""
  842. } {
  843. Finish $token "connect failed $err"
  844. } else {
  845. fileevent $state(sock) writable {}
  846. ::http::Connected $token $proto $phost $srvurl
  847. }
  848. return
  849. }
  850. # http::Write
  851. #
  852. # Write POST query data to the socket
  853. #
  854. # Arguments
  855. # token The token for the connection
  856. #
  857. # Side Effects
  858. # Write the socket and handle callbacks.
  859. proc http::Write {token} {
  860. variable $token
  861. upvar 0 $token state
  862. set sock $state(sock)
  863. # Output a block. Tcl will buffer this if the socket blocks
  864. set done 0
  865. if {[catch {
  866. # Catch I/O errors on dead sockets
  867. if {[info exists state(-query)]} {
  868. # Chop up large query strings so queryprogress callback can give
  869. # smooth feedback.
  870. puts -nonewline $sock \
  871. [string range $state(-query) $state(queryoffset) \
  872. [expr {$state(queryoffset) + $state(-queryblocksize) - 1}]]
  873. incr state(queryoffset) $state(-queryblocksize)
  874. if {$state(queryoffset) >= $state(querylength)} {
  875. set state(queryoffset) $state(querylength)
  876. set done 1
  877. }
  878. } else {
  879. # Copy blocks from the query channel
  880. set outStr [read $state(-querychannel) $state(-queryblocksize)]
  881. puts -nonewline $sock $outStr
  882. incr state(queryoffset) [string length $outStr]
  883. if {[eof $state(-querychannel)]} {
  884. set done 1
  885. }
  886. }
  887. } err]} {
  888. # Do not call Finish here, but instead let the read half of the socket
  889. # process whatever server reply there is to get.
  890. set state(posterror) $err
  891. set done 1
  892. }
  893. if {$done} {
  894. catch {flush $sock}
  895. fileevent $sock writable {}
  896. fileevent $sock readable [list http::Event $sock $token]
  897. }
  898. # Callback to the client after we've completely handled everything.
  899. if {[string length $state(-queryprogress)]} {
  900. eval $state(-queryprogress) \
  901. [list $token $state(querylength) $state(queryoffset)]
  902. }
  903. }
  904. # http::Event
  905. #
  906. # Handle input on the socket
  907. #
  908. # Arguments
  909. # sock The socket receiving input.
  910. # token The token returned from http::geturl
  911. #
  912. # Side Effects
  913. # Read the socket and handle callbacks.
  914. proc http::Event {sock token} {
  915. variable $token
  916. upvar 0 $token state
  917. if {![info exists state]} {
  918. Log "Event $sock with invalid token '$token' - remote close?"
  919. if {![eof $sock]} {
  920. if {[set d [read $sock]] ne ""} {
  921. Log "WARNING: additional data left on closed socket"
  922. }
  923. }
  924. CloseSocket $sock
  925. return
  926. }
  927. if {$state(state) eq "connecting"} {
  928. if {[catch {gets $sock state(http)} n]} {
  929. return [Finish $token $n]
  930. } elseif {$n >= 0} {
  931. set state(state) "header"
  932. }
  933. } elseif {$state(state) eq "header"} {
  934. if {[catch {gets $sock line} n]} {
  935. return [Finish $token $n]
  936. } elseif {$n == 0} {
  937. # We have now read all headers
  938. # We ignore HTTP/1.1 100 Continue returns. RFC2616 sec 8.2.3
  939. if {$state(http) == "" || ([regexp {^\S+\s(\d+)} $state(http) {} x] && $x == 100)} {
  940. return
  941. }
  942. set state(state) body
  943. # If doing a HEAD, then we won't get any body
  944. if {$state(-validate)} {
  945. Eof $token
  946. return
  947. }
  948. # For non-chunked transfer we may have no body - in this case we
  949. # may get no further file event if the connection doesn't close
  950. # and no more data is sent. We can tell and must finish up now -
  951. # not later.
  952. if {
  953. !(([info exists state(connection)]
  954. && ($state(connection) eq "close"))
  955. || [info exists state(transfer)])
  956. && ($state(totalsize) == 0)
  957. } {
  958. Log "body size is 0 and no events likely - complete."
  959. Eof $token
  960. return
  961. }
  962. # We have to use binary translation to count bytes properly.
  963. fconfigure $sock -translation binary
  964. if {
  965. $state(-binary) || ![string match -nocase text* $state(type)]
  966. } {
  967. # Turn off conversions for non-text data
  968. set state(binary) 1
  969. }
  970. if {[info exists state(-channel)]} {
  971. if {$state(binary) || [llength [ContentEncoding $token]]} {
  972. fconfigure $state(-channel) -translation binary
  973. }
  974. if {![info exists state(-handler)]} {
  975. # Initiate a sequence of background fcopies
  976. fileevent $sock readable {}
  977. CopyStart $sock $token
  978. return
  979. }
  980. }
  981. } elseif {$n > 0} {
  982. # Process header lines
  983. if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} {
  984. switch -- [string tolower $key] {
  985. content-type {
  986. set state(type) [string trim [string tolower $value]]
  987. # grab the optional charset information
  988. if {[regexp -nocase \
  989. {charset\s*=\s*\"((?:[^""]|\\\")*)\"} \
  990. $state(type) -> cs]} {
  991. set state(charset) [string map {{\"} \"} $cs]
  992. } else {
  993. regexp -nocase {charset\s*=\s*(\S+?);?} \
  994. $state(type) -> state(charset)
  995. }
  996. }
  997. content-length {
  998. set state(totalsize) [string trim $value]
  999. }
  1000. content-encoding {
  1001. set state(coding) [string trim $value]
  1002. }
  1003. transfer-encoding {
  1004. set state(transfer) \
  1005. [string trim [string tolower $value]]
  1006. }
  1007. proxy-connection -
  1008. connection {
  1009. set state(connection) \
  1010. [string trim [string tolower $value]]
  1011. }
  1012. }
  1013. lappend state(meta) $key [string trim $value]
  1014. }
  1015. }
  1016. } else {
  1017. # Now reading body
  1018. if {[catch {
  1019. if {[info exists state(-handler)]} {
  1020. set n [eval $state(-handler) [list $sock $token]]
  1021. } elseif {[info exists state(transfer_final)]} {
  1022. set line [getTextLine $sock]
  1023. set n [string length $line]
  1024. if {$n > 0} {
  1025. Log "found $n bytes following final chunk"
  1026. append state(transfer_final) $line
  1027. } else {
  1028. Log "final chunk part"
  1029. Eof $token
  1030. }
  1031. } elseif {
  1032. [info exists state(transfer)]
  1033. && $state(transfer) eq "chunked"
  1034. } {
  1035. set size 0
  1036. set chunk [getTextLine $sock]
  1037. set n [string length $chunk]
  1038. if {[string trim $chunk] ne ""} {
  1039. scan $chunk %x size
  1040. if {$size != 0} {
  1041. set bl [fconfigure $sock -blocking]
  1042. fconfigure $sock -blocking 1
  1043. set chunk [read $sock $size]
  1044. fconfigure $sock -blocking $bl
  1045. set n [string length $chunk]
  1046. if {$n >= 0} {
  1047. append state(body) $chunk
  1048. }
  1049. if {$size != [string length $chunk]} {
  1050. Log "WARNING: mis-sized chunk:\
  1051. was [string length $chunk], should be $size"
  1052. }
  1053. getTextLine $sock
  1054. } else {
  1055. set state(transfer_final) {}
  1056. }
  1057. }
  1058. } else {
  1059. #Log "read non-chunk $state(currentsize) of $state(totalsize)"
  1060. set block [read $sock $state(-blocksize)]
  1061. set n [string length $block]
  1062. if {$n >= 0} {
  1063. append state(body) $block
  1064. }
  1065. }
  1066. if {[info exists state]} {
  1067. if {$n >= 0} {
  1068. incr state(currentsize) $n
  1069. }
  1070. # If Content-Length - check for end of data.
  1071. if {
  1072. ($state(totalsize) > 0)
  1073. && ($state(currentsize) >= $state(totalsize))
  1074. } {
  1075. Eof $token
  1076. }
  1077. }
  1078. } err]} {
  1079. return [Finish $token $err]
  1080. } else {
  1081. if {[info exists state(-progress)]} {
  1082. eval $state(-progress) \
  1083. [list $token $state(totalsize) $state(currentsize)]
  1084. }
  1085. }
  1086. }
  1087. # catch as an Eof above may have closed the socket already
  1088. if {![catch {eof $sock} eof] && $eof} {
  1089. if {[info exists $token]} {
  1090. set state(connection) close
  1091. Eof $token
  1092. } else {
  1093. # open connection closed on a token that has been cleaned up.
  1094. CloseSocket $sock
  1095. }
  1096. return
  1097. }
  1098. }
  1099. # http::getTextLine --
  1100. #
  1101. # Get one line with the stream in blocking crlf mode
  1102. #
  1103. # Arguments
  1104. # sock The socket receiving input.
  1105. #
  1106. # Results:
  1107. # The line of text, without trailing newline
  1108. proc http::getTextLine {sock} {
  1109. set tr [fconfigure $sock -translation]
  1110. set bl [fconfigure $sock -blocking]
  1111. fconfigure $sock -translation crlf -blocking 1
  1112. set r [gets $sock]
  1113. fconfigure $sock -translation $tr -blocking $bl
  1114. return $r
  1115. }
  1116. # http::CopyStart
  1117. #
  1118. # Error handling wrapper around fcopy
  1119. #
  1120. # Arguments
  1121. # sock The socket to copy from
  1122. # token The token returned from http::geturl
  1123. #
  1124. # Side Effects
  1125. # This closes the connection upon error
  1126. proc http::CopyStart {sock token {initial 1}} {
  1127. upvar #0 $token state
  1128. if {[info exists state(transfer)] && $state(transfer) eq "chunked"} {
  1129. foreach coding [ContentEncoding $token] {
  1130. lappend state(zlib) [zlib stream $coding]
  1131. }
  1132. make-transformation-chunked $sock [namespace code [list CopyChunk $token]]
  1133. } else {
  1134. if {$initial} {
  1135. foreach coding [ContentEncoding $token] {
  1136. zlib push $coding $sock
  1137. }
  1138. }
  1139. if {[catch {
  1140. fcopy $sock $state(-channel) -size $state(-blocksize) -command \
  1141. [list http::CopyDone $token]
  1142. } err]} {
  1143. Finish $token $err
  1144. }
  1145. }
  1146. }
  1147. proc http::CopyChunk {token chunk} {
  1148. upvar 0 $token state
  1149. if {[set count [string length $chunk]]} {
  1150. incr state(currentsize) $count
  1151. if {[info exists state(zlib)]} {
  1152. foreach stream $state(zlib) {
  1153. set chunk [$stream add $chunk]
  1154. }
  1155. }
  1156. puts -nonewline $state(-channel) $chunk
  1157. if {[info exists state(-progress)]} {
  1158. eval [linsert $state(-progress) end \
  1159. $token $state(totalsize) $state(currentsize)]
  1160. }
  1161. } else {
  1162. Log "CopyChunk Finish $token"
  1163. if {[info exists state(zlib)]} {
  1164. set excess ""
  1165. foreach stream $state(zlib) {
  1166. catch {set excess [$stream add -finalize $excess]}
  1167. }
  1168. puts -nonewline $state(-channel) $excess
  1169. foreach stream $state(zlib) { $stream close }
  1170. unset state(zlib)
  1171. }
  1172. Eof $token ;# FIX ME: pipelining.
  1173. }
  1174. }
  1175. # http::CopyDone
  1176. #
  1177. # fcopy completion callback
  1178. #
  1179. # Arguments
  1180. # token The token returned from http::geturl
  1181. # count The amount transfered
  1182. #
  1183. # Side Effects
  1184. # Invokes callbacks
  1185. proc http::CopyDone {token count {error {}}} {
  1186. variable $token
  1187. upvar 0 $token state
  1188. set sock $state(sock)
  1189. incr state(currentsize) $count
  1190. if {[info exists state(-progress)]} {
  1191. eval $state(-progress) \
  1192. [list $token $state(totalsize) $state(currentsize)]
  1193. }
  1194. # At this point the token may have been reset
  1195. if {[string length $error]} {
  1196. Finish $token $error
  1197. } elseif {[catch {eof $sock} iseof] || $iseof} {
  1198. Eof $token
  1199. } else {
  1200. CopyStart $sock $token 0
  1201. }
  1202. }
  1203. # http::Eof
  1204. #
  1205. # Handle eof on the socket
  1206. #
  1207. # Arguments
  1208. # token The token returned from http::geturl
  1209. #
  1210. # Side Effects
  1211. # Clean up the socket
  1212. proc http::Eof {token {force 0}} {
  1213. variable $token
  1214. upvar 0 $token state
  1215. if {$state(state) eq "header"} {
  1216. # Premature eof
  1217. set state(status) eof
  1218. } else {
  1219. set state(status) ok
  1220. }
  1221. if {[string length $state(body)] > 0} {
  1222. if {[catch {
  1223. foreach coding [ContentEncoding $token] {
  1224. set state(body) [zlib $coding $state(body)]
  1225. }
  1226. } err]} {
  1227. Log "error doing decompression: $err"
  1228. return [Finish $token $err]
  1229. }
  1230. if {!$state(binary)} {
  1231. # If we are getting text, set the incoming channel's encoding
  1232. # correctly. iso8859-1 is the RFC default, but this could be any IANA
  1233. # charset. However, we only know how to convert what we have
  1234. # encodings for.
  1235. set enc [CharsetToEncoding $state(charset)]
  1236. if {$enc ne "binary"} {
  1237. set state(body) [encoding convertfrom $enc $state(body)]
  1238. }
  1239. # Translate text line endings.
  1240. set state(body) [string map {\r\n \n \r \n} $state(body)]
  1241. }
  1242. }
  1243. Finish $token
  1244. }
  1245. # http::wait --
  1246. #
  1247. # See documentation for details.
  1248. #
  1249. # Arguments:
  1250. # token Connection token.
  1251. #
  1252. # Results:
  1253. # The status after the wait.
  1254. proc http::wait {token} {
  1255. variable $token
  1256. upvar 0 $token state
  1257. if {![info exists state(status)] || $state(status) eq ""} {
  1258. # We must wait on the original variable name, not the upvar alias
  1259. vwait ${token}(status)
  1260. }
  1261. return [status $token]
  1262. }
  1263. # http::formatQuery --
  1264. #
  1265. # See documentation for details. Call http::formatQuery with an even
  1266. # number of arguments, where the first is a name, the second is a value,
  1267. # the third is another name, and so on.
  1268. #
  1269. # Arguments:
  1270. # args A list of name-value pairs.
  1271. #
  1272. # Results:
  1273. # TODO
  1274. proc http::formatQuery {args} {
  1275. set result ""
  1276. set sep ""
  1277. foreach i $args {
  1278. append result $sep [mapReply $i]
  1279. if {$sep eq "="} {
  1280. set sep &
  1281. } else {
  1282. set sep =
  1283. }
  1284. }
  1285. return $result
  1286. }
  1287. # http::mapReply --
  1288. #
  1289. # Do x-www-urlencoded character mapping
  1290. #
  1291. # Arguments:
  1292. # string The string the needs to be encoded
  1293. #
  1294. # Results:
  1295. # The encoded string
  1296. proc http::mapReply {string} {
  1297. variable http
  1298. variable formMap
  1299. # The spec says: "non-alphanumeric characters are replaced by '%HH'". Use
  1300. # a pre-computed map and [string map] to do the conversion (much faster
  1301. # than [regsub]/[subst]). [Bug 1020491]
  1302. if {$http(-urlencoding) ne ""} {
  1303. set string [encoding convertto $http(-urlencoding) $string]
  1304. return [string map $formMap $string]
  1305. }
  1306. set converted [string map $formMap $string]
  1307. if {[string match "*\[\u0100-\uffff\]*" $converted]} {
  1308. regexp "\[\u0100-\uffff\]" $converted badChar
  1309. # Return this error message for maximum compatability... :^/
  1310. return -code error \
  1311. "can't read \"formMap($badChar)\": no such element in array"
  1312. }
  1313. return $converted
  1314. }
  1315. # http::ProxyRequired --
  1316. # Default proxy filter.
  1317. #
  1318. # Arguments:
  1319. # host The destination host
  1320. #
  1321. # Results:
  1322. # The current proxy settings
  1323. proc http::ProxyRequired {host} {
  1324. variable http
  1325. if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} {
  1326. if {
  1327. ![info exists http(-proxyport)] ||
  1328. ![string length $http(-proxyport)]
  1329. } {
  1330. set http(-proxyport) 8080
  1331. }
  1332. return [list $http(-proxyhost) $http(-proxyport)]
  1333. }
  1334. }
  1335. # http::CharsetToEncoding --
  1336. #
  1337. # Tries to map a given IANA charset to a tcl encoding. If no encoding
  1338. # can be found, returns binary.
  1339. #
  1340. proc http::CharsetToEncoding {charset} {
  1341. variable encodings
  1342. set charset [string tolower $charset]
  1343. if {[regexp {iso-?8859-([0-9]+)} $charset -> num]} {
  1344. set encoding "iso8859-$num"
  1345. } elseif {[regexp {iso-?2022-(jp|kr)} $charset -> ext]} {
  1346. set encoding "iso2022-$ext"
  1347. } elseif {[regexp {shift[-_]?js} $charset]} {
  1348. set encoding "shiftjis"
  1349. } elseif {[regexp {(?:windows|cp)-?([0-9]+)} $charset -> num]} {
  1350. set encoding "cp$num"
  1351. } elseif {$charset eq "us-ascii"} {
  1352. set encoding "ascii"
  1353. } elseif {[regexp {(?:iso-?)?lat(?:in)?-?([0-9]+)} $charset -> num]} {
  1354. switch -- $num {
  1355. 5 {set encoding "iso8859-9"}
  1356. 1 - 2 - 3 {
  1357. set encoding "iso8859-$num"
  1358. }
  1359. }
  1360. } else {
  1361. # other charset, like euc-xx, utf-8,... may directly map to encoding
  1362. set encoding $charset
  1363. }
  1364. set idx [lsearch -exact $encodings $encoding]
  1365. if {$idx >= 0} {
  1366. return $encoding
  1367. } else {
  1368. return "binary"
  1369. }
  1370. }
  1371. # Return the list of content-encoding transformations we need to do in order.
  1372. proc http::ContentEncoding {token} {
  1373. upvar 0 $token state
  1374. set r {}
  1375. if {[info exists state(coding)]} {
  1376. foreach coding [split $state(coding) ,] {
  1377. switch -exact -- $coding {
  1378. deflate { lappend r inflate }
  1379. gzip - x-gzip { lappend r gunzip }
  1380. compress - x-compress { lappend r decompress }
  1381. identity {}
  1382. default {
  1383. return -code error "unsupported content-encoding \"$coding\""
  1384. }
  1385. }
  1386. }
  1387. }
  1388. return $r
  1389. }
  1390. proc http::make-transformation-chunked {chan command} {
  1391. set lambda {{chan command} {
  1392. set data ""
  1393. set size -1
  1394. yield
  1395. while {1} {
  1396. chan configure $chan -translation {crlf binary}
  1397. while {[gets $chan line] < 1} { yield }
  1398. chan configure $chan -translation {binary binary}
  1399. if {[scan $line %x size] != 1} { return -code error "invalid size: \"$line\"" }
  1400. set chunk ""
  1401. while {$size && ![chan eof $chan]} {
  1402. set part [chan read $chan $size]
  1403. incr size -[string length $part]
  1404. append chunk $part
  1405. }
  1406. if {[catch {
  1407. uplevel #0 [linsert $command end $chunk]
  1408. }]} {
  1409. http::Log "Error in callback: $::errorInfo"
  1410. }
  1411. if {[string length $chunk] == 0} {
  1412. # channel might have been closed in the callback
  1413. catch {chan event $chan readable {}}
  1414. return
  1415. }
  1416. }
  1417. }}
  1418. coroutine dechunk$chan ::apply $lambda $chan $command
  1419. chan event $chan readable [namespace origin dechunk$chan]
  1420. return
  1421. }
  1422. # Local variables:
  1423. # indent-tabs-mode: t
  1424. # End: