You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

119 lines
3.4 KiB

  1. package openid
  2. import (
  3. "errors"
  4. "io"
  5. "io/ioutil"
  6. "strings"
  7. "golang.org/x/net/html"
  8. )
  9. var yadisHeaders = map[string]string{
  10. "Accept": "application/xrds+xml"}
  11. func yadisDiscovery(id string, getter httpGetter) (opEndpoint string, opLocalID string, err error) {
  12. // Section 6.2.4 of Yadis 1.0 specifications.
  13. // The Yadis Protocol is initiated by the Relying Party Agent
  14. // with an initial HTTP request using the Yadis URL.
  15. // This request MUST be either a GET or a HEAD request.
  16. // A GET or HEAD request MAY include an HTTP Accept
  17. // request-header (HTTP 14.1) specifying MIME media type,
  18. // application/xrds+xml.
  19. resp, err := getter.Get(id, yadisHeaders)
  20. if err != nil {
  21. return "", "", err
  22. }
  23. defer resp.Body.Close()
  24. // Section 6.2.5 from Yadis 1.0 spec: Response
  25. contentType := resp.Header.Get("Content-Type")
  26. // The response MUST be one of:
  27. // (see 6.2.6 for precedence)
  28. if l := resp.Header.Get("X-XRDS-Location"); l != "" {
  29. // 2. HTTP response-headers that include an X-XRDS-Location
  30. // response-header, together with a document
  31. return getYadisResourceDescriptor(l, getter)
  32. } else if strings.Contains(contentType, "text/html") {
  33. // 1. An HTML document with a <head> element that includes a
  34. // <meta> element with http-equiv attribute, X-XRDS-Location,
  35. metaContent, err := findMetaXrdsLocation(resp.Body)
  36. if err == nil {
  37. return getYadisResourceDescriptor(metaContent, getter)
  38. }
  39. return "", "", err
  40. } else if strings.Contains(contentType, "application/xrds+xml") {
  41. // 4. A document of MIME media type, application/xrds+xml.
  42. body, err := ioutil.ReadAll(resp.Body)
  43. if err == nil {
  44. return parseXrds(body)
  45. }
  46. return "", "", err
  47. }
  48. // 3. HTTP response-headers only, which MAY include an
  49. // X-XRDS-Location response-header, a content-type
  50. // response-header specifying MIME media type,
  51. // application/xrds+xml, or both.
  52. // (this is handled by one of the 2 previous if statements)
  53. return "", "", errors.New("No expected header, or content type")
  54. }
  55. // Similar as above, but we expect an absolute Yadis document URL.
  56. func getYadisResourceDescriptor(id string, getter httpGetter) (opEndpoint string, opLocalID string, err error) {
  57. resp, err := getter.Get(id, yadisHeaders)
  58. if err != nil {
  59. return "", "", err
  60. }
  61. defer resp.Body.Close()
  62. // 4. A document of MIME media type, application/xrds+xml.
  63. body, err := ioutil.ReadAll(resp.Body)
  64. if err == nil {
  65. return parseXrds(body)
  66. }
  67. return "", "", err
  68. }
  69. // Search for
  70. // <head>
  71. // <meta http-equiv="X-XRDS-Location" content="....">
  72. func findMetaXrdsLocation(input io.Reader) (location string, err error) {
  73. tokenizer := html.NewTokenizer(input)
  74. inHead := false
  75. for {
  76. tt := tokenizer.Next()
  77. switch tt {
  78. case html.ErrorToken:
  79. return "", tokenizer.Err()
  80. case html.StartTagToken, html.EndTagToken:
  81. tk := tokenizer.Token()
  82. if tk.Data == "head" {
  83. if tt == html.StartTagToken {
  84. inHead = true
  85. } else {
  86. return "", errors.New("Meta X-XRDS-Location not found")
  87. }
  88. } else if inHead && tk.Data == "meta" {
  89. ok := false
  90. content := ""
  91. for _, attr := range tk.Attr {
  92. if attr.Key == "http-equiv" &&
  93. strings.ToLower(attr.Val) == "x-xrds-location" {
  94. ok = true
  95. } else if attr.Key == "content" {
  96. content = attr.Val
  97. }
  98. }
  99. if ok && len(content) > 0 {
  100. return content, nil
  101. }
  102. }
  103. }
  104. }
  105. return "", errors.New("Meta X-XRDS-Location not found")
  106. }