diff --git a/nselib/http.lua b/nselib/http.lua
index 2decdd0cb..f09481439 100644
--- a/nselib/http.lua
+++ b/nselib/http.lua
@@ -1,33 +1,87 @@
----
--- Client-side HTTP library.
+---Implements the HTTP client protocol in a standard form that Nmap scripts can
+-- take advantage of.
--
--- The return value of each function in this module is a table with the
--- following keys: status, status-line,
--- header, and body. status is a number
--- representing the HTTP status code returned in response to the HTTP request.
--- In case of an unhandled error, status is nil.
--- status-line is the entire status message which includes the HTTP
--- version, status code, and reason phrase. The header value is a
--- table containing key-value pairs of HTTP headers received in response to the
--- request. The header names are in lower-case and are the keys to their
--- corresponding header values (e.g. header.location =
--- "http://nmap.org/"). Multiple headers of the same name are
--- concatenated and separated by commas. The body value is a string
--- containing the body of the HTTP response.
--- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+-- Because HTTP has so many uses, there are a number of interfaces to this library.
+-- The most obvious and common ones are simply get, post,
+-- and head; or, if more control is required, generic_request
+-- can be used. Thse functions do what one would expect. The get_url
+-- helper function can be used to parse and retrieve a full URL.
+--
+-- These functions return a table of values, including:
+-- * status-line - A string representing the status, such as "HTTP/1.1 200 OK"
+-- * header - An associative array representing the header. Keys are all lowercase, and standard headers, such as 'date', 'content-length', etc. will typically be present.
+-- * rawheader - A numbered array of the headers, exactly as the server sent them. While header['content-type'] might be 'text/html', rawheader[3] might be 'Content-type: text/html'.
+-- * cookies - A numbered array of the cookies the server sent. Each cookie is a table with the following keys: name, value, path, domain, and expires.
+-- * body - The full body, as retunred by the server.
+--
+-- If a script is planning on making a lot of requests, the pipeling functions can
+-- be helpful. pipeline_add queues requests in a table, and
+-- pipeline performs the requests, returning the results as an array,
+-- with the respones in the same order as the queries were added. As a simple example:
+--
+-- -- Start by defining the 'all' variable as nil
+-- local all = nil
+--
+-- -- Add two 'GET' requests and one 'HEAD' to the queue. These requests are not performed
+-- -- yet. The second parameter represents the 'options' table, which we don't need.
+-- all = http.pipeline_add('/book', nil, all)
+-- all = http.pipeline_add('/test', nil, all)
+-- all = http.pipeline_add('/monkeys', nil, all)
+--
+-- -- Perform all three requests as parallel as Nmap is able to
+-- local results = http.pipeline('nmap.org', 80, all)
+--
+--
+-- At this point, results is an array with three elements. Each element
+-- is a table containing the HTTP result, as discussed above.
+--
+-- One more interface provided by the HTTP library helps scripts determine whether or not
+-- a page exists. The identify_404 function will try several URLs on the
+-- server to determine what the server's 404 pages look like. It will attempt to identify
+-- customized 404 pages that may not return the actual status code 404. If successful,
+-- the function page_exists can then be used to determine whether no not
+-- a page existed.
+--
+-- Some other miscellaneous functions that can come in handy are response_contains,
+-- can_use_head, and save_path. See the appropriate documentation
+-- for them.
+--
+-- The response to each function is typically a table on success or nil on failure. If
+-- a table is returned, the following keys will exist:
+-- status-line: The HTTP status line; for example, "HTTP/1.1 200 OK" (note: this is followed by a newline)
+-- status: The HTTP status value; for example, "200"
+-- header: A table of header values, where the keys are lowercase and the values are exactly what the server sent
+-- rawheader: A list of header values as "name: value" strings, in the exact format and order that the server sent them
+-- cookies: A list of cookies that the server is sending. Each cookie is a table containing the keys name, value, and path. This table can be sent to the server in subsequent responses in the options table to any function (see below).
+-- body: The body of the response
+--
+-- Many of the functions optionally allow an 'options' table. This table can alter the HTTP headers
+-- or other values like the timeout. The following are valid values in 'options' (note: not all
+-- options will necessarily affect every function):
+-- * timeout: A timeout used for socket operations.
+-- * header: A table containing additional headers to be used for the request. For example, options['header']['Content-Type'] = 'text/xml'
+-- * content: The content of the message (content-length will be added -- set header['Content-Length'] to override). This can be either a string, which will be directly added as the body of the message, or a table, which will have each key=value pair added (like a normal POST request).
+-- * cookies: A list of cookies as either a string, which will be directly sent, or a table. If it's a table, the following fields are recognized:
+-- ** name
+-- ** value
+-- ** path
+-- * auth: A table containing the keys username and password, which will be used for HTTP Basic authentication
+-- * bypass_cache: Do not perform a lookup in the local HTTP cache.
+-- * no_cache: Do not save the result of this request to the local HTTP cache.
+-- * no_cache_body: Do not save the body of the response to the local HTTP cache.
+--
-- @args http-max-cache-size The maximum memory size (in bytes) of the cache.
--
-- @args http.useragent The value of the User-Agent header field sent with
-- requests. By default it is
-- "Mozilla/5.0 (compatible; Nmap Scripting Engine; http://nmap.org/book/nse.html)".
-- A value of the empty string disables sending the User-Agent header field.
--- @args pipeline If set, it represents the number of HTTP requests that'll be
+--
+-- @args http.pipeline If set, it represents the number of HTTP requests that'll be
-- pipelined (ie, sent in a single request). This can be set low to make
-- debugging easier, or it can be set high to test how a server reacts (its
-- chosen max is ignored).
-local MAX_CACHE_SIZE = "http-max-cache-size";
-
local coroutine = require "coroutine";
local table = require "table";
@@ -42,17 +96,7 @@ module(... or "http",package.seeall)
---Use ssl if we have it
local have_ssl = (nmap.have_ssl() and pcall(require, "openssl"))
-local USER_AGENT
-do
- local arg = nmap.registry.args and nmap.registry.args["http.useragent"]
- if arg and arg == "" then
- USER_AGENT = nil
- elseif arg then
- USER_AGENT = arg
- else
- USER_AGENT = "Mozilla/5.0 (compatible; Nmap Scripting Engine; http://nmap.org/book/nse.html)"
- end
-end
+local USER_AGENT = stdnse.get_script_args('http.useragent') or "Mozilla/5.0 (compatible; Nmap Scripting Engine; http://nmap.org/book/nse.html)"
-- Recursively copy a table.
-- Only recurs when a value is a table, other values are copied by assignment.
@@ -193,6 +237,86 @@ local function skip_lws(s, pos)
end
end
+
+---Validate an 'options' table, which is passed to a number of the HTTP functions. It is
+-- often difficult to track down a mistake in the options table, and requires fiddling
+-- with the http.lua source, but this should make that a lot easier.
+local function validate_options(options)
+ local bad = false
+
+ if(options == nil) then
+ return true
+ end
+
+ for key, value in pairs(options) do
+ if(key == 'timeout') then
+ if(type(tonumber(value)) ~= 'number') then
+ stdnse.print_debug(1, 'http: options.timeout contains a non-numeric value')
+ bad = true
+ end
+ elseif(key == 'header') then
+ if(type(value) ~= 'table') then
+ stdnse.print_debug(1, "http: options.header should be a table")
+ bad = true
+ end
+ elseif(key == 'content') then
+ if(type(value) ~= 'string' and type(value) ~= 'table') then
+ stdnse.print_debug(1, "http: options.content should be a string or a table")
+ bad = true
+ end
+ elseif(key == 'cookies') then
+ if(type(value) == 'table') then
+ for cookie in pairs(value) do
+ for cookie_key, cookie_value in pairs(value) do
+ if(cookie_key == 'name') then
+ if(type(cookie_value) ~= 'string') then
+ stdnse.print_debug(1, "http: options.cookies[i].name should be a string")
+ bad = true
+ end
+ elseif(cookie_key == 'value') then
+ if(type(cookie_value) ~= 'string') then
+ stdnse.print_debug(1, "http: options.cookies[i].value should be a string")
+ bad = true
+ end
+ elseif(cookie_key == 'path') then
+ if(type(cookie_value) ~= 'string') then
+ stdnse.print_debug(1, "http: options.cookies[i].path should be a string")
+ bad = true
+ end
+ else
+ stdnse.print_debug(1, "http: Unknown field in cookie table: %s", cookie_key)
+ bad = true
+ end
+ end
+ end
+ elseif(type(value) ~= 'string') then
+ stdnse.print_debug(1, "http: options.cookies should be a table or a string")
+ bad = true
+ end
+ elseif(key == 'auth') then
+ if(type(value) == 'table') then
+ if(value['username'] == nil or value['password'] == nil) then
+ stdnse.print_debug(1, "http: options.auth should contain both a 'username' and a 'password' key")
+ bad = true
+ end
+ else
+ stdnse.print_debug(1, "http: options.auth should be a table")
+ bad = true
+ end
+ elseif(key == 'bypass_cache' or key == 'no_cache' or key == 'no_cache_body') then
+ if(type(value) ~= 'boolean') then
+ stdnse.print_debug(1, "http: options.bypass_cache, options.no_cache, and options.no_cache_body must be boolean values")
+ bad = true
+ end
+ else
+ stdnse.print_debug(1, "http: Unknown key in the options table: %s", key)
+ end
+ end
+
+ return not(bad)
+end
+
+
-- The following recv functions, and the function next_response
-- follow a common pattern. They each take a partial argument
-- whose value is data that has been read from the socket but not yet used in
@@ -693,7 +817,9 @@ end
-- @return The max number of requests on a keep-alive connection
local function getPipelineMax(response)
-- Allow users to override this with a script-arg
- if nmap.registry.args.pipeline ~= nil then
+ local pipeline = stdnse.get_script_args({'http.pipeline', 'pipeline'})
+
+ if(pipeline) then
return tonumber(nmap.registry.args.pipeline)
end
@@ -718,7 +844,7 @@ end
-- @param cookies A cookie jar just like the table returned parse_set_cookie.
-- @param path If the argument exists, only cookies with this path are included to the request
-- @return A string to be added to the mod_options table
-function buildCookies(cookies, path)
+local function buildCookies(cookies, path)
local cookie = ""
if type(cookies) == 'string' then return cookies end
for i, ck in ipairs(cookies or {}) do
@@ -731,7 +857,6 @@ function buildCookies(cookies, path)
end
-- HTTP cache.
-
-- Cache of GET and HEAD requests. Uses <"host:port:path", record>.
-- record is in the format:
-- result: The result from http.get or http.head
@@ -741,7 +866,8 @@ end
local cache = {size = 0};
local function check_size (cache)
- local max_size = tonumber(nmap.registry.args[MAX_CACHE_SIZE] or 1e6);
+ local max_size = tonumber(stdnse.get_script_args({'http.max-cache-size', 'http-max-cache-size'}) or 1e6);
+
local size = cache.size;
if size > max_size then
@@ -772,6 +898,10 @@ end
local WORKING = setmetatable({}, {__mode = "v"});
local function lookup_cache (method, host, port, path, options)
+ if(not(validate_options(options))) then
+ return nil
+ end
+
options = options or {};
local bypass_cache = options.bypass_cache; -- do not lookup
local no_cache = options.no_cache; -- do not save result
@@ -892,7 +1022,10 @@ end
-- * auth: A table containing the keys username and password.
-- @return A request string.
-- @see generic_request
-local build_request = function(host, port, method, path, options)
+local function build_request(host, port, method, path, options)
+ if(not(validate_options(options))) then
+ return nil
+ end
options = options or {}
-- Private copy of the options table, used to add default header fields.
@@ -949,25 +1082,6 @@ local build_request = function(host, port, method, path, options)
return request_line .. "\r\n" .. stdnse.strjoin("\r\n", header) .. "\r\n\r\n" .. (body or "")
end
---- Do a single request with the given parameters and return the response.
--- Any 1XX (informational) responses are discarded.
---
--- @param host The host to connect to.
--- @param port The port to connect to.
--- @param method The method to use, a string like "GET" or "HEAD".
--- @param path The path to retrieve.
--- @param options A table of other parameters. It may have any of these fields:
--- * timeout: A timeout used for socket operations.
--- * header: A table containing additional headers to be used for the request.
--- * content: The content of the message (content-length will be added -- set header['Content-Length'] to override)
--- * cookies: A table of cookies in the form returned by parse_set_cookie.
--- * auth: A table containing the keys username and password.
--- @return A table as described in the module description.
--- @see request
-generic_request = function(host, port, method, path, options)
- return request(host, port, build_request(host, port, method, path, options), options)
-end
-
--- Send a string to a host and port and return the HTTP result. This function
-- is like generic_request, to be used when you have a ready-made
-- request, not a collection of request parameters.
@@ -982,7 +1096,10 @@ end
-- * auth: A table containing the keys username and password.
-- @return A table as described in the module description.
-- @see generic_request
-request = function(host, port, data, options)
+local function request(host, port, data, options)
+ if(not(validate_options(options))) then
+ return nil
+ end
local method
local header, partial
local response
@@ -1023,22 +1140,43 @@ request = function(host, port, data, options)
return response
end
---- Fetches a resource with a GET request.
+---Do a single request with a given method. The response is returned as the standard
+-- response table (see the module documentation).
--
--- The first argument is either a string with the hostname or a table like the
--- host table passed to a portrule or hostrule. The second argument is either
--- the port number or a table like the port table passed to a portrule or
--- hostrule. The third argument is the path of the resource. The fourth argument
--- is a table for further options.
--- @param host The host to query.
--- @param port The port for the host.
--- @param path The path of the resource.
--- @param options A table of options, as with http.generic_request, with the following additional options understood:
--- * bypass_cache: The contents of the cache is ignored for the request (method == "GET" or "HEAD")
--- * no_cache: The result of the request is not saved in the cache (method == "GET" or "HEAD").
--- * no_cache_body: The body of the request is not saved in the cache (method == "GET" or "HEAD").
--- @return Table as described in the module description.
-get = function(host, port, path, options)
+-- The get, head, and post functions are simple
+-- wrappers around generic_request.
+--
+-- Any 1XX (informational) responses are discarded.
+--
+-- @param host The host to connect to.
+-- @param port The port to connect to.
+-- @param method The method to use; for example, 'GET', 'HEAD', etc.
+-- @param path The path to retrieve.
+-- @param options [optional] A table that lets the caller control socket timeouts, HTTP headers, and other parameters. For full documentation, see the module documentation (above).
+-- @return nil if an error occurs; otherwise, a table as described in the module documentation.
+-- @see request
+function generic_request(host, port, method, path, options)
+ if(not(validate_options(options))) then
+ return nil
+ end
+ return request(host, port, build_request(host, port, method, path, options), options)
+end
+
+---Fetches a resource with a GET request and returns the result as a table. This is a simple
+-- wraper around generic_request, with the added benefit of having local caching.
+-- This caching can be controlled in the options array, see module documentation
+-- for more information.
+--
+-- @param host The host to connect to.
+-- @param port The port to connect to.
+-- @param path The path to retrieve.
+-- @param options [optional] A table that lets the caller control socket timeouts, HTTP headers, and other parameters. For full documentation, see the module documentation (above).
+-- @return nil if an error occurs; otherwise, a table as described in the module documentation.
+-- @see http.generic_request
+function get(host, port, path, options)
+ if(not(validate_options(options))) then
+ return nil
+ end
local response, state = lookup_cache("GET", host, port, path, options);
if response == nil then
response = generic_request(host, port, "GET", path, options)
@@ -1047,14 +1185,17 @@ get = function(host, port, path, options)
return response
end
---- Parses a URL and calls http.get with the result.
+---Parses a URL and calls http.get with the result. The URL can contain
+-- all the standard fields, protocol://host:port/path
--
--- The second argument is a table for further options.
-- @param u The URL of the host.
--- @param options A table of options, as with http.request.
--- @return Table as described in the module description.
+-- @param options [optional] A table that lets the caller control socket timeouts, HTTP headers, and other parameters. For full documentation, see the module documentation (above).
+-- @return nil if an error occurs; otherwise, a table as described in the module documentation.
-- @see http.get
-get_url = function( u, options )
+function get_url( u, options )
+ if(not(validate_options(options))) then
+ return nil
+ end
local parsed = url.parse( u )
local port = {}
@@ -1077,22 +1218,19 @@ get_url = function( u, options )
return get( parsed.host, port, path, options )
end
---- Fetches a resource with a HEAD request.
+---Fetches a resource with a HEAD request. Like get, this is a simple
+-- wrapper around generic_request with response caching.
--
--- The first argument is either a string with the hostname or a table like the
--- host table passed to a portrule or hostrule. The second argument is either
--- the port number or a table like the port table passed to a portrule or
--- hostrule. The third argument is the path of the resource. The fourth argument
--- is a table for further options.
--- @param host The host to query.
--- @param port The port for the host.
--- @param path The path of the resource.
--- @param options A table of options, as with http.generic_request, with the following additional options understood:
--- * bypass_cache: The contents of the cache is ignored for the request (method == "GET" or "HEAD")
--- * no_cache: The result of the request is not saved in the cache (method == "GET" or "HEAD").
--- * no_cache_body: The body of the request is not saved in the cache (method == "GET" or "HEAD").
--- @return Table as described in the module description.
-head = function(host, port, path, options)
+-- @param host The host to connect to.
+-- @param port The port to connect to.
+-- @param path The path to retrieve.
+-- @param options [optional] A table that lets the caller control socket timeouts, HTTP headers, and other parameters. For full documentation, see the module documentation (above).
+-- @return nil if an error occurs; otherwise, a table as described in the module documentation.
+-- @see http.generic_request
+function head(host, port, path, options)
+ if(not(validate_options(options))) then
+ return nil
+ end
local response, state = lookup_cache("HEAD", host, port, path, options);
if response == nil then
response = generic_request(host, port, "HEAD", path, options)
@@ -1101,24 +1239,22 @@ head = function(host, port, path, options)
return response;
end
---- Fetches a resource with a POST request.
+---Fetches a resource with a POST request. Like get, this is a simple
+-- wrapper around generic_request except that postdata is handled
+-- properly.
--
--- The first argument is either a string with the hostname or a table like the
--- host table passed to a portrule or hostrule. The second argument is either
--- the port number or a table like the port table passed to a portrule or
--- hostrule. The third argument is the path of the resource. The fourth argument
--- is a table for further options. The fifth argument is ignored. The sixth
--- argument is a table with data to be posted.
--- @param host The host to query.
--- @param port The port for the host.
--- @param path The path of the resource.
--- @param options A table of options, as with http.request.
+-- @param host The host to connect to.
+-- @param port The port to connect to.
+-- @param path The path to retrieve.
+-- @param options [optional] A table that lets the caller control socket timeouts, HTTP headers, and other parameters. For full documentation, see the module documentation (above).
-- @param ignored Ignored for backwards compatibility.
--- @param postdata A string or a table of data to be posted. If a table, the
--- keys and values must be strings, and they will be encoded into an
--- application/x-www-form-encoded form submission.
--- @return Table as described in the module description.
-post = function( host, port, path, options, ignored, postdata )
+-- @param postdata A string or a table of data to be posted. If a table, the keys and values must be strings, and they will be encoded into an application/x-www-form-encoded form submission.
+-- @return nil if an error occurs; otherwise, a table as described in the module documentation.
+-- @see http.generic_request
+function post( host, port, path, options, ignored, postdata )
+ if(not(validate_options(options))) then
+ return nil
+ end
local mod_options = {
content = postdata,
}
@@ -1126,67 +1262,73 @@ post = function( host, port, path, options, ignored, postdata )
return generic_request(host, port, "POST", path, mod_options)
end
---- Builds a request to be used in a pipeline
---
--- @param host The host to query.
--- @param port The port for the host.
--- @param path The path of the resource.
--- @param options A table of options, as with http.generic_request.
--- @param ignored Ignored for backwards compatibility.
--- @param allReqs A table with all the pipeline requests
--- @param method The HTTP method (GET, POST, HEAD, etc)
--- @return Table with the pipeline get requests (plus this new one)
+-- Deprecated pipeline functions
+function pGet( host, port, path, options, ignored, allReqs )
+ stdnse.print_debug(1, "WARNING: pGet() is deprecated. Use pipeline_add() intead.")
+ return pipeline_add(path, options, allReqs, 'GET')
+end
+function pHead( host, port, path, options, ignored, allReqs )
+ stdnse.print_debug(1, "WARNING: pHead() is deprecated. Use pipeline_add instead.")
+ return pipeline_add(path, options, allReqs, 'HEAD')
+end
function addPipeline(host, port, path, options, ignored, allReqs, method)
- allReqs = allReqs or {}
+ stdnse.print_debug(1, "WARNING: addPipeline() is deprecated! Use pipeline_add instead.")
+ return pipeline_add(path, options, allReqs, method)
+end
+function pipeline(host, port, allReqs)
+ stdnse.print_debug(1, "WARNING: pipeline() is deprecated. Use pipeline_go() instead.")
+ return pipeline_go(host, port, allReqs)
+end
+
+
+---Adds a pending request to the HTTP pipeline. The HTTP pipeline is a set of requests that will
+-- all be sent at the same time, or as close as the server allows. This allows more efficient
+-- code, since requests are automatically buffered and sent simultaneously.
+--
+-- The all_requests argument contains the current list of queued requests (if this
+-- is the first time calling pipeline_add, it should be nil). After
+-- adding the request to end of the queue, the queue is returned and can be passed to the next
+-- pipeline_add call.
+--
+-- When all requests have been queued, call pipeline_go with the all_requests table
+-- that has been built.
+--
+-- @param path The path to retrieve.
+-- @param options [optional] A table that lets the caller control socket timeouts, HTTP headers, and other parameters. For full documentation, see the module documentation (above).
+-- @param all_requests [optional] The current pipeline queue (retunred from a previous add_pipeline call), or nil if it's the first call.
+-- @param method [optional] The HTTP method ('get', 'head', 'post', etc). Default: 'get'.
+-- @return Table with the pipeline get requests (plus this new one)
+-- @see http.pipeline_go
+function pipeline_add(path, options, all_requests, method)
+ if(not(validate_options(options))) then
+ return nil
+ end
+ method = method or 'GET'
+ all_requests = all_requests or {}
+
local mod_options = {
header = {
["Connection"] = "keep-alive"
}
}
table_augment(mod_options, options or {})
- -- This value is intended to be unpacked into arguments to build_request.
- local object = { host, port, method, path, mod_options }
- object.method = object[3]
- object.options = object[5]
- allReqs[#allReqs + 1] = object
- return allReqs
+
+ local object = { method=method, path=path, options=mod_options }
+ table.insert(all_requests, object)
+
+ return all_requests
end
---- Builds a get request to be used in a pipeline request
+---Performs all queued requests in the all_requests variable (created by the
+-- pipeline_add function). Returns an array of responses, each of
+-- which is a table as defined in the module documentation above.
--
--- @param host The host to query.
--- @param port The port for the host.
--- @param path The path of the resource.
--- @param options A table of options, as with http.generic_request.
--- @param ignored Ignored for backwards compatibility.
--- @param allReqs A table with all the pipeline requests
--- @return Table with the pipeline get requests (plus this new one)
-function pGet( host, port, path, options, ignored, allReqs )
- return addPipeline(host, port, path, options, ignored, allReqs, 'GET')
-end
-
---- Builds a Head request to be used in a pipeline request
---
--- @param host The host to query.
--- @param port The port for the host.
--- @param path The path of the resource.
--- @param options A table of options, as with http.generic_request.
--- @param ignored Ignored for backwards compatibility.
--- @param allReqs A table with all the pipeline requests
--- @return Table with the pipeline get requests (plus this new one)
-function pHead( host, port, path, options, ignored, allReqs )
- return addPipeline(host, port, path, options, ignored, allReqs, 'HEAD')
-end
-
----Performs pipelined that are in allReqs to the resource. Return an array of
--- response tables.
---
--- @param host The host to query.
--- @param port The port for the host.
--- @param allReqs A table with all the previously built pipeline requests
--- @return A table with multiple http response tables
-pipeline = function(host, port, allReqs)
- stdnse.print_debug("Total number of pipelined requests: " .. #allReqs)
+-- @param host The host to connect to.
+-- @param port The port to connect to.
+-- @param all_requests A table with all the previously built pipeline requests
+-- @return A list of responses, in the same order as the requests were queued. Each response is a table as described in the module documentation.
+function pipeline_go(host, port, all_requests)
+ stdnse.print_debug("Total number of pipelined requests: " .. #all_requests)
local responses
local response
local partial
@@ -1194,7 +1336,7 @@ pipeline = function(host, port, allReqs)
responses = {}
-- Check for an empty request
- if (#allReqs == 0) then
+ if (#all_requests == 0) then
stdnse.print_debug(1, "Warning: empty set of requests passed to http.pipeline()")
return responses
end
@@ -1203,42 +1345,42 @@ pipeline = function(host, port, allReqs)
-- We'll try a first request with keep-alive, just to check if the server
-- supports and how many requests we can send into one socket!
- -- Each element in allReqs is an array whose contents can be unpacked into
- -- arguments to build_request. Each element also has method and options keys
- -- whose values are the same as that of the corresponding integer index.
- socket, partial, bopt = comm.tryssl(host, port, build_request(unpack(allReqs[1])), {connect_timeout=5000, request_timeout=3000, recv_before=false})
+ local request = build_request(host, port, all_requests[1].method, all_requests[1].path, all_requests[1].options)
+
+ socket, partial, bopt = comm.tryssl(host, port, request, {connect_timeout=5000, request_timeout=3000, recv_before=false})
if not socket then
return nil
end
- response, partial = next_response(socket, allReqs[1].method, partial)
+ response, partial = next_response(socket, all_requests[1].method, partial)
if not response then
return nil
end
- responses[#responses + 1] = response
+ table.insert(responses, response)
local limit = getPipelineMax(response)
local count = 1
- stdnse.print_debug("Number of requests allowed by pipeline: " .. limit)
+ stdnse.print_debug(1, "Number of requests allowed by pipeline: " .. limit)
- while #responses < #allReqs do
+ while #responses < #all_requests do
local j, batch_end
-- we build a big string with many requests, upper limited by the var "limit"
local requests = ""
- if #responses + limit < #allReqs then
+ if #responses + limit < #all_requests then
batch_end = #responses + limit
else
- batch_end = #allReqs
+ batch_end = #all_requests
end
j = #responses + 1
while j <= batch_end do
if j == batch_end then
- allReqs[j].options.header["Connection"] = "close"
+ all_requests[j].options.header["Connection"] = "close"
end
- requests = requests .. build_request(unpack(allReqs[j]))
+
+ requests = requests .. build_request(host, port, all_requests[j].method, all_requests[j].path, all_requests[j].options)
j = j + 1
end
@@ -1251,8 +1393,8 @@ pipeline = function(host, port, allReqs)
socket:set_timeout(10000)
socket:send(requests)
- while #responses < #allReqs do
- response, partial = next_response(socket, allReqs[#responses + 1].method, partial)
+ while #responses < #all_requests do
+ response, partial = next_response(socket, all_requests[#responses + 1].method, partial)
if not response then
break
end
@@ -1282,7 +1424,7 @@ end
-- Skip whitespace (that has already been folded from LWS). See RFC 2616,
-- section 2.2, definition of LWS.
-local skip_space = function(s, pos)
+local function skip_space(s, pos)
local _
_, pos = string.find(s, "^[ \t]*", pos)
@@ -1291,7 +1433,7 @@ local skip_space = function(s, pos)
end
-- See RFC 2616, section 2.2.
-local read_token = function(s, pos)
+local function read_token(s, pos)
local _, token
pos = skip_space(s, pos)
@@ -1307,7 +1449,7 @@ end
-- See RFC 2616, section 2.2. Here we relax the restriction that TEXT may not
-- contain CTLs.
-local read_quoted_string = function(s, pos)
+local function read_quoted_string(s, pos)
local chars = {}
if string.sub(s, pos, pos) ~= "\"" then
@@ -1338,7 +1480,7 @@ local read_quoted_string = function(s, pos)
return pos + 1, table.concat(chars)
end
-local read_token_or_quoted_string = function(s, pos)
+local function read_token_or_quoted_string(s, pos)
pos = skip_space(s, pos)
if string.sub(s, pos, pos) == "\"" then
return read_quoted_string(s, pos)
@@ -1405,7 +1547,7 @@ end
-- See RFC 2617, section 1.2. This function returns a table with keys "scheme"
-- and "params".
-local read_auth_challenge = function(s, pos)
+local function read_auth_challenge(s, pos)
local _, scheme, params
pos, scheme = read_token(s, pos)
@@ -1458,14 +1600,13 @@ local read_auth_challenge = function(s, pos)
return pos, { scheme = scheme, params = params }
end
----
--- Parses the WWW-Authenticate header as described in RFC 2616, section 14.47
+---Parses the WWW-Authenticate header as described in RFC 2616, section 14.47
-- and RFC 2617, section 1.2. The return value is an array of challenges. Each
-- challenge is a table with the keys scheme and
-- params.
-- @param s The header value text.
-- @return An array of challenges, or nil on error.
-parse_www_authenticate = function(s)
+function parse_www_authenticate(s)
local challenges = {}
local pos
@@ -1484,11 +1625,11 @@ parse_www_authenticate = function(s)
end
---- Take the data returned from a HTTP request and return the status string.
--- Useful for print_debug messages and even for advanced output.
+---Take the data returned from a HTTP request and return the status string.
+-- Useful for stdnse.print_debug messages and even advanced output.
--
--- @param data The data returned by a HTTP request (can be nil or empty)
--- @return The status string, the status code, or "".
+-- @param data The response table from any HTTP request
+-- @return The best status string we could find: either the actual status string, the status code, or "".
function get_status_string(data)
-- Make sure we have valid data
if(data == nil) then
@@ -1515,11 +1656,14 @@ end
-- check like this because can't always rely on OPTIONS to tell the truth.
--
-- Note: If identify_404 returns a 200 status, HEAD requests
--- should be disabled.
+-- should be disabled. Sometimes, servers use a 200 status code with a message
+-- explaining that the page wasn't found. In this case, to actually identify
+-- a 404 page, we need the full body that a HEAD request doesn't supply.
+-- This is determined automatically if the result_404 field is
+-- set.
--
-- @param host The host object.
--- @param port The port to use -- note that SSL will automatically be used, if
--- necessary.
+-- @param port The port to use.
-- @param result_404 [optional] The result when an unknown page is requested.
-- This is returned by identify_404. If the 404 page returns a
-- 200 code, then we disable HEAD requests.
@@ -1565,43 +1709,6 @@ function can_use_head(host, port, result_404, path)
return false
end
---- Request the root folder, /, in order to determine if we can use a GET
--- request against this server. If the server returns 301 Moved Permanently or
--- 401 Authentication Required, then tests against this server will most likely
--- fail.
---
--- TODO: It's probably worthwhile adding a script-arg that will ignore the
--- output of this function and always scan servers.
---
--- @param host The host object.
--- @param port The port to use -- note that SSL will automatically be used, if
--- necessary.
--- @return (result, message) result is a boolean: true means we're good to go,
--- false means there's an error. The error is returned in message.
-function can_use_get(host, port)
- stdnse.print_debug(1, "Checking if a GET request is going to work out")
-
- -- Try getting the root directory
- local data = http.get( host, port, '/' )
- if(data == nil) then
- stdnse.print_debug(1, string.format("GET request for '/' returned nil when verifying host %s", host.ip))
- else
- -- If the root directory is a permanent redirect, we're going to run into troubles
- if(data.status == 301 or data.status == 302) then
- if(data.header and data.header.location) then
- stdnse.print_debug(1, string.format("GET request for '/' returned a forwarding address (%s) -- try scanning %s instead, if possible", get_status_string(data), data.header.location))
- end
- end
-
- -- If the root directory requires authentication, we're outta luck
- if(data.status == 401) then
- stdnse.print_debug(1, string.format("Root directory requires authentication (%s), scans may not work", get_status_string(data)))
- end
- end
-
- return true
-end
-
--- Try and remove anything that might change within a 404. For example:
-- * A file path (includes URI)
-- * A time
@@ -1770,17 +1877,11 @@ end
--identify_404, since they will generally be used together.
--
-- @param data The data returned by the HTTP request
--- @param result_404 The status code to expect for non-existent pages. This is
--- returned by identify_404.
--- @param known_404 The 404 page itself, if result_404 is 200. If
--- result_404 is something else, this parameter is ignored and can
--- be set to nil. This is returned by identfy_404.
--- @param page The page being requested (used in error messages).
--- @param displayall [optional] If set to true, "true", or "1", displays all
--- error codes that don't look like a 404 instead of just 200 OK and 401
--- Authentication Required.
--- @return A boolean value: true if the page appears to exist, and false if it
--- does not.
+-- @param result_404 The status code to expect for non-existent pages. This is returned by identify_404.
+-- @param known_404 The 404 page itself, if result_404 is 200. If result_404 is something else, this parameter is ignored and can be set to nil. This is returned by identfy_404.
+-- @param page The page being requested (used in error messages).
+-- @param displayall [optional] If set to true, don't exclude non-404 errors (such as 500).
+-- @return A boolean value: true if the page appears to exist, and false if it does not.
function page_exists(data, result_404, known_404, page, displayall)
if(data and data.status) then
-- Handle the most complicated case first: the "200 Ok" response
@@ -1836,13 +1937,11 @@ end
-- The search text is treated as a Lua pattern.
--
--@param response The full response table from a HTTP request.
---@param pattern The pattern we're searching for. Don't forget to escape '-', for example, 'Content%-type'.
--- the pattern can also contain captures, like 'abc(.*)def', which will be returned if successful.
---@param case_sensitive [optional] Set to true for case-sensitive searches. Default: not case sensitive.
+--@param pattern The pattern we're searching for. Don't forget to escape '-', for example, 'Content%-type'. The pattern can also contain captures, like 'abc(.*)def', which will be returned if successful.
+--@param case_sensitive [optional] Set to true for case-sensitive searches. Default: not case sensitive.
--@return result True if the string matched, false otherwise
--@return matches An array of captures from the match, if any
function response_contains(response, pattern, case_sensitive)
-
local result, _
local m = {}
@@ -1889,9 +1988,7 @@ end
-- a path or protocol and url are required.
--
--@param url The incoming URL to parse
---@return result A table containing the result, which can have the following fields: protocol,
--- hostname, port, uri, querystring. All fields are strings except querystring,
--- which is a table containing name=value pairs.
+--@return result A table containing the result, which can have the following fields: protocol, hostname, port, uri, querystring. All fields are strings except querystring, which is a table containing name=value pairs.
function parse_url(url)
local result = {}
@@ -1972,6 +2069,14 @@ end
---This function should be called whenever a valid path (a path that doesn't contain a known
-- 404 page) is discovered. It will add the path to the registry in several ways, allowing
-- other scripts to take advantage of it in interesting ways.
+--
+--@param host The host the path was discovered on (not necessarily the host being scanned).
+--@param port The port the path was discovered on (not necessarily the port being scanned).
+--@param path The path discovered. Calling this more than once with the same path is okay; it'll update the data as much as possible instead of adding a duplicate entry
+--@param status [optional] The status code (200, 404, 500, etc). This can be left off if it isn't known.
+--@param links_to [optional] A table of paths that this page links to.
+--@param linked_from [optional] A table of paths that link to this page.
+--@param contenttype [optional] The content-type value for the path, if it's known.
function save_path(host, port, path, status, links_to, linked_from, contenttype)
-- Make sure we have a proper hostname and port
host = stdnse.get_hostname(host)
@@ -2049,7 +2154,7 @@ function save_path(host, port, path, status, links_to, linked_from, contenttype)
end
end
-get_default_timeout = function( nmap_timing )
+local function get_default_timeout( nmap_timing )
local timeout = {}
if nmap_timing >= 0 and nmap_timing <= 3 then
timeout.connect = 10000
diff --git a/scripts/http-enum.nse b/scripts/http-enum.nse
index cf8f7f0a8..9b5284981 100644
--- a/scripts/http-enum.nse
+++ b/scripts/http-enum.nse
@@ -351,17 +351,17 @@ action = function(host, port)
for i = 1, #fingerprints, 1 do
-- Add each path. The order very much matters here.
for j = 1, #fingerprints[i].probes, 1 do
- all = http.addPipeline(host, port, basepath .. fingerprints[i].probes[j].path, nil, nil, all, fingerprints[i].probes[j].method or 'GET')
+ all = http.pipeline_add(basepath .. fingerprints[i].probes[j].path, nil, all, fingerprints[i].probes[j].method or 'GET')
end
end
-- Perform all the requests.
- local results = http.pipeline(host, port, all, nil)
+ local results = http.pipeline_go(host, port, all, nil)
-- Check for http.pipeline error
if(results == nil) then
- stdnse.print_debug(1, "http-enum: http.pipeline encountered an error")
- return stdnse.format_output(false, "http.pipeline encountered an error")
+ stdnse.print_debug(1, "http-enum: http.pipeline_go encountered an error")
+ return stdnse.format_output(false, "http.pipeline_go encountered an error")
end
-- Loop through the fingerprints. Note that for each fingerprint, we may have multiple results
diff --git a/scripts/http-userdir-enum.nse b/scripts/http-userdir-enum.nse
index c38b4febd..b41a6e62d 100644
--- a/scripts/http-userdir-enum.nse
+++ b/scripts/http-userdir-enum.nse
@@ -68,18 +68,6 @@ action = function(host, port)
-- Check if we can use HEAD requests
local use_head = http.can_use_head(host, port, result_404)
- -- If we can't use HEAD, make sure we can use GET requests
- if(use_head == false) then
- local result, err = http.can_use_get(host, port)
- if(result == false) then
- if(nmap.debugging() > 0) then
- return "ERROR: " .. err
- else
- return nil
- end
- end
- end
-
-- Queue up the checks
local all = {}
local i
@@ -90,13 +78,13 @@ action = function(host, port)
end
if(use_head) then
- all = http.pHead(host, port, "/~" .. usernames[i], nil, nil, all)
+ all = http.pipeline_add("/~" .. usernames[i], nil, 'HEAD')
else
- all = http.pGet(host, port, "/~" .. usernames[i], nil, nil, all)
+ all = http.pipeline_add("/~" .. usernames[i], nil, 'GET')
end
end
- local results = http.pipeline(host, port, all, nil)
+ local results = http.pipeline_go(host, port, all)
-- Check for http.pipeline error
if(results == nil) then
diff --git a/scripts/sql-injection.nse b/scripts/sql-injection.nse
index b3315e956..f9181cefa 100644
--- a/scripts/sql-injection.nse
+++ b/scripts/sql-injection.nse
@@ -101,11 +101,10 @@ Creates a pipeline table and returns the result
]]--
local function inject(host, port, injectable)
local all = {}
- local pOpts = {}
for k, v in pairs(injectable) do
- all = http.pGet(host, port, v, nil, nil, all)
+ all = http.pipeline_add(v, nil, all, 'GET')
end
- return http.pipeline(host, port, all, pOpts)
+ return http.pipeline_go(host, port, all)
end
--[[