tests/testthat/test-cookies.R

test_that("Parsing cookies works", {
  # Note that the Expires field is ignored
  cookie <- parseCookie("mycookie=myvalue; Path=/; Expires=Sat, 24 Jun 2017 16:16:05 GMT; Max-Age=3600; HttpOnly", "/")
  expect_equal(cookie$name, "mycookie")
  expect_equal(cookie$value, "myvalue")
  expect_true(cookie$expires > Sys.time() + 3595)
  expect_true(cookie$expires < Sys.time() + 3605)
  expect_equal(cookie$path, "/")
  expect_false(cookie$secure)

  # Max-Age with no semicolon, non-root path
  cookie <- parseCookie("mycookie2=myvalue2; Secure; Path=/test; max-AGE=3600")
  expect_equal(cookie$name, "mycookie2")
  expect_equal(cookie$value, "myvalue2")
  expect_true(cookie$expires > Sys.time() + 3595)
  expect_true(cookie$expires < Sys.time() + 3605)
  expect_equal(cookie$path, "/test")
  expect_true(cookie$secure)

  # Path with no semicolon, no max age
  cookie <- parseCookie("mycookie2=myvalue2; PATH=/test")
  expect_equal(cookie$name, "mycookie2")
  expect_equal(cookie$value, "myvalue2")
  expect_true(cookie$expires > (Sys.time() + 10^9))
  expect_equal(cookie$path, "/test")
  expect_false(cookie$secure)

  # No path, value with no semicolon
  cookie <- parseCookie("mycookie2=myvalue2")
  expect_equal(cookie$name, "mycookie2")
  expect_equal(cookie$value, "myvalue2")
  expect_true(cookie$expires > (Sys.time() + 10^9))
  expect_equal(cookie$path, "/")
  expect_false(cookie$secure)

  # Trailing secure
  cookie <- parseCookie("mycookie2=myvalue2; Secure")
  expect_equal(cookie$name, "mycookie2")
  expect_equal(cookie$value, "myvalue2")
  expect_true(cookie$expires > (Sys.time() + 10^9))
  expect_equal(cookie$path, "/")
  expect_true(cookie$secure)

  # Full cookie with spaces around =s
  cookie <- parseCookie("mycookie = myvalue; SEcure; Path = /; Expires = Sat, 24 Jun 2017 16:16:05 GMT; Max-Age = 3600; HttpOnly")
  expect_equal(cookie$name, "mycookie")
  expect_equal(cookie$value, "myvalue")
  expect_true(cookie$secure)
  expect_true(cookie$expires > Sys.time() + 3595)
  expect_true(cookie$expires < Sys.time() + 3605)
  expect_equal(cookie$path, "/")

  # Value-less cookie
  cookie <- parseCookie("mycookie=; Path = /")
  expect_equal(cookie$name, "mycookie")
  expect_equal(cookie$value, "")
  expect_equal(cookie$path, "/")

  # prove #229 is fixed
  cookie <- parseCookie("my_cookie-which/uses%20strange?characters=foo_-%20+?1234bar; Path = /")
  expect_equal(cookie$name, "my_cookie-which/uses%20strange?characters")
  expect_equal(cookie$value, "foo_-%20+?1234bar")
  expect_equal(cookie$path, "/")
})

test_that("Invalid cookies fail parsing", {
  # Invalid path, doesn't match request's path
  expect_snapshot(cookie <- parseCookie("x=1; Path=/something/else", "/path"))
  expect_null(cookie)

  # Invalid key/val format
  expect_snapshot(cookie <- parseCookie("mycookie;"))
  expect_null(cookie)
})

test_that("cookies can be stored", {
  local_cookie_store()

  parsedUrl <- parseHttpUrl("http://fakedomain:123/test/stuff")
  expect_warning({
    storeCookies(parsedUrl, c(
      "mycookie=myvalue; Path=/; Max-Age=3600; HttpOnly",
      "anotherCookie=what; Path=/test; Max-Age=100",
      "wrongpath=huh; Path=/uhoh; Max-Age=100",
      "secureCookie=123; Secure",
      "third=cookie; Path=/; Max-Age=500")
    )
  }, "Invalid path set for cookie")
  cookies <- get("fakedomain:123", envir = .cookieStore)
  expect_equal(nrow(cookies), 4)

  # Check the first cookie
  co <- cookies[cookies$name == "mycookie", ]
  expect_equal(co$name, "mycookie")
  expect_equal(co$value, "myvalue")
  expect_true(co$expires > Sys.time() + 3595)
  expect_true(co$expires < Sys.time() + 3605)
  expect_equal(co$path, "/")
  expect_false(co$secure)

  # And the other
  co <- cookies[cookies$name == "anotherCookie", ]
  expect_equal(co$name, "anotherCookie")
  expect_equal(co$value, "what")
  expect_true(co$expires > Sys.time() + 95)
  expect_true(co$expires < Sys.time() + 105)
  expect_equal(co$path, "/test")
  expect_false(co$secure)

  # Third
  co <- cookies[cookies$name == "third", ]
  expect_equal(co$name, "third")
  expect_equal(co$value, "cookie")
  expect_true(co$expires > Sys.time() + 495)
  expect_true(co$expires < Sys.time() + 505)
  expect_equal(co$path, "/")
  expect_false(co$secure)

  # Fourth
  co <- cookies[cookies$name == "secureCookie", ]
  expect_equal(co$name, "secureCookie")
  expect_equal(co$value, "123")
  expect_true(co$secure)
})

test_that("duplicate cookies overwrite one another", {
  local_cookie_store()

  parsedUrl <- parseHttpUrl("http://fakedomain:123/test/stuff")
  storeCookies(parsedUrl, "mycookie=myvalue; Path=/; Max-Age=3600")
  cookies <- get("fakedomain:123", envir = .cookieStore)
  expect_equal(nrow(cookies), 1)

  # Add another valid cookie, same domain, same name, different path
  storeCookies(parsedUrl, "mycookie=myvalue; Path=/test; Max-Age=3600")
  cookies <- get("fakedomain:123", envir = .cookieStore)
  expect_equal(nrow(cookies), 2)

  # Duplicate cookie should overwrite
  storeCookies(parsedUrl, "mycookie=myvalue; Path=/test; Max-Age=99")
  cookies <- get("fakedomain:123", envir = .cookieStore)
  expect_equal(nrow(cookies), 2)

  # Confirm that the stored cookie is the more recent one.
  co <- cookies[cookies$path == "/test", ]
  expect_true(co$expires > Sys.time() + 94)
  expect_true(co$expires < Sys.time() + 104)
})

test_that("appending cookie headers works", {
  local_cookie_store()

  parsedUrl <- parseHttpUrl("http://fakedomain:123/test/stuff")

  # Nothing to append, no-op
  headers <- appendCookieHeaders(parsedUrl, c(header1 = 123, header2 = "abc"))
  expect_length(headers, 2)
  expect_equal(headers[["header1"]], "123")
  expect_equal(headers[["header2"]], "abc")

  # Store a cookie
  storeCookies(parsedUrl, "cookie1=value1; Path=/; Max-Age=3600")

  headers <- appendCookieHeaders(parsedUrl, c(header1 = 123, header2 = "abc"))
  expect_length(headers, 3)
  expect_equal(headers[["header1"]], "123")
  expect_equal(headers[["header2"]], "abc")
  expect_equal(headers[["cookie"]], "cookie1=value1")

  # Store a couple more cookies
  storeCookies(parsedUrl, "cookie2=value2; Path=/test; Max-Age=3600")
  # This one has the wrong path, will be filtered out
  storeCookies(parseHttpUrl("http://fakedomain:123/another"), "cookie3=value3; Path=/another; Max-Age=3600")

  headers <- appendCookieHeaders(parsedUrl, c(header1 = 123, header2 = "abc"))
  expect_length(headers, 3)
  expect_equal(headers[["header1"]], "123")
  expect_equal(headers[["header2"]], "abc")
  expect_equal(headers[["cookie"]], "cookie2=value2; cookie1=value1")

  # If you already have a cookie header, you end up with two
  headers <- appendCookieHeaders(parsedUrl, c(cookie = "existing=value"))
  expect_length(headers, 2)
  expect_equal(headers[1], c(cookie = "existing=value"))
  expect_equal(headers[2], c(cookie = "cookie2=value2; cookie1=value1"))

  # Add a secure cookie
  storeCookies(parsedUrl, "securecookie=secureval; Path=/; Max-Age=3600; Secure")
  headers <- appendCookieHeaders(parsedUrl, c())
  expect_equal(headers[["cookie"]], "cookie2=value2; cookie1=value1")

  # But over a secure channel, you'd include the secure cookie
  headers <- appendCookieHeaders(parseHttpUrl("https://fakedomain:123/test/stuff"), c())
  expect_equal(headers[["cookie"]], "securecookie=secureval; cookie2=value2; cookie1=value1")
})

test_that("Expired cookies are removed", {
  local_cookie_store()

  parsedUrl <- parseHttpUrl("http://fakedomain:123/test/stuff")

  # Expired cookies are removed from the store and not included in the request
  storeCookies(parsedUrl, "expired=something; Max-Age=-1")

  cookies <- get("fakedomain:123", envir = .cookieStore)
  expect_equal(nrow(cookies), 1)

  # Now it will recognize that it's expired and remove it
  headers <- appendCookieHeaders(parsedUrl, NULL)
  expect_null(headers)

  cookies <- get("fakedomain:123", envir = .cookieStore)
  expect_equal(nrow(cookies), 0)

  # And with multiple cookies, it still removes only the expired one
  storeCookies(parsedUrl, "expired=something; Max-Age=-1")
  storeCookies(parsedUrl, "notexpired=something")

  cookies <- get("fakedomain:123", envir = .cookieStore)
  expect_equal(nrow(cookies), 2)

  # Now it will recognize that it's expired and remove it
  headers <- appendCookieHeaders(parsedUrl, c())
  expect_length(headers, 1)

  cookies <- get("fakedomain:123", envir = .cookieStore)
  expect_equal(nrow(cookies), 1)
})

test_that("getCookieHost works", {
  expect_equal(getCookieHost(parseHttpUrl("http://rstudio.com")), "rstudio.com")
  expect_equal(getCookieHost(parseHttpUrl("http://rstudio.com:3939")), "rstudio.com:3939")
  expect_equal(getCookieHost(parseHttpUrl("http://127.0.0.1")), "127.0.0.1")
  expect_equal(getCookieHost(parseHttpUrl("http://127.0.0.1:3939")), "127.0.0.1:3939")
})

test_that("getting and clearing cookies works", {
  local_cookie_store()

  all <- getCookies()
  expect_null(all)

  # Add a few cookies
  domain1 <- parseHttpUrl("http://domain1/test/stuff")
  storeCookies(domain1, "c1=v1")
  storeCookies(domain1, "c2=v2")

  domain2 <- parseHttpUrl("http://domain2:3939/test/stuff")
  storeCookies(domain2, "c3=v3")

  d1c <- getCookies("domain1")
  expect_equal(nrow(d1c), 2)
  expect_equal(d1c$host[1], "domain1")

  d2c <- getCookies("domain2")
  expect_null(d2c)
  d2c <- getCookies("domain2", 3939)
  expect_equal(nrow(d2c), 1)
  expect_equal(d2c$host[1], "domain2:3939")

  all <- getCookies()
  expect_equal(nrow(all), 3)

  # Delete cookies from one domain
  clearCookies("domain2", 3939)

  d2c <- getCookies("domain2", 3939)
  expect_null(d2c)

  all <- getCookies()
  expect_equal(nrow(all), 2)

  # Clear all cookies
  clearCookies()

  d1c <- getCookies("domain1")
  expect_null(d1c)

  all <- getCookies()
  expect_null(all)
})
rstudio/rsconnect documentation built on April 30, 2024, 2:14 p.m.