internal.lua 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. local table = table
  2. local type = type
  3. local M = {}
  4. local LIMIT = 8192
  5. local function chunksize(readbytes, body)
  6. while true do
  7. local f,e = body:find("\r\n",1,true)
  8. if f then
  9. return tonumber(body:sub(1,f-1),16), body:sub(e+1)
  10. end
  11. if #body > 128 then
  12. -- pervent the attacker send very long stream without \r\n
  13. return
  14. end
  15. body = body .. readbytes()
  16. end
  17. end
  18. local function readcrln(readbytes, body)
  19. if #body >= 2 then
  20. if body:sub(1,2) ~= "\r\n" then
  21. return
  22. end
  23. return body:sub(3)
  24. else
  25. body = body .. readbytes(2-#body)
  26. if body ~= "\r\n" then
  27. return
  28. end
  29. return ""
  30. end
  31. end
  32. function M.recvheader(readbytes, lines, header)
  33. if #header >= 2 then
  34. if header:find "^\r\n" then
  35. return header:sub(3)
  36. end
  37. end
  38. local result
  39. local e = header:find("\r\n\r\n", 1, true)
  40. if e then
  41. result = header:sub(e+4)
  42. else
  43. while true do
  44. local bytes = readbytes()
  45. header = header .. bytes
  46. e = header:find("\r\n\r\n", -#bytes-3, true)
  47. if e then
  48. result = header:sub(e+4)
  49. break
  50. end
  51. if header:find "^\r\n" then
  52. return header:sub(3)
  53. end
  54. if #header > LIMIT then
  55. return
  56. end
  57. end
  58. end
  59. for v in header:gmatch("(.-)\r\n") do
  60. if v == "" then
  61. break
  62. end
  63. table.insert(lines, v)
  64. end
  65. return result
  66. end
  67. function M.parseheader(lines, from, header)
  68. local name, value
  69. for i=from,#lines do
  70. local line = lines[i]
  71. if line:byte(1) == 9 then -- tab, append last line
  72. if name == nil then
  73. return
  74. end
  75. header[name] = header[name] .. line:sub(2)
  76. else
  77. name, value = line:match "^(.-):%s*(.*)"
  78. if name == nil or value == nil then
  79. return
  80. end
  81. name = name:lower()
  82. if header[name] then
  83. local v = header[name]
  84. if type(v) == "table" then
  85. table.insert(v, value)
  86. else
  87. header[name] = { v , value }
  88. end
  89. else
  90. header[name] = value
  91. end
  92. end
  93. end
  94. return header
  95. end
  96. function M.recvchunkedbody(readbytes, bodylimit, header, body)
  97. local result = ""
  98. local size = 0
  99. while true do
  100. local sz
  101. sz , body = chunksize(readbytes, body)
  102. if not sz then
  103. return
  104. end
  105. if sz == 0 then
  106. break
  107. end
  108. size = size + sz
  109. if bodylimit and size > bodylimit then
  110. return
  111. end
  112. if #body >= sz then
  113. result = result .. body:sub(1,sz)
  114. body = body:sub(sz+1)
  115. else
  116. result = result .. body .. readbytes(sz - #body)
  117. body = ""
  118. end
  119. body = readcrln(readbytes, body)
  120. if not body then
  121. return
  122. end
  123. end
  124. local tmpline = {}
  125. body = M.recvheader(readbytes, tmpline, body)
  126. if not body then
  127. return
  128. end
  129. header = M.parseheader(tmpline,1,header)
  130. return result, header
  131. end
  132. local function recvbody(interface, code, header, body)
  133. local length = header["content-length"]
  134. if length then
  135. length = tonumber(length)
  136. end
  137. if length then
  138. if #body >= length then
  139. body = body:sub(1,length)
  140. else
  141. local padding = interface.read(length - #body)
  142. body = body .. padding
  143. end
  144. elseif code == 204 or code == 304 or code < 200 then
  145. body = ""
  146. -- See https://stackoverflow.com/questions/15991173/is-the-content-length-header-required-for-a-http-1-0-response
  147. else
  148. -- no content-length, read all
  149. body = body .. interface.readall()
  150. end
  151. return body
  152. end
  153. function M.request(interface, method, host, url, recvheader, header, content)
  154. local read = interface.read
  155. local write = interface.write
  156. local header_content = ""
  157. if header then
  158. if not header.Host then
  159. header.Host = host
  160. end
  161. for k,v in pairs(header) do
  162. header_content = string.format("%s%s:%s\r\n", header_content, k, v)
  163. end
  164. else
  165. header_content = string.format("host:%s\r\n",host)
  166. end
  167. if content then
  168. local data = string.format("%s %s HTTP/1.1\r\n%sContent-length:%d\r\n\r\n", method, url, header_content, #content)
  169. write(data)
  170. write(content)
  171. else
  172. local request_header = string.format("%s %s HTTP/1.1\r\n%sContent-length:0\r\n\r\n", method, url, header_content)
  173. write(request_header)
  174. end
  175. local tmpline = {}
  176. local body = M.recvheader(read, tmpline, "")
  177. if not body then
  178. error("Recv header failed")
  179. end
  180. local statusline = tmpline[1]
  181. local code, info = statusline:match "HTTP/[%d%.]+%s+([%d]+)%s+(.*)$"
  182. code = assert(tonumber(code))
  183. local header = M.parseheader(tmpline,2,recvheader or {})
  184. if not header then
  185. error("Invalid HTTP response header")
  186. end
  187. return code, body, header
  188. end
  189. function M.response(interface, code, body, header)
  190. local mode = header["transfer-encoding"]
  191. if mode then
  192. if mode ~= "identity" and mode ~= "chunked" then
  193. error ("Unsupport transfer-encoding")
  194. end
  195. end
  196. if mode == "chunked" then
  197. body, header = M.recvchunkedbody(interface.read, nil, header, body)
  198. if not body then
  199. error("Invalid response body")
  200. end
  201. else
  202. -- identity mode
  203. body = recvbody(interface, code, header, body)
  204. end
  205. return body
  206. end
  207. local stream = {}; stream.__index = stream
  208. function stream:close()
  209. if self._onclose then
  210. self._onclose(self)
  211. self._onclose = nil
  212. end
  213. end
  214. function stream:padding()
  215. return self._reading(self), self
  216. end
  217. stream.__close = stream.close
  218. stream.__call = stream.padding
  219. local function stream_nobody(stream)
  220. stream._reading = stream.close
  221. stream.connected = nil
  222. return ""
  223. end
  224. local function stream_length(length)
  225. return function(stream)
  226. local body = stream._body
  227. if body == nil then
  228. local ret, padding = stream._interface.read()
  229. if not ret then
  230. -- disconnected
  231. body = padding
  232. stream.connected = false
  233. else
  234. body = ret
  235. end
  236. end
  237. local n = #body
  238. if n >= length then
  239. stream._reading = stream.close
  240. stream.connected = nil
  241. return (body:sub(1,length))
  242. else
  243. length = length - n
  244. stream._body = nil
  245. if not stream.connected then
  246. stream._reading = stream.close
  247. end
  248. return body
  249. end
  250. end
  251. end
  252. local function stream_read(stream)
  253. local ret, padding = stream._interface.read()
  254. if ret == "" or not ret then
  255. stream.connected = nil
  256. stream:close()
  257. if padding == "" then
  258. return
  259. end
  260. return padding
  261. end
  262. return ret
  263. end
  264. local function stream_all(stream)
  265. local body = stream._body
  266. stream._body = nil
  267. stream._reading = stream_read
  268. return body
  269. end
  270. local function stream_chunked(stream)
  271. local read = stream._interface.read
  272. local sz, body = chunksize(read, stream._body)
  273. if not sz then
  274. stream.connected = false
  275. stream:close()
  276. return
  277. end
  278. if sz == 0 then
  279. -- last chunk
  280. local tmpline = {}
  281. body = M.recvheader(read, tmpline, body)
  282. if not body then
  283. stream.connected = false
  284. stream:close()
  285. return
  286. end
  287. M.parseheader(tmpline,1, stream.header)
  288. stream._reading = stream.close
  289. stream.connected = nil
  290. return ""
  291. end
  292. local n = #body
  293. local remain
  294. if n >= sz then
  295. remain = body:sub(sz+1)
  296. body = body:sub(1,sz)
  297. else
  298. body = body .. read(sz - n)
  299. remain = ""
  300. end
  301. remain = readcrln(read, remain)
  302. if not remain then
  303. stream.connected = false
  304. stream:close()
  305. return
  306. end
  307. stream._body = remain
  308. return body
  309. end
  310. function M.response_stream(interface, code, body, header)
  311. local mode = header["transfer-encoding"]
  312. if mode then
  313. if mode ~= "identity" and mode ~= "chunked" then
  314. error ("Unsupport transfer-encoding")
  315. end
  316. end
  317. local read_func
  318. if mode == "chunked" then
  319. read_func = stream_chunked
  320. else
  321. -- identity mode
  322. local length = header["content-length"]
  323. if length then
  324. length = tonumber(length)
  325. end
  326. if length then
  327. read_func = stream_length(length)
  328. elseif code == 204 or code == 304 or code < 200 then
  329. read_func = stream_nobody
  330. else
  331. read_func = stream_all
  332. end
  333. end
  334. -- todo: timeout
  335. return setmetatable({
  336. status = code,
  337. _body = body,
  338. _interface = interface,
  339. _reading = read_func,
  340. header = header,
  341. connected = true,
  342. }, stream)
  343. end
  344. return M