package onionloc import ( "net/http" "strings" "golang.org/x/net/html" ) const ( HTTPHeaderName = "Onion-Location" ) func HTTP(rsp *http.Response) (string, bool) { v, ok := rsp.Header[HTTPHeaderName] if !ok { return "", false } if len(v) != 1 { return "", false } return v[0], true } func HTML(rsp *http.Response) (string, bool) { z := html.NewTokenizer(rsp.Body) for { tt := z.Next() if tt == html.ErrorToken { return "", false // EOF and other errors } switch tt { case html.StartTagToken, html.SelfClosingTagToken: t := z.Token() // Looking for the html meta tag, see: // https://www.w3schools.com/tags/tag_meta.asp // // We expect two attributes: "key" and "content" if strings.ToLower(t.Data) != "meta" { break } if len(t.Attr) != 2 { break } // We're looking for the "http-equiv" key, see: // https://www.w3schools.com/tags/att_meta_http_equiv.asp // // In particular with the value "onion-location", see: // https://community.torproject.org/onion-services/advanced/onion-location/ // // If we have all this and a following content // attribute, that is the Onion-Location URL that this // page advertises. We make no attempt to validate // whether the content value is really an onion address. attr := t.Attr[0] if strings.ToLower(attr.Key) != "http-equiv" { break } if strings.ToLower(attr.Val) != "onion-location" { break } attr = t.Attr[1] if strings.ToLower(attr.Key) != "content" { break } return attr.Val, true default: } } }