//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 17 Mar 08 Brian Frank Creation
//
**
** Cookie models an HTTP cookie used to pass data between the server
** and brower as defined by the original Netscape cookie specification
** and RFC 2109. Note the newer RFC 2965 is unsupported by most browsers,
** and even 2109 isn't really supported by some of the major browsers.
** See `WebReq.cookies` and `WebRes.cookies`.
**
@Js
const class Cookie
{
**
** Parse a HTTP cookie header name/value pair.
** Throw ParseErr if not formatted correctly.
**
static Cookie fromStr(Str s)
{
eq := s.index("=")
if (eq == null) throw ParseErr(s)
name := s[0..<eq].trim
val := s[eq+1..-1].trim
if (val.size >= 2 && val[0] == '"' && val[-1] == '"')
val = WebUtil.fromQuotedStr(val)
return make(name, val)
}
**
** Construct with name and value. The name must be a valid
** HTTP token and must not start with "$" (see `WebUtil.isToken`).
** The value string must be an ASCII string within the inclusive
** range of 0x20 and 0x7e (see `WebUtil.toQuotedStr`) with the
** exception of the semicolon.
**
** Fantom cookies will use quoted string values, however some browsers
** such as IE won't parse a quoted string with semicolons correctly,
** so we make semicolons illegal. If you have a value which might
** include non-ASCII characters or semicolons, then consider encoding
** using something like Base64:
**
** // write response
** res.cookies.add(Cookie("baz", val.toBuf.toBase64))
**
** // read from request
** val := Buf.fromBase64(req.cookies.get("baz", "")).readAllStr
**
new make(Str name, Str val, |This|? f := null)
{
if (f != null) f(this)
this.name = name
this.val = val
// validate name
if (!WebUtil.isToken(this.name) || this.name[0] == '$')
throw ArgErr("Cookie name has illegal chars: $val")
// validate value
if (!this.val.all |Int c->Bool| { return 0x20 <= c && c <= 0x7e && c != ';'})
throw ArgErr("Cookie value has illegal chars: $val")
if (this.val.size + 32 >= WebUtil.maxTokenSize) // fudge room for quotes & escapes
throw ArgErr("Cookie value too big")
}
**
** Name of the cookie.
**
const Str name
**
** Value string of the cookie.
**
const Str val
**
** Defines the lifetime of the cookie, after the the max-age
** elapses the client should discard the cookie. The duration
** is floored to seconds (fractional seconds are truncated).
** If maxAge is null (the default) then the cookie persists
** until the client is shutdown. If zero is specified, the
** cookie is discarded immediately. Note that many browsers
** still don't recognize max-age, so setting max-age also
** always includes an expires attribute.
**
const Duration? maxAge
**
** Specifies the domain for which the cookie is valid.
** An explicit domain must always start with a dot. If
** null (the default) then the cookie only applies to
** the server which set it.
**
const Str? domain
**
** Specifies the subset of URLs to which the cookie applies.
** If set to "/" (the default), then the cookie applies to all
** paths. If the path is null, it as assumed to be the same
** path as the document being described by the header which
** contains the cookie.
**
const Str? path := "/"
**
** If true, then the client only sends this cookie using a
** secure protocol such as HTTPS. Defaults to false.
**
const Bool secure := false
**
** Return the cookie formatted as an HTTP header.
**
override Str toStr()
{
s := StrBuf(64)
s.add(name).add("=").add(WebUtil.toQuotedStr(val))
if (maxAge != null)
{
// we need to use Max-Age *and* Expires since many browsers
// such as Safari and IE still don't recognize max-age
s.add(";Max-Age=").add(maxAge.toSec)
if (maxAge.ticks <= 0)
s.add(";Expires=").add("Sat, 01 Jan 2000 00:00:00 GMT")
else
s.add(";Expires=").add((DateTime.nowUtc+maxAge).toHttpStr)
}
if (domain != null) s.add(";Domain=").add(domain)
if (path != null) s.add(";Path=").add(path)
if (secure) s.add(";Secure")
return s.toStr
}
}