PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/script/socket/url.lua

http://sdccs.googlecode.com/
Lua | 297 lines | 263 code | 5 blank | 29 comment | 1 complexity | ffc9e8b65161d99b97c05e4673b47dfe MD5 | raw file
  1. -----------------------------------------------------------------------------
  2. -- URI parsing, composition and relative URL resolution
  3. -- LuaSocket toolkit.
  4. -- Author: Diego Nehab
  5. -- RCS ID: $Id: url.lua 7 2010-11-21 23:50:47Z kaszasg $
  6. -----------------------------------------------------------------------------
  7. -----------------------------------------------------------------------------
  8. -- Declare module
  9. -----------------------------------------------------------------------------
  10. local string = require("string")
  11. local base = _G
  12. local table = require("table")
  13. module("socket.url")
  14. -----------------------------------------------------------------------------
  15. -- Module version
  16. -----------------------------------------------------------------------------
  17. _VERSION = "URL 1.0.1"
  18. -----------------------------------------------------------------------------
  19. -- Encodes a string into its escaped hexadecimal representation
  20. -- Input
  21. -- s: binary string to be encoded
  22. -- Returns
  23. -- escaped representation of string binary
  24. -----------------------------------------------------------------------------
  25. function escape(s)
  26. return string.gsub(s, "([^A-Za-z0-9_])", function(c)
  27. return string.format("%%%02x", string.byte(c))
  28. end)
  29. end
  30. -----------------------------------------------------------------------------
  31. -- Protects a path segment, to prevent it from interfering with the
  32. -- url parsing.
  33. -- Input
  34. -- s: binary string to be encoded
  35. -- Returns
  36. -- escaped representation of string binary
  37. -----------------------------------------------------------------------------
  38. local function make_set(t)
  39. local s = {}
  40. for i,v in base.ipairs(t) do
  41. s[t[i]] = 1
  42. end
  43. return s
  44. end
  45. -- these are allowed withing a path segment, along with alphanum
  46. -- other characters must be escaped
  47. local segment_set = make_set {
  48. "-", "_", ".", "!", "~", "*", "'", "(",
  49. ")", ":", "@", "&", "=", "+", "$", ",",
  50. }
  51. local function protect_segment(s)
  52. return string.gsub(s, "([^A-Za-z0-9_])", function (c)
  53. if segment_set[c] then return c
  54. else return string.format("%%%02x", string.byte(c)) end
  55. end)
  56. end
  57. -----------------------------------------------------------------------------
  58. -- Encodes a string into its escaped hexadecimal representation
  59. -- Input
  60. -- s: binary string to be encoded
  61. -- Returns
  62. -- escaped representation of string binary
  63. -----------------------------------------------------------------------------
  64. function unescape(s)
  65. return string.gsub(s, "%%(%x%x)", function(hex)
  66. return string.char(base.tonumber(hex, 16))
  67. end)
  68. end
  69. -----------------------------------------------------------------------------
  70. -- Builds a path from a base path and a relative path
  71. -- Input
  72. -- base_path
  73. -- relative_path
  74. -- Returns
  75. -- corresponding absolute path
  76. -----------------------------------------------------------------------------
  77. local function absolute_path(base_path, relative_path)
  78. if string.sub(relative_path, 1, 1) == "/" then return relative_path end
  79. local path = string.gsub(base_path, "[^/]*$", "")
  80. path = path .. relative_path
  81. path = string.gsub(path, "([^/]*%./)", function (s)
  82. if s ~= "./" then return s else return "" end
  83. end)
  84. path = string.gsub(path, "/%.$", "/")
  85. local reduced
  86. while reduced ~= path do
  87. reduced = path
  88. path = string.gsub(reduced, "([^/]*/%.%./)", function (s)
  89. if s ~= "../../" then return "" else return s end
  90. end)
  91. end
  92. path = string.gsub(reduced, "([^/]*/%.%.)$", function (s)
  93. if s ~= "../.." then return "" else return s end
  94. end)
  95. return path
  96. end
  97. -----------------------------------------------------------------------------
  98. -- Parses a url and returns a table with all its parts according to RFC 2396
  99. -- The following grammar describes the names given to the URL parts
  100. -- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
  101. -- <authority> ::= <userinfo>@<host>:<port>
  102. -- <userinfo> ::= <user>[:<password>]
  103. -- <path> :: = {<segment>/}<segment>
  104. -- Input
  105. -- url: uniform resource locator of request
  106. -- default: table with default values for each field
  107. -- Returns
  108. -- table with the following fields, where RFC naming conventions have
  109. -- been preserved:
  110. -- scheme, authority, userinfo, user, password, host, port,
  111. -- path, params, query, fragment
  112. -- Obs:
  113. -- the leading '/' in {/<path>} is considered part of <path>
  114. -----------------------------------------------------------------------------
  115. function parse(url, default)
  116. -- initialize default parameters
  117. local parsed = {}
  118. for i,v in base.pairs(default or parsed) do parsed[i] = v end
  119. -- empty url is parsed to nil
  120. if not url or url == "" then return nil, "invalid url" end
  121. -- remove whitespace
  122. -- url = string.gsub(url, "%s", "")
  123. -- get fragment
  124. url = string.gsub(url, "#(.*)$", function(f)
  125. parsed.fragment = f
  126. return ""
  127. end)
  128. -- get scheme
  129. url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
  130. function(s) parsed.scheme = s; return "" end)
  131. -- get authority
  132. url = string.gsub(url, "^//([^/]*)", function(n)
  133. parsed.authority = n
  134. return ""
  135. end)
  136. -- get query stringing
  137. url = string.gsub(url, "%?(.*)", function(q)
  138. parsed.query = q
  139. return ""
  140. end)
  141. -- get params
  142. url = string.gsub(url, "%;(.*)", function(p)
  143. parsed.params = p
  144. return ""
  145. end)
  146. -- path is whatever was left
  147. if url ~= "" then parsed.path = url end
  148. local authority = parsed.authority
  149. if not authority then return parsed end
  150. authority = string.gsub(authority,"^([^@]*)@",
  151. function(u) parsed.userinfo = u; return "" end)
  152. authority = string.gsub(authority, ":([^:]*)$",
  153. function(p) parsed.port = p; return "" end)
  154. if authority ~= "" then parsed.host = authority end
  155. local userinfo = parsed.userinfo
  156. if not userinfo then return parsed end
  157. userinfo = string.gsub(userinfo, ":([^:]*)$",
  158. function(p) parsed.password = p; return "" end)
  159. parsed.user = userinfo
  160. return parsed
  161. end
  162. -----------------------------------------------------------------------------
  163. -- Rebuilds a parsed URL from its components.
  164. -- Components are protected if any reserved or unallowed characters are found
  165. -- Input
  166. -- parsed: parsed URL, as returned by parse
  167. -- Returns
  168. -- a stringing with the corresponding URL
  169. -----------------------------------------------------------------------------
  170. function build(parsed)
  171. local ppath = parse_path(parsed.path or "")
  172. local url = build_path(ppath)
  173. if parsed.params then url = url .. ";" .. parsed.params end
  174. if parsed.query then url = url .. "?" .. parsed.query end
  175. local authority = parsed.authority
  176. if parsed.host then
  177. authority = parsed.host
  178. if parsed.port then authority = authority .. ":" .. parsed.port end
  179. local userinfo = parsed.userinfo
  180. if parsed.user then
  181. userinfo = parsed.user
  182. if parsed.password then
  183. userinfo = userinfo .. ":" .. parsed.password
  184. end
  185. end
  186. if userinfo then authority = userinfo .. "@" .. authority end
  187. end
  188. if authority then url = "//" .. authority .. url end
  189. if parsed.scheme then url = parsed.scheme .. ":" .. url end
  190. if parsed.fragment then url = url .. "#" .. parsed.fragment end
  191. -- url = string.gsub(url, "%s", "")
  192. return url
  193. end
  194. -----------------------------------------------------------------------------
  195. -- Builds a absolute URL from a base and a relative URL according to RFC 2396
  196. -- Input
  197. -- base_url
  198. -- relative_url
  199. -- Returns
  200. -- corresponding absolute url
  201. -----------------------------------------------------------------------------
  202. function absolute(base_url, relative_url)
  203. if base.type(base_url) == "table" then
  204. base_parsed = base_url
  205. base_url = build(base_parsed)
  206. else
  207. base_parsed = parse(base_url)
  208. end
  209. local relative_parsed = parse(relative_url)
  210. if not base_parsed then return relative_url
  211. elseif not relative_parsed then return base_url
  212. elseif relative_parsed.scheme then return relative_url
  213. else
  214. relative_parsed.scheme = base_parsed.scheme
  215. if not relative_parsed.authority then
  216. relative_parsed.authority = base_parsed.authority
  217. if not relative_parsed.path then
  218. relative_parsed.path = base_parsed.path
  219. if not relative_parsed.params then
  220. relative_parsed.params = base_parsed.params
  221. if not relative_parsed.query then
  222. relative_parsed.query = base_parsed.query
  223. end
  224. end
  225. else
  226. relative_parsed.path = absolute_path(base_parsed.path or "",
  227. relative_parsed.path)
  228. end
  229. end
  230. return build(relative_parsed)
  231. end
  232. end
  233. -----------------------------------------------------------------------------
  234. -- Breaks a path into its segments, unescaping the segments
  235. -- Input
  236. -- path
  237. -- Returns
  238. -- segment: a table with one entry per segment
  239. -----------------------------------------------------------------------------
  240. function parse_path(path)
  241. local parsed = {}
  242. path = path or ""
  243. --path = string.gsub(path, "%s", "")
  244. string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end)
  245. for i = 1, table.getn(parsed) do
  246. parsed[i] = unescape(parsed[i])
  247. end
  248. if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end
  249. if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end
  250. return parsed
  251. end
  252. -----------------------------------------------------------------------------
  253. -- Builds a path component from its segments, escaping protected characters.
  254. -- Input
  255. -- parsed: path segments
  256. -- unsafe: if true, segments are not protected before path is built
  257. -- Returns
  258. -- path: corresponding path stringing
  259. -----------------------------------------------------------------------------
  260. function build_path(parsed, unsafe)
  261. local path = ""
  262. local n = table.getn(parsed)
  263. if unsafe then
  264. for i = 1, n-1 do
  265. path = path .. parsed[i]
  266. path = path .. "/"
  267. end
  268. if n > 0 then
  269. path = path .. parsed[n]
  270. if parsed.is_directory then path = path .. "/" end
  271. end
  272. else
  273. for i = 1, n-1 do
  274. path = path .. protect_segment(parsed[i])
  275. path = path .. "/"
  276. end
  277. if n > 0 then
  278. path = path .. protect_segment(parsed[n])
  279. if parsed.is_directory then path = path .. "/" end
  280. end
  281. end
  282. if parsed.is_absolute then path = "/" .. path end
  283. return path
  284. end