diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15812b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +build +.sizecache.json +*.log* diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..e3fbd98 --- /dev/null +++ b/.jshintignore @@ -0,0 +1,2 @@ +build +node_modules diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..fd016db --- /dev/null +++ b/.jshintrc @@ -0,0 +1,12 @@ +{ + "curly": true, + "eqeqeq": true, + "expr": true, + // "maxlen": 130, + "newcap": true, + "noarg": true, + "nonbsp": true, + "trailing": true, + "undef": true, + "unused": true +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..bfd8ca1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: node_js +node_js: + - 0.10 +before_script: + - npm install -g grunt-cli +script: grunt ci --verbose +env: + global: + - secure: HRae0kyIDDuhonvMi2SfEl1WJb4K/wX8WmzT9YkxFbmWwLjiOMkmqyuEyi76DbTC1cb9o7WwGVgbP1DhSm6n6m0Lz+PSzpprBN4QZuJc56jcc+tBA6gM81hyUufaTT0yUWz112Bu06kWIAs44w5PtG0FYZR0CuIN8fQvZi8fXCQ= + - secure: c+M5ECIfxDcVrr+ZlqgpGjv8kVM/hxiz3ACMCn4ZkDiaeq4Rw0wWIGZYL6aV5fhsoHgzEQ/XQPca8xKs3Umr7R3b6Vr3AEyFnW+LP67K/1Qbz4Pi3PvhDH/h4rvK7fOoTqTDCVVDEH3v4pefsz2VaKemG4iBKxrcof5aR4Rjopk= diff --git a/CHANGELOG.md b/CHANGELOG.md index b780a29..59d868f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,73 @@ +HEAD +----- + +1.4.1 +----- +- Added support for CommonJS. + +- Added support for package managers: Jam (http://jamjs.org), volo (http://volojs.org), Component (http://component.io), jspm (http://jspm.io). + +- The expires option now interpretes fractions of numbers (e.g. days) correctly. + +1.4.0 +----- +- Support for AMD. + +- Removed deprecated method `$.cookie('name', null)` for deleting a cookie, + use `$.removeCookie('name')`. + +- `$.cookie('name')` now returns `undefined` in case such cookie does not exist + (was `null`). Because the return value is still falsy, testing for existence + of a cookie like `if ( $.cookie('foo') )` keeps working without change. + +- Renamed bower package definition (component.json -> bower.json) for usage + with up-to-date bower. + +- Badly encoded cookies no longer throw exception upon reading but do return + undefined (similar to how we handle JSON parse errors with json = true). + +- Added conversion function as optional last argument for reading, + so that values can be changed to a different representation easily on the fly. + Useful for parsing numbers for instance: + + ```javascript + $.cookie('foo', '42'); + $.cookie('foo', Number); // => 42 + ``` + +1.3.1 +----- +- Fixed issue where it was no longer possible to check for an arbitrary cookie, + while json is set to true, there was a SyntaxError thrown from JSON.parse. + +- Fixed issue where RFC 2068 decoded cookies were not properly read. + +1.3.0 +----- +- Configuration options: `raw`, `json`. Replaces raw option, becomes config: + + ```javascript + $.cookie.raw = true; // bypass encoding/decoding the cookie value + $.cookie.json = true; // automatically JSON stringify/parse value + ``` + + Thus the default options now cleanly contain cookie attributes only. + +- Removing licensing under GPL Version 2, the plugin is now released under MIT License only +(keeping it simple and following the jQuery library itself here). + +- Bugfix: Properly handle RFC 2068 quoted cookie values. + +- Added component.json for bower. + +- Added jQuery plugin package manifest. + +- `$.cookie()` returns all available cookies. + +1.2.0 +----- +- Adding `$.removeCookie('foo')` for deleting a cookie, using `$.cookie('foo', null)` is now deprecated. + 1.1 --- -- Default options. +- Adding default options. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..608c33e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +##Issues + +- Report issues or feature requests on [GitHub Issues](https://github.com/carhartl/jquery-cookie/issues). +- If reporting a bug, please add a [simplified example](http://sscce.org/). + +##Pull requests +- Create a new topic branch for every separate change you make. +- Create a test case if you are fixing a bug or implementing an important feature. +- Make sure the build runs successfully. + +## Development + +###Tools +We use the following tools for development: + +- [Qunit](http://qunitjs.com/) for tests. +- [NodeJS](http://nodejs.org/download/) required to run grunt. +- [Grunt](http://gruntjs.com/getting-started) for task management. + +###Getting started +Install [NodeJS](http://nodejs.org/). +Install globally grunt-cli using the following command: + + $ npm install -g grunt-cli + +Browse to the project root directory and install the dev dependencies: + + $ npm install -d + +To execute the build and tests run the following command in the root of the project: + + $ grunt + +You should see a green message in the console: + + Done, without errors. + +###Tests +You can also run the tests in the browser. +Start a test server from the project root: + + $ grunt connect:tests + +This will automatically open the test suite at http://127.0.0.1:9998 in the default browser, with livereload enabled. + +_Note: we recommend cleaning all the browser cookies before running the tests, that can avoid false positive failures._ + +###Automatic build +You can build automatically after a file change using the following command: + + $ grunt watch diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..5ac9db5 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,168 @@ +/*jshint node:true, quotmark:single */ +'use strict'; + +module.exports = function (grunt) { + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + qunit: { + all: 'test/index.html' + }, + jshint: { + options: { + jshintrc: true + }, + grunt: 'Gruntfile.js', + source: 'src/**/*.js', + tests: 'test/**/*.js' + }, + uglify: { + options: { + banner: '/*! <%= pkg.name %> v<%= pkg.version %> | <%= pkg.license %> */\n' + }, + build: { + files: { + 'build/jquery.cookie-<%= pkg.version %>.min.js': 'src/jquery.cookie.js' + } + } + }, + watch: { + options: { + livereload: true + }, + files: '{src,test}/**/*.js', + tasks: 'default' + }, + compare_size: { + files: [ + 'build/jquery.cookie-<%= pkg.version %>.min.js', + 'src/jquery.cookie.js' + ], + options: { + compress: { + gz: function (fileContents) { + return require('gzip-js').zip(fileContents, {}).length; + } + } + } + }, + connect: { + saucelabs: { + options: { + port: 9999, + base: ['.', 'test'] + } + }, + tests: { + options: { + port: 9998, + base: ['.', 'test'], + open: 'http://127.0.0.1:9998', + keepalive: true, + livereload: true + } + } + }, + 'saucelabs-qunit': { + all: { + options: { + urls: ['http://127.0.0.1:9999'], + build: process.env.TRAVIS_JOB_ID, + browsers: [ + // iOS + { + browserName: 'iphone', + platform: 'OS X 10.9', + version: '7.1' + }, + { + browserName: 'ipad', + platform: 'OS X 10.9', + version: '7.1' + }, + // Android + { + browserName: 'android', + platform: 'Linux', + version: '4.3' + }, + // OS X + { + browserName: 'safari', + platform: 'OS X 10.9', + version: '7' + }, + { + browserName: 'safari', + platform: 'OS X 10.8', + version: '6' + }, + { + browserName: 'firefox', + platform: 'OS X 10.9', + version: '28' + }, + // Windows + { + browserName: 'internet explorer', + platform: 'Windows 8.1', + version: '11' + }, + { + browserName: 'internet explorer', + platform: 'Windows 8', + version: '10' + }, + { + browserName: 'internet explorer', + platform: 'Windows 7', + version: '11' + }, + { + browserName: 'internet explorer', + platform: 'Windows 7', + version: '10' + }, + { + browserName: 'internet explorer', + platform: 'Windows 7', + version: '9' + }, + { + browserName: 'internet explorer', + platform: 'Windows 7', + version: '8' + }, + { + browserName: 'firefox', + platform: 'Windows 7', + version: '29' + }, + { + browserName: 'chrome', + platform: 'Windows 7', + version: '34' + }, + // Linux + { + browserName: 'firefox', + platform: 'Linux', + version: '29' + } + ] + } + } + } + }); + + // Loading dependencies + for (var key in grunt.file.readJSON('package.json').devDependencies) { + if (key !== 'grunt' && key.indexOf('grunt') === 0) { + grunt.loadNpmTasks(key); + } + } + + grunt.registerTask('default', ['jshint', 'qunit', 'uglify', 'compare_size']); + grunt.registerTask('saucelabs', ['connect:saucelabs', 'saucelabs-qunit']); + grunt.registerTask('ci', ['jshint', 'qunit', 'saucelabs']); +}; diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt new file mode 100644 index 0000000..7a631e8 --- /dev/null +++ b/MIT-LICENSE.txt @@ -0,0 +1,20 @@ +Copyright 2014 Klaus Hartl + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 55f2a8b..3100800 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,161 @@ -# jquery.cookie +# IMPORTANT! + +This project was moved to https://github.com/js-cookie/js-cookie, check [the discussion](https://github.com/carhartl/jquery-cookie/issues/349). + +New issues should be opened at https://github.com/js-cookie/js-cookie/issues + +# jquery.cookie [![Build Status](https://travis-ci.org/carhartl/jquery-cookie.png?branch=master)](https://travis-ci.org/carhartl/jquery-cookie) [![Code Climate](https://codeclimate.com/github/carhartl/jquery-cookie.png)](https://codeclimate.com/github/carhartl/jquery-cookie) A simple, lightweight jQuery plugin for reading, writing and deleting cookies. +**If you're viewing this, you're reading the documentation for the old repository. +[View documentation for the latest backwards compatible release (1.5.1).](https://github.com/js-cookie/js-cookie/tree/v1.5.1)** + +## Build Status Matrix + +[![Selenium Test Status](https://saucelabs.com/browser-matrix/jquery-cookie.svg)](https://saucelabs.com/u/jquery-cookie) + ## Installation Include script *after* the jQuery library (unless you are packaging scripts somehow else): - +```html + +``` + +**Do not include the script directly from GitHub (http://raw.github.com/...).** The file is being served as text/plain and as such being blocked +in Internet Explorer on Windows 7 for instance (because of the wrong MIME type). Bottom line: GitHub is not a CDN. + +The plugin can also be loaded as AMD or CommonJS module. ## Usage Create session cookie: - $.cookie('the_cookie', 'the_value'); +```javascript +$.cookie('name', 'value'); +``` Create expiring cookie, 7 days from then: - $.cookie('the_cookie', 'the_value', { expires: 7 }); +```javascript +$.cookie('name', 'value', { expires: 7 }); +``` Create expiring cookie, valid across entire site: - $.cookie('the_cookie', 'the_value', { expires: 7, path: '/' }); +```javascript +$.cookie('name', 'value', { expires: 7, path: '/' }); +``` Read cookie: - $.cookie('the_cookie'); // => "the_value" - $.cookie('the_cookie', { raw: true }); // => "the_value" not URL decoded - $.cookie('not_existing'); // => null +```javascript +$.cookie('name'); // => "value" +$.cookie('nothing'); // => undefined +``` -Delete cookie by passing null as value: +Read all available cookies: - $.cookie('the_cookie', null); +```javascript +$.cookie(); // => { "name": "value" } +``` + +Delete cookie: + +```javascript +// Returns true when cookie was successfully deleted, otherwise false +$.removeCookie('name'); // => true +$.removeCookie('nothing'); // => false + +// Need to use the same attributes (path, domain) as what the cookie was written with +$.cookie('name', 'value', { path: '/' }); +// This won't work! +$.removeCookie('name'); // => false +// This will work! +$.removeCookie('name', { path: '/' }); // => true +``` *Note: when deleting a cookie, you must pass the exact same path, domain and secure options that were used to set the cookie, unless you're relying on the default options that is.* -## Options +## Configuration + +### raw + +By default the cookie value is encoded/decoded when writing/reading, using `encodeURIComponent`/`decodeURIComponent`. Bypass this by setting raw to true: + +```javascript +$.cookie.raw = true; +``` + +### json -Options can be set globally by setting properties of the `$.cookie.defaults` object or individually for each call to `$.cookie()` by passing a plain object to the options argument. Per-call options override the ones set by `$.cookie.defaults`. +Turn on automatic storage of JSON objects passed as the cookie value. Assumes `JSON.stringify` and `JSON.parse`: + +```javascript +$.cookie.json = true; +``` + +## Cookie Options + +Cookie attributes can be set globally by setting properties of the `$.cookie.defaults` object or individually for each call to `$.cookie()` by passing a plain object to the options argument. Per-call options override the default options. + +### expires expires: 365 Define lifetime of the cookie. Value can be a `Number` which will be interpreted as days from time of creation or a `Date` object. If omitted, the cookie becomes a session cookie. +### path + path: '/' Define the path where the cookie is valid. *By default the path of the cookie is the path of the page where the cookie was created (standard browser behavior).* If you want to make it available for instance across the entire domain use `path: '/'`. Default: path of page where the cookie was created. +**Note regarding Internet Explorer:** + +> Due to an obscure bug in the underlying WinINET InternetGetCookie implementation, IE’s document.cookie will not return a cookie if it was set with a path attribute containing a filename. + +(From [Internet Explorer Cookie Internals (FAQ)](http://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspx)) + +This means one cannot set a path using `path: window.location.pathname` in case such pathname contains a filename like so: `/check.html` (or at least, such cookie cannot be read correctly). + +### domain + domain: 'example.com' Define the domain where the cookie is valid. Default: domain of page where the cookie was created. +### secure + secure: true If true, the cookie transmission requires a secure protocol (https). Default: `false`. - raw: true +## Converters + +Provide a conversion function as optional last argument for reading, in order to change the cookie's value +to a different representation on the fly. + +Example for parsing a value into a number: + +```javascript +$.cookie('foo', '42'); +$.cookie('foo', Number); // => 42 +``` -By default the cookie value is encoded/decoded when creating/reading, using `encodeURIComponent`/`decodeURIComponent`. Turn off by setting `raw: true`. Default: `false`. +Dealing with cookies that have been encoded using `escape` (3rd party cookies): -## Changelog +```javascript +$.cookie.raw = true; +$.cookie('foo', unescape); +``` -## Development +You can pass an arbitrary conversion function. -- Source hosted at [GitHub](https://github.com/carhartl/jquery-cookie) -- Report issues, questions, feature requests on [GitHub Issues](https://github.com/carhartl/jquery-cookie/issues) +## Contributing -Pull requests are very welcome! Make sure your patches are well tested. Please create a topic branch for every separate change you make. +Check out the [Contributing Guidelines](CONTRIBUTING.md) ## Authors diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..3862b74 --- /dev/null +++ b/bower.json @@ -0,0 +1,18 @@ +{ + "name": "jquery.cookie", + "version": "1.4.1", + "main": [ + "src/jquery.cookie.js" + ], + "dependencies": { + "jquery": ">=1.2" + }, + "ignore": [ + "test", + ".*", + "*.json", + "*.md", + "*.txt", + "Gruntfile.js" + ] +} diff --git a/component.json b/component.json new file mode 100644 index 0000000..0fad480 --- /dev/null +++ b/component.json @@ -0,0 +1,14 @@ +{ + "name": "jquery.cookie", + "repo": "carhartl/jquery-cookie", + "description": "A simple, lightweight jQuery plugin for reading, writing and deleting cookies", + "version": "1.4.1", + "keywords": [], + "dependencies": {}, + "development": {}, + "license": "MIT", + "main": "src/jquery.cookie.js", + "scripts": [ + "src/jquery.cookie.js" + ] +} diff --git a/cookie.jquery.json b/cookie.jquery.json new file mode 100644 index 0000000..69d5748 --- /dev/null +++ b/cookie.jquery.json @@ -0,0 +1,32 @@ +{ + "name": "cookie", + "version": "1.4.1", + "title": "jQuery Cookie", + "description": "A simple, lightweight jQuery plugin for reading, writing and deleting cookies.", + "author": { + "name": "Klaus Hartl", + "url": "https://github.com/carhartl" + }, + "maintainers": [ + { + "name": "Klaus Hartl", + "url": "https://github.com/carhartl" + }, + { + "name": "Fagner Martins", + "url": "https://github.com/FagnerMartinsBrack" + } + ], + "licenses": [ + { + "type": "MIT", + "url": "https://raw.github.com/carhartl/jquery-cookie/master/MIT-LICENSE.txt" + } + ], + "dependencies": { + "jquery": ">=1.2" + }, + "bugs": "https://github.com/carhartl/jquery-cookie/issues", + "homepage": "https://github.com/carhartl/jquery-cookie", + "docs": "https://github.com/carhartl/jquery-cookie#readme" +} diff --git a/jquery.cookie.js b/jquery.cookie.js deleted file mode 100644 index 3e23eb0..0000000 --- a/jquery.cookie.js +++ /dev/null @@ -1,61 +0,0 @@ -/*jshint eqnull:true */ -/*! - * jQuery Cookie Plugin v1.1 - * https://github.com/carhartl/jquery-cookie - * - * Copyright 2011, Klaus Hartl - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://www.opensource.org/licenses/mit-license.php - * http://www.opensource.org/licenses/GPL-2.0 - */ -(function($, document) { - - var pluses = /\+/g; - function raw(s) { - return s; - } - function decoded(s) { - return decodeURIComponent(s.replace(pluses, ' ')); - } - - $.cookie = function(key, value, options) { - - // key and at least value given, set cookie... - if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value == null)) { - options = $.extend({}, $.cookie.defaults, options); - - if (value == null) { - options.expires = -1; - } - - if (typeof options.expires === 'number') { - var days = options.expires, t = options.expires = new Date(); - t.setDate(t.getDate() + days); - } - - value = String(value); - - return (document.cookie = [ - encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value), - options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE - options.path ? '; path=' + options.path : '', - options.domain ? '; domain=' + options.domain : '', - options.secure ? '; secure' : '' - ].join('')); - } - - // key and possibly options given, get cookie... - options = value || $.cookie.defaults || {}; - var decode = options.raw ? raw : decoded; - var cookies = document.cookie.split('; '); - for (var i = 0, parts; (parts = cookies[i] && cookies[i].split('=')); i++) { - if (decode(parts.shift()) === key) { - return decode(parts.join('=')); - } - } - return null; - }; - - $.cookie.defaults = {}; - -})(jQuery, document); diff --git a/package.json b/package.json new file mode 100644 index 0000000..38abffe --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "jquery.cookie", + "version": "1.4.1", + "description": "A simple, lightweight jQuery plugin for reading, writing and deleting cookies.", + "main": "src/jquery.cookie.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "grunt" + }, + "repository": { + "type": "git", + "url": "git://github.com/carhartl/jquery-cookie.git" + }, + "author": "Klaus Hartl", + "license": "MIT", + "gitHead": "bd3c9713222bace68d25fe2128c0f8633cad1269", + "readmeFilename": "README.md", + "devDependencies": { + "grunt": "~0.4.1", + "grunt-contrib-jshint": "~0.10.0", + "grunt-contrib-uglify": "~0.2.0", + "grunt-contrib-qunit": "~0.2.0", + "grunt-contrib-watch": "~0.6.1", + "grunt-compare-size": "~0.4.0", + "grunt-saucelabs": "~7.0.0", + "grunt-contrib-connect": "~0.7.1", + "gzip-js": "~0.3.0" + }, + "volo": { + "url": "https://raw.github.com/carhartl/jquery-cookie/v{version}/src/jquery.cookie.js" + }, + "jspm": { + "main": "jquery.cookie", + "files": ["src/jquery.cookie.js"], + "buildConfig": { + "uglify": true + } + }, + "jam": { + "dependencies": { + "jquery": ">=1.2" + }, + "main": "src/jquery.cookie.js", + "include": [ + "src/jquery.cookie.js" + ] + } +} diff --git a/server.js b/server.js deleted file mode 100644 index f454999..0000000 --- a/server.js +++ /dev/null @@ -1,24 +0,0 @@ -var http = require('http'); -var url = require('url'); -var path = require('path'); -var fs = require('fs'); - -http.createServer(function(request, response) { - var uri = url.parse(request.url).pathname; - var filename = path.join(process.cwd(), uri); - - fs.readFile(filename, 'binary', function(err, file) { - if (err) { - response.writeHead(500, { 'Content-Type': 'text/plain' }); - response.write(err + '\n'); - response.end(); - return; - } - - response.writeHead(200); - response.write(file, 'utf-8'); - response.end(); - }); -}).listen(8124, '0.0.0.0'); - -console.log('Test suite at http://0.0.0.0:8124/test.html'); diff --git a/src/.jshintrc b/src/.jshintrc new file mode 100644 index 0000000..241dd9f --- /dev/null +++ b/src/.jshintrc @@ -0,0 +1,13 @@ +{ + "browser": true, + "camelcase": true, + "jquery": true, + "quotmark": "single", + "globals": { + "define": true, + "module": true, + "require": true + }, + + "extends": "../.jshintrc" +} diff --git a/src/jquery.cookie.js b/src/jquery.cookie.js new file mode 100644 index 0000000..8218817 --- /dev/null +++ b/src/jquery.cookie.js @@ -0,0 +1,114 @@ +/*! + * jQuery Cookie Plugin v1.4.1 + * https://github.com/carhartl/jquery-cookie + * + * Copyright 2006, 2014 Klaus Hartl + * Released under the MIT license + */ +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD (Register as an anonymous module) + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS + module.exports = factory(require('jquery')); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + + var pluses = /\+/g; + + function encode(s) { + return config.raw ? s : encodeURIComponent(s); + } + + function decode(s) { + return config.raw ? s : decodeURIComponent(s); + } + + function stringifyCookieValue(value) { + return encode(config.json ? JSON.stringify(value) : String(value)); + } + + function parseCookieValue(s) { + if (s.indexOf('"') === 0) { + // This is a quoted cookie as according to RFC2068, unescape... + s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); + } + + try { + // Replace server-side written pluses with spaces. + // If we can't decode the cookie, ignore it, it's unusable. + // If we can't parse the cookie, ignore it, it's unusable. + s = decodeURIComponent(s.replace(pluses, ' ')); + return config.json ? JSON.parse(s) : s; + } catch(e) {} + } + + function read(s, converter) { + var value = config.raw ? s : parseCookieValue(s); + return $.isFunction(converter) ? converter(value) : value; + } + + var config = $.cookie = function (key, value, options) { + + // Write + + if (arguments.length > 1 && !$.isFunction(value)) { + options = $.extend({}, config.defaults, options); + + if (typeof options.expires === 'number') { + var days = options.expires, t = options.expires = new Date(); + t.setMilliseconds(t.getMilliseconds() + days * 864e+5); + } + + return (document.cookie = [ + encode(key), '=', stringifyCookieValue(value), + options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE + options.path ? '; path=' + options.path : '', + options.domain ? '; domain=' + options.domain : '', + options.secure ? '; secure' : '' + ].join('')); + } + + // Read + + var result = key ? undefined : {}, + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. Also prevents odd result when + // calling $.cookie(). + cookies = document.cookie ? document.cookie.split('; ') : [], + i = 0, + l = cookies.length; + + for (; i < l; i++) { + var parts = cookies[i].split('='), + name = decode(parts.shift()), + cookie = parts.join('='); + + if (key === name) { + // If second argument (value) is a function it's a converter... + result = read(cookie, value); + break; + } + + // Prevent storing a cookie that we couldn't decode. + if (!key && (cookie = read(cookie)) !== undefined) { + result[name] = cookie; + } + } + + return result; + }; + + config.defaults = {}; + + $.removeCookie = function (key, options) { + // Must not alter options, thus extending a fresh object... + $.cookie(key, '', $.extend({}, options, { expires: -1 })); + return !$.cookie(key); + }; + +})); diff --git a/test.html b/test.html deleted file mode 100644 index 90daad6..0000000 --- a/test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - jquery.cookie Test Suite - - - - - - - -

jquery.cookie Test Suite

-

-
-

-
    - - diff --git a/test.js b/test.js deleted file mode 100644 index cb40e59..0000000 --- a/test.js +++ /dev/null @@ -1,131 +0,0 @@ -var before = { - setup: function () { - cookies = document.cookie.split('; ') - for (var i = 0, c; (c = (cookies)[i]) && (c = c.split('=')[0]); i++) { - document.cookie = c + '=; expires=' + new Date(0).toUTCString(); - } - - $.cookie.defaults = {}; - } -}; - - -module('read', before); - -test('simple value', 1, function () { - document.cookie = 'c=v'; - equal($.cookie('c'), 'v', 'should return value'); -}); - -test('empty value', 1, function () { - // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, which - // resulted in a bug while reading such a cookie. - $.cookie('c', ''); - equal($.cookie('c'), '', 'should return value'); -}); - -test('not existing', 1, function () { - equal($.cookie('whatever'), null, 'should return null'); -}); - -test('decode', 1, function () { - document.cookie = encodeURIComponent(' c') + '=' + encodeURIComponent(' v'); - equal($.cookie(' c'), ' v', 'should decode key and value'); -}); - -test('decode pluses to space for server side written cookie', 1, function () { - document.cookie = 'c=foo+bar' - equal($.cookie('c'), 'foo bar', 'should convert pluses back to space'); -}); - -test('raw: true', 1, function () { - document.cookie = 'c=%20v'; - equal($.cookie('c', { raw: true }), '%20v', 'should not decode value'); -}); - -test('[] used in name', 1, function () { - document.cookie = 'c[999]=foo'; - equal($.cookie('c[999]'), 'foo', 'should return value'); -}); - -test('embedded equals', 1, function () { - $.cookie('c', 'foo=bar', { raw: true }); - equal($.cookie('c', { raw: true }), 'foo=bar', 'should include the entire value'); -}); - -test('defaults', 1, function () { - document.cookie = 'c=%20v'; - $.cookie.defaults.raw = true; - equal($.cookie('c'), '%20v', 'should use raw from defaults'); -}); - - -module('write', before); - -test('String primitive', 1, function () { - $.cookie('c', 'v'); - equal($.cookie('c'), 'v', 'should write value'); -}); - -test('String object', 1, function () { - $.cookie('c', new String('v')); - equal($.cookie('c'), 'v', 'should write value'); -}); - -test('value "[object Object]"', 1, function () { - $.cookie('c', '[object Object]'); - equal($.cookie('c'), '[object Object]', 'should write value'); -}); - -test('number', 1, function () { - $.cookie('c', 1234); - equal($.cookie('c'), '1234', 'should write value'); -}); - -test('expires option as days from now', 1, function() { - var sevenDaysFromNow = new Date(); - sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7); - equal($.cookie('c', 'v', { expires: 7 }), 'c=v; expires=' + sevenDaysFromNow.toUTCString(), - 'should write the cookie string with expires'); -}); - -test('expires option as Date instance', 1, function() { - var sevenDaysFromNow = new Date(); - sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7); - equal($.cookie('c', 'v', { expires: sevenDaysFromNow }), 'c=v; expires=' + sevenDaysFromNow.toUTCString(), - 'should write the cookie string with expires'); -}); - -test('invalid expires option (in the past)', 1, function() { - var yesterday = new Date(); - yesterday.setDate(yesterday.getDate() - 1); - $.cookie('c', 'v', { expires: yesterday }); - equal($.cookie('c'), null, 'should not save already expired cookie'); -}); - -test('return value', 1, function () { - equal($.cookie('c', 'v'), 'c=v', 'should return written cookie string'); -}); - -test('raw option set to true', 1, function () { - equal($.cookie('c', ' v', { raw: true }).split('=')[1], - ' v', 'should not encode'); -}); - -test('defaults', 1, function () { - $.cookie.defaults.raw = true; - equal($.cookie('c', ' v').split('=')[1], ' v', 'should use raw from defaults'); -}); - - -module('delete', before); - -test('delete', 2, function () { - document.cookie = 'c=v'; - $.cookie('c', null); - equal(document.cookie, '', 'should delete with null as value'); - - document.cookie = 'c=v'; - $.cookie('c', undefined); - equal(document.cookie, '', 'should delete with undefined as value'); -}); diff --git a/test/.jshintrc b/test/.jshintrc new file mode 100644 index 0000000..bc52b0a --- /dev/null +++ b/test/.jshintrc @@ -0,0 +1,9 @@ +{ + "browser": true, + "jquery": true, + "qunit": true, + + "-W053": true, + + "extends": "../.jshintrc" +} diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..ade6830 --- /dev/null +++ b/test/index.html @@ -0,0 +1,16 @@ + + + + + jquery.cookie Test Suite + + + + + + + +
    +
    + + diff --git a/test/malformed_cookie.html b/test/malformed_cookie.html new file mode 100644 index 0000000..74178d4 --- /dev/null +++ b/test/malformed_cookie.html @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/test/tests.js b/test/tests.js new file mode 100644 index 0000000..68b47cc --- /dev/null +++ b/test/tests.js @@ -0,0 +1,345 @@ +// Required for exposing test results to the Sauce Labs API. +// Can be removed when the following issue is fixed: +// https://github.com/axemclion/grunt-saucelabs/issues/84 +QUnit.done(function (details) { + window.global_test_results = details; +}); + + +var lifecycle = { + teardown: function () { + $.cookie.defaults = {}; + delete $.cookie.raw; + delete $.cookie.json; + $.each($.cookie(), $.removeCookie); + } +}; + + +module('read', lifecycle); + +test('simple value', function () { + expect(1); + document.cookie = 'c=v'; + strictEqual($.cookie('c'), 'v', 'should return value'); +}); + +test('empty value', function () { + expect(1); + // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, which + // resulted in a bug while reading such a cookie. + $.cookie('c', ''); + strictEqual($.cookie('c'), '', 'should return value'); +}); + +test('not existing', function () { + expect(1); + strictEqual($.cookie('whatever'), undefined, 'return undefined'); +}); + +test('RFC 2068 quoted string', function () { + expect(1); + document.cookie = 'c="v@address.com\\"\\\\\\""'; + strictEqual($.cookie('c'), 'v@address.com"\\"', 'should decode RFC 2068 quoted string'); +}); + +test('decode', function () { + expect(1); + document.cookie = encodeURIComponent(' c') + '=' + encodeURIComponent(' v'); + strictEqual($.cookie(' c'), ' v', 'should decode key and value'); +}); + +test('decode pluses to space for server side written cookie', function () { + expect(1); + document.cookie = 'c=foo+bar'; + strictEqual($.cookie('c'), 'foo bar', 'should convert pluses back to space'); +}); + +test('raw = true', function () { + expect(2); + $.cookie.raw = true; + + document.cookie = 'c=%20v'; + strictEqual($.cookie('c'), '%20v', 'should not decode value'); + + // see https://github.com/carhartl/jquery-cookie/issues/50 + $.cookie('c', 'foo=bar'); + strictEqual($.cookie('c'), 'foo=bar', 'should include the entire value'); +}); + +test('json = true', function () { + expect(1); + + if ('JSON' in window) { + $.cookie.json = true; + $.cookie('c', { foo: 'bar' }); + deepEqual($.cookie('c'), { foo: 'bar' }, 'should parse JSON'); + } else { + ok(true); + } +}); + +test('not existing with json = true', function () { + expect(1); + + if ('JSON' in window) { + $.cookie.json = true; + strictEqual($.cookie('whatever'), undefined, "won't throw exception"); + } else { + ok(true); + } +}); + +test('string with json = true', function () { + expect(1); + + if ('JSON' in window) { + $.cookie.json = true; + $.cookie('c', 'v'); + strictEqual($.cookie('c'), 'v', 'should return value'); + } else { + ok(true); + } +}); + +test('invalid JSON string with json = true', function () { + expect(1); + + if ('JSON' in window) { + $.cookie('c', 'v'); + $.cookie.json = true; + strictEqual($.cookie('c'), undefined, "won't throw exception, returns undefined"); + } else { + ok(true); + } +}); + +test('invalid URL encoding', function () { + expect(1); + document.cookie = 'bad=foo%'; + strictEqual($.cookie('bad'), undefined, "won't throw exception, returns undefined"); + // Delete manually here because it requires raw === true... + $.cookie.raw = true; + $.removeCookie('bad'); +}); + +asyncTest('malformed cookie value in IE (#88, #117)', function () { + expect(1); + // Sandbox in an iframe so that we can poke around with document.cookie. + var iframe = $('')[0]; + $(iframe).on('load', function () { + start(); + if (iframe.contentWindow.ok) { + strictEqual(iframe.contentWindow.testValue, 'two', 'reads all cookie values, skipping duplicate occurences of "; "'); + } else { + // Skip the test where we can't stub document.cookie using + // Object.defineProperty. Seems to work fine in + // Chrome, Firefox and IE 8+. + ok(true, 'N/A'); + } + }); + document.body.appendChild(iframe); +}); + +test('Call to read all when there are cookies', function () { + $.cookie('c', 'v'); + $.cookie('foo', 'bar'); + deepEqual($.cookie(), { c: 'v', foo: 'bar' }, 'returns object containing all cookies'); +}); + +test('Call to read all when there are no cookies at all', function () { + deepEqual($.cookie(), {}, 'returns empty object'); +}); + +test('Call to read all with json: true', function () { + $.cookie.json = true; + $.cookie('c', { foo: 'bar' }); + deepEqual($.cookie(), { c: { foo: 'bar' } }, 'returns JSON parsed cookies'); +}); + +test('Call to read all with a badly encoded cookie', function () { + expect(1); + document.cookie = 'bad=foo%'; + document.cookie = 'good=foo'; + deepEqual($.cookie(), { good: 'foo' }, 'returns object containing all decodable cookies'); + // Delete manually here because it requires raw === true... + $.cookie.raw = true; + $.removeCookie('bad'); +}); + + +module('write', lifecycle); + +test('String primitive', function () { + expect(1); + $.cookie('c', 'v'); + strictEqual($.cookie('c'), 'v', 'should write value'); +}); + +test('String object', function () { + expect(1); + $.cookie('c', new String('v')); + strictEqual($.cookie('c'), 'v', 'should write value'); +}); + +test('value "[object Object]"', function () { + expect(1); + $.cookie('c', '[object Object]'); + strictEqual($.cookie('c'), '[object Object]', 'should write value'); +}); + +test('number', function () { + expect(1); + $.cookie('c', 1234); + strictEqual($.cookie('c'), '1234', 'should write value'); +}); + +test('null', function () { + expect(1); + $.cookie('c', null); + strictEqual($.cookie('c'), 'null', 'should write value'); +}); + +test('undefined', function () { + expect(1); + $.cookie('c', undefined); + strictEqual($.cookie('c'), 'undefined', 'should write value'); +}); + +test('expires option as days from now', function () { + expect(1); + var sevenDaysFromNow = new Date(); + sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 21); + strictEqual($.cookie('c', 'v', { expires: 21 }), 'c=v; expires=' + sevenDaysFromNow.toUTCString(), + 'should write the cookie string with expires'); +}); + +test('expires option as fraction of a day', function () { + expect(1); + + var now = new Date().getTime(); + var expires = Date.parse($.cookie('c', 'v', { expires: 0.5 }).replace(/.+expires=/, '')); + + // When we were using Date.setDate() fractions have been ignored + // and expires resulted in the current date. Allow 1000 milliseconds + // difference for execution time. + ok(expires > now + 1000, 'should write expires attribute with the correct date'); +}); + +test('expires option as Date instance', function () { + expect(1); + var sevenDaysFromNow = new Date(); + sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7); + strictEqual($.cookie('c', 'v', { expires: sevenDaysFromNow }), 'c=v; expires=' + sevenDaysFromNow.toUTCString(), + 'should write the cookie string with expires'); +}); + +test('return value', function () { + expect(1); + strictEqual($.cookie('c', 'v'), 'c=v', 'should return written cookie string'); +}); + +test('defaults', function () { + expect(2); + $.cookie.defaults.path = '/foo'; + ok($.cookie('c', 'v').match(/path=\/foo/), 'should use options from defaults'); + ok($.cookie('c', 'v', { path: '/bar' }).match(/path=\/bar/), 'options argument has precedence'); +}); + +test('raw = true', function () { + expect(1); + $.cookie.raw = true; + strictEqual($.cookie('c[1]', 'v[1]'), 'c[1]=v[1]', 'should not encode'); + // Delete manually here because it requires raw === true... + $.removeCookie('c[1]'); +}); + +test('json = true', function () { + expect(1); + $.cookie.json = true; + + if ('JSON' in window) { + $.cookie('c', { foo: 'bar' }); + strictEqual(document.cookie, 'c=' + encodeURIComponent(JSON.stringify({ foo: 'bar' })), 'should stringify JSON'); + } else { + ok(true); + } +}); + + +module('removeCookie', lifecycle); + +test('deletion', function () { + expect(1); + $.cookie('c', 'v'); + $.removeCookie('c'); + strictEqual(document.cookie, '', 'should delete the cookie'); +}); + +test('when sucessfully deleted', function () { + expect(1); + $.cookie('c', 'v'); + strictEqual($.removeCookie('c'), true, 'returns true'); +}); + +test('when cookie does not exist', function () { + expect(1); + strictEqual($.removeCookie('c'), true, 'returns true'); +}); + +test('when deletion failed', function () { + expect(1); + $.cookie('c', 'v'); + + var originalCookie = $.cookie; + $.cookie = function () { + // Stub deletion... + if (arguments.length === 1) { + return originalCookie.apply(null, arguments); + } + }; + + strictEqual($.removeCookie('c'), false, 'returns false'); + + $.cookie = originalCookie; +}); + +test('with options', function () { + expect(1); + var options = { path: '/' }; + $.cookie('c', 'v', options); + $.removeCookie('c', options); + strictEqual(document.cookie, '', 'should delete the cookie'); +}); + +test('passing options reference', function () { + expect(1); + var options = { path: '/' }; + $.cookie('c', 'v', options); + $.removeCookie('c', options); + deepEqual(options, { path: '/' }, "won't alter options object"); +}); + +test('[] used in name', function () { + expect(1); + $.cookie.raw = true; + document.cookie = 'c[1]=foo'; + $.removeCookie('c[1]'); + strictEqual(document.cookie, '', 'delete the cookie'); +}); + + +module('conversion', lifecycle); + +test('read converter', function() { + expect(1); + $.cookie('c', '1'); + strictEqual($.cookie('c', Number), 1, 'converts read value'); +}); + +test('read converter with raw = true', function() { + expect(1); + $.cookie.raw = true; + $.cookie('c', '1'); + strictEqual($.cookie('c', Number), 1, 'does not decode, but converts read value'); +});