From 2a03bdea8845beb3063d2b866a54a12e7b893178 Mon Sep 17 00:00:00 2001 From: Jim Evans Date: Mon, 20 Sep 2021 19:14:00 -0400 Subject: [PATCH] [dotnet] Add transformation for network responses via CDP --- .../src/webdriver/DevTools/v85/V85Network.cs | 23 ++++---- .../src/webdriver/DevTools/v92/V92Network.cs | 23 ++++---- .../src/webdriver/DevTools/v93/V93Network.cs | 23 ++++---- dotnet/src/webdriver/INetwork.cs | 12 ++++ dotnet/src/webdriver/NetworkManager.cs | 55 ++++++++++++++++++- .../src/webdriver/NetworkResponseHandler.cs | 41 ++++++++++++++ 6 files changed, 144 insertions(+), 33 deletions(-) create mode 100644 dotnet/src/webdriver/NetworkResponseHandler.cs diff --git a/dotnet/src/webdriver/DevTools/v85/V85Network.cs b/dotnet/src/webdriver/DevTools/v85/V85Network.cs index d0bdcba4339f3..a5d4f9534fcb9 100644 --- a/dotnet/src/webdriver/DevTools/v85/V85Network.cs +++ b/dotnet/src/webdriver/DevTools/v85/V85Network.cs @@ -289,22 +289,25 @@ private void OnFetchRequestPaused(object sender, Fetch.RequestPausedEventArgs e) wrappedResponse.ResponseData.StatusCode = e.ResponseStatusCode.Value; } - foreach (var header in e.ResponseHeaders) + if (e.ResponseHeaders != null) { - if (header.Name.ToLowerInvariant() == "set-cookie") + foreach (var header in e.ResponseHeaders) { - wrappedResponse.ResponseData.CookieHeaders.Add(header.Value); - } - else - { - if (wrappedResponse.ResponseData.Headers.ContainsKey(header.Name)) + if (header.Name.ToLowerInvariant() == "set-cookie") { - string currentHeaderValue = wrappedResponse.ResponseData.Headers[header.Name]; - wrappedResponse.ResponseData.Headers[header.Name] = currentHeaderValue + ", " + header.Value; + wrappedResponse.ResponseData.CookieHeaders.Add(header.Value); } else { - wrappedResponse.ResponseData.Headers.Add(header.Name, header.Value); + if (wrappedResponse.ResponseData.Headers.ContainsKey(header.Name)) + { + string currentHeaderValue = wrappedResponse.ResponseData.Headers[header.Name]; + wrappedResponse.ResponseData.Headers[header.Name] = currentHeaderValue + ", " + header.Value; + } + else + { + wrappedResponse.ResponseData.Headers.Add(header.Name, header.Value); + } } } } diff --git a/dotnet/src/webdriver/DevTools/v92/V92Network.cs b/dotnet/src/webdriver/DevTools/v92/V92Network.cs index 2b1816a59badb..0c47ad6ebfd7d 100644 --- a/dotnet/src/webdriver/DevTools/v92/V92Network.cs +++ b/dotnet/src/webdriver/DevTools/v92/V92Network.cs @@ -290,22 +290,25 @@ private void OnFetchRequestPaused(object sender, Fetch.RequestPausedEventArgs e) wrappedResponse.ResponseData.StatusCode = e.ResponseStatusCode.Value; } - foreach (var header in e.ResponseHeaders) + if (e.ResponseHeaders != null) { - if (header.Name.ToLowerInvariant() == "set-cookie") + foreach (var header in e.ResponseHeaders) { - wrappedResponse.ResponseData.CookieHeaders.Add(header.Value); - } - else - { - if (wrappedResponse.ResponseData.Headers.ContainsKey(header.Name)) + if (header.Name.ToLowerInvariant() == "set-cookie") { - string currentHeaderValue = wrappedResponse.ResponseData.Headers[header.Name]; - wrappedResponse.ResponseData.Headers[header.Name] = currentHeaderValue + ", " + header.Value; + wrappedResponse.ResponseData.CookieHeaders.Add(header.Value); } else { - wrappedResponse.ResponseData.Headers.Add(header.Name, header.Value); + if (wrappedResponse.ResponseData.Headers.ContainsKey(header.Name)) + { + string currentHeaderValue = wrappedResponse.ResponseData.Headers[header.Name]; + wrappedResponse.ResponseData.Headers[header.Name] = currentHeaderValue + ", " + header.Value; + } + else + { + wrappedResponse.ResponseData.Headers.Add(header.Name, header.Value); + } } } } diff --git a/dotnet/src/webdriver/DevTools/v93/V93Network.cs b/dotnet/src/webdriver/DevTools/v93/V93Network.cs index 0c23e875ffa3e..29e42058ac718 100644 --- a/dotnet/src/webdriver/DevTools/v93/V93Network.cs +++ b/dotnet/src/webdriver/DevTools/v93/V93Network.cs @@ -289,22 +289,25 @@ private void OnFetchRequestPaused(object sender, Fetch.RequestPausedEventArgs e) wrappedResponse.ResponseData.StatusCode = e.ResponseStatusCode.Value; } - foreach (var header in e.ResponseHeaders) + if (e.ResponseHeaders != null) { - if (header.Name.ToLowerInvariant() == "set-cookie") + foreach (var header in e.ResponseHeaders) { - wrappedResponse.ResponseData.CookieHeaders.Add(header.Value); - } - else - { - if (wrappedResponse.ResponseData.Headers.ContainsKey(header.Name)) + if (header.Name.ToLowerInvariant() == "set-cookie") { - string currentHeaderValue = wrappedResponse.ResponseData.Headers[header.Name]; - wrappedResponse.ResponseData.Headers[header.Name] = currentHeaderValue + ", " + header.Value; + wrappedResponse.ResponseData.CookieHeaders.Add(header.Value); } else { - wrappedResponse.ResponseData.Headers.Add(header.Name, header.Value); + if (wrappedResponse.ResponseData.Headers.ContainsKey(header.Name)) + { + string currentHeaderValue = wrappedResponse.ResponseData.Headers[header.Name]; + wrappedResponse.ResponseData.Headers[header.Name] = currentHeaderValue + ", " + header.Value; + } + else + { + wrappedResponse.ResponseData.Headers.Add(header.Name, header.Value); + } } } } diff --git a/dotnet/src/webdriver/INetwork.cs b/dotnet/src/webdriver/INetwork.cs index a9ca439fafa7f..d0bf5aa292add 100644 --- a/dotnet/src/webdriver/INetwork.cs +++ b/dotnet/src/webdriver/INetwork.cs @@ -63,6 +63,18 @@ public interface INetwork /// void ClearAuthenticationHandlers(); + /// + /// Adds a to examine received network responses, + /// and optionally modify the response. + /// + /// The to add. + void AddResponseHandler(NetworkResponseHandler handler); + + /// + /// Clears all added instances. + /// + void ClearResponseHandlers(); + /// /// Asynchronously starts monitoring for network traffic. /// diff --git a/dotnet/src/webdriver/NetworkManager.cs b/dotnet/src/webdriver/NetworkManager.cs index d715429dde30a..7dd3df2b898e8 100644 --- a/dotnet/src/webdriver/NetworkManager.cs +++ b/dotnet/src/webdriver/NetworkManager.cs @@ -30,10 +30,11 @@ public class NetworkManager : INetwork { private Lazy session; private List requestHandlers = new List(); + private List responseHandlers = new List(); private List authenticationHandlers = new List(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The instance on which the network should be monitored. public NetworkManager(IWebDriver driver) @@ -161,6 +162,34 @@ public void ClearAuthenticationHandlers() this.authenticationHandlers.Clear(); } + /// + /// Adds a to examine received network responses, + /// and optionally modify the response. + /// + /// The to add. + public void AddResponseHandler(NetworkResponseHandler handler) + { + if (handler == null) + { + throw new ArgumentNullException("handler", "Request handler cannot be null"); + } + + if (handler.ResponseMatcher == null) + { + throw new ArgumentException("Matcher for response cannot be null", "handler"); + } + + this.responseHandlers.Add(handler); + } + + /// + /// Clears all added instances. + /// + public void ClearResponseHandlers() + { + this.responseHandlers.Clear(); + } + private async void OnAuthRequired(object sender, AuthRequiredEventArgs e) { string requestId = e.RequestId; @@ -213,12 +242,32 @@ private async void OnRequestPaused(object sender, RequestPausedEventArgs e) private async void OnResponsePaused(object sender, ResponsePausedEventArgs e) { - await this.session.Value.Domains.Network.AddResponseBody(e.ResponseData); - await this.session.Value.Domains.Network.ContinueResponseWithoutModification(e.ResponseData); + if (e.ResponseData.Headers.Count > 0) + { + // If no headers are present, the body cannot be retrieved. + await this.session.Value.Domains.Network.AddResponseBody(e.ResponseData); + } + if (this.NetworkResponseReceived != null) { this.NetworkResponseReceived(this, new NetworkResponseReceivedEventArgs(e.ResponseData)); } + + foreach (var handler in this.responseHandlers) + { + if (handler.ResponseMatcher.Invoke(e.ResponseData)) + { + // NOTE: We create a dummy HttpRequestData object here, because the ContinueRequestWithResponse + // method demands one; however, the only property used by that method is the RequestId property. + // It might be better to refactor that method signature to simply pass the request ID, or + // alternatively, just pass the response data, which should also contain the request ID anyway. + HttpRequestData requestData = new HttpRequestData() { RequestId = e.ResponseData.RequestId }; + await this.session.Value.Domains.Network.ContinueRequestWithResponse(requestData, handler.ResponseTransformer(e.ResponseData)); + return; + } + } + + await this.session.Value.Domains.Network.ContinueResponseWithoutModification(e.ResponseData); } } } diff --git a/dotnet/src/webdriver/NetworkResponseHandler.cs b/dotnet/src/webdriver/NetworkResponseHandler.cs new file mode 100644 index 0000000000000..bce62c7e0fe4c --- /dev/null +++ b/dotnet/src/webdriver/NetworkResponseHandler.cs @@ -0,0 +1,41 @@ +// +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; + +namespace OpenQA.Selenium +{ + /// + /// Allows a user to handle a returned network, potentially modifying it before processing by the browser. + /// + public class NetworkResponseHandler + { + /// + /// Gets or sets a function that evaluates returned response data in an object, + /// and returns a value indicating whether the data matches the specified criteria. + /// + public Func ResponseMatcher { get; set; } + + /// + /// Gets or sets a function that accepts an object describing a network + /// response received by the browser, and returns a modified object to used + /// as the actual network response. + /// + public Func ResponseTransformer { get; set; } + } +}