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 public NetscapeCookieFile(Path path) {
139 this(path, new Date());
140 }
141
142 NetscapeCookieFile(Path path, Date creationDate) {
143 this.path = path;
144 this.snapshot = FileSnapshot.DIRTY;
145 this.creationDate = creationDate;
146 }
147
148
149
150
151 public Path getPath() {
152 return path;
153 }
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169 public Set<HttpCookie> getCookies(boolean refresh) {
170 if (cookies == null || refresh) {
171 try {
172 byte[] in = getFileContentIfModified();
173 Set<HttpCookie> newCookies = parseCookieFile(in, creationDate);
174 if (cookies != null) {
175 cookies = mergeCookies(newCookies, cookies);
176 } else {
177 cookies = newCookies;
178 }
179 return cookies;
180 } catch (IOException | IllegalArgumentException e) {
181 LOG.warn(
182 MessageFormat.format(
183 JGitText.get().couldNotReadCookieFile, path),
184 e);
185 if (cookies == null) {
186 cookies = new LinkedHashSet<>();
187 }
188 }
189 }
190 return cookies;
191
192 }
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210 private static Set<HttpCookie> parseCookieFile(@NonNull byte[] input,
211 @NonNull Date creationDate)
212 throws IOException, IllegalArgumentException {
213
214 String decoded = RawParseUtils.decode(StandardCharsets.US_ASCII, input);
215
216 Set<HttpCookie> cookies = new LinkedHashSet<>();
217 try (BufferedReader reader = new BufferedReader(
218 new StringReader(decoded))) {
219 String line;
220 while ((line = reader.readLine()) != null) {
221 HttpCookie cookie = parseLine(line, creationDate);
222 if (cookie != null) {
223 cookies.add(cookie);
224 }
225 }
226 }
227 return cookies;
228 }
229
230 private static HttpCookie parseLine(@NonNull String line,
231 @NonNull Date creationDate) {
232 if (line.isEmpty() || (line.startsWith("#")
233 && !line.startsWith(HTTP_ONLY_PREAMBLE))) {
234 return null;
235 }
236 String[] cookieLineParts = line.split(COLUMN_SEPARATOR, 7);
237 if (cookieLineParts == null) {
238 throw new IllegalArgumentException(MessageFormat
239 .format(JGitText.get().couldNotFindTabInLine, line));
240 }
241 if (cookieLineParts.length < 7) {
242 throw new IllegalArgumentException(MessageFormat.format(
243 JGitText.get().couldNotFindSixTabsInLine,
244 Integer.valueOf(cookieLineParts.length), line));
245 }
246 String name = cookieLineParts[5];
247 String value = cookieLineParts[6];
248 HttpCookie cookie = new HttpCookie(name, value);
249
250 String domain = cookieLineParts[0];
251 if (domain.startsWith(HTTP_ONLY_PREAMBLE)) {
252 cookie.setHttpOnly(true);
253 domain = domain.substring(HTTP_ONLY_PREAMBLE.length());
254 }
255
256
257 if (domain.startsWith(".")) {
258 domain = domain.substring(1);
259 }
260 cookie.setDomain(domain);
261
262
263 cookie.setPath(cookieLineParts[2]);
264 cookie.setSecure(Boolean.parseBoolean(cookieLineParts[3]));
265
266 long expires = Long.parseLong(cookieLineParts[4]);
267 long maxAge = (expires - creationDate.getTime()) / 1000;
268 if (maxAge <= 0) {
269 return null;
270 }
271 cookie.setMaxAge(maxAge);
272 return cookie;
273 }
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288 public void write(URL url)
289 throws IllegalArgumentException, IOException, InterruptedException {
290 try {
291 byte[] cookieFileContent = getFileContentIfModified();
292 if (cookieFileContent != null) {
293 LOG.debug(
294 "Reading the underlying cookie file '{}' as it has been modified since the last access",
295 path);
296
297 Set<HttpCookie> cookiesFromFile = NetscapeCookieFile
298 .parseCookieFile(cookieFileContent, creationDate);
299 this.cookies = mergeCookies(cookiesFromFile, cookies);
300 }
301 } catch (FileNotFoundException e) {
302
303 }
304
305 ByteArrayOutputStream output = new ByteArrayOutputStream();
306 try (Writer writer = new OutputStreamWriter(output,
307 StandardCharsets.US_ASCII)) {
308 write(writer, cookies, url, creationDate);
309 }
310 LockFile lockFile = new LockFile(path.toFile());
311 for (int retryCount = 0; retryCount < LOCK_ACQUIRE_MAX_RETRY_COUNT; retryCount++) {
312 if (lockFile.lock()) {
313 try {
314 lockFile.setNeedSnapshot(true);
315 lockFile.write(output.toByteArray());
316 if (!lockFile.commit()) {
317 throw new IOException(MessageFormat.format(
318 JGitText.get().cannotCommitWriteTo, path));
319 }
320 } finally {
321 lockFile.unlock();
322 }
323 return;
324 }
325 Thread.sleep(LOCK_ACQUIRE_RETRY_SLEEP);
326 }
327 throw new IOException(
328 MessageFormat.format(JGitText.get().cannotLock, lockFile));
329
330 }
331
332
333
334
335
336
337
338
339
340
341
342
343 private byte[] getFileContentIfModified() throws IOException {
344 final int maxStaleRetries = 5;
345 int retries = 0;
346 File file = getPath().toFile();
347 if (!file.exists()) {
348 LOG.warn(MessageFormat.format(JGitText.get().missingCookieFile,
349 file.getAbsolutePath()));
350 return new byte[0];
351 }
352 while (true) {
353 final FileSnapshot oldSnapshot = snapshot;
354 final FileSnapshot newSnapshot = FileSnapshot.save(file);
355 try {
356 final byte[] in = IO.readFully(file);
357 byte[] newHash = hash(in);
358 if (Arrays.equals(hash, newHash)) {
359 if (oldSnapshot.equals(newSnapshot)) {
360 oldSnapshot.setClean(newSnapshot);
361 } else {
362 snapshot = newSnapshot;
363 }
364 } else {
365 snapshot = newSnapshot;
366 hash = newHash;
367 }
368 return in;
369 } catch (FileNotFoundException e) {
370 throw e;
371 } catch (IOException e) {
372 if (FileUtils.isStaleFileHandle(e)
373 && retries < maxStaleRetries) {
374 if (LOG.isDebugEnabled()) {
375 LOG.debug(MessageFormat.format(
376 JGitText.get().configHandleIsStale,
377 Integer.valueOf(retries)), e);
378 }
379 retries++;
380 continue;
381 }
382 throw new IOException(MessageFormat
383 .format(JGitText.get().cannotReadFile, getPath()), e);
384 }
385 }
386
387 }
388
389 private byte[] hash(final byte[] in) {
390 return Constants.newMessageDigest().digest(in);
391 }
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410 static void write(@NonNull Writer writer,
411 @NonNull Collection<HttpCookie> cookies, @NonNull URL url,
412 @NonNull Date creationDate) throws IOException {
413 for (HttpCookie cookie : cookies) {
414 writeCookie(writer, cookie, url, creationDate);
415 }
416 }
417
418 private static void writeCookie(@NonNull Writer writer,
419 @NonNull HttpCookie cookie, @NonNull URL url,
420 @NonNull Date creationDate) throws IOException {
421 if (cookie.getMaxAge() <= 0) {
422 return;
423 }
424 String domain = "";
425 if (cookie.isHttpOnly()) {
426 domain = HTTP_ONLY_PREAMBLE;
427 }
428 if (cookie.getDomain() != null) {
429 domain += cookie.getDomain();
430 } else {
431 domain += url.getHost();
432 }
433 writer.write(domain);
434 writer.write(COLUMN_SEPARATOR);
435 writer.write("TRUE");
436 writer.write(COLUMN_SEPARATOR);
437 String path = cookie.getPath();
438 if (path == null) {
439 path = url.getPath();
440 }
441 writer.write(path);
442 writer.write(COLUMN_SEPARATOR);
443 writer.write(Boolean.toString(cookie.getSecure()).toUpperCase());
444 writer.write(COLUMN_SEPARATOR);
445 final String expirationDate;
446
447 expirationDate = String
448 .valueOf(creationDate.getTime() + (cookie.getMaxAge() * 1000));
449 writer.write(expirationDate);
450 writer.write(COLUMN_SEPARATOR);
451 writer.write(cookie.getName());
452 writer.write(COLUMN_SEPARATOR);
453 writer.write(cookie.getValue());
454 writer.write(LINE_SEPARATOR);
455 }
456
457
458
459
460
461
462
463
464
465
466
467
468 static Set<HttpCookie> mergeCookies(Set<HttpCookie> cookies1,
469 @Nullable Set<HttpCookie> cookies2) {
470 Set<HttpCookie> mergedCookies = new LinkedHashSet<>(cookies1);
471 if (cookies2 != null) {
472 mergedCookies.addAll(cookies2);
473 }
474 return mergedCookies;
475 }
476 }