-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathcookiestxt.go
More file actions
145 lines (126 loc) · 3.68 KB
/
cookiestxt.go
File metadata and controls
145 lines (126 loc) · 3.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2017 Meng Zhuo.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package cookiestxt implement parser of cookies txt format that commonly supported by
// curl / wget / aria2c / chrome / firefox
//
// see http://www.cookiecentral.com/faq/#3.5 for more detail
package cookiestxt
import (
"bufio"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
)
const (
// http://www.cookiecentral.com/faq/#3.5
// The domain that created AND that can read the variable.
domainIdx = iota
// A TRUE/FALSE value indicating if all machines within a given domain can access the variable. This value is set automatically by the browser, depending on the value you set for domain.
flagIdx
// The path within the domain that the variable is valid for.
pathIdx
// A TRUE/FALSE value indicating if a secure connection with the domain is needed to access the variable.
secureIdx
// The UNIX time that the variable will expire on. UNIX time is defined as the number of seconds since Jan 1, 1970 00:00:00 GMT.
expirationIdx
// The name of the variable.
nameIdx
// The value of the variable.
valueIdx
)
const (
httpOnlyPrefix = "#HttpOnly_"
fieldsCount = 7
)
// Parse cookie txt file format from input stream
func Parse(rd io.Reader) (cl []*http.Cookie, err error) {
scanner := bufio.NewScanner(rd)
// allow bigger lines if needed
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
var line int
for scanner.Scan() {
line++
trimmed := strings.TrimSpace(scanner.Text())
if trimmed == "" {
continue
}
// skip comments except HttpOnly_ prefixed lines
if strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(trimmed, httpOnlyPrefix) {
continue
}
var c *http.Cookie
c, err = ParseLine(trimmed)
if err != nil {
return cl, fmt.Errorf("cookiestxt line:%d, err:%s", line, err)
}
cl = append(cl, c)
}
err = scanner.Err()
return
}
// ParseLine parse single cookie from one line with stricter validation
func ParseLine(raw string) (c *http.Cookie, err error) {
raw = strings.TrimSpace(raw)
f := strings.Fields(raw)
if len(f) == fieldsCount-1 {
// missing value -> treat as empty
f = append(f, "")
} else if len(f) < fieldsCount {
return nil, fmt.Errorf("expecting fields=%d, got=%d", fieldsCount, len(f))
}
// basic required fields
if strings.TrimSpace(f[domainIdx]) == "" {
return nil, fmt.Errorf("empty domain")
}
if strings.TrimSpace(f[nameIdx]) == "" {
return nil, fmt.Errorf("empty cookie name")
}
// validate flag (second field) format but do not use value
if _, err = parseBoolStrict(f[flagIdx]); err != nil {
return nil, fmt.Errorf("invalid flag value: %v", err)
}
// secure field must be a valid boolean token
secureVal, err := parseBoolStrict(f[secureIdx])
if err != nil {
return nil, fmt.Errorf("invalid secure value: %v", err)
}
c = &http.Cookie{
Raw: raw,
Name: f[nameIdx],
Value: f[valueIdx],
Path: f[pathIdx],
MaxAge: 0,
Secure: secureVal,
}
var ts int64
ts, err = strconv.ParseInt(f[expirationIdx], 10, 64)
if err != nil {
return
}
c.Expires = time.Unix(ts, 0)
c.Domain = f[domainIdx]
if strings.HasPrefix(c.Domain, httpOnlyPrefix) {
c.HttpOnly = true
c.Domain = c.Domain[len(httpOnlyPrefix):]
}
return
}
// parseBoolStrict validates boolean tokens and returns an error on unknown token.
// Accepts: "1"/"0", "TRUE"/"FALSE" (case-insensitive).
func parseBoolStrict(input string) (bool, error) {
s := strings.TrimSpace(input)
if s == "1" || s == "0" {
return s == "1", nil
}
if strings.EqualFold(s, "TRUE") {
return true, nil
}
if strings.EqualFold(s, "FALSE") {
return false, nil
}
return false, fmt.Errorf("expect TRUE/FALSE or 1/0, got %q", input)
}