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

org.refcodes.web.ResponseCookie Maven / Gradle / Ivy

package org.refcodes.web;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.time.DateTimeException;
import java.time.Instant;
import java.util.Date;
import java.util.Locale;
import java.util.StringTokenizer;

import org.refcodes.data.Delimiter;
import org.refcodes.data.Encoding;
import org.refcodes.mixin.DomainAccessor.DomainBuilder;
import org.refcodes.mixin.DomainAccessor.DomainProperty;
import org.refcodes.mixin.PathAccessor.PathBuilder;
import org.refcodes.mixin.PathAccessor.PathProperty;
import org.refcodes.struct.Property;
import org.refcodes.struct.PropertyImpl;
import org.refcodes.struct.PropertyImpl.PropertyBuilderImpl;
import org.refcodes.time.DateFormat;
import org.refcodes.time.DateFormats;
import org.refcodes.time.TimeUnit;

/**
 * As of "HTTP cookies explained - NCZOnline": "... There is some confusion over
 * encoding of a cookie value. The commonly held belief is that cookie values
 * must be URL-encoded, but this is a fallacy even though it is the de facto
 * implementation. The original specification indicates that only three types of
 * characters must be encoded: semicolon, comma, and white space. The
 * specification indicates that URL encoding may be used but stops short of
 * requiring it. The RFC makes no mention of encoding whatsoever. Still, almost
 * all implementations perform some sort of URL encoding on cookie values. In
 * the case of name=value formats, the name and value are typically encoded
 * separately while the equals sign is left as is. ..." Therefore we use URL
 * encoding to make life easier and not fall into the trap of unescaped values.
 * 
 * The {@link ResponseCookie} represents response cookies: We use URL encoding /
 * decoding for the cookie value (regarding {@link #fromHttpCookie(String)} and
 * {@link #toHttpCookie()}) to make life easier and not fall into the trap of
 * unescaped values.
 * 
 * @see "https://www.nczonline.net/blog/2009/05/05/http-cookies-explained"
 */
public class ResponseCookie extends PropertyBuilderImpl implements Cookie, PathProperty, PathBuilder, DomainProperty, DomainBuilder {

	// /////////////////////////////////////////////////////////////////////////
	// VARIABLES:
	// /////////////////////////////////////////////////////////////////////////

	private Date _expiresDate = null;
	private int _maxAge = -1;
	private String _domain = null;
	private String _path = null;
	private boolean _isSecure = false;
	private boolean _isHttpOnly = false;
	private String _version = null;

	// /////////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Instantiates a new response cookie impl.
	 */
	public ResponseCookie() {}

	/**
	 * Constructs a {@link ResponseCookie}.
	 * 
	 * @param aCookieName The name of the cookie.
	 * @param aValue The value for the cookie.
	 */
	public ResponseCookie( String aCookieName, String aValue ) {
		super( aCookieName, aValue );
	}

	/**
	 * Constructs a {@link ResponseCookie} from the given HTTP cookie.
	 *
	 * @param aHttpCookie The text as being found in the according HTTP header
	 *        field.
	 */
	public ResponseCookie( String aHttpCookie ) {
		fromHttpCookie( aHttpCookie );
	}

	/**
	 * Constructs a {@link ResponseCookie}.
	 * 
	 * @param aCookie The {@link Cookie} from which to take the data.
	 */
	public ResponseCookie( Cookie aCookie ) {
		super( aCookie.getKey(), aCookie.getValue() );
		if ( aCookie instanceof ResponseCookie theResponseCookie ) {
			setDomain( theResponseCookie.getDomain() );
			setPath( theResponseCookie.getPath() );
			setExpiresDate( theResponseCookie.getExpiresDate() );
			setHttpOnly( theResponseCookie.isHttpOnly() );
			setSecure( theResponseCookie.isSecure() );
			setMaxAge( theResponseCookie.getMaxAge() );
			setVersion( theResponseCookie.getVersion() );
		}
	}

	/**
	 * Constructs a {@link ResponseCookie}.
	 * 
	 * @param aCookieName The name for the cookie
	 * @param aValue The value of the cookie
	 * @param aExpiresDate The expiration date of the cookie
	 * @param aDomain The domain of the cookie
	 * @param aPath The path of the cookie
	 * @param isSecure The secure flag of the cookie
	 * @param isHttpOnly The HTTP-only flag of the cookie
	 * @param aVersion The version of the cookie.
	 */
	public ResponseCookie( String aCookieName, String aValue, Date aExpiresDate, String aDomain, String aPath, boolean isSecure, boolean isHttpOnly, String aVersion ) {
		super( aCookieName, aValue );
		_expiresDate = aExpiresDate;
		_domain = aDomain;
		_path = aPath;
		_isSecure = isSecure;
		_isHttpOnly = isHttpOnly;
		_version = aVersion;
	}

	/**
	 * Constructs a {@link ResponseCookie}.
	 * 
	 * @param aCookieName The name for the cookie
	 * @param aValue The value of the cookie
	 * @param aMaxAge Sets the Max-Age (seconds) of this cookie.
	 * @param aDomain The domain of the cookie
	 * @param aPath The path of the cookie
	 * @param isSecure The secure flag of the cookie
	 * @param isHttpOnly The HTTP-only flag of the cookie
	 * @param aVersion The version of the cookie.
	 */
	public ResponseCookie( String aCookieName, String aValue, int aMaxAge, String aDomain, String aPath, boolean isSecure, boolean isHttpOnly, String aVersion ) {
		super( aCookieName, aValue );
		_maxAge = aMaxAge;
		_domain = aDomain;
		_path = aPath;
		_isSecure = isSecure;
		_isHttpOnly = isHttpOnly;
		_version = aVersion;
	}

	// /////////////////////////////////////////////////////////////////////////
	// METHODS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Returns the Max-Age (seconds) for this cookie.
	 * 
	 * @return The Max-Age.
	 */
	public int getMaxAge() {
		return _maxAge;
	}

	/**
	 * Sets the Max-Age (seconds) of this cookie.
	 *
	 * @param aMaxAge Sets the Max-Age.
	 */
	public void setMaxAge( int aMaxAge ) {
		_maxAge = aMaxAge;
	}

	/**
	 * Returns the version this cookie.
	 * 
	 * @return The version.
	 */
	public String getVersion() {
		return _version;
	}

	/**
	 * Sets the version of this cookie.
	 *
	 * @param aVersion Sets the version.
	 */
	public void setVersion( String aVersion ) {
		_version = aVersion;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getPath() {
		return _path;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setPath( String aPath ) {
		_path = aPath;
	}

	/**
	 * Returns the expiration date of this cookie.
	 * 
	 * @return The expiration date.
	 */
	public Date getExpiresDate() {
		return _expiresDate;
	}

	/**
	 * Sets the expiration date of this cookie.
	 *
	 * @param aExpireDate The expiration date.
	 */
	public void setExpiresDate( Date aExpireDate ) {
		_expiresDate = aExpireDate;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getDomain() {
		return _domain;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setDomain( String aDomain ) {
		_domain = aDomain;
	}

	/**
	 * Sets whether it be a secure HTTP cookie. Such a cookie only be
	 * transferred via HTTPS.
	 * 
	 * @param isSecure True in case of being a secure only cookie.
	 */
	public void setSecure( boolean isSecure ) {
		_isSecure = isSecure;
	}

	/**
	 * Returns true if we have a secure HTTP cookie. Such a cookie only be
	 * transferred via HTTPS.
	 * 
	 * @return True in case of being a secure only cookie.
	 */
	public boolean isSecure() {
		return _isSecure;
	}

	/**
	 * Returns true if we have an HTTP only cookie. Such a cookie cannot be
	 * accessed client-side (via JavaScript).
	 * 
	 * @return True in case of being an HTTP only cookie.
	 */
	public boolean isHttpOnly() {
		return _isHttpOnly;
	}

	/**
	 * Sets whether it be an HTTP only cookie. Such a cookie cannot be accessed
	 * client-side (via JavaScript).
	 * 
	 * @param isHttpOnly True in case of being an HTTP only cookie.
	 */
	public void setHttpOnly( boolean isHttpOnly ) {
		_isHttpOnly = isHttpOnly;
	}

	/**
	 * Sets the expires date to the current time plus the provided time. Use
	 * {@link #getExpiresDate()} to retrieve the resulting effective
	 * {@link Date}.
	 * 
	 * @param aTimeUnit The {@link TimeUnit} of the provided time
	 * @param aTime The provided time after which to expire
	 */
	public void setExpiresAfter( TimeUnit aTimeUnit, long aTime ) {
		final long theMilliseconds = TimeUnit.toMilliseconds( aTimeUnit, aTime );
		Date theExpiresDate = new Date();
		theExpiresDate = new Date( theExpiresDate.getTime() + theMilliseconds );
		setExpiresDate( theExpiresDate );
	}

	/**
	 * Sets the expiration date of this cookie and returns this instance as of
	 * the Builder-Pattern.
	 *
	 * @param aExpiresDate the expires date
	 * 
	 * @return This instance as of the Builder-Pattern.
	 */
	public ResponseCookie withExpiresDate( Date aExpiresDate ) {
		setExpiresDate( aExpiresDate );
		return this;
	}

	/**
	 * Sets the path of the cookie. {@inheritDoc}
	 */
	@Override
	public ResponseCookie withPath( String aPath ) {
		setPath( aPath );
		return this;
	}

	/**
	 * Sets the domain for this cookie. {@inheritDoc}
	 */
	@Override
	public ResponseCookie withDomain( String aDomain ) {
		setDomain( aDomain );
		return this;
	}

	/**
	 * Sets whether it be an HTTP only cookie. Such a cookie cannot be accessed
	 * client-side (via JavaScript). Returns this instance as of the builder
	 * pattern.
	 * 
	 * @param isHttpOnly True in case of being an HTTP only cookie.
	 * 
	 * @return Returns this instance as of the Builder-Pattern.
	 */
	public ResponseCookie withHttpOnly( boolean isHttpOnly ) {
		setHttpOnly( isHttpOnly );
		return this;
	}

	/**
	 * Builder method for the method {@link #setExpiresAfter(TimeUnit, long)}.
	 * 
	 * @param aTimeUnit The {@link TimeUnit} of the provided time
	 * @param aTime The provided time after which to expire
	 * 
	 * @return This cookie instance for further configuration.
	 */
	public ResponseCookie withExpiresAfter( TimeUnit aTimeUnit, long aTime ) {
		setExpiresAfter( aTimeUnit, aTime );
		return this;
	}

	/**
	 * Builder method for {@link #setVersion(String)}.
	 *
	 * @param aVersion Sets the version.
	 * 
	 * @return This instance as of the Builder-Pattern.
	 */
	public ResponseCookie withVersion( String aVersion ) {
		setVersion( aVersion );
		return this;
	}

	/**
	 * Builder method for {@link #setMaxAge(int)}.
	 *
	 * @param aMaxAge Sets the Max-Age.
	 * 
	 * @return This instance as of the Builder-Pattern.
	 */
	public ResponseCookie withMaxAge( int aMaxAge ) {
		setMaxAge( aMaxAge );
		return this;
	}

	/**
	 * Sets whether it be a secure HTTP cookie. Such a cookie only be
	 * transferred via HTTPS. Returns this instance as of the Builder-Pattern.
	 * 
	 * @param isSecure True in case of being a secure only cookie.
	 * 
	 * @return Returns this instance as of the Builder-Pattern.
	 */
	public ResponseCookie withSecure( boolean isSecure ) {
		setSecure( isSecure );
		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ResponseCookie withHttpCookie( String aCookie ) {
		fromHttpCookie( aCookie );
		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void fromHttpCookie( String aHttpCookie ) {
		final StringTokenizer theTokenizer = new StringTokenizer( aHttpCookie, Delimiter.COOKIE_PROPERTIES.getChar() + "" );
		String eTupel = theTokenizer.nextToken();
		Property eProperty = new PropertyImpl( eTupel );
		setKey( eProperty.getKey() );

		// Cookie value URL decoding -->
		try {
			setValue( URLDecoder.decode( eProperty.getValue(), Encoding.UTF_8.getCode() ) );
		}
		catch ( UnsupportedEncodingException ignore ) {
			setValue( eProperty.getValue() );
		}
		// <-- Cookie value URL decoding

		CookieAttribute eCookieKey;
		while ( theTokenizer.hasMoreTokens() ) {
			eTupel = theTokenizer.nextToken();
			if ( eTupel.equalsIgnoreCase( CookieAttribute.SECURE.getKey() ) ) {
				eCookieKey = CookieAttribute.SECURE;
			}
			else if ( eTupel.equalsIgnoreCase( CookieAttribute.HTTPONLY.getKey() ) ) {
				eCookieKey = CookieAttribute.HTTPONLY;
			}
			else {
				eProperty = new PropertyImpl( eTupel );
				eCookieKey = CookieAttribute.fromKey( eProperty.getKey() );
			}
			if ( eCookieKey != null ) { // Ignore "null" Cookie key:
				switch ( eCookieKey ) {
				case DOMAIN -> setDomain( eProperty.getValue() );
				case EXPIRES -> {
					try {
						setExpiresDate( DateFormats.COOKIE_DATE_FORMATS.toDate( eProperty.getValue() ) );
					}
					catch ( DateTimeException e ) {
						throw new IllegalArgumentException( "Encountered unparsable date <" + eProperty.getValue() + "> for key <" + eCookieKey.getKey() + ">.", e );
					}
				}
				case HTTPONLY -> setHttpOnly( true );
				case PATH -> setPath( eProperty.getValue() );
				case SECURE -> setSecure( true );
				case VERSION -> setVersion( eProperty.getValue() );
				case MAX_AGE -> {
					int theMaxAgeInSeconds = -1;
					try {
						theMaxAgeInSeconds = Integer.parseInt( eProperty.getValue() );
						// setExpiresAfter( TimeUnit.SECOND, theMaxAgeInSeconds );
						setMaxAge( theMaxAgeInSeconds );
					}
					catch ( NumberFormatException e ) {
						throw new IllegalArgumentException( "Encountered unparsable max-age <" + eProperty.getValue() + "> for key <" + eCookieKey.getKey() + ">.", e );
					}
				}
				default -> {
				}
				}
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toHttpCookie() {
		// Cookie value URL encoding |-->
		String theValue = null;
		try {
			theValue = URLEncoder.encode( getValue(), Encoding.UTF_8.getCode() );
		}
		catch ( UnsupportedEncodingException ignore ) {
			theValue = getValue();
		}
		// Cookie value URL encoding <--|

		String theHttpCookie = getKey() + Delimiter.COOKIE_TUPEL.getChar() + theValue;
		if ( getExpiresDate() != null ) {
			theHttpCookie += Delimiter.COOKIE_PROPERTIES.getChar() + CookieAttribute.EXPIRES.getKey() + Delimiter.COOKIE_TUPEL.getChar() + DateFormat.NETSCAPE_COOKIE_DATE_FORMAT.getFormatter().withLocale( Locale.ENGLISH ).format( Instant.ofEpochMilli( getExpiresDate().getTime() ) );
		}
		if ( getMaxAge() != -1 ) {
			theHttpCookie += Delimiter.COOKIE_PROPERTIES.getChar() + CookieAttribute.MAX_AGE.getKey() + Delimiter.COOKIE_TUPEL.getChar() + getMaxAge();
		}
		if ( getDomain() != null ) {
			theHttpCookie += Delimiter.COOKIE_PROPERTIES.getChar() + CookieAttribute.DOMAIN.getKey() + Delimiter.COOKIE_TUPEL.getChar() + getDomain();
		}
		if ( getPath() != null ) {
			theHttpCookie += Delimiter.COOKIE_PROPERTIES.getChar() + CookieAttribute.PATH.getKey() + Delimiter.COOKIE_TUPEL.getChar() + getPath();
		}
		if ( isSecure() ) {
			theHttpCookie += Delimiter.COOKIE_PROPERTIES.getChar() + CookieAttribute.SECURE.getKey();
		}
		if ( isHttpOnly() ) {
			theHttpCookie += Delimiter.COOKIE_PROPERTIES.getChar() + CookieAttribute.HTTPONLY.getKey();
		}
		if ( getVersion() != null ) {
			theHttpCookie += Delimiter.COOKIE_PROPERTIES.getChar() + CookieAttribute.VERSION.getKey() + Delimiter.COOKIE_TUPEL.getChar() + getVersion();
		}
		return theHttpCookie;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {
		return getClass().getName() + "@[key=" + _key + ", value=" + _value + ", expiresDate=" + _expiresDate + ", maxAge=" + _maxAge + ", domain=" + _domain + ", path=" + _path + ", isSecure=" + _isSecure + ", isHttpOnly=" + _isHttpOnly + ", version=" + _version + "]";
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy