diff options
| author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2000-12-29 22:15:09 +0000 |
|---|---|---|
| committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2000-12-29 22:15:09 +0000 |
| commit | 17c4d1c30544f0ed638879835f179ada96249868 (patch) | |
| tree | 46ca8042ba8fda147f56af61e26ec2ceec41f614 /src/http.lua | |
| parent | 6f9d15b66027cef58441549f8ac0603ca42da0ac (diff) | |
| download | luasocket-17c4d1c30544f0ed638879835f179ada96249868.tar.gz luasocket-17c4d1c30544f0ed638879835f179ada96249868.tar.bz2 luasocket-17c4d1c30544f0ed638879835f179ada96249868.zip | |
Initial revision
Diffstat (limited to 'src/http.lua')
| -rw-r--r-- | src/http.lua | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/src/http.lua b/src/http.lua new file mode 100644 index 0000000..8f08725 --- /dev/null +++ b/src/http.lua | |||
| @@ -0,0 +1,312 @@ | |||
| 1 | ----------------------------------------------------------------------------- | ||
| 2 | -- Simple HTTP/1.1 support for the Lua language using the LuaSocket toolkit. | ||
| 3 | -- Author: Diego Nehab | ||
| 4 | -- Date: 26/12/2000 | ||
| 5 | -- Conforming to: RFC 2068 | ||
| 6 | ----------------------------------------------------------------------------- | ||
| 7 | |||
| 8 | ----------------------------------------------------------------------------- | ||
| 9 | -- Program constants | ||
| 10 | ----------------------------------------------------------------------------- | ||
| 11 | -- connection timeout in seconds | ||
| 12 | local TIMEOUT = 60 | ||
| 13 | -- default port for document retrieval | ||
| 14 | local PORT = 80 | ||
| 15 | -- user agent field sent in request | ||
| 16 | local USERAGENT = "LuaSocket/HTTP 1.0" | ||
| 17 | |||
| 18 | ----------------------------------------------------------------------------- | ||
| 19 | -- Tries to get a line from the server or close socket if error | ||
| 20 | -- sock: socket connected to the server | ||
| 21 | -- Returns | ||
| 22 | -- line: line received or nil in case of error | ||
| 23 | -- err: error message if any | ||
| 24 | ----------------------------------------------------------------------------- | ||
| 25 | local try_getline = function(sock) | ||
| 26 | line, err = sock:receive() | ||
| 27 | if err then | ||
| 28 | sock:close() | ||
| 29 | return nil, err | ||
| 30 | end | ||
| 31 | return line | ||
| 32 | end | ||
| 33 | |||
| 34 | ----------------------------------------------------------------------------- | ||
| 35 | -- Tries to send a line to the server or close socket if error | ||
| 36 | -- sock: socket connected to the server | ||
| 37 | -- line: line to send | ||
| 38 | -- Returns | ||
| 39 | -- err: error message if any | ||
| 40 | ----------------------------------------------------------------------------- | ||
| 41 | local try_sendline = function(sock, line) | ||
| 42 | err = sock:send(line) | ||
| 43 | if err then sock:close() end | ||
| 44 | return err | ||
| 45 | end | ||
| 46 | |||
| 47 | ----------------------------------------------------------------------------- | ||
| 48 | -- Retrieves status from http reply | ||
| 49 | -- Input | ||
| 50 | -- reply: http reply string | ||
| 51 | -- Returns | ||
| 52 | -- status: integer with status code | ||
| 53 | ----------------------------------------------------------------------------- | ||
| 54 | local get_status = function(reply) | ||
| 55 | local _,_, status = strfind(reply, " (%d%d%d) ") | ||
| 56 | return tonumber(status) | ||
| 57 | end | ||
| 58 | |||
| 59 | ----------------------------------------------------------------------------- | ||
| 60 | -- Receive server reply messages | ||
| 61 | -- Input | ||
| 62 | -- sock: server socket | ||
| 63 | -- Returns | ||
| 64 | -- status: server reply status code or nil if error | ||
| 65 | -- reply: full server reply | ||
| 66 | -- err: error message if any | ||
| 67 | ----------------------------------------------------------------------------- | ||
| 68 | local get_reply = function(sock) | ||
| 69 | local reply, err | ||
| 70 | reply, err = %try_getline(sock) | ||
| 71 | if not err then return %get_status(reply), reply | ||
| 72 | else return nil, nil, err end | ||
| 73 | end | ||
| 74 | |||
| 75 | ----------------------------------------------------------------------------- | ||
| 76 | -- Receive and parse mime headers | ||
| 77 | -- Input | ||
| 78 | -- sock: server socket | ||
| 79 | -- mime: a table that might already contain mime headers | ||
| 80 | -- Returns | ||
| 81 | -- mime: a table with all mime headers in the form | ||
| 82 | -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} | ||
| 83 | -- all name_i are lowercase | ||
| 84 | -- nil and error message in case of error | ||
| 85 | ----------------------------------------------------------------------------- | ||
| 86 | local get_mime = function(sock, mime) | ||
| 87 | local line, err | ||
| 88 | local name, value | ||
| 89 | -- get first line | ||
| 90 | line, err = %try_getline(sock) | ||
| 91 | if err then return nil, err end | ||
| 92 | -- headers go until a blank line is found | ||
| 93 | while line ~= "" do | ||
| 94 | -- get field-name and value | ||
| 95 | _,_, name, value = strfind(line, "(.-):%s*(.*)") | ||
| 96 | name = strlower(name) | ||
| 97 | -- get next line (value might be folded) | ||
| 98 | line, err = %try_getline(sock) | ||
| 99 | if err then return nil, err end | ||
| 100 | -- unfold any folded values | ||
| 101 | while not err and line ~= "" and (strsub(line, 1, 1) == " ") do | ||
| 102 | value = value .. line | ||
| 103 | line, err = %try_getline(sock) | ||
| 104 | if err then return nil, err end | ||
| 105 | end | ||
| 106 | -- save pair in table | ||
| 107 | if mime[name] then | ||
| 108 | -- join any multiple field | ||
| 109 | mime[name] = mime[name] .. ", " .. value | ||
| 110 | else | ||
| 111 | -- new field | ||
| 112 | mime[name] = value | ||
| 113 | end | ||
| 114 | end | ||
| 115 | return mime | ||
| 116 | end | ||
| 117 | |||
| 118 | ----------------------------------------------------------------------------- | ||
| 119 | -- Receives http body | ||
| 120 | -- Input | ||
| 121 | -- sock: server socket | ||
| 122 | -- mime: initial mime headers | ||
| 123 | -- Returns | ||
| 124 | -- body: a string containing the body of the document | ||
| 125 | -- nil and error message in case of error | ||
| 126 | -- Obs: | ||
| 127 | -- mime: headers might be modified by chunked transfer | ||
| 128 | ----------------------------------------------------------------------------- | ||
| 129 | local get_body = function(sock, mime) | ||
| 130 | local body, err | ||
| 131 | if mime["transfer-encoding"] == "chunked" then | ||
| 132 | local chunk_size, line | ||
| 133 | body = "" | ||
| 134 | repeat | ||
| 135 | -- get chunk size, skip extention | ||
| 136 | line, err = %try_getline(sock) | ||
| 137 | if err then return nil, err end | ||
| 138 | chunk_size = tonumber(gsub(line, ";.*", ""), 16) | ||
| 139 | if not chunk_size then | ||
| 140 | sock:close() | ||
| 141 | return nil, "invalid chunk size" | ||
| 142 | end | ||
| 143 | -- get chunk | ||
| 144 | line, err = sock:receive(chunk_size) | ||
| 145 | if err then | ||
| 146 | sock:close() | ||
| 147 | return nil, err | ||
| 148 | end | ||
| 149 | -- concatenate new chunk | ||
| 150 | body = body .. line | ||
| 151 | -- skip blank line | ||
| 152 | _, err = %try_getline(sock) | ||
| 153 | if err then return nil, err end | ||
| 154 | until chunk_size <= 0 | ||
| 155 | -- store extra mime headers | ||
| 156 | --_, err = %get_mime(sock, mime) | ||
| 157 | --if err then return nil, err end | ||
| 158 | elseif mime["content-length"] then | ||
| 159 | body, err = sock:receive(tonumber(mime["content-length"])) | ||
| 160 | if err then | ||
| 161 | sock:close() | ||
| 162 | return nil, err | ||
| 163 | end | ||
| 164 | else | ||
| 165 | -- get it all until connection closes! | ||
| 166 | body, err = sock:receive("*a") | ||
| 167 | if err then | ||
| 168 | sock:close() | ||
| 169 | return nil, err | ||
| 170 | end | ||
| 171 | end | ||
| 172 | -- return whole body | ||
| 173 | return body | ||
| 174 | end | ||
| 175 | |||
| 176 | ----------------------------------------------------------------------------- | ||
| 177 | -- Parses a url and returns its scheme, user, password, host, port | ||
| 178 | -- and path components, according to RFC 1738, Uniform Resource Locators (URL), | ||
| 179 | -- of December 1994 | ||
| 180 | -- Input | ||
| 181 | -- url: unique resource locator desired | ||
| 182 | -- default: table containing default values to be returned | ||
| 183 | -- Returns | ||
| 184 | -- table with the following fields: | ||
| 185 | -- host: host to connect | ||
| 186 | -- path: url path | ||
| 187 | -- port: host port to connect | ||
| 188 | -- user: user name | ||
| 189 | -- pass: password | ||
| 190 | -- scheme: protocol | ||
| 191 | ----------------------------------------------------------------------------- | ||
| 192 | local split_url = function(url, default) | ||
| 193 | -- initialize default parameters | ||
| 194 | local parsed = default or {} | ||
| 195 | -- get scheme | ||
| 196 | url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) | ||
| 197 | -- get user name and password. both can be empty! | ||
| 198 | -- moreover, password can be ommited | ||
| 199 | url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) | ||
| 200 | %parsed.user = u | ||
| 201 | -- there can be an empty password, but the ':' has to be there | ||
| 202 | -- or else there is no password | ||
| 203 | %parsed.pass = nil -- kill default password | ||
| 204 | if c == ":" then %parsed.pass = p end | ||
| 205 | end) | ||
| 206 | -- get host | ||
| 207 | url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) | ||
| 208 | -- get port if any | ||
| 209 | url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) | ||
| 210 | -- whatever is left is the path | ||
| 211 | if url ~= "" then parsed.path = url end | ||
| 212 | return parsed | ||
| 213 | end | ||
| 214 | |||
| 215 | ----------------------------------------------------------------------------- | ||
| 216 | -- Sends a GET message through socket | ||
| 217 | -- Input | ||
| 218 | -- socket: http connection socket | ||
| 219 | -- path: path requested | ||
| 220 | -- mime: mime headers to send in request | ||
| 221 | -- Returns | ||
| 222 | -- err: nil in case of success, error message otherwise | ||
| 223 | ----------------------------------------------------------------------------- | ||
| 224 | local send_get = function(sock, path, mime) | ||
| 225 | local err = %try_sendline(sock, "GET " .. path .. " HTTP/1.1\r\n") | ||
| 226 | if err then return err end | ||
| 227 | for i, v in mime do | ||
| 228 | err = %try_sendline(sock, i .. ": " .. v .. "\r\n") | ||
| 229 | if err then return err end | ||
| 230 | end | ||
| 231 | err = %try_sendline(sock, "\r\n") | ||
| 232 | return err | ||
| 233 | end | ||
| 234 | |||
| 235 | ----------------------------------------------------------------------------- | ||
| 236 | -- Converts field names to lowercase | ||
| 237 | -- Input | ||
| 238 | -- headers: user header fields | ||
| 239 | -- parsed: parsed url components | ||
| 240 | -- Returns | ||
| 241 | -- mime: a table with the same headers, but with lowercase field names | ||
| 242 | ----------------------------------------------------------------------------- | ||
| 243 | local fill_headers = function(headers, parsed) | ||
| 244 | local mime = {} | ||
| 245 | headers = headers or {} | ||
| 246 | for i,v in headers do | ||
| 247 | mime[strlower(i)] = v | ||
| 248 | end | ||
| 249 | mime["connection"] = "close" | ||
| 250 | mime["host"] = parsed.host | ||
| 251 | mime["user-agent"] = %USERAGENT | ||
| 252 | if parsed.user and parsed.pass then -- Basic Authentication | ||
| 253 | mime["authorization"] = "Basic ".. | ||
| 254 | base64(parsed.user .. ":" .. parsed.pass) | ||
| 255 | end | ||
| 256 | return mime | ||
| 257 | end | ||
| 258 | |||
| 259 | ----------------------------------------------------------------------------- | ||
| 260 | -- We need base64 convertion routines for Basic Authentication Scheme | ||
| 261 | ----------------------------------------------------------------------------- | ||
| 262 | dofile("base64.lua") | ||
| 263 | |||
| 264 | ----------------------------------------------------------------------------- | ||
| 265 | -- Downloads and receives a http url, with its mime headers | ||
| 266 | -- Input | ||
| 267 | -- url: unique resource locator desired | ||
| 268 | -- headers: headers to send with request | ||
| 269 | -- tried: is this an authentication retry? | ||
| 270 | -- Returns | ||
| 271 | -- body: document body, if successfull | ||
| 272 | -- mime: headers received with document, if sucessfull | ||
| 273 | -- reply: server reply, if successfull | ||
| 274 | -- err: error message, if any | ||
| 275 | ----------------------------------------------------------------------------- | ||
| 276 | function http_get(url, headers) | ||
| 277 | local sock, err, mime, body, status, reply | ||
| 278 | -- get url components | ||
| 279 | local parsed = %split_url(url, {port = %PORT, path ="/"}) | ||
| 280 | -- fill default headers | ||
| 281 | headers = %fill_headers(headers, parsed) | ||
| 282 | -- try connection | ||
| 283 | sock, err = connect(parsed.host, parsed.port) | ||
| 284 | if not sock then return nil, nil, nil, err end | ||
| 285 | -- set connection timeout | ||
| 286 | sock:timeout(%TIMEOUT) | ||
| 287 | -- send request | ||
| 288 | err = %send_get(sock, parsed.path, headers) | ||
| 289 | if err then return nil, nil, nil, err end | ||
| 290 | -- get server message | ||
| 291 | status, reply, err = %get_reply(sock) | ||
| 292 | if err then return nil, nil, nil, err end | ||
| 293 | -- get url accordingly | ||
| 294 | if status == 200 then -- ok, go on and get it | ||
| 295 | mime, err = %get_mime(sock, {}) | ||
| 296 | if err then return nil, nil, reply, err end | ||
| 297 | body, err = %get_body(sock, mime) | ||
| 298 | if err then return nil, mime, reply, err end | ||
| 299 | sock:close() | ||
| 300 | return body, mime, reply | ||
| 301 | elseif status == 301 then -- moved permanently, try again | ||
| 302 | mime = %get_mime(sock, {}) | ||
| 303 | sock:close() | ||
| 304 | if mime["location"] then return http_get(mime["location"], headers) | ||
| 305 | else return nil, mime, reply end | ||
| 306 | elseif status == 401 then | ||
| 307 | mime, err = %get_mime(sock, {}) | ||
| 308 | if err then return nil, nil, reply, err end | ||
| 309 | return nil, mime, reply | ||
| 310 | end | ||
| 311 | return nil, nil, reply | ||
| 312 | end | ||
