latte_web_cookie
Version:
172 lines (152 loc) • 4.56 kB
text/typescript
let decode = decodeURIComponent;
let encode = encodeURIComponent;
let pairSplitRegExp = /; */;
let fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
/**
*
* server => client
* Set-Cookie : {{name}}={{val}}; Path=/;
*
* client => server
* Cookie: {{name}}={{value}};
*/
class Cookie {
cookies: any;
sets: any;
constructor(cookies) {
this.cookies = cookies;
this.sets = {};
};
set(key, value, option) {
if(value == undefined) {
return delete this.sets[key];
}
this.sets[key] = {
value: value,
option: option
};
};
get(key) {
return this.cookies[key];
}
}
class CookieUtil {
options: any;
constructor(options) {
this.options = options || {};
};
parse(str) {
let obj = {};
let pairs = str.split(pairSplitRegExp);
let dec = this.options.decode || decode;
for(let i = 0; i < pairs.length; i++) {
let pair = pairs[i];
let eq_idx = pair.indexOf('=');
if (eq_idx < 0) {
continue;
}
let key = pair.substr(0, eq_idx).trim()
let val = pair.substr(++eq_idx, pair.length).trim();
if ('"' == val[0]) {
val = val.slice(1, -1);
}
// only assign once
if (undefined == obj[key]) {
try {
obj[key] = dec(val, dec);
}catch(err) {
obj[key] = val;
}
}
}
return obj
};
stringify(name:string, val:string, options:any) {
var opt = options || {};
var enc = opt.encode || encode;
if (typeof enc !== 'function') {
throw new TypeError('option encode is invalid');
}
if (!fieldContentRegExp.test(name)) {
throw new TypeError('argument name is invalid');
}
var value = enc(val);
if (value && !fieldContentRegExp.test(value)) {
throw new TypeError('argument val is invalid');
}
var str = name + '=' + value;
if (null != opt.maxAge) {
var maxAge = opt.maxAge - 0;
if (isNaN(maxAge)) throw new Error('maxAge should be a Number');
str += '; Max-Age=' + Math.floor(maxAge);
}
if (opt.domain) {
if (!fieldContentRegExp.test(opt.domain)) {
throw new TypeError('option domain is invalid');
}
str += '; Domain=' + opt.domain;
}
if (opt.path) {
if (!fieldContentRegExp.test(opt.path)) {
throw new TypeError('option path is invalid');
}
str += '; Path=' + opt.path;
}
if (opt.expires) {
if (typeof opt.expires.toUTCString !== 'function') {
throw new TypeError('option expires is invalid');
}
str += '; Expires=' + opt.expires.toUTCString();
}
if (opt.httpOnly) {
str += '; HttpOnly';
}
if (opt.secure) {
str += '; Secure';
}
if (opt.sameSite) {
var sameSite = typeof opt.sameSite === 'string'
? opt.sameSite.toLowerCase() : opt.sameSite;
switch (sameSite) {
case true:
str += '; SameSite=Strict';
break;
case 'lax':
str += '; SameSite=Lax';
break;
case 'strict':
str += '; SameSite=Strict';
break;
default:
throw new TypeError('option sameSite is invalid');
}
}
return str;
};
before() {
var self = this;
return function(ctx, next) {
if(ctx.cookie) {
return next();
}
let cookies = self.parse(ctx.req.headers.cookie || "");
ctx.cookie = new Cookie(cookies);
next();
}
};
after(ctx, next) {
let self = this;
return (ctx, next) => {
if(ctx.cookie) {
Object.keys(ctx.cookie.sets).forEach((c) => {
let v = ctx.cookie.sets[c];
ctx.res.setHeader('Set-Cookie', self.stringify(c, v.value, v.option));
});
}
next();
}
}
}
export function create(options) {
return new CookieUtil(options);
}