mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 20:11:19 -04:00
amp package.
This package contains a CacheURL function that modifies a URL to be accessed through an AMP cache, and the "AMP armor" data encoding scheme for encoding data into the AMP subset of HTML.
This commit is contained in:
parent
0f34a7778f
commit
c9e0dd287f
8 changed files with 1223 additions and 0 deletions
320
common/amp/cache_test.go
Normal file
320
common/amp/cache_test.go
Normal file
|
@ -0,0 +1,320 @@
|
|||
package amp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
func TestDomainPrefixBasic(t *testing.T) {
|
||||
// Tests expecting no error.
|
||||
for _, test := range []struct {
|
||||
domain, expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"xn--", ""},
|
||||
{"...", "---"},
|
||||
|
||||
// Should not apply mappings such as case folding and
|
||||
// normalization.
|
||||
{"b\u00fccher.de", "xn--bcher-de-65a"},
|
||||
{"B\u00fccher.de", "xn--Bcher-de-65a"},
|
||||
{"bu\u0308cher.de", "xn--bucher-de-hkf"},
|
||||
|
||||
// Check some that differ between IDNA 2003 and IDNA 2008.
|
||||
// https://unicode.org/reports/tr46/#Deviations
|
||||
// https://util.unicode.org/UnicodeJsps/idna.jsp
|
||||
{"faß.de", "xn--fa-de-mqa"},
|
||||
{"βόλοσ.com", "xn---com-4ld8c2a6a8e"},
|
||||
|
||||
// Lengths of 63 and 64. 64 is too long for a DNS label, but
|
||||
// domainPrefixBasic is not expected to check for that.
|
||||
{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
|
||||
{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
|
||||
|
||||
// https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#basic-algorithm
|
||||
{"example.com", "example-com"},
|
||||
{"foo.example.com", "foo-example-com"},
|
||||
{"foo-example.com", "foo--example-com"},
|
||||
{"xn--57hw060o.com", "xn---com-p33b41770a"},
|
||||
{"\u26a1\U0001f60a.com", "xn---com-p33b41770a"},
|
||||
{"en-us.example.com", "0-en--us-example-com-0"},
|
||||
} {
|
||||
output, err := domainPrefixBasic(test.domain)
|
||||
if err != nil || output != test.expected {
|
||||
t.Errorf("%+q → (%+q, %v), expected (%+q, %v)",
|
||||
test.domain, output, err, test.expected, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests expecting an error.
|
||||
for _, domain := range []string{
|
||||
"xn---",
|
||||
} {
|
||||
output, err := domainPrefixBasic(domain)
|
||||
if err == nil || output != "" {
|
||||
t.Errorf("%+q → (%+q, %v), expected (%+q, non-nil)",
|
||||
domain, output, err, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainPrefixFallback(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
domain, expected string
|
||||
}{
|
||||
{
|
||||
"",
|
||||
"4oymiquy7qobjgx36tejs35zeqt24qpemsnzgtfeswmrw6csxbkq",
|
||||
},
|
||||
{
|
||||
"example.com",
|
||||
"un42n5xov642kxrxrqiyanhcoupgql5lt4wtbkyt2ijflbwodfdq",
|
||||
},
|
||||
|
||||
// These checked against the output of
|
||||
// https://github.com/ampproject/amp-toolbox/tree/84cb3057e5f6c54d64369ddd285db1cb36237ee8/packages/cache-url,
|
||||
// using the widget at
|
||||
// https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#url-format.
|
||||
{
|
||||
"000000000000000000000000000000000000000000000000000000000000.com",
|
||||
"stejanx4hsijaoj4secyecy4nvqodk56kw72whwcmvdbtucibf5a",
|
||||
},
|
||||
{
|
||||
"00000000000000000000000000000000000000000000000000000000000a.com",
|
||||
"jdcvbsorpnc3hcjrhst56nfm6ymdpovlawdbm2efyxpvlt4cpbya",
|
||||
},
|
||||
{
|
||||
"00000000000000000000000000000000000000000000000000000000000\u03bb.com",
|
||||
"qhzqeumjkfpcpuic3vqruyjswcr7y7gcm3crqyhhywvn3xrhchfa",
|
||||
},
|
||||
} {
|
||||
output := domainPrefixFallback(test.domain)
|
||||
if output != test.expected {
|
||||
t.Errorf("%+q → %+q, expected %+q",
|
||||
test.domain, output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checks that domainPrefix chooses domainPrefixBasic or domainPrefixFallback as
|
||||
// appropriate; i.e., always returns string that is a valid DNS label and is
|
||||
// IDNA-decodable.
|
||||
func TestDomainPrefix(t *testing.T) {
|
||||
// A validating IDNA profile, which checks label length and that the
|
||||
// label contains only certain ASCII characters. It does not do the
|
||||
// ValidateLabels check, because that depends on the input having
|
||||
// certain properties.
|
||||
profile := idna.New(
|
||||
idna.VerifyDNSLength(true),
|
||||
idna.StrictDomainName(true),
|
||||
)
|
||||
for _, domain := range []string{
|
||||
"example.com",
|
||||
"\u0314example.com",
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // 63 bytes
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // 64 bytes
|
||||
"xn--57hw060o.com",
|
||||
"a b c",
|
||||
} {
|
||||
output := domainPrefix(domain)
|
||||
if bytes.IndexByte([]byte(output), '.') != -1 {
|
||||
t.Errorf("%+q → %+q contains a dot", domain, output)
|
||||
}
|
||||
_, err := profile.ToUnicode(output)
|
||||
if err != nil {
|
||||
t.Errorf("%+q → error %v", domain, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustParseURL(rawurl string) *url.URL {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func TestCacheURL(t *testing.T) {
|
||||
// Tests expecting no error.
|
||||
for _, test := range []struct {
|
||||
pub string
|
||||
cache string
|
||||
contentType string
|
||||
expected string
|
||||
}{
|
||||
// With or without trailing slash on pubURL.
|
||||
{
|
||||
"http://example.com/",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
"https://example-com.amp.cache/c/example.com",
|
||||
},
|
||||
{
|
||||
"http://example.com",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
"https://example-com.amp.cache/c/example.com",
|
||||
},
|
||||
// https pubURL.
|
||||
{
|
||||
"https://example.com/",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
"https://example-com.amp.cache/c/s/example.com",
|
||||
},
|
||||
// The content type should be escaped if necessary.
|
||||
{
|
||||
"http://example.com/",
|
||||
"https://amp.cache/",
|
||||
"/",
|
||||
"https://example-com.amp.cache/%2F/example.com",
|
||||
},
|
||||
// Retain pubURL path, query, and fragment, including escaping.
|
||||
{
|
||||
"http://example.com/my%2Fpath/index.html?a=1#fragment",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
"https://example-com.amp.cache/c/example.com/my%2Fpath/index.html?a=1#fragment",
|
||||
},
|
||||
// Retain scheme, userinfo, port, and path of cacheURL, escaping
|
||||
// whatever is necessary.
|
||||
{
|
||||
"http://example.com",
|
||||
"http://cache%2Fuser:cache%40pass@amp.cache:123/with/../../path/..%2f../",
|
||||
"c",
|
||||
"http://cache%2Fuser:cache%40pass@example-com.amp.cache:123/path/..%2f../c/example.com",
|
||||
},
|
||||
// Port numbers in pubURL are allowed, if they're the default
|
||||
// for scheme.
|
||||
{
|
||||
"http://example.com:80/",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
"https://example-com.amp.cache/c/example.com",
|
||||
},
|
||||
{
|
||||
"https://example.com:443/",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
"https://example-com.amp.cache/c/s/example.com",
|
||||
},
|
||||
// "?" at the end of cacheURL is okay, as long as the query is
|
||||
// empty.
|
||||
{
|
||||
"http://example.com/",
|
||||
"https://amp.cache/?",
|
||||
"c",
|
||||
"https://example-com.amp.cache/c/example.com",
|
||||
},
|
||||
|
||||
// https://developers.google.com/amp/cache/overview#example-requesting-document-using-tls
|
||||
{
|
||||
"https://example.com/amp_document.html",
|
||||
"https://cdn.ampproject.org/",
|
||||
"c",
|
||||
"https://example-com.cdn.ampproject.org/c/s/example.com/amp_document.html",
|
||||
},
|
||||
// https://developers.google.com/amp/cache/overview#example-requesting-image-using-plain-http
|
||||
{
|
||||
"http://example.com/logo.png",
|
||||
"https://cdn.ampproject.org/",
|
||||
"i",
|
||||
"https://example-com.cdn.ampproject.org/i/example.com/logo.png",
|
||||
},
|
||||
// https://developers.google.com/amp/cache/overview#query-parameter-example
|
||||
{
|
||||
"https://example.com/g?value=Hello%20World",
|
||||
"https://cdn.ampproject.org/",
|
||||
"c",
|
||||
"https://example-com.cdn.ampproject.org/c/s/example.com/g?value=Hello%20World",
|
||||
},
|
||||
} {
|
||||
pubURL := mustParseURL(test.pub)
|
||||
cacheURL := mustParseURL(test.cache)
|
||||
outputURL, err := CacheURL(pubURL, cacheURL, test.contentType)
|
||||
if err != nil {
|
||||
t.Errorf("%+q %+q %+q → error %v",
|
||||
test.pub, test.cache, test.contentType, err)
|
||||
continue
|
||||
}
|
||||
if outputURL.String() != test.expected {
|
||||
t.Errorf("%+q %+q %+q → %+q, expected %+q",
|
||||
test.pub, test.cache, test.contentType, outputURL, test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Tests expecting an error.
|
||||
for _, test := range []struct {
|
||||
pub string
|
||||
cache string
|
||||
contentType string
|
||||
}{
|
||||
// Empty content type.
|
||||
{
|
||||
"http://example.com/",
|
||||
"https://amp.cache/",
|
||||
"",
|
||||
},
|
||||
// Empty host.
|
||||
{
|
||||
"http:///index.html",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
},
|
||||
// Empty scheme.
|
||||
{
|
||||
"//example.com/",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
},
|
||||
// Unrecognized scheme.
|
||||
{
|
||||
"ftp://example.com/",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
},
|
||||
// Wrong port number for scheme.
|
||||
{
|
||||
"http://example.com:443/",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
},
|
||||
// userinfo in pubURL.
|
||||
{
|
||||
"http://user@example.com/",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
},
|
||||
{
|
||||
"http://user:pass@example.com/",
|
||||
"https://amp.cache/",
|
||||
"c",
|
||||
},
|
||||
// cacheURL may not contain a query.
|
||||
{
|
||||
"http://example.com/",
|
||||
"https://amp.cache/?a=1",
|
||||
"c",
|
||||
},
|
||||
// cacheURL may not contain a fragment.
|
||||
{
|
||||
"http://example.com/",
|
||||
"https://amp.cache/#fragment",
|
||||
"c",
|
||||
},
|
||||
} {
|
||||
pubURL := mustParseURL(test.pub)
|
||||
cacheURL := mustParseURL(test.cache)
|
||||
outputURL, err := CacheURL(pubURL, cacheURL, test.contentType)
|
||||
if err == nil {
|
||||
t.Errorf("%+q %+q %+q → %+q, expected error",
|
||||
test.pub, test.cache, test.contentType, outputURL)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue