View Javadoc
1   package org.oxerr.http.client;
2   
3   import java.io.BufferedReader;
4   import java.io.BufferedWriter;
5   import java.io.ByteArrayInputStream;
6   import java.io.ByteArrayOutputStream;
7   import java.io.File;
8   import java.io.FileInputStream;
9   import java.io.FileOutputStream;
10  import java.io.IOException;
11  import java.io.InputStreamReader;
12  import java.io.LineNumberReader;
13  import java.io.ObjectInputStream;
14  import java.io.ObjectOutputStream;
15  import java.io.OutputStreamWriter;
16  import java.io.Writer;
17  import java.nio.charset.Charset;
18  import java.nio.charset.StandardCharsets;
19  import java.text.SimpleDateFormat;
20  import java.util.Base64;
21  import java.util.Date;
22  import java.util.Objects;
23  import java.util.TimeZone;
24  import java.util.logging.Logger;
25  
26  import org.apache.http.client.CookieStore;
27  import org.apache.http.cookie.ClientCookie;
28  import org.apache.http.cookie.Cookie;
29  import org.apache.http.impl.client.BasicCookieStore;
30  import org.apache.http.impl.cookie.BasicClientCookie;
31  
32  /**
33   * Implementation of {@link CookieStore} using file as backend store.
34   */
35  public class FileCookieStore extends BasicCookieStore implements AutoCloseable {
36  
37  	private static final long serialVersionUID = 2017101401L;
38  
39  	private static final int FIELD_COUNT = 7;
40  
41  	private static final String COMMENT_PREFIX = "#";
42  	private static final String SERIALIZED_PREFIX = "#/* ";
43  	private static final String SERIALIZED_SUFFIX = " */#";
44  
45  	private final Logger log = Logger.getLogger(getClass().getName());
46  	private final Charset charset = StandardCharsets.UTF_8;
47  	private final File file;
48  
49  	public FileCookieStore(File file) throws IOException {
50  		this.file = file;
51  		if (file.canRead()) {
52  			try {
53  				read();
54  			} catch (ClassNotFoundException e) {
55  				throw new IOException(e);
56  			}
57  		} else {
58  			log.fine("file " + file.toString() + " cannot be read.");
59  		}
60  	}
61  
62  	@Override
63  	public void close() throws IOException {
64  		write();
65  	}
66  
67  	private void read() throws IOException, ClassNotFoundException {
68  		try (
69  			FileInputStream fin = new FileInputStream(file);
70  			InputStreamReader isr = new InputStreamReader(fin, charset);
71  			BufferedReader br = new BufferedReader(isr);
72  			LineNumberReader lnr = new LineNumberReader(br);
73  		) {
74  			String line;
75  			while ((line = lnr.readLine()) != null) {
76  				if (line.startsWith(SERIALIZED_PREFIX) && line.endsWith(SERIALIZED_SUFFIX)) {
77  					String serialized = line.substring(SERIALIZED_PREFIX.length(),
78  						line.length() - SERIALIZED_SUFFIX.length());
79  					Cookie cookie = (Cookie) deserialize(serialized);
80  					log.finer("Adding cookie: " + cookie);
81  					addCookie(cookie);
82  
83  					log.finer("Skipping next line");
84  					lnr.readLine();
85  				} else if (line.startsWith(COMMENT_PREFIX)) {
86  					log.finer("Skipping comment line");
87  				} else {
88  					Cookie cookie = read(line);
89  					log.finer("Adding cookie: " + cookie);
90  					addCookie(cookie);
91  				}
92  			}
93  		}
94  	}
95  
96  	private Cookie read(String cookieString) {
97  		log.fine("cookieString: " + cookieString);
98  
99  		String[] cookieStrings = cookieString.split("\t", FIELD_COUNT);
100 
101 		String domain = cookieStrings[0];
102 		@SuppressWarnings("unused")
103 		boolean hostOnly = !Boolean.parseBoolean(cookieStrings[1]);
104 		String path = cookieStrings[2];
105 		boolean secure = Boolean.parseBoolean(cookieStrings[3]);
106 		long expiryEpochSecond = Long.parseLong(cookieStrings[4]);
107 		String name = cookieStrings[5];
108 		String value = cookieStrings[6];
109 
110 		Cookie cookie;
111 
112 		BasicClientCookie cc = new BasicClientCookie(name, value);
113 
114 		cc.setAttribute(ClientCookie.DOMAIN_ATTR, domain);
115 		cc.setAttribute(ClientCookie.PATH_ATTR, path);
116 
117 		cc.setDomain(domain.replaceAll("^\\.", ""));
118 		cc.setPath(path);
119 		cc.setSecure(secure);
120 		if (expiryEpochSecond != 0L) {
121 			Date expiryDate = new Date(expiryEpochSecond * 1000);
122 			SimpleDateFormat format = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z");
123 			format.setTimeZone(TimeZone.getTimeZone("GMT"));
124 			String expires = format.format(expiryDate);
125 			cc.setAttribute(ClientCookie.EXPIRES_ATTR, expires);
126 			cc.setExpiryDate(expiryDate);
127 		}
128 
129 		cookie = cc;
130 		return cookie;
131 	}
132 
133 	private void write() throws IOException {
134 		try (
135 			FileOutputStream fileOutputStream = new FileOutputStream(file);
136 			OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, charset);
137 			BufferedWriter bw = new BufferedWriter(outputStreamWriter);
138 		) {
139 			bw.write(COMMENT_PREFIX + " domain\tnot host only\tpath\tsecure\texpiry date\tname\tvalue\n");
140 			for (Cookie cookie : getCookies()) {
141 				write(bw, cookie);
142 			}
143 		}
144 	}
145 
146 	private void write(Writer w, Cookie cookie) throws IOException {
147 		String domainAttr = null;
148 		boolean hostOnly = true;
149 
150 		if (cookie instanceof ClientCookie) {
151 			ClientCookie clientCookie = (ClientCookie) cookie;
152 			domainAttr = clientCookie.getAttribute(ClientCookie.DOMAIN_ATTR);
153 			hostOnly = Objects.equals(cookie.getDomain(), domainAttr);
154 		}
155 
156 		String cookieString = String.format("%s\t%s\t%s\t%s\t%d\t%s\t%s\n",
157 			domainAttr != null ? domainAttr : cookie.getDomain(),
158 			Boolean.valueOf(!hostOnly).toString().toUpperCase(),
159 			cookie.getPath(),
160 			Boolean.valueOf(cookie.isSecure()).toString().toUpperCase(),
161 			cookie.getExpiryDate() != null ? Long.valueOf(cookie.getExpiryDate().getTime() / 1000) : 0,
162 			cookie.getName(),
163 			cookie.getValue()
164 		);
165 
166 		log.finer("Writing cookie: " + cookie);
167 
168 		w.write(SERIALIZED_PREFIX);
169 		w.write(serialize(cookie));
170 		w.write(SERIALIZED_SUFFIX);
171 		w.write("\n");
172 
173 		w.write(cookieString);
174 	}
175 
176 	private Object deserialize(String serialized) throws IOException, ClassNotFoundException {
177 		byte[] base64Encoded = serialized.getBytes(charset);
178 		byte[] objectData = Base64.getUrlDecoder().decode(base64Encoded);
179 		try (
180 			ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
181 			ObjectInputStream ois = new ObjectInputStream(bais);
182 		) {
183 			return ois.readObject();
184 		}
185 	}
186 
187 	private String serialize(Object object) throws IOException {
188 		byte[] objectData;
189 		try (
190 			ByteArrayOutputStream baos = new ByteArrayOutputStream();
191 			ObjectOutputStream oos = new ObjectOutputStream(baos);
192 		) {
193 			oos.writeObject(object);
194 			objectData = baos.toByteArray();
195 		}
196 		byte[] base64Encoded = Base64.getUrlEncoder().encode(objectData);
197 		String serialized = new String(base64Encoded, charset);
198 		return serialized;
199 	}
200 
201 }