Implement ampCacheRendezvous.

This commit is contained in:
David Fifield 2021-07-18 14:57:45 -06:00
parent c13810192d
commit 5adb994028
2 changed files with 181 additions and 7 deletions

View file

@ -1,11 +1,14 @@
package lib
import (
"bytes"
"errors"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"git.torproject.org/pluggable-transports/snowflake.git/common/amp"
)
// ampCacheRendezvous is a rendezvousMethod that communicates with the
@ -49,9 +52,22 @@ func (r *ampCacheRendezvous) Exchange(encPollReq []byte) ([]byte, error) {
log.Println("AMP cache URL:", r.cacheURL)
log.Println("Front domain:", r.front)
// Suffix the path with the broker's client registration handler.
reqURL := r.brokerURL.ResolveReference(&url.URL{Path: "client"})
req, err := http.NewRequest("POST", reqURL.String(), bytes.NewReader(encPollReq))
// We cannot POST a body through an AMP cache, so instead we GET and
// encode the client poll request message into the URL.
reqURL := r.brokerURL.ResolveReference(&url.URL{
Path: "amp/client/" + amp.EncodePath(encPollReq),
})
if r.cacheURL != nil {
// Rewrite reqURL to its AMP cache version.
var err error
reqURL, err = amp.CacheURL(reqURL, r.cacheURL, "c")
if err != nil {
return nil, err
}
}
req, err := http.NewRequest("GET", reqURL.String(), nil)
if err != nil {
return nil, err
}
@ -71,8 +87,38 @@ func (r *ampCacheRendezvous) Exchange(encPollReq []byte) ([]byte, error) {
log.Printf("AMP cache rendezvous response: %s", resp.Status)
if resp.StatusCode != http.StatusOK {
// A non-200 status indicates an error:
// * If the broker returns a page with invalid AMP, then the AMP
// cache returns a redirect that would bypass the cache.
// * If the broker returns a 5xx status, the AMP cache
// translates it to a 404.
// https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#redirect-%26-error-handling
return nil, errors.New(BrokerErrorUnexpected)
}
if _, err := resp.Location(); err == nil {
// The Google AMP Cache may return a "silent redirect" with
// status 200, a Location header set, and a JavaScript redirect
// in the body. The redirect points directly at the origin
// server for the request (bypassing the AMP cache). We do not
// follow redirects nor execute JavaScript, but in any case we
// cannot extract information from this response and can only
// treat it as an error.
return nil, errors.New(BrokerErrorUnexpected)
}
return limitedRead(resp.Body, readLimit)
lr := io.LimitReader(resp.Body, readLimit+1)
dec, err := amp.NewArmorDecoder(lr)
if err != nil {
return nil, err
}
encPollResp, err := ioutil.ReadAll(dec)
if err != nil {
return nil, err
}
if lr.(*io.LimitedReader).N == 0 {
// We hit readLimit while decoding AMP armor, that's an error.
return nil, io.ErrUnexpectedEOF
}
return encPollResp, err
}