diff --git a/doc/UTIL_API.md b/doc/UTIL_API.md index f29d907..6737e3f 100644 --- a/doc/UTIL_API.md +++ b/doc/UTIL_API.md @@ -962,6 +962,20 @@ dateFormat('yyyy-mm-dd HH:MM:ss'); // -> 2016-11-19 19:00:04 dateFormat(new Date(), 'yyyy-mm-dd'); // -> 2016-11-19 ``` +## decodeUriComponent + +Better decodeURIComponent that does not throw if input is invalid. + +|Name |Type |Desc | +|------|------|----------------| +|str |string|String to decode| +|return|string|Decoded string | + +```javascript +decodeUriComponent('%%25%'); // -> '%%%' +decodeUriComponent('%E0%A4%A'); // -> '\xE0\xA4%A' +``` + ## defaults Fill in undefined properties in object with the first value present in the following list of defaults objects. @@ -2244,6 +2258,35 @@ type(function () {}); // -> 'function' type([]); // -> 'array' ``` +## ucs2 + +UCS-2 encoding and decoding. + +### encode + +Create a string using an array of code point values. + +|Name |Type |Desc | +|------|------|--------------------| +|arr |array |Array of code points| +|return|string|Encoded string | + +### decode + +Create an array of code point values using a string. + +|Name |Type |Desc | +|------|------|--------------------| +|str |string|Input string | +|return|array |Array of code points| + +```javascript +ucs2.encode([0x61, 0x62, 0x63]); // -> 'abc' +ucs2.decode('abc'); // -> [0x61, 0x62, 0x63] +'𝌆'.length; // -> 2 +ucs2.decode('𝌆').length; // -> 1 +``` + ## uniqId Generate a globally-unique id. @@ -2284,6 +2327,34 @@ Convert the first character of string to upper case. upperFirst('red'); // -> Red ``` +## utf8 + +UTF-8 encoding and decoding. + +### encode + +Turn any UTF-8 decoded string into UTF-8 encoded string. + +|Name |Type |Desc | +|------|------|----------------| +|str |string|String to encode| +|return|string|Encoded string | + +### decode + +|Name |Type |Desc | +|------------|-------|----------------------| +|str |string |String to decode | +|[safe=false]|boolean|Suppress error if true| +|return |string |Decoded string | + +Turn any UTF-8 encoded string into UTF-8 decoded string. + +```javascript +utf8.encode('\uD800\uDC00'); // -> '\xF0\x90\x80\x80' +utf8.decode('\xF0\x90\x80\x80'); // -> '\uD800\uDC00' +``` + ## values Create an array of the own enumerable property values of object. diff --git a/src/Resources/Resources.js b/src/Resources/Resources.js index 82ca33b..4a9d195 100644 --- a/src/Resources/Resources.js +++ b/src/Resources/Resources.js @@ -16,7 +16,8 @@ import { isErudaEl, toArr, concat, - rmCookie + rmCookie, + decodeUriComponent } from '../lib/util'; export default class Resources extends Tool @@ -170,13 +171,7 @@ export default class Resources extends Tool each(document.cookie.split(';'), function (val, t) { val = val.split('='); - try - { - t = decodeURIComponent(val[1]); - } catch(e) - { - t = val[1]; - } + t = decodeUriComponent(val[1]); cookieData.push({ key: trim(val[0]), val: t diff --git a/src/lib/util.js b/src/lib/util.js index 769daac..b82ae98 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -774,6 +774,313 @@ export var toStr = _.toStr = (function () return exports; })(); +/* ------------------------------ ucs2 ------------------------------ */ + +export var ucs2 = _.ucs2 = (function (exports) +{ + /* UCS-2 encoding and decoding. + * + * ### encode + * + * Create a string using an array of code point values. + * + * |Name |Type |Desc | + * |------|------|--------------------| + * |arr |array |Array of code points| + * |return|string|Encoded string | + * + * ### decode + * + * Create an array of code point values using a string. + * + * |Name |Type |Desc | + * |------|------|--------------------| + * |str |string|Input string | + * |return|array |Array of code points| + * + * ```javascript + * ucs2.encode([0x61, 0x62, 0x63]); // -> 'abc' + * ucs2.decode('abc'); // -> [0x61, 0x62, 0x63] + * '𝌆'.length; // -> 2 + * ucs2.decode('𝌆').length; // -> 1 + * ``` + */ + + /* module + * env: all + * test: all + */ + + // https://mathiasbynens.be/notes/javascript-encoding + exports = { + encode: function (arr) + { + return String.fromCodePoint.apply(String, arr); + }, + decode: function (str) + { + var ret = []; + + var i = 0, + len = str.length; + + while(i < len) + { + var c = str.charCodeAt(i++); + + // A high surrogate + if (c >= 0xD800 && c <= 0xDBFF && i < len) + { + var tail = str.charCodeAt(i++); + // nextC >= 0xDC00 && nextC <= 0xDFFF + if ((tail & 0xFC00) === 0xDC00) + { + // C = (H - 0xD800) * 0x400 + L - 0xDC00 + 0x10000 + ret.push(((c & 0x3FF) << 10) + (tail & 0x3FF) + 0x10000); + } else + { + ret.push(c); + i--; + } + } else + { + ret.push(c); + } + } + + return ret; + } + }; + + return exports; +})({}); + +/* ------------------------------ utf8 ------------------------------ */ + +export var utf8 = _.utf8 = (function (exports) +{ + /* UTF-8 encoding and decoding. + * + * ### encode + * + * Turn any UTF-8 decoded string into UTF-8 encoded string. + * + * |Name |Type |Desc | + * |------|------|----------------| + * |str |string|String to encode| + * |return|string|Encoded string | + * + * ### decode + * + * |Name |Type |Desc | + * |------------|-------|----------------------| + * |str |string |String to decode | + * |[safe=false]|boolean|Suppress error if true| + * |return |string |Decoded string | + * + * Turn any UTF-8 encoded string into UTF-8 decoded string. + * + * ```javascript + * utf8.encode('\uD800\uDC00'); // -> '\xF0\x90\x80\x80' + * utf8.decode('\xF0\x90\x80\x80'); // -> '\uD800\uDC00' + * ``` + */ + + /* module + * env: all + * test: all + */ + + /* dependencies + * ucs2 + */ + + // https://encoding.spec.whatwg.org/#utf-8 + exports = { + encode: function (str) + { + var codePoints = ucs2.decode(str); + + var byteArr = ''; + + for (var i = 0, len = codePoints.length; i < len; i++) + { + byteArr += encodeCodePoint(codePoints[i]); + } + + return byteArr; + }, + decode: function decode(str, safe) + { + byteArr = ucs2.decode(str); + byteIdx = 0; + byteCount = byteArr.length; + codePoint = 0; + bytesSeen = 0; + bytesNeeded = 0; + lowerBoundary = 0x80; + upperBoundary = 0xBF; + + var codePoints = []; + + var tmp; + + while((tmp = decodeCodePoint(safe)) !== false) + { + codePoints.push(tmp); + } + + return ucs2.encode(codePoints); + } + }; + + var fromCharCode = String.fromCharCode; + + function encodeCodePoint(codePoint) + { + // U+0000 to U+0080, ASCII code point + if ((codePoint & 0xFFFFFF80) === 0) + { + return fromCharCode(codePoint); + } + + var ret = '', + count, + offset; + + // U+0080 to U+07FF, inclusive + if ((codePoint & 0xFFFFF800) === 0) + { + count = 1; + offset = 0xC0; + } else if ((codePoint & 0xFFFF0000) === 0) + { + // U+0800 to U+FFFF, inclusive + count = 2; + offset = 0xE0; + } else if ((codePoint & 0xFFE00000) == 0) + { + // U+10000 to U+10FFFF, inclusive + count = 3; + offset = 0xF0; + } + + ret += fromCharCode((codePoint >> (6 * count)) + offset); + + while (count > 0) + { + var tmp = codePoint >> (6 * (count - 1)); + ret += fromCharCode(0x80 | tmp & 0x3F); + count--; + } + + return ret; + } + + var byteArr, + byteIdx, + byteCount, + codePoint, + bytesSeen, + bytesNeeded, + lowerBoundary, + upperBoundary; + + function decodeCodePoint(safe) + { + /* eslint-disable no-constant-condition */ + while (true) + { + if (byteIdx >= byteCount && bytesNeeded) + { + if (safe) return goBack(); + throw new Error('Invalid byte index'); + } + + if (byteIdx === byteCount) return false; + + var byte = byteArr[byteIdx]; + byteIdx++; + + if (!bytesNeeded) + { + // 0x00 to 0x7F + if ((byte & 0x80) === 0) + { + return byte; + } + // 0xC2 to 0xDF + if ((byte & 0xE0) === 0xC0) + { + bytesNeeded = 1; + codePoint = byte & 0x1F; + } else if ((byte & 0xF0) === 0xE0) + { + // 0xE0 to 0xEF + if (byte === 0xE0) lowerBoundary = 0xA0; + if (byte === 0xED) upperBoundary = 0x9F; + bytesNeeded = 2; + codePoint = byte & 0xF; + } else if ((byte & 0xF8) === 0xF0) + { + // 0xF0 to 0xF4 + if (byte === 0xF0) lowerBoundary = 0x90; + if (byte === 0xF4) upperBoundary = 0x8F; + bytesNeeded = 3; + codePoint = byte & 0x7; + } else + { + if (safe) return goBack(); + throw new Error('Invalid UTF-8 detected'); + } + + continue; + } + + if (byte < lowerBoundary || byte > upperBoundary) + { + if (safe) { + byteIdx--; + return goBack(); + } + throw new Error('Invalid continuation byte'); + } + + lowerBoundary = 0x80; + upperBoundary = 0xBF; + + codePoint = (codePoint << 6) | (byte & 0x3F); + + bytesSeen++; + + if (bytesSeen !== bytesNeeded) continue; + + var tmp = codePoint; + + codePoint = 0; + bytesNeeded = 0; + bytesSeen = 0; + + return tmp; + } + } + + function goBack() + { + var start = byteIdx - bytesSeen - 1; + byteIdx = start + 1; + codePoint = 0; + bytesNeeded = 0; + bytesSeen = 0; + lowerBoundary = 0x80; + upperBoundary = 0xBF; + + return byteArr[start]; + } + + return exports; +})({}); + /* ------------------------------ isBrowser ------------------------------ */ export var isBrowser = _.isBrowser = (function (exports) @@ -1696,127 +2003,6 @@ export var defaults = _.defaults = (function (exports) return exports; })({}); -/* ------------------------------ cookie ------------------------------ */ - -export var cookie = _.cookie = (function (exports) -{ - /* Simple api for handling browser cookies. - * - * ### get - * - * Get cookie value. - * - * |Name |Type |Desc | - * |------|------|--------------------------| - * |key |string|Cookie key | - * |return|string|Corresponding cookie value| - * - * ### set - * - * Set cookie value. - * - * |Name |Type |Desc | - * |---------|-------|--------------| - * |key |string |Cookie key | - * |val |string |Cookie value | - * |[options]|object |Cookie options| - * |return |exports|Module cookie | - * - * ### remove - * - * Remove cookie value. - * - * |Name |Type |Desc | - * |---------|-------|--------------| - * |key |string |Cookie key | - * |[options]|object |Cookie options| - * |return |exports|Module cookie | - * - * ```javascript - * cookie.set('a', '1', {path: '/'}); - * cookie.get('a'); // -> '1' - * cookie.remove('a'); - * ``` - */ - - /* module - * env: browser - * test: browser - */ - - /* dependencies - * defaults isNum isUndef - */ - - var defOpts = { path: '/' }; - - function setCookie(key, val, options) - { - if (!isUndef(val)) - { - options = options || {}; - options = defaults(options, defOpts); - - if (isNum(options.expires)) - { - var expires = new Date(); - expires.setMilliseconds(expires.getMilliseconds() + options.expires * 864e+5); - options.expires = expires; - } - - val = encodeURIComponent(val); - key = encodeURIComponent(key); - - document.cookie = [ - key, '=', val, - options.expires && '; expires=' + options.expires.toUTCString(), - options.path && '; path=' + options.path, - options.domain && '; domain=' + options.domain, - options.secure ? '; secure' : '' - ].join(''); - - return exports; - } - - var cookies = document.cookie ? document.cookie.split('; ') : [], - result = key ? undefined : {}; - - for (var i = 0, len = cookies.length; i < len; i++) - { - var c = cookies[i], - parts = c.split('='), - name = decodeURIComponent(parts.shift()); - - c = parts.join('='); - c = decodeURIComponent(c); - - if (key === name) - { - result = c; - break; - } - - if (!key) result[name] = c; - } - - return result; - } - - exports = { - get: setCookie, - set: setCookie, - remove: function (key, options) - { - options = options || {}; - options.expires = -1; - - return setCookie(key, '', options); - } - }; - - return exports; -})({}); - /* ------------------------------ extend ------------------------------ */ export var extend = _.extend = (function (exports) @@ -3227,6 +3413,195 @@ export var map = _.map = (function () return exports; })(); +/* ------------------------------ decodeUriComponent ------------------------------ */ + +export var decodeUriComponent = _.decodeUriComponent = (function () +{ + /* Better decodeURIComponent that does not throw if input is invalid. + * + * |Name |Type |Desc | + * |------|------|----------------| + * |str |string|String to decode| + * |return|string|Decoded string | + * + * ```javascript + * decodeUriComponent('%%25%'); // -> '%%%' + * decodeUriComponent('%E0%A4%A'); // -> '\xE0\xA4%A' + * ``` + */ + + /* module + * env: all + * test: all + */ + + /* dependencies + * each ucs2 map utf8 + */ + + function exports(str) + { + try + { + return decodeURIComponent(str); + } catch (e) + { + var replaceMap = {}; + + var matches = str.match(regMatcher); + + each(matches, function (match) + { + str = str.replace(match, decode(match)); + }); + + return str; + } + } + + function decode(str) + { + str = str.split('%').slice(1); + + var bytes = map(str, hexToInt); + + str = ucs2.encode(bytes); + str = utf8.decode(str, true); + + return str; + } + + function hexToInt(numStr) + { + return +('0x' + numStr); + } + + var regMatcher = /(%[a-f0-9]{2})+/gi; + + return exports; +})(); + +/* ------------------------------ cookie ------------------------------ */ + +export var cookie = _.cookie = (function (exports) +{ + /* Simple api for handling browser cookies. + * + * ### get + * + * Get cookie value. + * + * |Name |Type |Desc | + * |------|------|--------------------------| + * |key |string|Cookie key | + * |return|string|Corresponding cookie value| + * + * ### set + * + * Set cookie value. + * + * |Name |Type |Desc | + * |---------|-------|--------------| + * |key |string |Cookie key | + * |val |string |Cookie value | + * |[options]|object |Cookie options| + * |return |exports|Module cookie | + * + * ### remove + * + * Remove cookie value. + * + * |Name |Type |Desc | + * |---------|-------|--------------| + * |key |string |Cookie key | + * |[options]|object |Cookie options| + * |return |exports|Module cookie | + * + * ```javascript + * cookie.set('a', '1', {path: '/'}); + * cookie.get('a'); // -> '1' + * cookie.remove('a'); + * ``` + */ + + /* module + * env: browser + * test: browser + */ + + /* dependencies + * defaults isNum isUndef decodeUriComponent + */ + + var defOpts = { path: '/' }; + + function setCookie(key, val, options) + { + if (!isUndef(val)) + { + options = options || {}; + options = defaults(options, defOpts); + + if (isNum(options.expires)) + { + var expires = new Date(); + expires.setMilliseconds(expires.getMilliseconds() + options.expires * 864e+5); + options.expires = expires; + } + + val = encodeURIComponent(val); + key = encodeURIComponent(key); + + document.cookie = [ + key, '=', val, + options.expires && '; expires=' + options.expires.toUTCString(), + options.path && '; path=' + options.path, + options.domain && '; domain=' + options.domain, + options.secure ? '; secure' : '' + ].join(''); + + return exports; + } + + var cookies = document.cookie ? document.cookie.split('; ') : [], + result = key ? undefined : {}; + + for (var i = 0, len = cookies.length; i < len; i++) + { + var c = cookies[i], + parts = c.split('='), + name = decodeUriComponent(parts.shift()); + + c = parts.join('='); + c = decodeUriComponent(c); + + if (key === name) + { + result = c; + break; + } + + if (!key) result[name] = c; + } + + return result; + } + + exports = { + get: setCookie, + set: setCookie, + remove: function (key, options) + { + options = options || {}; + options.expires = -1; + + return setCookie(key, '', options); + } + }; + + return exports; +})({}); + /* ------------------------------ toArr ------------------------------ */ export var toArr = _.toArr = (function () diff --git a/test/util.js b/test/util.js index 5f0c454..f16b0fd 100644 --- a/test/util.js +++ b/test/util.js @@ -138,6 +138,313 @@ return exports; })(); + /* ------------------------------ ucs2 ------------------------------ */ + + var ucs2 = _.ucs2 = (function (exports) + { + /* UCS-2 encoding and decoding. + * + * ### encode + * + * Create a string using an array of code point values. + * + * |Name |Type |Desc | + * |------|------|--------------------| + * |arr |array |Array of code points| + * |return|string|Encoded string | + * + * ### decode + * + * Create an array of code point values using a string. + * + * |Name |Type |Desc | + * |------|------|--------------------| + * |str |string|Input string | + * |return|array |Array of code points| + * + * ```javascript + * ucs2.encode([0x61, 0x62, 0x63]); // -> 'abc' + * ucs2.decode('abc'); // -> [0x61, 0x62, 0x63] + * '𝌆'.length; // -> 2 + * ucs2.decode('𝌆').length; // -> 1 + * ``` + */ + + /* module + * env: all + * test: all + */ + + // https://mathiasbynens.be/notes/javascript-encoding + exports = { + encode: function (arr) + { + return String.fromCodePoint.apply(String, arr); + }, + decode: function (str) + { + var ret = []; + + var i = 0, + len = str.length; + + while(i < len) + { + var c = str.charCodeAt(i++); + + // A high surrogate + if (c >= 0xD800 && c <= 0xDBFF && i < len) + { + var tail = str.charCodeAt(i++); + // nextC >= 0xDC00 && nextC <= 0xDFFF + if ((tail & 0xFC00) === 0xDC00) + { + // C = (H - 0xD800) * 0x400 + L - 0xDC00 + 0x10000 + ret.push(((c & 0x3FF) << 10) + (tail & 0x3FF) + 0x10000); + } else + { + ret.push(c); + i--; + } + } else + { + ret.push(c); + } + } + + return ret; + } + }; + + return exports; + })({}); + + /* ------------------------------ utf8 ------------------------------ */ + + var utf8 = _.utf8 = (function (exports) + { + /* UTF-8 encoding and decoding. + * + * ### encode + * + * Turn any UTF-8 decoded string into UTF-8 encoded string. + * + * |Name |Type |Desc | + * |------|------|----------------| + * |str |string|String to encode| + * |return|string|Encoded string | + * + * ### decode + * + * |Name |Type |Desc | + * |------------|-------|----------------------| + * |str |string |String to decode | + * |[safe=false]|boolean|Suppress error if true| + * |return |string |Decoded string | + * + * Turn any UTF-8 encoded string into UTF-8 decoded string. + * + * ```javascript + * utf8.encode('\uD800\uDC00'); // -> '\xF0\x90\x80\x80' + * utf8.decode('\xF0\x90\x80\x80'); // -> '\uD800\uDC00' + * ``` + */ + + /* module + * env: all + * test: all + */ + + /* dependencies + * ucs2 + */ + + // https://encoding.spec.whatwg.org/#utf-8 + exports = { + encode: function (str) + { + var codePoints = ucs2.decode(str); + + var byteArr = ''; + + for (var i = 0, len = codePoints.length; i < len; i++) + { + byteArr += encodeCodePoint(codePoints[i]); + } + + return byteArr; + }, + decode: function decode(str, safe) + { + byteArr = ucs2.decode(str); + byteIdx = 0; + byteCount = byteArr.length; + codePoint = 0; + bytesSeen = 0; + bytesNeeded = 0; + lowerBoundary = 0x80; + upperBoundary = 0xBF; + + var codePoints = []; + + var tmp; + + while((tmp = decodeCodePoint(safe)) !== false) + { + codePoints.push(tmp); + } + + return ucs2.encode(codePoints); + } + }; + + var fromCharCode = String.fromCharCode; + + function encodeCodePoint(codePoint) + { + // U+0000 to U+0080, ASCII code point + if ((codePoint & 0xFFFFFF80) === 0) + { + return fromCharCode(codePoint); + } + + var ret = '', + count, + offset; + + // U+0080 to U+07FF, inclusive + if ((codePoint & 0xFFFFF800) === 0) + { + count = 1; + offset = 0xC0; + } else if ((codePoint & 0xFFFF0000) === 0) + { + // U+0800 to U+FFFF, inclusive + count = 2; + offset = 0xE0; + } else if ((codePoint & 0xFFE00000) == 0) + { + // U+10000 to U+10FFFF, inclusive + count = 3; + offset = 0xF0; + } + + ret += fromCharCode((codePoint >> (6 * count)) + offset); + + while (count > 0) + { + var tmp = codePoint >> (6 * (count - 1)); + ret += fromCharCode(0x80 | tmp & 0x3F); + count--; + } + + return ret; + } + + var byteArr, + byteIdx, + byteCount, + codePoint, + bytesSeen, + bytesNeeded, + lowerBoundary, + upperBoundary; + + function decodeCodePoint(safe) + { + /* eslint-disable no-constant-condition */ + while (true) + { + if (byteIdx >= byteCount && bytesNeeded) + { + if (safe) return goBack(); + throw new Error('Invalid byte index'); + } + + if (byteIdx === byteCount) return false; + + var byte = byteArr[byteIdx]; + byteIdx++; + + if (!bytesNeeded) + { + // 0x00 to 0x7F + if ((byte & 0x80) === 0) + { + return byte; + } + // 0xC2 to 0xDF + if ((byte & 0xE0) === 0xC0) + { + bytesNeeded = 1; + codePoint = byte & 0x1F; + } else if ((byte & 0xF0) === 0xE0) + { + // 0xE0 to 0xEF + if (byte === 0xE0) lowerBoundary = 0xA0; + if (byte === 0xED) upperBoundary = 0x9F; + bytesNeeded = 2; + codePoint = byte & 0xF; + } else if ((byte & 0xF8) === 0xF0) + { + // 0xF0 to 0xF4 + if (byte === 0xF0) lowerBoundary = 0x90; + if (byte === 0xF4) upperBoundary = 0x8F; + bytesNeeded = 3; + codePoint = byte & 0x7; + } else + { + if (safe) return goBack(); + throw new Error('Invalid UTF-8 detected'); + } + + continue; + } + + if (byte < lowerBoundary || byte > upperBoundary) + { + if (safe) { + byteIdx--; + return goBack(); + } + throw new Error('Invalid continuation byte'); + } + + lowerBoundary = 0x80; + upperBoundary = 0xBF; + + codePoint = (codePoint << 6) | (byte & 0x3F); + + bytesSeen++; + + if (bytesSeen !== bytesNeeded) continue; + + var tmp = codePoint; + + codePoint = 0; + bytesNeeded = 0; + bytesSeen = 0; + + return tmp; + } + } + + function goBack() + { + var start = byteIdx - bytesSeen - 1; + byteIdx = start + 1; + codePoint = 0; + bytesNeeded = 0; + bytesSeen = 0; + lowerBoundary = 0x80; + upperBoundary = 0xBF; + + return byteArr[start]; + } + + return exports; + })({}); + /* ------------------------------ optimizeCb ------------------------------ */ var optimizeCb = _.optimizeCb = (function () @@ -624,127 +931,6 @@ return exports; })({}); - /* ------------------------------ cookie ------------------------------ */ - - _.cookie = (function (exports) - { - /* Simple api for handling browser cookies. - * - * ### get - * - * Get cookie value. - * - * |Name |Type |Desc | - * |------|------|--------------------------| - * |key |string|Cookie key | - * |return|string|Corresponding cookie value| - * - * ### set - * - * Set cookie value. - * - * |Name |Type |Desc | - * |---------|-------|--------------| - * |key |string |Cookie key | - * |val |string |Cookie value | - * |[options]|object |Cookie options| - * |return |exports|Module cookie | - * - * ### remove - * - * Remove cookie value. - * - * |Name |Type |Desc | - * |---------|-------|--------------| - * |key |string |Cookie key | - * |[options]|object |Cookie options| - * |return |exports|Module cookie | - * - * ```javascript - * cookie.set('a', '1', {path: '/'}); - * cookie.get('a'); // -> '1' - * cookie.remove('a'); - * ``` - */ - - /* module - * env: browser - * test: browser - */ - - /* dependencies - * defaults isNum isUndef - */ - - var defOpts = { path: '/' }; - - function setCookie(key, val, options) - { - if (!isUndef(val)) - { - options = options || {}; - options = defaults(options, defOpts); - - if (isNum(options.expires)) - { - var expires = new Date(); - expires.setMilliseconds(expires.getMilliseconds() + options.expires * 864e+5); - options.expires = expires; - } - - val = encodeURIComponent(val); - key = encodeURIComponent(key); - - document.cookie = [ - key, '=', val, - options.expires && '; expires=' + options.expires.toUTCString(), - options.path && '; path=' + options.path, - options.domain && '; domain=' + options.domain, - options.secure ? '; secure' : '' - ].join(''); - - return exports; - } - - var cookies = document.cookie ? document.cookie.split('; ') : [], - result = key ? undefined : {}; - - for (var i = 0, len = cookies.length; i < len; i++) - { - var c = cookies[i], - parts = c.split('='), - name = decodeURIComponent(parts.shift()); - - c = parts.join('='); - c = decodeURIComponent(c); - - if (key === name) - { - result = c; - break; - } - - if (!key) result[name] = c; - } - - return result; - } - - exports = { - get: setCookie, - set: setCookie, - remove: function (key, options) - { - options = options || {}; - options.expires = -1; - - return setCookie(key, '', options); - } - }; - - return exports; - })({}); - /* ------------------------------ extendOwn ------------------------------ */ var extendOwn = _.extendOwn = (function (exports) @@ -1226,6 +1412,195 @@ return exports; })(); + /* ------------------------------ decodeUriComponent ------------------------------ */ + + var decodeUriComponent = _.decodeUriComponent = (function () + { + /* Better decodeURIComponent that does not throw if input is invalid. + * + * |Name |Type |Desc | + * |------|------|----------------| + * |str |string|String to decode| + * |return|string|Decoded string | + * + * ```javascript + * decodeUriComponent('%%25%'); // -> '%%%' + * decodeUriComponent('%E0%A4%A'); // -> '\xE0\xA4%A' + * ``` + */ + + /* module + * env: all + * test: all + */ + + /* dependencies + * each ucs2 map utf8 + */ + + function exports(str) + { + try + { + return decodeURIComponent(str); + } catch (e) + { + var replaceMap = {}; + + var matches = str.match(regMatcher); + + each(matches, function (match) + { + str = str.replace(match, decode(match)); + }); + + return str; + } + } + + function decode(str) + { + str = str.split('%').slice(1); + + var bytes = map(str, hexToInt); + + str = ucs2.encode(bytes); + str = utf8.decode(str, true); + + return str; + } + + function hexToInt(numStr) + { + return +('0x' + numStr); + } + + var regMatcher = /(%[a-f0-9]{2})+/gi; + + return exports; + })(); + + /* ------------------------------ cookie ------------------------------ */ + + _.cookie = (function (exports) + { + /* Simple api for handling browser cookies. + * + * ### get + * + * Get cookie value. + * + * |Name |Type |Desc | + * |------|------|--------------------------| + * |key |string|Cookie key | + * |return|string|Corresponding cookie value| + * + * ### set + * + * Set cookie value. + * + * |Name |Type |Desc | + * |---------|-------|--------------| + * |key |string |Cookie key | + * |val |string |Cookie value | + * |[options]|object |Cookie options| + * |return |exports|Module cookie | + * + * ### remove + * + * Remove cookie value. + * + * |Name |Type |Desc | + * |---------|-------|--------------| + * |key |string |Cookie key | + * |[options]|object |Cookie options| + * |return |exports|Module cookie | + * + * ```javascript + * cookie.set('a', '1', {path: '/'}); + * cookie.get('a'); // -> '1' + * cookie.remove('a'); + * ``` + */ + + /* module + * env: browser + * test: browser + */ + + /* dependencies + * defaults isNum isUndef decodeUriComponent + */ + + var defOpts = { path: '/' }; + + function setCookie(key, val, options) + { + if (!isUndef(val)) + { + options = options || {}; + options = defaults(options, defOpts); + + if (isNum(options.expires)) + { + var expires = new Date(); + expires.setMilliseconds(expires.getMilliseconds() + options.expires * 864e+5); + options.expires = expires; + } + + val = encodeURIComponent(val); + key = encodeURIComponent(key); + + document.cookie = [ + key, '=', val, + options.expires && '; expires=' + options.expires.toUTCString(), + options.path && '; path=' + options.path, + options.domain && '; domain=' + options.domain, + options.secure ? '; secure' : '' + ].join(''); + + return exports; + } + + var cookies = document.cookie ? document.cookie.split('; ') : [], + result = key ? undefined : {}; + + for (var i = 0, len = cookies.length; i < len; i++) + { + var c = cookies[i], + parts = c.split('='), + name = decodeUriComponent(parts.shift()); + + c = parts.join('='); + c = decodeUriComponent(c); + + if (key === name) + { + result = c; + break; + } + + if (!key) result[name] = c; + } + + return result; + } + + exports = { + get: setCookie, + set: setCookie, + remove: function (key, options) + { + options = options || {}; + options.expires = -1; + + return setCookie(key, '', options); + } + }; + + return exports; + })({}); + /* ------------------------------ rtrim ------------------------------ */ var rtrim = _.rtrim = (function ()