-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpatch_codec.go
139 lines (118 loc) · 2.78 KB
/
patch_codec.go
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
package braid
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
"github.com/pkg/errors"
)
type Patch struct {
Name string
ContentRange string
ContentLength uint64
ExtraHeaders map[string]string
Body []byte
}
func (p Patch) MarshalRequest() ([]byte, error) {
if p.ContentLength == 0 {
p.ContentLength = uint64(len(p.Body))
}
var buf bytes.Buffer
if p.Name != "" {
buf.WriteString(fmt.Sprintf("Patch-Name: \"%v\"\n", p.Name))
}
if p.ContentRange != "" {
buf.WriteString(fmt.Sprintf("Content-Range: %v\n", p.ContentRange))
}
buf.WriteString(fmt.Sprintf("Content-Length: %v\n", p.ContentLength))
for header, value := range p.ExtraHeaders {
buf.WriteString(fmt.Sprintf("%v: %v\n", header, value))
}
buf.WriteString("\n")
_, err := io.CopyN(&buf, bytes.NewReader(p.Body), int64(p.ContentLength))
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
type patchReadState int
const (
patchReadStateHeaders patchReadState = iota
patchReadStateBody
patchReadStateDone
)
func (p *Patch) UnmarshalRequest(r io.Reader) error {
state := patchReadStateHeaders
for {
line, err := readUntil(r, '\n')
if err == io.EOF {
state = patchReadStateDone
} else if err != nil {
return err
}
switch state {
case patchReadStateHeaders:
if len(bytes.TrimSpace(line)) == 0 {
state = patchReadStateBody
continue
}
err := p.handleHeader(line)
if err != nil {
return err
}
case patchReadStateBody, patchReadStateDone:
if len(bytes.TrimSpace(line)) == 0 {
state = patchReadStateDone
break
}
err := p.handleBody(line)
if err != nil {
return err
}
}
if state == patchReadStateDone {
break
}
}
if uint64(len(p.Body)) < p.ContentLength {
return errors.Errorf("bad content length (expected %v, got %v)", p.ContentLength, len(p.Body))
}
p.Body = p.Body[:p.ContentLength]
return nil
}
func (p *Patch) handleHeader(line []byte) error {
if len(bytes.TrimSpace(line)) == 0 {
return nil
}
parts := bytes.SplitN(line, []byte(":"), 2)
if len(parts) < 2 {
return errors.Errorf("bad patch header: %v", string(line))
}
header, value := bytes.TrimSpace(parts[0]), bytes.TrimSpace(parts[1])
switch strings.ToLower(string(header)) {
case "patch-name":
p.Name = string(bytes.Trim(value, `"`))
case "content-range":
p.ContentRange = string(value)
case "content-length":
contentLength, err := strconv.ParseUint(string(value), 10, 64)
if err != nil {
return err
}
p.ContentLength = contentLength
default:
if p.ExtraHeaders == nil {
p.ExtraHeaders = make(map[string]string)
}
p.ExtraHeaders[string(header)] = string(value)
}
return nil
}
func (p *Patch) handleBody(line []byte) error {
if len(bytes.TrimSpace(line)) == 0 {
return nil
}
p.Body = append(p.Body, line...)
return nil
}