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
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 }