Skip to content

Commit 9d2522d

Browse files
author
Aleksandr Borisov
committed
internal: non-strict RFC etag support
For some caldav providers, it wasn't possible to unmarshal unquoted text for etag. https://datatracker.ietf.org/doc/html/rfc2616#section-3.11 points to wrap text with quotes, but in reality it's sometimes ignored. internal: test for ETag UnmarshalText, ETag String, ETag unmarshal and marshal
1 parent 9d778f4 commit 9d2522d

File tree

2 files changed

+159
-1
lines changed

2 files changed

+159
-1
lines changed

internal/elements.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,15 @@ type GetETag struct {
377377
type ETag string
378378

379379
func (etag *ETag) UnmarshalText(b []byte) error {
380-
s, err := strconv.Unquote(string(b))
380+
s := string(b)
381+
// support for providers with non-strict https://datatracker.ietf.org/doc/html/rfc2616#section-3.11 implementation
382+
if !strings.HasPrefix(s, "\"") {
383+
*etag = ETag(s)
384+
return nil
385+
}
386+
387+
var err error
388+
s, err = strconv.Unquote(s)
381389
if err != nil {
382390
return fmt.Errorf("webdav: failed to unquote ETag: %v", err)
383391
}

internal/elements_test.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,153 @@ func TestTimeRoundTrip(t *testing.T) {
6363
t.Fatalf("invalid round-trip:\ngot= %s\nwant=%s", got, want)
6464
}
6565
}
66+
67+
func TestETag_UnmarshalText(t *testing.T) {
68+
type args struct {
69+
b []byte
70+
}
71+
tests := []struct {
72+
name string
73+
etag ETag
74+
args args
75+
wantErr bool
76+
wantETag ETag
77+
}{
78+
{
79+
name: "double quoted string",
80+
etag: "",
81+
args: args{
82+
b: []byte("\"1692394723948\""),
83+
},
84+
wantErr: false,
85+
wantETag: "1692394723948",
86+
},
87+
{
88+
name: "empty double quoted string",
89+
etag: "",
90+
args: args{
91+
b: []byte("\"\""),
92+
},
93+
wantErr: false,
94+
wantETag: "",
95+
},
96+
{
97+
name: "unquoted string",
98+
etag: "",
99+
args: args{
100+
b: []byte("1692394723948"),
101+
},
102+
wantErr: false,
103+
wantETag: "1692394723948",
104+
},
105+
{
106+
name: "empty string",
107+
etag: "",
108+
args: args{
109+
b: []byte(""),
110+
},
111+
wantErr: false,
112+
wantETag: "",
113+
},
114+
}
115+
116+
for _, tt := range tests {
117+
t.Run(tt.name, func(t *testing.T) {
118+
if err := tt.etag.UnmarshalText(tt.args.b); (err != nil) != tt.wantErr {
119+
t.Errorf("UnmarshalText() error = %v, wantErr %v", err, tt.wantErr)
120+
}
121+
122+
if tt.etag != tt.wantETag {
123+
t.Errorf("UnmarshalText() want %s, got %s", tt.wantETag, tt.etag)
124+
}
125+
})
126+
}
127+
}
128+
129+
func TestETag_String(t *testing.T) {
130+
tests := []struct {
131+
name string
132+
etag ETag
133+
want string
134+
}{
135+
{
136+
name: "string with double-quote",
137+
etag: "162392347123",
138+
want: "\"162392347123\"",
139+
},
140+
{
141+
name: "empty string with double-quote",
142+
etag: "",
143+
want: "\"\"",
144+
},
145+
}
146+
147+
for _, tt := range tests {
148+
t.Run(tt.name, func(t *testing.T) {
149+
if got := tt.etag.String(); got != tt.want {
150+
t.Errorf("String() = %v, want %v", got, tt.want)
151+
}
152+
})
153+
}
154+
}
155+
156+
func TestETag_UnmarshalAndMarshalText(t *testing.T) {
157+
tests := []struct {
158+
name string
159+
// initial ETag
160+
etag ETag
161+
// first value for ETag UnmarshalText
162+
unmarshalValue []byte
163+
// expected MarshalText() result after UnmarshalText()
164+
wantMarshalled []byte
165+
// expected UnmarshalText() result after MarshalText() with unmarshalValue
166+
wantUnmarshalledETag ETag
167+
}{
168+
{
169+
name: "string",
170+
etag: "",
171+
unmarshalValue: []byte("162392347123"),
172+
wantMarshalled: []byte("\"162392347123\""),
173+
wantUnmarshalledETag: "162392347123",
174+
},
175+
{
176+
name: "double-quoted",
177+
etag: "",
178+
unmarshalValue: []byte("\"162392347123\""),
179+
wantMarshalled: []byte("\"162392347123\""),
180+
wantUnmarshalledETag: "162392347123",
181+
},
182+
{
183+
name: "empty string",
184+
etag: "",
185+
unmarshalValue: []byte(""),
186+
wantMarshalled: []byte("\"\""),
187+
wantUnmarshalledETag: "",
188+
},
189+
}
190+
191+
for _, tt := range tests {
192+
t.Run(tt.name, func(t *testing.T) {
193+
if err := tt.etag.UnmarshalText(tt.unmarshalValue); err != nil {
194+
t.Errorf("UnmarshalText() = %v", err)
195+
}
196+
197+
m, err := tt.etag.MarshalText()
198+
if err != nil {
199+
t.Errorf("MarshalText() = %v", err)
200+
}
201+
202+
if !bytes.Equal(m, tt.wantMarshalled) {
203+
t.Errorf("MarshalText() want %s, got %s", tt.wantMarshalled, m)
204+
}
205+
206+
if err := tt.etag.UnmarshalText(m); err != nil {
207+
t.Errorf("UnmarshalText() got error after MarshalText() = %v", err)
208+
}
209+
210+
if tt.etag != tt.wantUnmarshalledETag {
211+
t.Errorf("UnmarshalText() after MarshalText() want %s, got %s", tt.wantUnmarshalledETag, tt.etag)
212+
}
213+
})
214+
}
215+
}

0 commit comments

Comments
 (0)