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