From 6a8c3c5f71a6262faec146e2922c9ab4aaf740e2 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 4 Sep 2024 18:17:36 +0200 Subject: [PATCH] Add `responseHeaders` extension on `XMLHttpRequest` (#298) * Add `responseHeaders` extension on `XMLHttpRequest` Move this extension from `package:http` to here, it might also be used in `package:grpc`. * Add test * Reformat * Add changelog * Use LineSplitter * Use `update` * Use split instead of indexOf --- web/CHANGELOG.md | 1 + web/lib/src/helpers/extensions.dart | 31 +++++++++++++++++++++++++++++ web/test/helpers_test.dart | 19 ++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/web/CHANGELOG.md b/web/CHANGELOG.md index bc5d5cdc..72de9522 100644 --- a/web/CHANGELOG.md +++ b/web/CHANGELOG.md @@ -10,6 +10,7 @@ `getter`s and `setter`s, respectively. - Exposed constants with primitive values as non-`external` so they can be `switch`ed over. +- Add an extension `responseHeaders` to `XMLHttpRequest`. ## 1.0.0 diff --git a/web/lib/src/helpers/extensions.dart b/web/lib/src/helpers/extensions.dart index 24f5be3b..b3d68092 100644 --- a/web/lib/src/helpers/extensions.dart +++ b/web/lib/src/helpers/extensions.dart @@ -21,6 +21,7 @@ /// * conversions: for example to wrap a `TouchList` as a `List` library; +import 'dart:convert'; import 'dart:js_interop'; import 'dart:math' show Point; @@ -103,3 +104,33 @@ extension TouchListConvert on TouchList { @Deprecated('Use JSImmutableListWrapper directly instead.') List toList() => JSImmutableListWrapper(this); } + +/// Returns all response headers as a key-value map. +/// +/// Multiple values for the same header key can be combined into one, +/// separated by a comma and a space. +/// +/// See: http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method +extension XMLHttpRequestGlue on XMLHttpRequest { + Map get responseHeaders { + // from Closure's goog.net.Xhrio.getResponseHeaders. + final headers = {}; + final headersString = getAllResponseHeaders(); + final headersList = + LineSplitter.split(headersString).where((header) => header.isNotEmpty); + for (final header in headersList) { + final split = header.split(': '); + if (split.length <= 1) { + continue; + } + final key = split[0].toLowerCase(); + final value = split.skip(1).join(': '); + headers.update( + key, + (oldValue) => '$oldValue, $value', + ifAbsent: () => value, + ); + } + return headers; + } +} diff --git a/web/test/helpers_test.dart b/web/test/helpers_test.dart index aa419ac3..fa9b33de 100644 --- a/web/test/helpers_test.dart +++ b/web/test/helpers_test.dart @@ -36,4 +36,23 @@ void main() { // Ensure accessing any arbitrary item in the list does not throw. expect(() => dartList[0], returnsNormally); }); + + test('responseHeaders transforms headers into a map', () async { + final request = XMLHttpRequest() + ..open('GET', 'www.google.com') + ..send(); + + await request.onLoad.first; + + expect( + request.responseHeaders, + allOf( + containsPair('content-length', '10'), + containsPair('content-type', 'text/plain; charset=utf-8'), + containsPair('x-content-type-options', 'nosniff'), + containsPair('x-frame-options', 'SAMEORIGIN'), + containsPair('x-xss-protection', '1; mode=block'), + ), + ); + }); }