| // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | part of "dart:_http"; | 
 |  | 
 | /// Utility functions for working with dates with HTTP specific date | 
 | /// formats. | 
 | class HttpDate { | 
 |   // From RFC-2616 section "3.3.1 Full Date", | 
 |   // http://tools.ietf.org/html/rfc2616#section-3.3.1 | 
 |   // | 
 |   // HTTP-date    = rfc1123-date | rfc850-date | asctime-date | 
 |   // rfc1123-date = wkday "," SP date1 SP time SP "GMT" | 
 |   // rfc850-date  = weekday "," SP date2 SP time SP "GMT" | 
 |   // asctime-date = wkday SP date3 SP time SP 4DIGIT | 
 |   // date1        = 2DIGIT SP month SP 4DIGIT | 
 |   //                ; day month year (e.g., 02 Jun 1982) | 
 |   // date2        = 2DIGIT "-" month "-" 2DIGIT | 
 |   //                ; day-month-year (e.g., 02-Jun-82) | 
 |   // date3        = month SP ( 2DIGIT | ( SP 1DIGIT )) | 
 |   //                ; month day (e.g., Jun  2) | 
 |   // time         = 2DIGIT ":" 2DIGIT ":" 2DIGIT | 
 |   //                ; 00:00:00 - 23:59:59 | 
 |   // wkday        = "Mon" | "Tue" | "Wed" | 
 |   //              | "Thu" | "Fri" | "Sat" | "Sun" | 
 |   // weekday      = "Monday" | "Tuesday" | "Wednesday" | 
 |   //              | "Thursday" | "Friday" | "Saturday" | "Sunday" | 
 |   // month        = "Jan" | "Feb" | "Mar" | "Apr" | 
 |   //              | "May" | "Jun" | "Jul" | "Aug" | 
 |   //              | "Sep" | "Oct" | "Nov" | "Dec" | 
 |  | 
 |   /// Format a date according to | 
 |   /// [RFC-1123](http://tools.ietf.org/html/rfc1123 "RFC-1123"), | 
 |   /// e.g. `Thu, 1 Jan 1970 00:00:00 GMT`. | 
 |   static String format(DateTime date) { | 
 |     StringBuffer sb = StringBuffer(); | 
 |     _formatTo(date, sb); | 
 |     return sb.toString(); | 
 |   } | 
 |  | 
 |   static const List<String> _weekdayAbbreviations = <String>[ | 
 |     "Mon", | 
 |     "Tue", | 
 |     "Wed", | 
 |     "Thu", | 
 |     "Fri", | 
 |     "Sat", | 
 |     "Sun", | 
 |   ]; | 
 |  | 
 |   static const List<String> _weekdays = <String>[ | 
 |     "Monday", | 
 |     "Tuesday", | 
 |     "Wednesday", | 
 |     "Thursday", | 
 |     "Friday", | 
 |     "Saturday", | 
 |     "Sunday", | 
 |   ]; | 
 |  | 
 |   static const List<String> _monthAbbreviations = <String>[ | 
 |     "Jan", | 
 |     "Feb", | 
 |     "Mar", | 
 |     "Apr", | 
 |     "May", | 
 |     "Jun", | 
 |     "Jul", | 
 |     "Aug", | 
 |     "Sep", | 
 |     "Oct", | 
 |     "Nov", | 
 |     "Dec", | 
 |   ]; | 
 |  | 
 |   static String _formatTo(DateTime date, StringSink sb) { | 
 |     DateTime d = date.toUtc(); | 
 |     sb | 
 |       ..write(_weekdayAbbreviations[d.weekday - 1]) | 
 |       ..write(", ") | 
 |       ..write(d.day <= 9 ? "0" : "") | 
 |       ..write(d.day.toString()) | 
 |       ..write(" ") | 
 |       ..write(_monthAbbreviations[d.month - 1]) | 
 |       ..write(" ") | 
 |       ..write(d.year.toString()) | 
 |       ..write(d.hour <= 9 ? " 0" : " ") | 
 |       ..write(d.hour.toString()) | 
 |       ..write(d.minute <= 9 ? ":0" : ":") | 
 |       ..write(d.minute.toString()) | 
 |       ..write(d.second <= 9 ? ":0" : ":") | 
 |       ..write(d.second.toString()) | 
 |       ..write(" GMT"); | 
 |     return sb.toString(); | 
 |   } | 
 |  | 
 |   /// Parse a date string in either of the formats | 
 |   /// [RFC-1123](http://tools.ietf.org/html/rfc1123 "RFC-1123"), | 
 |   /// [RFC-850](http://tools.ietf.org/html/rfc850 "RFC-850") or | 
 |   /// ANSI C's asctime() format. These formats are listed here. | 
 |   /// | 
 |   /// * Thu, 1 Jan 1970 00:00:00 GMT | 
 |   /// * Thursday, 1-Jan-1970 00:00:00 GMT | 
 |   /// * Thu Jan  1 00:00:00 1970 | 
 |   /// | 
 |   /// For more information see [RFC-2616 section | 
 |   /// 3.1.1](http://tools.ietf.org/html/rfc2616#section-3.3.1 | 
 |   /// "RFC-2616 section 3.1.1"). | 
 |   static DateTime parse(String date) => | 
 |       _parse(date, 0, date.length, _invalidHttpDate, isCookieDate: false); | 
 |  | 
 |   static Never _invalidHttpDate(String source, int start, int end) => | 
 |       throw HttpException("Invalid HTTP date ${source.substring(start, end)}"); | 
 |  | 
 |   /// Try parsing like [parse], but return `null` if parsing fails. | 
 |   static DateTime? _tryParse(String date) { | 
 |     try { | 
 |       return _parse(date, 0, date.length, _failedTryParse, isCookieDate: false); | 
 |     } on HttpException { | 
 |       return null; | 
 |     } | 
 |   } | 
 |  | 
 |   // Cheaper throw for [_tryParse] | 
 |   static Never _failedTryParse(String source, int start, int end) => | 
 |       throw const HttpException(""); | 
 |  | 
 |   /// Implements [parse] on a substring. | 
 |   /// | 
 |   /// If [isCookieDate] is `true`, also accepts `Thu, 1-Jan-1970 00:00:00 GMT`, | 
 |   /// with `-`s between day-month-year. | 
 |   /// This format was accepted by the special Cookie-date parser, | 
 |   /// which now uses this function too. | 
 |   static DateTime _parse( | 
 |     String source, | 
 |     int start, | 
 |     int end, | 
 |     Never Function(String, int, int) onError, { | 
 |     required bool isCookieDate, | 
 |   }) { | 
 |     final int SP = 32; | 
 |  | 
 |     // Almost same format after the week-day, only differ by one character. | 
 |     const int formatRfc1123 = _CharCode.SP; | 
 |     const int formatRfc850 = _CharCode.MINUS; | 
 |     // Separate format. | 
 |     const int formatAsctime = 0; | 
 |  | 
 |     int index = start; | 
 |  | 
 |     Never throwError() { | 
 |       onError(source, start, end); | 
 |     } | 
 |  | 
 |     bool maybeExpectChar(int charCode) { | 
 |       if (index < end && source.codeUnitAt(index) == charCode) { | 
 |         index++; | 
 |         return true; | 
 |       } | 
 |       return false; | 
 |     } | 
 |  | 
 |     void expectChar(int charCode) { | 
 |       if (!maybeExpectChar(charCode)) throwError(); | 
 |     } | 
 |  | 
 |     // Detects one of three recognized formats. | 
 |     // | 
 |     // All three formats start with the week-day in different ways: | 
 |     // * `Mon `: [formatAsctime] | 
 |     // * `Mon,`: [formatRfc1123] | 
 |     // * `Monday,`: [formatRfc850] | 
 |     int expectWeekday() { | 
 |       for (var i = 0; i < _weekdayAbbreviations.length; i++) { | 
 |         var wkday = _weekdayAbbreviations[i]; // Three-letter day abbreviation. | 
 |         assert(wkday.length == 3); | 
 |         if (index + 3 <= end && _isTextNoCase(source, index, 3, wkday)) { | 
 |           var weekday = _weekdays[i]; // Unabbreviated day. | 
 |           // Check if following characters are the rest of the day name. | 
 |           if (index + weekday.length <= end && | 
 |               _isTextNoCase( | 
 |                 source, | 
 |                 index + 3, | 
 |                 weekday.length - 3, | 
 |                 weekday, | 
 |                 3, | 
 |               )) { | 
 |             index += weekday.length; | 
 |             expectChar(_CharCode.COMMA); | 
 |             return formatRfc850; | 
 |           } | 
 |           index += 3; | 
 |           if (index < end) { | 
 |             var nextChar = source.codeUnitAt(index); | 
 |             if (nextChar == _CharCode.COMMA) { | 
 |               index++; | 
 |               return formatRfc1123; | 
 |             } | 
 |             if (nextChar == _CharCode.SP) { | 
 |               index++; | 
 |               return formatAsctime; | 
 |             } | 
 |           } | 
 |           break; | 
 |         } | 
 |       } | 
 |       throwError(); | 
 |     } | 
 |  | 
 |     int expectMonth() { | 
 |       for (var i = 0; i < _monthAbbreviations.length; i++) { | 
 |         String monthAbbreviation = _monthAbbreviations[i]; | 
 |         assert(monthAbbreviation.length == 3); | 
 |         if (index + 3 <= end && | 
 |             _isTextNoCase(source, index, 3, monthAbbreviation)) { | 
 |           index += 3; | 
 |           return i; | 
 |         } | 
 |       } | 
 |       throwError(); | 
 |     } | 
 |  | 
 |     int expectNum(int maxLength) { | 
 |       int value = 0; | 
 |       int start = index; | 
 |       while (index < end) { | 
 |         int digit = source.codeUnitAt(index) ^ 0x30; | 
 |         if (digit <= 9) { | 
 |           value = value * 10 + digit; | 
 |           index++; | 
 |           continue; | 
 |         } | 
 |         break; | 
 |       } | 
 |       int length = index - start; | 
 |       if (length > 0 && length <= maxLength) { | 
 |         return value; | 
 |       } | 
 |       throwError(); | 
 |     } | 
 |  | 
 |     int format = expectWeekday(); | 
 |     int year; | 
 |     int month; | 
 |     int day; | 
 |     int hours; | 
 |     int minutes; | 
 |     int seconds; | 
 |     if (format == formatAsctime) { | 
 |       month = expectMonth(); | 
 |       expectChar(_CharCode.SP); | 
 |       if (source.codeUnitAt(index) == _CharCode.SP) index++; | 
 |       day = expectNum(2); | 
 |       expectChar(_CharCode.SP); | 
 |       hours = expectNum(2); | 
 |       expectChar(_CharCode.COLON); | 
 |       minutes = expectNum(2); | 
 |       expectChar(_CharCode.COLON); | 
 |       seconds = expectNum(2); | 
 |       expectChar(_CharCode.SP); | 
 |       year = expectNum(4); | 
 |     } else { | 
 |       var dateSeparator = format; | 
 |       var alternateDateSeparator = isCookieDate | 
 |           ? _CharCode.MINUS | 
 |           : _CharCode.NONE; | 
 |       expectChar(_CharCode.SP); | 
 |       day = expectNum(2); | 
 |       if (!maybeExpectChar(dateSeparator)) expectChar(alternateDateSeparator); | 
 |       month = expectMonth(); | 
 |       if (!maybeExpectChar(dateSeparator)) expectChar(alternateDateSeparator); | 
 |       year = expectNum(4); | 
 |       expectChar(_CharCode.SP); | 
 |       hours = expectNum(2); | 
 |       expectChar(_CharCode.COLON); | 
 |       minutes = expectNum(2); | 
 |       expectChar(_CharCode.COLON); | 
 |       seconds = expectNum(2); | 
 |       if (index + 4 <= end && _isTextNoCase(source, index, 4, ' GMT')) { | 
 |         index += 4; | 
 |       } else { | 
 |         throwError(); | 
 |       } | 
 |     } | 
 |     if (index != end) throwError(); | 
 |     if (isCookieDate && year < 100) { | 
 |       year += year >= 70 ? 1900 : 2000; | 
 |     } | 
 |     return DateTime.utc(year, month + 1, day, hours, minutes, seconds); | 
 |   } | 
 |  | 
 |   // Parse a cookie date (sub-)string. | 
 |   // | 
 |   // Allows all HTTP legacy formats, not just the recommended RFC-1123 format. | 
 |   // | 
 |   // See https://www.rfc-editor.org/rfc/rfc2616#section-3.3.1 | 
 |   static DateTime _parseCookieDate( | 
 |     String source, | 
 |     int sourceStart, | 
 |     int sourceEnd, | 
 |   ) => _parse( | 
 |     source, | 
 |     sourceStart, | 
 |     sourceEnd, | 
 |     _invalidCookieDate, | 
 |     isCookieDate: true, | 
 |   ); | 
 |  | 
 |   static Never _invalidCookieDate(String source, int start, int end) => | 
 |       throw HttpException( | 
 |         "Invalid cookie date ${source.substring(start, end)}", | 
 |       ); | 
 | } |