Commit aa4fbfe7 authored by Scott Violet's avatar Scott Violet Committed by Commit Bot

weblayer: adds API for setting network request headers

Here's the doc for the new function, which is added to Navigation:

Sets a header for a network request. If a header with the specified name exists it is
overwritten. This method can only be called at two times, from
{@link NavigationCallback.onNavigationStarted} and {@link
NavigationCallback.onNavigationStarted}. When called during start, the header applies to both
the initial network request as well as redirects.

@param name The name of the header. The name must be rfc 2616 compliant.
@param value The value of the header. The value must not contain '\0', '\n' or '\r'.

@throws IllegalArgumentException If supplied invalid values.
@throws IllegalStateException If not called during start or a redirect.

@since 83
public void setRequestHeader(@NonNull String name, @NonNull String value) {

BUG=1065536
TEST=covered by tests

Change-Id: Ibe6487bcc7167fd2e68fcaee46915a877f4619b6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2133171Reviewed-by: default avatarJohn Abd-El-Malek <jam@chromium.org>
Commit-Queue: Scott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#758190}
parent 3e80a7b5
...@@ -22,6 +22,8 @@ import org.junit.runner.RunWith; ...@@ -22,6 +22,8 @@ import org.junit.runner.RunWith;
import org.chromium.base.test.util.CallbackHelper; import org.chromium.base.test.util.CallbackHelper;
import org.chromium.content_public.browser.test.util.Criteria; import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper; import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.net.test.util.TestWebServer;
import org.chromium.weblayer.LoadError; import org.chromium.weblayer.LoadError;
import org.chromium.weblayer.Navigation; import org.chromium.weblayer.Navigation;
import org.chromium.weblayer.NavigationCallback; import org.chromium.weblayer.NavigationCallback;
...@@ -483,6 +485,14 @@ public class NavigationTest { ...@@ -483,6 +485,14 @@ public class NavigationTest {
mCallback)); mCallback));
} }
private void registerNavigationCallback(NavigationCallback callback) {
runOnUiThreadBlocking(()
-> mActivityTestRule.getActivity()
.getTab()
.getNavigationController()
.registerNavigationCallback(callback));
}
private void navigateAndWaitForCompletion(String expectedUrl, Runnable navigateRunnable) private void navigateAndWaitForCompletion(String expectedUrl, Runnable navigateRunnable)
throws Exception { throws Exception {
int currentCallCount = mCallback.onCompletedCallback.getCallCount(); int currentCallCount = mCallback.onCompletedCallback.getCallCount();
...@@ -516,4 +526,106 @@ public class NavigationTest { ...@@ -516,4 +526,106 @@ public class NavigationTest {
return navigationUrl.get(); return navigationUrl.get();
} }
// NavigationCallback implementation that sets a header in either start or redirect.
private static final class HeaderSetter extends NavigationCallback {
private final String mName;
private final String mValue;
private final boolean mInStart;
public boolean mGotIllegalArgumentException;
HeaderSetter(String name, String value, boolean inStart) {
mName = name;
mValue = value;
mInStart = inStart;
}
@Override
public void onNavigationStarted(Navigation navigation) {
if (mInStart) applyHeader(navigation);
}
@Override
public void onNavigationRedirected(Navigation navigation) {
if (!mInStart) applyHeader(navigation);
}
private void applyHeader(Navigation navigation) {
try {
navigation.setRequestHeader(mName, mValue);
} catch (IllegalArgumentException e) {
mGotIllegalArgumentException = true;
}
}
}
@Test
@SmallTest
public void testSetRequestHeaderInStart() throws Exception {
TestWebServer testServer = TestWebServer.start();
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
String headerName = "header";
String headerValue = "value";
HeaderSetter setter = new HeaderSetter(headerName, headerValue, true);
registerNavigationCallback(setter);
String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
mActivityTestRule.navigateAndWait(url);
assertFalse(setter.mGotIllegalArgumentException);
assertEquals(headerValue, testServer.getLastRequest("/ok.html").headerValue(headerName));
}
@Test
@SmallTest
public void testSetRequestHeaderInRedirect() throws Exception {
TestWebServer testServer = TestWebServer.start();
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
String headerName = "header";
String headerValue = "value";
HeaderSetter setter = new HeaderSetter(headerName, headerValue, false);
registerNavigationCallback(setter);
// The destination of the redirect.
String finalUrl = testServer.setResponse("/ok.html", "<html>ok</html>", null);
// The url that redirects to |finalUrl|.
String redirectingUrl = testServer.setRedirect("/redirect.html", finalUrl);
Tab tab = mActivityTestRule.getActivity().getTab();
NavigationWaiter waiter = new NavigationWaiter(finalUrl, tab, false, false);
TestThreadUtils.runOnUiThreadBlocking(
() -> { tab.getNavigationController().navigate(Uri.parse(redirectingUrl)); });
waiter.waitForNavigation();
assertFalse(setter.mGotIllegalArgumentException);
assertEquals(headerValue, testServer.getLastRequest("/ok.html").headerValue(headerName));
}
@Test
@SmallTest
public void testSetRequestHeaderThrowsExceptionInCompleted() throws Exception {
mActivityTestRule.launchShellWithUrl(null);
boolean gotCompleted[] = new boolean[1];
NavigationCallback navigationCallback = new NavigationCallback() {
@Override
public void onNavigationCompleted(Navigation navigation) {
gotCompleted[0] = true;
boolean gotException = false;
try {
navigation.setRequestHeader("name", "value");
} catch (IllegalStateException e) {
gotException = true;
}
assertTrue(gotException);
}
};
registerNavigationCallback(navigationCallback);
mActivityTestRule.navigateAndWait(URL1);
assertTrue(gotCompleted[0]);
}
@Test
@SmallTest
public void testSetRequestHeaderThrowsExceptionWithInvalidValue() throws Exception {
mActivityTestRule.launchShellWithUrl(null);
HeaderSetter setter = new HeaderSetter("name", "\0", true);
registerNavigationCallback(setter);
mActivityTestRule.navigateAndWait(URL1);
assertTrue(setter.mGotIllegalArgumentException);
}
} }
...@@ -113,6 +113,19 @@ public final class NavigationImpl extends INavigation.Stub { ...@@ -113,6 +113,19 @@ public final class NavigationImpl extends INavigation.Stub {
NavigationImplJni.get().getLoadError(mNativeNavigationImpl, NavigationImpl.this)); NavigationImplJni.get().getLoadError(mNativeNavigationImpl, NavigationImpl.this));
} }
@Override
public void setRequestHeader(String name, String value) {
if (!NavigationImplJni.get().isValidRequestHeaderName(name)) {
throw new IllegalArgumentException("Invalid header");
}
if (!NavigationImplJni.get().isValidRequestHeaderValue(value)) {
throw new IllegalArgumentException("Invalid value");
}
if (!NavigationImplJni.get().setRequestHeader(mNativeNavigationImpl, this, name, value)) {
throw new IllegalStateException();
}
}
private void throwIfNativeDestroyed() { private void throwIfNativeDestroyed() {
if (mNativeNavigationImpl == 0) { if (mNativeNavigationImpl == 0) {
throw new IllegalStateException("Using Navigation after native destroyed"); throw new IllegalStateException("Using Navigation after native destroyed");
...@@ -155,5 +168,9 @@ public final class NavigationImpl extends INavigation.Stub { ...@@ -155,5 +168,9 @@ public final class NavigationImpl extends INavigation.Stub {
boolean isSameDocument(long nativeNavigationImpl, NavigationImpl caller); boolean isSameDocument(long nativeNavigationImpl, NavigationImpl caller);
boolean isErrorPage(long nativeNavigationImpl, NavigationImpl caller); boolean isErrorPage(long nativeNavigationImpl, NavigationImpl caller);
int getLoadError(long nativeNavigationImpl, NavigationImpl caller); int getLoadError(long nativeNavigationImpl, NavigationImpl caller);
boolean setRequestHeader(
long nativeNavigationImpl, NavigationImpl caller, String name, String value);
boolean isValidRequestHeaderName(String name);
boolean isValidRequestHeaderValue(String value);
} }
} }
...@@ -21,4 +21,6 @@ interface INavigation { ...@@ -21,4 +21,6 @@ interface INavigation {
boolean isErrorPage() = 5; boolean isErrorPage() = 5;
int getLoadError() = 6; int getLoadError() = 6;
void setRequestHeader(in String name, in String value) = 7;
} }
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "content/public/test/url_loader_interceptor.h" #include "content/public/test/url_loader_interceptor.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/embedded_test_server.h"
#include "weblayer/public/navigation.h" #include "weblayer/public/navigation.h"
#include "weblayer/public/navigation_controller.h" #include "weblayer/public/navigation_controller.h"
...@@ -148,4 +149,101 @@ IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpConnectivityError) { ...@@ -148,4 +149,101 @@ IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpConnectivityError) {
EXPECT_EQ(observer.navigation_state(), NavigationState::kFailed); EXPECT_EQ(observer.navigation_state(), NavigationState::kFailed);
} }
namespace {
class HeaderInjectorNavigationObserver : public NavigationObserver {
public:
HeaderInjectorNavigationObserver(Shell* shell,
const std::string& header_name,
const std::string& header_value,
bool inject_in_start)
: tab_(shell->tab()),
header_name_(header_name),
header_value_(header_value),
inject_in_start_(inject_in_start) {
tab_->GetNavigationController()->AddObserver(this);
}
~HeaderInjectorNavigationObserver() override {
tab_->GetNavigationController()->RemoveObserver(this);
}
private:
// NavigationObserver implementation:
void NavigationStarted(Navigation* navigation) override {
if (inject_in_start_)
InjectHeaders(navigation);
}
void NavigationRedirected(Navigation* navigation) override {
if (!inject_in_start_)
InjectHeaders(navigation);
}
void InjectHeaders(Navigation* navigation) {
navigation->SetRequestHeader(header_name_, header_value_);
}
Tab* tab_;
const std::string header_name_;
const std::string header_value_;
// If true, header is set in start, otherwise header is set in redirect.
const bool inject_in_start_;
};
} // namespace
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetRequestHeader) {
net::test_server::ControllableHttpResponse response_1(embedded_test_server(),
"", true);
net::test_server::ControllableHttpResponse response_2(embedded_test_server(),
"", true);
ASSERT_TRUE(embedded_test_server()->Start());
const std::string header_name = "header";
const std::string header_value = "value";
HeaderInjectorNavigationObserver observer(shell(), header_name, header_value,
true);
shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html"));
response_1.WaitForRequest();
// Header should be present in initial request.
EXPECT_EQ(header_value, response_1.http_request()->headers.at(header_name));
response_1.Send(
"HTTP/1.1 302 Moved Temporarily\r\nLocation: /new_doc\r\n\r\n");
response_1.Done();
// Header should carry through to redirect.
response_2.WaitForRequest();
EXPECT_EQ(header_value, response_2.http_request()->headers.at(header_name));
}
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SetRequestHeaderInRedirect) {
net::test_server::ControllableHttpResponse response_1(embedded_test_server(),
"", true);
net::test_server::ControllableHttpResponse response_2(embedded_test_server(),
"", true);
ASSERT_TRUE(embedded_test_server()->Start());
const std::string header_name = "header";
const std::string header_value = "value";
HeaderInjectorNavigationObserver observer(shell(), header_name, header_value,
false);
shell()->LoadURL(embedded_test_server()->GetURL("/simple_page.html"));
response_1.WaitForRequest();
// Header should not be present in initial request.
EXPECT_FALSE(base::Contains(response_1.http_request()->headers, header_name));
response_1.Send(
"HTTP/1.1 302 Moved Temporarily\r\nLocation: /new_doc\r\n\r\n");
response_1.Done();
response_2.WaitForRequest();
// Header should be in redirect.
EXPECT_EQ(header_value, response_2.http_request()->headers.at(header_name));
}
} // namespace weblayer } // namespace weblayer
...@@ -150,6 +150,7 @@ void NavigationControllerImpl::DidStartNavigation( ...@@ -150,6 +150,7 @@ void NavigationControllerImpl::DidStartNavigation(
navigation_map_[navigation_handle] = navigation_map_[navigation_handle] =
std::make_unique<NavigationImpl>(navigation_handle); std::make_unique<NavigationImpl>(navigation_handle);
auto* navigation = navigation_map_[navigation_handle].get(); auto* navigation = navigation_map_[navigation_handle].get();
navigation->set_safe_to_set_request_headers(true);
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
if (java_controller_) { if (java_controller_) {
JNIEnv* env = AttachCurrentThread(); JNIEnv* env = AttachCurrentThread();
...@@ -166,6 +167,7 @@ void NavigationControllerImpl::DidStartNavigation( ...@@ -166,6 +167,7 @@ void NavigationControllerImpl::DidStartNavigation(
#endif #endif
for (auto& observer : observers_) for (auto& observer : observers_)
observer.NavigationStarted(navigation); observer.NavigationStarted(navigation);
navigation->set_safe_to_set_request_headers(false);
} }
void NavigationControllerImpl::DidRedirectNavigation( void NavigationControllerImpl::DidRedirectNavigation(
...@@ -175,6 +177,7 @@ void NavigationControllerImpl::DidRedirectNavigation( ...@@ -175,6 +177,7 @@ void NavigationControllerImpl::DidRedirectNavigation(
DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end()); DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end());
auto* navigation = navigation_map_[navigation_handle].get(); auto* navigation = navigation_map_[navigation_handle].get();
navigation->set_safe_to_set_request_headers(true);
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
if (java_controller_) { if (java_controller_) {
TRACE_EVENT0("weblayer", TRACE_EVENT0("weblayer",
...@@ -185,6 +188,7 @@ void NavigationControllerImpl::DidRedirectNavigation( ...@@ -185,6 +188,7 @@ void NavigationControllerImpl::DidRedirectNavigation(
#endif #endif
for (auto& observer : observers_) for (auto& observer : observers_)
observer.NavigationRedirected(navigation); observer.NavigationRedirected(navigation);
navigation->set_safe_to_set_request_headers(false);
} }
void NavigationControllerImpl::ReadyToCommitNavigation( void NavigationControllerImpl::ReadyToCommitNavigation(
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "net/base/net_errors.h" #include "net/base/net_errors.h"
#include "net/http/http_util.h"
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
#include "base/android/jni_array.h" #include "base/android/jni_array.h"
...@@ -55,6 +56,19 @@ ScopedJavaLocalRef<jobjectArray> NavigationImpl::GetRedirectChain( ...@@ -55,6 +56,19 @@ ScopedJavaLocalRef<jobjectArray> NavigationImpl::GetRedirectChain(
return base::android::ToJavaArrayOfStrings(env, jni_redirects); return base::android::ToJavaArrayOfStrings(env, jni_redirects);
} }
jboolean NavigationImpl::SetRequestHeader(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jstring>& name,
const base::android::JavaParamRef<jstring>& value) {
if (!safe_to_set_request_headers_)
return false;
SetRequestHeader(ConvertJavaStringToUTF8(name),
ConvertJavaStringToUTF8(value));
return true;
}
#endif #endif
GURL NavigationImpl::GetURL() { GURL NavigationImpl::GetURL() {
...@@ -110,4 +124,23 @@ Navigation::LoadError NavigationImpl::GetLoadError() { ...@@ -110,4 +124,23 @@ Navigation::LoadError NavigationImpl::GetLoadError() {
return kOtherError; return kOtherError;
} }
void NavigationImpl::SetRequestHeader(const std::string& name,
const std::string& value) {
navigation_handle_->SetRequestHeader(name, value);
}
#if defined(OS_ANDROID)
static jboolean JNI_NavigationImpl_IsValidRequestHeaderName(
JNIEnv* env,
const base::android::JavaParamRef<jstring>& name) {
return net::HttpUtil::IsValidHeaderName(ConvertJavaStringToUTF8(name));
}
static jboolean JNI_NavigationImpl_IsValidRequestHeaderValue(
JNIEnv* env,
const base::android::JavaParamRef<jstring>& value) {
return net::HttpUtil::IsValidHeaderValue(ConvertJavaStringToUTF8(value));
}
#endif
} // namespace weblayer } // namespace weblayer
...@@ -24,6 +24,10 @@ class NavigationImpl : public Navigation { ...@@ -24,6 +24,10 @@ class NavigationImpl : public Navigation {
explicit NavigationImpl(content::NavigationHandle* navigation_handle); explicit NavigationImpl(content::NavigationHandle* navigation_handle);
~NavigationImpl() override; ~NavigationImpl() override;
void set_safe_to_set_request_headers(bool value) {
safe_to_set_request_headers_ = value;
}
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
void SetJavaNavigation( void SetJavaNavigation(
JNIEnv* env, JNIEnv* env,
...@@ -53,6 +57,10 @@ class NavigationImpl : public Navigation { ...@@ -53,6 +57,10 @@ class NavigationImpl : public Navigation {
const base::android::JavaParamRef<jobject>& obj) { const base::android::JavaParamRef<jobject>& obj) {
return static_cast<int>(GetLoadError()); return static_cast<int>(GetLoadError());
} }
jboolean SetRequestHeader(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jstring>& name,
const base::android::JavaParamRef<jstring>& value);
base::android::ScopedJavaGlobalRef<jobject> java_navigation() { base::android::ScopedJavaGlobalRef<jobject> java_navigation() {
return java_navigation_; return java_navigation_;
...@@ -68,9 +76,14 @@ class NavigationImpl : public Navigation { ...@@ -68,9 +76,14 @@ class NavigationImpl : public Navigation {
bool IsSameDocument() override; bool IsSameDocument() override;
bool IsErrorPage() override; bool IsErrorPage() override;
LoadError GetLoadError() override; LoadError GetLoadError() override;
void SetRequestHeader(const std::string& name,
const std::string& value) override;
content::NavigationHandle* navigation_handle_; content::NavigationHandle* navigation_handle_;
// Whether SetRequestHeader() is allowed at this time.
bool safe_to_set_request_headers_ = false;
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
base::android::ScopedJavaGlobalRef<jobject> java_navigation_; base::android::ScopedJavaGlobalRef<jobject> java_navigation_;
#endif #endif
......
...@@ -125,4 +125,31 @@ public class Navigation extends IClientNavigation.Stub { ...@@ -125,4 +125,31 @@ public class Navigation extends IClientNavigation.Stub {
throw new APICallException(e); throw new APICallException(e);
} }
} }
/**
* Sets a header for a network request. If a header with the specified name exists it is
* overwritten. This method can only be called at two times, from
* {@link NavigationCallback.onNavigationStarted} and {@link
* NavigationCallback.onNavigationStarted}. When called during start, the header applies to both
* the initial network request as well as redirects.
*
* @param name The name of the header. The name must be rfc 2616 compliant.
* @param value The value of the header. The value must not contain '\0', '\n' or '\r'.
*
* @throws IllegalArgumentException If supplied invalid values.
* @throws IllegalStateException If not called during start or a redirect.
*
* @since 83
*/
public void setRequestHeader(@NonNull String name, @NonNull String value) {
ThreadCheck.ensureOnUiThread();
if (WebLayer.getSupportedMajorVersionInternal() < 83) {
throw new UnsupportedOperationException();
}
try {
mNavigationImpl.setRequestHeader(name, value);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef WEBLAYER_PUBLIC_NAVIGATION_H_ #ifndef WEBLAYER_PUBLIC_NAVIGATION_H_
#define WEBLAYER_PUBLIC_NAVIGATION_H_ #define WEBLAYER_PUBLIC_NAVIGATION_H_
#include <string>
#include <vector> #include <vector>
class GURL; class GURL;
...@@ -73,6 +74,14 @@ class Navigation { ...@@ -73,6 +74,14 @@ class Navigation {
// Return information about the error, if any, that was encountered while // Return information about the error, if any, that was encountered while
// loading the page. // loading the page.
virtual LoadError GetLoadError() = 0; virtual LoadError GetLoadError() = 0;
// Set a request's header. If the header is already present, its value is
// overwritten. This function can only be called at two times, during start
// and redirect. When called during start, the header applies to both the
// start and redirect. |name| must be rfc 2616 compliant and |value| must
// not contain '\0', '\n' or '\r'.
virtual void SetRequestHeader(const std::string& name,
const std::string& value) = 0;
}; };
} // namespace weblayer } // namespace weblayer
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment