home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Networking / nmap-5.00-setup.exe / nselib / http.lua < prev    next >
Text File  |  2009-07-06  |  10KB  |  301 lines

  1. --- Client-side HTTP library.
  2. --
  3. -- The return value of each function in this module is a table with the
  4. -- following keys: <code>status</code>, <code>status-line</code>,
  5. -- <code>header</code>, and <code>body</code>. <code>status</code> is a number
  6. -- representing the HTTP status code returned in response to the HTTP request.
  7. -- In case of an unhandled error, <code>status</code> is <code>nil</code>.
  8. -- <code>status-line</code> is the entire status message which includes the HTTP
  9. -- version, status code, and reason phrase. The <code>header</code> value is a
  10. -- table containing key-value pairs of HTTP headers received in response to the
  11. -- request. The header names are in lower-case and are the keys to their
  12. -- corresponding header values (e.g. <code>header.location</code> =
  13. -- <code>"http://nmap.org/"</code>). Multiple headers of the same name are
  14. -- concatenated and separated by commas. The <code>body</code> value is a string
  15. -- containing the body of the HTTP response.
  16. -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
  17.  
  18. module(... or "http",package.seeall)
  19.  
  20. local url    = require 'url'
  21. local stdnse = require 'stdnse'
  22.  
  23. --
  24. -- http.get( host, port, path, options )
  25. -- http.request( host, port, request, options )
  26. -- http.get_url( url, options )
  27. --
  28. -- host may either be a string or table
  29. -- port may either be a number or a table
  30. --
  31. -- the format of the return value is a table with the following structure:
  32. -- {status = 200, status-line = "HTTP/1.1 200 OK", header = {}, body ="<html>...</html>"}
  33. -- the header table has an entry for each received header with the header name being the key
  34. -- the table also has an entry named "status" which contains the http status code of the request
  35. -- in case of an error status is nil
  36.  
  37. --- Recursively copy into a table any elements from another table whose key it
  38. -- doesn't have.
  39. local function table_augment(to, from)
  40.   for k, v in pairs(from) do
  41.     if type( to[k] ) == 'table' then
  42.       table_augment(to[k], from[k])
  43.     else
  44.       to[k] = from[k]
  45.     end
  46.   end
  47. end
  48.  
  49. --- Get a suitable hostname string from the argument, which may be either a
  50. -- string or a host table.
  51. local function get_hostname(host)
  52.   if type(host) == "table" then
  53.     return host.targetname or ( host.name ~= '' and host.name ) or host.ip
  54.   else
  55.     return host
  56.   end
  57. end
  58.  
  59. --- Fetches a resource with a GET request.
  60. --
  61. -- The first argument is either a string with the hostname or a table like the
  62. -- host table passed to a portrule or hostrule. The second argument is either
  63. -- the port number or a table like the port table passed to a portrule or
  64. -- hostrule. The third argument is the path of the resource. The fourth argument
  65. -- is a table for further options. The function builds the request and calls
  66. -- <code>http.request</code>.
  67. -- @param host The host to query.
  68. -- @param port The port for the host.
  69. -- @param path The path of the resource.
  70. -- @param options A table of options, as with <code>http.request</code>.
  71. -- @return Table as described in the module description.
  72. -- @see http.request
  73. get = function( host, port, path, options )
  74.   options = options or {}
  75.  
  76.   -- Private copy of the options table, used to add default header fields.
  77.   local mod_options = {
  78.     header = {
  79.       Host = get_hostname(host),
  80.       Connection = "close",
  81.       ["User-Agent"]  = "Mozilla/5.0 (compatible; Nmap Scripting Engine; http://nmap.org/book/nse.html)"
  82.     }
  83.   }
  84.   -- Add any other options into the local copy.
  85.   table_augment(mod_options, options)
  86.  
  87.   local data = "GET " .. path .. " HTTP/1.1\r\n"
  88.  
  89.   return request( host, port, data, mod_options )
  90. end
  91.  
  92. --- Parses a URL and calls <code>http.get</code> with the result.
  93. --
  94. -- The second argument is a table for further options.
  95. -- @param u The URL of the host.
  96. -- @param options A table of options, as with <code>http.request</code>.
  97. -- @see http.get
  98. get_url = function( u, options )
  99.   local parsed = url.parse( u )
  100.   local port = {}
  101.  
  102.   port.service = parsed.scheme
  103.   port.number = parsed.port
  104.  
  105.   if not port.number then
  106.     if parsed.scheme == 'https' then
  107.       port.number = 443
  108.     else
  109.       port.number = 80
  110.     end
  111.   end
  112.  
  113.   local path = parsed.path or "/"
  114.   if parsed.query then
  115.     path = path .. "?" .. parsed.query
  116.   end
  117.  
  118.   return get( parsed.host, port, path, options )
  119. end
  120.  
  121. --- Sends request to host:port and parses the answer.
  122. --
  123. -- The first argument is either a string with the hostname or a table like the
  124. -- host table passed to a portrule or hostrule. The second argument is either
  125. -- the port number or a table like the port table passed to a portrule or
  126. -- hostrule. SSL is used for the request if <code>port.service</code> is
  127. -- <code>"https"</code> or <code>"https-alt"</code> or
  128. -- <code>port.version.service_tunnel</code> is <code>"ssl"</code>.
  129. -- The third argument is the request. The fourth argument is
  130. -- a table for further options.
  131. -- @param host The host to query.
  132. -- @param port The port on the host.
  133. -- @param data Data to send initially to the host, like a <code>GET</code> line.
  134. -- Should end in a single <code>\r\n</code>.
  135. -- @param options A table of options. It may have any of these fields:
  136. -- * <code>timeout</code>: A timeout used for socket operations.
  137. -- * <code>header</code>: A table containing additional headers to be used for the request.
  138. -- * <code>content</code>: The content of the message (content-length will be added -- set header['Content-Length'] to override)
  139. request = function( host, port, data, options )
  140.   options = options or {}
  141.  
  142.   if type(host) == 'table' then
  143.     host = host.ip
  144.   end
  145.  
  146.   local protocol = 'tcp'
  147.   if type(port) == 'table' then
  148.     if port.protocol and port.protocol ~= 'tcp' then
  149.       stdnse.print_debug(1, "http.request() supports the TCP protocol only, your request to %s cannot be completed.", host)
  150.       return nil
  151.     end
  152.     if port.service == 'https' or port.service == 'https-alt' or ( port.version and port.version.service_tunnel == 'ssl' ) then
  153.       protocol = 'ssl'
  154.     end
  155.     port = port.number
  156.   end
  157.  
  158.   -- Build the header.
  159.   for key, value in pairs(options.header or {}) do
  160.     data = data .. key .. ": " .. value .. "\r\n"
  161.   end
  162.   if(options.content ~= nil and options.header['Content-Length'] == nil) then
  163.     data = data .. "Content-Length: " .. string.len(options.content) .. "\r\n"
  164.   end
  165.   data = data .. "\r\n"
  166.  
  167.   if(options.content ~= nil) then
  168.     data = data .. options.content
  169.   end
  170.  
  171.   local result = {status=nil,["status-line"]=nil,header={},body=""}
  172.   local socket = nmap.new_socket()
  173.   local default_timeout = {}
  174.   if options.timeout then
  175.     socket:set_timeout( options.timeout )
  176.   else
  177.     default_timeout = get_default_timeout( nmap.timing_level() )
  178.     socket:set_timeout( default_timeout.connect )
  179.   end
  180.  
  181.   if not socket:connect( host, port, protocol ) then
  182.     return result
  183.   end
  184.  
  185.   if not options.timeout then
  186.     socket:set_timeout( default_timeout.request )
  187.   end
  188.  
  189.   if not socket:send( data ) then
  190.     return result
  191.   end
  192.  
  193.   -- no buffer - we want everything now!
  194.   local response = {}
  195.   while true do
  196.     local status, part = socket:receive()
  197.     if not status then
  198.       break
  199.     else
  200.       response[#response+1] = part
  201.     end
  202.   end
  203.  
  204.   socket:close()
  205.  
  206.   response = table.concat( response )
  207.  
  208.   -- try and separate the head from the body
  209.   local header, body
  210.   if response:match( "\r?\n\r?\n" ) then
  211.     header, body = response:match( "^(.-)\r?\n\r?\n(.*)$" )
  212.   else
  213.     header, body = "", response
  214.   end
  215.  
  216.   header = stdnse.strsplit( "\r?\n", header )
  217.  
  218.   local line, _
  219.  
  220.   -- build nicer table for header
  221.   local last_header, match
  222.   for number, line in ipairs( header or {} ) do
  223.     if number == 1 then
  224.       local code
  225.       _, _, code = string.find( line, "HTTP/%d\.%d (%d+)")
  226.       result.status = tonumber(code)
  227.       if code then result["status-line"] = line end
  228.     else
  229.       match, _, key, value = string.find( line, "(.+): (.*)" )
  230.       if match and key and value then
  231.         key = key:lower()
  232.         if result.header[key] then
  233.           result.header[key] = result.header[key] .. ',' .. value
  234.         else
  235.           result.header[key] = value
  236.         end
  237.         last_header = key
  238.       else
  239.         match, _, value = string.find( line, " +(.*)" )
  240.         if match and value and last_header then
  241.           result.header[last_header] = result.header[last_header] .. ',' .. value
  242.         end
  243.       end
  244.     end
  245.   end
  246.  
  247.   body_delim = ( body:match( "\r\n" ) and "\r\n" )  or
  248.                ( body:match( "\n" )   and "\n" ) or nil
  249.  
  250.   -- handle chunked encoding
  251.   if result.header['transfer-encoding'] == 'chunked' and type( body_delim ) == "string" then
  252.     body = body_delim .. body
  253.     local b = {}
  254.     local start, ptr = 1, 1
  255.     local chunk_len
  256.     local pattern = ("%s([^%s]+)%s"):format( body_delim, body_delim, body_delim )
  257.     while ( ptr < ( type( body ) == "string" and body:len() ) or 1 ) do
  258.       local hex = body:match( pattern, ptr )
  259.       if not hex then break end
  260.       chunk_len = tonumber( hex or 0, 16 ) or nil
  261.       if chunk_len then
  262.         start = ptr + hex:len() + 2*body_delim:len()
  263.         ptr = start + chunk_len
  264.         b[#b+1] = body:sub( start, ptr-1 )
  265.       end
  266.     end
  267.     body = table.concat( b )
  268.   end
  269.  
  270.   -- special case for conjoined header and body
  271.   if type( result.status ) ~= "number" and type( body ) == "string" then
  272.     local code, remainder = body:match( "HTTP/%d\.%d (%d+)(.*)") -- The Reason-Phrase will be prepended to the body :(
  273.     if code then
  274.       stdnse.print_debug( "Interesting variation on the HTTP standard.  Please submit a --script-trace output for this host (%s) to nmap-dev[at]insecure.org.", host )
  275.       result.status = tonumber(code)
  276.       body = remainder or body
  277.     end
  278.   end
  279.  
  280.   result.body = body
  281.  
  282.   return result
  283.  
  284. end
  285.  
  286. get_default_timeout = function( nmap_timing )
  287.   local timeout = {}
  288.   if nmap_timing >= 0 and nmap_timing <= 3 then
  289.     timeout.connect = 10000
  290.     timeout.request = 15000
  291.   end
  292.   if nmap_timing >= 4 then
  293.     timeout.connect = 5000
  294.     timeout.request = 10000
  295.   end
  296.   if nmap_timing >= 5 then
  297.     timeout.request = 7000
  298.   end
  299.   return timeout
  300. end
  301.