diff options
author | 2024-07-26 13:11:07 +0200 | |
---|---|---|
committer | 2024-07-26 13:11:07 +0200 | |
commit | ecfea10e359b9c9e7c0e6b5fd092e3caa5587df6 (patch) | |
tree | cc5cb01cf7d97baf7e47bb7a246fbc392c41fe9e /internal/util | |
parent | [feature] Federate interaction policies + Accepts; enforce policies (#3138) (diff) | |
download | gotosocial-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.go | 101 | ||||
-rw-r--r-- | internal/util/punycode.go | 68 |
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 +} |