-
Notifications
You must be signed in to change notification settings - Fork 25
Creates the NormalizeAddr Func to Format IPv6 Addresses #157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+629
−0
Merged
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
f541e5b
create NormalizeAddr which accepts addresses and returns a formatted …
kheina d8d2dfe
add more tests and switch back to using url.Parse for better edge cas…
kheina 7217bad
update empty or invalid errors to just empty
kheina 7466b96
update NormalizeAddr func comment
kheina 24aa702
update file licenses
kheina ba4215f
comment typo
kheina ebe38e0
add additional rfc reference in comment, use loop instead of recursio…
kheina f985fe1
remove the commented vault tests
kheina 1eb90a9
remove net.SplitHostPort because it will parse anything into port as …
kheina b9faf19
add edge case for missing port values
kheina 25cf3e7
move NormalizeAddr from configutil to parseutil
kheina File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| package configutil | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "net" | ||
| "net/url" | ||
| "strings" | ||
| ) | ||
|
|
||
| // general delimiters as defined in RFC-3986 §2.2 | ||
| // See: https://www.rfc-editor.org/rfc/rfc3986#section-2.2 | ||
| const genDelims = ":/?#[]@" | ||
|
|
||
| func normalizeHostPort(host string, port string) (string, error) { | ||
| if host == "" { | ||
| return "", fmt.Errorf("empty hostname") | ||
| } | ||
| if ip := net.ParseIP(host); ip != nil { | ||
| if ip.To4() == nil && ip.To16() != nil && port == "" { | ||
| // this is a unique case, host is ipv6 and requires brackets due to | ||
| // being part of a url, but they won't be added by net.JoinHostPort | ||
| // as there is no port | ||
| // See: https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2 | ||
| return "[" + ip.String() + "]", nil | ||
| } | ||
| host = ip.String() | ||
| } else if strings.Contains(host, ":") { | ||
| // host is an invalid ipv6 literal. | ||
| // hosts cannot contain certain reserved characters, including ":" | ||
| // See: https://www.rfc-editor.org/rfc/rfc3986#section-2.2, | ||
| // https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2 | ||
| return "", fmt.Errorf("host contains an invalid IPv6 literal") | ||
| } | ||
| if port == "" { | ||
| return host, nil | ||
| } | ||
| return net.JoinHostPort(host, port), nil | ||
| } | ||
|
|
||
| func parseUrl(addr string) (string, error) { | ||
| if u, err := url.Parse(addr); err == nil { | ||
| if strings.HasSuffix(u.Host, ":") { | ||
| return "", fmt.Errorf("url has malformed host: missing port value after colon") | ||
| } | ||
| if u.Host, err = normalizeHostPort(u.Hostname(), u.Port()); err != nil { | ||
| return "", err | ||
| } | ||
| return u.String(), nil | ||
| } | ||
| return "", fmt.Errorf("failed to parse address") | ||
| } | ||
|
|
||
| // NormalizeAddr takes an address as a string and returns a normalized copy. | ||
| // If the address is a URL, IP Address, or host:port address that includes an | ||
| // IPv6 address, the normalized copy will be conformant with RFC-5952 §4. If | ||
| // the address cannot be parsed, an error will be returned. | ||
| // | ||
| // There are two valid formats: | ||
| // | ||
| // - hosts: "host" | ||
| // - may be any of: IPv6 literal, IPv4 literal, dns name, or [sub]domain name | ||
| // - IPv6 literals cannot be encapsulated within square brackets in this format | ||
| // | ||
| // - URIs: "[scheme://] [user@] host [:port] [/path] [?query] [#frag]" | ||
| // - format should conform with RFC-3986 §3 or else the returned address may | ||
| // be parsed and formatted incorrectly | ||
| // - hosts containing IPv6 literals MUST be encapsulated within square brackets, | ||
| // as defined in RFC-3986 §3.2.2 and RFC-5952 §6 | ||
| // - all non-host components are optional | ||
| // | ||
| // See: | ||
| // - https://www.rfc-editor.org/rfc/rfc5952 | ||
| // - https://www.rfc-editor.org/rfc/rfc3986 | ||
| func NormalizeAddr(address string) (string, error) { | ||
| if address == "" { | ||
| return "", fmt.Errorf("empty address") | ||
| } | ||
|
|
||
| if strings.HasPrefix(address, "[") && strings.HasSuffix(address, "]") { | ||
| return "", fmt.Errorf("address cannot be encapsulated by brackets") | ||
| } | ||
|
|
||
| if ip := net.ParseIP(address); ip != nil { | ||
| return ip.String(), nil | ||
| } | ||
|
|
||
| // if the provided address does not have a scheme provided, attempt to | ||
| // provide one and re-parse the result. this is done by looking for the | ||
| // first general delimiter and checking if it exists or if it's not a colon | ||
| // or by subsequently checking if the first character of the address is a | ||
| // letter or a colon or if the colon is part of "://" | ||
| // See: https://www.rfc-editor.org/rfc/rfc3986#section-3 | ||
| // | ||
| // though the first character being a colon is not mentioned in the scheme | ||
| // spec, we check for it as url.Parse will read certain invalid ipv6 | ||
| // addresses as valid urls, and we want to avoid that | ||
| idx := strings.IndexAny(address, genDelims) | ||
| switch { | ||
| case idx < 0: | ||
| fallthrough | ||
| case address[idx] != ':': | ||
| fallthrough | ||
| // by this point we already know that idx > 0 and that address[idx] == ':' | ||
| case idx > 1 && !strings.HasPrefix(address[idx:], "://"): | ||
| const scheme = "default://" | ||
| // attempt to parse it as a url. we only want to try this func when we | ||
| // know for sure it has a scheme, since it will parse ANYTHING, but | ||
| // just put it into u.Path when called without the scheme | ||
| u, err := parseUrl(scheme + address) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| return strings.TrimPrefix(u, scheme), nil | ||
|
|
||
| default: | ||
| return parseUrl(address) | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.