PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/luarocks/share/lua/5.2/socket/http.lua

https://gitlab.com/shorten-Team/super
Lua | 354 lines | 259 code | 28 blank | 67 comment | 47 complexity | eafd39f22b1911a88e18309b1bcbd4ac MD5 | raw file
  1. -----------------------------------------------------------------------------
  2. -- HTTP/1.1 client support for the Lua language.
  3. -- LuaSocket toolkit.
  4. -- Author: Diego Nehab
  5. -----------------------------------------------------------------------------
  6. -----------------------------------------------------------------------------
  7. -- Declare module and import dependencies
  8. -------------------------------------------------------------------------------
  9. local socket = require("socket")
  10. local url = require("socket.url")
  11. local ltn12 = require("ltn12")
  12. local mime = require("mime")
  13. local string = require("string")
  14. local headers = require("socket.headers")
  15. local base = _G
  16. local table = require("table")
  17. socket.http = {}
  18. local _M = socket.http
  19. -----------------------------------------------------------------------------
  20. -- Program constants
  21. -----------------------------------------------------------------------------
  22. -- connection timeout in seconds
  23. TIMEOUT = 60
  24. -- default port for document retrieval
  25. _M.PORT = 80
  26. -- user agent field sent in request
  27. _M.USERAGENT = socket._VERSION
  28. -----------------------------------------------------------------------------
  29. -- Reads MIME headers from a connection, unfolding where needed
  30. -----------------------------------------------------------------------------
  31. local function receiveheaders(sock, headers)
  32. local line, name, value, err
  33. headers = headers or {}
  34. -- get first line
  35. line, err = sock:receive()
  36. if err then return nil, err end
  37. -- headers go until a blank line is found
  38. while line ~= "" do
  39. -- get field-name and value
  40. name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
  41. if not (name and value) then return nil, "malformed reponse headers" end
  42. name = string.lower(name)
  43. -- get next line (value might be folded)
  44. line, err = sock:receive()
  45. if err then return nil, err end
  46. -- unfold any folded values
  47. while string.find(line, "^%s") do
  48. value = value .. line
  49. line = sock:receive()
  50. if err then return nil, err end
  51. end
  52. -- save pair in table
  53. if headers[name] then headers[name] = headers[name] .. ", " .. value
  54. else headers[name] = value end
  55. end
  56. return headers
  57. end
  58. -----------------------------------------------------------------------------
  59. -- Extra sources and sinks
  60. -----------------------------------------------------------------------------
  61. socket.sourcet["http-chunked"] = function(sock, headers)
  62. return base.setmetatable({
  63. getfd = function() return sock:getfd() end,
  64. dirty = function() return sock:dirty() end
  65. }, {
  66. __call = function()
  67. -- get chunk size, skip extention
  68. local line, err = sock:receive()
  69. if err then return nil, err end
  70. local size = base.tonumber(string.gsub(line, ";.*", ""), 16)
  71. if not size then return nil, "invalid chunk size" end
  72. -- was it the last chunk?
  73. if size > 0 then
  74. -- if not, get chunk and skip terminating CRLF
  75. local chunk, err, part = sock:receive(size)
  76. if chunk then sock:receive() end
  77. return chunk, err
  78. else
  79. -- if it was, read trailers into headers table
  80. headers, err = receiveheaders(sock, headers)
  81. if not headers then return nil, err end
  82. end
  83. end
  84. })
  85. end
  86. socket.sinkt["http-chunked"] = function(sock)
  87. return base.setmetatable({
  88. getfd = function() return sock:getfd() end,
  89. dirty = function() return sock:dirty() end
  90. }, {
  91. __call = function(self, chunk, err)
  92. if not chunk then return sock:send("0\r\n\r\n") end
  93. local size = string.format("%X\r\n", string.len(chunk))
  94. return sock:send(size .. chunk .. "\r\n")
  95. end
  96. })
  97. end
  98. -----------------------------------------------------------------------------
  99. -- Low level HTTP API
  100. -----------------------------------------------------------------------------
  101. local metat = { __index = {} }
  102. function _M.open(host, port, create)
  103. -- create socket with user connect function, or with default
  104. local c = socket.try((create or socket.tcp)())
  105. local h = base.setmetatable({ c = c }, metat)
  106. -- create finalized try
  107. h.try = socket.newtry(function() h:close() end)
  108. -- set timeout before connecting
  109. h.try(c:settimeout(_M.TIMEOUT))
  110. h.try(c:connect(host, port or _M.PORT))
  111. -- here everything worked
  112. return h
  113. end
  114. function metat.__index:sendrequestline(method, uri)
  115. local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri)
  116. return self.try(self.c:send(reqline))
  117. end
  118. function metat.__index:sendheaders(tosend)
  119. local canonic = headers.canonic
  120. local h = "\r\n"
  121. for f, v in base.pairs(tosend) do
  122. h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h
  123. end
  124. self.try(self.c:send(h))
  125. return 1
  126. end
  127. function metat.__index:sendbody(headers, source, step)
  128. source = source or ltn12.source.empty()
  129. step = step or ltn12.pump.step
  130. -- if we don't know the size in advance, send chunked and hope for the best
  131. local mode = "http-chunked"
  132. if headers["content-length"] then mode = "keep-open" end
  133. return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step))
  134. end
  135. function metat.__index:receivestatusline()
  136. local status = self.try(self.c:receive(5))
  137. -- identify HTTP/0.9 responses, which do not contain a status line
  138. -- this is just a heuristic, but is what the RFC recommends
  139. if status ~= "HTTP/" then return nil, status end
  140. -- otherwise proceed reading a status line
  141. status = self.try(self.c:receive("*l", status))
  142. local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)"))
  143. return self.try(base.tonumber(code), status)
  144. end
  145. function metat.__index:receiveheaders()
  146. return self.try(receiveheaders(self.c))
  147. end
  148. function metat.__index:receivebody(headers, sink, step)
  149. sink = sink or ltn12.sink.null()
  150. step = step or ltn12.pump.step
  151. local length = base.tonumber(headers["content-length"])
  152. local t = headers["transfer-encoding"] -- shortcut
  153. local mode = "default" -- connection close
  154. if t and t ~= "identity" then mode = "http-chunked"
  155. elseif base.tonumber(headers["content-length"]) then mode = "by-length" end
  156. return self.try(ltn12.pump.all(socket.source(mode, self.c, length),
  157. sink, step))
  158. end
  159. function metat.__index:receive09body(status, sink, step)
  160. local source = ltn12.source.rewind(socket.source("until-closed", self.c))
  161. source(status)
  162. return self.try(ltn12.pump.all(source, sink, step))
  163. end
  164. function metat.__index:close()
  165. return self.c:close()
  166. end
  167. -----------------------------------------------------------------------------
  168. -- High level HTTP API
  169. -----------------------------------------------------------------------------
  170. local function adjusturi(reqt)
  171. local u = reqt
  172. -- if there is a proxy, we need the full url. otherwise, just a part.
  173. if not reqt.proxy and not PROXY then
  174. u = {
  175. path = socket.try(reqt.path, "invalid path 'nil'"),
  176. params = reqt.params,
  177. query = reqt.query,
  178. fragment = reqt.fragment
  179. }
  180. end
  181. return url.build(u)
  182. end
  183. local function adjustproxy(reqt)
  184. local proxy = reqt.proxy or PROXY
  185. if proxy then
  186. proxy = url.parse(proxy)
  187. return proxy.host, proxy.port or 3128
  188. else
  189. return reqt.host, reqt.port
  190. end
  191. end
  192. local function adjustheaders(reqt)
  193. -- default headers
  194. local lower = {
  195. ["user-agent"] = _M.USERAGENT,
  196. ["host"] = reqt.host,
  197. ["connection"] = "close, TE",
  198. ["te"] = "trailers"
  199. }
  200. -- if we have authentication information, pass it along
  201. if reqt.user and reqt.password then
  202. lower["authorization"] =
  203. "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password))
  204. end
  205. -- override with user headers
  206. for i,v in base.pairs(reqt.headers or lower) do
  207. lower[string.lower(i)] = v
  208. end
  209. return lower
  210. end
  211. -- default url parts
  212. local default = {
  213. host = "",
  214. port = _M.PORT,
  215. path ="/",
  216. scheme = "http"
  217. }
  218. local function adjustrequest(reqt)
  219. -- parse url if provided
  220. local nreqt = reqt.url and url.parse(reqt.url, default) or {}
  221. -- explicit components override url
  222. for i,v in base.pairs(reqt) do nreqt[i] = v end
  223. if nreqt.port == "" then nreqt.port = 80 end
  224. socket.try(nreqt.host and nreqt.host ~= "",
  225. "invalid host '" .. base.tostring(nreqt.host) .. "'")
  226. -- compute uri if user hasn't overriden
  227. nreqt.uri = reqt.uri or adjusturi(nreqt)
  228. -- ajust host and port if there is a proxy
  229. nreqt.host, nreqt.port = adjustproxy(nreqt)
  230. -- adjust headers in request
  231. nreqt.headers = adjustheaders(nreqt)
  232. return nreqt
  233. end
  234. local function shouldredirect(reqt, code, headers)
  235. return headers.location and
  236. string.gsub(headers.location, "%s", "") ~= "" and
  237. (reqt.redirect ~= false) and
  238. (code == 301 or code == 302 or code == 303 or code == 307) and
  239. (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD")
  240. and (not reqt.nredirects or reqt.nredirects < 5)
  241. end
  242. local function shouldreceivebody(reqt, code)
  243. if reqt.method == "HEAD" then return nil end
  244. if code == 204 or code == 304 then return nil end
  245. if code >= 100 and code < 200 then return nil end
  246. return 1
  247. end
  248. -- forward declarations
  249. local trequest, tredirect
  250. --[[local]] function tredirect(reqt, location)
  251. local result, code, headers, status = trequest {
  252. -- the RFC says the redirect URL has to be absolute, but some
  253. -- servers do not respect that
  254. url = url.absolute(reqt.url, location),
  255. source = reqt.source,
  256. sink = reqt.sink,
  257. headers = reqt.headers,
  258. proxy = reqt.proxy,
  259. nredirects = (reqt.nredirects or 0) + 1,
  260. create = reqt.create
  261. }
  262. -- pass location header back as a hint we redirected
  263. headers = headers or {}
  264. headers.location = headers.location or location
  265. return result, code, headers, status
  266. end
  267. --[[local]] function trequest(reqt)
  268. -- we loop until we get what we want, or
  269. -- until we are sure there is no way to get it
  270. local nreqt = adjustrequest(reqt)
  271. local h = _M.open(nreqt.host, nreqt.port, nreqt.create)
  272. -- send request line and headers
  273. h:sendrequestline(nreqt.method, nreqt.uri)
  274. h:sendheaders(nreqt.headers)
  275. -- if there is a body, send it
  276. if nreqt.source then
  277. h:sendbody(nreqt.headers, nreqt.source, nreqt.step)
  278. end
  279. local code, status = h:receivestatusline()
  280. -- if it is an HTTP/0.9 server, simply get the body and we are done
  281. if not code then
  282. h:receive09body(status, nreqt.sink, nreqt.step)
  283. return 1, 200
  284. end
  285. local headers
  286. -- ignore any 100-continue messages
  287. while code == 100 do
  288. headers = h:receiveheaders()
  289. code, status = h:receivestatusline()
  290. end
  291. headers = h:receiveheaders()
  292. -- at this point we should have a honest reply from the server
  293. -- we can't redirect if we already used the source, so we report the error
  294. if shouldredirect(nreqt, code, headers) and not nreqt.source then
  295. h:close()
  296. return tredirect(reqt, headers.location)
  297. end
  298. -- here we are finally done
  299. if shouldreceivebody(nreqt, code) then
  300. h:receivebody(headers, nreqt.sink, nreqt.step)
  301. end
  302. h:close()
  303. return 1, code, headers, status
  304. end
  305. local function srequest(u, b)
  306. local t = {}
  307. local reqt = {
  308. url = u,
  309. sink = ltn12.sink.table(t)
  310. }
  311. if b then
  312. reqt.source = ltn12.source.string(b)
  313. reqt.headers = {
  314. ["content-length"] = string.len(b),
  315. ["content-type"] = "application/x-www-form-urlencoded"
  316. }
  317. reqt.method = "POST"
  318. end
  319. local code, headers, status = socket.skip(1, trequest(reqt))
  320. return table.concat(t), code, headers, status
  321. end
  322. _M.request = socket.protect(function(reqt, body)
  323. if base.type(reqt) == "string" then return srequest(reqt, body)
  324. else return trequest(reqt) end
  325. end)
  326. return _M