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