Line data Source code
1 : // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 : // for details. All rights reserved. Use of this source code is governed by a
3 : // BSD-style license that can be found in the LICENSE file.
4 :
5 : import 'package:string_scanner/string_scanner.dart';
6 :
7 : import 'utils.dart';
8 :
9 : const _WEEKDAYS = const ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
10 : const _MONTHS = const ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
11 : "Sep", "Oct", "Nov", "Dec"];
12 :
13 : final _shortWeekdayRegExp = new RegExp(r"Mon|Tue|Wed|Thu|Fri|Sat|Sun");
14 : final _longWeekdayRegExp =
15 : new RegExp(r"Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday");
16 : final _monthRegExp =
17 : new RegExp(r"Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec");
18 : final _digitRegExp = new RegExp(r"\d+");
19 :
20 : /// Return a HTTP-formatted string representation of [date].
21 : ///
22 : /// This follows [RFC 822](http://tools.ietf.org/html/rfc822) as updated by [RFC
23 : /// 1123](http://tools.ietf.org/html/rfc1123).
24 : String formatHttpDate(DateTime date) {
25 0 : date = date.toUtc();
26 0 : var buffer = new StringBuffer()
27 0 : ..write(_WEEKDAYS[date.weekday - 1])
28 0 : ..write(", ")
29 0 : ..write(date.day <= 9 ? "0" : "")
30 0 : ..write(date.day.toString())
31 0 : ..write(" ")
32 0 : ..write(_MONTHS[date.month - 1])
33 0 : ..write(" ")
34 0 : ..write(date.year.toString())
35 0 : ..write(date.hour <= 9 ? " 0" : " ")
36 0 : ..write(date.hour.toString())
37 0 : ..write(date.minute <= 9 ? ":0" : ":")
38 0 : ..write(date.minute.toString())
39 0 : ..write(date.second <= 9 ? ":0" : ":")
40 0 : ..write(date.second.toString())
41 0 : ..write(" GMT");
42 0 : return buffer.toString();
43 : }
44 :
45 : /// Parses an HTTP-formatted date into a UTC [DateTime].
46 : ///
47 : /// This follows [RFC
48 : /// 2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3). It will
49 : /// throw a [FormatException] if [date] is invalid.
50 : DateTime parseHttpDate(String date) {
51 0 : return wrapFormatException("HTTP date", date, () {
52 0 : var scanner = new StringScanner(date);
53 :
54 0 : if (scanner.scan(_longWeekdayRegExp)) {
55 : // RFC 850 starts with a long weekday.
56 0 : scanner.expect(", ");
57 0 : var day = _parseInt(scanner, 2);
58 0 : scanner.expect("-");
59 0 : var month = _parseMonth(scanner);
60 0 : scanner.expect("-");
61 0 : var year = 1900 + _parseInt(scanner, 2);
62 0 : scanner.expect(" ");
63 0 : var time = _parseTime(scanner);
64 0 : scanner.expect(" GMT");
65 0 : scanner.expectDone();
66 :
67 0 : return _makeDateTime(year, month, day, time);
68 : }
69 :
70 : // RFC 1123 and asctime both start with a short weekday.
71 0 : scanner.expect(_shortWeekdayRegExp);
72 0 : if (scanner.scan(", ")) {
73 : // RFC 1123 follows the weekday with a comma.
74 0 : var day = _parseInt(scanner, 2);
75 0 : scanner.expect(" ");
76 0 : var month = _parseMonth(scanner);
77 0 : scanner.expect(" ");
78 0 : var year = _parseInt(scanner, 4);
79 0 : scanner.expect(" ");
80 0 : var time = _parseTime(scanner);
81 0 : scanner.expect(" GMT");
82 0 : scanner.expectDone();
83 :
84 0 : return _makeDateTime(year, month, day, time);
85 : }
86 :
87 : // asctime follows the weekday with a space.
88 0 : scanner.expect(" ");
89 0 : var month = _parseMonth(scanner);
90 0 : scanner.expect(" ");
91 0 : var day = scanner.scan(" ") ? _parseInt(scanner, 1) : _parseInt(scanner, 2);
92 0 : scanner.expect(" ");
93 0 : var time = _parseTime(scanner);
94 0 : scanner.expect(" ");
95 0 : var year = _parseInt(scanner, 4);
96 0 : scanner.expectDone();
97 :
98 0 : return _makeDateTime(year, month, day, time);
99 : });
100 : }
101 :
102 : /// Parses a short-form month name to a form accepted by [DateTime].
103 : int _parseMonth(StringScanner scanner) {
104 0 : scanner.expect(_monthRegExp);
105 : // DateTime uses 1-indexed months.
106 0 : return _MONTHS.indexOf(scanner.lastMatch[0]) + 1;
107 : }
108 :
109 : /// Parses an int an enforces that it has exactly [digits] digits.
110 : int _parseInt(StringScanner scanner, int digits) {
111 0 : scanner.expect(_digitRegExp);
112 0 : if (scanner.lastMatch[0].length != digits) {
113 0 : scanner.error("expected a $digits-digit number.");
114 : }
115 :
116 0 : return int.parse(scanner.lastMatch[0]);
117 : }
118 :
119 : /// Parses an timestamp of the form "HH:MM:SS" on a 24-hour clock.
120 : DateTime _parseTime(StringScanner scanner) {
121 0 : var hours = _parseInt(scanner, 2);
122 0 : if (hours >= 24) scanner.error("hours may not be greater than 24.");
123 0 : scanner.expect(':');
124 :
125 0 : var minutes = _parseInt(scanner, 2);
126 0 : if (minutes >= 60) scanner.error("minutes may not be greater than 60.");
127 0 : scanner.expect(':');
128 :
129 0 : var seconds = _parseInt(scanner, 2);
130 0 : if (seconds >= 60) scanner.error("seconds may not be greater than 60.");
131 :
132 0 : return new DateTime(1, 1, 1, hours, minutes, seconds);
133 : }
134 :
135 : /// Returns a UTC [DateTime] from the given components.
136 : ///
137 : /// Validates that [day] is a valid day for [month]. If it's not, throws a
138 : /// [FormatException].
139 : DateTime _makeDateTime(int year, int month, int day, DateTime time) {
140 : var dateTime =
141 0 : new DateTime.utc(year, month, day, time.hour, time.minute, time.second);
142 :
143 : // If [day] was too large, it will cause [month] to overflow.
144 0 : if (dateTime.month != month) {
145 0 : throw new FormatException("invalid day '$day' for month '$month'.");
146 : }
147 : return dateTime;
148 : }
|