diff options
| author | Thijs Schreijer <thijs@thijsschreijer.nl> | 2026-08-30 15:34:15 +0200 |
|---|---|---|
| committer | Thijs Schreijer <thijs@thijsschreijer.nl> | 2026-08-30 15:34:15 +0200 |
| commit | 6fe110be47c0fc34bc9dce6d377ce9b48ab27c2e (patch) | |
| tree | 15eabb11745033d916e61c565ea4205bae84d0fe /src/url.lua | |
| parent | 84c2bb905bda5f1fc58195171c75723b5fec6e87 (diff) | |
| download | luasocket-6fe110be47c0fc34bc9dce6d377ce9b48ab27c2e.tar.gz luasocket-6fe110be47c0fc34bc9dce6d377ce9b48ab27c2e.tar.bz2 luasocket-6fe110be47c0fc34bc9dce6d377ce9b48ab27c2e.zip | |
feat(url): classify host as name/ipv4/ipv6 in parse and build
parse() now sets hosttype ("name"/"ipv4"/"ipv6") plus a matching
hostname/ipv4/ipv6 field alongside host. build() accepts those same
fields when host is absent, erroring if more than one is set.
classify_host is exported for standalone use; it classifies by shape
(colon present -> ipv6, dotted-quad shape -> ipv4) rather than
validating the address.
Diffstat (limited to 'src/url.lua')
| -rw-r--r-- | src/url.lua | 53 |
1 files changed, 47 insertions, 6 deletions
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 |
