github.com/geniusesgroup/libgo@v0.0.0-20220713101832-828057a9d3d4/http/header-cookie.go (about)

     1  /* For license and copyright information please see LEGAL file in repository */
     2  
     3  package http
     4  
     5  import (
     6  	"strings"
     7  
     8  	"../convert"
     9  	"../protocol"
    10  )
    11  
    12  type Cookies string
    13  
    14  // Cookies parses and returns the Cookie headers.
    15  // By related RFC we just support one Cookie in header.
    16  // https://tools.ietf.org/html/rfc6265#section-5.4
    17  func (c Cookies) Next() (cookie Cookie, remain Cookies) {
    18  	var rawRemain, _ = cookie.UnmarshalFrom(string(c))
    19  	remain = Cookies(rawRemain)
    20  	return
    21  }
    22  
    23  // Cookies parses and returns the Cookie headers.
    24  // By related RFC we just support one Cookie in header.
    25  // https://tools.ietf.org/html/rfc6265#section-5.4
    26  func (h *header) Cookies() (cookies []Cookie) {
    27  	var cookie = h.Get(HeaderKeyCookie)
    28  	if len(cookie) == 0 {
    29  		return
    30  	}
    31  	var index int
    32  	cookies = make([]Cookie, 0, 8)
    33  	var c Cookie
    34  	for {
    35  		index = strings.IndexByte(cookie, ';')
    36  		if index == -1 {
    37  			c.Unmarshal(cookie)
    38  			cookies = append(cookies, c)
    39  			return
    40  		}
    41  		c.Unmarshal(cookie[:index])
    42  		cookies = append(cookies, c)
    43  
    44  		cookie = cookie[index+2:]
    45  	}
    46  }
    47  
    48  // MarshalCookies parses and set them to the Cookie header.
    49  func (h *header) MarshalCookies(cookies []Cookie) {
    50  	// TODO::: make buffer by needed size.
    51  	var b strings.Builder
    52  	var ln = len(cookies)
    53  	var i int
    54  	for ; ; i++ {
    55  		b.WriteString(cookies[i].Name)
    56  		b.WriteByte('=')
    57  		b.WriteString(cookies[i].Value)
    58  		if i < ln {
    59  			b.WriteString(SemiColonSpace)
    60  		} else {
    61  			break
    62  		}
    63  	}
    64  	h.Set(HeaderKeyCookie, b.String())
    65  }
    66  
    67  // Cookie represents an HTTP cookie as sent in the Cookie header of an HTTP request.
    68  // implement by https://tools.ietf.org/html/rfc6265#section-4.2
    69  type Cookie struct {
    70  	Name  string
    71  	Value string
    72  }
    73  
    74  // CheckAndSanitize check if the cookie is in standard by RFC and try to fix them. It returns last error!
    75  func (c *Cookie) CheckAndSanitize() (err protocol.Error) {
    76  	c.Name, err = sanitizeCookieName(c.Name)
    77  	if err != nil {
    78  		return
    79  	}
    80  	c.Value, err = sanitizeCookieValue(c.Value)
    81  	return
    82  }
    83  
    84  // Marshal returns the serialization of the cookie.
    85  func (c *Cookie) Marshal() string {
    86  	return c.Name + "=" + c.Value
    87  }
    88  
    89  // Unmarshal parse given cookie value to c and return
    90  func (c *Cookie) Unmarshal(cookie string) {
    91  	var equalIndex = strings.IndexByte(cookie, '=')
    92  	// First check no equal(=) sign or empty name or value
    93  	if equalIndex < 1 || equalIndex == len(cookie)-1 {
    94  		return
    95  	}
    96  	c.Name = cookie[:equalIndex]
    97  	c.Value = cookie[equalIndex+1:]
    98  }
    99  
   100  // Unmarshal parse given cookies value to c and return
   101  func (c *Cookie) UnmarshalFrom(cookies string) (remain string, err protocol.Error) {
   102  	var equalIndex = strings.IndexByte(cookies, '=')
   103  	// First check no equal(=) sign or empty name or value
   104  	if equalIndex < 1 || equalIndex == len(cookies)-1 {
   105  		// err =
   106  		return
   107  	}
   108  	c.Name = cookies[:equalIndex]
   109  
   110  	var SemiColonIndex = strings.IndexByte(cookies, ';')
   111  	if SemiColonIndex == -1 {
   112  		c.Value = cookies[equalIndex+1:]
   113  	} else {
   114  		c.Value = cookies[equalIndex+1 : SemiColonIndex]
   115  		remain = cookies[SemiColonIndex+2:] // Due to have space after semi colon do +2
   116  	}
   117  	return
   118  }
   119  
   120  func sanitizeCookieName(n string) (name string, err protocol.Error) {
   121  	var ln = len(n)
   122  	var buf = make([]byte, 0, ln)
   123  	var b byte
   124  	for i := 0; i < ln; i++ {
   125  		b = n[i]
   126  		if b == '\n' || b == '\r' {
   127  			buf = append(buf, '-')
   128  			err = ErrCookieBadName
   129  		} else {
   130  			buf = append(buf, b)
   131  		}
   132  	}
   133  	name = convert.UnsafeByteSliceToString(buf)
   134  	return
   135  }
   136  
   137  // https://tools.ietf.org/html/rfc6265#section-4.1.1
   138  // cookie-value      = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
   139  // cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
   140  //           ; US-ASCII characters excluding CTLs,
   141  //           ; whitespace, DQUOTE, comma, semicolon,
   142  //           ; and backslash
   143  // Don't check for ; due to Unmarshal will panic for bad cookie!!
   144  func sanitizeCookieValue(v string) (value string, err protocol.Error) {
   145  	var ln = len(v)
   146  	var buf = make([]byte, 0, ln)
   147  	var b byte
   148  	for i := 0; i < ln; i++ {
   149  		b = v[i]
   150  		if 0x20 <= b && b < 0x7f && b != ' ' && b != '"' && b != ',' && b != '\\' {
   151  			buf = append(buf, b)
   152  		} else {
   153  			err = ErrCookieBadValue
   154  		}
   155  	}
   156  	value = convert.UnsafeByteSliceToString(buf)
   157  	return
   158  }