All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jscookie.javacookie.Cookies Maven / Gradle / Ivy

The newest version!
package com.github.jscookie.javacookie;

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.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 {}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy