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

io.datakernel.http.HttpCookie Maven / Gradle / Ivy

Go to download

High-performance asynchronous HTTP clients and servers collection. Package contains a bunch of different built-in servlets for request dispatching, loading of a static content, etc.

There is a newer version: 3.1.0
Show newest version
/*
 * Copyright (C) 2015-2018 SoftIndex LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.datakernel.http;

import io.datakernel.bytebuf.ByteBuf;
import io.datakernel.exception.ParseException;

import java.util.Date;
import java.util.List;

import static io.datakernel.bytebuf.ByteBufStrings.*;
import static io.datakernel.http.HttpUtils.decodeUnsignedInt;
import static io.datakernel.http.HttpUtils.skipSpaces;

// RFC 6265
/*
 set-cookie-header  = "Set-Cookie:" SP set-cookie-string
 set-cookie-string  = cookie-pair *( ";" SP cookie-av )
 cookie-header      = "Cookie:" OWS cookie-string OWS
 cookie-string      = cookie-pair *( ";" SP cookie-pair )

 cookie-pair        = cookie-name "=" cookie-value
 cookie-name        = 1*
 cookie-value       = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )

 cookie-octet       = any CHAR except [",;\SPACE] and CTLs
 separators         = "(" | ")" | "<" | ">" | "@"
                    | "," | ";" | ":" | "\" | <">
                    | "/" | "[" | "]" | "?" | "="
                    | "{" | "}" | SP | HT
 CTLs               = [\u0000...\0032, DEL]
*/
public final class HttpCookie {
	private abstract static class AvHandler {
		protected abstract void handle(HttpCookie cookie, byte[] bytes, int start, int end) throws ParseException;
	}

	private static final byte[] EXPIRES = encodeAscii("Expires");
	private static final int EXPIRES_HC = 433574931;
	private static final byte[] MAX_AGE = encodeAscii("Max-Age");
	private static final int MAX_AGE_HC = -1709216267;
	private static final byte[] DOMAIN = encodeAscii("Domain");
	private static final int DOMAIN_HC = -438693883;
	private static final byte[] PATH = encodeAscii("Path");
	private static final int PATH_HC = 4357030;
	private static final byte[] HTTPONLY = encodeAscii("HttpOnly");
	private static final int SECURE_HC = -18770248;
	private static final byte[] SECURE = encodeAscii("Secure");
	private static final int HTTP_ONLY_HC = -1939729611;

	private final String name;
	private String value;
	private Date expirationDate;
	private int maxAge = -1;
	private String domain;
	private String path = "/";
	private boolean secure;
	private boolean httpOnly;
	private String extension;

	// region builders
	private HttpCookie(String name, String value) {
		this.name = name;
		this.value = value;
	}

	public static HttpCookie of(String name, String value) {
		return new HttpCookie(name, value);
	}

	public static HttpCookie of(String name) {
		return new HttpCookie(name, null);
	}

	public HttpCookie withValue(String value) {
		setValue(value);
		return this;
	}

	public HttpCookie withExpirationDate(Date expirationDate) {
		// 
		setExpirationDate(expirationDate);
		return this;
	}

	public HttpCookie withMaxAge(int maxAge) {
		// %x31-39 ; digits 1 through 9
		setMaxAge(maxAge);
		return this;
	}

	public HttpCookie withDomain(String domain) {
		// https://tools.ietf.org/html/rfc1034#section-3.5
		setDomain(domain);
		return this;
	}

	public HttpCookie withPath(String path) {
		// 
		setPath(path);
		return this;
	}

	public HttpCookie withSecure(boolean secure) {
		setSecure(secure);
		return this;
	}

	public HttpCookie withHttpOnly(boolean httpOnly) {
		setHttpOnly(httpOnly);
		return this;
	}

	public HttpCookie withExtension(String extension) {
		// any CHAR except CTLs or ";"
		setExtension(extension);
		return this;
	}
	// endregion

	public String getName() {
		return name;
	}

	public String getValue() {
		return value;
	}

	public void setValue(String value) {
		this.value = value;
	}

	public Date getExpirationDate() {
		return expirationDate;
	}

	public void setExpirationDate(Date expirationDate) {
		this.expirationDate = expirationDate;
	}

	public int getMaxAge() {
		return maxAge;
	}

	public void setMaxAge(int maxAge) {
		this.maxAge = maxAge;
	}

	public String getDomain() {
		return domain;
	}

	public void setDomain(String domain) {
		this.domain = domain;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}

	public boolean isSecure() {
		return secure;
	}

	public void setSecure(boolean secure) {
		this.secure = secure;
	}

	public boolean isHttpOnly() {
		return httpOnly;
	}

	public void setHttpOnly(boolean httpOnly) {
		this.httpOnly = httpOnly;
	}

	public String getExtension() {
		return extension;
	}

	public void setExtension(String extension) {
		this.extension = extension;
	}
	// endregion

	// region etc
	static void parse(String cookieString, List cookies) throws ParseException {
		byte[] bytes = encodeAscii(cookieString);
		parse(bytes, 0, bytes.length, cookies);
	}

	static void parse(ByteBuf buf, List cookies) throws ParseException {
		parse(buf.array(), buf.readPosition(), buf.writePosition(), cookies);
	}

	static void parse(byte[] bytes, int pos, int end, List cookies) throws ParseException {
		try {
			HttpCookie cookie = new HttpCookie("", "");
			while (pos < end) {
				pos = skipSpaces(bytes, pos, end);
				int keyStart = pos;
				while (pos < end && bytes[pos] != ';') {
					pos++;
				}
				int valueEnd = pos;
				int equalSign = -1;
				for (int i = keyStart; i < valueEnd; i++) {
					if (bytes[i] == '=') {
						equalSign = i;
						break;
					}
				}
				AvHandler handler = getCookieHandler(hashCodeLowerCaseAscii
						(bytes, keyStart, (equalSign == -1 ? valueEnd : equalSign) - keyStart));
				if (equalSign == -1 && handler == null) {
					cookie.setExtension(decodeAscii(bytes, keyStart, valueEnd - keyStart));
				} else if (handler == null) {
					String key = decodeAscii(bytes, keyStart, equalSign - keyStart);
					String value;
					if (bytes[equalSign + 1] == '\"' && bytes[valueEnd - 1] == '\"') {
						value = decodeAscii(bytes, equalSign + 2, valueEnd - equalSign - 3);
					} else {
						value = decodeAscii(bytes, equalSign + 1, valueEnd - equalSign - 1);
					}
					cookie = new HttpCookie(key, value);
					cookies.add(cookie);
				} else {
					handler.handle(cookie, bytes, equalSign + 1, valueEnd);
				}
				pos = valueEnd + 1;
			}
		} catch (RuntimeException e) {
			throw new ParseException();
		}
	}

	static void renderSimple(List cookies, ByteBuf buf) {
		int pos = renderSimple(cookies, buf.array(), buf.writePosition());
		buf.writePosition(pos);
	}

	static int renderSimple(List cookies, byte[] bytes, int pos) {
		for (int i = 0; i < cookies.size(); i++) {
			HttpCookie cookie = cookies.get(i);
			encodeAscii(bytes, pos, cookie.name);
			pos += cookie.name.length();

			if (cookie.value != null) {
				encodeAscii(bytes, pos, "=");
				pos += 1;
				encodeAscii(bytes, pos, cookie.value);
				pos += cookie.value.length();
			}

			if (i != cookies.size() - 1) {
				encodeAscii(bytes, pos, "; ");
				pos += 2;
			}
		}
		return pos;
	}

	static void parseSimple(String string, List cookies) throws ParseException {
		byte[] bytes = encodeAscii(string);
		parseSimple(bytes, 0, bytes.length, cookies);
	}

	static void parseSimple(byte[] bytes, int pos, int end, List cookies) throws ParseException {
		try {
			while (pos < end) {
				pos = skipSpaces(bytes, pos, end);
				int keyStart = pos;
				while (pos < end && !(bytes[pos] == ';' || bytes[pos] == ',')) {
					pos++;
				}
				int valueEnd = pos;
				int equalSign = -1;
				for (int i = keyStart; i < valueEnd; i++) {
					if (bytes[i] == '=') {
						equalSign = i;
						break;
					}
				}

				if (equalSign == -1) {
					String key = decodeAscii(bytes, keyStart, valueEnd - keyStart);
					cookies.add(new HttpCookie(key, null));
				} else {
					String key = decodeAscii(bytes, keyStart, equalSign - keyStart);
					String value;
					if (bytes[equalSign + 1] == '\"' && bytes[valueEnd - 1] == '\"') {
						value = decodeAscii(bytes, equalSign + 2, valueEnd - equalSign - 3);
					} else {
						value = decodeAscii(bytes, equalSign + 1, valueEnd - equalSign - 1);
					}

					cookies.add(new HttpCookie(key, value));
				}

				pos = valueEnd + 1;
			}
		} catch (RuntimeException e) {
			throw new ParseException();
		}
	}

	static void renderFull(List cookies, ByteBuf buf) {
		for (int i = 0; i < cookies.size(); i++) {
			cookies.get(i).renderFull(buf);
			if (i < cookies.size() - 1) {
				buf.put((byte) ',');
				buf.put((byte) ' ');
			}
		}
	}

	void renderFull(ByteBuf buf) {
		putAscii(buf, name);
		putAscii(buf, "=");
		if (value != null) {
			putAscii(buf, value);
		}
		if (expirationDate != null) {
			putAscii(buf, "; ");
			buf.put(EXPIRES);
			putAscii(buf, "=");
			HttpDate.render(expirationDate.getTime(), buf);
		}
		if (maxAge >= 0) {
			putAscii(buf, "; ");
			buf.put(MAX_AGE);
			putAscii(buf, "=");
			putDecimal(buf, maxAge);
		}
		if (domain != null) {
			putAscii(buf, "; ");
			buf.put(DOMAIN);
			putAscii(buf, "=");
			putAscii(buf, domain);
		}
		if (!(path == null || path.equals("/"))) {
			putAscii(buf, "; ");
			buf.put(PATH);
			putAscii(buf, "=");
			putAscii(buf, path);
		}
		if (secure) {
			putAscii(buf, "; ");
			buf.put(SECURE);
		}
		if (httpOnly) {
			putAscii(buf, "; ");
			buf.put(HTTPONLY);
		}
		if (extension != null) {
			putAscii(buf, "; ");
			putAscii(buf, extension);
		}
	}

	private static AvHandler getCookieHandler(int hash) {
		switch (hash) {
			case EXPIRES_HC:
				return new AvHandler() {
					@Override
					protected void handle(HttpCookie cookie, byte[] bytes, int start, int end) throws ParseException {
						cookie.setExpirationDate(parseExpirationDate(bytes, start));
					}
				};
			case MAX_AGE_HC:
				return new AvHandler() {
					@Override
					protected void handle(HttpCookie cookie, byte[] bytes, int start, int end) throws ParseException {
						cookie.setMaxAge(decodeUnsignedInt(bytes, start, end - start));
					}
				};
			case DOMAIN_HC:
				return new AvHandler() {
					@Override
					protected void handle(HttpCookie cookie, byte[] bytes, int start, int end) {
						cookie.setDomain(decodeAscii(bytes, start, end - start));
					}
				};
			case PATH_HC:
				return new AvHandler() {
					@Override
					protected void handle(HttpCookie cookie, byte[] bytes, int start, int end) {
						cookie.setPath(decodeAscii(bytes, start, end - start));
					}
				};
			case SECURE_HC:
				return new AvHandler() {
					@Override
					protected void handle(HttpCookie cookie, byte[] bytes, int start, int end) {
						cookie.setSecure(true);
					}
				};
			case HTTP_ONLY_HC:
				return new AvHandler() {
					@Override
					protected void handle(HttpCookie cookie, byte[] bytes, int start, int end) {
						cookie.setHttpOnly(true);
					}
				};

		}
		return null;
	}

	private static Date parseExpirationDate(byte[] bytes, int start) throws ParseException {
		long timestamp = HttpDate.parse(bytes, start);
		return new Date(timestamp);
	}

	@Override
	public String toString() {
		return "HttpCookie{" +
				"name='" + name + '\'' +
				", value='" + value + '\'' + '}';
	}
	// endregion
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy