link.go raw

   1  package link
   2  
   3  import (
   4  	"net/http"
   5  	"regexp"
   6  	"strings"
   7  )
   8  
   9  var (
  10  	commaRegexp      = regexp.MustCompile(`,\s{0,}`)
  11  	valueCommaRegexp = regexp.MustCompile(`([^"]),`)
  12  	equalRegexp      = regexp.MustCompile(` *= *`)
  13  	keyRegexp        = regexp.MustCompile(`[a-z*]+`)
  14  	linkRegexp       = regexp.MustCompile(`\A<(.+)>;(.+)\z`)
  15  	semiRegexp       = regexp.MustCompile(`;\s{0,}`)
  16  	valRegexp        = regexp.MustCompile(`"+([^"]+)"+`)
  17  )
  18  
  19  // Group returned by Parse, contains multiple links indexed by "rel".
  20  type Group map[string]*Link
  21  
  22  // Link contains a Link item with URI, Rel, and other non-URI components in Extra.
  23  type Link struct {
  24  	URI   string
  25  	Rel   string
  26  	Extra map[string]string
  27  }
  28  
  29  // String returns the URI.
  30  func (l *Link) String() string {
  31  	return l.URI
  32  }
  33  
  34  // ParseRequest parses the provided *http.Request into a Group.
  35  func ParseRequest(req *http.Request) Group {
  36  	if req == nil {
  37  		return nil
  38  	}
  39  
  40  	return ParseHeader(req.Header)
  41  }
  42  
  43  // ParseResponse parses the provided *http.Response into a Group.
  44  func ParseResponse(resp *http.Response) Group {
  45  	if resp == nil {
  46  		return nil
  47  	}
  48  
  49  	return ParseHeader(resp.Header)
  50  }
  51  
  52  // ParseHeader retrieves the Link header from the provided http.Header and parses it into a Group.
  53  func ParseHeader(h http.Header) Group {
  54  	if headers, found := h["Link"]; found {
  55  		return Parse(strings.Join(headers, ", "))
  56  	}
  57  
  58  	return nil
  59  }
  60  
  61  // Parse parses the provided string into a Group.
  62  func Parse(s string) Group {
  63  	if s == "" {
  64  		return nil
  65  	}
  66  
  67  	s = valueCommaRegexp.ReplaceAllString(s, "$1")
  68  
  69  	group := Group{}
  70  
  71  	for _, l := range commaRegexp.Split(s, -1) {
  72  		linkMatches := linkRegexp.FindAllStringSubmatch(l, -1)
  73  
  74  		if len(linkMatches) == 0 {
  75  			return nil
  76  		}
  77  
  78  		pieces := linkMatches[0]
  79  
  80  		link := &Link{URI: pieces[1], Extra: map[string]string{}}
  81  
  82  		for _, extra := range semiRegexp.Split(pieces[2], -1) {
  83  			vals := equalRegexp.Split(extra, -1)
  84  
  85  			if len(vals) != 2 {
  86  				continue
  87  			}
  88  
  89  			val := strings.TrimSpace(vals[1])
  90  			key := keyRegexp.FindString(vals[0])
  91  			vsm := valRegexp.FindStringSubmatch(vals[1])
  92  
  93  			if len(vsm) == 2 {
  94  				val = vsm[1]
  95  			}
  96  
  97  			if key == "rel" {
  98  				vals := strings.Split(val, " ")
  99  				rels := []string{vals[0]}
 100  
 101  				if len(vals) > 1 {
 102  					for _, v := range vals[1:] {
 103  						if !strings.HasPrefix(v, "http") {
 104  							rels = append(rels, v)
 105  						}
 106  					}
 107  				}
 108  
 109  				rel := strings.Join(rels, " ")
 110  
 111  				link.Rel = rel
 112  				group[rel] = link
 113  			} else {
 114  				link.Extra[key] = val
 115  			}
 116  		}
 117  	}
 118  
 119  	return group
 120  }
 121