1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.apache.chemistry.opencmis.client.bindings.spi.cookies;
24
25 import java.io.Serializable;
26 import java.util.ArrayList;
27 import java.util.Date;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34
35 import org.apache.chemistry.opencmis.commons.impl.DateTimeHelper;
36
37
38
39
40
41
42
43
44 public final class CmisHttpCookie implements Cloneable, Serializable {
45
46 private static final long serialVersionUID = 1L;
47
48 private static final String DOT_STR = ".";
49 private static final String LOCAL_STR = ".local";
50 private static final String QUOTE_STR = "\"";
51 private static final String COMMA_STR = ",";
52 private static final Pattern HEAD_PATTERN = Pattern.compile("Set-Cookie2?:", Pattern.CASE_INSENSITIVE);
53 private static final Pattern NAME_PATTERN = Pattern.compile(
54 "([^$=,\u0085\u2028\u2029][^,\n\t\r\r\n\u0085\u2028\u2029]*?)=([^;]*)(;)?", Pattern.DOTALL
55 | Pattern.CASE_INSENSITIVE);
56 private static final Pattern ATTR_PATTERN0 = Pattern.compile("([^;=]*)(?:=([^;]*))?");
57 private static final Pattern ATTR_PATTERN1 = Pattern.compile("(,?[^;=]*)(?:=([^;,]*))?((?=.))?");
58
59 private abstract static class Setter {
60 private boolean set;
61
62 Setter() {
63 set = false;
64 }
65
66 boolean isSet() {
67 return set;
68 }
69
70 void set(boolean isSet) {
71 set = isSet;
72 }
73
74 abstract void setValue(String value, CmisHttpCookie cookie);
75
76 void validate(String value, CmisHttpCookie cookie) {
77 if (cookie.getVersion() == 1 && value != null && value.contains(COMMA_STR)) {
78 throw new IllegalArgumentException();
79 }
80 }
81 }
82
83 private Map<String, Setter> attributeSet = new HashMap<String, Setter>();
84
85 private String comment;
86 private String commentURL;
87 private boolean discard;
88 private String domain;
89 private long maxAge = -1L;
90 private String name;
91 private String path;
92 private String portList;
93 private boolean secure;
94 private String value;
95 private int version = 1;
96
97
98
99
100
101
102
103
104
105
106
107 public static boolean domainMatches(String domain, String host) {
108 if (domain == null || host == null) {
109 return false;
110 }
111 String newDomain = domain.toLowerCase(Locale.ENGLISH);
112 String newHost = host.toLowerCase(Locale.ENGLISH);
113
114 return newDomain.equals(newHost)
115 || (isValidDomain(newDomain) && effDomainMatches(newDomain, newHost) && isValidHost(newDomain, newHost));
116 }
117
118 private static boolean effDomainMatches(String domain, String host) {
119
120 String effHost = host.indexOf(DOT_STR) != -1 ? host : (host + LOCAL_STR);
121
122
123
124 boolean inDomain = domain.equals(effHost);
125 inDomain = inDomain
126 || (effHost.endsWith(domain) && effHost.length() > domain.length() && domain.startsWith(DOT_STR));
127
128 return inDomain;
129 }
130
131 private static boolean isCommaDelim(CmisHttpCookie cookie) {
132 String value = cookie.getValue();
133 if (value.startsWith(QUOTE_STR) && value.endsWith(QUOTE_STR)) {
134 cookie.setValue(value.substring(1, value.length() - 1));
135 return false;
136 }
137
138 if (cookie.getVersion() == 1 && value.contains(COMMA_STR)) {
139 cookie.setValue(value.substring(0, value.indexOf(COMMA_STR)));
140 return true;
141 }
142
143 return false;
144 }
145
146 private static boolean isValidDomain(String domain) {
147
148 if (domain.length() <= 2) {
149 return false;
150 }
151
152 return domain.substring(1, domain.length() - 1).indexOf(DOT_STR) != -1 || domain.equals(LOCAL_STR);
153 }
154
155 private static boolean isValidHost(String domain, String host) {
156
157
158 boolean matches = !host.endsWith(domain);
159 if (!matches) {
160 String hostSub = host.substring(0, host.length() - domain.length());
161 matches = hostSub.indexOf(DOT_STR) == -1;
162 }
163
164 return matches;
165 }
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 public static List<CmisHttpCookie> parse(String header) {
184 Matcher matcher = HEAD_PATTERN.matcher(header);
185
186 List<CmisHttpCookie> list = null;
187 CmisHttpCookie cookie = null;
188 String headerString = header;
189 int version = 0;
190
191 if (matcher.find()) {
192 String cookieHead = matcher.group();
193 if ("set-cookie2:".equalsIgnoreCase(cookieHead)) {
194 version = 1;
195 }
196 headerString = header.substring(cookieHead.length());
197 }
198
199
200 matcher = NAME_PATTERN.matcher(headerString);
201 if (matcher.lookingAt()) {
202 list = new ArrayList<CmisHttpCookie>();
203 cookie = new CmisHttpCookie(matcher.group(1), matcher.group(2));
204 cookie.setVersion(version);
205
206
207
208
209
210 String nameGroup = matcher.group();
211 if (isCommaDelim(cookie)) {
212 headerString = headerString.substring(nameGroup.indexOf(COMMA_STR));
213 } else {
214 headerString = headerString.substring(nameGroup.length());
215 }
216 list.add(cookie);
217 } else {
218 throw new IllegalArgumentException();
219 }
220
221
222 while (!(headerString.length() == 0)) {
223 matcher = cookie.getVersion() == 1 ? ATTR_PATTERN1.matcher(headerString) : ATTR_PATTERN0
224 .matcher(headerString);
225
226 if (matcher.lookingAt()) {
227 String attrName = matcher.group(1).trim();
228
229
230 if (attrName.length() == 0) {
231 headerString = headerString.substring(1);
232 continue;
233 }
234
235
236
237 if (attrName.equalsIgnoreCase("port") || attrName.equalsIgnoreCase("expires")) {
238 int start = matcher.regionStart();
239 matcher = ATTR_PATTERN0.matcher(headerString);
240 matcher.region(start, headerString.length());
241 matcher.lookingAt();
242 } else if (cookie.getVersion() == 1 && attrName.startsWith(COMMA_STR)) {
243
244
245
246 headerString = headerString.substring(1);
247 matcher = NAME_PATTERN.matcher(headerString);
248 if (matcher.lookingAt()) {
249 cookie = new CmisHttpCookie(matcher.group(1), matcher.group(2));
250 list.add(cookie);
251 headerString = headerString.substring(matcher.group().length());
252 continue;
253 }
254 }
255
256 Setter setter = cookie.attributeSet.get(attrName.toLowerCase(Locale.ENGLISH));
257 if (setter != null && !setter.isSet()) {
258 String attrValue = matcher.group(2);
259 setter.validate(attrValue, cookie);
260 setter.setValue(matcher.group(2), cookie);
261 }
262 headerString = headerString.substring(matcher.end());
263 }
264 }
265
266 return list;
267 }
268
269 {
270 attributeSet.put("comment", new Setter() {
271 @Override
272 void setValue(String value, CmisHttpCookie cookie) {
273 cookie.setComment(value);
274 if (cookie.getComment() != null) {
275 set(true);
276 }
277 }
278 });
279 attributeSet.put("commenturl", new Setter() {
280 @Override
281 void setValue(String value, CmisHttpCookie cookie) {
282 cookie.setCommentURL(value);
283 if (cookie.getCommentURL() != null) {
284 set(true);
285 }
286 }
287 });
288 attributeSet.put("discard", new Setter() {
289 @Override
290 void setValue(String value, CmisHttpCookie cookie) {
291 cookie.setDiscard(true);
292 set(true);
293 }
294 });
295 attributeSet.put("domain", new Setter() {
296 @Override
297 void setValue(String value, CmisHttpCookie cookie) {
298 cookie.setDomain(value);
299 if (cookie.getDomain() != null) {
300 set(true);
301 }
302 }
303 });
304 attributeSet.put("max-age", new Setter() {
305 @Override
306 void setValue(String value, CmisHttpCookie cookie) {
307 try {
308 cookie.setMaxAge(Long.parseLong(value));
309 } catch (NumberFormatException e) {
310 throw new IllegalArgumentException("Invalid max-age!", e);
311 }
312 set(true);
313
314 if (!attributeSet.get("version").isSet()) {
315 cookie.setVersion(1);
316 }
317 }
318 });
319
320 attributeSet.put("path", new Setter() {
321 @Override
322 void setValue(String value, CmisHttpCookie cookie) {
323 cookie.setPath(value);
324 if (cookie.getPath() != null) {
325 set(true);
326 }
327 }
328 });
329 attributeSet.put("port", new Setter() {
330 @Override
331 void setValue(String value, CmisHttpCookie cookie) {
332 cookie.setPortlist(value);
333 if (cookie.getPortlist() != null) {
334 set(true);
335 }
336 }
337
338 @Override
339 void validate(String v, CmisHttpCookie cookie) {
340 }
341 });
342 attributeSet.put("secure", new Setter() {
343 @Override
344 void setValue(String value, CmisHttpCookie cookie) {
345 cookie.setSecure(true);
346 set(true);
347 }
348 });
349 attributeSet.put("version", new Setter() {
350 @Override
351 void setValue(String value, CmisHttpCookie cookie) {
352 try {
353 int v = Integer.parseInt(value);
354 if (v > cookie.getVersion()) {
355 cookie.setVersion(v);
356 }
357 } catch (NumberFormatException e) {
358 throw new IllegalArgumentException("Invalid version!", e);
359 }
360 if (cookie.getVersion() != 0) {
361 set(true);
362 }
363 }
364 });
365
366 attributeSet.put("expires", new Setter() {
367 @Override
368 void setValue(String value, CmisHttpCookie cookie) {
369 cookie.setVersion(0);
370 attributeSet.get("version").set(true);
371 if (!attributeSet.get("max-age").isSet()) {
372 attributeSet.get("max-age").set(true);
373 if (!"en".equalsIgnoreCase(Locale.getDefault().getLanguage())) {
374 cookie.setMaxAge(0);
375 return;
376 }
377
378 Date date = DateTimeHelper.parseHttpDateTime(value);
379 if (date != null) {
380 cookie.setMaxAge((date.getTime() - System.currentTimeMillis()) / 1000);
381 } else {
382 cookie.setMaxAge(0);
383 }
384 }
385 }
386
387 @Override
388 void validate(String v, CmisHttpCookie cookie) {
389 }
390 });
391 }
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 public CmisHttpCookie(String name, String value) {
418 String ntrim = name.trim();
419 if (!isValidName(ntrim)) {
420 throw new IllegalArgumentException("Invalid name!");
421 }
422
423 this.name = ntrim;
424 this.value = value;
425 }
426
427 private void attrToString(StringBuilder builder, String attrName, String attrValue) {
428 if (attrValue != null && builder != null) {
429 builder.append(';');
430 builder.append('$');
431 builder.append(attrName);
432 builder.append("=\"");
433 builder.append(attrValue);
434 builder.append(QUOTE_STR);
435 }
436 }
437
438
439
440
441
442
443 @Override
444 public Object clone() {
445 try {
446 return super.clone();
447 } catch (CloneNotSupportedException e) {
448 return null;
449 }
450 }
451
452
453
454
455
456
457
458
459
460
461 @Override
462 public boolean equals(Object obj) {
463 if (obj == this) {
464 return true;
465 }
466 if (obj instanceof CmisHttpCookie) {
467 CmisHttpCookie anotherCookie = (CmisHttpCookie) obj;
468 if (name.equalsIgnoreCase(anotherCookie.getName())) {
469 String anotherDomain = anotherCookie.getDomain();
470 boolean equals = domain == null ? anotherDomain == null : domain.equalsIgnoreCase(anotherDomain);
471 if (equals) {
472 String anotherPath = anotherCookie.getPath();
473 return path == null ? anotherPath == null : path.equals(anotherPath);
474 }
475 }
476 }
477 return false;
478 }
479
480
481
482
483
484
485
486 public String getComment() {
487 return comment;
488 }
489
490
491
492
493
494
495
496 public String getCommentURL() {
497 return commentURL;
498 }
499
500
501
502
503
504
505
506 public boolean getDiscard() {
507 return discard;
508 }
509
510
511
512
513
514
515
516 public String getDomain() {
517 return domain;
518 }
519
520
521
522
523
524
525 public long getMaxAge() {
526 return maxAge;
527 }
528
529
530
531
532
533
534 public String getName() {
535 return name;
536 }
537
538
539
540
541
542
543
544 public String getPath() {
545 return path;
546 }
547
548
549
550
551
552
553
554 public String getPortlist() {
555 return portList;
556 }
557
558
559
560
561
562
563
564
565 public boolean getSecure() {
566 return secure;
567 }
568
569
570
571
572
573
574 public String getValue() {
575 return value;
576 }
577
578
579
580
581
582
583
584 public int getVersion() {
585 return version;
586 }
587
588
589
590
591
592
593 public boolean hasExpired() {
594
595
596 if (maxAge == -1L) {
597 return false;
598 }
599
600 boolean expired = false;
601 if (maxAge <= 0L) {
602 expired = true;
603 }
604 return expired;
605 }
606
607
608
609
610
611
612
613
614
615 @Override
616 public int hashCode() {
617 int hashCode = name.toLowerCase(Locale.ENGLISH).hashCode();
618 hashCode += domain == null ? 0 : domain.toLowerCase(Locale.ENGLISH).hashCode();
619 hashCode += path == null ? 0 : path.hashCode();
620 return hashCode;
621 }
622
623 private boolean isValidName(String n) {
624
625
626 boolean isValid = !(n.length() == 0 || n.charAt(0) == '$' || attributeSet.containsKey(n
627 .toLowerCase(Locale.ENGLISH)));
628 if (isValid) {
629 for (int i = 0; i < n.length(); i++) {
630 char nameChar = n.charAt(i);
631
632
633 if (nameChar < 0 || nameChar >= 127 || nameChar == ';' || nameChar == ','
634 || (Character.isWhitespace(nameChar) && nameChar != ' ')) {
635 isValid = false;
636 break;
637 }
638 }
639 }
640
641 return isValid;
642 }
643
644
645
646
647
648
649
650 public void setComment(String purpose) {
651 comment = purpose;
652 }
653
654
655
656
657
658
659
660
661 public void setCommentURL(String purpose) {
662 commentURL = purpose;
663 }
664
665
666
667
668
669
670
671 public void setDiscard(boolean discard) {
672 this.discard = discard;
673 }
674
675
676
677
678
679
680
681
682
683 public void setDomain(String pattern) {
684 domain = pattern == null ? null : pattern.toLowerCase(Locale.ENGLISH);
685 }
686
687
688
689
690
691
692
693 public void setMaxAge(long expiry) {
694 maxAge = expiry;
695 }
696
697
698
699
700
701
702
703
704 public void setPath(String path) {
705 this.path = path;
706 }
707
708
709
710
711
712
713
714 public void setPortlist(String ports) {
715 portList = ports;
716 }
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731 public void setSecure(boolean flag) {
732 secure = flag;
733 }
734
735
736
737
738
739
740
741
742
743
744
745
746 public void setValue(String newValue) {
747
748
749 value = newValue;
750 }
751
752
753
754
755
756
757
758
759
760
761 public void setVersion(int v) {
762 if (v != 0 && v != 1) {
763 throw new IllegalArgumentException("Unknown version!");
764 }
765 version = v;
766 }
767
768
769
770
771
772
773
774 @Override
775 public String toString() {
776 StringBuilder cookieStr = new StringBuilder(128);
777 cookieStr.append(name);
778 cookieStr.append('=');
779 if (version == 0) {
780 cookieStr.append(value);
781 } else if (version == 1) {
782 cookieStr.append(QUOTE_STR);
783 cookieStr.append(value);
784 cookieStr.append(QUOTE_STR);
785
786 attrToString(cookieStr, "Path", path);
787 attrToString(cookieStr, "Domain", domain);
788 attrToString(cookieStr, "Port", portList);
789 }
790
791 return cookieStr.toString();
792 }
793 }