package search import ( "encoding/xml" "fmt" "strings" ) // OpenSearchDescription is the root element of an // OSD 1.1 document. Browsers (Firefox, Chromium) parse this to register // a search engine; the {searchTerms} placeholder in URL templates is // substituted by the browser at search time. // // Spec: https://github.com/dewitt/opensearch/blob/master/opensearch-1-1-draft-6.md type OpenSearchDescription struct { XMLName xml.Name `xml:"OpenSearchDescription"` XMLNS string `xml:"xmlns,attr"` ShortName string `xml:"ShortName"` Description string `xml:"Description"` InputEncoding string `xml:"InputEncoding"` Image *Image `xml:"Image,omitempty"` URLs []URL `xml:"Url"` SearchForm string `xml:"SearchForm,omitempty"` Developer string `xml:"Developer,omitempty"` Attribution string `xml:"Attribution,omitempty"` SyndicationRgt string `xml:"SyndicationRight,omitempty"` AdultContent string `xml:"AdultContent,omitempty"` } // URL is a single element pointing at a search template. type URL struct { Type string `xml:"type,attr"` Template string `xml:"template,attr"` Method string `xml:"method,attr,omitempty"` } // Image is a element for the search engine icon. type Image struct { Height int `xml:"height,attr"` Width int `xml:"width,attr"` Type string `xml:"type,attr"` Value string `xml:",chardata"` } const xmlNS = "http://a9.com/-/spec/opensearch/1.1/" // DescriptionForProvider builds an OSD document that points the browser // directly at the upstream provider — useful when the user wants to add // e.g. urbandictionary as a standalone search engine. func DescriptionForProvider(p Provider) OpenSearchDescription { osd := OpenSearchDescription{ XMLNS: xmlNS, ShortName: p.Name, Description: p.Description, InputEncoding: "UTF-8", URLs: []URL{{ Type: "text/html", // OpenSearch uses {searchTerms}; convert from our internal {q}. Template: strings.ReplaceAll(p.SearchURL, "{q}", "{searchTerms}"), }}, SearchForm: p.HomeURL, } if p.IconURL != "" { osd.Image = &Image{Height: 16, Width: 16, Type: "image/x-icon", Value: p.IconURL} } return osd } // DescriptionForRouter builds an OSD document that points the browser at // this service's own /search endpoint, so prefix routing ("ud foo") works // from the browser's address bar. // // publicURL must be the externally-visible base URL (no trailing slash). func DescriptionForRouter(publicURL string) OpenSearchDescription { publicURL = strings.TrimRight(publicURL, "/") return OpenSearchDescription{ XMLNS: xmlNS, ShortName: "huntsman", Description: "Multi-provider search router (ud, gh, steam)", InputEncoding: "UTF-8", URLs: []URL{{ Type: "text/html", Template: fmt.Sprintf("%s/search?q={searchTerms}", publicURL), }}, SearchForm: publicURL + "/", } } // Marshal serializes an OSD document with the standard XML prolog. func Marshal(osd OpenSearchDescription) ([]byte, error) { body, err := xml.MarshalIndent(osd, "", " ") if err != nil { return nil, err } return append([]byte(xml.Header), body...), nil }