From 04e451eb4dd8d0a862876f735c2f191ef8f51f0f Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Tue, 17 Dec 2024 14:35:22 -0800 Subject: [PATCH 1/2] Fix a potential exponential backtracking issue when parsing quoted headers --- pkgs/http_parser/CHANGELOG.md | 7 ++ pkgs/http_parser/lib/src/scan.dart | 4 +- pkgs/http_parser/pubspec.yaml | 2 +- .../test/authentication_challenge_test.dart | 8 ++ pkgs/http_parser/test/scan_test.dart | 74 +++++++++++++++++++ 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 pkgs/http_parser/test/scan_test.dart diff --git a/pkgs/http_parser/CHANGELOG.md b/pkgs/http_parser/CHANGELOG.md index 5c56cf7113..d66d5d63e7 100644 --- a/pkgs/http_parser/CHANGELOG.md +++ b/pkgs/http_parser/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.1.2-wip + +* Fixed a bug where parsing quoted header values could require a regex to + backtrack +* Fixed a bug where quoted header values containing escaped quotes would not + be correctly parsed. + ## 4.1.1 * Move to `dart-lang/http` monorepo. diff --git a/pkgs/http_parser/lib/src/scan.dart b/pkgs/http_parser/lib/src/scan.dart index 96fb8ae3f5..2624b175a0 100644 --- a/pkgs/http_parser/lib/src/scan.dart +++ b/pkgs/http_parser/lib/src/scan.dart @@ -11,7 +11,7 @@ final token = RegExp(r'[^()<>@,;:"\\/[\]?={} \t\x00-\x1F\x7F]+'); final _lws = RegExp(r'(?:\r\n)?[ \t]+'); /// A quoted string. -final _quotedString = RegExp(r'"(?:[^"\x00-\x1F\x7F]|\\.)*"'); +final _quotedString = RegExp(r'"(?:[^"\x00-\x1F\x7F\\]|\\.)*"'); /// A quoted pair. final _quotedPair = RegExp(r'\\(.)'); @@ -54,7 +54,7 @@ List parseList(StringScanner scanner, T Function() parseElement) { return result; } -/// Parses a single quoted string, and returns its contents. +/// Parses a double quoted string, and returns its contents. /// /// If [name] is passed, it's used to describe the expected value if it's not /// found. diff --git a/pkgs/http_parser/pubspec.yaml b/pkgs/http_parser/pubspec.yaml index 13888276f1..95c85b6aaa 100644 --- a/pkgs/http_parser/pubspec.yaml +++ b/pkgs/http_parser/pubspec.yaml @@ -1,5 +1,5 @@ name: http_parser -version: 4.1.1 +version: 4.1.2-wip description: >- A platform-independent package for parsing and serializing HTTP formats. repository: https://github.com/dart-lang/http/tree/master/pkgs/http_parser diff --git a/pkgs/http_parser/test/authentication_challenge_test.dart b/pkgs/http_parser/test/authentication_challenge_test.dart index 52d798ca3a..3e0f63fdcf 100644 --- a/pkgs/http_parser/test/authentication_challenge_test.dart +++ b/pkgs/http_parser/test/authentication_challenge_test.dart @@ -73,6 +73,14 @@ void _singleChallengeTests( equals({'realm': 'fblthp, foo=bar', 'baz': 'qux'})); }); + test('parses quoted string parameters with surrounding spaces', () { + final challenge = + parseChallenge('scheme realm= "fblthp, foo=bar" , baz= "qux" '); + expect(challenge.scheme, equals('scheme')); + expect(challenge.parameters, + equals({'realm': 'fblthp, foo=bar', 'baz': 'qux'})); + }); + test('normalizes the case of the scheme', () { final challenge = parseChallenge('ScHeMe realm=fblthp'); expect(challenge.scheme, equals('scheme')); diff --git a/pkgs/http_parser/test/scan_test.dart b/pkgs/http_parser/test/scan_test.dart new file mode 100644 index 0000000000..a7710775f2 --- /dev/null +++ b/pkgs/http_parser/test/scan_test.dart @@ -0,0 +1,74 @@ +// Copyright (c) 2024, 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. + +import 'package:http_parser/src/scan.dart'; +import 'package:string_scanner/string_scanner.dart'; +import 'package:test/test.dart'; + +void main() { + group('expectQuotedString', () { + test('no open quote', () { + final scanner = StringScanner('test"'); + expect( + () => expectQuotedString(scanner), + throwsA(isA() + .having((e) => e.offset, 'offset', 0) + .having((e) => e.message, 'message', 'expected quoted string.') + .having((e) => e.source, 'source', 'test"'))); + expect(scanner.isDone, isFalse); + expect(scanner.lastMatch, null); + expect(scanner.position, 0); + }); + + test('no close quote', () { + final scanner = StringScanner('"test'); + expect( + () => expectQuotedString(scanner), + throwsA(isA() + .having((e) => e.offset, 'offset', 0) + .having((e) => e.message, 'message', 'expected quoted string.') + .having((e) => e.source, 'source', '"test'))); + expect(scanner.isDone, isFalse); + expect(scanner.lastMatch, null); + expect(scanner.position, 0); + }); + + test('simple quoted', () { + final scanner = StringScanner('"test"'); + expect(expectQuotedString(scanner), 'test'); + expect(scanner.isDone, isTrue); + expect(scanner.lastMatch?.group(0), '"test"'); + expect(scanner.position, 6); + }); + + test(r'escaped \', () { + final scanner = StringScanner(r'"escaped: \\"'); + expect(expectQuotedString(scanner), r'escaped: \'); + expect(scanner.isDone, isTrue); + expect(scanner.lastMatch?.group(0), r'"escaped: \\"'); + expect(scanner.position, 13); + }); + + test(r'bare \', () { + final scanner = StringScanner(r'"bare: \"'); + expect( + () => expectQuotedString(scanner), + throwsA(isA() + .having((e) => e.offset, 'offset', 0) + .having((e) => e.message, 'message', 'expected quoted string.') + .having((e) => e.source, 'source', r'"bare: \"'))); + expect(scanner.isDone, isFalse); + expect(scanner.lastMatch, null); + expect(scanner.position, 0); + }); + + test(r'escaped "', () { + final scanner = StringScanner(r'"escaped: \""'); + expect(expectQuotedString(scanner), r'escaped: "'); + expect(scanner.isDone, isTrue); + expect(scanner.lastMatch?.group(0), r'"escaped: \""'); + expect(scanner.position, 13); + }); + }); +} From eb2bc28f52e711a9c8b6a8541f3a69f76188e938 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Tue, 17 Dec 2024 14:45:56 -0800 Subject: [PATCH 2/2] Update scan.dart --- pkgs/http_parser/lib/src/scan.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkgs/http_parser/lib/src/scan.dart b/pkgs/http_parser/lib/src/scan.dart index 2624b175a0..685f89bfbf 100644 --- a/pkgs/http_parser/lib/src/scan.dart +++ b/pkgs/http_parser/lib/src/scan.dart @@ -11,6 +11,11 @@ final token = RegExp(r'[^()<>@,;:"\\/[\]?={} \t\x00-\x1F\x7F]+'); final _lws = RegExp(r'(?:\r\n)?[ \t]+'); /// A quoted string. +/// +/// [RFC-2616 2.2](https://datatracker.ietf.org/doc/html/rfc2616#section-2.2) +/// defines the `quoted-string` production. This expression is identical to +/// the RFC definition expect that, in this regex, `qdtext` is not allowed to +/// contain `"\"`. final _quotedString = RegExp(r'"(?:[^"\x00-\x1F\x7F\\]|\\.)*"'); /// A quoted pair.