package io.vertx.ext.web.client.impl;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.Consumer;
import io.netty.handler.codec.http.cookie.Cookie;
import io.vertx.core.http.impl.HttpUtils;
import io.vertx.ext.web.client.spi.CookieStore;
public class CookieStoreImpl implements CookieStore {
private ConcurrentHashMap<Key, Cookie> noDomainCookies;
private ConcurrentSkipListMap<Key, Cookie> domainCookies;
public CookieStoreImpl() {
noDomainCookies = new ConcurrentHashMap<>();
domainCookies = new ConcurrentSkipListMap<>();
}
@Override
public Iterable<Cookie> get(Boolean ssl, String domain, String path) {
assert domain != null && domain.length() > 0;
String cleanPath;
{
String uri = HttpUtils.removeDots(path);
int pos = uri.indexOf('?');
if (pos > -1) {
uri = uri.substring(0, pos);
}
pos = uri.indexOf('#');
if (pos > -1) {
uri = uri.substring(0, pos);
}
cleanPath = uri;
}
TreeMap<String, Cookie> matches = new TreeMap<>();
Consumer<Cookie> adder = c -> {
if (ssl != Boolean.TRUE && c.isSecure()) {
return;
}
if (c.path() != null && !cleanPath.equals(c.path())) {
String cookiePath = c.path();
if (!cookiePath.endsWith("/")) {
cookiePath += '/';
}
if (!cleanPath.startsWith(cookiePath)) {
return;
}
}
matches.put(c.name(), c);
};
for (Cookie c : noDomainCookies.values()) {
adder.accept(c);
}
Key key = new Key(domain, "", "");
String prefix = key.domain.substring(0, 1);
for (Entry<Key, Cookie> entry : domainCookies.tailMap(new Key(prefix, "", ""), true).entrySet()) {
if (entry.getKey().domain.compareTo(key.domain) > 0) {
break;
}
if (!key.domain.startsWith(entry.getKey().domain)) {
continue;
}
adder.accept(entry.getValue());
}
return matches.values();
}
@Override
public CookieStore put(Cookie cookie) {
Key key = new Key(cookie.domain(), cookie.path(), cookie.name());
if (key.domain.equals(Key.NO_DOMAIN)) {
noDomainCookies.put(key, cookie);
return this;
}
domainCookies.put(key, cookie);
return this;
}
@Override
public CookieStore remove(Cookie cookie) {
Key key = new Key(cookie.domain(), cookie.path(), cookie.name());
if (key.domain.equals(Key.NO_DOMAIN)) {
noDomainCookies.remove(key);
} else {
domainCookies.remove(key);
}
return this;
}
private static class Key implements Comparable<Key> {
private static final String NO_DOMAIN = "";
private final String domain;
private final String path;
private final String name;
public Key(String domain, String path, String name) {
if (domain == null || domain.length() == 0) {
this.domain = NO_DOMAIN;
} else {
while (domain.charAt(0) == '.') {
domain = domain.substring(1);
}
while (domain.charAt(domain.length() - 1) == '.') {
domain = domain.substring(0, domain.length() - 1);
}
if (domain.length() == 0) {
this.domain = NO_DOMAIN;
} else {
String[] tokens = domain.split("\\.");
String tmp;
for (int i = 0, j = tokens.length - 1; i < tokens.length / 2; ++i, --j) {
tmp = tokens[j];
tokens[j] = tokens[i];
tokens[i] = tmp;
}
this.domain = String.join(".", tokens);
}
}
this.path = path == null ? "" : path;
this.name = name;
}
@Override
public int compareTo(Key o) {
int ret = domain.compareTo(o.domain);
if (ret == 0)
ret = path.compareTo(o.path);
if (ret == 0)
ret = name.compareTo(o.name);
return ret;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((domain == null) ? 0 : domain.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((path == null) ? 0 : path.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Key other = (Key) obj;
if (domain == null) {
if (other.domain != null)
return false;
} else if (!domain.equals(other.domain))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (path == null) {
return other.path == null;
} else return path.equals(other.path);
}
}
}