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