twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

index.js (3969B)


      1 /*!
      2  * cookie
      3  * Copyright(c) 2012-2014 Roman Shtylman
      4  * Copyright(c) 2015 Douglas Christopher Wilson
      5  * MIT Licensed
      6  */
      7 
      8 'use strict';
      9 
     10 /**
     11  * Module exports.
     12  * @public
     13  */
     14 
     15 exports.parse = parse;
     16 exports.serialize = serialize;
     17 
     18 /**
     19  * Module variables.
     20  * @private
     21  */
     22 
     23 var decode = decodeURIComponent;
     24 var encode = encodeURIComponent;
     25 var pairSplitRegExp = /; */;
     26 
     27 /**
     28  * RegExp to match field-content in RFC 7230 sec 3.2
     29  *
     30  * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
     31  * field-vchar   = VCHAR / obs-text
     32  * obs-text      = %x80-FF
     33  */
     34 
     35 var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
     36 
     37 /**
     38  * Parse a cookie header.
     39  *
     40  * Parse the given cookie header string into an object
     41  * The object has the various cookies as keys(names) => values
     42  *
     43  * @param {string} str
     44  * @param {object} [options]
     45  * @return {object}
     46  * @public
     47  */
     48 
     49 function parse(str, options) {
     50   if (typeof str !== 'string') {
     51     throw new TypeError('argument str must be a string');
     52   }
     53 
     54   var obj = {}
     55   var opt = options || {};
     56   var pairs = str.split(pairSplitRegExp);
     57   var dec = opt.decode || decode;
     58 
     59   for (var i = 0; i < pairs.length; i++) {
     60     var pair = pairs[i];
     61     var eq_idx = pair.indexOf('=');
     62 
     63     // skip things that don't look like key=value
     64     if (eq_idx < 0) {
     65       continue;
     66     }
     67 
     68     var key = pair.substr(0, eq_idx).trim()
     69     var val = pair.substr(++eq_idx, pair.length).trim();
     70 
     71     // quoted values
     72     if ('"' == val[0]) {
     73       val = val.slice(1, -1);
     74     }
     75 
     76     // only assign once
     77     if (undefined == obj[key]) {
     78       obj[key] = tryDecode(val, dec);
     79     }
     80   }
     81 
     82   return obj;
     83 }
     84 
     85 /**
     86  * Serialize data into a cookie header.
     87  *
     88  * Serialize the a name value pair into a cookie string suitable for
     89  * http headers. An optional options object specified cookie parameters.
     90  *
     91  * serialize('foo', 'bar', { httpOnly: true })
     92  *   => "foo=bar; httpOnly"
     93  *
     94  * @param {string} name
     95  * @param {string} val
     96  * @param {object} [options]
     97  * @return {string}
     98  * @public
     99  */
    100 
    101 function serialize(name, val, options) {
    102   var opt = options || {};
    103   var enc = opt.encode || encode;
    104 
    105   if (typeof enc !== 'function') {
    106     throw new TypeError('option encode is invalid');
    107   }
    108 
    109   if (!fieldContentRegExp.test(name)) {
    110     throw new TypeError('argument name is invalid');
    111   }
    112 
    113   var value = enc(val);
    114 
    115   if (value && !fieldContentRegExp.test(value)) {
    116     throw new TypeError('argument val is invalid');
    117   }
    118 
    119   var str = name + '=' + value;
    120 
    121   if (null != opt.maxAge) {
    122     var maxAge = opt.maxAge - 0;
    123     if (isNaN(maxAge)) throw new Error('maxAge should be a Number');
    124     str += '; Max-Age=' + Math.floor(maxAge);
    125   }
    126 
    127   if (opt.domain) {
    128     if (!fieldContentRegExp.test(opt.domain)) {
    129       throw new TypeError('option domain is invalid');
    130     }
    131 
    132     str += '; Domain=' + opt.domain;
    133   }
    134 
    135   if (opt.path) {
    136     if (!fieldContentRegExp.test(opt.path)) {
    137       throw new TypeError('option path is invalid');
    138     }
    139 
    140     str += '; Path=' + opt.path;
    141   }
    142 
    143   if (opt.expires) {
    144     if (typeof opt.expires.toUTCString !== 'function') {
    145       throw new TypeError('option expires is invalid');
    146     }
    147 
    148     str += '; Expires=' + opt.expires.toUTCString();
    149   }
    150 
    151   if (opt.httpOnly) {
    152     str += '; HttpOnly';
    153   }
    154 
    155   if (opt.secure) {
    156     str += '; Secure';
    157   }
    158 
    159   if (opt.sameSite) {
    160     var sameSite = typeof opt.sameSite === 'string'
    161       ? opt.sameSite.toLowerCase() : opt.sameSite;
    162 
    163     switch (sameSite) {
    164       case true:
    165         str += '; SameSite=Strict';
    166         break;
    167       case 'lax':
    168         str += '; SameSite=Lax';
    169         break;
    170       case 'strict':
    171         str += '; SameSite=Strict';
    172         break;
    173       default:
    174         throw new TypeError('option sameSite is invalid');
    175     }
    176   }
    177 
    178   return str;
    179 }
    180 
    181 /**
    182  * Try decoding a string using a decoding function.
    183  *
    184  * @param {string} str
    185  * @param {function} decode
    186  * @private
    187  */
    188 
    189 function tryDecode(str, decode) {
    190   try {
    191     return decode(str);
    192   } catch (e) {
    193     return str;
    194   }
    195 }