mirror of
https://github.com/denoland/deno.git
synced 2024-12-30 11:16:38 -05:00
parent
8d49022ef6
commit
ce101a0f86
5 changed files with 386 additions and 14 deletions
|
@ -1,4 +1,6 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
import { pad } from "../strings/pad.ts";
|
||||
|
||||
export type DateFormat = "mm-dd-yyyy" | "dd-mm-yyyy" | "yyyy-mm-dd";
|
||||
|
||||
/**
|
||||
|
@ -105,3 +107,40 @@ export function dayOfYear(date: Date): number {
|
|||
export function currentDayOfYear(): number {
|
||||
return dayOfYear(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a date to return a IMF formated string date
|
||||
* RFC: https://tools.ietf.org/html/rfc7231#section-7.1.1.1
|
||||
* IMF is the time format to use when generating times in HTTP
|
||||
* headers. The time being formatted must be in UTC for Format to
|
||||
* generate the correct format.
|
||||
* @param date Date to parse
|
||||
* @return IMF date formated string
|
||||
*/
|
||||
export function toIMF(date: Date): string {
|
||||
function dtPad(v: string, lPad: number = 2): string {
|
||||
return pad(v, lPad, { char: "0" });
|
||||
}
|
||||
const d = dtPad(date.getUTCDate().toString());
|
||||
const h = dtPad(date.getUTCHours().toString());
|
||||
const min = dtPad(date.getUTCMinutes().toString());
|
||||
const s = dtPad(date.getUTCSeconds().toString());
|
||||
const y = date.getUTCFullYear();
|
||||
const days = ["Sun", "Mon", "Tue", "Wed", "Thus", "Fri", "Sat"];
|
||||
const months = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec"
|
||||
];
|
||||
return `${days[date.getDay()]}, ${d} ${
|
||||
months[date.getUTCMonth()]
|
||||
} ${y} ${h}:${min}:${s} GMT`;
|
||||
}
|
||||
|
|
|
@ -74,3 +74,12 @@ test(function DayOfYear(): void {
|
|||
test(function currentDayOfYear(): void {
|
||||
assertEquals(datetime.currentDayOfYear(), datetime.dayOfYear(new Date()));
|
||||
});
|
||||
|
||||
test({
|
||||
name: "[DateTime] to IMF",
|
||||
fn(): void {
|
||||
const actual = datetime.toIMF(new Date(Date.UTC(1994, 3, 5, 15, 32)));
|
||||
const expected = "Tue, 05 May 1994 15:32:00 GMT";
|
||||
assertEquals(actual, expected);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -2,6 +2,45 @@
|
|||
|
||||
A framework for creating HTTP/HTTPS server.
|
||||
|
||||
## Cookie
|
||||
|
||||
Helper to manipulate `Cookie` throught `ServerRequest` and `Response`.
|
||||
|
||||
```ts
|
||||
import { getCookies } from "https://deno.land/std/http/cookie.ts";
|
||||
|
||||
let req = new ServerRequest();
|
||||
req.headers = new Headers();
|
||||
req.headers.set("Cookie", "full=of; tasty=chocolate");
|
||||
|
||||
const c = getCookies(request);
|
||||
// c = { full: "of", tasty: "chocolate" }
|
||||
```
|
||||
|
||||
To set a `Cookie` you can add `CookieOptions` to properly set your `Cookie`
|
||||
|
||||
```ts
|
||||
import { setCookie } from "https://deno.land/std/http/cookie.ts";
|
||||
|
||||
let res: Response = {};
|
||||
res.headers = new Headers();
|
||||
setCookie(res, { name: "Space", value: "Cat" });
|
||||
```
|
||||
|
||||
Deleting a `Cookie` will set its expiration date before now.
|
||||
Forcing the browser to delete it.
|
||||
|
||||
```ts
|
||||
import { delCookie } from "https://deno.land/std/http/cookie.ts";
|
||||
|
||||
let res = new Response();
|
||||
delCookie(res, "deno");
|
||||
// Will append this header in the response
|
||||
// "Set-Cookie: deno=; Expires=Thus, 01 Jan 1970 00:00:00 GMT"
|
||||
```
|
||||
|
||||
**Note**: At the moment multiple `Set-Cookie` in a `Response` is not handled.
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
|
|
125
http/cookie.ts
125
http/cookie.ts
|
@ -1,15 +1,81 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
import { ServerRequest } from "./server.ts";
|
||||
// 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";
|
||||
|
||||
export interface Cookie {
|
||||
export interface Cookies {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
/* Parse the cookie of the Server Request */
|
||||
export function getCookie(rq: ServerRequest): Cookie {
|
||||
if (rq.headers.has("Cookie")) {
|
||||
const out: Cookie = {};
|
||||
const c = rq.headers.get("Cookie").split(";");
|
||||
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");
|
||||
}
|
||||
if (Number.isInteger(cookie.maxAge)) {
|
||||
assert(cookie.maxAge > 0, "Max-Age must be an integer superior to 0");
|
||||
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 = {};
|
||||
const c = req.headers.get("Cookie").split(";");
|
||||
for (const kv of c) {
|
||||
const cookieVal = kv.split("=");
|
||||
const key = cookieVal.shift().trim();
|
||||
|
@ -19,3 +85,48 @@ export function getCookie(rq: ServerRequest): Cookie {
|
|||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
import { ServerRequest } from "./server.ts";
|
||||
import { getCookie } from "./cookie.ts";
|
||||
import { assertEquals } from "../testing/asserts.ts";
|
||||
import { ServerRequest, Response } from "./server.ts";
|
||||
import { getCookies, delCookie, setCookie } from "./cookie.ts";
|
||||
import { assert, assertEquals } from "../testing/asserts.ts";
|
||||
import { test } from "../testing/mod.ts";
|
||||
|
||||
test({
|
||||
|
@ -9,17 +9,191 @@ test({
|
|||
fn(): void {
|
||||
let req = new ServerRequest();
|
||||
req.headers = new Headers();
|
||||
assertEquals(getCookie(req), {});
|
||||
assertEquals(getCookies(req), {});
|
||||
req.headers = new Headers();
|
||||
req.headers.set("Cookie", "foo=bar");
|
||||
assertEquals(getCookie(req), { foo: "bar" });
|
||||
assertEquals(getCookies(req), { foo: "bar" });
|
||||
|
||||
req.headers = new Headers();
|
||||
req.headers.set("Cookie", "full=of ; tasty=chocolate");
|
||||
assertEquals(getCookie(req), { full: "of ", tasty: "chocolate" });
|
||||
assertEquals(getCookies(req), { full: "of ", tasty: "chocolate" });
|
||||
|
||||
req.headers = new Headers();
|
||||
req.headers.set("Cookie", "igot=99; problems=but...");
|
||||
assertEquals(getCookie(req), { igot: "99", problems: "but..." });
|
||||
assertEquals(getCookies(req), { igot: "99", problems: "but..." });
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "[HTTP] Cookie Delete",
|
||||
fn(): void {
|
||||
let res: Response = {};
|
||||
delCookie(res, "deno");
|
||||
assertEquals(
|
||||
res.headers.get("Set-Cookie"),
|
||||
"deno=; Expires=Thus, 01 Jan 1970 00:00:00 GMT"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "[HTTP] Cookie Set",
|
||||
fn(): void {
|
||||
let res: Response = {};
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, { name: "Space", value: "Cat" });
|
||||
assertEquals(res.headers.get("Set-Cookie"), "Space=Cat");
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, { name: "Space", value: "Cat", secure: true });
|
||||
assertEquals(res.headers.get("Set-Cookie"), "Space=Cat; Secure");
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, { name: "Space", value: "Cat", httpOnly: true });
|
||||
assertEquals(res.headers.get("Set-Cookie"), "Space=Cat; HttpOnly");
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, {
|
||||
name: "Space",
|
||||
value: "Cat",
|
||||
httpOnly: true,
|
||||
secure: true
|
||||
});
|
||||
assertEquals(res.headers.get("Set-Cookie"), "Space=Cat; Secure; HttpOnly");
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, {
|
||||
name: "Space",
|
||||
value: "Cat",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
maxAge: 2
|
||||
});
|
||||
assertEquals(
|
||||
res.headers.get("Set-Cookie"),
|
||||
"Space=Cat; Secure; HttpOnly; Max-Age=2"
|
||||
);
|
||||
|
||||
let error = false;
|
||||
res.headers = new Headers();
|
||||
try {
|
||||
setCookie(res, {
|
||||
name: "Space",
|
||||
value: "Cat",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
maxAge: 0
|
||||
});
|
||||
} catch (e) {
|
||||
error = true;
|
||||
}
|
||||
assert(error);
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, {
|
||||
name: "Space",
|
||||
value: "Cat",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
maxAge: 2,
|
||||
domain: "deno.land"
|
||||
});
|
||||
assertEquals(
|
||||
res.headers.get("Set-Cookie"),
|
||||
"Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land"
|
||||
);
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, {
|
||||
name: "Space",
|
||||
value: "Cat",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
maxAge: 2,
|
||||
domain: "deno.land",
|
||||
sameSite: "Strict"
|
||||
});
|
||||
assertEquals(
|
||||
res.headers.get("Set-Cookie"),
|
||||
"Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land; SameSite=Strict"
|
||||
);
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, {
|
||||
name: "Space",
|
||||
value: "Cat",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
maxAge: 2,
|
||||
domain: "deno.land",
|
||||
sameSite: "Lax"
|
||||
});
|
||||
assertEquals(
|
||||
res.headers.get("Set-Cookie"),
|
||||
"Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land; SameSite=Lax"
|
||||
);
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, {
|
||||
name: "Space",
|
||||
value: "Cat",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
maxAge: 2,
|
||||
domain: "deno.land",
|
||||
path: "/"
|
||||
});
|
||||
assertEquals(
|
||||
res.headers.get("Set-Cookie"),
|
||||
"Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land; Path=/"
|
||||
);
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, {
|
||||
name: "Space",
|
||||
value: "Cat",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
maxAge: 2,
|
||||
domain: "deno.land",
|
||||
path: "/",
|
||||
unparsed: ["unparsed=keyvalue", "batman=Bruce"]
|
||||
});
|
||||
assertEquals(
|
||||
res.headers.get("Set-Cookie"),
|
||||
"Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land; Path=/; unparsed=keyvalue; batman=Bruce"
|
||||
);
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, {
|
||||
name: "Space",
|
||||
value: "Cat",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
maxAge: 2,
|
||||
domain: "deno.land",
|
||||
path: "/",
|
||||
expires: new Date(Date.UTC(1983, 0, 7, 15, 32))
|
||||
});
|
||||
assertEquals(
|
||||
res.headers.get("Set-Cookie"),
|
||||
"Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land; Path=/; Expires=Fri, 07 Jan 1983 15:32:00 GMT"
|
||||
);
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, { name: "__Secure-Kitty", value: "Meow" });
|
||||
assertEquals(res.headers.get("Set-Cookie"), "__Secure-Kitty=Meow; Secure");
|
||||
|
||||
res.headers = new Headers();
|
||||
setCookie(res, {
|
||||
name: "__Host-Kitty",
|
||||
value: "Meow",
|
||||
domain: "deno.land"
|
||||
});
|
||||
assertEquals(
|
||||
res.headers.get("Set-Cookie"),
|
||||
"__Host-Kitty=Meow; Secure; Path=/"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue