Skip to content

Commit ea4be70

Browse files
committed
Fix for weekly.2011-04-27
1 parent d56ba64 commit ea4be70

File tree

5 files changed

+766
-762
lines changed

5 files changed

+766
-762
lines changed

cookie.go

Lines changed: 154 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -5,178 +5,177 @@
55
package web
66

77
import (
8-
"bytes"
9-
"fmt"
10-
"http"
11-
"io"
12-
"os"
13-
"sort"
14-
"strconv"
15-
"strings"
16-
"time"
8+
"bytes"
9+
"fmt"
10+
"http"
11+
"io"
12+
"os"
13+
"sort"
14+
"strings"
15+
"time"
1716
)
1817

18+
func sanitizeName(n string) string {
19+
n = strings.Replace(n, "\n", "-", -1)
20+
n = strings.Replace(n, "\r", "-", -1)
21+
return n
22+
}
23+
24+
func sanitizeValue(v string) string {
25+
v = strings.Replace(v, "\n", " ", -1)
26+
v = strings.Replace(v, "\r", " ", -1)
27+
v = strings.Replace(v, ";", " ", -1)
28+
return v
29+
}
30+
31+
func isCookieByte(c byte) bool {
32+
switch true {
33+
case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a,
34+
0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e:
35+
return true
36+
}
37+
return false
38+
}
39+
40+
func isSeparator(c byte) bool {
41+
switch c {
42+
case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t':
43+
return true
44+
}
45+
return false
46+
}
47+
func isChar(c byte) bool { return 0 <= c && c <= 127 }
48+
func isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 }
49+
func isToken(c byte) bool { return isChar(c) && !isCtl(c) && !isSeparator(c) }
50+
51+
func parseCookieValue(raw string) (string, bool) {
52+
raw = unquoteCookieValue(raw)
53+
for i := 0; i < len(raw); i++ {
54+
if !isCookieByte(raw[i]) {
55+
return "", false
56+
}
57+
}
58+
return raw, true
59+
}
60+
61+
func unquoteCookieValue(v string) string {
62+
if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' {
63+
return v[1 : len(v)-1]
64+
}
65+
return v
66+
}
67+
68+
func isCookieNameValid(raw string) bool {
69+
for _, c := range raw {
70+
if !isToken(byte(c)) {
71+
return false
72+
}
73+
}
74+
return true
75+
}
76+
1977
// writeSetCookies writes the wire representation of the set-cookies
2078
// to w. Each cookie is written on a separate "Set-Cookie: " line.
2179
// This choice is made because HTTP parsers tend to have a limit on
2280
// line-length, so it seems safer to place cookies on separate lines.
2381
func writeSetCookies(w io.Writer, kk []*http.Cookie) os.Error {
24-
if kk == nil {
25-
return nil
26-
}
27-
lines := make([]string, 0, len(kk))
28-
var b bytes.Buffer
29-
for _, c := range kk {
30-
b.Reset()
31-
// TODO(petar): c.Value (below) should be unquoted if it is recognized as quoted
32-
fmt.Fprintf(&b, "%s=%s", http.CanonicalHeaderKey(c.Name), c.Value)
33-
if c.Version > 0 {
34-
fmt.Fprintf(&b, "Version=%d; ", c.Version)
35-
}
36-
if len(c.Path) > 0 {
37-
fmt.Fprintf(&b, "; Path=%s", http.URLEscape(c.Path))
38-
}
39-
if len(c.Domain) > 0 {
40-
fmt.Fprintf(&b, "; Domain=%s", http.URLEscape(c.Domain))
41-
}
42-
if len(c.Expires.Zone) > 0 {
43-
fmt.Fprintf(&b, "; Expires=%s", c.Expires.Format(time.RFC1123))
44-
}
45-
if c.MaxAge >= 0 {
46-
fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge)
47-
}
48-
if c.HttpOnly {
49-
fmt.Fprintf(&b, "; HttpOnly")
50-
}
51-
if c.Secure {
52-
fmt.Fprintf(&b, "; Secure")
53-
}
54-
if len(c.Comment) > 0 {
55-
fmt.Fprintf(&b, "; Comment=%s", http.URLEscape(c.Comment))
56-
}
57-
lines = append(lines, "Set-Cookie: "+b.String()+"\r\n")
58-
}
59-
sort.SortStrings(lines)
60-
for _, l := range lines {
61-
if _, err := io.WriteString(w, l); err != nil {
62-
return err
63-
}
64-
}
65-
return nil
82+
if kk == nil {
83+
return nil
84+
}
85+
lines := make([]string, 0, len(kk))
86+
var b bytes.Buffer
87+
for _, c := range kk {
88+
b.Reset()
89+
fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value))
90+
if len(c.Path) > 0 {
91+
fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path))
92+
}
93+
if len(c.Domain) > 0 {
94+
fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain))
95+
}
96+
if len(c.Expires.Zone) > 0 {
97+
fmt.Fprintf(&b, "; Expires=%s", c.Expires.Format(time.RFC1123))
98+
}
99+
if c.MaxAge > 0 {
100+
fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge)
101+
} else if c.MaxAge < 0 {
102+
fmt.Fprintf(&b, "; Max-Age=0")
103+
}
104+
if c.HttpOnly {
105+
fmt.Fprintf(&b, "; HttpOnly")
106+
}
107+
if c.Secure {
108+
fmt.Fprintf(&b, "; Secure")
109+
}
110+
lines = append(lines, "Set-Cookie: "+b.String()+"\r\n")
111+
}
112+
sort.SortStrings(lines)
113+
for _, l := range lines {
114+
if _, err := io.WriteString(w, l); err != nil {
115+
return err
116+
}
117+
}
118+
return nil
66119
}
67120

68121
// writeCookies writes the wire representation of the cookies
69122
// to w. Each cookie is written on a separate "Cookie: " line.
70123
// This choice is made because HTTP parsers tend to have a limit on
71124
// line-length, so it seems safer to place cookies on separate lines.
72125
func writeCookies(w io.Writer, kk []*http.Cookie) os.Error {
73-
lines := make([]string, 0, len(kk))
74-
var b bytes.Buffer
75-
for _, c := range kk {
76-
b.Reset()
77-
n := c.Name
78-
if c.Version > 0 {
79-
fmt.Fprintf(&b, "$Version=%d; ", c.Version)
80-
}
81-
// TODO(petar): c.Value (below) should be unquoted if it is recognized as quoted
82-
fmt.Fprintf(&b, "%s=%s", http.CanonicalHeaderKey(n), c.Value)
83-
if len(c.Path) > 0 {
84-
fmt.Fprintf(&b, "; $Path=%s", http.URLEscape(c.Path))
85-
}
86-
if len(c.Domain) > 0 {
87-
fmt.Fprintf(&b, "; $Domain=%s", http.URLEscape(c.Domain))
88-
}
89-
if c.HttpOnly {
90-
fmt.Fprintf(&b, "; $HttpOnly")
91-
}
92-
if len(c.Comment) > 0 {
93-
fmt.Fprintf(&b, "; $Comment=%s", http.URLEscape(c.Comment))
94-
}
95-
lines = append(lines, "Cookie: "+b.String()+"\r\n")
96-
}
97-
sort.SortStrings(lines)
98-
for _, l := range lines {
99-
if _, err := io.WriteString(w, l); err != nil {
100-
return err
101-
}
102-
}
103-
return nil
126+
lines := make([]string, 0, len(kk))
127+
for _, c := range kk {
128+
lines = append(lines, fmt.Sprintf("Cookie: %s=%s\r\n", sanitizeName(c.Name), sanitizeValue(c.Value)))
129+
}
130+
sort.SortStrings(lines)
131+
for _, l := range lines {
132+
if _, err := io.WriteString(w, l); err != nil {
133+
return err
134+
}
135+
}
136+
return nil
104137
}
105138

106139
// readCookies parses all "Cookie" values from
107-
// the header h, removes the successfully parsed values from the
140+
// the header h, removes the successfully parsed values from the
108141
// "Cookie" key in h and returns the parsed Cookies.
109142
func readCookies(h http.Header) []*http.Cookie {
110-
cookies := []*http.Cookie{}
111-
lines, ok := h["Cookie"]
112-
if !ok {
113-
return cookies
114-
}
115-
unparsedLines := []string{}
116-
for _, line := range lines {
117-
parts := strings.Split(strings.TrimSpace(line), ";", -1)
118-
if len(parts) == 1 && parts[0] == "" {
119-
continue
120-
}
121-
// Per-line attributes
122-
var lineCookies = make(map[string]string)
123-
var version int
124-
var path string
125-
var domain string
126-
var comment string
127-
var httponly bool
128-
for i := 0; i < len(parts); i++ {
129-
parts[i] = strings.TrimSpace(parts[i])
130-
if len(parts[i]) == 0 {
131-
continue
132-
}
133-
attr, val := parts[i], ""
134-
var err os.Error
135-
if j := strings.Index(attr, "="); j >= 0 {
136-
attr, val = attr[:j], attr[j+1:]
137-
val, err = http.URLUnescape(val)
138-
if err != nil {
139-
continue
140-
}
141-
}
142-
switch strings.ToLower(attr) {
143-
case "$httponly":
144-
httponly = true
145-
case "$version":
146-
version, err = strconv.Atoi(val)
147-
if err != nil {
148-
version = 0
149-
continue
150-
}
151-
case "$domain":
152-
domain = val
153-
// TODO: Add domain parsing
154-
case "$path":
155-
path = val
156-
// TODO: Add path parsing
157-
case "$comment":
158-
comment = val
159-
default:
160-
lineCookies[attr] = val
161-
}
162-
}
163-
if len(lineCookies) == 0 {
164-
unparsedLines = append(unparsedLines, line)
165-
}
166-
for n, v := range lineCookies {
167-
cookies = append(cookies, &http.Cookie{
168-
Name: n,
169-
Value: v,
170-
Path: path,
171-
Domain: domain,
172-
Comment: comment,
173-
Version: version,
174-
HttpOnly: httponly,
175-
MaxAge: -1,
176-
Raw: line,
177-
})
178-
}
179-
}
180-
h["Cookie"] = unparsedLines, len(unparsedLines) > 0
181-
return cookies
143+
cookies := []*http.Cookie{}
144+
lines, ok := h["Cookie"]
145+
if !ok {
146+
return cookies
147+
}
148+
unparsedLines := []string{}
149+
for _, line := range lines {
150+
parts := strings.Split(strings.TrimSpace(line), ";", -1)
151+
if len(parts) == 1 && parts[0] == "" {
152+
continue
153+
}
154+
// Per-line attributes
155+
parsedPairs := 0
156+
for i := 0; i < len(parts); i++ {
157+
parts[i] = strings.TrimSpace(parts[i])
158+
if len(parts[i]) == 0 {
159+
continue
160+
}
161+
attr, val := parts[i], ""
162+
if j := strings.Index(attr, "="); j >= 0 {
163+
attr, val = attr[:j], attr[j+1:]
164+
}
165+
if !isCookieNameValid(attr) {
166+
continue
167+
}
168+
val, success := parseCookieValue(val)
169+
if !success {
170+
continue
171+
}
172+
cookies = append(cookies, &http.Cookie{Name: attr, Value: val})
173+
parsedPairs++
174+
}
175+
if parsedPairs == 0 {
176+
unparsedLines = append(unparsedLines, line)
177+
}
178+
}
179+
h["Cookie"] = unparsedLines, len(unparsedLines) > 0
180+
return cookies
182181
}

0 commit comments

Comments
 (0)