diff --git a/.cspell/misc.txt b/.cspell/misc.txt index cd626a0bb05..5d3a63a95f4 100644 --- a/.cspell/misc.txt +++ b/.cspell/misc.txt @@ -1,3 +1,4 @@ +asctime browsable cleartext codegen diff --git a/packages/neon_framework/lib/src/utils/request_manager.dart b/packages/neon_framework/lib/src/utils/request_manager.dart index f1f422cf930..2cf3b065a60 100644 --- a/packages/neon_framework/lib/src/utils/request_manager.dart +++ b/packages/neon_framework/lib/src/utils/request_manager.dart @@ -5,7 +5,6 @@ import 'package:built_value/serializer.dart'; import 'package:dynamite_runtime/http_client.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; -import 'package:http_parser/http_parser.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:neon_framework/models.dart'; @@ -13,6 +12,7 @@ import 'package:neon_framework/src/bloc/result.dart'; import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/storage.dart'; import 'package:nextcloud/nextcloud.dart'; +import 'package:nextcloud/utils.dart'; import 'package:rxdart/rxdart.dart'; import 'package:timezone/timezone.dart' as tz; import 'package:xml/xml.dart' as xml; @@ -368,8 +368,7 @@ class CacheParameters { factory CacheParameters.parseHeaders(Map headers) { tz.TZDateTime? expiry; if (headers.containsKey('expires')) { - final parsed = parseHttpDate(headers['expires']! as String); - expiry = tz.TZDateTime.from(parsed, tz.UTC); + expiry = parseHttpDate(headers['expires']! as String); } return CacheParameters( diff --git a/packages/neon_framework/pubspec.yaml b/packages/neon_framework/pubspec.yaml index fa4906130f5..704daf14b9f 100644 --- a/packages/neon_framework/pubspec.yaml +++ b/packages/neon_framework/pubspec.yaml @@ -31,7 +31,6 @@ dependencies: flutter_zxing: ^1.0.0 go_router: ^13.0.0 http: ^1.0.0 - http_parser: ^4.0.0 image: ^4.0.0 intersperse: ^2.0.0 intl: ^0.18.0 diff --git a/packages/neon_framework/test/request_manager_test.dart b/packages/neon_framework/test/request_manager_test.dart index e79b864cf6b..13dd91c2c75 100644 --- a/packages/neon_framework/test/request_manager_test.dart +++ b/packages/neon_framework/test/request_manager_test.dart @@ -8,11 +8,11 @@ import 'package:built_value/serializer.dart'; import 'package:dynamite_runtime/http_client.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart'; -import 'package:http_parser/http_parser.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/src/bloc/result.dart'; import 'package:neon_framework/src/utils/request_manager.dart'; import 'package:neon_framework/testing.dart'; +import 'package:nextcloud/utils.dart'; import 'package:rxdart/rxdart.dart'; import 'package:timezone/timezone.dart' as tz; diff --git a/packages/nextcloud/lib/src/utils/http_date_parser.dart b/packages/nextcloud/lib/src/utils/http_date_parser.dart new file mode 100644 index 00000000000..1776b8fd3dd --- /dev/null +++ b/packages/nextcloud/lib/src/utils/http_date_parser.dart @@ -0,0 +1,174 @@ +// Copyright (c) 2014, 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. + +// ignore_for_file: unnecessary_raw_strings, cascade_invocations, parameter_assignments, always_put_control_body_on_new_line + +import 'package:string_scanner/string_scanner.dart'; +import 'package:timezone/timezone.dart' as tz; + +const _weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; +const _months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +final _shortWeekdayRegExp = RegExp(r'Mon|Tue|Wed|Thu|Fri|Sat|Sun'); +final _longWeekdayRegExp = RegExp(r'Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday'); +final _monthRegExp = RegExp(r'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec'); +final _digitRegExp = RegExp(r'\d+'); +final _zoneRegExp = RegExp(r'[+|-]'); + +/// Return a HTTP-formatted string representation of [date]. +/// +/// This follows [RFC 822](http://tools.ietf.org/html/rfc822) as updated by +/// [RFC 1123](http://tools.ietf.org/html/rfc1123). +String formatHttpDate(DateTime date) { + date = date.toUtc(); + final buffer = StringBuffer() + ..write(_weekdays[date.weekday - 1]) + ..write(', ') + ..write(date.day <= 9 ? '0' : '') + ..write(date.day.toString()) + ..write(' ') + ..write(_months[date.month - 1]) + ..write(' ') + ..write(date.year.toString()) + ..write(date.hour <= 9 ? ' 0' : ' ') + ..write(date.hour.toString()) + ..write(date.minute <= 9 ? ':0' : ':') + ..write(date.minute.toString()) + ..write(date.second <= 9 ? ':0' : ':') + ..write(date.second.toString()) + ..write(' GMT'); + return buffer.toString(); +} + +/// Parses an HTTP-formatted date into a UTC [DateTime]. +/// +/// This follows [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3). +/// It will throw a [FormatException] if [date] is invalid. +tz.TZDateTime parseHttpDate(String date) { + final scanner = StringScanner(date); + + if (scanner.scan(_longWeekdayRegExp)) { + // RFC 850 starts with a long weekday. + scanner.expect(', '); + final day = _parseInt(scanner, 2); + scanner.expect('-'); + final month = _parseMonth(scanner); + scanner.expect('-'); + final year = 1900 + _parseInt(scanner, 2); + scanner.expect(' '); + final time = _parseTime(scanner); + scanner.expect(' GMT'); + scanner.expectDone(); + + return _makeDateTime(year, month, day, time); + } + + // RFC 1123 and asctime both start with a short weekday. + scanner.expect(_shortWeekdayRegExp); + if (scanner.scan(', ')) { + // RFC 1123 follows the weekday with a comma. + final day = _parseInt(scanner, 2); + scanner.expect(' '); + final month = _parseMonth(scanner); + scanner.expect(' '); + final year = _parseInt(scanner, 4); + scanner.expect(' '); + final time = _parseTime(scanner); + scanner.expect(' '); + final offset = _parseLocation(scanner); + scanner.expectDone(); + + return _makeDateTime(year, month, day, time, offset); + } + + // asctime follows the weekday with a space. + scanner.expect(' '); + final month = _parseMonth(scanner); + scanner.expect(' '); + final day = scanner.scan(' ') ? _parseInt(scanner, 1) : _parseInt(scanner, 2); + scanner.expect(' '); + final time = _parseTime(scanner); + scanner.expect(' '); + final year = _parseInt(scanner, 4); + scanner.expectDone(); + + return _makeDateTime(year, month, day, time); +} + +/// Parses a short-form month name to a form accepted by [DateTime]. +int _parseMonth(StringScanner scanner) { + scanner.expect(_monthRegExp); + // DateTime uses 1-indexed months. + return _months.indexOf(scanner.lastMatch![0]!) + 1; +} + +/// Parses an int an enforces that it has exactly [digits] digits. +int _parseInt(StringScanner scanner, int digits) { + scanner.expect(_digitRegExp); + if (scanner.lastMatch![0]!.length != digits) { + scanner.error('expected a $digits-digit number.'); + } + + return int.parse(scanner.lastMatch![0]!); +} + +/// Parses an timestamp of the form "HH:MM:SS" on a 24-hour clock. +tz.TZDateTime _parseTime(StringScanner scanner) { + final hours = _parseInt(scanner, 2); + if (hours >= 24) scanner.error('hours may not be greater than 24.'); + scanner.expect(':'); + + final minutes = _parseInt(scanner, 2); + if (minutes >= 60) scanner.error('minutes may not be greater than 60.'); + scanner.expect(':'); + + final seconds = _parseInt(scanner, 2); + if (seconds >= 60) scanner.error('seconds may not be greater than 60.'); + + return tz.TZDateTime.utc(1, 1, 1, hours, minutes, seconds); +} + +Duration? _parseLocation(StringScanner scanner) { + if (scanner.scan('GMT')) { + return null; + } else if (scanner.scan(_zoneRegExp)) { + final modifier = scanner.lastMatch![0]!; + + scanner.expect(RegExp(r'\d{2}')); + final hours = int.parse(scanner.lastMatch![0]!); + + scanner.expect(RegExp(r'\d{2}')); + final minutes = int.parse(scanner.lastMatch![0]!); + + if (hours >= 99 && minutes > 59) { + throw FormatException("invalid timezone offset '$hours$minutes'."); + } + + var offset = Duration(hours: hours, minutes: minutes); + if (modifier == '-') { + offset *= -1; + } + + return offset; + } else { + throw const FormatException('Parsing timezone can not be done unambiguously.'); + } +} + +/// Returns a UTC [tz.TZDateTime] from the given components. +/// +/// Validates that [day] is a valid day for [month]. If it's not, throws a +/// [FormatException]. +tz.TZDateTime _makeDateTime(int year, int month, int day, tz.TZDateTime time, [Duration? offset]) { + var dateTime = tz.TZDateTime(tz.UTC, year, month, day, time.hour, time.minute, time.second); + if (offset != null) { + dateTime = dateTime.add(offset); + } + + // If [day] was too large, it will cause [month] to overflow. + if (dateTime.month != month) { + throw FormatException("invalid day '$day' for month '$month'."); + } + return dateTime; +} diff --git a/packages/nextcloud/lib/src/webdav/file.dart b/packages/nextcloud/lib/src/webdav/file.dart index fa5c42fb1b0..3032e3b2989 100644 --- a/packages/nextcloud/lib/src/webdav/file.dart +++ b/packages/nextcloud/lib/src/webdav/file.dart @@ -1,5 +1,5 @@ -import 'package:http_parser/http_parser.dart'; import 'package:nextcloud/src/utils/date_time.dart'; +import 'package:nextcloud/src/utils/http_date_parser.dart'; import 'package:nextcloud/src/webdav/client.dart'; import 'package:nextcloud/src/webdav/path_uri.dart'; import 'package:nextcloud/src/webdav/props.dart'; @@ -65,8 +65,7 @@ class WebDavFile { /// Last modified date of the file late final tz.TZDateTime? lastModified = () { if (props.davgetlastmodified != null) { - final parsed = parseHttpDate(props.davgetlastmodified!); - return tz.TZDateTime.from(parsed, tz.UTC); + return parseHttpDate(props.davgetlastmodified!); } return null; }(); diff --git a/packages/nextcloud/lib/utils.dart b/packages/nextcloud/lib/utils.dart index 14fdbe4831c..b47d93fa0a0 100644 --- a/packages/nextcloud/lib/utils.dart +++ b/packages/nextcloud/lib/utils.dart @@ -2,3 +2,4 @@ library; export 'src/utils/date_time.dart'; +export 'src/utils/http_date_parser.dart'; diff --git a/packages/nextcloud/pubspec.yaml b/packages/nextcloud/pubspec.yaml index af0a2b74d80..4e00609c3c8 100644 --- a/packages/nextcloud/pubspec.yaml +++ b/packages/nextcloud/pubspec.yaml @@ -19,9 +19,9 @@ dependencies: crypton: ^2.0.0 dynamite_runtime: ^0.2.0 http: ^1.2.0 - http_parser: ^4.0.0 json_annotation: ^4.8.1 meta: ^1.0.0 + string_scanner: ^1.1.0 timezone: ^0.9.2 universal_io: ^2.0.0 uri: ^1.0.0 diff --git a/packages/nextcloud/test/http_date_test.dart b/packages/nextcloud/test/http_date_test.dart new file mode 100644 index 00000000000..1b2380fe10a --- /dev/null +++ b/packages/nextcloud/test/http_date_test.dart @@ -0,0 +1,387 @@ +// Copyright (c) 2014, 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. + +// ignore_for_file: require_trailing_commas + +import 'package:nextcloud/src/utils/http_date_parser.dart'; +import 'package:test/test.dart'; + +void main() { + group('format', () { + test('many values with 9', () { + final date = DateTime.utc(2014, 9, 9, 9, 9, 9); + final formatted = formatHttpDate(date); + + expect(formatted, 'Tue, 09 Sep 2014 09:09:09 GMT'); + final parsed = parseHttpDate(formatted); + + expect(parsed, date); + }); + + test('end of year', () { + final date = DateTime.utc(1999, 12, 31, 23, 59, 59); + final formatted = formatHttpDate(date); + + expect(formatted, 'Fri, 31 Dec 1999 23:59:59 GMT'); + final parsed = parseHttpDate(formatted); + + expect(parsed, date); + }); + + test('start of year', () { + final date = DateTime.utc(2000); + final formatted = formatHttpDate(date); + + expect(formatted, 'Sat, 01 Jan 2000 00:00:00 GMT'); + final parsed = parseHttpDate(formatted); + + expect(parsed, date); + }); + }); + + group('parse', () { + group('RFC 1123', () { + test('parses the example date', () { + final date = parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT'); + expect(date.day, equals(6)); + expect(date.month, equals(DateTime.november)); + expect(date.year, equals(1994)); + expect(date.hour, equals(8)); + expect(date.minute, equals(49)); + expect(date.second, equals(37)); + expect(date.timeZoneName, equals('UTC')); + }); + + test('whitespace is required', () { + expect(() => parseHttpDate('Sun,06 Nov 1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06Nov 1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 199408:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37GMT'), throwsFormatException); + }); + + test('exactly one space is required', () { + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37 GMT'), throwsFormatException); + }); + + test('requires precise number lengths', () { + expect(() => parseHttpDate('Sun, 6 Nov 1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 94 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 8:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:9:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:7 GMT'), throwsFormatException); + }); + + test('requires reasonable numbers', () { + expect(() => parseHttpDate('Sun, 00 Nov 1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 31 Nov 1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 32 Aug 1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 24:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:60:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:60 GMT'), throwsFormatException); + }); + + test('only allows short weekday names', () { + expect(() => parseHttpDate('Sunday, 6 Nov 1994 08:49:37 GMT'), throwsFormatException); + }); + + test('only allows short month names', () { + expect(() => parseHttpDate('Sun, 6 November 1994 08:49:37 GMT'), throwsFormatException); + }); + + test('only allows GMT', () { + expect(() => parseHttpDate('Sun, 6 Nov 1994 08:49:37 PST'), throwsFormatException); + }); + + test('disallows trailing whitespace', () { + expect(() => parseHttpDate('Sun, 6 Nov 1994 08:49:37 GMT '), throwsFormatException); + }); + }); + + group('RFC 822', () { + test('parses the example date', () { + final date = parseHttpDate('Thu, 14 Mar 2024 10:42:04 +0000'); + expect(date.day, equals(14)); + expect(date.month, equals(DateTime.march)); + expect(date.year, equals(2024)); + expect(date.hour, equals(10)); + expect(date.minute, equals(42)); + expect(date.second, equals(04)); + expect(date.timeZoneName, equals('UTC')); + }); + + test('parses the example date with positive offset', () { + final date = parseHttpDate('Thu, 14 Mar 2024 10:42:04 +0800'); + expect(date.day, equals(14)); + expect(date.month, equals(DateTime.march)); + expect(date.year, equals(2024)); + expect(date.hour, equals(18)); + expect(date.minute, equals(42)); + expect(date.second, equals(04)); + expect(date.timeZoneName, equals('UTC')); + }); + + test('parses the example date with negative offset', () { + final date = parseHttpDate('Thu, 14 Mar 2024 10:42:04 -2410'); + expect(date.day, equals(13)); + expect(date.month, equals(DateTime.march)); + expect(date.year, equals(2024)); + expect(date.hour, equals(10)); + expect(date.minute, equals(32)); + expect(date.second, equals(04)); + expect(date.timeZoneName, equals('UTC')); + }); + + test('whitespace is required', () { + expect(() => parseHttpDate('Sun,06 Nov 1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06Nov 1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 199408:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37+0000'), throwsFormatException); + }); + + test('exactly one space is required', () { + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:37 +0000'), throwsFormatException); + }); + + test('requires precise number lengths', () { + expect(() => parseHttpDate('Sun, 6 Nov 1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 94 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 8:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:9:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:7 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:7 +000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:7 +00000'), throwsFormatException); + }); + + test('requires reasonable numbers', () { + expect(() => parseHttpDate('Sun, 00 Nov 1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 31 Nov 1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 32 Aug 1994 08:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 24:49:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:60:37 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Sun, 06 Nov 1994 08:49:60 +0000'), throwsFormatException); + + expect(() => parseHttpDate('Thu, 14 Mar 2024 10:42:04 +9960'), throwsFormatException); + + expect(() => parseHttpDate('Thu, 14 Mar 2024 10:42:04 -9960'), throwsFormatException); + }); + + test('only allows short weekday names', () { + expect(() => parseHttpDate('Sunday, 6 Nov 1994 08:49:37 +0000'), throwsFormatException); + }); + + test('only allows short month names', () { + expect(() => parseHttpDate('Sun, 6 November 1994 08:49:37 +0000'), throwsFormatException); + }); + + test('only allows GMT or zone offset', () { + expect(() => parseHttpDate('Sun, 6 Nov 1994 08:49:37 PST'), throwsFormatException); + }); + + test('disallows trailing whitespace', () { + expect(() => parseHttpDate('Sun, 6 Nov 1994 08:49:37 +0000 '), throwsFormatException); + }); + }); + + group('RFC 850', () { + test('parses the example date', () { + final date = parseHttpDate('Sunday, 06-Nov-94 08:49:37 GMT'); + expect(date.day, equals(6)); + expect(date.month, equals(DateTime.november)); + expect(date.year, equals(1994)); + expect(date.hour, equals(8)); + expect(date.minute, equals(49)); + expect(date.second, equals(37)); + expect(date.timeZoneName, equals('UTC')); + }); + + test('whitespace is required', () { + expect(() => parseHttpDate('Sunday,06-Nov-94 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-9408:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-94 08:49:37GMT'), throwsFormatException); + }); + + test('exactly one space is required', () { + expect(() => parseHttpDate('Sunday, 06-Nov-94 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-94 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-94 08:49:37 GMT'), throwsFormatException); + }); + + test('requires precise number lengths', () { + expect(() => parseHttpDate('Sunday, 6-Nov-94 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-1994 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-94 8:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-94 08:9:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-94 08:49:7 GMT'), throwsFormatException); + }); + + test('requires reasonable numbers', () { + expect(() => parseHttpDate('Sunday, 00-Nov-94 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 31-Nov-94 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 32-Aug-94 08:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-94 24:49:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-94 08:60:37 GMT'), throwsFormatException); + + expect(() => parseHttpDate('Sunday, 06-Nov-94 08:49:60 GMT'), throwsFormatException); + }); + + test('only allows long weekday names', () { + expect(() => parseHttpDate('Sun, 6-Nov-94 08:49:37 GMT'), throwsFormatException); + }); + + test('only allows short month names', () { + expect(() => parseHttpDate('Sunday, 6-November-94 08:49:37 GMT'), throwsFormatException); + }); + + test('only allows GMT', () { + expect(() => parseHttpDate('Sunday, 6-Nov-94 08:49:37 PST'), throwsFormatException); + }); + + test('disallows trailing whitespace', () { + expect(() => parseHttpDate('Sunday, 6-Nov-94 08:49:37 GMT '), throwsFormatException); + }); + }); + + group('asctime()', () { + test('parses the example date', () { + final date = parseHttpDate('Sun Nov 6 08:49:37 1994'); + expect(date.day, equals(6)); + expect(date.month, equals(DateTime.november)); + expect(date.year, equals(1994)); + expect(date.hour, equals(8)); + expect(date.minute, equals(49)); + expect(date.second, equals(37)); + expect(date.timeZoneName, equals('UTC')); + }); + + test('parses a date with a two-digit day', () { + final date = parseHttpDate('Sun Nov 16 08:49:37 1994'); + expect(date.day, equals(16)); + expect(date.month, equals(DateTime.november)); + expect(date.year, equals(1994)); + expect(date.hour, equals(8)); + expect(date.minute, equals(49)); + expect(date.second, equals(37)); + expect(date.timeZoneName, equals('UTC')); + }); + + test('whitespace is required', () { + expect(() => parseHttpDate('SunNov 6 08:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov6 08:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 608:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 08:49:371994'), throwsFormatException); + }); + + test('the right amount of whitespace is required', () { + expect(() => parseHttpDate('Sun Nov 6 08:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 08:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 08:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 08:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 08:49:37 1994'), throwsFormatException); + }); + + test('requires precise number lengths', () { + expect(() => parseHttpDate('Sun Nov 016 08:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 8:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 08:9:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 08:49:7 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 08:49:37 94'), throwsFormatException); + }); + + test('requires reasonable numbers', () { + expect(() => parseHttpDate('Sun Nov 0 08:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 31 08:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Aug 32 08:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 24:49:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 08:60:37 1994'), throwsFormatException); + + expect(() => parseHttpDate('Sun Nov 6 08:49:60 1994'), throwsFormatException); + }); + + test('only allows short weekday names', () { + expect(() => parseHttpDate('Sunday Nov 0 08:49:37 1994'), throwsFormatException); + }); + + test('only allows short month names', () { + expect(() => parseHttpDate('Sun November 0 08:49:37 1994'), throwsFormatException); + }); + + test('disallows trailing whitespace', () { + expect(() => parseHttpDate('Sun November 0 08:49:37 1994 '), throwsFormatException); + }); + }); + }); +} diff --git a/packages/nextcloud/test/webdav_test.dart b/packages/nextcloud/test/webdav_test.dart index c7568fd9809..a7a6925034a 100644 --- a/packages/nextcloud/test/webdav_test.dart +++ b/packages/nextcloud/test/webdav_test.dart @@ -2,10 +2,10 @@ import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; -import 'package:http_parser/http_parser.dart'; import 'package:mocktail/mocktail.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/src/utils/date_time.dart'; +import 'package:nextcloud/src/utils/http_date_parser.dart'; import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; import 'package:test_api/src/backend/invoker.dart';