summaryrefslogtreecommitdiff
path: root/internal/util/domain.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/util/domain.go')
-rw-r--r--internal/util/domain.go138
1 files changed, 138 insertions, 0 deletions
diff --git a/internal/util/domain.go b/internal/util/domain.go
new file mode 100644
index 000000000..c64cc65d7
--- /dev/null
+++ b/internal/util/domain.go
@@ -0,0 +1,138 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package util
+
+import (
+ "net/url"
+ "strings"
+
+ "golang.org/x/net/idna"
+)
+
+var (
+ // IDNA (Internationalized Domain Names for Applications)
+ // profiles for fast punycode conv and full verification.
+ punifyProfile = *idna.Punycode
+ verifyProfile = *idna.Lookup
+)
+
+// PunifySafely validates the provided domain name,
+// and converts unicode chars to ASCII, i.e. punified form.
+func PunifySafely(domain string) (string, error) {
+ if i := strings.LastIndexByte(domain, ':'); i >= 0 {
+
+ // If there is a port included in domain, we
+ // strip it as colon is invalid in a hostname.
+ domain, port := domain[:i], domain[i:]
+ domain, err := verifyProfile.ToASCII(domain)
+ if err != nil {
+ return "", err
+ }
+
+ // Then rebuild with port after.
+ domain = strings.ToLower(domain)
+ return domain + port, nil
+ } else { //nolint:revive
+
+ // Otherwise we just punify domain as-is.
+ domain, err := verifyProfile.ToASCII(domain)
+ return strings.ToLower(domain), err
+ }
+}
+
+// Punify is a faster form of PunifySafely() without validation.
+func Punify(domain string) (string, error) {
+ domain, err := punifyProfile.ToASCII(domain)
+ return strings.ToLower(domain), err
+}
+
+// DePunify converts any punycode-encoded unicode characters
+// in domain name back to their origin unicode. Please note
+// that this performs minimal validation of domain name.
+func DePunify(domain string) (string, error) {
+ domain = strings.ToLower(domain)
+ return punifyProfile.ToUnicode(domain)
+}
+
+// URIMatches returns true if the expected URI matches
+// any of the given URIs, taking account of punycode.
+func URIMatches(expect *url.URL, uris ...*url.URL) (ok bool, err error) {
+
+ // Create new URL to hold
+ // punified URI information.
+ punyURI := new(url.URL)
+ *punyURI = *expect
+
+ // Set punified expected URL host.
+ punyURI.Host, err = Punify(expect.Host)
+ if err != nil {
+ return false, err
+ }
+
+ // Calculate expected URI string.
+ expectStr := punyURI.String()
+
+ // Use punyURI to iteratively
+ // store each punified URI info
+ // and generate punified URI
+ // strings to check against.
+ for _, uri := range uris {
+ *punyURI = *uri
+ punyURI.Host, err = Punify(uri.Host)
+ if err != nil {
+ return false, err
+ }
+
+ // Check for a match against expect.
+ if expectStr == punyURI.String() {
+ return true, nil
+ }
+ }
+
+ // Didn't match.
+ return false, nil
+}
+
+// PunifyURI returns a new copy of URI with the 'host'
+// part converted to punycode with PunifySafely().
+// For simple comparisons prefer the faster URIMatches().
+func PunifyURI(in *url.URL) (*url.URL, error) {
+ punyHost, err := PunifySafely(in.Host)
+ if err != nil {
+ return nil, err
+ }
+ out := new(url.URL)
+ *out = *in
+ out.Host = punyHost
+ return out, nil
+}
+
+// PunifyURIToStr returns given URI serialized with the
+// 'host' part converted to punycode with PunifySafely().
+// For simple comparisons prefer the faster URIMatches().
+func PunifyURIToStr(in *url.URL) (string, error) {
+ punyHost, err := PunifySafely(in.Host)
+ if err != nil {
+ return "", err
+ }
+ oldHost := in.Host
+ in.Host = punyHost
+ str := in.String()
+ in.Host = oldHost
+ return str, nil
+}