protocol.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. -- Copyright 2008 Freifunk Leipzig / Jo-Philipp Wich <jow@openwrt.org>
  2. -- Licensed to the public under the Apache License 2.0.
  3. -- This class contains several functions useful for http message- and content
  4. -- decoding and to retrive form data from raw http messages.
  5. module("luci.http.protocol", package.seeall)
  6. local ltn12 = require("luci.ltn12")
  7. HTTP_MAX_CONTENT = 1024*8 -- 8 kB maximum content size
  8. -- the "+" sign to " " - and return the decoded string.
  9. function urldecode( str, no_plus )
  10. local function __chrdec( hex )
  11. return string.char( tonumber( hex, 16 ) )
  12. end
  13. if type(str) == "string" then
  14. if not no_plus then
  15. str = str:gsub( "+", " " )
  16. end
  17. str = str:gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec )
  18. end
  19. return str
  20. end
  21. -- from given url or string. Returns a table with urldecoded values.
  22. -- Simple parameters are stored as string values associated with the parameter
  23. -- name within the table. Parameters with multiple values are stored as array
  24. -- containing the corresponding values.
  25. function urldecode_params( url, tbl )
  26. local params = tbl or { }
  27. if url:find("?") then
  28. url = url:gsub( "^.+%?([^?]+)", "%1" )
  29. end
  30. for pair in url:gmatch( "[^&;]+" ) do
  31. -- find key and value
  32. local key = urldecode( pair:match("^([^=]+)") )
  33. local val = urldecode( pair:match("^[^=]+=(.+)$") )
  34. -- store
  35. if type(key) == "string" and key:len() > 0 then
  36. if type(val) ~= "string" then val = "" end
  37. if not params[key] then
  38. params[key] = val
  39. elseif type(params[key]) ~= "table" then
  40. params[key] = { params[key], val }
  41. else
  42. table.insert( params[key], val )
  43. end
  44. end
  45. end
  46. return params
  47. end
  48. function urlencode( str )
  49. local function __chrenc( chr )
  50. return string.format(
  51. "%%%02x", string.byte( chr )
  52. )
  53. end
  54. if type(str) == "string" then
  55. str = str:gsub(
  56. "([^a-zA-Z0-9$_%-%.!*'(),])",
  57. __chrenc
  58. )
  59. end
  60. return str
  61. end
  62. -- separated by "&". Tables are encoded as parameters with multiple values by
  63. -- repeating the parameter name with each value.
  64. function urlencode_params( tbl )
  65. local enc = ""
  66. for k, v in pairs(tbl) do
  67. if type(v) == "table" then
  68. for i, v2 in ipairs(v) do
  69. enc = enc .. ( #enc > 0 and "&" or "" ) ..
  70. urlencode(k) .. "=" .. urlencode(v2)
  71. end
  72. else
  73. enc = enc .. ( #enc > 0 and "&" or "" ) ..
  74. urlencode(k) .. "=" .. urlencode(v)
  75. end
  76. end
  77. return enc
  78. end
  79. -- (Internal function)
  80. -- Initialize given parameter and coerce string into table when the parameter
  81. -- already exists.
  82. local function __initval( tbl, key )
  83. if tbl[key] == nil then
  84. tbl[key] = ""
  85. elseif type(tbl[key]) == "string" then
  86. tbl[key] = { tbl[key], "" }
  87. else
  88. table.insert( tbl[key], "" )
  89. end
  90. end
  91. -- (Internal function)
  92. -- Append given data to given parameter, either by extending the string value
  93. -- or by appending it to the last string in the parameter's value table.
  94. local function __appendval( tbl, key, chunk )
  95. if type(tbl[key]) == "table" then
  96. tbl[key][#tbl[key]] = tbl[key][#tbl[key]] .. chunk
  97. else
  98. tbl[key] = tbl[key] .. chunk
  99. end
  100. end
  101. -- (Internal function)
  102. -- Finish the value of given parameter, either by transforming the string value
  103. -- or - in the case of multi value parameters - the last element in the
  104. -- associated values table.
  105. local function __finishval( tbl, key, handler )
  106. if handler then
  107. if type(tbl[key]) == "table" then
  108. tbl[key][#tbl[key]] = handler( tbl[key][#tbl[key]] )
  109. else
  110. tbl[key] = handler( tbl[key] )
  111. end
  112. end
  113. end
  114. -- Table of our process states
  115. local process_states = { }
  116. -- Extract "magic", the first line of a http message.
  117. -- Extracts the message type ("get", "post" or "response"), the requested uri
  118. -- or the status code if the line descripes a http response.
  119. process_states['magic'] = function( msg, chunk, err )
  120. if chunk ~= nil then
  121. -- ignore empty lines before request
  122. if #chunk == 0 then
  123. return true, nil
  124. end
  125. -- Is it a request?
  126. local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
  127. -- Yup, it is
  128. if method then
  129. msg.type = "request"
  130. msg.request_method = method:lower()
  131. msg.request_uri = uri
  132. msg.http_version = tonumber( http_ver )
  133. msg.headers = { }
  134. -- We're done, next state is header parsing
  135. return true, function( chunk )
  136. return process_states['headers']( msg, chunk )
  137. end
  138. -- Is it a response?
  139. else
  140. local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
  141. -- Is a response
  142. if code then
  143. msg.type = "response"
  144. msg.status_code = code
  145. msg.status_message = message
  146. msg.http_version = tonumber( http_ver )
  147. msg.headers = { }
  148. -- We're done, next state is header parsing
  149. return true, function( chunk )
  150. return process_states['headers']( msg, chunk )
  151. end
  152. end
  153. end
  154. end
  155. -- Can't handle it
  156. return nil, "Invalid HTTP message magic"
  157. end
  158. -- Extract headers from given string.
  159. process_states['headers'] = function( msg, chunk )
  160. if chunk ~= nil then
  161. -- Look for a valid header format
  162. local hdr, val = chunk:match( "^([A-Za-z][A-Za-z0-9%-_]+): +(.+)$" )
  163. if type(hdr) == "string" and hdr:len() > 0 and
  164. type(val) == "string" and val:len() > 0
  165. then
  166. msg.headers[hdr] = val
  167. -- Valid header line, proceed
  168. return true, nil
  169. elseif #chunk == 0 then
  170. -- Empty line, we won't accept data anymore
  171. return false, nil
  172. else
  173. -- Junk data
  174. return nil, "Invalid HTTP header received"
  175. end
  176. else
  177. return nil, "Unexpected EOF"
  178. end
  179. end
  180. -- data line by line with the trailing \r\n stripped of.
  181. function header_source( sock )
  182. return ltn12.source.simplify( function()
  183. local chunk, err, part = sock:receive("*l")
  184. -- Line too long
  185. if chunk == nil then
  186. if err ~= "timeout" then
  187. return nil, part
  188. and "Line exceeds maximum allowed length"
  189. or "Unexpected EOF"
  190. else
  191. return nil, err
  192. end
  193. -- Line ok
  194. elseif chunk ~= nil then
  195. -- Strip trailing CR
  196. chunk = chunk:gsub("\r$","")
  197. return chunk, nil
  198. end
  199. end )
  200. end
  201. -- Content-Type. Stores all extracted data associated with its parameter name
  202. -- in the params table withing the given message object. Multiple parameter
  203. -- values are stored as tables, ordinary ones as strings.
  204. -- If an optional file callback function is given then it is feeded with the
  205. -- file contents chunk by chunk and only the extracted file name is stored
  206. -- within the params table. The callback function will be called subsequently
  207. -- with three arguments:
  208. -- o Table containing decoded (name, file) and raw (headers) mime header data
  209. -- o String value containing a chunk of the file data
  210. -- o Boolean which indicates wheather the current chunk is the last one (eof)
  211. function mimedecode_message_body( src, msg, filecb )
  212. if msg and msg.env.CONTENT_TYPE then
  213. msg.mime_boundary = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)$")
  214. end
  215. if not msg.mime_boundary then
  216. return nil, "Invalid Content-Type found"
  217. end
  218. local tlen = 0
  219. local inhdr = false
  220. local field = nil
  221. local store = nil
  222. local lchunk = nil
  223. local function parse_headers( chunk, field )
  224. local stat
  225. repeat
  226. chunk, stat = chunk:gsub(
  227. "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n",
  228. function(k,v)
  229. field.headers[k] = v
  230. return ""
  231. end
  232. )
  233. until stat == 0
  234. chunk, stat = chunk:gsub("^\r\n","")
  235. -- End of headers
  236. if stat > 0 then
  237. if field.headers["Content-Disposition"] then
  238. if field.headers["Content-Disposition"]:match("^form%-data; ") then
  239. field.name = field.headers["Content-Disposition"]:match('name="(.-)"')
  240. field.file = field.headers["Content-Disposition"]:match('filename="(.+)"$')
  241. end
  242. end
  243. if not field.headers["Content-Type"] then
  244. field.headers["Content-Type"] = "text/plain"
  245. end
  246. if field.name and field.file and filecb then
  247. __initval( msg.params, field.name )
  248. __appendval( msg.params, field.name, field.file )
  249. store = filecb
  250. elseif field.name then
  251. __initval( msg.params, field.name )
  252. store = function( hdr, buf, eof )
  253. __appendval( msg.params, field.name, buf )
  254. end
  255. else
  256. store = nil
  257. end
  258. return chunk, true
  259. end
  260. return chunk, false
  261. end
  262. local function snk( chunk )
  263. tlen = tlen + ( chunk and #chunk or 0 )
  264. if msg.env.CONTENT_LENGTH and tlen > tonumber(msg.env.CONTENT_LENGTH) + 2 then
  265. return nil, "Message body size exceeds Content-Length"
  266. end
  267. if chunk and not lchunk then
  268. lchunk = "\r\n" .. chunk
  269. elseif lchunk then
  270. local data = lchunk .. ( chunk or "" )
  271. local spos, epos, found
  272. repeat
  273. spos, epos = data:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
  274. if not spos then
  275. spos, epos = data:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
  276. end
  277. if spos then
  278. local predata = data:sub( 1, spos - 1 )
  279. if inhdr then
  280. predata, eof = parse_headers( predata, field )
  281. if not eof then
  282. return nil, "Invalid MIME section header"
  283. elseif not field.name then
  284. return nil, "Invalid Content-Disposition header"
  285. end
  286. end
  287. if store then
  288. store( field, predata, true )
  289. end
  290. field = { headers = { } }
  291. found = found or true
  292. data, eof = parse_headers( data:sub( epos + 1, #data ), field )
  293. inhdr = not eof
  294. end
  295. until not spos
  296. if found then
  297. -- We found at least some boundary. Save
  298. -- the unparsed remaining data for the
  299. -- next chunk.
  300. lchunk, data = data, nil
  301. else
  302. -- There was a complete chunk without a boundary. Parse it as headers or
  303. -- append it as data, depending on our current state.
  304. if inhdr then
  305. lchunk, eof = parse_headers( data, field )
  306. inhdr = not eof
  307. else
  308. -- We're inside data, so append the data. Note that we only append
  309. -- lchunk, not all of data, since there is a chance that chunk
  310. -- contains half a boundary. Assuming that each chunk is at least the
  311. -- boundary in size, this should prevent problems
  312. store( field, lchunk, false )
  313. lchunk, chunk = chunk, nil
  314. end
  315. end
  316. end
  317. return true
  318. end
  319. return ltn12.pump.all( src, snk )
  320. end
  321. -- Content-Type. Stores all extracted data associated with its parameter name
  322. -- in the params table withing the given message object. Multiple parameter
  323. -- values are stored as tables, ordinary ones as strings.
  324. function urldecode_message_body( src, msg )
  325. local tlen = 0
  326. local lchunk = nil
  327. local function snk( chunk )
  328. tlen = tlen + ( chunk and #chunk or 0 )
  329. if msg.env.CONTENT_LENGTH and tlen > tonumber(msg.env.CONTENT_LENGTH) + 2 then
  330. return nil, "Message body size exceeds Content-Length"
  331. elseif tlen > HTTP_MAX_CONTENT then
  332. return nil, "Message body size exceeds maximum allowed length"
  333. end
  334. if not lchunk and chunk then
  335. lchunk = chunk
  336. elseif lchunk then
  337. local data = lchunk .. ( chunk or "&" )
  338. local spos, epos
  339. repeat
  340. spos, epos = data:find("^.-[;&]")
  341. if spos then
  342. local pair = data:sub( spos, epos - 1 )
  343. local key = pair:match("^(.-)=")
  344. local val = pair:match("=([^%s]*)%s*$")
  345. if key and #key > 0 then
  346. __initval( msg.params, key )
  347. __appendval( msg.params, key, val )
  348. __finishval( msg.params, key, urldecode )
  349. end
  350. data = data:sub( epos + 1, #data )
  351. end
  352. until not spos
  353. lchunk = data
  354. end
  355. return true
  356. end
  357. return ltn12.pump.all( src, snk )
  358. end
  359. -- version, message headers and resulting CGI environment variables from the
  360. -- given ltn12 source.
  361. function parse_message_header( src )
  362. local ok = true
  363. local msg = { }
  364. local sink = ltn12.sink.simplify(
  365. function( chunk )
  366. return process_states['magic']( msg, chunk )
  367. end
  368. )
  369. -- Pump input data...
  370. while ok do
  371. -- get data
  372. ok, err = ltn12.pump.step( src, sink )
  373. -- error
  374. if not ok and err then
  375. return nil, err
  376. -- eof
  377. elseif not ok then
  378. -- Process get parameters
  379. if ( msg.request_method == "get" or msg.request_method == "post" ) and
  380. msg.request_uri:match("?")
  381. then
  382. msg.params = urldecode_params( msg.request_uri )
  383. else
  384. msg.params = { }
  385. end
  386. -- Populate common environment variables
  387. msg.env = {
  388. CONTENT_LENGTH = msg.headers['Content-Length'];
  389. CONTENT_TYPE = msg.headers['Content-Type'] or msg.headers['Content-type'];
  390. REQUEST_METHOD = msg.request_method:upper();
  391. REQUEST_URI = msg.request_uri;
  392. SCRIPT_NAME = msg.request_uri:gsub("?.+$","");
  393. SCRIPT_FILENAME = ""; -- XXX implement me
  394. SERVER_PROTOCOL = "HTTP/" .. string.format("%.1f", msg.http_version);
  395. QUERY_STRING = msg.request_uri:match("?")
  396. and msg.request_uri:gsub("^.+?","") or ""
  397. }
  398. -- Populate HTTP_* environment variables
  399. for i, hdr in ipairs( {
  400. 'Accept',
  401. 'Accept-Charset',
  402. 'Accept-Encoding',
  403. 'Accept-Language',
  404. 'Connection',
  405. 'Cookie',
  406. 'Host',
  407. 'Referer',
  408. 'User-Agent',
  409. } ) do
  410. local var = 'HTTP_' .. hdr:upper():gsub("%-","_")
  411. local val = msg.headers[hdr]
  412. msg.env[var] = val
  413. end
  414. end
  415. end
  416. return msg
  417. end
  418. -- This function will examine the Content-Type within the given message object
  419. -- to select the appropriate content decoder.
  420. -- Currently the application/x-www-urlencoded and application/form-data
  421. -- mime types are supported. If the encountered content encoding can't be
  422. -- handled then the whole message body will be stored unaltered as "content"
  423. -- property within the given message object.
  424. function parse_message_body( src, msg, filecb )
  425. -- Is it multipart/mime ?
  426. if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
  427. msg.env.CONTENT_TYPE:match("^multipart/form%-data")
  428. then
  429. return mimedecode_message_body( src, msg, filecb )
  430. -- Is it application/x-www-form-urlencoded ?
  431. elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
  432. msg.env.CONTENT_TYPE:match("^application/x%-www%-form%-urlencoded")
  433. then
  434. return urldecode_message_body( src, msg, filecb )
  435. -- Unhandled encoding
  436. -- If a file callback is given then feed it chunk by chunk, else
  437. -- store whole buffer in message.content
  438. else
  439. local sink
  440. -- If we have a file callback then feed it
  441. if type(filecb) == "function" then
  442. local meta = {
  443. name = "raw",
  444. encoding = msg.env.CONTENT_TYPE
  445. }
  446. sink = function( chunk )
  447. if chunk then
  448. return filecb(meta, chunk, false)
  449. else
  450. return filecb(meta, nil, true)
  451. end
  452. end
  453. -- ... else append to .content
  454. else
  455. msg.content = ""
  456. msg.content_length = 0
  457. sink = function( chunk )
  458. if chunk then
  459. if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
  460. msg.content = msg.content .. chunk
  461. msg.content_length = msg.content_length + #chunk
  462. return true
  463. else
  464. return nil, "POST data exceeds maximum allowed length"
  465. end
  466. end
  467. return true
  468. end
  469. end
  470. -- Pump data...
  471. while true do
  472. local ok, err = ltn12.pump.step( src, sink )
  473. if not ok and err then
  474. return nil, err
  475. elseif not ok then -- eof
  476. return true
  477. end
  478. end
  479. return true
  480. end
  481. end
  482. statusmsg = {
  483. [200] = "OK",
  484. [206] = "Partial Content",
  485. [301] = "Moved Permanently",
  486. [302] = "Found",
  487. [304] = "Not Modified",
  488. [400] = "Bad Request",
  489. [403] = "Forbidden",
  490. [404] = "Not Found",
  491. [405] = "Method Not Allowed",
  492. [408] = "Request Time-out",
  493. [411] = "Length Required",
  494. [412] = "Precondition Failed",
  495. [416] = "Requested range not satisfiable",
  496. [500] = "Internal Server Error",
  497. [503] = "Server Unavailable",
  498. }