1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 package org.eclipse.jgit.internal.transport.http;
44
45 import java.io.BufferedReader;
46 import java.io.ByteArrayOutputStream;
47 import java.io.File;
48 import java.io.FileNotFoundException;
49 import java.io.IOException;
50 import java.io.OutputStreamWriter;
51 import java.io.StringReader;
52 import java.io.Writer;
53 import java.net.HttpCookie;
54 import java.net.URL;
55 import java.nio.charset.StandardCharsets;
56 import java.nio.file.Path;
57 import java.text.MessageFormat;
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.Date;
61 import java.util.LinkedHashSet;
62 import java.util.Set;
63
64 import org.eclipse.jgit.annotations.NonNull;
65 import org.eclipse.jgit.annotations.Nullable;
66 import org.eclipse.jgit.internal.JGitText;
67 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
68 import org.eclipse.jgit.internal.storage.file.LockFile;
69 import org.eclipse.jgit.lib.Constants;
70 import org.eclipse.jgit.storage.file.FileBasedConfig;
71 import org.eclipse.jgit.util.FileUtils;
72 import org.eclipse.jgit.util.IO;
73 import org.eclipse.jgit.util.RawParseUtils;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 public final class NetscapeCookieFile {
103
104 private static final String HTTP_ONLY_PREAMBLE = "#HttpOnly_";
105
106 private static final String COLUMN_SEPARATOR = "\t";
107
108 private static final String LINE_SEPARATOR = "\n";
109
110
111
112
113
114 private static final int LOCK_ACQUIRE_MAX_RETRY_COUNT = 4;
115
116
117
118
119
120 private static final int LOCK_ACQUIRE_RETRY_SLEEP = 500;
121
122 private final Path path;
123
124 private FileSnapshot snapshot;
125
126 private byte[] hash;
127
128 final Date creationDate;
129
130 private Set<HttpCookie> cookies = null;
131
132 private static final Logger LOG = LoggerFactory
133 .getLogger(NetscapeCookieFile.class);
134
135
136
137
138
139 public NetscapeCookieFile(Path path) {
140 this(path, new Date());
141 }
142
143 NetscapeCookieFile(Path path, Date creationDate) {
144 this.path = path;
145 this.snapshot = FileSnapshot.DIRTY;
146 this.creationDate = creationDate;
147 }
148
149
150
151
152
153
154 public Path getPath() {
155 return path;
156 }
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174 public Set<HttpCookie> getCookies(boolean refresh) {
175 if (cookies == null || refresh) {
176 try {
177 byte[] in = getFileContentIfModified();
178 Set<HttpCookie> newCookies = parseCookieFile(in, creationDate);
179 if (cookies != null) {
180 cookies = mergeCookies(newCookies, cookies);
181 } else {
182 cookies = newCookies;
183 }
184 return cookies;
185 } catch (IOException | IllegalArgumentException e) {
186 LOG.warn(
187 MessageFormat.format(
188 JGitText.get().couldNotReadCookieFile, path),
189 e);
190 if (cookies == null) {
191 cookies = new LinkedHashSet<>();
192 }
193 }
194 }
195 return cookies;
196
197 }
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215 private static Set<HttpCookie> parseCookieFile(@NonNull byte[] input,
216 @NonNull Date creationDate)
217 throws IOException, IllegalArgumentException {
218
219 String decoded = RawParseUtils.decode(StandardCharsets.US_ASCII, input);
220
221 Set<HttpCookie> cookies = new LinkedHashSet<>();
222 try (BufferedReader reader = new BufferedReader(
223 new StringReader(decoded))) {
224 String line;
225 while ((line = reader.readLine()) != null) {
226 HttpCookie cookie = parseLine(line, creationDate);
227 if (cookie != null) {
228 cookies.add(cookie);
229 }
230 }
231 }
232 return cookies;
233 }
234
235 private static HttpCookie parseLine(@NonNull String line,
236 @NonNull Date creationDate) {
237 if (line.isEmpty() || (line.startsWith("#")
238 && !line.startsWith(HTTP_ONLY_PREAMBLE))) {
239 return null;
240 }
241 String[] cookieLineParts = line.split(COLUMN_SEPARATOR, 7);
242 if (cookieLineParts == null) {
243 throw new IllegalArgumentException(MessageFormat
244 .format(JGitText.get().couldNotFindTabInLine, line));
245 }
246 if (cookieLineParts.length < 7) {
247 throw new IllegalArgumentException(MessageFormat.format(
248 JGitText.get().couldNotFindSixTabsInLine,
249 Integer.valueOf(cookieLineParts.length), line));
250 }
251 String name = cookieLineParts[5];
252 String value = cookieLineParts[6];
253 HttpCookie cookie = new HttpCookie(name, value);
254
255 String domain = cookieLineParts[0];
256 if (domain.startsWith(HTTP_ONLY_PREAMBLE)) {
257 cookie.setHttpOnly(true);
258 domain = domain.substring(HTTP_ONLY_PREAMBLE.length());
259 }
260
261
262 if (domain.startsWith(".")) {
263 domain = domain.substring(1);
264 }
265 cookie.setDomain(domain);
266
267
268 cookie.setPath(cookieLineParts[2]);
269 cookie.setSecure(Boolean.parseBoolean(cookieLineParts[3]));
270
271 long expires = Long.parseLong(cookieLineParts[4]);
272 long maxAge = (expires - creationDate.getTime()) / 1000;
273 if (maxAge <= 0) {
274 return null;
275 }
276 cookie.setMaxAge(maxAge);
277 return cookie;
278 }
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294 private byte[] getFileContentIfModified() throws IOException {
295 final int maxStaleRetries = 5;
296 int retries = 0;
297 File file = getPath().toFile();
298 if (!file.exists()) {
299 LOG.warn(MessageFormat.format(JGitText.get().missingCookieFile,
300 file.getAbsolutePath()));
301 return new byte[0];
302 }
303 while (true) {
304 final FileSnapshot oldSnapshot = snapshot;
305 final FileSnapshot newSnapshot = FileSnapshot.save(file);
306 try {
307 final byte[] in = IO.readFully(file);
308 byte[] newHash = hash(in);
309 if (Arrays.equals(hash, newHash)) {
310 if (oldSnapshot.equals(newSnapshot)) {
311 oldSnapshot.setClean(newSnapshot);
312 } else {
313 snapshot = newSnapshot;
314 }
315 } else {
316 snapshot = newSnapshot;
317 hash = newHash;
318 }
319 return in;
320 } catch (FileNotFoundException e) {
321 throw e;
322 } catch (IOException e) {
323 if (FileUtils.isStaleFileHandle(e)
324 && retries < maxStaleRetries) {
325 if (LOG.isDebugEnabled()) {
326 LOG.debug(MessageFormat.format(
327 JGitText.get().configHandleIsStale,
328 Integer.valueOf(retries)), e);
329 }
330 retries++;
331 continue;
332 }
333 throw new IOException(MessageFormat
334 .format(JGitText.get().cannotReadFile, getPath()), e);
335 }
336 }
337
338 }
339
340 private static byte[] hash(final byte[] in) {
341 return Constants.newMessageDigest().digest(in);
342 }
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359 public void write(URL url) throws IOException, InterruptedException {
360 try {
361 byte[] cookieFileContent = getFileContentIfModified();
362 if (cookieFileContent != null) {
363 LOG.debug("Reading the underlying cookie file '{}' "
364 + "as it has been modified since "
365 + "the last access",
366 path);
367
368 Set<HttpCookie> cookiesFromFile = NetscapeCookieFile
369 .parseCookieFile(cookieFileContent, creationDate);
370 this.cookies = mergeCookies(cookiesFromFile, cookies);
371 }
372 } catch (FileNotFoundException e) {
373
374 }
375
376 ByteArrayOutputStream output = new ByteArrayOutputStream();
377 try (Writer writer = new OutputStreamWriter(output,
378 StandardCharsets.US_ASCII)) {
379 write(writer, cookies, url, creationDate);
380 }
381 LockFile lockFile = new LockFile(path.toFile());
382 for (int retryCount = 0; retryCount < LOCK_ACQUIRE_MAX_RETRY_COUNT; retryCount++) {
383 if (lockFile.lock()) {
384 try {
385 lockFile.setNeedSnapshot(true);
386 lockFile.write(output.toByteArray());
387 if (!lockFile.commit()) {
388 throw new IOException(MessageFormat.format(
389 JGitText.get().cannotCommitWriteTo, path));
390 }
391 } finally {
392 lockFile.unlock();
393 }
394 return;
395 }
396 Thread.sleep(LOCK_ACQUIRE_RETRY_SLEEP);
397 }
398 throw new IOException(
399 MessageFormat.format(JGitText.get().cannotLock, lockFile));
400 }
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420 static void write(@NonNull Writer writer,
421 @NonNull Collection<HttpCookie> cookies, @NonNull URL url,
422 @NonNull Date creationDate) throws IOException {
423 for (HttpCookie cookie : cookies) {
424 writeCookie(writer, cookie, url, creationDate);
425 }
426 }
427
428 private static void writeCookie(@NonNull Writer writer,
429 @NonNull HttpCookie cookie, @NonNull URL url,
430 @NonNull Date creationDate) throws IOException {
431 if (cookie.getMaxAge() <= 0) {
432 return;
433 }
434 String domain = "";
435 if (cookie.isHttpOnly()) {
436 domain = HTTP_ONLY_PREAMBLE;
437 }
438 if (cookie.getDomain() != null) {
439 domain += cookie.getDomain();
440 } else {
441 domain += url.getHost();
442 }
443 writer.write(domain);
444 writer.write(COLUMN_SEPARATOR);
445 writer.write("TRUE");
446 writer.write(COLUMN_SEPARATOR);
447 String path = cookie.getPath();
448 if (path == null) {
449 path = url.getPath();
450 }
451 writer.write(path);
452 writer.write(COLUMN_SEPARATOR);
453 writer.write(Boolean.toString(cookie.getSecure()).toUpperCase());
454 writer.write(COLUMN_SEPARATOR);
455 final String expirationDate;
456
457 expirationDate = String
458 .valueOf(creationDate.getTime() + (cookie.getMaxAge() * 1000));
459 writer.write(expirationDate);
460 writer.write(COLUMN_SEPARATOR);
461 writer.write(cookie.getName());
462 writer.write(COLUMN_SEPARATOR);
463 writer.write(cookie.getValue());
464 writer.write(LINE_SEPARATOR);
465 }
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480 static Set<HttpCookie> mergeCookies(Set<HttpCookie> cookies1,
481 @Nullable Set<HttpCookie> cookies2) {
482 Set<HttpCookie> mergedCookies = new LinkedHashSet<>(cookies1);
483 if (cookies2 != null) {
484 mergedCookies.addAll(cookies2);
485 }
486 return mergedCookies;
487 }
488 }