diff --git a/.classpath b/.classpath deleted file mode 100644 index 9c8395b..0000000 --- a/.classpath +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.gitignore b/.gitignore index 4de0f79..10809b8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,9 @@ /node_modules /bower_components /release.properties -/pom.xml.releaseBackup \ No newline at end of file +/pom.xml.releaseBackup +*.iml +.idea +.project +.classpath +.settings \ No newline at end of file diff --git a/.project b/.project deleted file mode 100644 index b77bffc..0000000 --- a/.project +++ /dev/null @@ -1,29 +0,0 @@ - - - Java Cookie - - - - - - org.eclipse.wst.common.project.facet.core.builder - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - org.eclipse.jdt.core.javanature - org.eclipse.wst.common.project.facet.core.nature - - diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index cdfe4f1..0000000 --- a/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,5 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/test/java=UTF-8 -encoding//src/test/resources=UTF-8 -encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 443e085..0000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,8 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.7 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f..0000000 --- a/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml deleted file mode 100644 index e06acaa..0000000 --- a/.settings/org.eclipse.wst.common.project.facet.core.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.travis.yml b/.travis.yml index 88ccde2..5af472f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: java jdk: - - oraclejdk7 - - openjdk6 \ No newline at end of file + - oraclejdk8 + - oraclejdk11 + - openjdk10 + - openjdk11 \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index db99085..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = function (grunt) { - - grunt.initConfig({ - bower_postinst: { - all: { - options: { - components: { - 'js-cookie': ['npm', 'grunt'] - } - } - } - } - }); - - grunt.loadNpmTasks('grunt-bower-postinst'); - - grunt.registerTask('default', ['bower_postinst']); -}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..68eb935 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14 @@ +{ + "name": "java-cookie", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "bower": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.8.tgz", + "integrity": "sha512-1SrJnXnkP9soITHptSO+ahx3QKp3cVzn8poI6ujqc5SeOkg5iqM1pK9H+DSc2OQ8SnO0jC/NG4Ur/UIwy7574A==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 4edcf21..cffb38a 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,12 @@ { "name": "java-cookie", "version": "1.0.0", + "license": "MIT", "repository": { "type": "git", "url": "https://github.com/js-cookie/java-cookie.git" }, "devDependencies": { - "bower": "1.4.1", - "grunt": "0.4.5", - "grunt-bower-postinst": "0.2.1", - "grunt-cli": "0.1.13" + "bower": "1.8.8" } } diff --git a/pom.xml b/pom.xml index d5637e3..104d88c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 com.github.js-cookie java-cookie @@ -35,8 +36,8 @@ - RedHat - https://maven.repository.redhat.com/earlyaccess/all + RedHat GA + https://maven.repository.redhat.com/ga/ @@ -64,13 +65,12 @@ - - org.jboss.arquillian - arquillian-bom - 1.1.8.Final - import - pom - + + org.jboss.arquillian + arquillian-bom + 1.4.1.Final + pom + @@ -83,12 +83,12 @@ joda-time joda-time - 2.7 + 2.10.1 com.fasterxml.jackson.core jackson-databind - 2.8.11.1 + 2.9.8 junit @@ -123,25 +123,32 @@ org.jboss.arquillian.junit arquillian-junit-container + 1.4.1.Final test org.jboss.shrinkwrap.resolver - shrinkwrap-resolver-bom - 2.2.0-beta-2 - pom - import + shrinkwrap-resolver-impl-maven + 3.1.3 + test org.apache.httpcomponents httpclient - 4.3.3 + 4.5.7 test org.apache.httpcomponents fluent-hc - 4.3.3 + 4.5.7 + test + + + org.jboss.as + jboss-as-dist + 7.5.7.Final-redhat-3 + pom test @@ -153,30 +160,6 @@ - - maven-dependency-plugin - - - unpack - process-test-classes - - unpack - - - - - org.jboss.as - jboss-as-dist - 7.5.0.Final-redhat-15 - zip - false - target - - - - - - org.codehaus.mojo failsafe-maven-plugin @@ -193,22 +176,22 @@ com.github.eirslett frontend-maven-plugin - 0.0.23 + 1.5 install node and npm - install-node-and-npm + install-node-and-npm - v0.10.18 - 1.3.8 + v11.10.0 + 6.8.0 npm install - npm + npm @@ -217,12 +200,6 @@ bower - - grunt build - - grunt - - @@ -245,7 +222,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.3 + 3.0.1 attach-javadocs @@ -258,7 +235,7 @@ org.apache.maven.plugins maven-source-plugin - 2.4 + 3.0.1 attach-sources @@ -271,7 +248,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.5 + 1.6.8 true ossrh diff --git a/src/main/java/com/github/jscookie/javacookie/Cookies.java b/src/main/java/com/github/jscookie/javacookie/Cookies.java index a8abe7f..22da112 100644 --- a/src/main/java/com/github/jscookie/javacookie/Cookies.java +++ b/src/main/java/com/github/jscookie/javacookie/Cookies.java @@ -1,491 +1,495 @@ package com.github.jscookie.javacookie; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.CharArrayWriter; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.ResourceBundle; -import java.util.Set; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - public final class Cookies implements CookiesDefinition { - private static String UTF_8 = "UTF-8"; - private HttpServletRequest request; - private HttpServletResponse response; - private AttributesDefinition defaults = Attributes.empty(); - private ConverterStrategy converter; - private ObjectMapper mapper = new ObjectMapper(); - - private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings"; - private static ResourceBundle lStrings = ResourceBundle.getBundle( LSTRING_FILE ); - - private Cookies( HttpServletRequest request, HttpServletResponse response, ConverterStrategy converter ) { - this.request = request; - this.response = response; - this.converter = converter; - } - - public static Cookies initFromServlet( HttpServletRequest request, HttpServletResponse response ) { - return new Cookies( request, response, null ); - } - - @Override - public synchronized String get( String name ) { - if ( name == null || name.length() == 0 ) { - throw new IllegalArgumentException( lStrings.getString( "err.cookie_name_blank" ) ); - } - - String cookieHeader = request.getHeader( "cookie" ); - if ( cookieHeader == null ) { - return null; - } - - Map cookies = getCookies( cookieHeader ); - for ( String decodedName : cookies.keySet() ) { - if ( !name.equals( decodedName ) ) { - continue; - } - return cookies.get( decodedName ); - } - - return null; - } - - @Override - public T get( String name, Class dataType ) throws CookieParseException { - String value = get( name ); - try { - return mapper.readValue( value, dataType ); - } catch ( IOException e ) { - throw new CookieParseException( e ); - } - } - - @Override - public T get( String name, TypeReference typeRef ) throws CookieParseException { - String value = get( name ); - try { - return mapper.readValue( value, typeRef ); - } catch ( IOException e ) { - throw new CookieParseException( e ); - } - } - - @Override - public Map get() { - Map result = new HashMap(); - - String cookieHeader = request.getHeader( "cookie" ); - if ( cookieHeader == null ) { - return result; - } - - return getCookies( cookieHeader ); - } - - @Override - public synchronized void set( String name, String value, AttributesDefinition attributes ) { - if ( name == null || name.length() == 0 ) { - throw new IllegalArgumentException( lStrings.getString( "err.cookie_name_blank" ) ); - } - if ( value == null ) { - throw new IllegalArgumentException(); - } - if ( attributes == null ) { - throw new IllegalArgumentException(); - } - - String encodedName = encode( name ); - String encodedValue = encodeValue( value ); - - StringBuilder header = new StringBuilder(); - header.append( encodedName ); - header.append( '=' ); - header.append( encodedValue ); - - attributes = extend( Attributes.empty().path( "/" ), defaults, attributes ); - - String path = attributes.path(); - if ( path != null && !path.isEmpty() ) { - header.append( "; Path=" + path ); - } - - Expiration expires = attributes.expires(); - if ( expires != null ) { - header.append( "; Expires=" + expires.toExpiresString() ); - } - - String domain = attributes.domain(); - if ( domain != null ) { - header.append( "; Domain=" + domain ); - } - - Boolean secure = attributes.secure(); - if ( Boolean.TRUE.equals( secure ) ) { - header.append( "; Secure" ); - } - - Boolean httpOnly = attributes.httpOnly(); - if ( Boolean.TRUE.equals( httpOnly ) ) { - header.append( "; HttpOnly" ); - } - - if ( response.isCommitted() ) { - return; - } - - setCookie( header.toString(), response ); - } - - @Override - public void set( String name, int value, AttributesDefinition attributes ) throws CookieSerializationException { - set( name, String.valueOf( value ), attributes ); - } - - @Override - public void set( String name, boolean value, AttributesDefinition attributes ) throws CookieSerializationException { - set( name, String.valueOf( value ), attributes ); - } - - @Override - public void set( String name, List value, AttributesDefinition attributes ) throws CookieSerializationException { - try { - set( name, mapper.writeValueAsString( value ), attributes ); - } catch ( JsonProcessingException e ) { - throw new CookieSerializationException( e ); - } - } - - @Override - public void set( String name, CookieValue value, AttributesDefinition attributes ) throws CookieSerializationException { - try { - set( name, mapper.writeValueAsString( value ), attributes ); - } catch ( JsonProcessingException e ) { - throw new CookieSerializationException( e ); - } - } - - @Override - public void set( String name, String value ) { - if ( name == null || name.length() == 0 ) { - throw new IllegalArgumentException( lStrings.getString( "err.cookie_name_blank" ) ); - } - if ( value == null ) { - throw new IllegalArgumentException(); - } - set( name, value, defaults ); - } - - @Override - public void set( String name, int value ) throws CookieSerializationException { - set( name, value, Attributes.empty() ); - } - - @Override - public void set( String name, boolean value ) { - set( name, String.valueOf( value ) ); - } - - @Override - public void set( String name, List value ) throws CookieSerializationException { - set( name, value, Attributes.empty() ); - } - - @Override - public void set( String name, CookieValue value ) throws CookieSerializationException { - set( name, value, Attributes.empty() ); - } - - @Override - public void remove( String name, AttributesDefinition attributes ) { - if ( name == null || name.length() == 0 ) { - throw new IllegalArgumentException( lStrings.getString( "err.cookie_name_blank" ) ); - } - if ( attributes == null ) { - throw new IllegalArgumentException(); - } - - set( name, "", extend( attributes, Attributes.empty() - .expires( Expiration.days( -1 ) )) - ); - } - - @Override - public void remove( String name ) { - if ( name == null || name.length() == 0 ) { - throw new IllegalArgumentException( lStrings.getString( "err.cookie_name_blank" ) ); - } - remove( name, Attributes.empty() ); - } - - @Override - public AttributesDefinition defaults() { - return this.defaults; - } - - @Override - public Cookies withConverter( ConverterStrategy converter ) { - return new Cookies( request, response, converter ); - } - - private Attributes extend( AttributesDefinition... mergeables ) { - Attributes result = Attributes.empty(); - for ( AttributesDefinition mergeable : mergeables ) { - result.merge( mergeable ); - } - return result; - } - - private void setCookie( String cookieValue, HttpServletResponse response ) { - response.addHeader( "Set-Cookie", cookieValue ); - } - - private String encode( String decoded ) { - return encode( decoded, new HashSet() ); - } - - private String encode( String decoded, Set exceptions ) { - String encoded = decoded; - for ( int i = 0; i < decoded.length(); ) { - int codePoint = decoded.codePointAt( i ); - i += Character.charCount( codePoint ); - - boolean isDigit = codePoint >= codePoint( "0" ) && codePoint <= codePoint( "9" ); - if ( isDigit ) { - continue; - } - - boolean isAsciiUppercaseLetter = codePoint >= codePoint( "A" ) && codePoint <= codePoint( "Z" ); - if ( isAsciiUppercaseLetter ) { - continue; - } - - boolean isAsciiLowercaseLetter = codePoint >= codePoint( "a" ) && codePoint <= codePoint( "z" ); - if ( isAsciiLowercaseLetter ) { - continue; - } - - boolean isAllowed = - codePoint == codePoint( "!" ) || codePoint == codePoint( "#" ) || - codePoint == codePoint( "$" ) || codePoint == codePoint( "&" ) || - codePoint == codePoint( "'" ) || codePoint == codePoint( "*" ) || - codePoint == codePoint( "+" ) || codePoint == codePoint( "-" ) || - codePoint == codePoint( "." ) || codePoint == codePoint( "^" ) || - codePoint == codePoint( "_" ) || codePoint == codePoint( "`" ) || - codePoint == codePoint( "|" ) || codePoint == codePoint( "~" ); - if ( isAllowed ) { - continue; - } - - if ( exceptions.contains( codePoint ) ) { - continue; - } - - try { - String character = new String( Character.toChars( codePoint ) ); - CharArrayWriter hexSequence = new CharArrayWriter(); - byte[] bytes = character.getBytes( UTF_8 ); - for ( int bytesIndex = 0; bytesIndex < bytes.length; bytesIndex++ ) { - char left = Character.forDigit( bytes[ bytesIndex ] >> 4 & 0xF, 16 ); - char right = Character.forDigit( bytes[ bytesIndex ] & 0xF, 16 ); - hexSequence - .append( '%' ) - .append( left ) - .append( right ); - } - String target = character.toString(); - String sequence = hexSequence.toString().toUpperCase(); - encoded = encoded.replace( target, sequence ); - } catch ( UnsupportedEncodingException e ) { - e.printStackTrace(); - } - } - return encoded; - } - - private String decode( String encoded ) { - String decoded = encoded; - Pattern pattern = Pattern.compile( "(%[0-9A-Z]{2})+" ); - Matcher matcher = pattern.matcher( encoded ); - while ( matcher.find() ) { - String encodedChar = matcher.group(); - String[] encodedBytes = encodedChar.split( "%" ); - byte[] bytes = new byte[ encodedBytes.length - 1 ]; - for ( int i = 1; i < encodedBytes.length; i++ ) { - String encodedByte = encodedBytes[ i ]; - bytes[ i - 1 ] = ( byte )Integer.parseInt( encodedByte, 16 ); - } - try { - String decodedChar = new String( bytes, UTF_8 ); - decoded = decoded.replace( encodedChar, decodedChar ); - } catch ( UnsupportedEncodingException e ) { - e.printStackTrace(); - } - } - return decoded; - } - - private String encodeValue( String decodedValue ) { - Set exceptions = new HashSet(); - for ( int i = 0; i < decodedValue.length(); ) { - int codePoint = decodedValue.codePointAt( i ); - i += Character.charCount( codePoint ); - - boolean isIgnorable = false; - if ( codePoint == codePoint( "/" ) || codePoint == codePoint( ":") ) { - isIgnorable = true; - } - - if ( codePoint >= codePoint( "<" ) && codePoint <= codePoint( "@" ) ) { - isIgnorable = true; - } - - if ( codePoint == codePoint( "[" ) || codePoint == codePoint( "]" ) ) { - isIgnorable = true; - } - - if ( codePoint == codePoint( "{" ) || codePoint == codePoint( "}" ) ) { - isIgnorable = true; - } - - if ( isIgnorable ) { - exceptions.add( codePoint ); - } - } - - return encode( decodedValue, exceptions ); - } - - private int codePoint( String character ) { - return character.codePointAt( 0 ); - } - - private String decodeValue( String encodedValue, String decodedName ) { - String decodedValue = null; - - if ( converter != null ) { - try { - decodedValue = converter.convert( encodedValue, decodedName ); - } catch ( ConverterException e ) { - e.printStackTrace(); - } - } - - if ( decodedValue == null ) { - decodedValue = decode( encodedValue ); - } - - return decodedValue; - } - - private Map getCookies( String cookieHeader ) { - Map result = new HashMap(); - String[] cookies = cookieHeader.split( "; " ); - for ( int i = 0; i < cookies.length; i++ ) { - String cookie = cookies[ i ]; - String encodedName = cookie.split( "=" )[ 0 ]; - String decodedName = decode( encodedName ); - - String encodedValue = cookie.substring( cookie.indexOf( '=' ) + 1, cookie.length() ); - String decodedValue = decodeValue( encodedValue, decodedName ); - result.put( decodedName, decodedValue ); - } - return result; - } - - public static class Attributes extends AttributesDefinition { - private Expiration expires; - private String path; - private String domain; - private Boolean secure; - private Boolean httpOnly; - - private Attributes() {} - - public static Attributes empty() { - return new Attributes(); - } - - @Override - Expiration expires() { - return expires; - } - @Override - public Attributes expires( Expiration expires ) { - this.expires = expires; - return this; - } - - @Override - String path() { - return path; - } - @Override - public Attributes path( String path ) { - this.path = path; - return this; - } - - @Override - String domain() { - return domain; - } - @Override - public Attributes domain( String domain ) { - this.domain = domain; - return this; - } - - @Override - Boolean secure() { - return secure; - } - @Override - public Attributes secure( Boolean secure ) { - this.secure = secure; - return this; - } - - @Override - Boolean httpOnly() { - return httpOnly; - } - @Override - public Attributes httpOnly( Boolean httpOnly ) { - this.httpOnly = httpOnly; - return this; - } - - private Attributes merge( AttributesDefinition reference ) { - if ( reference.path() != null ) { - path = reference.path(); - } - if ( reference.domain() != null ) { - domain = reference.domain(); - } - if ( reference.expires() != null ) { - expires = reference.expires(); - } - if ( reference.secure() != null ) { - secure = reference.secure(); - } - if ( reference.httpOnly() != null ) { - httpOnly = reference.httpOnly(); - } - return this; - } - } - - public static abstract class Converter implements ConverterStrategy {} + private static String UTF_8 = "UTF-8"; + private HttpServletRequest request; + private HttpServletResponse response; + private AttributesDefinition defaults = Attributes.empty(); + private ConverterStrategy converter; + private ObjectMapper mapper = new ObjectMapper(); + + private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings"; + private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE); + + private Cookies(HttpServletRequest request, HttpServletResponse response, ConverterStrategy converter) { + this.request = request; + this.response = response; + this.converter = converter; + } + + public static Cookies initFromServlet(HttpServletRequest request, HttpServletResponse response) { + return new Cookies(request, response, null); + } + + @Override + public synchronized String get(String name) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank")); + } + + String cookieHeader = request.getHeader("cookie"); + if (cookieHeader == null) { + return null; + } + + Map cookies = getCookies(cookieHeader); + for (String decodedName : cookies.keySet()) { + if (!name.equals(decodedName)) { + continue; + } + return cookies.get(decodedName); + } + + return null; + } + + @Override + public T get(String name, Class dataType) throws CookieParseException { + String value = get(name); + try { + return mapper.readValue(value, dataType); + } catch (IOException e) { + throw new CookieParseException(e); + } + } + + @Override + public T get(String name, TypeReference typeRef) throws CookieParseException { + String value = get(name); + try { + return mapper.readValue(value, typeRef); + } catch (IOException e) { + throw new CookieParseException(e); + } + } + + @Override + public Map get() { + Map result = new HashMap(); + + String cookieHeader = request.getHeader("cookie"); + if (cookieHeader == null) { + return result; + } + + return getCookies(cookieHeader); + } + + @Override + public synchronized void set(String name, String value, AttributesDefinition attributes) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank")); + } + if (value == null) { + throw new IllegalArgumentException(); + } + if (attributes == null) { + throw new IllegalArgumentException(); + } + + String encodedName = encode(name); + String encodedValue = encodeValue(value); + + StringBuilder header = new StringBuilder(); + header.append(encodedName); + header.append('='); + header.append(encodedValue); + + attributes = extend(Attributes.empty().path("/"), defaults, attributes); + + String path = attributes.path(); + if (path != null && !path.isEmpty()) { + header.append("; Path=").append(path); + } + + Expiration expires = attributes.expires(); + if (expires != null) { + header.append("; Expires=").append(expires.toExpiresString()); + } + + String domain = attributes.domain(); + if (domain != null) { + header.append("; Domain=").append(domain); + } + + Boolean secure = attributes.secure(); + if (Boolean.TRUE.equals(secure)) { + header.append("; Secure"); + } + + Boolean httpOnly = attributes.httpOnly(); + if (Boolean.TRUE.equals(httpOnly)) { + header.append("; HttpOnly"); + } + + if (response.isCommitted()) { + return; + } + + setCookie(header.toString(), response); + } + + @Override + public void set(String name, int value, AttributesDefinition attributes) throws CookieSerializationException { + set(name, String.valueOf(value), attributes); + } + + @Override + public void set(String name, boolean value, AttributesDefinition attributes) throws CookieSerializationException { + set(name, String.valueOf(value), attributes); + } + + @Override + public void set(String name, List value, AttributesDefinition attributes) throws CookieSerializationException { + try { + set(name, mapper.writeValueAsString(value), attributes); + } catch (JsonProcessingException e) { + throw new CookieSerializationException(e); + } + } + + @Override + public void set(String name, CookieValue value, AttributesDefinition attributes) throws CookieSerializationException { + try { + set(name, mapper.writeValueAsString(value), attributes); + } catch (JsonProcessingException e) { + throw new CookieSerializationException(e); + } + } + + @Override + public void set(String name, String value) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank")); + } + if (value == null) { + throw new IllegalArgumentException(); + } + set(name, value, defaults); + } + + @Override + public void set(String name, int value) throws CookieSerializationException { + set(name, value, Attributes.empty()); + } + + @Override + public void set(String name, boolean value) { + set(name, String.valueOf(value)); + } + + @Override + public void set(String name, List value) throws CookieSerializationException { + set(name, value, Attributes.empty()); + } + + @Override + public void set(String name, CookieValue value) throws CookieSerializationException { + set(name, value, Attributes.empty()); + } + + @Override + public void remove(String name, AttributesDefinition attributes) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank")); + } + if (attributes == null) { + throw new IllegalArgumentException(); + } + + set(name, "", extend(attributes, Attributes.empty() + .expires(Expiration.days(-1))) + ); + } + + @Override + public void remove(String name) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank")); + } + remove(name, Attributes.empty()); + } + + @Override + public AttributesDefinition defaults() { + return this.defaults; + } + + @Override + public Cookies withConverter(ConverterStrategy converter) { + return new Cookies(request, response, converter); + } + + private Attributes extend(AttributesDefinition... mergeables) { + Attributes result = Attributes.empty(); + for (AttributesDefinition mergeable : mergeables) { + result.merge(mergeable); + } + return result; + } + + private void setCookie(String cookieValue, HttpServletResponse response) { + response.addHeader("Set-Cookie", cookieValue); + } + + private String encode(String decoded) { + return encode(decoded, new HashSet()); + } + + private String encode(String decoded, Set exceptions) { + String encoded = decoded; + for (int i = 0; i < decoded.length(); ) { + int codePoint = decoded.codePointAt(i); + i += Character.charCount(codePoint); + + boolean isDigit = codePoint >= codePoint("0") && codePoint <= codePoint("9"); + if (isDigit) { + continue; + } + + boolean isAsciiUppercaseLetter = codePoint >= codePoint("A") && codePoint <= codePoint("Z"); + if (isAsciiUppercaseLetter) { + continue; + } + + boolean isAsciiLowercaseLetter = codePoint >= codePoint("a") && codePoint <= codePoint("z"); + if (isAsciiLowercaseLetter) { + continue; + } + + boolean isAllowed = + codePoint == codePoint("!") || codePoint == codePoint("#") || + codePoint == codePoint("$") || codePoint == codePoint("&") || + codePoint == codePoint("'") || codePoint == codePoint("*") || + codePoint == codePoint("+") || codePoint == codePoint("-") || + codePoint == codePoint(".") || codePoint == codePoint("^") || + codePoint == codePoint("_") || codePoint == codePoint("`") || + codePoint == codePoint("|") || codePoint == codePoint("~"); + if (isAllowed) { + continue; + } + + if (exceptions.contains(codePoint)) { + continue; + } + + try { + String character = new String(Character.toChars(codePoint)); + CharArrayWriter hexSequence = new CharArrayWriter(); + byte[] bytes = character.getBytes(UTF_8); + for (byte aByte : bytes) { + char left = Character.forDigit(aByte >> 4 & 0xF, 16); + char right = Character.forDigit(aByte & 0xF, 16); + hexSequence + .append('%') + .append(left) + .append(right); + } + String sequence = hexSequence.toString().toUpperCase(); + encoded = encoded.replace(character, sequence); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + return encoded; + } + + private String decode(String encoded) { + // Decode characters with 3 bytes first, then with 1 byte to fix https://github.com/js-cookie/java-cookie/issues/14 + return decode(decode(encoded, 3), 1); + } + + private String decode(String encoded, Integer bytesPerCharacter) { + String decoded = encoded; + Pattern pattern = Pattern.compile("(%[0-9A-Z]{2}){" + bytesPerCharacter + "}"); + Matcher matcher = pattern.matcher(encoded); + while (matcher.find()) { + String encodedChar = matcher.group(); + String[] encodedBytes = encodedChar.split("%"); + byte[] bytes = new byte[encodedBytes.length - 1]; + for (int i = 1; i < encodedBytes.length; i++) { + String encodedByte = encodedBytes[i]; + bytes[i - 1] = (byte) Integer.parseInt(encodedByte, 16); + } + try { + String decodedChar = new String(bytes, UTF_8); + decoded = decoded.replace(encodedChar, decodedChar); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + return decoded; + } + + private String encodeValue(String decodedValue) { + Set exceptions = new HashSet(); + for (int i = 0; i < decodedValue.length(); ) { + int codePoint = decodedValue.codePointAt(i); + i += Character.charCount(codePoint); + + boolean isIgnorable = false; + if (codePoint == codePoint("/") || codePoint == codePoint(":")) { + isIgnorable = true; + } + + if (codePoint >= codePoint("<") && codePoint <= codePoint("@")) { + isIgnorable = true; + } + + if (codePoint == codePoint("[") || codePoint == codePoint("]")) { + isIgnorable = true; + } + + if (codePoint == codePoint("{") || codePoint == codePoint("}")) { + isIgnorable = true; + } + + if (isIgnorable) { + exceptions.add(codePoint); + } + } + + return encode(decodedValue, exceptions); + } + + private int codePoint(String character) { + return character.codePointAt(0); + } + + private String decodeValue(String encodedValue, String decodedName) { + String decodedValue = null; + + if (converter != null) { + try { + decodedValue = converter.convert(encodedValue, decodedName); + } catch (ConverterException e) { + e.printStackTrace(); + } + } + + if (decodedValue == null) { + decodedValue = decode(encodedValue); + } + + return decodedValue; + } + + private Map getCookies(String cookieHeader) { + Map result = new HashMap(); + String[] cookies = cookieHeader.split("; "); + for (String cookie : cookies) { + String encodedName = cookie.split("=")[0]; + String decodedName = decode(encodedName); + + String encodedValue = cookie.substring(cookie.indexOf('=') + 1); + String decodedValue = decodeValue(encodedValue, decodedName); + result.put(decodedName, decodedValue); + } + return result; + } + + public static class Attributes extends AttributesDefinition { + private Expiration expires; + private String path; + private String domain; + private Boolean secure; + private Boolean httpOnly; + + private Attributes() { + } + + public static Attributes empty() { + return new Attributes(); + } + + @Override + Expiration expires() { + return expires; + } + + @Override + public Attributes expires(Expiration expires) { + this.expires = expires; + return this; + } + + @Override + String path() { + return path; + } + + @Override + public Attributes path(String path) { + this.path = path; + return this; + } + + @Override + String domain() { + return domain; + } + + @Override + public Attributes domain(String domain) { + this.domain = domain; + return this; + } + + @Override + Boolean secure() { + return secure; + } + + @Override + public Attributes secure(Boolean secure) { + this.secure = secure; + return this; + } + + @Override + Boolean httpOnly() { + return httpOnly; + } + + @Override + public Attributes httpOnly(Boolean httpOnly) { + this.httpOnly = httpOnly; + return this; + } + + private Attributes merge(AttributesDefinition reference) { + if (reference.path() != null) { + path = reference.path(); + } + if (reference.domain() != null) { + domain = reference.domain(); + } + if (reference.expires() != null) { + expires = reference.expires(); + } + if (reference.secure() != null) { + secure = reference.secure(); + } + if (reference.httpOnly() != null) { + httpOnly = reference.httpOnly(); + } + return this; + } + } + + public static abstract class Converter implements ConverterStrategy { + } } diff --git a/src/test/java/com/github/jscookie/javacookie/test/unit/CookiesDecodingTest.java b/src/test/java/com/github/jscookie/javacookie/test/unit/CookiesDecodingTest.java index ef5150c..330dce5 100644 --- a/src/test/java/com/github/jscookie/javacookie/test/unit/CookiesDecodingTest.java +++ b/src/test/java/com/github/jscookie/javacookie/test/unit/CookiesDecodingTest.java @@ -1,5 +1,7 @@ package com.github.jscookie.javacookie.test.unit; +import com.github.jscookie.javacookie.Cookies; +import com.github.jscookie.javacookie.test.unit.utils.BaseTest; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -7,31 +9,36 @@ import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; -import com.github.jscookie.javacookie.Cookies; -import com.github.jscookie.javacookie.test.unit.utils.BaseTest; - -@RunWith( MockitoJUnitRunner.class ) +@RunWith(MockitoJUnitRunner.class) public class CookiesDecodingTest extends BaseTest { - private Cookies cookies; + private Cookies cookies; + + @Before + public void before() { + cookies = Cookies.initFromServlet(request, response); + } - @Before - public void before() { - cookies = Cookies.initFromServlet( request, response ); - } + @Test + public void character_not_allowed_in_name_and_value() { + Mockito.when(request.getHeader("cookie")).thenReturn("%3B=%3B"); + String actual = cookies.get(";"); + String expected = ";"; + Assert.assertEquals(expected, actual); + } - @Test - public void character_not_allowed_in_name_and_value() { - Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "%3B=%3B" ); - String actual = cookies.get( ";" ); - String expected = ";"; - Assert.assertEquals( expected, actual ); - } + @Test + public void character_with_3_bytes() { + Mockito.when(request.getHeader("cookie")).thenReturn("c=%E4%BA%AC"); + String actual = cookies.get("c"); + String expected = "京"; + Assert.assertEquals(expected, actual); + } - @Test - public void character_with_3_bytes() { - Mockito.when( request.getHeader( "cookie" ) ).thenReturn( "c=%E4%BA%AC" ); - String actual = cookies.get( "c" ); - String expected = "京"; - Assert.assertEquals( expected, actual ); - } + @Test + public void two_encoded_characters() { + Mockito.when(request.getHeader("cookie")).thenReturn("c=New%20York%2C%20NY"); + String actual = cookies.get("c"); + String expected = "New York, NY"; + Assert.assertEquals(expected, actual); + } } diff --git a/src/test/java/com/github/jscookie/javacookie/test/unit/utils/IntegrationUtils.java b/src/test/java/com/github/jscookie/javacookie/test/unit/utils/IntegrationUtils.java index 4afe8a0..7229d10 100644 --- a/src/test/java/com/github/jscookie/javacookie/test/unit/utils/IntegrationUtils.java +++ b/src/test/java/com/github/jscookie/javacookie/test/unit/utils/IntegrationUtils.java @@ -1,26 +1,26 @@ package com.github.jscookie.javacookie.test.unit.utils; -import java.io.File; - import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import java.io.File; + public class IntegrationUtils { - public static WebArchive createCommonDeployment() { - boolean RECURSIVE_TRUE = true; - return ShrinkWrap.create( WebArchive.class ) - .addPackage( "com.github.jscookie.javacookie" ) - .addPackages( RECURSIVE_TRUE, "com.github.jscookie.javacookie.test.integration" ) - .addAsLibraries( - Maven.resolver() - .loadPomFromFile( "pom.xml" ) - .resolve( - "joda-time:joda-time", - "com.fasterxml.jackson.core:jackson-databind" - ) - .withTransitivity() - .as( File.class ) - ); - } + public static WebArchive createCommonDeployment() { + boolean RECURSIVE_TRUE = true; + return ShrinkWrap.create(WebArchive.class) + .addPackage("com.github.jscookie.javacookie") + .addPackages(RECURSIVE_TRUE, "com.github.jscookie.javacookie.test.integration") + .addAsLibraries( + Maven.resolver() + .loadPomFromFile("pom.xml") + .resolve( + "joda-time:joda-time", + "com.fasterxml.jackson.core:jackson-databind" + ) + .withTransitivity() + .as(File.class) + ); + } }