summaryrefslogtreecommitdiff
path: root/internal/util
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2024-07-26 13:11:07 +0200
committerLibravatar GitHub <noreply@github.com>2024-07-26 13:11:07 +0200
commitecfea10e359b9c9e7c0e6b5fd092e3caa5587df6 (patch)
treecc5cb01cf7d97baf7e47bb7a246fbc392c41fe9e /internal/util
parent[feature] Federate interaction policies + Accepts; enforce policies (#3138) (diff)
downloadgotosocial-ecfea10e359b9c9e7c0e6b5fd092e3caa5587df6.tar.xz
[bugfix] Use punycode for `host` part of `resource` query param when doing webfinger requests (#3133)
* [bugfix] use punycode when webfingering * account for punycode when checking if final URI matches expected * hmm * fix test
Diffstat (limited to 'internal/util')
-rw-r--r--internal/util/puny_test.go101
-rw-r--r--internal/util/punycode.go68
2 files changed, 169 insertions, 0 deletions
diff --git a/internal/util/puny_test.go b/internal/util/puny_test.go
new file mode 100644
index 000000000..a0ffd30b4
--- /dev/null
+++ b/internal/util/puny_test.go
@@ -0,0 +1,101 @@
+// 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_test
+
+import (
+ "net/url"
+ "strconv"
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
+ "github.com/superseriousbusiness/gotosocial/testrig"
+)
+
+type PunyTestSuite struct {
+ suite.Suite
+}
+
+func (suite *PunyTestSuite) TestMatches() {
+ for i, testCase := range []struct {
+ expect *url.URL
+ actual []*url.URL
+ match bool
+ }{
+ {
+ expect: testrig.URLMustParse("https://%D5%A9%D5%B8%D6%82%D5%A9.%D5%B0%D5%A1%D5%B5/@ankap"),
+ actual: []*url.URL{
+ testrig.URLMustParse("https://xn--69aa8bzb.xn--y9a3aq/users/ankap"),
+ testrig.URLMustParse("https://xn--69aa8bzb.xn--y9a3aq/@ankap"),
+ },
+ match: true,
+ },
+ {
+ expect: testrig.URLMustParse("https://xn--69aa8bzb.xn--y9a3aq/@ankap"),
+ actual: []*url.URL{
+ testrig.URLMustParse("https://xn--69aa8bzb.xn--y9a3aq/users/ankap"),
+ testrig.URLMustParse("https://xn--69aa8bzb.xn--y9a3aq/@ankap"),
+ },
+ match: true,
+ },
+ {
+ expect: testrig.URLMustParse("https://թութ.հայ/@ankap"),
+ actual: []*url.URL{
+ testrig.URLMustParse("https://xn--69aa8bzb.xn--y9a3aq/users/ankap"),
+ testrig.URLMustParse("https://xn--69aa8bzb.xn--y9a3aq/@ankap"),
+ },
+ match: true,
+ },
+ {
+ expect: testrig.URLMustParse("https://թութ.հայ/@ankap"),
+ actual: []*url.URL{
+ testrig.URLMustParse("https://example.org/users/ankap"),
+ testrig.URLMustParse("https://%D5%A9%D5%B8%D6%82%D5%A9.%D5%B0%D5%A1%D5%B5/@ankap"),
+ },
+ match: true,
+ },
+ {
+ expect: testrig.URLMustParse("https://example.org/@ankap"),
+ actual: []*url.URL{
+ testrig.URLMustParse("https://xn--69aa8bzb.xn--y9a3aq/users/ankap"),
+ testrig.URLMustParse("https://xn--69aa8bzb.xn--y9a3aq/@ankap"),
+ },
+ match: false,
+ },
+ } {
+ matches, err := util.URIMatches(
+ testCase.expect,
+ testCase.actual...,
+ )
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ if matches != testCase.match {
+ suite.Failf(
+ "case "+strconv.Itoa(i)+" matches not equal expected",
+ "wanted %t, got %t",
+ testCase.match, matches,
+ )
+ }
+ }
+}
+
+func TestPunyTestSuite(t *testing.T) {
+ suite.Run(t, new(PunyTestSuite))
+}
diff --git a/internal/util/punycode.go b/internal/util/punycode.go
index 4a595a281..cc1b57a27 100644
--- a/internal/util/punycode.go
+++ b/internal/util/punycode.go
@@ -18,6 +18,7 @@
package util
import (
+ "net/url"
"strings"
"golang.org/x/net/idna"
@@ -42,3 +43,70 @@ func DePunify(domain string) (string, error) {
out, err := idna.ToUnicode(domain)
return strings.ToLower(out), err
}
+
+// 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) (bool, error) {
+ // Normalize expect to punycode.
+ expectPuny, err := PunifyURI(expect)
+ if err != nil {
+ return false, err
+ }
+ expectStr := expectPuny.String()
+
+ for _, uri := range uris {
+ uriPuny, err := PunifyURI(uri)
+ if err != nil {
+ return false, err
+ }
+
+ if uriPuny.String() == expectStr {
+ // Looks good.
+ return true, nil
+ }
+ }
+
+ // Didn't match.
+ return false, nil
+}
+
+// PunifyURI returns a copy of the given URI
+// with the 'host' part converted to punycode.
+func PunifyURI(in *url.URL) (*url.URL, error) {
+ // Take a copy of in.
+ out := new(url.URL)
+ *out = *in
+
+ // Normalize host to punycode.
+ var err error
+ out.Host, err = Punify(in.Host)
+ return out, err
+}
+
+// PunifyURIStr returns a copy of the given URI
+// string with the 'host' part converted to punycode.
+func PunifyURIStr(in string) (string, error) {
+ inURI, err := url.Parse(in)
+ if err != nil {
+ return "", err
+ }
+
+ outURIPuny, err := Punify(inURI.Host)
+ if err != nil {
+ return "", err
+ }
+
+ if outURIPuny == in {
+ // Punify did nothing, so in was
+ // already punified, return as-is.
+ return in, nil
+ }
+
+ // Take a copy of in.
+ outURI := new(url.URL)
+ *outURI = *inURI
+
+ // Normalize host to punycode.
+ outURI.Host = outURIPuny
+ return outURI.String(), err
+}