mirror of
https://github.com/denoland/deno.git
synced 2025-01-02 12:28:46 -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.
|
// 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";
|
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 {
|
export function currentDayOfYear(): number {
|
||||||
return dayOfYear(new Date());
|
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 {
|
test(function currentDayOfYear(): void {
|
||||||
assertEquals(datetime.currentDayOfYear(), datetime.dayOfYear(new Date()));
|
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.
|
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
|
## Example
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
|
125
http/cookie.ts
125
http/cookie.ts
|
@ -1,15 +1,81 @@
|
||||||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
// 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;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse the cookie of the Server Request */
|
export interface Cookie {
|
||||||
export function getCookie(rq: ServerRequest): Cookie {
|
name: string;
|
||||||
if (rq.headers.has("Cookie")) {
|
value: string;
|
||||||
const out: Cookie = {};
|
expires?: Date;
|
||||||
const c = rq.headers.get("Cookie").split(";");
|
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) {
|
for (const kv of c) {
|
||||||
const cookieVal = kv.split("=");
|
const cookieVal = kv.split("=");
|
||||||
const key = cookieVal.shift().trim();
|
const key = cookieVal.shift().trim();
|
||||||
|
@ -19,3 +85,48 @@ export function getCookie(rq: ServerRequest): Cookie {
|
||||||
}
|
}
|
||||||
return {};
|
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.
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
import { ServerRequest } from "./server.ts";
|
import { ServerRequest, Response } from "./server.ts";
|
||||||
import { getCookie } from "./cookie.ts";
|
import { getCookies, delCookie, setCookie } from "./cookie.ts";
|
||||||
import { assertEquals } from "../testing/asserts.ts";
|
import { assert, assertEquals } from "../testing/asserts.ts";
|
||||||
import { test } from "../testing/mod.ts";
|
import { test } from "../testing/mod.ts";
|
||||||
|
|
||||||
test({
|
test({
|
||||||
|
@ -9,17 +9,191 @@ test({
|
||||||
fn(): void {
|
fn(): void {
|
||||||
let req = new ServerRequest();
|
let req = new ServerRequest();
|
||||||
req.headers = new Headers();
|
req.headers = new Headers();
|
||||||
assertEquals(getCookie(req), {});
|
assertEquals(getCookies(req), {});
|
||||||
req.headers = new Headers();
|
req.headers = new Headers();
|
||||||
req.headers.set("Cookie", "foo=bar");
|
req.headers.set("Cookie", "foo=bar");
|
||||||
assertEquals(getCookie(req), { foo: "bar" });
|
assertEquals(getCookies(req), { foo: "bar" });
|
||||||
|
|
||||||
req.headers = new Headers();
|
req.headers = new Headers();
|
||||||
req.headers.set("Cookie", "full=of ; tasty=chocolate");
|
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 = new Headers();
|
||||||
req.headers.set("Cookie", "igot=99; problems=but...");
|
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