Skip to content
This repository was archived by the owner on Feb 8, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## Issues

- Report issues or feature requests on [GitHub Issues](https://github.com/js-cookie/java-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 [(see below)](#development).

## Development

### Tools
We use the following tools for development:

- [Maven](https://maven.apache.org/) for Java Build.
- [NodeJS](http://nodejs.org/download/) required to run grunt.
- [Grunt](http://gruntjs.com/getting-started) for JavaScript task management.

### Getting started

Install [NodeJS](http://nodejs.org/).
Install [Maven](https://maven.apache.org/download.cgi) and add `mvn` as a global alias to run the `/bin/mvn` command inside Maven folder.

Browse to the project root directory and run the build:

$ mvn install

After the build completes, you should see the following message in the console:

----------------------------------------------------------------------------
BUILD SUCCESS
----------------------------------------------------------------------------

### Unit tests
To run the unit tests, execute the following command:

$ mvn test

### Integration tests

If you want to debug the integration tests in the browser, switch `Debug.FALSE` to `Debug.TRUE` in `CookiesEncodingIT.java` and run the build:

$ mvn install

[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.
267 changes: 266 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,267 @@
# Java Cookie
A simple Java Servlet API for handling cookies

A simple Java API for handling cookies

* [Unobstrusive](#json-data-binding) JSON Data Binding support
* [RFC 6265](http://www.rfc-editor.org/rfc/rfc6265.txt) compliant
* Enable [custom decoding](#converter)

## Basic Usage

Create a cookie, valid across the entire site

```java
Cookies cookies = Cookies.initFromServlet( request, response );
cookies.set( "name", "value" );
```

Create a cookie that expires 7 days from now, valid across the entire site:

```java
Cookies cookies = Cookies.initFromServlet( request, response );
cookies.set( "name", "value", Attributes.empty()
.expires( Expiration.days( 7 ) )
);
```

Create an expiring cookie, valid to the path of the current page:

```java
Cookies cookies = Cookies.initFromServlet( request, response );
cookies.set( "name", "value", Attributes.empty()
.expires( Expiration.days( 7 ) )
.path( "" )
);
```

Read cookie:

```java
Cookies cookies = Cookies.initFromServlet( request, response );
cookies.get( "name" ); // => "value"
cookies.get( "nothing" ); // => null
```

Read all available cookies:

```java
Cookies cookies = Cookies.initFromServlet( request, response );
Map<String, String> all = cookies.get(); // => {name=value}
```

Delete cookie:

```java
Cookies cookies = Cookies.initFromServlet( request, response );
cookies.remove( "name" );
```

Delete a cookie valid to the path of the current page:

```java
Cookies cookies = Cookies.initFromServlet( request, response );
cookies.set( "name", "value", Attributes.empty()
.path( "" )
);
cookies.remove( "name" ); // fail!
cookies.remove( "name", Attributes.empty().path( "path" ) ); // removed!
```

*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).*

## JSON Data Binding

java-cookie provides unobstrusive JSON storage for cookies with data binding.

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).

Consider the following class that implements the `CookieValue` interface:

```java
public class Person implements CookieValue {
private int age;
public Person( int age ) {
this.age = age;
}
public int getAge() {
return age;
}
}
```

And the following usage:

```java
Cookies cookies = Cookies.initFromServlet( request, response );
cookies.set( "name", new Person( 25 ) );
```

When reading a cookie with the default `get()` api, you receive the string representation stored in the cookie:

```java
Cookies cookies = Cookies.initFromServlet( request, response );
String value = cookies.get( "name" ); // => "{\"age\":25}"
```

If you pass the type reference, it will parse the JSON into a new instance:

```java
Cookies cookies = Cookies.initFromServlet( request, response );
Person adult = cookies.get( "name", Person.class );
if ( adult != null ) {
adult.getAge(); // => 25
}
```

## Encoding

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).
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.
To override the default cookie decoding you need to use a [converter](#converter).

## Cookie Attributes

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.

```java
Cookies cookies = Cookies.initFromServlet( request, response );
cookies.defaults()
.secure( true )
.httpOnly( true );
cookies.set( "name", "value", Attributes.empty()
.httpOnly( false ) // override defaults
);
```

### expires

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.

**Default:** Cookie is removed when the user closes the browser.

**Examples:**

```java
DateTime date_2015_06_07_23h38m46s = new DateTime( 2015, 6, 7, 23, 38, 46 );
Cookies cookies = Cookies.initFromServlet( request, response );
cookies.set( "name", "value", Attributes.empty()
.expires( Expiration.date( date_2015_06_07_23h38m46s ) )
);
cookies.get( "name" ); // => "value"
cookies.remove( "name" );
```

### path

Define the path where the cookie is available.

**Default:** `/`

**Examples:**

```java
Cookies cookies = Cookies.initFromServlet( request, response );
Attributes validToTheCurrentPage = Attributes.empty().path( "" );
cookies.set( "name", "value", validToTheCurrentPath );
cookies.get( "name" ); // => "value"
cookies.remove( "name", validToTheCurrentPath );
```

### domain

Define the domain where the cookie is available

**Default:** Domain of the page where the cookie was created

**Examples:**

```java
Cookies cookies = Cookies.initFromServlet( request, response );
cookies.set( "name", "value", Attributes.empty().domain( "sub.domain.com" ) );
cookies.get( "name" ); // => null (need to read at "sub.domain.com")
```

### secure

A `Boolean` indicating if the cookie transmission requires a secure protocol (https)

**Default:** No secure protocol requirement

**Examples:**

```java
Cookies cookies = Cookies.initFromServlet( request, response );
Attributes secureCookie = Attributes.empty().secure( true );
cookies.set( "name", "value", secureCookie );
cookies.get( "name" ); // => "value"
cookies.remove( "name", secureCookie );
```

### httpOnly

A `Boolean` indicating if the cookie should be restricted to be manipulated only in the server.

**Default:** The cookie can be manipulated in the server and in the client

**Examples:**

```java
Cookies cookies = Cookies.initFromServlet( request, response );
Attributes httpOnlyCookie = Attributes.empty().httpOnly( true );
cookies.set( "name", "value", httpOnlyCookie );
cookies.get( "name" ); // => "value"
cookies.remove( "name", httpOnlyCookie );
```

## Converter

Create a new instance of the api that overrides the default decoding implementation.
All methods that rely in a proper decoding to work, such as `remove()` and `get()`, will run the converter first for each cookie.
The returning String will be used as the cookie value.

Example from reading one of the cookies that can only be decoded using the Javascript `escape` function:

``` java
// document.cookie = 'escaped=%u5317';
// document.cookie = 'default=%E5%8C%97';

Cookies cookies = Cookies.initFromServlet( request, response );
Cookies escapedCookies = cookies.withConverter(new Cookies.Converter() {
@Override
public String convert( String value, String name ) throws ConverterException {
ScriptEngine javascript = new ScriptEngineManager().getEngineByName( "JavaScript" );
if ( name.equals( "escaped" ) ) {
try {
return javascript.eval( "unescape('" + value + "')" ).toString();
} catch ( ScriptException e ) {
throw new ConverterException( e );
}
}
return null;
}
});

escapedCookies.get( "escaped" ); // => 北
escapedCookies.get( "default" ); // => 北
escapedCookies.get(); // => {escaped=北, default=北}
```

Instead of passing a converter inline, you can also create a custom strategy by implementing the `ConverterStrategy` interface:

```java
class CustomConverter implements ConverterStrategy {
@Override
public String convert( String value, String name ) throws ConverterException {
return value;
}
}
```

```java
Cookies cookies = Cookies.initFromServlet( request, response );
Cookies cookiesWithCustomConverter = cookies.withConverter( new CustomConverter() );
```

## Contributing

Check out the [Contributing Guidelines](CONTRIBUTING.md).
2 changes: 1 addition & 1 deletion src/main/java/org/jscookie/ConverterStrategy.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.jscookie;

interface ConverterStrategy {
public interface ConverterStrategy {
/**
* Apply the decoding strategy of a cookie. The return will be used as the cookie value
*
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/jscookie/CookieParseException.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.jscookie;

public class CookieParseException extends Exception {
public final class CookieParseException extends Exception {
private static final long serialVersionUID = 1;
@SuppressWarnings( "unused" )
private CookieParseException() {}
CookieParseException( Throwable cause ) {
super( cause );
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/jscookie/CookieSerializationException.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

public class CookieSerializationException extends Exception {
private static final long serialVersionUID = 1;
@SuppressWarnings( "unused" )
private CookieSerializationException() {}
CookieSerializationException( Throwable cause ) {
super( cause );
}
Expand Down
13 changes: 13 additions & 0 deletions src/test/java/org/jscookie/test/unit/CookiesConverterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import javax.script.ScriptException;

import org.jscookie.ConverterException;
import org.jscookie.ConverterStrategy;
import org.jscookie.Cookies;
import org.jscookie.test.unit.utils.BaseTest;
import org.junit.Assert;
Expand Down Expand Up @@ -51,4 +52,16 @@ public String convert( String value, String name ) throws ConverterException {
expected = "京";
Assert.assertEquals( expected, actual );
}

@Test
public void should_be_able_to_create_a_custom_strategy() {
this.cookies.withConverter( new CustomConverter() );
}

private class CustomConverter implements ConverterStrategy {
@Override
public String convert( String value, String name ) throws ConverterException {
return value;
}
}
}