github.com/cloudwego/hertz@v0.9.3/pkg/protocol/cookie_test.go (about) 1 /* 2 * Copyright 2022 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 * The MIT License (MIT) 17 * 18 * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors 19 * 20 * Permission is hereby granted, free of charge, to any person obtaining a copy 21 * of this software and associated documentation files (the "Software"), to deal 22 * in the Software without restriction, including without limitation the rights 23 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 * copies of the Software, and to permit persons to whom the Software is 25 * furnished to do so, subject to the following conditions: 26 * 27 * The above copyright notice and this permission notice shall be included in 28 * all copies or substantial portions of the Software. 29 * 30 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 * THE SOFTWARE. 37 * 38 * This file may have been modified by CloudWeGo authors. All CloudWeGo 39 * Modifications are Copyright 2022 CloudWeGo Authors. 40 */ 41 42 package protocol 43 44 import ( 45 "math/rand" 46 "strings" 47 "testing" 48 "time" 49 50 "github.com/cloudwego/hertz/pkg/common/test/assert" 51 ) 52 53 func TestCookieAppendBytes(t *testing.T) { 54 t.Parallel() 55 56 c := &Cookie{} 57 58 testCookieAppendBytes(t, c, "", "bar", "bar") 59 testCookieAppendBytes(t, c, "foo", "", "foo=") 60 testCookieAppendBytes(t, c, "ффф", "12 лодлы", "ффф=12 лодлы") 61 62 c.SetDomain("foobar.com") 63 testCookieAppendBytes(t, c, "a", "b", "a=b; domain=foobar.com") 64 65 c.SetPath("/a/b") 66 testCookieAppendBytes(t, c, "aa", "bb", "aa=bb; domain=foobar.com; path=/a/b") 67 68 c.SetExpire(CookieExpireDelete) 69 testCookieAppendBytes(t, c, "xxx", "yyy", "xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b") 70 71 c.SetPartitioned(true) 72 testCookieAppendBytes(t, c, "xxx", "yyy", "xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b; secure; Partitioned") 73 } 74 75 func testCookieAppendBytes(t *testing.T, c *Cookie, key, value, expectedS string) { 76 c.SetKey(key) 77 c.SetValue(value) 78 result := string(c.AppendBytes(nil)) 79 if result != expectedS { 80 t.Fatalf("Unexpected cookie %q. Expecting %q", result, expectedS) 81 } 82 } 83 84 func TestParseRequestCookies(t *testing.T) { 85 t.Parallel() 86 87 testParseRequestCookies(t, "", "") 88 testParseRequestCookies(t, "=", "") 89 testParseRequestCookies(t, "foo", "foo") 90 testParseRequestCookies(t, "=foo", "foo") 91 testParseRequestCookies(t, "bar=", "bar=") 92 testParseRequestCookies(t, "xxx=aa;bb=c; =d; ;;e=g", "xxx=aa; bb=c; d; e=g") 93 testParseRequestCookies(t, "a;b;c; d=1;d=2", "a; b; c; d=1; d=2") 94 testParseRequestCookies(t, " %D0%B8%D0%B2%D0%B5%D1%82=a%20b%3Bc ;s%20s=aaa ", "%D0%B8%D0%B2%D0%B5%D1%82=a%20b%3Bc; s%20s=aaa") 95 } 96 97 func testParseRequestCookies(t *testing.T, s, expectedS string) { 98 cookies := parseRequestCookies(nil, []byte(s)) 99 ss := string(appendRequestCookieBytes(nil, cookies)) 100 if ss != expectedS { 101 t.Fatalf("Unexpected cookies after parsing: %q. Expecting %q. String to parse %q", ss, expectedS, s) 102 } 103 } 104 105 func TestAppendRequestCookieBytes(t *testing.T) { 106 t.Parallel() 107 108 testAppendRequestCookieBytes(t, "=", "") 109 testAppendRequestCookieBytes(t, "foo=", "foo=") 110 testAppendRequestCookieBytes(t, "=bar", "bar") 111 testAppendRequestCookieBytes(t, "привет=a bc&s s=aaa", "привет=a bc; s s=aaa") 112 } 113 114 func testAppendRequestCookieBytes(t *testing.T, s, expectedS string) { 115 kvs := strings.Split(s, "&") 116 cookies := make([]argsKV, 0, len(kvs)) 117 for _, ss := range kvs { 118 tmp := strings.SplitN(ss, "=", 2) 119 if len(tmp) != 2 { 120 t.Fatalf("Cannot find '=' in %q, part of %q", ss, s) 121 } 122 cookies = append(cookies, argsKV{ 123 key: []byte(tmp[0]), 124 value: []byte(tmp[1]), 125 }) 126 } 127 128 prefix := "foobar" 129 result := string(appendRequestCookieBytes([]byte(prefix), cookies)) 130 if result[:len(prefix)] != prefix { 131 t.Fatalf("unexpected prefix %q. Expecting %q for cookie %q", result[:len(prefix)], prefix, s) 132 } 133 result = result[len(prefix):] 134 if result != expectedS { 135 t.Fatalf("Unexpected result %q. Expecting %q for cookie %q", result, expectedS, s) 136 } 137 } 138 139 func TestCookieSecureHTTPOnly(t *testing.T) { 140 t.Parallel() 141 142 var c Cookie 143 144 if err := c.Parse("foo=bar; HttpOnly; secure"); err != nil { 145 t.Fatalf("unexpected error: %s", err) 146 } 147 if !c.Secure() { 148 t.Fatalf("secure must be set") 149 } 150 if !c.HTTPOnly() { 151 t.Fatalf("HttpOnly must be set") 152 } 153 s := c.String() 154 if !strings.Contains(s, "; secure") { 155 t.Fatalf("missing secure flag in cookie %q", s) 156 } 157 if !strings.Contains(s, "; HttpOnly") { 158 t.Fatalf("missing HttpOnly flag in cookie %q", s) 159 } 160 } 161 162 func TestCookieSecure(t *testing.T) { 163 t.Parallel() 164 165 var c Cookie 166 167 if err := c.Parse("foo=bar; secure"); err != nil { 168 t.Fatalf("unexpected error: %s", err) 169 } 170 if !c.Secure() { 171 t.Fatalf("secure must be set") 172 } 173 s := c.String() 174 if !strings.Contains(s, "; secure") { 175 t.Fatalf("missing secure flag in cookie %q", s) 176 } 177 178 if err := c.Parse("foo=bar"); err != nil { 179 t.Fatalf("unexpected error: %s", err) 180 } 181 if c.Secure() { 182 t.Fatalf("Unexpected secure flag set") 183 } 184 s = c.String() 185 if strings.Contains(s, "secure") { 186 t.Fatalf("unexpected secure flag in cookie %q", s) 187 } 188 } 189 190 func TestCookieSameSite(t *testing.T) { 191 t.Parallel() 192 193 var c Cookie 194 195 if err := c.Parse("foo=bar; samesite"); err != nil { 196 t.Fatalf("unexpected error: %s", err) 197 } 198 if c.SameSite() != CookieSameSiteDefaultMode { 199 t.Fatalf("SameSite must be set") 200 } 201 s := c.String() 202 if !strings.Contains(s, "; SameSite") { 203 t.Fatalf("missing SameSite flag in cookie %q", s) 204 } 205 206 if err := c.Parse("foo=bar; samesite=lax"); err != nil { 207 t.Fatalf("unexpected error: %s", err) 208 } 209 if c.SameSite() != CookieSameSiteLaxMode { 210 t.Fatalf("SameSite Lax Mode must be set") 211 } 212 s = c.String() 213 if !strings.Contains(s, "; SameSite=Lax") { 214 t.Fatalf("missing SameSite flag in cookie %q", s) 215 } 216 217 if err := c.Parse("foo=bar; samesite=strict"); err != nil { 218 t.Fatalf("unexpected error: %s", err) 219 } 220 if c.SameSite() != CookieSameSiteStrictMode { 221 t.Fatalf("SameSite Strict Mode must be set") 222 } 223 s = c.String() 224 if !strings.Contains(s, "; SameSite=Strict") { 225 t.Fatalf("missing SameSite flag in cookie %q", s) 226 } 227 228 if err := c.Parse("foo=bar; samesite=none"); err != nil { 229 t.Fatalf("unexpected error: %s", err) 230 } 231 if c.SameSite() != CookieSameSiteNoneMode { 232 t.Fatalf("SameSite None Mode must be set") 233 } 234 s = c.String() 235 if !strings.Contains(s, "; SameSite=None") { 236 t.Fatalf("missing SameSite flag in cookie %q", s) 237 } 238 239 if err := c.Parse("foo=bar"); err != nil { 240 t.Fatalf("unexpected error: %s", err) 241 } 242 c.SetSameSite(CookieSameSiteNoneMode) 243 s = c.String() 244 if !strings.Contains(s, "; SameSite=None") { 245 t.Fatalf("missing SameSite flag in cookie %q", s) 246 } 247 if !strings.Contains(s, "; secure") { 248 t.Fatalf("missing Secure flag in cookie %q", s) 249 } 250 251 if err := c.Parse("foo=bar"); err != nil { 252 t.Fatalf("unexpected error: %s", err) 253 } 254 if c.SameSite() != CookieSameSiteDisabled { 255 t.Fatalf("Unexpected SameSite flag set") 256 } 257 s = c.String() 258 if strings.Contains(s, "SameSite") { 259 t.Fatalf("unexpected SameSite flag in cookie %q", s) 260 } 261 } 262 263 func TestCookiePartitioned(t *testing.T) { 264 t.Parallel() 265 266 var c Cookie 267 268 if err := c.Parse("__Host-name=value; Secure; Path=/; SameSite=None; Partitioned;"); err != nil { 269 t.Fatalf("unexpected error for valid paritionedd cookie: %s", err) 270 } 271 if !c.Partitioned() { 272 t.Fatalf("partitioned must be set") 273 } 274 275 if err := c.Parse("foo=bar"); err != nil { 276 t.Fatalf("unexpected error: %s", err) 277 } 278 c.SetPartitioned(true) 279 s := c.String() 280 if !strings.Contains(s, "; Partitioned") { 281 t.Fatalf("missing Partitioned flag in cookie %q", s) 282 } 283 if !strings.Contains(s, "; secure") { 284 t.Fatalf("missing Secure flag in cookie %q", s) 285 } 286 } 287 288 func TestCookieMaxAge(t *testing.T) { 289 t.Parallel() 290 291 var c Cookie 292 293 maxAge := 100 294 if err := c.Parse("foo=bar; max-age=100"); err != nil { 295 t.Fatalf("unexpected error: %s", err) 296 } 297 if maxAge != c.MaxAge() { 298 t.Fatalf("max-age must be set") 299 } 300 s := c.String() 301 if !strings.Contains(s, "; max-age=100") { 302 t.Fatalf("missing max-age flag in cookie %q", s) 303 } 304 305 if err := c.Parse("foo=bar; expires=Tue, 10 Nov 2009 23:00:00 GMT; max-age=100;"); err != nil { 306 t.Fatalf("unexpected error: %s", err) 307 } 308 if maxAge != c.MaxAge() { 309 t.Fatalf("max-age ignored") 310 } 311 s = c.String() 312 if s != "foo=bar; max-age=100" { 313 t.Fatalf("missing max-age in cookie %q", s) 314 } 315 316 expires := time.Unix(100, 0) 317 c.SetExpire(expires) 318 s = c.String() 319 if s != "foo=bar; max-age=100" { 320 t.Fatalf("expires should be ignored due to max-age: %q", s) 321 } 322 323 c.SetMaxAge(0) 324 s = c.String() 325 if s != "foo=bar; expires=Thu, 01 Jan 1970 00:01:40 GMT" { 326 t.Fatalf("missing expires %q", s) 327 } 328 } 329 330 func TestCookieHttpOnly(t *testing.T) { 331 t.Parallel() 332 333 var c Cookie 334 335 if err := c.Parse("foo=bar; HttpOnly"); err != nil { 336 t.Fatalf("unexpected error: %s", err) 337 } 338 if !c.HTTPOnly() { 339 t.Fatalf("HTTPOnly must be set") 340 } 341 s := c.String() 342 if !strings.Contains(s, "; HttpOnly") { 343 t.Fatalf("missing HttpOnly flag in cookie %q", s) 344 } 345 346 if err := c.Parse("foo=bar"); err != nil { 347 t.Fatalf("unexpected error: %s", err) 348 } 349 if c.HTTPOnly() { 350 t.Fatalf("Unexpected HTTPOnly flag set") 351 } 352 s = c.String() 353 if strings.Contains(s, "HttpOnly") { 354 t.Fatalf("unexpected HttpOnly flag in cookie %q", s) 355 } 356 } 357 358 func TestCookieParse(t *testing.T) { 359 t.Parallel() 360 361 testCookieParse(t, "foo", "foo") 362 testCookieParse(t, "foo=bar", "foo=bar") 363 testCookieParse(t, "foo=", "foo=") 364 testCookieParse(t, `foo="bar"`, "foo=bar") 365 testCookieParse(t, `"foo"=bar`, `"foo"=bar`) 366 testCookieParse(t, "foo=bar; Domain=aaa.com; PATH=/foo/bar", "foo=bar; domain=aaa.com; path=/foo/bar") 367 testCookieParse(t, "foo=bar; max-age= 101 ; expires= Tue, 10 Nov 2009 23:00:00 GMT", "foo=bar; max-age=101") 368 testCookieParse(t, " xxx = yyy ; path=/a/b;;;domain=foobar.com ; expires= Tue, 10 Nov 2009 23:00:00 GMT ; ;;", 369 "xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b") 370 } 371 372 func Test_decodeCookieArg(t *testing.T) { 373 src := []byte(" \"aaaaabbbbb\" ") 374 dst := make([]byte, 0) 375 dst = decodeCookieArg(dst, src, true) 376 assert.DeepEqual(t, []byte("aaaaabbbbb"), dst) 377 } 378 379 func testCookieParse(t *testing.T, s, expectedS string) { 380 var c Cookie 381 if err := c.Parse(s); err != nil { 382 t.Fatalf("unexpected error: %s", err) 383 } 384 result := string(c.Cookie()) 385 if result != expectedS { 386 t.Fatalf("unexpected cookies %q. Expecting %q. Original %q", result, expectedS, s) 387 } 388 } 389 390 func Test_WarnIfInvalid(t *testing.T) { 391 assert.False(t, warnIfInvalid([]byte(";"))) 392 assert.False(t, warnIfInvalid([]byte("\\"))) 393 assert.False(t, warnIfInvalid([]byte("\""))) 394 assert.True(t, warnIfInvalid([]byte(""))) 395 for i := 0; i < 5; i++ { 396 validCookie := getValidCookie() 397 assert.True(t, warnIfInvalid(validCookie)) 398 } 399 } 400 401 func getValidCookie() []byte { 402 var validCookie []byte 403 for i := 0; i < 100; i++ { 404 r := rand.Intn(0x78-0x20) + 0x20 405 if r == ';' || r == '\\' || r == '"' { 406 continue 407 } 408 validCookie = append(validCookie, byte(r)) 409 } 410 return validCookie 411 }