2019-04-24 07:38:52 -04:00
|
|
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
2019-04-27 19:07:11 -04:00
|
|
|
// Structured similarly to Go's cookie.go
|
|
|
|
// https://github.com/golang/go/blob/master/src/net/http/cookie.go
|
|
|
|
import { ServerRequest, Response } from "./server.ts";
|
|
|
|
import { assert } from "../testing/asserts.ts";
|
|
|
|
import { toIMF } from "../datetime/mod.ts";
|
2019-04-24 07:38:52 -04:00
|
|
|
|
2019-04-27 19:07:11 -04:00
|
|
|
export interface Cookies {
|
2019-04-24 07:38:52 -04:00
|
|
|
[key: string]: string;
|
|
|
|
}
|
|
|
|
|
2019-04-27 19:07:11 -04:00
|
|
|
export interface Cookie {
|
|
|
|
name: string;
|
|
|
|
value: string;
|
|
|
|
expires?: Date;
|
|
|
|
maxAge?: number;
|
|
|
|
domain?: string;
|
|
|
|
path?: string;
|
|
|
|
secure?: boolean;
|
|
|
|
httpOnly?: boolean;
|
|
|
|
sameSite?: SameSite;
|
|
|
|
unparsed?: string[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export type SameSite = "Strict" | "Lax";
|
|
|
|
|
|
|
|
function toString(cookie: Cookie): string {
|
|
|
|
const out: string[] = [];
|
|
|
|
out.push(`${cookie.name}=${cookie.value}`);
|
|
|
|
|
|
|
|
// Fallback for invalid Set-Cookie
|
|
|
|
// ref: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
|
|
|
|
if (cookie.name.startsWith("__Secure")) {
|
|
|
|
cookie.secure = true;
|
|
|
|
}
|
|
|
|
if (cookie.name.startsWith("__Host")) {
|
|
|
|
cookie.path = "/";
|
|
|
|
cookie.secure = true;
|
|
|
|
delete cookie.domain;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cookie.secure) {
|
|
|
|
out.push("Secure");
|
|
|
|
}
|
|
|
|
if (cookie.httpOnly) {
|
|
|
|
out.push("HttpOnly");
|
|
|
|
}
|
2019-05-30 08:59:30 -04:00
|
|
|
if (Number.isInteger(cookie.maxAge!)) {
|
|
|
|
assert(cookie.maxAge! > 0, "Max-Age must be an integer superior to 0");
|
2019-04-27 19:07:11 -04:00
|
|
|
out.push(`Max-Age=${cookie.maxAge}`);
|
|
|
|
}
|
|
|
|
if (cookie.domain) {
|
|
|
|
out.push(`Domain=${cookie.domain}`);
|
|
|
|
}
|
|
|
|
if (cookie.sameSite) {
|
|
|
|
out.push(`SameSite=${cookie.sameSite}`);
|
|
|
|
}
|
|
|
|
if (cookie.path) {
|
|
|
|
out.push(`Path=${cookie.path}`);
|
|
|
|
}
|
|
|
|
if (cookie.expires) {
|
|
|
|
let dateString = toIMF(cookie.expires);
|
|
|
|
out.push(`Expires=${dateString}`);
|
|
|
|
}
|
|
|
|
if (cookie.unparsed) {
|
|
|
|
out.push(cookie.unparsed.join("; "));
|
|
|
|
}
|
|
|
|
return out.join("; ");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse the cookies of the Server Request
|
|
|
|
* @param req Server Request
|
|
|
|
*/
|
|
|
|
export function getCookies(req: ServerRequest): Cookies {
|
|
|
|
if (req.headers.has("Cookie")) {
|
|
|
|
const out: Cookies = {};
|
2019-05-30 08:59:30 -04:00
|
|
|
const c = req.headers.get("Cookie")!.split(";");
|
2019-04-24 07:38:52 -04:00
|
|
|
for (const kv of c) {
|
|
|
|
const cookieVal = kv.split("=");
|
2019-05-30 08:59:30 -04:00
|
|
|
const key = cookieVal.shift()!.trim();
|
2019-04-29 10:49:50 -04:00
|
|
|
out[key] = cookieVal.join("=");
|
2019-04-24 07:38:52 -04:00
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
2019-04-27 19:07:11 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the cookie header properly in the Response
|
|
|
|
* @param res Server Response
|
|
|
|
* @param cookie Cookie to set
|
|
|
|
* @param [cookie.name] Name of the cookie
|
|
|
|
* @param [cookie.value] Value of the cookie
|
|
|
|
* @param [cookie.expires] Expiration Date of the cookie
|
|
|
|
* @param [cookie.maxAge] Max-Age of the Cookie. Must be integer superior to 0
|
|
|
|
* @param [cookie.domain] Specifies those hosts to which the cookie will be sent
|
|
|
|
* @param [cookie.path] Indicates a URL path that must exist in the request.
|
|
|
|
* @param [cookie.secure] Indicates if the cookie is made using SSL & HTTPS.
|
|
|
|
* @param [cookie.httpOnly] Indicates that cookie is not accessible via Javascript
|
|
|
|
* @param [cookie.sameSite] Allows servers to assert that a cookie ought not to be
|
|
|
|
* sent along with cross-site requests
|
|
|
|
* Example:
|
|
|
|
*
|
|
|
|
* setCookie(response, { name: 'deno', value: 'runtime',
|
|
|
|
* httpOnly: true, secure: true, maxAge: 2, domain: "deno.land" });
|
|
|
|
*/
|
|
|
|
export function setCookie(res: Response, cookie: Cookie): void {
|
|
|
|
if (!res.headers) {
|
|
|
|
res.headers = new Headers();
|
|
|
|
}
|
|
|
|
// TODO (zekth) : Add proper parsing of Set-Cookie headers
|
|
|
|
// Parsing cookie headers to make consistent set-cookie header
|
|
|
|
// ref: https://tools.ietf.org/html/rfc6265#section-4.1.1
|
|
|
|
res.headers.set("Set-Cookie", toString(cookie));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the cookie header properly in the Response to delete it
|
|
|
|
* @param res Server Response
|
|
|
|
* @param name Name of the cookie to Delete
|
|
|
|
* Example:
|
|
|
|
*
|
|
|
|
* delCookie(res,'foo');
|
|
|
|
*/
|
|
|
|
export function delCookie(res: Response, name: string): void {
|
|
|
|
setCookie(res, {
|
|
|
|
name: name,
|
|
|
|
value: "",
|
|
|
|
expires: new Date(0)
|
|
|
|
});
|
|
|
|
}
|