Click here to show or hide the menubar.

Allow data to be an address, if so we don't make a copy of it.
The code that checked to see if we're running on the "samemachine" wasn't checking if server was 127.0.0.1, and was only checking the port on user.inetd.config.http, when there may be more listeners running on different ports, as there are in the OPML Editor. It would only shortcut of it was localhost and if it was running on one of the daemons.
When testing for proxy-exempt servers, don't inclue the port number if specified in the server parameter.
Respect user.webBrowser.proxy.exceptions.
Removed debugging code.
If server is 127.0.0.1 or "localhost", set flUseProxy to false.
New optional param -- adrRedirectInfo. If it's non-nil (the default) and we're redirecting, we fill the table with info about the redirect. The RSS aggregator needs this information, and it may prove useful in other places. See xml.rss.readService for an example of its usage.
Added a call to webBrowser.init, which initializes prefs at user.webBrowser.proxy. Added optional parameter, flUseProxy, which defaults to nil. If nil, then the value at user.webBrowser.proxy.enabled is used. Pass the value of flUseProxy when following redirects.
When recursing to following redirects, pass in the flJustHeaders and flAcceptOpml parameters.
If the stream is unexpectedly closed by the remote host, call tcp.abortStream, to avoid consuming the available connections.
If no Accept header has been passed in, add an Accept header with the value: text/x-opml, */*, so that we get the response in opml instead of html.
Trim white space around the content-length header before coercing it to a number. If a server adds white space here, the coercion would fail, resulting in a connection counter leak. (History: this was discovered in Radio when reading the Red Herring channel.)
Add an optional parameter, flJustHeaders, if true, we don't read the body, we just return the header portion of the response. This is useful if you just want to know how big a download is, and want to defer downloading big things until the middle of the night when it's quiet on the Internet.
In Radio, set User-Agent to Radio UserLand.
Changed the header so it doesn't use backslashes. The outliner now supports wrapping headlines.
Respect the user's offline settings.
If the server doesn't send a Content-Length header, read until connection closes.
Adapted to use new TCP verbs in 6.1.
If flMessages is true, we now clean up the message area before we return.
Both the read and write loop now yield time to other Frontier threads and to the system.
The Host header is now generated correctly if the request goes thru a proxy server.
Fixed a few inconsistencies in the cookie handling code. Thinks should now work correctly when connecting via a proxy server.
Added an optional parameter, ctFollowRedirects, determining the number of times that HTTP redirects will be followed. In order to preserve previous behaviour, the default value is 0.
If adrHdrTable^ contains a "Host" sub-item, don't add a Host header item.
if adrHdrTable^ contains a "User-Agent" sub-item, don't add a User-Agent header item
example: www.scripting.com:80
the assumption is that most callers either specify 80 or don't specify the port at all
for crawlers, for example, they definitely want to use the port specified in the server string
some commands, esp in the webBrowser class, get the url from another piece of software
if we're sending to the same machine, set the stream ID to 0
user.webserver.responders.CGI.methods.any is copying it, hope no CGIs are using it!
msgs now say that they're from tcp.httpClient.
Can't process the request because the connection to xxx timed out.
allow server to be "localhost", if so, we totally optimize the functionality
Initial code assembled from various sample scripts by Dave Winer.
The goal is to have a single documented way to talk to HTTP servers.
It's got a long parameter list, but requires no configuration, doesn't run off a user.xxx table.
The target user of this script is someone who needs to understand how the code works.
It will be distributed with Frontier on both platforms as soon as we agree that this is complete and works.
When you call it, use parameter labels, don't depend on the order of these params.
For examples, see tcp.examples.
local (adrdata)
scriptError ("Can't send HTTP request because TCP is offline.")
adrdata = data
adrdata = @data
adrdata = @data
webBrowser.init ()
flUseProxy = user.webBrowser.proxy.enabled
flUseProxy = false
local (i)
return (true)
local (part = string.nthField (oneip, ".", i))
local (pattern = string.nthField (s, ".", i))
continue
local (min = number (string.nthField (pattern, "-", 1)) )
local (max = number (string.nthField (pattern, "-", 2)) )
local (n = number (part))
continue
return (false)
return (false)
return (false)
return (true)
local (flIpAddress = false, hostToCheck = string.lower (string.nthField (server, ":", 1)) )
local (serverWithoutDots = string.replaceAll (server, ".", ""))
double (serverWithoutDots) //will error if non-numeric characters are present
flIpAddress = true
local (adr)
local (lowerserver = string.lower (server))
hostToCheck = tcp.dns.getDottedId (server)
flIpAddress = true
local (i)
local (oneexception = nameOf (user.webBrowser.proxy.exceptions[i]))
flUseProxy = false
break
msg ("tcp.httpClient: " + s)
local (myAddress = tcp.myDottedID (), flSameMachine = false)
try {port = number (string.nthField (server, ":", 2))} //it might not be a number
server = string.nthField (server, ":", 1)
port = 80
local (lowerserver = string.lower (server))
flSameMachine = true
try {flSameMachine = tcp.equalNames (server, myAddress)} //DW 3/9/98
local (adrd, flfound = false)
flsamemachine = false
PBS 9/18/98: check that the server on the requested port is Frontier.
flSameMachine = false //it's the same machine, but it's not Frontier as a server
flSameMachine = false //Frontier isn't running as a webserver, there must be another server on this machine
path = "/" + path
local (httpCommand = "", adrCookiesTable = @user.webBrowser.cookies)
httpCommand = httpCommand + s + "\r\n"
local (uri = path)
uri = "http://" + server
uri = uri + ":" + port
uri = uri + path
add (method + " " + uri + " HTTP/1.0")
add ("User-Agent: Radio UserLand/" + Frontier.version () + " (" + sys.os () + ")")
add ("User-Agent: Frontier/" + Frontier.version () + " (" + sys.os () + ")")
local (host = "Host: " + server)
host = host + ":" + port
add (host)
add ("Accept: text/x-opml, */*")
local (acceptHeader = adrHdrTable^.Accept)
acceptHeader = "text/x-opml, " + acceptHeader
acceptHeader = "text/x-opml"
acceptHeader = acceptHeader + ", */*"
add ("Accept: " + acceptHeader)
add ("Accept: text/x-opml, */*")
add ("Content-Type: " + datatype)
add ("Content-length: " + sizeOf (adrdata^))
add ("Authorization: Basic " + base64.encode (username + ":" + password, 0))
add ("Proxy-Authorization: Basic " + base64.encode (proxyUsername + ":" + proxyPassword, 0))
local (i)
local (headerName = nameOf (adrHdrTable^[i]))
add (headerName + ": " + adrHdrTable^[i])
local (i, j, k, cookiePath = "/", cookiestring)
cookiePath = string.popSuffix (path, "/")
local (adrdomain = @adrCookiesTable^ [i])
local (adrcookie = @adrdomain^ [j])
cookiestring = cookiestring + nameOf (adrcookie^ [k]) + "=" + adrcookie^ [k].value + "; "
delete (@adrcookie^ [k]) //this cookie has expired so delete it
cookiestring = string.delete (cookiestring, sizeof (cookiestring) - 1, 2) //delete trailing "; "
add ("Cookie: " + cookiestring) //add the cookies to the header
add ("") //blank line
httpCommand = httpCommand + adrdata^
wp.newTextObject (httpCommand, @scratchpad.httpCommand)
local (httpResult = "")
local (params)
new (tabletype, @params)
params.client = myAddress
params.port = port
params.stream = 0 //9/28/98; 4:42:45 AM by DW
params.request = httpCommand
httpResult = webserver.server (@params, httpCommand)
local (serverAddress = server, serverPort = port)
serverAddress = proxy
serverPort = proxyPort
displayMsg ("Connecting to " + serverAddress + ".")
local (stream = tcp.openStream (serverAddress, serverPort))
local (chunksize = 5 * 1024)
displayMsg ("Sending request to " + serverAddress + ".")
tcp.writeStringToStream (stream, httpCommand, chunkSize, timeOutTicks/60) //write the command to the server
tcp.abortStream (stream)
scriptError (tryError)
displayMsg ("Waiting for data from " + serverAddress + ".")
local (pat = "\r\n\r\n")
tcp.readStreamUntil (stream, pat, timeOutTicks/60, @httpResult)
tcp.abortStream (stream)
scriptError (tryerror)
local (headerTable)
webserver.util.parseHeaders (httpResult, @headerTable)
tcp.closeStream (stream)
local (lenHeader = string.patternMatch (pat, httpResult) - 1)
local (lenContent = number (string.trimWhiteSpace (headerTable.["Content-Length"]))) //PBS 03/01/01: trim white space before coercing
local (lenTotal = lenHeader + string.length (pat) + lenContent)
displayMsg ("Receiving response body (" + string.kBytes (lenContent) + ") from " + serverAddress + ".")
tcp.readStreamBytes (stream, lenTotal, timeOutTicks/60, @httpResult)
tcp.abortStream (stream)
scriptError (tryerror)
displayMsg ("Receiving response from " + serverAddress + ".")
tcp.readStreamUntilClosed (stream, timeOutTicks/60, @httpResult)
tcp.abortStream (stream)
scriptError (tryerror)
tcp.closeStream (stream)
displayMsg ("Finished receiving " + string.kBytes (string.length (httpResult)) + " from " + serverAddress + ".")
local (ixEndHeader, responseHeader, i, j)
ixEndHeader = string.patternMatch ("\r\n\r\n", httpResult) //try to find the end of the response header
responseHeader = string.mid (httpResult, 1, ixEndHeader - 1) //get the header
new (tableType, adrCookiesTable)
local (onecookie = string.nthField (responseHeader, "\r", i)) //get the cookie
onecookie = string.popLeading (onecookie, '\n')
local (flsecure = false) //not secure by default
local (domain = server) //default domain to this server
local (cookiePath = "/")
local (expires = clock.now () + (24 * 60 * 60)) //default expires at "end of session"
local (cookieName, cookieValue)
cookiePath = string.popSuffix (path, "/")
onecookie = string.popLeading (string.mid (onecookie, 12, infinity), ' ')
local (cookiepart = string.nthField (onecookie, ';', j))
cookiepart = string.popLeading (cookiepart, ' ')
domain = string.nthField (cookiepart, '=', 2)
cookiePath = string.nthField (cookiepart, '=', 2)
local (systemDate = date (cookieDate))
return (systemDate)
local (lastpart = string.nthField (cookieDate, "-", 3))
local (ixstart = string.patternMatch (lastpart, cookieDate))
return (date (string.insert ("20", cookieDate, ixstart)))
return (false)
expires = cookieDateToSystemDate (string.nthField (cookiepart, '=', 2))
break
flsecure = true
cookieName = string.nthField (cookiepart, '=', 1)
cookieValue = string.nthField (cookiepart, '=', 2)
new (tableType, @adrCookiesTable^.[domain])
new (tableType, @adrCookiesTable^.[domain].[cookiePath])
new (tableType, @adrCookiesTable^.[domain].[cookiePath].[cookieName])
adrCookiesTable^.[domain].[cookiePath].[cookieName].value = cookieValue
adrCookiesTable^.[domain].[cookiePath].[cookieName].expires = expires
adrCookiesTable^.[domain].[cookiePath].[cookieName].secure = flsecure
wp.newTextObject (httpResult, @scratchpad.httpResult)
local (statusCode = string.nthField (httpResult, ' ', 2))
local (flRedirect = false, redirectServer, redirectPath)
local (t); new (tableType, @t)
webserver.util.parseHeaders (httpResult, @t)
return (false)
local (scheme = string.lower (string.nthField (s, ":", 1)))
return (false)
local (ix)
return (true)
return (s beginsWith "/")
local (s = string.delete (t.location, 1, string.length ("http://")))
redirectServer = string.nthField (s, "/", 1)
redirectPath = string.delete (s, 1, string.length (redirectServer) + 1)
flRedirect = true
redirectServer = server
redirectPath = t.location
flRedirect = true
redirectServer = server
redirectPath = string.popSuffix (path, "/") + "/" + t.location
flRedirect = true
displayMsg ("Redirected to " + t.Location + ".")
method = "GET" //see section 10.3.4 of RFC2068
local (adrsubtable = @adrRedirectInfo^.[string.padWithZeros (sizeof (adrRedirectInfo^) + 1, 5)])
new (tableType, adrsubtable)
adrsubtable^.code = statusCode
adrsubtable^.server = redirectServer
adrsubtable^.port = port
adrsubtable^.path = redirectPath
httpResult = tcp.httpClient (method, redirectServer, port, redirectPath, proxy, proxyPort, proxyUserName, proxyPassword, data, datatype, username, password, adrHdrTable, cookiesOn, debug, timeOutTicks, flMessages, --ctFollowRedirects, flJustHeaders, flAcceptOpml, flUseProxy, adrRedirectInfo)
msg ("")
return (httpResult)
XML Stats & Atts.

This aggression will not stand.

Note: This page is being rendered using a template I have in a root, named My Template. This feels very nice and natural and I'm excited that it will lead very easily to themes. For more info, read this post.