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

  1. --- URI parsing, composition, and relative URL resolution.
  2. -- @author Diego Nehab
  3. -- @author Eddie Bell <ejlbell@gmail.com>
  4.  
  5. --[[
  6. URI parsing, composition and relative URL resolution
  7. LuaSocket toolkit.
  8. Author: Diego Nehab
  9. RCS ID: $Id: url.lua,v 1.37 2005/11/22 08:33:29 diego Exp $
  10.  
  11. parse_query and build_query added For nmap (Eddie Bell <ejlbell@gmail.com>) 
  12. --]]
  13.  
  14. -----------------------------------------------------------------------------
  15. -- Declare module
  16. -----------------------------------------------------------------------------
  17. local string = require("string")
  18. local base = _G
  19. local table = require("table")
  20. module(... or "url",package.seeall)
  21.  
  22. _VERSION = "URL 1.0"
  23.  
  24.         --[[ Internal functions --]]
  25.  
  26. local function make_set(t)
  27.     local s = {}
  28.     for i,v in base.ipairs(t) do
  29.         s[t[i]] = 1
  30.     end
  31.     return s
  32. end
  33.  
  34. -- these are allowed withing a path segment, along with alphanum
  35. -- other characters must be escaped
  36. local segment_set = make_set {
  37.     "-", "_", ".", "!", "~", "*", "'", "(",
  38.     ")", ":", "@", "&", "=", "+", "$", ",",
  39. }
  40.  
  41. ---
  42. -- Protects a path segment, to prevent it from interfering with the
  43. -- URL parsing.
  44. -- @param s Binary string to be encoded.
  45. -- @return Escaped representation of string.
  46. local function protect_segment(s)
  47.     return string.gsub(s, "([^A-Za-z0-9_])", function (c)
  48.         if segment_set[c] then return c
  49.         else return string.format("%%%02x", string.byte(c)) end
  50.     end)
  51. end
  52.  
  53. ---
  54. -- Builds a path from a base path and a relative path
  55. -- @param base_path A base path.
  56. -- @param relative_path A relative path.
  57. -- @return The corresponding absolute path.
  58. -----------------------------------------------------------------------------
  59. local function absolute_path(base_path, relative_path)
  60.     if string.sub(relative_path, 1, 1) == "/" then return relative_path end
  61.     local path = string.gsub(base_path, "[^/]*$", "")
  62.     path = path .. relative_path
  63.     path = string.gsub(path, "([^/]*%./)", function (s)
  64.         if s ~= "./" then return s else return "" end
  65.     end)
  66.     path = string.gsub(path, "/%.$", "/")
  67.     local reduced
  68.     while reduced ~= path do
  69.         reduced = path
  70.         path = string.gsub(reduced, "([^/]*/%.%./)", function (s)
  71.             if s ~= "../../" then return "" else return s end
  72.         end)
  73.     end
  74.     path = string.gsub(reduced, "([^/]*/%.%.)$", function (s)
  75.         if s ~= "../.." then return "" else return s end
  76.     end)
  77.     return path
  78. end
  79.  
  80.  
  81.         --[[ External functions --]]
  82.  
  83. ---
  84. -- Encodes a string into its escaped hexadecimal representation.
  85. -- @param s Binary string to be encoded.
  86. -- @return Escaped representation of string.
  87. -----------------------------------------------------------------------------
  88. function escape(s)
  89.     return string.gsub(s, "([^A-Za-z0-9_])", function(c)
  90.         return string.format("%%%02x", string.byte(c))
  91.     end)
  92. end
  93.  
  94.  
  95. ---
  96. -- Decodes an escaped hexadecimal string.
  97. -- @param s Hexadecimal-encoded string.
  98. -- @return Decoded string.
  99. -----------------------------------------------------------------------------
  100. function unescape(s)
  101.     return string.gsub(s, "%%(%x%x)", function(hex)
  102.         return string.char(base.tonumber(hex, 16))
  103.     end)
  104. end
  105.  
  106.  
  107. ---
  108. -- Parses a URL and returns a table with all its parts according to RFC 2396.
  109. --
  110. -- The following grammar describes the names given to the URL parts.
  111. -- <code>
  112. -- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
  113. -- <authority> ::= <userinfo>@<host>:<port>
  114. -- <userinfo> ::= <user>[:<password>]
  115. -- <path> :: = {<segment>/}<segment>
  116. -- </code>
  117. --
  118. -- The leading <code>/</code> in <code>/<path></code> is considered part of
  119. -- <code><path></code>.
  120. -- @param url URL of request.
  121. -- @param default Table with default values for each field.
  122. -- @return A table with the following fields, where RFC naming conventions have
  123. --   been preserved:
  124. --     <code>scheme</code>, <code>authority</code>, <code>userinfo</code>,
  125. --     <code>user</code>, <code>password</code>, <code>host</code>,
  126. --     <code>port</code>, <code>path</code>, <code>params</code>,
  127. --     <code>query</code>, and <code>fragment</code>.
  128. -----------------------------------------------------------------------------
  129. function parse(url, default)
  130.     -- initialize default parameters
  131.     local parsed = {}
  132.     for i,v in base.pairs(default or parsed) do parsed[i] = v end
  133.     -- empty url is parsed to nil
  134.     if not url or url == "" then return nil, "invalid url" end
  135.     -- remove whitespace
  136.     -- url = string.gsub(url, "%s", "")
  137.     -- get fragment
  138.     url = string.gsub(url, "#(.*)$", function(f)
  139.         parsed.fragment = f
  140.         return ""
  141.     end)
  142.     -- get scheme
  143.     url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
  144.         function(s) parsed.scheme = s; return "" end)
  145.     -- get authority
  146.     url = string.gsub(url, "^//([^/]*)", function(n)
  147.         parsed.authority = n
  148.         return ""
  149.     end)
  150.     -- get query stringing
  151.     url = string.gsub(url, "%?(.*)", function(q)
  152.         parsed.query = q
  153.         return ""
  154.     end)
  155.     -- get params
  156.     url = string.gsub(url, "%;(.*)", function(p)
  157.         parsed.params = p
  158.         return ""
  159.     end)
  160.     -- path is whatever was left
  161.     if url ~= "" then parsed.path = url end
  162.     local authority = parsed.authority
  163.     if not authority then return parsed end
  164.     authority = string.gsub(authority,"^([^@]*)@",
  165.         function(u) parsed.userinfo = u; return "" end)
  166.     authority = string.gsub(authority, ":([^:]*)$",
  167.         function(p) parsed.port = p; return "" end)
  168.     if authority ~= "" then parsed.host = authority end
  169.     local userinfo = parsed.userinfo
  170.     if not userinfo then return parsed end
  171.     userinfo = string.gsub(userinfo, ":([^:]*)$",
  172.         function(p) parsed.password = p; return "" end)
  173.     parsed.user = userinfo
  174.     return parsed
  175. end
  176.  
  177. ---
  178. -- Rebuilds a parsed URL from its components.
  179. --
  180. -- Components are protected if any reserved or unallowed characters are found.
  181. -- @param parsed Parsed URL, as returned by parse.
  182. -- @return A string with the corresponding URL.
  183. -----------------------------------------------------------------------------
  184. function build(parsed)
  185.     local ppath = parse_path(parsed.path or "")
  186.     local url = build_path(ppath)
  187.     if parsed.params then url = url .. ";" .. parsed.params end
  188.     if parsed.query then url = url .. "?" .. parsed.query end
  189.     local authority = parsed.authority
  190.     if parsed.host then
  191.         authority = parsed.host
  192.         if parsed.port then authority = authority .. ":" .. parsed.port end
  193.         local userinfo = parsed.userinfo
  194.         if parsed.user then
  195.             userinfo = parsed.user
  196.             if parsed.password then
  197.                 userinfo = userinfo .. ":" .. parsed.password
  198.             end
  199.         end
  200.         if userinfo then authority = userinfo .. "@" .. authority end
  201.     end
  202.     if authority then url = "//" .. authority .. url end
  203.     if parsed.scheme then url = parsed.scheme .. ":" .. url end
  204.     if parsed.fragment then url = url .. "#" .. parsed.fragment end
  205.     -- url = string.gsub(url, "%s", "")
  206.     return url
  207. end
  208.  
  209. ---
  210. -- Builds an absolute URL from a base and a relative URL according to RFC 2396.
  211. -- @param base_url A base URL.
  212. -- @param relative_url A relative URL.
  213. -- @return The corresponding absolute URL.
  214. -----------------------------------------------------------------------------
  215. function absolute(base_url, relative_url)
  216.     if type(base_url) == "table" then
  217.         base_parsed = base_url
  218.         base_url = build(base_parsed)
  219.     else
  220.         base_parsed = parse(base_url)
  221.     end
  222.     local relative_parsed = parse(relative_url)
  223.     if not base_parsed then return relative_url
  224.     elseif not relative_parsed then return base_url
  225.     elseif relative_parsed.scheme then return relative_url
  226.     else
  227.         relative_parsed.scheme = base_parsed.scheme
  228.         if not relative_parsed.authority then
  229.             relative_parsed.authority = base_parsed.authority
  230.             if not relative_parsed.path then
  231.                 relative_parsed.path = base_parsed.path
  232.                 if not relative_parsed.params then
  233.                     relative_parsed.params = base_parsed.params
  234.                     if not relative_parsed.query then
  235.                         relative_parsed.query = base_parsed.query
  236.                     end
  237.                 end
  238.             else    
  239.                 relative_parsed.path = absolute_path(base_parsed.path or "",
  240.                     relative_parsed.path)
  241.             end
  242.         end
  243.         return build(relative_parsed)
  244.     end
  245. end
  246.  
  247. ---
  248. -- Breaks a path into its segments, unescaping the segments.
  249. -- @param path A path to break.
  250. -- @return A table with one entry per segment.
  251. -----------------------------------------------------------------------------
  252. function parse_path(path)
  253.     local parsed = {}
  254.     path = path or ""
  255.     --path = string.gsub(path, "%s", "")
  256.     string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end)
  257.     for i = 1, table.getn(parsed) do
  258.         parsed[i] = unescape(parsed[i])
  259.     end
  260.     if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end
  261.     if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end
  262.     return parsed
  263. end
  264.  
  265. ---
  266. -- Builds a path component from its segments, escaping protected characters.
  267. -- @param parsed Path segments.
  268. -- @param unsafe If true, segments are not protected before path is built.
  269. -- @return The corresponding path string
  270. -----------------------------------------------------------------------------
  271. function build_path(parsed, unsafe)
  272.     local path = ""
  273.     local n = table.getn(parsed)
  274.     if unsafe then
  275.         for i = 1, n-1 do
  276.             path = path .. parsed[i]
  277.             path = path .. "/"
  278.         end
  279.         if n > 0 then
  280.             path = path .. parsed[n]
  281.             if parsed.is_directory then path = path .. "/" end
  282.         end
  283.     else
  284.         for i = 1, n-1 do
  285.             path = path .. protect_segment(parsed[i])
  286.             path = path .. "/"
  287.         end
  288.         if n > 0 then
  289.             path = path .. protect_segment(parsed[n])
  290.             if parsed.is_directory then path = path .. "/" end
  291.         end
  292.     end
  293.     if parsed.is_absolute then path = "/" .. path end
  294.     return path
  295. end
  296.  
  297. ---
  298. -- Breaks a query string into name/value pairs.
  299. --
  300. -- This function takes a <code><query></code> of the form
  301. -- <code>"name1=value1&name2=value2"</code>
  302. -- and returns a table containing the name-value pairs, with the name as the key
  303. -- and the value as its associated value.
  304. -- @param query Query string.
  305. -- @return A table of name-value pairs following the pattern
  306. -- <code>table["name"]</code> = <code>value</code>.
  307. -----------------------------------------------------------------------------
  308. function parse_query(query)
  309.     local parsed = {}
  310.     local pos = 0
  311.  
  312.     query = string.gsub(query, "&", "&")
  313.     query = string.gsub(query, "<", "<")
  314.     query = string.gsub(query, ">", ">")
  315.  
  316.     function ginsert(qstr)
  317.         local first, last = string.find(qstr, "=")
  318.         if first then
  319.             parsed[string.sub(qstr, 0, first-1)] = string.sub(qstr, first+1)
  320.         end
  321.     end
  322.  
  323.     while true do
  324.         local first, last = string.find(query, "&", pos)
  325.         if first then
  326.             ginsert(string.sub(query, pos, first-1));
  327.             pos = last+1
  328.         else
  329.             ginsert(string.sub(query, pos));
  330.             break;
  331.         end
  332.     end
  333.     return parsed
  334. end
  335.  
  336. ---
  337. -- Builds a query string from a table.
  338. --
  339. -- This is the inverse of <code>parse_query</code>.
  340. -- @param query A dictionary table where <code>table['name']</code> =
  341. -- <code>value</code>.
  342. -- @return A query string (like <code>"name=value2&name=value2"</code>).
  343. -----------------------------------------------------------------------------
  344. function build_query(query)
  345.     local qstr = ""
  346.  
  347.     for i,v in pairs(query) do 
  348.         qstr = qstr .. i .. '=' .. v .. '&'
  349.     end
  350.     return string.sub(qstr, 0, string.len(qstr)-1)
  351. end    
  352.