diff options
| -rw-r--r-- | docs/url.html | 82 | ||||
| -rw-r--r-- | src/url.lua | 53 | ||||
| -rw-r--r-- | test/urltest.lua | 196 |
3 files changed, 325 insertions, 6 deletions
diff --git a/docs/url.html b/docs/url.html index e03b094..788f409 100644 --- a/docs/url.html +++ b/docs/url.html | |||
| @@ -140,6 +140,14 @@ Lower level components, if specified, | |||
| 140 | take precedence over high level components of the URL grammar. | 140 | take precedence over high level components of the URL grammar. |
| 141 | </p> | 141 | </p> |
| 142 | 142 | ||
| 143 | <p class=parameters> | ||
| 144 | If <tt>host</tt> is not set, the host is taken from whichever single | ||
| 145 | one of <tt>hostname</tt>, <tt>ipv4</tt> or <tt>ipv6</tt> is set | ||
| 146 | (<tt>hosttype</tt> is ignored when building). It is an error to set more | ||
| 147 | than one of <tt>hostname</tt>, <tt>ipv4</tt> or <tt>ipv6</tt> while | ||
| 148 | <tt>host</tt> is absent. | ||
| 149 | </p> | ||
| 150 | |||
| 143 | <p class=return> | 151 | <p class=return> |
| 144 | The function returns a string with the built URL. | 152 | The function returns a string with the built URL. |
| 145 | </p> | 153 | </p> |
| @@ -169,6 +177,62 @@ The function returns a string with the | |||
| 169 | built <tt><path></tt> component. | 177 | built <tt><path></tt> component. |
| 170 | </p> | 178 | </p> |
| 171 | 179 | ||
| 180 | <!-- classify_host ++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
| 181 | |||
| 182 | <p class=name id="classify_host"> | ||
| 183 | url.<b>classify_host(</b>host<b>)</b> | ||
| 184 | </p> | ||
| 185 | |||
| 186 | <p class=description> | ||
| 187 | Classifies a raw <tt><host></tt> string into <tt>"name"</tt>, | ||
| 188 | <tt>"ipv4"</tt> or <tt>"ipv6"</tt>. This is the same classification | ||
| 189 | <a href="#parse"><tt>parse</tt></a> uses to fill in <tt>hosttype</tt>, | ||
| 190 | exposed for callers that have a host string to classify without a full | ||
| 191 | URL to parse. | ||
| 192 | </p> | ||
| 193 | |||
| 194 | <p class=parameters> | ||
| 195 | <tt>Host</tt> is a host string as found in a URL's authority. Any | ||
| 196 | <tt>:</tt> in <tt>host</tt> is taken as a sign of an IPv6 literal, so | ||
| 197 | the enclosing <tt>[...]</tt> brackets URLs normally require for one are | ||
| 198 | optional here: they are stripped when present, but classification does | ||
| 199 | not depend on them. | ||
| 200 | </p> | ||
| 201 | |||
| 202 | <p class=return> | ||
| 203 | The function returns two values: <tt>hosttype</tt>, one of | ||
| 204 | <tt>"name"</tt>, <tt>"ipv4"</tt> or <tt>"ipv6"</tt>; and <tt>host</tt>, | ||
| 205 | the input with any enclosing <tt>[...]</tt> brackets stripped, if | ||
| 206 | present. | ||
| 207 | </p> | ||
| 208 | |||
| 209 | <p class=note> | ||
| 210 | Note: classification is done by exclusion of shape, not by validating | ||
| 211 | the address. A host is <tt>"ipv4"</tt> if it merely has the shape of | ||
| 212 | four dot-separated digit groups — octet ranges are not checked, | ||
| 213 | so <tt>"999.1.1.1"</tt> classifies as <tt>"ipv4"</tt> — and | ||
| 214 | <tt>"ipv6"</tt> if it merely contains a <tt>:</tt>, whether or not that | ||
| 215 | is a well-formed IPv6 address. Anything left over is <tt>"name"</tt>, | ||
| 216 | whether or not it is actually a valid hostname. | ||
| 217 | </p> | ||
| 218 | |||
| 219 | <pre class=example> | ||
| 220 | -- load url module | ||
| 221 | url = require("socket.url") | ||
| 222 | |||
| 223 | print(url.classify_host("example.com")) | ||
| 224 | -- name example.com | ||
| 225 | |||
| 226 | print(url.classify_host("192.168.1.1")) | ||
| 227 | -- ipv4 192.168.1.1 | ||
| 228 | |||
| 229 | print(url.classify_host("[::1]")) | ||
| 230 | -- ipv6 ::1 | ||
| 231 | |||
| 232 | print(url.classify_host("::1")) | ||
| 233 | -- ipv6 ::1 | ||
| 234 | </pre> | ||
| 235 | |||
| 172 | <!-- escape +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | 236 | <!-- escape +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> |
| 173 | 237 | ||
| 174 | <p class=name id="escape"> | 238 | <p class=name id="escape"> |
| @@ -230,12 +294,28 @@ parsed_url = {<br> | |||
| 230 | fragment = <i>string</i>,<br> | 294 | fragment = <i>string</i>,<br> |
| 231 | userinfo = <i>string</i>,<br> | 295 | userinfo = <i>string</i>,<br> |
| 232 | host = <i>string</i>,<br> | 296 | host = <i>string</i>,<br> |
| 297 | hosttype = <i>string</i>,<br> | ||
| 298 | hostname = <i>string</i>,<br> | ||
| 299 | ipv4 = <i>string</i>,<br> | ||
| 300 | ipv6 = <i>string</i>,<br> | ||
| 233 | port = <i>string</i>,<br> | 301 | port = <i>string</i>,<br> |
| 234 | user = <i>string</i>,<br> | 302 | user = <i>string</i>,<br> |
| 235 | password = <i>string</i><br> | 303 | password = <i>string</i><br> |
| 236 | } | 304 | } |
| 237 | </tt></blockquote> | 305 | </tt></blockquote> |
| 238 | 306 | ||
| 307 | <p class=parameters> | ||
| 308 | When a host is present, <tt>hosttype</tt> is set to one of | ||
| 309 | <tt>"name"</tt>, <tt>"ipv4"</tt> or <tt>"ipv6"</tt>, describing the | ||
| 310 | syntax of <tt>host</tt>. Exactly one of <tt>hostname</tt>, <tt>ipv4</tt> | ||
| 311 | or <tt>ipv6</tt> is then also set to the same value as <tt>host</tt>, | ||
| 312 | matching <tt>hosttype</tt> — the other two are left <b><tt>nil</tt></b>. | ||
| 313 | In case of an ipv6 address the brackets are stripped, both in <tt>host</tt> and <tt>ipv6</tt>. | ||
| 314 | This classification is done by | ||
| 315 | <a href="#classify_host"><tt>classify_host</tt></a>, by exclusion of | ||
| 316 | shape rather than by validating the address (see its notes). | ||
| 317 | </p> | ||
| 318 | |||
| 239 | <pre class=example> | 319 | <pre class=example> |
| 240 | -- load url module | 320 | -- load url module |
| 241 | url = require("socket.url") | 321 | url = require("socket.url") |
| @@ -248,6 +328,8 @@ parsed_url = url.parse("http://www.example.com/cgilua/index.lua?a=2#there") | |||
| 248 | -- query = "a=2", | 328 | -- query = "a=2", |
| 249 | -- fragment = "there", | 329 | -- fragment = "there", |
| 250 | -- host = "www.puc-rio.br", | 330 | -- host = "www.puc-rio.br", |
| 331 | -- hosttype = "name", | ||
| 332 | -- hostname = "www.puc-rio.br", | ||
| 251 | -- } | 333 | -- } |
| 252 | 334 | ||
| 253 | parsed_url = url.parse("ftp://root:passwd@unsafe.org/pub/virus.exe;type=i") | 335 | parsed_url = url.parse("ftp://root:passwd@unsafe.org/pub/virus.exe;type=i") |
diff --git a/src/url.lua b/src/url.lua index aef1698..be82421 100644 --- a/src/url.lua +++ b/src/url.lua | |||
| @@ -123,6 +123,25 @@ local function absolute_path(base_path, relative_path) | |||
| 123 | end | 123 | end |
| 124 | 124 | ||
| 125 | ----------------------------------------------------------------------------- | 125 | ----------------------------------------------------------------------------- |
| 126 | -- Classifies a raw host string into "name", "ipv4" or "ipv6" | ||
| 127 | -- Input | ||
| 128 | -- raw: host part of the authority, as found in the URL (with its | ||
| 129 | -- enclosing [...] brackets, if any) | ||
| 130 | -- Returns | ||
| 131 | -- hosttype: "name", "ipv4" or "ipv6" | ||
| 132 | -- host: raw, with any enclosing [...] brackets stripped | ||
| 133 | ----------------------------------------------------------------------------- | ||
| 134 | function _M.classify_host(raw) | ||
| 135 | if string.find(raw, ":", 1, true) then | ||
| 136 | return "ipv6", string.match(raw, "^%[?(.-)%]?$") | ||
| 137 | end | ||
| 138 | if string.match(raw, "^%d+%.%d+%.%d+%.%d+$") then | ||
| 139 | return "ipv4", raw | ||
| 140 | end | ||
| 141 | return "name", raw | ||
| 142 | end | ||
| 143 | |||
| 144 | ----------------------------------------------------------------------------- | ||
| 126 | -- Parses a url and returns a table with all its parts according to RFC 2396 | 145 | -- Parses a url and returns a table with all its parts according to RFC 2396 |
| 127 | -- The following grammar describes the names given to the URL parts | 146 | -- The following grammar describes the names given to the URL parts |
| 128 | -- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment> | 147 | -- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment> |
| @@ -137,6 +156,10 @@ end | |||
| 137 | -- been preserved: | 156 | -- been preserved: |
| 138 | -- scheme, authority, userinfo, user, password, host, port, | 157 | -- scheme, authority, userinfo, user, password, host, port, |
| 139 | -- path, params, query, fragment | 158 | -- path, params, query, fragment |
| 159 | -- plus, when a host is present: | ||
| 160 | -- hosttype: "name", "ipv4" or "ipv6", describing the syntax of host | ||
| 161 | -- hostname, ipv4 or ipv6: same value as host, keyed by hosttype | ||
| 162 | -- (only one of these three is ever set) | ||
| 140 | -- Obs: | 163 | -- Obs: |
| 141 | -- the leading '/' in {/<path>} is considered part of <path> | 164 | -- the leading '/' in {/<path>} is considered part of <path> |
| 142 | ----------------------------------------------------------------------------- | 165 | ----------------------------------------------------------------------------- |
| @@ -180,8 +203,10 @@ function _M.parse(url, default) | |||
| 180 | authority = string.gsub(authority, ":([^:%]]*)$", | 203 | authority = string.gsub(authority, ":([^:%]]*)$", |
| 181 | function(p) parsed.port = p; return "" end) | 204 | function(p) parsed.port = p; return "" end) |
| 182 | if authority ~= "" then | 205 | if authority ~= "" then |
| 183 | -- IPv6? | 206 | parsed.hosttype, parsed.host = _M.classify_host(authority) |
| 184 | parsed.host = string.match(authority, "^%[(.+)%]$") or authority | 207 | if parsed.hosttype == "name" then parsed.hostname = parsed.host |
| 208 | elseif parsed.hosttype == "ipv4" then parsed.ipv4 = parsed.host | ||
| 209 | else parsed.ipv6 = parsed.host end | ||
| 185 | end | 210 | end |
| 186 | local userinfo = parsed.userinfo | 211 | local userinfo = parsed.userinfo |
| 187 | if not userinfo then return parsed end | 212 | if not userinfo then return parsed end |
| @@ -195,7 +220,11 @@ end | |||
| 195 | -- Rebuilds a parsed URL from its components. | 220 | -- Rebuilds a parsed URL from its components. |
| 196 | -- Components are protected if any reserved or unallowed characters are found | 221 | -- Components are protected if any reserved or unallowed characters are found |
| 197 | -- Input | 222 | -- Input |
| 198 | -- parsed: parsed URL, as returned by parse | 223 | -- parsed: parsed URL, as returned by parse. If parsed.host is absent, |
| 224 | -- the host is taken from whichever single one of hostname/ipv4/ipv6 | ||
| 225 | -- is set (parsed.hosttype is ignored for building). It is an error | ||
| 226 | -- (raised with error()) for more than one of hostname/ipv4/ipv6 to | ||
| 227 | -- be set when parsed.host is absent. | ||
| 199 | -- Returns | 228 | -- Returns |
| 200 | -- a stringing with the corresponding URL | 229 | -- a stringing with the corresponding URL |
| 201 | ----------------------------------------------------------------------------- | 230 | ----------------------------------------------------------------------------- |
| @@ -206,9 +235,21 @@ function _M.build(parsed) | |||
| 206 | if parsed.params then url = url .. ";" .. parsed.params end | 235 | if parsed.params then url = url .. ";" .. parsed.params end |
| 207 | if parsed.query then url = url .. "?" .. parsed.query end | 236 | if parsed.query then url = url .. "?" .. parsed.query end |
| 208 | local authority = parsed.authority | 237 | local authority = parsed.authority |
| 209 | if parsed.host then | 238 | local host = parsed.host |
| 210 | authority = parsed.host | 239 | if not host then |
| 211 | if string.find(authority, ":") then -- IPv6? | 240 | local set = 0 |
| 241 | if parsed.hostname then host, set = parsed.hostname, set+1 end | ||
| 242 | if parsed.ipv4 then host, set = parsed.ipv4, set+1 end | ||
| 243 | if parsed.ipv6 then host, set = parsed.ipv6, set+1 end | ||
| 244 | if set > 1 then | ||
| 245 | base.error("url.build: ambiguous host, more than one of " .. | ||
| 246 | "hostname/ipv4/ipv6 is set") | ||
| 247 | end | ||
| 248 | end | ||
| 249 | if host then | ||
| 250 | local hosttype | ||
| 251 | hosttype, authority = _M.classify_host(host) | ||
| 252 | if hosttype == "ipv6" then | ||
| 212 | authority = "[" .. authority .. "]" | 253 | authority = "[" .. authority .. "]" |
| 213 | end | 254 | end |
| 214 | if parsed.port then authority = authority .. ":" .. base.tostring(parsed.port) end | 255 | if parsed.port then authority = authority .. ":" .. base.tostring(parsed.port) end |
diff --git a/test/urltest.lua b/test/urltest.lua index 9a3c470..8c50f50 100644 --- a/test/urltest.lua +++ b/test/urltest.lua | |||
| @@ -95,6 +95,8 @@ check_parse_url{ | |||
| 95 | scheme = "scheme", | 95 | scheme = "scheme", |
| 96 | authority = "user:pass$%?#wd@host:port", | 96 | authority = "user:pass$%?#wd@host:port", |
| 97 | host = "host", | 97 | host = "host", |
| 98 | hosttype = "name", | ||
| 99 | hostname = "host", | ||
| 98 | port = "port", | 100 | port = "port", |
| 99 | userinfo = "user:pass$%?#wd", | 101 | userinfo = "user:pass$%?#wd", |
| 100 | password = "pass$%?#wd", | 102 | password = "pass$%?#wd", |
| @@ -109,6 +111,8 @@ check_parse_url{ | |||
| 109 | scheme = "scheme", | 111 | scheme = "scheme", |
| 110 | authority = "user:pass?#wd@host:port", | 112 | authority = "user:pass?#wd@host:port", |
| 111 | host = "host", | 113 | host = "host", |
| 114 | hosttype = "name", | ||
| 115 | hostname = "host", | ||
| 112 | port = "port", | 116 | port = "port", |
| 113 | userinfo = "user:pass?#wd", | 117 | userinfo = "user:pass?#wd", |
| 114 | password = "pass?#wd", | 118 | password = "pass?#wd", |
| @@ -123,6 +127,8 @@ check_parse_url{ | |||
| 123 | scheme = "scheme", | 127 | scheme = "scheme", |
| 124 | authority = "user:pass-wd@host:port", | 128 | authority = "user:pass-wd@host:port", |
| 125 | host = "host", | 129 | host = "host", |
| 130 | hosttype = "name", | ||
| 131 | hostname = "host", | ||
| 126 | port = "port", | 132 | port = "port", |
| 127 | userinfo = "user:pass-wd", | 133 | userinfo = "user:pass-wd", |
| 128 | password = "pass-wd", | 134 | password = "pass-wd", |
| @@ -137,6 +143,8 @@ check_parse_url{ | |||
| 137 | scheme = "scheme", | 143 | scheme = "scheme", |
| 138 | authority = "user:pass#wd@host:port", | 144 | authority = "user:pass#wd@host:port", |
| 139 | host = "host", | 145 | host = "host", |
| 146 | hosttype = "name", | ||
| 147 | hostname = "host", | ||
| 140 | port = "port", | 148 | port = "port", |
| 141 | userinfo = "user:pass#wd", | 149 | userinfo = "user:pass#wd", |
| 142 | password = "pass#wd", | 150 | password = "pass#wd", |
| @@ -151,6 +159,8 @@ check_parse_url{ | |||
| 151 | scheme = "scheme", | 159 | scheme = "scheme", |
| 152 | authority = "user:pass#wd@host:port", | 160 | authority = "user:pass#wd@host:port", |
| 153 | host = "host", | 161 | host = "host", |
| 162 | hosttype = "name", | ||
| 163 | hostname = "host", | ||
| 154 | port = "port", | 164 | port = "port", |
| 155 | userinfo = "user:pass#wd", | 165 | userinfo = "user:pass#wd", |
| 156 | password = "pass#wd", | 166 | password = "pass#wd", |
| @@ -164,6 +174,8 @@ check_parse_url{ | |||
| 164 | scheme = "scheme", | 174 | scheme = "scheme", |
| 165 | authority = "userinfo@host:port", | 175 | authority = "userinfo@host:port", |
| 166 | host = "host", | 176 | host = "host", |
| 177 | hosttype = "name", | ||
| 178 | hostname = "host", | ||
| 167 | port = "port", | 179 | port = "port", |
| 168 | userinfo = "userinfo", | 180 | userinfo = "userinfo", |
| 169 | user = "userinfo", | 181 | user = "userinfo", |
| @@ -178,6 +190,8 @@ check_parse_url{ | |||
| 178 | scheme = "scheme", | 190 | scheme = "scheme", |
| 179 | authority = "user:password@host:port", | 191 | authority = "user:password@host:port", |
| 180 | host = "host", | 192 | host = "host", |
| 193 | hosttype = "name", | ||
| 194 | hostname = "host", | ||
| 181 | port = "port", | 195 | port = "port", |
| 182 | userinfo = "user:password", | 196 | userinfo = "user:password", |
| 183 | user = "user", | 197 | user = "user", |
| @@ -193,6 +207,8 @@ check_parse_url{ | |||
| 193 | scheme = "scheme", | 207 | scheme = "scheme", |
| 194 | authority = "userinfo@host:port", | 208 | authority = "userinfo@host:port", |
| 195 | host = "host", | 209 | host = "host", |
| 210 | hosttype = "name", | ||
| 211 | hostname = "host", | ||
| 196 | port = "port", | 212 | port = "port", |
| 197 | userinfo = "userinfo", | 213 | userinfo = "userinfo", |
| 198 | user = "userinfo", | 214 | user = "userinfo", |
| @@ -207,6 +223,8 @@ check_parse_url{ | |||
| 207 | scheme = "scheme", | 223 | scheme = "scheme", |
| 208 | authority = "userinfo@host:port", | 224 | authority = "userinfo@host:port", |
| 209 | host = "host", | 225 | host = "host", |
| 226 | hosttype = "name", | ||
| 227 | hostname = "host", | ||
| 210 | port = "port", | 228 | port = "port", |
| 211 | userinfo = "userinfo", | 229 | userinfo = "userinfo", |
| 212 | user = "userinfo", | 230 | user = "userinfo", |
| @@ -221,6 +239,8 @@ check_parse_url{ | |||
| 221 | scheme = "scheme", | 239 | scheme = "scheme", |
| 222 | authority = "userinfo@host:port", | 240 | authority = "userinfo@host:port", |
| 223 | host = "host", | 241 | host = "host", |
| 242 | hosttype = "name", | ||
| 243 | hostname = "host", | ||
| 224 | port = "port", | 244 | port = "port", |
| 225 | userinfo = "userinfo", | 245 | userinfo = "userinfo", |
| 226 | user = "userinfo", | 246 | user = "userinfo", |
| @@ -234,6 +254,8 @@ check_parse_url{ | |||
| 234 | scheme = "scheme", | 254 | scheme = "scheme", |
| 235 | authority = "userinfo@host:port", | 255 | authority = "userinfo@host:port", |
| 236 | host = "host", | 256 | host = "host", |
| 257 | hosttype = "name", | ||
| 258 | hostname = "host", | ||
| 237 | port = "port", | 259 | port = "port", |
| 238 | userinfo = "userinfo", | 260 | userinfo = "userinfo", |
| 239 | user = "userinfo", | 261 | user = "userinfo", |
| @@ -248,6 +270,8 @@ check_parse_url{ | |||
| 248 | scheme = "scheme", | 270 | scheme = "scheme", |
| 249 | authority = "userinfo@host:port", | 271 | authority = "userinfo@host:port", |
| 250 | host = "host", | 272 | host = "host", |
| 273 | hosttype = "name", | ||
| 274 | hostname = "host", | ||
| 251 | port = "port", | 275 | port = "port", |
| 252 | userinfo = "userinfo", | 276 | userinfo = "userinfo", |
| 253 | user = "userinfo", | 277 | user = "userinfo", |
| @@ -261,6 +285,8 @@ check_parse_url{ | |||
| 261 | scheme = "scheme", | 285 | scheme = "scheme", |
| 262 | authority = "userinfo@host:port", | 286 | authority = "userinfo@host:port", |
| 263 | host = "host", | 287 | host = "host", |
| 288 | hosttype = "name", | ||
| 289 | hostname = "host", | ||
| 264 | port = "port", | 290 | port = "port", |
| 265 | userinfo = "userinfo", | 291 | userinfo = "userinfo", |
| 266 | user = "userinfo", | 292 | user = "userinfo", |
| @@ -275,6 +301,8 @@ check_parse_url{ | |||
| 275 | scheme = "scheme", | 301 | scheme = "scheme", |
| 276 | authority = "userinfo@host:port", | 302 | authority = "userinfo@host:port", |
| 277 | host = "host", | 303 | host = "host", |
| 304 | hosttype = "name", | ||
| 305 | hostname = "host", | ||
| 278 | port = "port", | 306 | port = "port", |
| 279 | userinfo = "userinfo", | 307 | userinfo = "userinfo", |
| 280 | user = "userinfo", | 308 | user = "userinfo", |
| @@ -284,6 +312,8 @@ check_parse_url{ | |||
| 284 | url = "//userinfo@host:port/path;params?query#fragment", | 312 | url = "//userinfo@host:port/path;params?query#fragment", |
| 285 | authority = "userinfo@host:port", | 313 | authority = "userinfo@host:port", |
| 286 | host = "host", | 314 | host = "host", |
| 315 | hosttype = "name", | ||
| 316 | hostname = "host", | ||
| 287 | port = "port", | 317 | port = "port", |
| 288 | userinfo = "userinfo", | 318 | userinfo = "userinfo", |
| 289 | user = "userinfo", | 319 | user = "userinfo", |
| @@ -297,6 +327,8 @@ check_parse_url{ | |||
| 297 | url = "//userinfo@host:port/path", | 327 | url = "//userinfo@host:port/path", |
| 298 | authority = "userinfo@host:port", | 328 | authority = "userinfo@host:port", |
| 299 | host = "host", | 329 | host = "host", |
| 330 | hosttype = "name", | ||
| 331 | hostname = "host", | ||
| 300 | port = "port", | 332 | port = "port", |
| 301 | userinfo = "userinfo", | 333 | userinfo = "userinfo", |
| 302 | user = "userinfo", | 334 | user = "userinfo", |
| @@ -307,6 +339,8 @@ check_parse_url{ | |||
| 307 | url = "//userinfo@host/path", | 339 | url = "//userinfo@host/path", |
| 308 | authority = "userinfo@host", | 340 | authority = "userinfo@host", |
| 309 | host = "host", | 341 | host = "host", |
| 342 | hosttype = "name", | ||
| 343 | hostname = "host", | ||
| 310 | userinfo = "userinfo", | 344 | userinfo = "userinfo", |
| 311 | user = "userinfo", | 345 | user = "userinfo", |
| 312 | path = "/path", | 346 | path = "/path", |
| @@ -316,6 +350,8 @@ check_parse_url{ | |||
| 316 | url = "//user:password@host/path", | 350 | url = "//user:password@host/path", |
| 317 | authority = "user:password@host", | 351 | authority = "user:password@host", |
| 318 | host = "host", | 352 | host = "host", |
| 353 | hosttype = "name", | ||
| 354 | hostname = "host", | ||
| 319 | userinfo = "user:password", | 355 | userinfo = "user:password", |
| 320 | password = "password", | 356 | password = "password", |
| 321 | user = "user", | 357 | user = "user", |
| @@ -326,6 +362,8 @@ check_parse_url{ | |||
| 326 | url = "//user:@host/path", | 362 | url = "//user:@host/path", |
| 327 | authority = "user:@host", | 363 | authority = "user:@host", |
| 328 | host = "host", | 364 | host = "host", |
| 365 | hosttype = "name", | ||
| 366 | hostname = "host", | ||
| 329 | userinfo = "user:", | 367 | userinfo = "user:", |
| 330 | password = "", | 368 | password = "", |
| 331 | user = "user", | 369 | user = "user", |
| @@ -336,6 +374,8 @@ check_parse_url{ | |||
| 336 | url = "//user@host:port/path", | 374 | url = "//user@host:port/path", |
| 337 | authority = "user@host:port", | 375 | authority = "user@host:port", |
| 338 | host = "host", | 376 | host = "host", |
| 377 | hosttype = "name", | ||
| 378 | hostname = "host", | ||
| 339 | userinfo = "user", | 379 | userinfo = "user", |
| 340 | user = "user", | 380 | user = "user", |
| 341 | port = "port", | 381 | port = "port", |
| @@ -347,6 +387,8 @@ check_parse_url{ | |||
| 347 | authority = "host:port", | 387 | authority = "host:port", |
| 348 | port = "port", | 388 | port = "port", |
| 349 | host = "host", | 389 | host = "host", |
| 390 | hosttype = "name", | ||
| 391 | hostname = "host", | ||
| 350 | path = "/path", | 392 | path = "/path", |
| 351 | } | 393 | } |
| 352 | 394 | ||
| @@ -354,6 +396,8 @@ check_parse_url{ | |||
| 354 | url = "//host/path", | 396 | url = "//host/path", |
| 355 | authority = "host", | 397 | authority = "host", |
| 356 | host = "host", | 398 | host = "host", |
| 399 | hosttype = "name", | ||
| 400 | hostname = "host", | ||
| 357 | path = "/path", | 401 | path = "/path", |
| 358 | } | 402 | } |
| 359 | 403 | ||
| @@ -361,6 +405,8 @@ check_parse_url{ | |||
| 361 | url = "//host", | 405 | url = "//host", |
| 362 | authority = "host", | 406 | authority = "host", |
| 363 | host = "host", | 407 | host = "host", |
| 408 | hosttype = "name", | ||
| 409 | hostname = "host", | ||
| 364 | } | 410 | } |
| 365 | 411 | ||
| 366 | check_parse_url{ | 412 | check_parse_url{ |
| @@ -379,6 +425,8 @@ check_parse_url{ | |||
| 379 | url = "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html", | 425 | url = "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html", |
| 380 | scheme = "http", | 426 | scheme = "http", |
| 381 | host = "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", | 427 | host = "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", |
| 428 | hosttype = "ipv6", | ||
| 429 | ipv6 = "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", | ||
| 382 | authority = "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80", | 430 | authority = "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80", |
| 383 | port = "80", | 431 | port = "80", |
| 384 | path = "/index.html" | 432 | path = "/index.html" |
| @@ -388,6 +436,8 @@ check_parse_url{ | |||
| 388 | url = "http://[1080:0:0:0:8:800:200C:417A]/index.html", | 436 | url = "http://[1080:0:0:0:8:800:200C:417A]/index.html", |
| 389 | scheme = "http", | 437 | scheme = "http", |
| 390 | host = "1080:0:0:0:8:800:200C:417A", | 438 | host = "1080:0:0:0:8:800:200C:417A", |
| 439 | hosttype = "ipv6", | ||
| 440 | ipv6 = "1080:0:0:0:8:800:200C:417A", | ||
| 391 | authority = "[1080:0:0:0:8:800:200C:417A]", | 441 | authority = "[1080:0:0:0:8:800:200C:417A]", |
| 392 | path = "/index.html" | 442 | path = "/index.html" |
| 393 | } | 443 | } |
| @@ -396,6 +446,8 @@ check_parse_url{ | |||
| 396 | url = "http://[3ffe:2a00:100:7031::1]", | 446 | url = "http://[3ffe:2a00:100:7031::1]", |
| 397 | scheme = "http", | 447 | scheme = "http", |
| 398 | host = "3ffe:2a00:100:7031::1", | 448 | host = "3ffe:2a00:100:7031::1", |
| 449 | hosttype = "ipv6", | ||
| 450 | ipv6 = "3ffe:2a00:100:7031::1", | ||
| 399 | authority = "[3ffe:2a00:100:7031::1]", | 451 | authority = "[3ffe:2a00:100:7031::1]", |
| 400 | } | 452 | } |
| 401 | 453 | ||
| @@ -403,6 +455,8 @@ check_parse_url{ | |||
| 403 | url = "http://[1080::8:800:200C:417A]/foo", | 455 | url = "http://[1080::8:800:200C:417A]/foo", |
| 404 | scheme = "http", | 456 | scheme = "http", |
| 405 | host = "1080::8:800:200C:417A", | 457 | host = "1080::8:800:200C:417A", |
| 458 | hosttype = "ipv6", | ||
| 459 | ipv6 = "1080::8:800:200C:417A", | ||
| 406 | authority = "[1080::8:800:200C:417A]", | 460 | authority = "[1080::8:800:200C:417A]", |
| 407 | path = "/foo" | 461 | path = "/foo" |
| 408 | } | 462 | } |
| @@ -411,6 +465,8 @@ check_parse_url{ | |||
| 411 | url = "http://[::192.9.5.5]/ipng", | 465 | url = "http://[::192.9.5.5]/ipng", |
| 412 | scheme = "http", | 466 | scheme = "http", |
| 413 | host = "::192.9.5.5", | 467 | host = "::192.9.5.5", |
| 468 | hosttype = "ipv6", | ||
| 469 | ipv6 = "::192.9.5.5", | ||
| 414 | authority = "[::192.9.5.5]", | 470 | authority = "[::192.9.5.5]", |
| 415 | path = "/ipng" | 471 | path = "/ipng" |
| 416 | } | 472 | } |
| @@ -419,6 +475,8 @@ check_parse_url{ | |||
| 419 | url = "http://[::FFFF:129.144.52.38]:80/index.html", | 475 | url = "http://[::FFFF:129.144.52.38]:80/index.html", |
| 420 | scheme = "http", | 476 | scheme = "http", |
| 421 | host = "::FFFF:129.144.52.38", | 477 | host = "::FFFF:129.144.52.38", |
| 478 | hosttype = "ipv6", | ||
| 479 | ipv6 = "::FFFF:129.144.52.38", | ||
| 422 | port = "80", | 480 | port = "80", |
| 423 | authority = "[::FFFF:129.144.52.38]:80", | 481 | authority = "[::FFFF:129.144.52.38]:80", |
| 424 | path = "/index.html" | 482 | path = "/index.html" |
| @@ -428,6 +486,8 @@ check_parse_url{ | |||
| 428 | url = "http://[2010:836B:4179::836B:4179]", | 486 | url = "http://[2010:836B:4179::836B:4179]", |
| 429 | scheme = "http", | 487 | scheme = "http", |
| 430 | host = "2010:836B:4179::836B:4179", | 488 | host = "2010:836B:4179::836B:4179", |
| 489 | hosttype = "ipv6", | ||
| 490 | ipv6 = "2010:836B:4179::836B:4179", | ||
| 431 | authority = "[2010:836B:4179::836B:4179]", | 491 | authority = "[2010:836B:4179::836B:4179]", |
| 432 | } | 492 | } |
| 433 | 493 | ||
| @@ -435,6 +495,8 @@ check_parse_url{ | |||
| 435 | url = "//userinfo@[::FFFF:129.144.52.38]:port/path;params?query#fragment", | 495 | url = "//userinfo@[::FFFF:129.144.52.38]:port/path;params?query#fragment", |
| 436 | authority = "userinfo@[::FFFF:129.144.52.38]:port", | 496 | authority = "userinfo@[::FFFF:129.144.52.38]:port", |
| 437 | host = "::FFFF:129.144.52.38", | 497 | host = "::FFFF:129.144.52.38", |
| 498 | hosttype = "ipv6", | ||
| 499 | ipv6 = "::FFFF:129.144.52.38", | ||
| 438 | port = "port", | 500 | port = "port", |
| 439 | userinfo = "userinfo", | 501 | userinfo = "userinfo", |
| 440 | user = "userinfo", | 502 | user = "userinfo", |
| @@ -449,6 +511,8 @@ check_parse_url{ | |||
| 449 | scheme = "scheme", | 511 | scheme = "scheme", |
| 450 | authority = "user:password@[::192.9.5.5]:port", | 512 | authority = "user:password@[::192.9.5.5]:port", |
| 451 | host = "::192.9.5.5", | 513 | host = "::192.9.5.5", |
| 514 | hosttype = "ipv6", | ||
| 515 | ipv6 = "::192.9.5.5", | ||
| 452 | port = "port", | 516 | port = "port", |
| 453 | userinfo = "user:password", | 517 | userinfo = "user:password", |
| 454 | user = "user", | 518 | user = "user", |
| @@ -459,6 +523,138 @@ check_parse_url{ | |||
| 459 | fragment = "fragment" | 523 | fragment = "fragment" |
| 460 | } | 524 | } |
| 461 | 525 | ||
| 526 | print("testing host classification (hosttype/hostname/ipv4/ipv6)") | ||
| 527 | check_parse_url{ | ||
| 528 | url = "http://example.com/path", | ||
| 529 | scheme = "http", | ||
| 530 | authority = "example.com", | ||
| 531 | host = "example.com", | ||
| 532 | hosttype = "name", | ||
| 533 | hostname = "example.com", | ||
| 534 | path = "/path", | ||
| 535 | } | ||
| 536 | |||
| 537 | check_parse_url{ | ||
| 538 | url = "http://192.168.1.1:8080/path", | ||
| 539 | scheme = "http", | ||
| 540 | authority = "192.168.1.1:8080", | ||
| 541 | host = "192.168.1.1", | ||
| 542 | hosttype = "ipv4", | ||
| 543 | ipv4 = "192.168.1.1", | ||
| 544 | port = "8080", | ||
| 545 | path = "/path", | ||
| 546 | } | ||
| 547 | |||
| 548 | -- octet ranges aren't validated: any dotted-quad shape is classified ipv4, | ||
| 549 | -- even with an out-of-range octet like this one | ||
| 550 | check_parse_url{ | ||
| 551 | url = "http://999.1.1.1/path", | ||
| 552 | scheme = "http", | ||
| 553 | authority = "999.1.1.1", | ||
| 554 | host = "999.1.1.1", | ||
| 555 | hosttype = "ipv4", | ||
| 556 | ipv4 = "999.1.1.1", | ||
| 557 | path = "/path", | ||
| 558 | } | ||
| 559 | |||
| 560 | check_parse_url{ | ||
| 561 | url = "http://[::1]:8080/path", | ||
| 562 | scheme = "http", | ||
| 563 | authority = "[::1]:8080", | ||
| 564 | host = "::1", | ||
| 565 | hosttype = "ipv6", | ||
| 566 | ipv6 = "::1", | ||
| 567 | port = "8080", | ||
| 568 | path = "/path", | ||
| 569 | } | ||
| 570 | |||
| 571 | check_parse_url{ | ||
| 572 | url = "http://[2010:836B:4179::836B:4179]/", | ||
| 573 | scheme = "http", | ||
| 574 | authority = "[2010:836B:4179::836B:4179]", | ||
| 575 | host = "2010:836B:4179::836B:4179", | ||
| 576 | hosttype = "ipv6", | ||
| 577 | ipv6 = "2010:836B:4179::836B:4179", | ||
| 578 | path = "/", | ||
| 579 | } | ||
| 580 | |||
| 581 | -- no authority at all: no host, no hosttype | ||
| 582 | check_parse_url{ | ||
| 583 | url = "relative/path", | ||
| 584 | path = "relative/path", | ||
| 585 | } | ||
| 586 | |||
| 587 | check_build_url{ | ||
| 588 | url = "http://example.com/path", | ||
| 589 | scheme = "http", | ||
| 590 | hostname = "example.com", | ||
| 591 | path = "/path", | ||
| 592 | } | ||
| 593 | |||
| 594 | check_build_url{ | ||
| 595 | url = "http://1.2.3.4/path", | ||
| 596 | scheme = "http", | ||
| 597 | ipv4 = "1.2.3.4", | ||
| 598 | path = "/path", | ||
| 599 | } | ||
| 600 | |||
| 601 | check_build_url{ | ||
| 602 | url = "http://[::1]/path", | ||
| 603 | scheme = "http", | ||
| 604 | ipv6 = "::1", | ||
| 605 | path = "/path", | ||
| 606 | } | ||
| 607 | |||
| 608 | -- an already-bracketed ipv6/host value must not be bracketed again | ||
| 609 | check_build_url{ | ||
| 610 | url = "http://[::1]/path", | ||
| 611 | scheme = "http", | ||
| 612 | ipv6 = "[::1]", | ||
| 613 | path = "/path", | ||
| 614 | } | ||
| 615 | |||
| 616 | check_build_url{ | ||
| 617 | url = "http://[::1]/path", | ||
| 618 | scheme = "http", | ||
| 619 | host = "[::1]", | ||
| 620 | path = "/path", | ||
| 621 | } | ||
| 622 | |||
| 623 | local check_classify_host = function(raw, expect_hosttype, expect_host) | ||
| 624 | local hosttype, host = socket.url.classify_host(raw) | ||
| 625 | if hosttype ~= expect_hosttype or host ~= expect_host then | ||
| 626 | io.write("classify_host: for '", raw, "' expected ", expect_hosttype, | ||
| 627 | " '", expect_host, "' but got ", tostring(hosttype), " '", | ||
| 628 | tostring(host), "'\n") | ||
| 629 | os.exit() | ||
| 630 | end | ||
| 631 | end | ||
| 632 | |||
| 633 | check_classify_host("example.com", "name", "example.com") | ||
| 634 | check_classify_host("999.1.1.1", "ipv4", "999.1.1.1") | ||
| 635 | check_classify_host("192.168.1.1", "ipv4", "192.168.1.1") | ||
| 636 | check_classify_host("[::1]", "ipv6", "::1") | ||
| 637 | check_classify_host("2010:836B:4179::836B:4179", "ipv6", "2010:836B:4179::836B:4179") | ||
| 638 | |||
| 639 | -- legacy 'host' field takes precedence over hostname/ipv4/ipv6 if both given | ||
| 640 | check_build_url{ | ||
| 641 | url = "http://legacy.example/path", | ||
| 642 | scheme = "http", | ||
| 643 | host = "legacy.example", | ||
| 644 | hostname = "ignored.example", | ||
| 645 | path = "/path", | ||
| 646 | } | ||
| 647 | |||
| 648 | -- ambiguous: more than one of hostname/ipv4/ipv6 set, no 'host' to disambiguate | ||
| 649 | do | ||
| 650 | local ok = pcall(socket.url.build, | ||
| 651 | {scheme = "http", ipv4 = "1.2.3.4", hostname = "example.com", path = "/path"}) | ||
| 652 | if ok then | ||
| 653 | print("build: expected error for ambiguous host, got none") | ||
| 654 | os.exit() | ||
| 655 | end | ||
| 656 | end | ||
| 657 | |||
| 462 | print("testing URL building") | 658 | print("testing URL building") |
| 463 | check_build_url { | 659 | check_build_url { |
| 464 | url = "scheme://user:password@host:port/path;params?query#fragment", | 660 | url = "scheme://user:password@host:port/path;params?query#fragment", |
