Bug 1567549 - Added overloads to GeckoSession.loadUri() that accept additional HTTP request headers r=geckoview-reviewers,snorp,mayhemer

Adds overloads to GeckoSession.loadUri() that accept additional HTTP request headers

Differential Revision: https://phabricator.services.mozilla.com/D40951

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Elliot Stirling 2019-09-09 15:59:13 +00:00
Родитель 6333d847ea
Коммит 73adec6cbf
5 изменённых файлов: 194 добавлений и 7 удалений

Просмотреть файл

@ -479,12 +479,17 @@ package org.mozilla.geckoview {
method @AnyThread public void loadData(@NonNull byte[], @Nullable String);
method @AnyThread public void loadString(@NonNull String, @Nullable String);
method @AnyThread public void loadUri(@NonNull String);
method @AnyThread public void loadUri(@NonNull String, @Nullable Map<String,String>);
method @AnyThread public void loadUri(@NonNull String, int);
method @AnyThread public void loadUri(@NonNull String, @Nullable String, int);
method @AnyThread public void loadUri(@NonNull String, @Nullable String, int, @Nullable Map<String,String>);
method @AnyThread public void loadUri(@NonNull String, @Nullable GeckoSession, int);
method @AnyThread public void loadUri(@NonNull String, @Nullable GeckoSession, int, @Nullable Map<String,String>);
method @AnyThread public void loadUri(@NonNull Uri);
method @AnyThread public void loadUri(@NonNull Uri, @Nullable Map<String,String>);
method @AnyThread public void loadUri(@NonNull Uri, int);
method @AnyThread public void loadUri(@NonNull Uri, @Nullable Uri, int);
method @AnyThread public void loadUri(@NonNull Uri, @Nullable Uri, int, @Nullable Map<String,String>);
method @UiThread public void open(@NonNull GeckoRuntime);
method @AnyThread public void readFromParcel(@NonNull Parcel);
method @UiThread public void releaseDisplay(@NonNull GeckoDisplay);

Просмотреть файл

@ -21,6 +21,7 @@ import org.mozilla.geckoview.test.util.Callbacks
import android.support.test.filters.MediumTest
import android.support.test.runner.AndroidJUnit4
import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers.*
import org.json.JSONObject
import org.junit.After
@ -1207,6 +1208,63 @@ class NavigationDelegateTest : BaseSessionTest() {
})
}
@Test fun loadUriHeader() {
val headers = mapOf<String, String>("Header1" to "Value", "Header2" to "Value1, Value2")
sessionRule.session.loadUri("$TEST_ENDPOINT/anything", headers)
sessionRule.session.waitForPageStop()
val content = sessionRule.session.evaluateJS("document.body.children[0].innerHTML") as String
val body = JSONObject(content)
MatcherAssert.assertThat("Headers should match", body.getJSONObject("headers")
.getString("Header1"), equalTo("Value"))
MatcherAssert.assertThat("Headers should match", body.getJSONObject("headers")
.getString("Header2"), equalTo("Value1, Value2"))
}
@Test fun loadUriHeaderBadOverrides() {
val headers = mapOf<String?, String?>(
null to "BadNull",
"Connection" to "BadConnection",
"Host" to "BadHost",
"ValueLess1" to "",
"ValueLess2" to null,
"ValueLess3" to " ",
"ValueLess4" to "\t")
sessionRule.session.loadUri("$TEST_ENDPOINT/anything", headers)
sessionRule.session.waitForPageStop()
val content = sessionRule.session.evaluateJS("document.body.children[0].innerHTML") as String
val body = JSONObject(content)
val headersJSON = body.getJSONObject("headers")
headersJSON.keys().forEach { key ->
MatcherAssert.assertThat( "No value field should be empty or null",
headersJSON.optString(key), not(isEmptyOrNullString()))
MatcherAssert.assertThat( "No value field should be only whitespace",
headersJSON.getString(key).trim(), not(isEmptyOrNullString()))
MatcherAssert.assertThat( "BadNull should not exist as a header value",
headersJSON.getString(key), not("BadNull"))
}
MatcherAssert.assertThat("Headers should not match", headersJSON
.getString("Connection"), not("BadConnection"))
MatcherAssert.assertThat("Headers should not match", headersJSON
.getString("Host"), not("BadHost"))
// As per RFC7230 all request headers must have a field value (Except Host, which we filter)
// RFC7230 makes RFC2616 obsolete but 2616 allowed empty field values.
MatcherAssert.assertThat("Header with no field value should not be included",
!headersJSON.has("ValueLess1"))
MatcherAssert.assertThat("Header with no field value should not be included",
!headersJSON.has("ValueLess2"))
MatcherAssert.assertThat("Header with no field value should not be included",
!headersJSON.has("ValueLess3"))
MatcherAssert.assertThat("Header with no field value should not be included",
!headersJSON.has("ValueLess4"))
}
@Test(expected = GeckoResult.UncaughtException::class)
fun onNewSession_doesNotAllowOpened() {

Просмотреть файл

@ -10,10 +10,12 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.AbstractSequentialList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
@ -1586,13 +1588,45 @@ public class GeckoSession implements Parcelable {
*/
public static final int LOAD_FLAGS_REPLACE_HISTORY = 1 << 6;
/**
* Formats the map of additional request headers into an ArrayList
* @param additionalHeaders Request headers to be formatted
* @return Correctly formatted request headers as a ArrayList
*/
private ArrayList<String> additionalHeadersToStringArray(final @NonNull Map<String, String> additionalHeaders) {
ArrayList<String> headers = new ArrayList<String>();
for (String key : additionalHeaders.keySet()) {
// skip null key if one exists
if (key == null)
continue;
String value = additionalHeaders.get(key);
// As per RFC7230 headers must contain a field value
if (value != null && !value.equals("")) {
headers.add( String.format("%s:%s", key, additionalHeaders.get(key)) );
}
}
return headers;
}
/**
* Load the given URI.
* @param uri The URI of the resource to load.
*/
@AnyThread
public void loadUri(final @NonNull String uri) {
loadUri(uri, (GeckoSession)null, LOAD_FLAGS_NONE);
loadUri(uri, (GeckoSession)null, LOAD_FLAGS_NONE, (Map<String, String>) null);
}
/**
* Load the given URI with specified HTTP request headers.
* @param uri The URI of the resource to load.
* @param additionalHeaders any additional request headers used with the load
*/
@AnyThread
public void loadUri(final @NonNull String uri, final @Nullable Map<String, String> additionalHeaders) {
loadUri(uri, (GeckoSession)null, LOAD_FLAGS_NONE, additionalHeaders);
}
/**
@ -1603,7 +1637,7 @@ public class GeckoSession implements Parcelable {
*/
@AnyThread
public void loadUri(final @NonNull String uri, final @LoadFlags int flags) {
loadUri(uri, (GeckoSession)null, flags);
loadUri(uri, (GeckoSession)null, flags, (Map<String, String>) null);
}
/**
@ -1616,6 +1650,20 @@ public class GeckoSession implements Parcelable {
@AnyThread
public void loadUri(final @NonNull String uri, final @Nullable String referrer,
final @LoadFlags int flags) {
loadUri(uri, referrer, flags, (Map<String, String>) null);
}
/**
* Load the given URI with the specified referrer, load type and HTTP request headers.
*
* @param uri the URI to load
* @param referrer the referrer, may be null
* @param flags the load flags to use, an OR-ed value of {@link #LOAD_FLAGS_NONE LOAD_FLAGS_*}
* @param additionalHeaders any additional request headers used with the load
*/
@AnyThread
public void loadUri(final @NonNull String uri, final @Nullable String referrer,
final @LoadFlags int flags, final @Nullable Map<String, String> additionalHeaders) {
final GeckoBundle msg = new GeckoBundle();
msg.putString("uri", uri);
msg.putInt("flags", flags);
@ -1623,6 +1671,10 @@ public class GeckoSession implements Parcelable {
if (referrer != null) {
msg.putString("referrerUri", referrer);
}
if (additionalHeaders != null) {
msg.putStringArray("headers", additionalHeadersToStringArray(additionalHeaders));
}
mEventDispatcher.dispatch("GeckoView:LoadUri", msg);
}
@ -1638,6 +1690,22 @@ public class GeckoSession implements Parcelable {
@AnyThread
public void loadUri(final @NonNull String uri, final @Nullable GeckoSession referrer,
final @LoadFlags int flags) {
loadUri(uri, referrer, flags, (Map<String, String>) null);
}
/**
* Load the given URI with the specified referrer, load type and HTTP request headers. This
* method will also do any applicable checks to ensure that the specified URI is both safe and
* allowable according to the referring GeckoSession.
*
* @param uri the URI to load
* @param referrer the referring GeckoSession, may be null
* @param flags the load flags to use, an OR-ed value of {@link #LOAD_FLAGS_NONE LOAD_FLAGS_*}
* @param additionalHeaders any additional request headers used with the load
*/
@AnyThread
public void loadUri(final @NonNull String uri, final @Nullable GeckoSession referrer,
final @LoadFlags int flags, final @Nullable Map<String, String> additionalHeaders) {
final GeckoBundle msg = new GeckoBundle();
msg.putString("uri", uri);
msg.putInt("flags", flags);
@ -1645,6 +1713,10 @@ public class GeckoSession implements Parcelable {
if (referrer != null) {
msg.putString("referrerSessionId", referrer.mId);
}
if (additionalHeaders != null) {
msg.putStringArray("headers", additionalHeadersToStringArray(additionalHeaders));
}
mEventDispatcher.dispatch("GeckoView:LoadUri", msg);
}
@ -1654,7 +1726,17 @@ public class GeckoSession implements Parcelable {
*/
@AnyThread
public void loadUri(final @NonNull Uri uri) {
loadUri(uri, null, LOAD_FLAGS_NONE);
loadUri(uri.toString(), (GeckoSession)null, LOAD_FLAGS_NONE, (Map<String, String>) null);
}
/**
* Load the given URI with specified HTTP request headers.
* @param uri The URI of the resource to load.
* @param additionalHeaders any additional request headers used with the load
*/
@AnyThread
public void loadUri(final @NonNull Uri uri, final @Nullable Map<String, String> additionalHeaders) {
loadUri(uri.toString(), (GeckoSession)null, LOAD_FLAGS_NONE, additionalHeaders);
}
/**
@ -1664,7 +1746,7 @@ public class GeckoSession implements Parcelable {
*/
@AnyThread
public void loadUri(final @NonNull Uri uri, final @LoadFlags int flags) {
loadUri(uri.toString(), (GeckoSession)null, flags);
loadUri(uri.toString(), (GeckoSession)null, flags, (Map<String, String>) null);
}
/**
@ -1676,7 +1758,20 @@ public class GeckoSession implements Parcelable {
@AnyThread
public void loadUri(final @NonNull Uri uri, final @Nullable Uri referrer,
final @LoadFlags int flags) {
loadUri(uri.toString(), referrer != null ? referrer.toString() : null, flags);
loadUri(uri.toString(), referrer != null ? referrer.toString() : null, flags, (Map<String, String>) null);
}
/**
* Load the given URI with the specified referrer, load type and HTTP request headers.
* @param uri the URI to load
* @param referrer the Uri to use as the referrer
* @param flags the load flags to use, an OR-ed value of {@link #LOAD_FLAGS_NONE LOAD_FLAGS_*}
* @param additionalHeaders any additional request headers used with the load
*/
@AnyThread
public void loadUri(final @NonNull Uri uri, final @Nullable Uri referrer,
final @LoadFlags int flags, final @Nullable Map<String, String> additionalHeaders) {
loadUri(uri.toString(), referrer != null ? referrer.toString() : null, flags, additionalHeaders);
}
/**

Просмотреть файл

@ -20,12 +20,15 @@ exclude: true
`onTelemetryReceived` has been renamed to [`onHistogram`][71.4], and
[`Metric`][71.5] now takes a type parameter.
([bug 1576730]({{bugzilla}}1576730))
- Added overloads of [`GeckoSession.loadUri()`][71.6] that accept a map of
additional HTTP request headers.
[71.1]: {{javadoc_uri}}/RuntimeTelemetry.Delegate.html#onBooleanScalar-org.mozilla.geckoview.RuntimeTelemetry.Metric-
[71.2]: {{javadoc_uri}}/RuntimeTelemetry.Delegate.html#onLongScalar-org.mozilla.geckoview.RuntimeTelemetry.Metric-
[71.3]: {{javadoc_uri}}/RuntimeTelemetry.Delegate.html#onStringScalar-org.mozilla.geckoview.RuntimeTelemetry.Metric-
[71.4]: {{javadoc_uri}}/RuntimeTelemetry.Delegate.html#onHistogram-org.mozilla.geckoview.RuntimeTelemetry.Metric-
[71.5]: {{javadoc_uri}}/RuntimeTelemetry.Metric.html
[71.6]: {{javadoc_uri}}/GeckoSession.html#loadUri-java.lang.String-java.io.File-java.util.Map-
## v70
- Added API for session context assignment
@ -348,4 +351,4 @@ exclude: true
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: c35dda98b313f389d0bb297a1226c801035f7c77
[api-version]: ccc454a846e5d3c22667842aecbbdef182524f94

Просмотреть файл

@ -101,7 +101,7 @@ class GeckoViewNavigation extends GeckoViewModule {
this.browser.gotoIndex(aData.index);
break;
case "GeckoView:LoadUri":
const { uri, referrerUri, referrerSessionId, flags } = aData;
const { uri, referrerUri, referrerSessionId, flags, headers } = aData;
let navFlags = 0;
@ -183,6 +183,31 @@ class GeckoViewNavigation extends GeckoViewModule {
);
}
let additionalHeaders = null;
if (headers) {
// Filter out request headers as per discussion in Bug #1567549
// CONNECTION: Used by Gecko to manage connections
// HOST: Relates to how gecko will ultimately interpret the resulting resource as that
// determines the effective request URI
const badHeaders = ["connection", "host"];
additionalHeaders = "";
headers.forEach(entry => {
const key = entry
.split(/:/)[0]
.toLowerCase()
.trim();
if (!badHeaders.includes(key)) {
additionalHeaders += entry + "\r\n";
}
});
if (additionalHeaders != "") {
additionalHeaders = E10SUtils.makeInputStream(additionalHeaders);
} else {
additionalHeaders = null;
}
}
// For any navigation here, we should have an appropriate triggeringPrincipal:
//
// 1) If we have a referring session, triggeringPrincipal is the contentPrincipal from the
@ -203,6 +228,7 @@ class GeckoViewNavigation extends GeckoViewModule {
flags: navFlags,
referrerInfo,
triggeringPrincipal,
headers: additionalHeaders,
csp,
});
break;