/*
ESXX - The friendly ECMAscript/XML Application Server
Copyright (C) 2007-2015 Martin Blom <martin@blom.org>
This program is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.esxx.js.protocol;
import java.util.*;
import org.apache.http.client.*;
import org.apache.http.cookie.*;
import org.apache.http.impl.cookie.*;
import org.esxx.ESXXException;
import org.esxx.js.*;
import org.mozilla.javascript.*;
class CookieJar
implements CookieStore {
public CookieJar(JSURI jsuri) {
this.jsuri = jsuri;
}
@Override public synchronized void addCookie(Cookie cookie) {
if (cookie == null) {
return;
}
try {
Context cx = Context.getCurrentContext();
Scriptable jar = jsuri.getCookieJar(cx, jsuri.getURI());
if (jar != null) {
// Clear equivalent cookies
purge(cx, jar, null, cookie.getName(), cookie.getDomain());
// Add new cookie unless it has already expired
if (!cookie.isExpired(new Date())) {
Object len = jar.get("length", jar);
if (len != Scriptable.NOT_FOUND) {
int length = (int) Context.toNumber(len);
jar.put(length, jar, cookieToScriptable(cx, cookie));
}
}
}
}
catch (RuntimeException ex) {
throw new ESXXException("Failed to add cookie: " + ex.getMessage(), ex);
}
}
@Override public synchronized void clear() {
try {
Context cx = Context.getCurrentContext();
Scriptable jar = jsuri.getCookieJar(cx, jsuri.getURI());
if (jar != null) {
jar.put("length", jar, 0);
}
}
catch (RuntimeException ex) {
throw new ESXXException("Failed to clear cookies: " + ex.getMessage(), ex);
}
}
@Override public synchronized boolean clearExpired(Date date) {
try {
Context cx = Context.getCurrentContext();
Scriptable jar = jsuri.getCookieJar(cx, jsuri.getURI());
return purge(cx, jar, date, null, null);
}
catch (RuntimeException ex) {
throw new ESXXException("Failed to purge cookies: " + ex.getMessage(), ex);
}
}
@Override public synchronized List<Cookie> getCookies() {
ArrayList<Cookie> result = new ArrayList<Cookie>();
try {
Context cx = Context.getCurrentContext();
Scriptable jar = jsuri.getCookieJar(cx, jsuri.getURI());
if (jar != null) {
Object len = jar.get("length", jar);
if (len != Scriptable.NOT_FOUND) {
int length = (int) Context.toNumber(len);
for (int i = 0; i < length; ++i) {
Object o = jar.get(i, jar);
if (o instanceof Scriptable) {
result.add(scriptableToCookie(cx, (Scriptable) o));
}
}
}
}
return result;
}
catch (RuntimeException ex) {
throw new ESXXException("Failed to retrieve cookies: " + ex.getMessage(), ex);
}
}
/** Purges cookies from the cookie jar.
*
* @param cx A Context
* @param jar The Scriptable cookie jar
* @param date If not null, expired cookies (given this Date) will be purged
* @param name If not null, all cookies matching name and domain will be purged
* @param domain The cookie domain. Domain names are case insensitive and null equals ""
*
* @returns true if one or more cookies were purged
*/
private boolean purge(Context cx, Scriptable jar, Date date, String name, String domain) {
boolean purged = false;
if (domain == null) {
domain = "";
}
if (jar != null) {
Object len = jar.get("length", jar);
if (len != Scriptable.NOT_FOUND) {
int length = (int) Context.toNumber(len);
int j = 0;
for (int i = 0; i < length; ++i) {
Object o = jar.get(i, jar);
if (o instanceof Scriptable) {
Scriptable cookie = (Scriptable) o;
Object edate = cookie.get(ClientCookie.EXPIRES_ATTR, cookie);
Object cname = cookie.get("name", cookie);
Object cdomain = cookie.get("domain", cookie);
if (cdomain == Scriptable.NOT_FOUND) {
cdomain = "";
}
else {
cdomain = Context.toString(cdomain);
}
if (date != null &&
edate != Scriptable.NOT_FOUND &&
Context.toNumber(edate) <= date.getTime()) {
// Cookie has expired
purged = true;
}
else if (name != null &&
cname != Scriptable.NOT_FOUND &&
Context.toString(cname).equals(name) &&
domain.equalsIgnoreCase((String) cdomain)) {
// Cookie matches the name/domain parameters
purged = true;
}
else {
if (i != j) {
jar.put(j, jar, o);
}
++j;
}
}
}
jar.put("length", jar, j);
}
}
return purged;
}
private Scriptable cookieToScriptable(Context cx, Cookie cookie) {
Scriptable js = cx.newObject(jsuri);
Scriptable raw = cx.newObject(js);
js.put("raw", js, raw);
setValue(cx, cookie, js, raw, "name", cookie.getName());
setValue(cx, cookie, js, raw, "value", cookie.getValue());
setValue(cx, cookie, js, raw, ClientCookie.COMMENT_ATTR, cookie.getComment());
setValue(cx, cookie, js, raw, ClientCookie.COMMENTURL_ATTR, cookie.getCommentURL());
setValue(cx, cookie, js, raw, ClientCookie.DISCARD_ATTR, null);
setValue(cx, cookie, js, raw, ClientCookie.DOMAIN_ATTR, cookie.getDomain());
setValue(cx, cookie, js, raw, ClientCookie.EXPIRES_ATTR, cookie.getExpiryDate());
setValue(cx, cookie, js, raw, ClientCookie.MAX_AGE_ATTR, null);
setValue(cx, cookie, js, raw, ClientCookie.PATH_ATTR, cookie.getPath());
setValue(cx, cookie, js, raw, ClientCookie.PORT_ATTR, cookie.getPorts());
setValue(cx, cookie, js, raw, ClientCookie.SECURE_ATTR, cookie.isSecure());
setValue(cx, cookie, js, raw, ClientCookie.VERSION_ATTR, cookie.getVersion());
return js;
}
private Cookie scriptableToCookie(Context cx, Scriptable js) {
String name = Context.toString(js.get("name", js));
String value = Context.toString(js.get("value", js));
Object raw = js.get("raw", js);
BasicClientCookie cookie;
if (js.has(ClientCookie.COMMENTURL_ATTR, js) ||
js.has(ClientCookie.DISCARD_ATTR, js) ||
js.has(ClientCookie.PORT_ATTR, js)) {
BasicClientCookie2 cookie2 = new BasicClientCookie2(name, value);
cookie2.setCommentURL(stringValue(cx, js, raw, cookie2, ClientCookie.COMMENTURL_ATTR));
cookie2.setDiscard( booleanValue(cx, js, raw, cookie2, ClientCookie.DISCARD_ATTR));
cookie2.setPorts( intArrayValue(cx, js, raw, cookie2, ClientCookie.PORT_ATTR));
cookie = cookie2;
}
else {
cookie = new BasicClientCookie(name, value);
}
cookie.setComment( stringValue(cx, js, raw, cookie, ClientCookie.COMMENT_ATTR));
cookie.setDomain( stringValue(cx, js, raw, cookie, ClientCookie.DOMAIN_ATTR));
cookie.setExpiryDate( dateValue(cx, js, raw, cookie, ClientCookie.EXPIRES_ATTR));
cookie.setPath( stringValue(cx, js, raw, cookie, ClientCookie.PATH_ATTR));
cookie.setSecure( booleanValue(cx, js, raw, cookie, ClientCookie.SECURE_ATTR));
cookie.setVersion( intValue(cx, js, raw, cookie, ClientCookie.VERSION_ATTR));
setRawValue(raw, cookie, ClientCookie.MAX_AGE_ATTR);
return cookie;
}
private void setValue(Context cx, Cookie cookie, Scriptable js, Scriptable raw,
String name, Object value) {
if (value instanceof String ||
value instanceof Number ||
value instanceof Boolean) {
js.put(name, js, value);
}
else if (value instanceof Date) {
js.put(name, js, ((Date) value).getTime());
}
else if (value instanceof int[]) {
int[] ports = (int[]) value;
Object[] ip = new Object[ports.length];
for (int i = 0 ; i < ports.length; ++i) {
ip[i] = i;
}
js.put(name, js, cx.newArray(jsuri, ip));
}
// Add raw attribute, if present
if (cookie instanceof ClientCookie) {
ClientCookie cc = (ClientCookie) cookie;
if (cc.containsAttribute(name)) {
raw.put(name, raw, cc.getAttribute(name));
}
}
}
private void setRawValue(Object raw, BasicClientCookie cookie, String name) {
if (raw instanceof Scriptable) {
Object value = ((Scriptable) raw).get(name, (Scriptable) raw);
if (value != Scriptable.NOT_FOUND) {
cookie.setAttribute(name, Context.toString(value));
}
}
}
private String stringValue(Context cx, Scriptable js, Object raw,
BasicClientCookie cookie, String name) {
setRawValue(raw, cookie, name);
Object value = js.get(name, js);
if (value == Scriptable.NOT_FOUND) {
return null;
}
else {
return Context.toString(value);
}
}
private int intValue(Context cx, Scriptable js, Object raw,
BasicClientCookie cookie, String name) {
setRawValue(raw, cookie, name);
Object value = js.get(name, js);
if (value == Scriptable.NOT_FOUND) {
return 0;
}
else {
return (int) Context.toNumber(value);
}
}
private int[] intArrayValue(Context cx, Scriptable js, Object raw,
BasicClientCookie cookie, String name) {
setRawValue(raw, cookie, name);
Object value = js.get(name, js);
if (!(value instanceof Scriptable)) {
return null;
}
else {
Object[] elements = cx.getElements((Scriptable) value);
int[] result = new int[elements.length];
for (int i = 0; i < elements.length; ++i) {
result[i] = (int) Context.toNumber(elements[i]);
}
return result;
}
}
private Date dateValue(Context cx, Scriptable js, Object raw,
BasicClientCookie cookie, String name) {
setRawValue(raw, cookie, name);
Object value = js.get(name, js);
if (value == Scriptable.NOT_FOUND) {
return null;
}
else {
return new Date((long) Context.toNumber(value));
}
}
private boolean booleanValue(Context cx, Scriptable js, Object raw,
BasicClientCookie cookie, String name) {
setRawValue(raw, cookie, name);
return Context.toBoolean(js.get(name, js));
}
private JSURI jsuri;
}