diff --git a/nselib/http.lua b/nselib/http.lua new file mode 100644 index 000000000..fb502a01e --- /dev/null +++ b/nselib/http.lua @@ -0,0 +1,149 @@ +-- See nmaps COPYING for licence +module(...,package.seeall) + +require 'stdnse' +require 'url' + +-- +-- http.get( host, port, path, options ) +-- http.request( host, port, request, options ) +-- http.get_url( url, options ) +-- +-- host may either be a string or table +-- port may either be a number or a table +-- +-- the format of the return value is a table with the following structure: +-- {status = 200, header = {}, body ="..."} +-- the header table has an entry for each received header with the header name being the key +-- the table also has an entry named "status" which contains the http status code of the request +-- in case of an error status is nil + + +-- fetch relative URL with get request +get = function( host, port, path, options ) + options = options or {} + local presets = {Host=host,Connection="close",['User-Agent']="Nmap NSE"} + if type(host) == 'table' then + presets['Host'] = ( host.name ~= '' and host.name ) or host.ip + end + + local header = options.header or {} + for key,value in pairs(presets) do + header[key] = header[key] or value + end + + local data = "GET "..path.." HTTP/1.1\r\n" + for key,value in pairs(header) do + data = data .. key .. ": " .. value .. "\r\n" + end + data = data .. "\r\n" + + return request( host, port, data, options ) +end + +-- fetch URL with get request +get_url = function( u, options ) + local parsed = url.parse( u ) + local port = {} + + port.service = parsed.scheme + port.number = parsed.port + + if not port.number then + if parsed.scheme == 'https' then + port.number = 443 + else + port.number = 80 + end + end + + local path = parsed.path or "/" + if parsed.query then + path = path .. "?" .. parsed.query + end + + return get( parsed.host, port, path, options ) +end + +-- send http request and return the result as table +-- host may be a table or the hostname +-- port may be a table or the portnumber +request = function( host, port, data, options ) + options = options or {} + + if type(host) == 'table' then + host = ( host.name ~= '' and host.name ) or host.ip + end + + local protocol = 'tcp' + if type(port) == 'table' then + if port.service == 'https' or ( port.version and port.version.service_tunnel == 'ssl' ) then + protocol = 'ssl' + end + port = port.number + end + + local result = {status=nil,header={},body=""} + local socket = nmap.new_socket() + if options.timeout then + socket:set_timeout( options.timeout ) + end + if not socket:connect( host, port, protocol ) then + return result + end + if not socket:send( data ) then + return result + end + + local buffer = stdnse.make_buffer( socket, "\r?\n" ) + + local status, line, _ + local header, body = {}, {} + + -- header loop + while true do + status, line = buffer() + if (not status or line == "") then break end + table.insert(header,line) + end + + -- build nicer table for header + local last_header, match + for number, line in pairs( header ) do + if number == 1 then + local code + _, _, code = string.find( line, "HTTP/%d\.%d (%d+)") + result.status = tonumber(code) + else + match, _, key, value = string.find( line, "(.+): (.*)" ) + if match and key and value then + key = key:lower() + if result.header[key] then + result.header[key] = result.header[key] .. ',' .. value + else + result.header[key] = value + end + last_header = key + else + match, _, value = string.find( line, " +(.*)" ) + if match and value then + result.header[last_header] = result.header[last_header] .. ',' .. value + end + end + end + end + + -- body loop + while true do + status, line = buffer() + if (not status) then break end + table.insert(body,line) + end + + socket:close() + result.body = table.concat( body, "\n" ) + + return result + +end +