├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── MIT-LICENSE.txt ├── README.md ├── bower.json ├── package-lock.json ├── package.json ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── jscookie │ └── javacookie │ ├── AttributesDefinition.java │ ├── ConverterException.java │ ├── ConverterStrategy.java │ ├── CookieParseException.java │ ├── CookieSerializationException.java │ ├── CookieValue.java │ ├── Cookies.java │ ├── CookiesDefinition.java │ └── Expiration.java └── test ├── java └── com │ └── github │ └── jscookie │ └── javacookie │ └── test │ ├── integration │ ├── encoding │ │ ├── CookiesEncodingIT.java │ │ ├── EncodingPageObject.java │ │ └── EncodingServlet.java │ ├── qunit │ │ ├── QUnitPageObject.java │ │ └── QUnitResults.java │ └── test │ │ └── utils │ │ └── Debug.java │ └── unit │ ├── CookiesConverterTest.java │ ├── CookiesDecodingTest.java │ ├── CookiesEncodingTest.java │ ├── CookiesJSONReadTest.java │ ├── CookiesJSONWriteTest.java │ ├── CookiesReadTest.java │ ├── CookiesWriteTest.java │ └── utils │ ├── BaseTest.java │ └── IntegrationUtils.java └── resources ├── arquillian.xml └── web.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /node 3 | /node_modules 4 | /bower_components 5 | /release.properties 6 | /pom.xml.releaseBackup 7 | *.iml 8 | .idea 9 | .vscode 10 | .settings 11 | .classpath 12 | .project 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk11 5 | - oraclejdk12 6 | - oraclejdk13 7 | - oraclejdk14 8 | - openjdk8 9 | - openjdk9 10 | - openjdk10 11 | - openjdk11 12 | - openjdk12 13 | - openjdk13 14 | - openjdk14 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Issues 2 | 3 | - Report issues or feature requests on [GitHub Issues](https://github.com/js-cookie/java-cookie/issues). 4 | - If reporting a bug, please add a [simplified example](http://sscce.org/). 5 | 6 | ## Pull requests 7 | 8 | - Create a new topic branch for every separate change you make. 9 | - Create a test case if you are fixing a bug or implementing an important feature. 10 | - Make sure the build runs successfully [(see below)](#development). 11 | 12 | ## Development 13 | 14 | ### Tools 15 | 16 | We use the following tools for development: 17 | 18 | - [Maven](https://maven.apache.org/) for Java Build. 19 | - [NodeJS](https://nodejs.org/en/download/) used for NPM (installed by Maven automatically). 20 | - [NPM](https://www.npmjs.com) used to install Bower (installed by Maven automatically). 21 | - [Bower](https://bower.io) used to get [js-cookie](https://github.com/js-cookie/js-cookie/) for Integration tests (installed by NPM automatically). 22 | 23 | 24 | ### Getting started 25 | 26 | Install [Maven](https://maven.apache.org/download.cgi) and add `mvn` as a global alias to run the `/bin/mvn` command inside Maven folder. 27 | 28 | Browse to the project root directory and run the build: 29 | 30 | $ mvn install 31 | 32 | After the build completes, you should see the following message in the console: 33 | 34 | ---------------------------------------------------------------------------- 35 | BUILD SUCCESS 36 | ---------------------------------------------------------------------------- 37 | 38 | ### Unit tests 39 | 40 | To run the unit tests, execute the following command: 41 | 42 | $ mvn test 43 | 44 | ### Integration tests 45 | 46 | If you want to debug the integration tests in the browser, switch `Debug.FALSE` to `Debug.TRUE` in `CookiesEncodingIT.java` and run the build: 47 | 48 | $ mvn verify 49 | 50 | [Arquillian](http://arquillian.org/) will start the server, [Selenium](http://www.seleniumhq.org/) will run the tests in Firefox, but the build will hang to allow debugging in the browser. 51 | 52 | It uses the [integration hook](https://github.com/js-cookie/js-cookie/blob/master/CONTRIBUTING.md#integration-with-server-side) provided by the project [js-cookie](https://github.com/js-cookie/js-cookie). 53 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014 Fagner Brack 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Cookie [![Build Status](https://travis-ci.org/js-cookie/java-cookie.svg?branch=master)](https://travis-ci.org/js-cookie/java-cookie) 2 | 3 | A simple Java API for handling cookies 4 | 5 | * Supports Java 8+, Servlet 2.2+ 6 | * [Unobtrusive](#json-data-binding) JSON Data Binding support 7 | * [RFC 6265](http://www.rfc-editor.org/rfc/rfc6265.txt) compliant 8 | * Enable [custom decoding](#converter) 9 | 10 | ## Installation 11 | 12 | Include the maven dependency in your `pom.xml`: 13 | 14 | ```xml 15 | 16 | com.github.js-cookie 17 | java-cookie 18 | 0.0.2 19 | 20 | ``` 21 | 22 | If you don't use Maven you can build the artifact from this repository, by installing [git SCM](https://git-scm.com/downloads), [Maven](https://maven.apache.org/download.cgi) and executing the commands below: 23 | 24 | ```shell 25 | $ git clone https://github.com/js-cookie/java-cookie.git 26 | $ cd java-cookie 27 | $ mvn install -P simple 28 | ``` 29 | 30 | The artifact will be created inside the `java-cookie/target` folder. 31 | 32 | ## Basic Usage 33 | 34 | Create a cookie, valid across the entire site 35 | 36 | ```java 37 | Cookies cookies = Cookies.initFromServlet( request, response ); 38 | cookies.set( "name", "value" ); 39 | ``` 40 | 41 | Create a cookie that expires 7 days from now, valid across the entire site: 42 | 43 | ```java 44 | Cookies cookies = Cookies.initFromServlet( request, response ); 45 | cookies.set( "name", "value", Attributes.empty() 46 | .expires( Expiration.days( 7 ) ) 47 | ); 48 | ``` 49 | 50 | Create an expiring cookie, valid to the path of the current page: 51 | 52 | ```java 53 | Cookies cookies = Cookies.initFromServlet( request, response ); 54 | cookies.set( "name", "value", Attributes.empty() 55 | .expires( Expiration.days( 7 ) ) 56 | .path( "" ) 57 | ); 58 | ``` 59 | 60 | Read cookie: 61 | 62 | ```java 63 | Cookies cookies = Cookies.initFromServlet( request, response ); 64 | cookies.get( "name" ); // => "value" 65 | cookies.get( "nothing" ); // => null 66 | ``` 67 | 68 | Read all available cookies: 69 | 70 | ```java 71 | Cookies cookies = Cookies.initFromServlet( request, response ); 72 | Map all = cookies.get(); // => {name=value} 73 | ``` 74 | 75 | Delete cookie: 76 | 77 | ```java 78 | Cookies cookies = Cookies.initFromServlet( request, response ); 79 | cookies.remove( "name" ); 80 | ``` 81 | 82 | Delete a cookie valid to the path of the current page: 83 | 84 | ```java 85 | Cookies cookies = Cookies.initFromServlet( request, response ); 86 | cookies.set( "name", "value", Attributes.empty() 87 | .path( "" ) 88 | ); 89 | cookies.remove( "name" ); // fail! 90 | cookies.remove( "name", Attributes.empty().path( "path" ) ); // removed! 91 | ``` 92 | 93 | *IMPORTANT! when deleting a cookie, you must pass the exact same path, domain and secure attributes that were used to set the cookie, unless you're relying on the [default attributes](#cookie-attributes).* 94 | 95 | ## JSON Data Binding 96 | 97 | java-cookie provides unobtrusive JSON storage for cookies with data binding. 98 | 99 | When creating a cookie, you can pass a few supported types instead of String in the value. If you do so, java-cookie will store the stringified JSON representation of the value using [jackson databind](https://github.com/FasterXML/jackson-databind/#use-it). 100 | 101 | Consider the following class that implements the `CookieValue` interface: 102 | 103 | ```java 104 | public class Person implements CookieValue { 105 | private int age; 106 | public Person( int age ) { 107 | this.age = age; 108 | } 109 | public int getAge() { 110 | return age; 111 | } 112 | } 113 | ``` 114 | 115 | And the following usage: 116 | 117 | ```java 118 | Cookies cookies = Cookies.initFromServlet( request, response ); 119 | cookies.set( "name", new Person( 25 ) ); 120 | ``` 121 | 122 | When reading a cookie with the default `get()` api, you receive the string representation stored in the cookie: 123 | 124 | ```java 125 | Cookies cookies = Cookies.initFromServlet( request, response ); 126 | String value = cookies.get( "name" ); // => "{\"age\":25}" 127 | ``` 128 | 129 | If you pass the type reference, it will parse the JSON into a new instance: 130 | 131 | ```java 132 | Cookies cookies = Cookies.initFromServlet( request, response ); 133 | Person adult = cookies.get( "name", Person.class ); 134 | if ( adult != null ) { 135 | adult.getAge(); // => 25 136 | } 137 | ``` 138 | 139 | ## Encoding 140 | 141 | This project is [RFC 6265](http://tools.ietf.org/html/rfc6265#section-4.1.1) compliant. All special characters that are not allowed in the cookie-name or cookie-value are encoded with each one's UTF-8 Hex equivalent using [percent-encoding](http://en.wikipedia.org/wiki/Percent-encoding). 142 | The only character in cookie-name or cookie-value that is allowed and still encoded is the percent `%` character, it is escaped in order to interpret percent input as literal. 143 | To override the default cookie decoding you need to use a [converter](#converter). 144 | 145 | ## Cookie Attributes 146 | 147 | The default cookie attributes can be set globally by setting properties of the `.defaults()` instance or individually for each call to `.set(...)` by passing an `Attributes` instance in the last argument. Per-call attributes override the default attributes. 148 | 149 | ```java 150 | Cookies cookies = Cookies.initFromServlet( request, response ); 151 | cookies.defaults() 152 | .secure( true ) 153 | .httpOnly( true ); 154 | cookies.set( "name", "value", Attributes.empty() 155 | .httpOnly( false ) // override defaults 156 | ); 157 | ``` 158 | 159 | ### expires 160 | 161 | Define when the cookie will be removed. Value can be an `Expiration.days()` which will be interpreted as days from time of creation, a `java.util.Date` or an `org.joda.time.DateTime` instance. If omitted, the cookie becomes a session cookie. 162 | 163 | **Default:** Cookie is removed when the user closes the browser. 164 | 165 | **Examples:** 166 | 167 | ```java 168 | DateTime date_2015_06_07_23h38m46s = new DateTime( 2015, 6, 7, 23, 38, 46 ); 169 | Cookies cookies = Cookies.initFromServlet( request, response ); 170 | cookies.set( "name", "value", Attributes.empty() 171 | .expires( Expiration.date( date_2015_06_07_23h38m46s ) ) 172 | ); 173 | cookies.get( "name" ); // => "value" 174 | cookies.remove( "name" ); 175 | ``` 176 | 177 | ### path 178 | 179 | Define the path where the cookie is available. 180 | 181 | **Default:** `/` 182 | 183 | **Examples:** 184 | 185 | ```java 186 | Cookies cookies = Cookies.initFromServlet( request, response ); 187 | Attributes validToTheCurrentPage = Attributes.empty().path( "" ); 188 | cookies.set( "name", "value", validToTheCurrentPath ); 189 | cookies.get( "name" ); // => "value" 190 | cookies.remove( "name", validToTheCurrentPath ); 191 | ``` 192 | 193 | ### domain 194 | 195 | Define the domain where the cookie is available 196 | 197 | **Default:** Domain of the page where the cookie was created 198 | 199 | **Examples:** 200 | 201 | ```java 202 | Cookies cookies = Cookies.initFromServlet( request, response ); 203 | cookies.set( "name", "value", Attributes.empty().domain( "sub.domain.com" ) ); 204 | cookies.get( "name" ); // => null (need to read at "sub.domain.com") 205 | ``` 206 | 207 | ### secure 208 | 209 | A `Boolean` indicating if the cookie transmission requires a secure protocol (https) 210 | 211 | **Default:** No secure protocol requirement 212 | 213 | **Examples:** 214 | 215 | ```java 216 | Cookies cookies = Cookies.initFromServlet( request, response ); 217 | Attributes secureCookie = Attributes.empty().secure( true ); 218 | cookies.set( "name", "value", secureCookie ); 219 | cookies.get( "name" ); // => "value" 220 | cookies.remove( "name", secureCookie ); 221 | ``` 222 | 223 | ### httpOnly 224 | 225 | A `Boolean` indicating if the cookie should be restricted to be manipulated only in the server. 226 | 227 | **Default:** The cookie can be manipulated in the server and in the client 228 | 229 | **Examples:** 230 | 231 | ```java 232 | Cookies cookies = Cookies.initFromServlet( request, response ); 233 | Attributes httpOnlyCookie = Attributes.empty().httpOnly( true ); 234 | cookies.set( "name", "value", httpOnlyCookie ); 235 | cookies.get( "name" ); // => "value" 236 | cookies.remove( "name", httpOnlyCookie ); 237 | ``` 238 | 239 | ### sameSite 240 | 241 | Define whether your cookie should be restricted to a first party or same-site context 242 | 243 | **Default:** not set 244 | 245 | Note that more recent browsers are making "Lax" the default value even without specifying anything here. 246 | 247 | **Examples:** 248 | 249 | ```java 250 | Cookies cookies = Cookies.initFromServlet( request, response ); 251 | cookies.set( "name", "value", Attributes.empty().sameSite( "Lax" ) ); 252 | cookies.get( "name" ); // => "value" 253 | ``` 254 | 255 | ## Converter 256 | 257 | Create a new instance of the api that overrides the default decoding implementation. 258 | All methods that rely in a proper decoding to work, such as `remove()` and `get()`, will run the converter first for each cookie. 259 | The returning String will be used as the cookie value. 260 | 261 | Example from reading one of the cookies that can only be decoded using the Javascript `escape` function: 262 | 263 | ``` java 264 | // document.cookie = 'escaped=%u5317'; 265 | // document.cookie = 'default=%E5%8C%97'; 266 | 267 | Cookies cookies = Cookies.initFromServlet( request, response ); 268 | Cookies escapedCookies = cookies.withConverter(new Cookies.Converter() { 269 | @Override 270 | public String convert( String value, String name ) throws ConverterException { 271 | ScriptEngine javascript = new ScriptEngineManager().getEngineByName( "JavaScript" ); 272 | if ( name.equals( "escaped" ) ) { 273 | try { 274 | return javascript.eval( "unescape('" + value + "')" ).toString(); 275 | } catch ( ScriptException e ) { 276 | throw new ConverterException( e ); 277 | } 278 | } 279 | return null; 280 | } 281 | }); 282 | 283 | escapedCookies.get( "escaped" ); // => 北 284 | escapedCookies.get( "default" ); // => 北 285 | escapedCookies.get(); // => {escaped=北, default=北} 286 | ``` 287 | 288 | Instead of passing a converter inline, you can also create a custom strategy by implementing the `ConverterStrategy` interface: 289 | 290 | ```java 291 | class CustomConverter implements ConverterStrategy { 292 | @Override 293 | public String convert( String value, String name ) throws ConverterException { 294 | return value; 295 | } 296 | } 297 | ``` 298 | 299 | ```java 300 | Cookies cookies = Cookies.initFromServlet( request, response ); 301 | Cookies cookiesWithCustomConverter = cookies.withConverter( new CustomConverter() ); 302 | ``` 303 | 304 | ## Contributing 305 | 306 | Check out the [Contributing Guidelines](CONTRIBUTING.md). 307 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "java-cookie", 3 | "devDependencies": { 4 | "js-cookie": "2.0.3" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "java-cookie", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bower": { 8 | "version": "1.8.8", 9 | "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.8.tgz", 10 | "integrity": "sha512-1SrJnXnkP9soITHptSO+ahx3QKp3cVzn8poI6ujqc5SeOkg5iqM1pK9H+DSc2OQ8SnO0jC/NG4Ur/UIwy7574A==", 11 | "dev": true 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "java-cookie", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/js-cookie/java-cookie.git" 8 | }, 9 | "devDependencies": { 10 | "bower": "1.8.8" 11 | }, 12 | "scripts": { 13 | "test": "cd bower_components/js-cookie && ../../node/node \"../../node/node_modules/npm/bin/npm-cli.js\" install" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.github.js-cookie 5 | java-cookie 6 | 0.1.0 7 | Java Cookie 8 | A simple Servlet API for handling cookies 9 | https://github.com/js-cookie/java-cookie 10 | 11 | UTF-8 12 | UTF-8 13 | 2.45.0 14 | 20.0.1.Final 15 | java-cookie-scm 16 | 1.8 17 | 18 | 19 | 20 | The MIT License (MIT) 21 | http://opensource.org/licenses/MIT 22 | repo 23 | 24 | 25 | 26 | 27 | Fagner Brack 28 | github@fagnermartins.com 29 | js-cookie 30 | https://github.com/js-cookie 31 | 32 | 33 | 34 | https://github.com/js-cookie/java-cookie 35 | scm:git:https://github.com/js-cookie/java-cookie.git 36 | scm:git:https://github.com/js-cookie/java-cookie.git 37 | v0.1.0 38 | 39 | 40 | 41 | RedHat GA 42 | https://maven.repository.redhat.com/ga/ 43 | 44 | 45 | 46 | 47 | 48 | maven-compiler-plugin 49 | 3.8.1 50 | 51 | ${java.version} 52 | ${java.version} 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-release-plugin 58 | 3.0.0-M1 59 | 60 | false 61 | release 62 | v@{project.version} 63 | deploy 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.jboss.arquillian 72 | arquillian-bom 73 | 1.6.0.Final 74 | test 75 | pom 76 | 77 | 78 | 79 | 80 | 81 | javax.servlet 82 | servlet-api 83 | 2.2 84 | provided 85 | 86 | 87 | joda-time 88 | joda-time 89 | 2.10.6 90 | 91 | 92 | com.fasterxml.jackson.core 93 | jackson-databind 94 | 2.11.2 95 | 96 | 97 | junit 98 | junit 99 | 4.13.1 100 | test 101 | 102 | 103 | org.mockito 104 | mockito-all 105 | 2.0.2-beta 106 | test 107 | 108 | 109 | org.seleniumhq.selenium 110 | selenium-java 111 | ${selenium.version} 112 | test 113 | 114 | 115 | org.seleniumhq.selenium 116 | selenium-firefox-driver 117 | ${selenium.version} 118 | test 119 | 120 | 121 | org.jboss.arquillian.protocol 122 | arquillian-protocol-servlet 123 | 1.6.0.Final 124 | test 125 | 126 | 127 | org.jboss.arquillian.junit 128 | arquillian-junit-container 129 | 1.6.0.Final 130 | test 131 | 132 | 133 | org.wildfly.arquillian 134 | wildfly-arquillian-container-managed 135 | 2.2.0.Final 136 | test 137 | 138 | 139 | org.jboss.shrinkwrap.resolver 140 | shrinkwrap-resolver-impl-maven 141 | 3.1.4 142 | test 143 | 144 | 145 | org.jboss.shrinkwrap.resolver 146 | shrinkwrap-resolver-spi 147 | 3.1.4 148 | test 149 | 150 | 151 | org.apache.httpcomponents 152 | httpclient 153 | 4.5.13 154 | test 155 | 156 | 157 | org.apache.httpcomponents 158 | fluent-hc 159 | 4.5.12 160 | test 161 | 162 | 163 | 164 | 165 | test 166 | 167 | true 168 | 169 | 170 | 171 | 172 | maven-dependency-plugin 173 | 174 | 175 | unpack 176 | process-test-classes 177 | 178 | unpack 179 | 180 | 181 | 182 | 183 | org.wildfly 184 | wildfly-dist 185 | ${wildfly.version} 186 | zip 187 | false 188 | target 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | org.apache.maven.plugins 197 | maven-failsafe-plugin 198 | 2.22.2 199 | 200 | 201 | 202 | integration-test 203 | verify 204 | 205 | 206 | 207 | 208 | 209 | com.github.eirslett 210 | frontend-maven-plugin 211 | 1.10.0 212 | 213 | 214 | install node and npm 215 | 216 | install-node-and-npm 217 | 218 | 219 | v12.18.2 220 | 221 | 222 | 223 | 224 | npm install 225 | 226 | npm 227 | 228 | 229 | 230 | bower install 231 | 232 | bower 233 | 234 | 235 | 236 | npm test 237 | 238 | npm 239 | 240 | 241 | test 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | release 251 | 252 | 253 | ossrh 254 | https://oss.sonatype.org/content/repositories/snapshots 255 | 256 | 257 | ossrh 258 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 259 | 260 | 261 | 262 | 263 | 264 | org.apache.maven.plugins 265 | maven-javadoc-plugin 266 | 3.2.0 267 | 268 | 269 | attach-javadocs 270 | 271 | jar 272 | 273 | 274 | 275 | 276 | 277 | org.apache.maven.plugins 278 | maven-source-plugin 279 | 3.2.1 280 | 281 | 282 | attach-sources 283 | 284 | jar-no-fork 285 | 286 | 287 | 288 | 289 | 290 | org.sonatype.plugins 291 | nexus-staging-maven-plugin 292 | 1.6.8 293 | true 294 | 295 | ossrh 296 | https://oss.sonatype.org/ 297 | true 298 | 299 | 300 | 301 | org.apache.maven.plugins 302 | maven-gpg-plugin 303 | 1.5 304 | 305 | 306 | sign-artifacts 307 | verify 308 | 309 | sign 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | simple 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /src/main/java/com/github/jscookie/javacookie/AttributesDefinition.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie; 2 | 3 | public abstract class AttributesDefinition { 4 | public abstract AttributesDefinition expires( Expiration expiration ); 5 | abstract Expiration expires(); 6 | public abstract AttributesDefinition path( String path ); 7 | abstract String path(); 8 | public abstract AttributesDefinition domain( String domain ); 9 | abstract String domain(); 10 | public abstract AttributesDefinition secure( Boolean secure ); 11 | abstract Boolean secure(); 12 | public abstract AttributesDefinition httpOnly( Boolean httpOnly ); 13 | abstract Boolean httpOnly(); 14 | public abstract AttributesDefinition sameSite( String sameSite ); 15 | abstract String sameSite(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/jscookie/javacookie/ConverterException.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie; 2 | 3 | public final class ConverterException extends Exception { 4 | private static final long serialVersionUID = 1; 5 | public ConverterException( Throwable cause ) { 6 | super( cause ); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/github/jscookie/javacookie/ConverterStrategy.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie; 2 | 3 | public interface ConverterStrategy { 4 | /** 5 | * Apply the decoding strategy of a cookie. The return will be used as the cookie value 6 | * 7 | * @return null if the default encoding mechanism should be used instead 8 | */ 9 | public abstract String convert( String value, String name ) throws ConverterException; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/github/jscookie/javacookie/CookieParseException.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie; 2 | 3 | public final class CookieParseException extends Exception { 4 | private static final long serialVersionUID = 1; 5 | @SuppressWarnings( "unused" ) 6 | private CookieParseException() {} 7 | CookieParseException( Throwable cause ) { 8 | super( cause ); 9 | } 10 | CookieParseException( String msg ) { 11 | super( msg ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/jscookie/javacookie/CookieSerializationException.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie; 2 | 3 | public class CookieSerializationException extends Exception { 4 | private static final long serialVersionUID = 1; 5 | @SuppressWarnings( "unused" ) 6 | private CookieSerializationException() {} 7 | CookieSerializationException( Throwable cause ) { 8 | super( cause ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/github/jscookie/javacookie/CookieValue.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie; 2 | 3 | /** 4 | * Use this interface to mark that a given class can be used as a cookie value and 5 | * serialized/deserialized to/from JSON 6 | */ 7 | public interface CookieValue {} 8 | -------------------------------------------------------------------------------- /src/main/java/com/github/jscookie/javacookie/Cookies.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie; 2 | 3 | import java.io.CharArrayWriter; 4 | import java.io.IOException; 5 | import java.io.UnsupportedEncodingException; 6 | import java.util.HashMap; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.ResourceBundle; 11 | import java.util.Set; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | 18 | import com.fasterxml.jackson.core.JsonProcessingException; 19 | import com.fasterxml.jackson.core.type.TypeReference; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | 22 | public final class Cookies implements CookiesDefinition { 23 | private static String UTF_8 = "UTF-8"; 24 | private HttpServletRequest request; 25 | private HttpServletResponse response; 26 | private AttributesDefinition defaults = Attributes.empty(); 27 | private ConverterStrategy converter; 28 | private ObjectMapper mapper = new ObjectMapper(); 29 | 30 | private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings"; 31 | private static ResourceBundle lStrings = ResourceBundle.getBundle( LSTRING_FILE ); 32 | 33 | private Cookies( HttpServletRequest request, HttpServletResponse response, ConverterStrategy converter ) { 34 | this.request = request; 35 | this.response = response; 36 | this.converter = converter; 37 | } 38 | 39 | public static Cookies initFromServlet( HttpServletRequest request, HttpServletResponse response ) { 40 | return new Cookies( request, response, null ); 41 | } 42 | 43 | @Override 44 | public synchronized String get( String name ) { 45 | if ( name == null || name.length() == 0 ) { 46 | throw new IllegalArgumentException( lStrings.getString( "err.cookie_name_blank" ) ); 47 | } 48 | 49 | String cookieHeader = request.getHeader( "cookie" ); 50 | if ( cookieHeader == null ) { 51 | return null; 52 | } 53 | 54 | Map cookies = getCookies( cookieHeader ); 55 | for ( String decodedName : cookies.keySet() ) { 56 | if ( !name.equals( decodedName ) ) { 57 | continue; 58 | } 59 | return cookies.get( decodedName ); 60 | } 61 | 62 | return null; 63 | } 64 | 65 | @Override 66 | public T get( String name, Class dataType ) throws CookieParseException { 67 | String value = get( name ); 68 | if ( value == null ) { 69 | return null; 70 | } 71 | try { 72 | return mapper.readValue( value, dataType ); 73 | } catch ( IOException e ) { 74 | throw new CookieParseException( e ); 75 | } 76 | } 77 | 78 | @Override 79 | public T get( String name, TypeReference typeRef ) throws CookieParseException { 80 | String value = get( name ); 81 | if ( value == null ) { 82 | return null; 83 | } 84 | try { 85 | return mapper.readValue( value, typeRef ); 86 | } catch ( IOException e ) { 87 | throw new CookieParseException( e ); 88 | } 89 | } 90 | 91 | @Override 92 | public Map get() { 93 | Map result = new HashMap(); 94 | 95 | String cookieHeader = request.getHeader( "cookie" ); 96 | if ( cookieHeader == null ) { 97 | return result; 98 | } 99 | 100 | return getCookies( cookieHeader ); 101 | } 102 | 103 | @Override 104 | public synchronized void set( String name, String value, AttributesDefinition attributes ) { 105 | if ( name == null || name.length() == 0 ) { 106 | throw new IllegalArgumentException( lStrings.getString( "err.cookie_name_blank" ) ); 107 | } 108 | if ( value == null ) { 109 | throw new IllegalArgumentException(); 110 | } 111 | if ( attributes == null ) { 112 | throw new IllegalArgumentException(); 113 | } 114 | 115 | String encodedName = encode( name ); 116 | String encodedValue = encodeValue( value ); 117 | 118 | StringBuilder header = new StringBuilder(); 119 | header.append( encodedName ); 120 | header.append( '=' ); 121 | header.append( encodedValue ); 122 | 123 | attributes = extend( Attributes.empty().path( "/" ), defaults, attributes ); 124 | 125 | String path = attributes.path(); 126 | if ( path != null && !path.isEmpty() ) { 127 | header.append( "; Path=" + path ); 128 | } 129 | 130 | Expiration expires = attributes.expires(); 131 | if ( expires != null ) { 132 | header.append( "; Expires=" + expires.toExpiresString() ); 133 | } 134 | 135 | String domain = attributes.domain(); 136 | if ( domain != null ) { 137 | header.append( "; Domain=" + domain ); 138 | } 139 | 140 | Boolean secure = attributes.secure(); 141 | if ( Boolean.TRUE.equals( secure ) ) { 142 | header.append( "; Secure" ); 143 | } 144 | 145 | Boolean httpOnly = attributes.httpOnly(); 146 | if ( Boolean.TRUE.equals( httpOnly ) ) { 147 | header.append( "; HttpOnly" ); 148 | } 149 | 150 | String sameSite = attributes.sameSite(); 151 | if ( sameSite != null ) { 152 | header.append( "; SameSite=" + sameSite ); 153 | } 154 | 155 | if ( response.isCommitted() ) { 156 | return; 157 | } 158 | 159 | setCookie( header.toString(), response ); 160 | } 161 | 162 | @Override 163 | public void set( String name, int value, AttributesDefinition attributes ) throws CookieSerializationException { 164 | set( name, String.valueOf( value ), attributes ); 165 | } 166 | 167 | @Override 168 | public void set( String name, boolean value, AttributesDefinition attributes ) throws CookieSerializationException { 169 | set( name, String.valueOf( value ), attributes ); 170 | } 171 | 172 | @Override 173 | public void set( String name, List value, AttributesDefinition attributes ) throws CookieSerializationException { 174 | try { 175 | set( name, mapper.writeValueAsString( value ), attributes ); 176 | } catch ( JsonProcessingException e ) { 177 | throw new CookieSerializationException( e ); 178 | } 179 | } 180 | 181 | @Override 182 | public void set( String name, CookieValue value, AttributesDefinition attributes ) throws CookieSerializationException { 183 | try { 184 | set( name, mapper.writeValueAsString( value ), attributes ); 185 | } catch ( JsonProcessingException e ) { 186 | throw new CookieSerializationException( e ); 187 | } 188 | } 189 | 190 | @Override 191 | public void set( String name, String value ) { 192 | if ( name == null || name.length() == 0 ) { 193 | throw new IllegalArgumentException( lStrings.getString( "err.cookie_name_blank" ) ); 194 | } 195 | if ( value == null ) { 196 | throw new IllegalArgumentException(); 197 | } 198 | set( name, value, defaults ); 199 | } 200 | 201 | @Override 202 | public void set( String name, int value ) throws CookieSerializationException { 203 | set( name, value, Attributes.empty() ); 204 | } 205 | 206 | @Override 207 | public void set( String name, boolean value ) { 208 | set( name, String.valueOf( value ) ); 209 | } 210 | 211 | @Override 212 | public void set( String name, List value ) throws CookieSerializationException { 213 | set( name, value, Attributes.empty() ); 214 | } 215 | 216 | @Override 217 | public void set( String name, CookieValue value ) throws CookieSerializationException { 218 | set( name, value, Attributes.empty() ); 219 | } 220 | 221 | @Override 222 | public void remove( String name, AttributesDefinition attributes ) { 223 | if ( name == null || name.length() == 0 ) { 224 | throw new IllegalArgumentException( lStrings.getString( "err.cookie_name_blank" ) ); 225 | } 226 | if ( attributes == null ) { 227 | throw new IllegalArgumentException(); 228 | } 229 | 230 | set( name, "", extend( attributes, Attributes.empty() 231 | .expires( Expiration.days( -1 ) )) 232 | ); 233 | } 234 | 235 | @Override 236 | public void remove( String name ) { 237 | if ( name == null || name.length() == 0 ) { 238 | throw new IllegalArgumentException( lStrings.getString( "err.cookie_name_blank" ) ); 239 | } 240 | remove( name, Attributes.empty() ); 241 | } 242 | 243 | @Override 244 | public AttributesDefinition defaults() { 245 | return this.defaults; 246 | } 247 | 248 | @Override 249 | public Cookies withConverter( ConverterStrategy converter ) { 250 | return new Cookies( request, response, converter ); 251 | } 252 | 253 | private Attributes extend( AttributesDefinition... mergeables ) { 254 | Attributes result = Attributes.empty(); 255 | for ( AttributesDefinition mergeable : mergeables ) { 256 | result.merge( mergeable ); 257 | } 258 | return result; 259 | } 260 | 261 | private void setCookie( String cookieValue, HttpServletResponse response ) { 262 | response.addHeader( "Set-Cookie", cookieValue ); 263 | } 264 | 265 | private String encode( String decoded ) { 266 | return encode( decoded, new HashSet() ); 267 | } 268 | 269 | private String encode( String decoded, Set exceptions ) { 270 | String encoded = decoded; 271 | for ( int i = 0; i < decoded.length(); ) { 272 | int codePoint = decoded.codePointAt( i ); 273 | i += Character.charCount( codePoint ); 274 | 275 | boolean isDigit = codePoint >= codePoint( "0" ) && codePoint <= codePoint( "9" ); 276 | if ( isDigit ) { 277 | continue; 278 | } 279 | 280 | boolean isAsciiUppercaseLetter = codePoint >= codePoint( "A" ) && codePoint <= codePoint( "Z" ); 281 | if ( isAsciiUppercaseLetter ) { 282 | continue; 283 | } 284 | 285 | boolean isAsciiLowercaseLetter = codePoint >= codePoint( "a" ) && codePoint <= codePoint( "z" ); 286 | if ( isAsciiLowercaseLetter ) { 287 | continue; 288 | } 289 | 290 | boolean isAllowed = 291 | codePoint == codePoint( "!" ) || codePoint == codePoint( "#" ) || 292 | codePoint == codePoint( "$" ) || codePoint == codePoint( "&" ) || 293 | codePoint == codePoint( "'" ) || codePoint == codePoint( "*" ) || 294 | codePoint == codePoint( "+" ) || codePoint == codePoint( "-" ) || 295 | codePoint == codePoint( "." ) || codePoint == codePoint( "^" ) || 296 | codePoint == codePoint( "_" ) || codePoint == codePoint( "`" ) || 297 | codePoint == codePoint( "|" ) || codePoint == codePoint( "~" ); 298 | if ( isAllowed ) { 299 | continue; 300 | } 301 | 302 | if ( exceptions.contains( codePoint ) ) { 303 | continue; 304 | } 305 | 306 | try { 307 | String character = new String( Character.toChars( codePoint ) ); 308 | CharArrayWriter hexSequence = new CharArrayWriter(); 309 | byte[] bytes = character.getBytes( UTF_8 ); 310 | for ( int bytesIndex = 0; bytesIndex < bytes.length; bytesIndex++ ) { 311 | char left = Character.forDigit( bytes[ bytesIndex ] >> 4 & 0xF, 16 ); 312 | char right = Character.forDigit( bytes[ bytesIndex ] & 0xF, 16 ); 313 | hexSequence 314 | .append( '%' ) 315 | .append( left ) 316 | .append( right ); 317 | } 318 | String target = character.toString(); 319 | String sequence = hexSequence.toString().toUpperCase(); 320 | encoded = encoded.replace( target, sequence ); 321 | } catch ( UnsupportedEncodingException e ) { 322 | e.printStackTrace(); 323 | } 324 | } 325 | return encoded; 326 | } 327 | 328 | private String decode( String encoded ) { 329 | String decoded = encoded; 330 | Pattern pattern = Pattern.compile( "(%[0-9A-Z]{2})+" ); 331 | Matcher matcher = pattern.matcher( encoded ); 332 | while ( matcher.find() ) { 333 | String encodedChar = matcher.group(); 334 | String[] encodedBytes = encodedChar.split( "%" ); 335 | byte[] bytes = new byte[ encodedBytes.length - 1 ]; 336 | for ( int i = 1; i < encodedBytes.length; i++ ) { 337 | String encodedByte = encodedBytes[ i ]; 338 | bytes[ i - 1 ] = ( byte )Integer.parseInt( encodedByte, 16 ); 339 | } 340 | try { 341 | String decodedChar = new String( bytes, UTF_8 ); 342 | decoded = decoded.replace( encodedChar, decodedChar ); 343 | } catch ( UnsupportedEncodingException e ) { 344 | e.printStackTrace(); 345 | } 346 | } 347 | return decoded; 348 | } 349 | 350 | private String encodeValue( String decodedValue ) { 351 | Set exceptions = new HashSet(); 352 | for ( int i = 0; i < decodedValue.length(); ) { 353 | int codePoint = decodedValue.codePointAt( i ); 354 | i += Character.charCount( codePoint ); 355 | 356 | boolean isIgnorable = false; 357 | if ( codePoint == codePoint( "/" ) || codePoint == codePoint( ":") ) { 358 | isIgnorable = true; 359 | } 360 | 361 | if ( codePoint >= codePoint( "<" ) && codePoint <= codePoint( "@" ) ) { 362 | isIgnorable = true; 363 | } 364 | 365 | if ( codePoint == codePoint( "[" ) || codePoint == codePoint( "]" ) ) { 366 | isIgnorable = true; 367 | } 368 | 369 | if ( codePoint == codePoint( "{" ) || codePoint == codePoint( "}" ) ) { 370 | isIgnorable = true; 371 | } 372 | 373 | if ( isIgnorable ) { 374 | exceptions.add( codePoint ); 375 | } 376 | } 377 | 378 | return encode( decodedValue, exceptions ); 379 | } 380 | 381 | private int codePoint( String character ) { 382 | return character.codePointAt( 0 ); 383 | } 384 | 385 | private String decodeValue( String encodedValue, String decodedName ) { 386 | String decodedValue = null; 387 | 388 | if ( converter != null ) { 389 | try { 390 | decodedValue = converter.convert( encodedValue, decodedName ); 391 | } catch ( ConverterException e ) { 392 | e.printStackTrace(); 393 | } 394 | } 395 | 396 | if ( decodedValue == null ) { 397 | decodedValue = decode( encodedValue ); 398 | } 399 | 400 | return decodedValue; 401 | } 402 | 403 | private Map getCookies( String cookieHeader ) { 404 | Map result = new HashMap(); 405 | String[] cookies = cookieHeader.split( "; " ); 406 | for ( int i = 0; i < cookies.length; i++ ) { 407 | String cookie = cookies[ i ]; 408 | String encodedName = cookie.split( "=" )[ 0 ]; 409 | String decodedName = decode( encodedName ); 410 | 411 | String encodedValue = cookie.substring( cookie.indexOf( '=' ) + 1, cookie.length() ); 412 | String decodedValue = decodeValue( encodedValue, decodedName ); 413 | result.put( decodedName, decodedValue ); 414 | } 415 | return result; 416 | } 417 | 418 | public static class Attributes extends AttributesDefinition { 419 | private Expiration expires; 420 | private String path; 421 | private String domain; 422 | private Boolean secure; 423 | private Boolean httpOnly; 424 | private String sameSite; 425 | 426 | private Attributes() {} 427 | 428 | public static Attributes empty() { 429 | return new Attributes(); 430 | } 431 | 432 | @Override 433 | Expiration expires() { 434 | return expires; 435 | } 436 | @Override 437 | public Attributes expires( Expiration expires ) { 438 | this.expires = expires; 439 | return this; 440 | } 441 | 442 | @Override 443 | String path() { 444 | return path; 445 | } 446 | @Override 447 | public Attributes path( String path ) { 448 | this.path = path; 449 | return this; 450 | } 451 | 452 | @Override 453 | String domain() { 454 | return domain; 455 | } 456 | @Override 457 | public Attributes domain( String domain ) { 458 | this.domain = domain; 459 | return this; 460 | } 461 | 462 | @Override 463 | Boolean secure() { 464 | return secure; 465 | } 466 | @Override 467 | public Attributes secure( Boolean secure ) { 468 | this.secure = secure; 469 | return this; 470 | } 471 | 472 | @Override 473 | Boolean httpOnly() { 474 | return httpOnly; 475 | } 476 | @Override 477 | public Attributes httpOnly( Boolean httpOnly ) { 478 | this.httpOnly = httpOnly; 479 | return this; 480 | } 481 | 482 | @Override 483 | String sameSite() { 484 | return sameSite; 485 | } 486 | @Override 487 | public Attributes sameSite( String sameSite ) { 488 | this.sameSite = sameSite; 489 | return this; 490 | } 491 | 492 | private Attributes merge( AttributesDefinition reference ) { 493 | if ( reference.path() != null ) { 494 | path = reference.path(); 495 | } 496 | if ( reference.domain() != null ) { 497 | domain = reference.domain(); 498 | } 499 | if ( reference.expires() != null ) { 500 | expires = reference.expires(); 501 | } 502 | if ( reference.secure() != null ) { 503 | secure = reference.secure(); 504 | } 505 | if ( reference.httpOnly() != null ) { 506 | httpOnly = reference.httpOnly(); 507 | } 508 | if ( reference.sameSite() != null ) { 509 | sameSite = reference.sameSite(); 510 | } 511 | return this; 512 | } 513 | } 514 | 515 | public static abstract class Converter implements ConverterStrategy {} 516 | } 517 | -------------------------------------------------------------------------------- /src/main/java/com/github/jscookie/javacookie/CookiesDefinition.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import com.fasterxml.jackson.core.type.TypeReference; 7 | 8 | interface CookiesDefinition { 9 | /** 10 | * Retrieves a cookie
11 | * By default, assumes the characters not allowed in each cookie name are encoded with each 12 | * one's UTF-8 Hex equivalent using percent-encoding. 13 | * 14 | * @return null if the cookie doesn't exist 15 | */ 16 | String get( String name ); 17 | 18 | /** 19 | * Retrieves a cookie and parse it using the given dataType instance 20 | * 21 | * @throws CookieParseException 22 | * If there's an error while parsing the cookie name using the given dataType 23 | */ 24 | T get( String name, Class dataType ) throws CookieParseException; 25 | 26 | /** 27 | * Retrieves a cookie and parse it using the given type reference. The type reference is used 28 | * to infer generic types from within a class and workaround Java Type Erasure.
29 | * 30 | * For more information check http://wiki.fasterxml.com/JacksonDataBinding#Full_Data_Binding 31 | * 32 | * @throws CookieParseException 33 | * If there's an error while parsing the cookie name using the given dataType 34 | * 35 | * @see #get(String, Class) 36 | */ 37 | T get( String name, TypeReference typeRef ) throws CookieParseException; 38 | 39 | /** 40 | * Retrieves all cookies 41 | * 42 | * @see #get(String) 43 | */ 44 | Map get(); 45 | 46 | /** 47 | * Create or update an existing cookie extending the default attributes
48 | * By default, the characters not allowed in the cookie name or value are encoded with each 49 | * one's UTF-8 Hex equivalent using percent-encoding. 50 | * 51 | * @see #get(String) 52 | */ 53 | void set( String name, String value, AttributesDefinition attributes ); 54 | 55 | /** 56 | * Create or update an existing cookie extending the default attributes and serializing the typed value 57 | * 58 | * @see #set(String, String, AttributesDefinition) 59 | */ 60 | void set( String name, int value, AttributesDefinition attributes ) throws CookieSerializationException; 61 | 62 | /** 63 | * Create or update an existing cookie extending the default attributes and serializing the typed value 64 | * 65 | * @see #set(String, String, AttributesDefinition) 66 | */ 67 | void set( String name, boolean value, AttributesDefinition attributes ) throws CookieSerializationException; 68 | 69 | /** 70 | * Create or update an existing cookie extending the default attributes and serializing the typed value 71 | * 72 | * @see #set(String, String, AttributesDefinition) 73 | */ 74 | void set( String name, List value, AttributesDefinition attributes ) throws CookieSerializationException; 75 | 76 | /** 77 | * Create or update an existing cookie extending the default attributes and serializing the typed value 78 | * 79 | * @see #set(String, String, AttributesDefinition) 80 | */ 81 | void set( String name, CookieValue value, AttributesDefinition attributes ) throws CookieSerializationException; 82 | 83 | /** 84 | * Create or update an existing cookie using the default attributes 85 | * 86 | * @see #set(String, String, AttributesDefinition) 87 | */ 88 | void set( String name, String value ); 89 | 90 | /** 91 | * Create or update an existing cookie using the default attributes and serializing the typed value 92 | * 93 | * @see #set(String, String) 94 | */ 95 | void set( String name, int value ) throws CookieSerializationException; 96 | 97 | /** 98 | * Create or update an existing cookie using the default attributes and serializing the typed value 99 | * 100 | * @see #set(String, String) 101 | */ 102 | void set( String name, boolean value ) throws CookieSerializationException; 103 | 104 | /** 105 | * Create or update an existing cookie using the default attributes and serializing the typed value 106 | * 107 | * @see #set(String, String) 108 | */ 109 | void set( String name, List value ) throws CookieSerializationException; 110 | 111 | /** 112 | * Create or update an existing cookie extending the default attributes and serializing the typed value 113 | * 114 | * @see #set(String, String) 115 | */ 116 | void set( String name, CookieValue value ) throws CookieSerializationException; 117 | 118 | /** 119 | * Remove an existing cookie
120 | * By default, assumes the characters not allowed in each cookie name are encoded with each 121 | * one's UTF-8 Hex equivalent using percent-encoding. 122 | * 123 | * @param attributes 124 | * You must pass the exact same path, domain and secure attributes that were used to set 125 | * the cookie, unless you're relying on the default attributes 126 | * 127 | * @see #get(String) 128 | */ 129 | void remove( String name, AttributesDefinition attributes ); 130 | 131 | /** 132 | * Remove an existing cookie using the default attributes 133 | * 134 | * @see #remove(String, AttributesDefinition) 135 | */ 136 | void remove( String name ); 137 | 138 | /** 139 | * Retrieve the default attributes of this instance 140 | */ 141 | AttributesDefinition defaults(); 142 | 143 | /** 144 | * Create a new instance of the api that overrides the default decoding implementation
145 | * All methods that rely in a proper decoding to work, such as 146 | * {@link #remove(String, AttributesDefinition)} and {@link #get(String)}, will run the converter first 147 | * for each cookie.
148 | * The returning String will be used as the cookie value. 149 | */ 150 | CookiesDefinition withConverter( ConverterStrategy converter ); 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/com/github/jscookie/javacookie/Expiration.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie; 2 | 3 | import java.util.Date; 4 | import java.util.Locale; 5 | 6 | import org.joda.time.DateTime; 7 | import org.joda.time.format.DateTimeFormat; 8 | import org.joda.time.format.DateTimeFormatter; 9 | 10 | public final class Expiration { 11 | private DateTimeFormatter EXPIRES_FORMAT = DateTimeFormat 12 | .forPattern( "EEE, dd MMM yyyy HH:mm:ss 'GMT'" ) 13 | .withLocale( Locale.US ); 14 | private final DateTime date; 15 | private Expiration( DateTime dateTime ) { 16 | this.date = dateTime; 17 | } 18 | public static Expiration days( int days ) { 19 | DateTime withDays = new DateTime().plusDays( days ); 20 | return new Expiration( withDays ); 21 | } 22 | public static Expiration date( DateTime dateTime ) { 23 | if ( dateTime == null ) { 24 | throw new IllegalArgumentException(); 25 | } 26 | return new Expiration( dateTime ); 27 | } 28 | public static Expiration date( Date date ) { 29 | if ( date == null ) { 30 | throw new IllegalArgumentException(); 31 | } 32 | DateTime dateTime = new DateTime( date ); 33 | return new Expiration( dateTime ); 34 | } 35 | String toExpiresString() { 36 | return date.toString( EXPIRES_FORMAT ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/integration/encoding/CookiesEncodingIT.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.integration.encoding; 2 | 3 | import java.io.File; 4 | import java.net.URL; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import org.jboss.arquillian.container.test.api.Deployment; 8 | import org.jboss.arquillian.container.test.api.RunAsClient; 9 | import org.jboss.arquillian.junit.Arquillian; 10 | import org.jboss.arquillian.test.api.ArquillianResource; 11 | import org.jboss.shrinkwrap.api.Archive; 12 | import org.jboss.shrinkwrap.api.Filters; 13 | import org.jboss.shrinkwrap.api.GenericArchive; 14 | import org.jboss.shrinkwrap.api.ShrinkWrap; 15 | import org.jboss.shrinkwrap.api.importer.ExplodedImporter; 16 | import org.jboss.shrinkwrap.api.spec.WebArchive; 17 | import org.junit.Assert; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.openqa.selenium.WebDriver; 21 | import org.openqa.selenium.firefox.FirefoxDriver; 22 | import org.openqa.selenium.support.PageFactory; 23 | 24 | import com.github.jscookie.javacookie.test.integration.qunit.QUnitPageObject; 25 | import com.github.jscookie.javacookie.test.integration.qunit.QUnitResults; 26 | import com.github.jscookie.javacookie.test.integration.test.utils.Debug; 27 | import com.github.jscookie.javacookie.test.unit.utils.IntegrationUtils; 28 | 29 | @RunWith( Arquillian.class ) 30 | public class CookiesEncodingIT { 31 | private static Debug debug = Debug.FALSE; 32 | 33 | @Deployment 34 | public static Archive createDeployment() { 35 | GenericArchive qunitFiles = ShrinkWrap.create( GenericArchive.class ) 36 | .as( ExplodedImporter.class ) 37 | .importDirectory( "bower_components/js-cookie/" ) 38 | .as( GenericArchive.class ); 39 | 40 | WebArchive war = IntegrationUtils.createCommonDeployment() 41 | .merge( qunitFiles, "/", Filters.includeAll() ) 42 | .addAsWebInfResource( 43 | new File( "src/test/resources/web.xml" ), 44 | "web.xml" 45 | ); 46 | 47 | System.out.println( " ----- LOGGING THE FILES ADDED TO JBOSS" ); 48 | System.out.println( war.toString( true ) ); 49 | System.out.println( " ----- END OF LOGGING THE FILES ADDED TO JBOSS" ); 50 | 51 | return war; 52 | } 53 | 54 | @RunAsClient 55 | @Test 56 | public void read_qunit_test( @ArquillianResource URL baseURL ) { 57 | WebDriver driver = new FirefoxDriver(); 58 | driver.manage().timeouts().implicitlyWait( 20, TimeUnit.SECONDS ); 59 | 60 | EncodingPageObject encoding = PageFactory.initElements( driver, EncodingPageObject.class ); 61 | encoding.navigateTo( baseURL ); 62 | QUnitPageObject qunit = encoding.qunit(); 63 | QUnitResults results = qunit.waitForTests( debug ); 64 | 65 | int expectedPasses = results.getTotal(); 66 | int actualPasses = results.getPassed(); 67 | Assert.assertEquals( "should pass all tests", expectedPasses, actualPasses ); 68 | 69 | if ( debug.is( false ) ) { 70 | driver.quit(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/integration/encoding/EncodingPageObject.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.integration.encoding; 2 | 3 | import java.net.URL; 4 | 5 | import org.openqa.selenium.WebDriver; 6 | import org.openqa.selenium.support.PageFactory; 7 | 8 | import com.github.jscookie.javacookie.test.integration.qunit.QUnitPageObject; 9 | 10 | public class EncodingPageObject { 11 | private WebDriver driver; 12 | public EncodingPageObject( WebDriver driver ) { 13 | this.driver = driver; 14 | } 15 | EncodingPageObject navigateTo( URL baseURL ) { 16 | String query = "integration_baseurl=" + baseURL; 17 | String url = baseURL + "test/encoding.html"; 18 | driver.navigate().to( url + "?" + query ); 19 | return this; 20 | } 21 | QUnitPageObject qunit() { 22 | return PageFactory.initElements( driver, QUnitPageObject.class ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/integration/encoding/EncodingServlet.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.integration.encoding; 2 | 3 | import java.io.IOException; 4 | import java.io.UnsupportedEncodingException; 5 | import java.net.URLDecoder; 6 | 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServlet; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import com.github.jscookie.javacookie.Cookies; 14 | 15 | public class EncodingServlet extends HttpServlet { 16 | private static final long serialVersionUID = 1; 17 | @Override 18 | public void doGet( HttpServletRequest request, HttpServletResponse response ) 19 | throws ServletException, IOException { 20 | String name = getUTF8Param( "name", request ); 21 | 22 | System.out.println( "Testing: " + name ); 23 | 24 | Cookies cookies = Cookies.initFromServlet( request, response ); 25 | String value = cookies.get( name ); 26 | 27 | if ( value == null ) { 28 | throw new NullPointerException( "Cookie not found with name: " + name ); 29 | } 30 | 31 | cookies.set( name, value ); 32 | 33 | response.setContentType( "application/json" ); 34 | new ObjectMapper() 35 | .writeValue( response.getOutputStream(), new Result( name, value ) ); 36 | } 37 | 38 | /** 39 | * Retrieves the parameter using UTF-8 charset since the server default is ISO-8859-1 40 | */ 41 | private String getUTF8Param( String name, HttpServletRequest request ) throws UnsupportedEncodingException { 42 | String query = request.getQueryString(); 43 | for ( String pair : query.split( "&" ) ) { 44 | if ( name.equals( pair.split( "=" )[ 0 ] ) ) { 45 | return URLDecoder.decode( pair.split( "=" )[ 1 ], "UTF-8" ); 46 | } 47 | } 48 | return null; 49 | } 50 | } 51 | 52 | class Result { 53 | private String name; 54 | private String value; 55 | Result( String name, String value ) { 56 | this.name = name; 57 | this.value = value; 58 | } 59 | public String getName() { 60 | return name; 61 | } 62 | public String getValue() { 63 | return value; 64 | } 65 | } -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/integration/qunit/QUnitPageObject.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.integration.qunit; 2 | 3 | import java.io.IOException; 4 | 5 | import org.openqa.selenium.JavascriptExecutor; 6 | import org.openqa.selenium.WebDriver; 7 | import org.openqa.selenium.support.ui.WebDriverWait; 8 | 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | import com.github.jscookie.javacookie.test.integration.test.utils.Debug; 11 | import com.google.common.base.Function; 12 | 13 | public class QUnitPageObject { 14 | private WebDriver driver; 15 | private static int TIMEOUT_IN_SECONDS = 1800; 16 | private ObjectMapper mapper = new ObjectMapper(); 17 | public QUnitPageObject( WebDriver driver ) { 18 | this.driver = driver; 19 | } 20 | public QUnitResults waitForTests( final Debug debug ) { 21 | return new WebDriverWait( driver, TIMEOUT_IN_SECONDS ) 22 | .until(new Function() { 23 | @Override 24 | public QUnitResults apply( WebDriver input ) { 25 | JavascriptExecutor js; 26 | if ( driver instanceof JavascriptExecutor ) { 27 | js = ( JavascriptExecutor )driver; 28 | String result = ( String )js.executeScript( 29 | "return window.global_test_results && JSON.stringify(window.global_test_results)" 30 | ); 31 | if ( debug.is( true ) ) { 32 | return null; 33 | } 34 | System.out.println( "Waiting for 'window.global_test_results': " + result ); 35 | if ( result == null ) { 36 | return null; 37 | } 38 | try { 39 | return mapper.readValue( result, QUnitResults.class ); 40 | } catch ( IOException cause ) { 41 | throw new GlobalTestResultException( result, cause ); 42 | } 43 | } 44 | return null; 45 | } 46 | }); 47 | } 48 | private class GlobalTestResultException extends IllegalStateException { 49 | private static final long serialVersionUID = 1; 50 | private GlobalTestResultException( String result, Throwable cause ) { 51 | super( "Failed to read 'window.global_test_results': " + result, cause ); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/integration/qunit/QUnitResults.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.integration.qunit; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class QUnitResults { 7 | private int failed; 8 | private int passed; 9 | private int runtime; 10 | private int total; 11 | private List tests = new ArrayList(); 12 | 13 | public int getFailed() { 14 | return failed; 15 | } 16 | 17 | public int getPassed() { 18 | return passed; 19 | } 20 | 21 | public int getRuntime() { 22 | return runtime; 23 | } 24 | 25 | public List getTests() { 26 | return tests; 27 | } 28 | 29 | public int getTotal() { 30 | return total; 31 | } 32 | 33 | public static class Test { 34 | private String actual = ""; 35 | private String expected = ""; 36 | private String name = ""; 37 | private boolean result = false; 38 | private String source; 39 | 40 | public String getActual() { 41 | return actual; 42 | } 43 | 44 | public String getExpected() { 45 | return expected; 46 | } 47 | 48 | public String getName() { 49 | return name; 50 | } 51 | 52 | public boolean getResult() { 53 | return result; 54 | } 55 | 56 | public String getSource() { 57 | return source; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/integration/test/utils/Debug.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.integration.test.utils; 2 | 3 | public enum Debug { 4 | TRUE( true ), FALSE( false ); 5 | private boolean state; 6 | private Debug( boolean state ) { 7 | this.state = state; 8 | } 9 | public boolean is( boolean state ) { 10 | return this.state == state; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/unit/CookiesConverterTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.unit; 2 | 3 | import javax.script.ScriptEngine; 4 | import javax.script.ScriptEngineManager; 5 | import javax.script.ScriptException; 6 | 7 | import org.junit.Assert; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.mockito.Mockito; 12 | import org.mockito.runners.MockitoJUnitRunner; 13 | 14 | import com.github.jscookie.javacookie.ConverterException; 15 | import com.github.jscookie.javacookie.ConverterStrategy; 16 | import com.github.jscookie.javacookie.Cookies; 17 | import com.github.jscookie.javacookie.test.unit.utils.BaseTest; 18 | 19 | @RunWith( MockitoJUnitRunner.class ) 20 | public class CookiesConverterTest extends BaseTest { 21 | private Cookies cookies; 22 | 23 | @Before 24 | public void before() { 25 | cookies = Cookies.initFromServlet( request, response ); 26 | } 27 | 28 | @Test 29 | public void should_be_able_to_conditionally_decode_a_single_malformed_cookie() { 30 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( 31 | "escaped=%u5317; encoded=%E4%BA%AC" 32 | ); 33 | Cookies cookies = this.cookies.withConverter(new Cookies.Converter() { 34 | @Override 35 | public String convert( String value, String name ) throws ConverterException { 36 | ScriptEngine javascript = new ScriptEngineManager().getEngineByName( "JavaScript" ); 37 | if ( name.equals( "escaped" ) ) { 38 | try { 39 | return javascript.eval( "unescape('" + value + "')" ).toString(); 40 | } catch ( ScriptException e ) { 41 | throw new ConverterException( e ); 42 | } 43 | } 44 | return null; 45 | } 46 | }); 47 | 48 | String actual = cookies.get( "escaped" ); 49 | String expected = "北"; 50 | Assert.assertEquals( expected, actual ); 51 | 52 | actual = cookies.get( "encoded" ); 53 | expected = "京"; 54 | Assert.assertEquals( expected, actual ); 55 | } 56 | 57 | @Test 58 | public void should_be_able_to_create_a_custom_strategy() { 59 | this.cookies.withConverter( new CustomConverter() ); 60 | } 61 | 62 | private class CustomConverter implements ConverterStrategy { 63 | @Override 64 | public String convert( String value, String name ) throws ConverterException { 65 | return value; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/unit/CookiesDecodingTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.unit; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.Mockito; 8 | import org.mockito.runners.MockitoJUnitRunner; 9 | 10 | import com.github.jscookie.javacookie.Cookies; 11 | import com.github.jscookie.javacookie.test.unit.utils.BaseTest; 12 | 13 | @RunWith( MockitoJUnitRunner.class ) 14 | public class CookiesDecodingTest extends BaseTest { 15 | private Cookies cookies; 16 | 17 | @Before 18 | public void before() { 19 | cookies = Cookies.initFromServlet( request, response ); 20 | } 21 | 22 | @Test 23 | public void character_not_allowed_in_name_and_value() { 24 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "%3B=%3B" ); 25 | String actual = cookies.get( ";" ); 26 | String expected = ";"; 27 | Assert.assertEquals( expected, actual ); 28 | } 29 | 30 | @Test 31 | public void character_with_3_bytes() { 32 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c=%E4%BA%AC" ); 33 | String actual = cookies.get( "c" ); 34 | String expected = "京"; 35 | Assert.assertEquals( expected, actual ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/unit/CookiesEncodingTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.unit; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.Mockito; 7 | import org.mockito.runners.MockitoJUnitRunner; 8 | 9 | import com.github.jscookie.javacookie.Cookies; 10 | import com.github.jscookie.javacookie.test.unit.utils.BaseTest; 11 | 12 | @RunWith( MockitoJUnitRunner.class ) 13 | public class CookiesEncodingTest extends BaseTest { 14 | private Cookies cookies; 15 | 16 | @Before 17 | public void before() { 18 | cookies = Cookies.initFromServlet( request, response ); 19 | } 20 | 21 | @Test 22 | public void character_not_allowed_in_name_and_value() { 23 | cookies.set( ";,\\\" ", ";,\\\" " ); 24 | Mockito.verify( response ).addHeader( "Set-Cookie", "%3B%2C%5C%22%20=%3B%2C%5C%22%20; Path=/" ); 25 | } 26 | 27 | @Test 28 | public void characters_allowed_in_name_and_value() { 29 | cookies.set( 30 | "!#$&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~", 31 | "!#$&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~" 32 | ); 33 | Mockito.verify( response ).addHeader( 34 | "Set-Cookie", 35 | "!#$&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~=!#$&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~; Path=/" 36 | ); 37 | } 38 | 39 | @Test 40 | public void character_with_3_bytes_in_value() { 41 | cookies.set( "c", "京" ); 42 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=%E4%BA%AC; Path=/" ); 43 | } 44 | 45 | @Test 46 | public void character_with_3_bytes_in_name() { 47 | cookies.set( "京", "v" ); 48 | Mockito.verify( response ).addHeader( "Set-Cookie", "%E4%BA%AC=v; Path=/" ); 49 | } 50 | 51 | @Test 52 | public void character_with_4_bytes_in_name() { 53 | cookies.set( "c", "𩸽" ); 54 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=%F0%A9%B8%BD; Path=/" ); 55 | } 56 | 57 | @Test 58 | public void character_with_4_bytes_mixed_with_single_bytes() { 59 | cookies.set( "c", "a𩸽b" ); 60 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=a%F0%A9%B8%BDb; Path=/" ); 61 | } 62 | 63 | @Test 64 | public void characters_allowed_in_cookie_value() { 65 | cookies.set( "c", "/:<=>?@[]{}" ); 66 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=/:<=>?@[]{}; Path=/" ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/unit/CookiesJSONReadTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.unit; 2 | 3 | import java.util.List; 4 | 5 | import org.junit.Assert; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.Mockito; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | 12 | import com.fasterxml.jackson.annotation.JsonProperty; 13 | import com.fasterxml.jackson.core.type.TypeReference; 14 | import com.github.jscookie.javacookie.CookieParseException; 15 | import com.github.jscookie.javacookie.Cookies; 16 | import com.github.jscookie.javacookie.test.unit.utils.BaseTest; 17 | 18 | @RunWith( MockitoJUnitRunner.class ) 19 | public class CookiesJSONReadTest extends BaseTest { 20 | private Cookies cookies; 21 | 22 | @Before 23 | public void before() { 24 | cookies = Cookies.initFromServlet( request, response ); 25 | } 26 | 27 | @Test 28 | public void read_int_type() throws CookieParseException { 29 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c=1" ); 30 | 31 | String actual = cookies.get( "c" ); 32 | String expected = "1"; 33 | Assert.assertEquals( expected, actual ); 34 | 35 | int actual2 = cookies.get( "c", Integer.class ); 36 | int expected2 = 1; 37 | Assert.assertEquals( expected2, actual2 ); 38 | } 39 | 40 | @Test 41 | public void read_boolean_type() throws CookieParseException { 42 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c=true" ); 43 | 44 | String actual = cookies.get( "c" ); 45 | String expected = "true"; 46 | Assert.assertEquals( expected, actual ); 47 | 48 | boolean actual2 = cookies.get( "c", Boolean.class ); 49 | boolean expected2 = true; 50 | Assert.assertEquals( expected2, actual2 ); 51 | } 52 | 53 | @Test 54 | public void read_JSON_array_with_string() throws CookieParseException { 55 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c=[%22v%22]" ); 56 | 57 | String actual = cookies.get( "c" ); 58 | String expected = "[\"v\"]"; 59 | Assert.assertEquals( expected, actual ); 60 | 61 | String actual2 = cookies.get( "c", new TypeReference>() {} ).get( 0 ); 62 | String expected2 = "v"; 63 | Assert.assertEquals( expected2, actual2 ); 64 | } 65 | 66 | @Test 67 | public void read_custom_type_with_string_prop() throws CookieParseException { 68 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c={%22property%22:%22v%22}" ); 69 | 70 | String actual = cookies.get( "c" ); 71 | String expected = "{\"property\":\"v\"}"; 72 | Assert.assertEquals( expected, actual ); 73 | 74 | String actual2 = cookies.get( "c", CustomTypeString.class ).getProperty(); 75 | String expected2 = "v"; 76 | Assert.assertEquals( expected2, actual2 ); 77 | } 78 | 79 | @Test 80 | public void read_custom_type_with_boolean_prop() throws CookieParseException { 81 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c={%22property%22:true}" ); 82 | 83 | String actual = cookies.get( "c" ); 84 | String expected = "{\"property\":true}"; 85 | Assert.assertEquals( expected, actual ); 86 | 87 | Boolean actual2 = cookies.get( "c", CustomTypeBoolean.class ).getProperty(); 88 | Boolean expected2 = true; 89 | Assert.assertEquals( expected2, actual2 ); 90 | } 91 | 92 | @Test 93 | public void read_custom_type_with_number_prop() throws CookieParseException { 94 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c={%22property%22:1}" ); 95 | 96 | String actual = cookies.get( "c" ); 97 | String expected = "{\"property\":1}"; 98 | Assert.assertEquals( expected, actual ); 99 | 100 | Integer actual2 = cookies.get( "c", CustomTypeInteger.class ).getProperty(); 101 | Integer expected2 = 1; 102 | Assert.assertEquals( expected2, actual2 ); 103 | } 104 | 105 | @Test 106 | public void read_missing_cookie() throws CookieParseException { 107 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( null ); 108 | 109 | Assert.assertNull( cookies.get( "c" ) ); 110 | Assert.assertNull( cookies.get( "c", CustomTypeInteger.class ) ); 111 | Assert.assertNull( cookies.get( "c", CustomTypeBoolean.class ) ); 112 | Assert.assertNull( cookies.get( "c", CustomTypeString.class ) ); 113 | Assert.assertNull( cookies.get( "c", new TypeReference>() {} ) ); 114 | } 115 | 116 | private static class CustomTypeString { 117 | private String property; 118 | @JsonProperty( "property" ) 119 | private String getProperty() { 120 | return property; 121 | } 122 | } 123 | 124 | private static class CustomTypeBoolean { 125 | private Boolean property; 126 | @JsonProperty( "property" ) 127 | private Boolean getProperty() { 128 | return property; 129 | } 130 | } 131 | 132 | private static class CustomTypeInteger { 133 | private Integer property; 134 | @JsonProperty( "property" ) 135 | private Integer getProperty() { 136 | return property; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/unit/CookiesJSONWriteTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.unit; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.Mockito; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import com.github.jscookie.javacookie.CookieSerializationException; 12 | import com.github.jscookie.javacookie.CookieValue; 13 | import com.github.jscookie.javacookie.Cookies; 14 | import com.github.jscookie.javacookie.test.unit.utils.BaseTest; 15 | 16 | @RunWith( MockitoJUnitRunner.class ) 17 | public class CookiesJSONWriteTest extends BaseTest { 18 | private Cookies cookies; 19 | 20 | @Before 21 | public void before() { 22 | cookies = Cookies.initFromServlet( request, response ); 23 | } 24 | 25 | @Test 26 | public void write_int_type() throws CookieSerializationException { 27 | cookies.set( "c", 1 ); 28 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=1; Path=/" ); 29 | } 30 | 31 | @Test 32 | public void write_boolean_type() throws CookieSerializationException { 33 | cookies.set( "c", true ); 34 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=true; Path=/" ); 35 | } 36 | 37 | @Test 38 | public void write_JSON_array_with_string() throws CookieSerializationException { 39 | cookies.set( "c", Arrays.asList( "v" ) ); 40 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=[%22v%22]; Path=/" ); 41 | } 42 | 43 | @Test 44 | public void write_custom_type_with_string_prop() throws CookieSerializationException { 45 | cookies.set( "c", new CustomTypeString( "v" ) ); 46 | Mockito.verify( response ).addHeader( "Set-Cookie", "c={%22property%22:%22v%22}; Path=/" ); 47 | } 48 | 49 | @Test 50 | public void write_custom_type_with_boolean_prop() throws CookieSerializationException { 51 | cookies.set( "c", new CustomTypeBoolean( true ) ); 52 | Mockito.verify( response ).addHeader( "Set-Cookie", "c={%22property%22:true}; Path=/" ); 53 | } 54 | 55 | @Test 56 | public void write_custom_type_with_number_prop() throws CookieSerializationException { 57 | cookies.set( "c", new CustomTypeInteger( 1 ) ); 58 | Mockito.verify( response ).addHeader( "Set-Cookie", "c={%22property%22:1}; Path=/" ); 59 | } 60 | 61 | class CustomTypeString implements CookieValue { 62 | private String property; 63 | private CustomTypeString( String property ) { 64 | this.property = property; 65 | } 66 | public String getProperty() { 67 | return property; 68 | } 69 | } 70 | 71 | class CustomTypeBoolean implements CookieValue { 72 | private Boolean property; 73 | private CustomTypeBoolean( Boolean property ) { 74 | this.property = property; 75 | } 76 | public Boolean getProperty() { 77 | return property; 78 | } 79 | } 80 | 81 | class CustomTypeInteger implements CookieValue { 82 | private Integer property; 83 | private CustomTypeInteger( Integer property ) { 84 | this.property = property; 85 | } 86 | public Integer getProperty() { 87 | return property; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/unit/CookiesReadTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.unit; 2 | 3 | import java.util.Map; 4 | 5 | import org.junit.Assert; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.Mockito; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | 12 | import com.github.jscookie.javacookie.Cookies; 13 | import com.github.jscookie.javacookie.test.unit.utils.BaseTest; 14 | 15 | @RunWith( MockitoJUnitRunner.class ) 16 | public class CookiesReadTest extends BaseTest { 17 | private Cookies cookies; 18 | 19 | @Before 20 | public void before() { 21 | cookies = Cookies.initFromServlet( request, response ); 22 | } 23 | 24 | @Test 25 | public void simple_value() { 26 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c=v" ); 27 | String actual = cookies.get( "c" ); 28 | String expected = "v"; 29 | Assert.assertEquals( expected, actual ); 30 | } 31 | 32 | @Test 33 | public void read_all() { 34 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c=v; foo=bar" ); 35 | Map result = cookies.get(); 36 | 37 | String actual = result.get( "c" ); 38 | String expected = "v"; 39 | Assert.assertEquals( expected, actual ); 40 | 41 | actual = result.get( "foo" ); 42 | expected = "bar"; 43 | Assert.assertEquals( expected, actual ); 44 | } 45 | 46 | @Test 47 | public void equal_sign_in_cookie_name() { 48 | Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c=a=b" ); 49 | 50 | String actual = cookies.get( "c" ); 51 | String expected = "a=b"; 52 | Assert.assertEquals( expected, actual ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/unit/CookiesWriteTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.unit; 2 | 3 | import org.joda.time.DateTime; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.Mockito; 8 | import org.mockito.runners.MockitoJUnitRunner; 9 | 10 | import com.github.jscookie.javacookie.Cookies; 11 | import com.github.jscookie.javacookie.Expiration; 12 | import com.github.jscookie.javacookie.Cookies.Attributes; 13 | import com.github.jscookie.javacookie.test.unit.utils.BaseTest; 14 | 15 | @RunWith( MockitoJUnitRunner.class ) 16 | public class CookiesWriteTest extends BaseTest { 17 | private Cookies cookies; 18 | 19 | @Before 20 | public void before() { 21 | cookies = Cookies.initFromServlet( request, response ); 22 | } 23 | 24 | @Test 25 | public void simple_write() { 26 | cookies.set( "c", "v" ); 27 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=v; Path=/" ); 28 | } 29 | 30 | @Test 31 | public void simple_write_with_default_attributes() { 32 | cookies.defaults() 33 | .path( "/" ) 34 | .domain( "site.com" ) 35 | .secure( true ); 36 | cookies.set( "c", "v" ); 37 | 38 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=v; Path=/; Domain=site.com; Secure" ); 39 | } 40 | 41 | @Test 42 | public void simple_write_with_attributes() { 43 | cookies.set( "c", "v", Cookies.Attributes.empty() 44 | .path( "/" ) 45 | .domain( "example.com" ) 46 | .secure( true ) 47 | ); 48 | 49 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=v; Path=/; Domain=example.com; Secure" ); 50 | } 51 | 52 | @Test 53 | public void simple_write_overriding_default_attributes() { 54 | cookies.defaults() 55 | .path( "/path/" ) 56 | .secure( true ); 57 | cookies.set( "c", "v", Cookies.Attributes.empty() 58 | .path( "/" ) 59 | ); 60 | 61 | // Should consider default secure if not overriden 62 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=v; Path=/; Secure" ); 63 | } 64 | 65 | @Test 66 | public void removing_default_path_should_fallback_to_whole_site() { 67 | cookies.defaults() 68 | .path( null ); 69 | cookies.set( "c", "v" ); 70 | 71 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=v; Path=/" ); 72 | } 73 | 74 | @Test 75 | public void should_not_write_the_path_attribute_if_set_as_an_empty_string() { 76 | cookies.defaults() 77 | .path( "" ); 78 | cookies.set( "c", "v" ); 79 | 80 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=v" ); 81 | } 82 | 83 | @Test 84 | public void expires_attribute() { 85 | DateTime date_2015_06_07_23h38m46s = new DateTime( 2015, 6, 7, 23, 38, 46 ); 86 | cookies.set( "c", "v", Attributes.empty() 87 | .expires( Expiration.date( date_2015_06_07_23h38m46s ) ) 88 | ); 89 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=v; Path=/; Expires=Sun, 07 Jun 2015 23:38:46 GMT" ); 90 | } 91 | 92 | @Test 93 | public void httponly_attribute() { 94 | cookies.set( "c", "v", Attributes.empty() 95 | .httpOnly( true ) 96 | ); 97 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=v; Path=/; HttpOnly" ); 98 | } 99 | 100 | @Test 101 | public void sameSite_attribute() { 102 | cookies.set( "c", "v", Attributes.empty() 103 | .sameSite( "Lax" ) 104 | ); 105 | Mockito.verify( response ).addHeader( "Set-Cookie", "c=v; Path=/; SameSite=Lax" ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/unit/utils/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.unit.utils; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | import org.mockito.Mock; 7 | 8 | public class BaseTest { 9 | protected @Mock HttpServletRequest request; 10 | protected @Mock HttpServletResponse response; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/github/jscookie/javacookie/test/unit/utils/IntegrationUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.jscookie.javacookie.test.unit.utils; 2 | 3 | import java.io.File; 4 | 5 | import org.jboss.shrinkwrap.api.ShrinkWrap; 6 | import org.jboss.shrinkwrap.api.spec.WebArchive; 7 | import org.jboss.shrinkwrap.resolver.api.maven.Maven; 8 | 9 | public class IntegrationUtils { 10 | public static WebArchive createCommonDeployment() { 11 | boolean RECURSIVE_TRUE = true; 12 | return ShrinkWrap.create( WebArchive.class ) 13 | .addPackage( "com.github.jscookie.javacookie" ) 14 | .addPackages( RECURSIVE_TRUE, "com.github.jscookie.javacookie.test.integration" ) 15 | .addAsLibraries( 16 | Maven.resolver() 17 | .loadPomFromFile( "pom.xml" ) 18 | .resolve( 19 | "joda-time:joda-time", 20 | "com.fasterxml.jackson.core:jackson-databind" 21 | ) 22 | .withTransitivity() 23 | .as( File.class ) 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | target/wildfly-20.0.1.Final 9 | -Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | encoding 7 | com.github.jscookie.javacookie.test.integration.encoding.EncodingServlet 8 | 9 | 10 | encoding 11 | /encoding 12 | 13 | 14 | --------------------------------------------------------------------------------