okhttp3.internal.http.OkHeaders Maven / Gradle / Ivy
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed 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.
*/
package okhttp3.internal.http;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import okhttp3.Challenge;
import okhttp3.Headers;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.Platform;
import static okhttp3.internal.Util.equal;
/** Headers and utilities for internal use by OkHttp. */
public final class OkHeaders {
static final String PREFIX = Platform.get().getPrefix();
/**
* Synthetic response header: the local time when the request was sent.
*/
public static final String SENT_MILLIS = PREFIX + "-Sent-Millis";
/**
* Synthetic response header: the local time when the response was received.
*/
public static final String RECEIVED_MILLIS = PREFIX + "-Received-Millis";
/**
* Synthetic response header: the selected {@link Protocol protocol} ("spdy/3.1", "http/1.1",
* etc).
*/
public static final String SELECTED_PROTOCOL = PREFIX + "-Selected-Protocol";
/** Synthetic response header: the location from which the response was loaded. */
public static final String RESPONSE_SOURCE = PREFIX + "-Response-Source";
private OkHeaders() {
}
public static long contentLength(Request request) {
return contentLength(request.headers());
}
public static long contentLength(Response response) {
return contentLength(response.headers());
}
public static long contentLength(Headers headers) {
return stringToLong(headers.get("Content-Length"));
}
private static long stringToLong(String s) {
if (s == null) return -1;
try {
return Long.parseLong(s);
} catch (NumberFormatException e) {
return -1;
}
}
/**
* Returns true if none of the Vary headers have changed between {@code cachedRequest} and {@code
* newRequest}.
*/
public static boolean varyMatches(
Response cachedResponse, Headers cachedRequest, Request newRequest) {
for (String field : varyFields(cachedResponse)) {
if (!equal(cachedRequest.values(field), newRequest.headers(field))) return false;
}
return true;
}
/**
* Returns true if a Vary header contains an asterisk. Such responses cannot be cached.
*/
public static boolean hasVaryAll(Response response) {
return hasVaryAll(response.headers());
}
/**
* Returns true if a Vary header contains an asterisk. Such responses cannot be cached.
*/
public static boolean hasVaryAll(Headers responseHeaders) {
return varyFields(responseHeaders).contains("*");
}
private static Set varyFields(Response response) {
return varyFields(response.headers());
}
/**
* Returns the names of the request headers that need to be checked for equality when caching.
*/
public static Set varyFields(Headers responseHeaders) {
Set result = Collections.emptySet();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
if (!"Vary".equalsIgnoreCase(responseHeaders.name(i))) continue;
String value = responseHeaders.value(i);
if (result.isEmpty()) {
result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
}
for (String varyField : value.split(",")) {
result.add(varyField.trim());
}
}
return result;
}
/**
* Returns the subset of the headers in {@code response}'s request that impact the content of
* response's body.
*/
public static Headers varyHeaders(Response response) {
// Use the request headers sent over the network, since that's what the
// response varies on. Otherwise OkHttp-supplied headers like
// "Accept-Encoding: gzip" may be lost.
Headers requestHeaders = response.networkResponse().request().headers();
Headers responseHeaders = response.headers();
return varyHeaders(requestHeaders, responseHeaders);
}
/**
* Returns the subset of the headers in {@code requestHeaders} that impact the content of
* response's body.
*/
public static Headers varyHeaders(Headers requestHeaders, Headers responseHeaders) {
Set varyFields = varyFields(responseHeaders);
if (varyFields.isEmpty()) return new Headers.Builder().build();
Headers.Builder result = new Headers.Builder();
for (int i = 0, size = requestHeaders.size(); i < size; i++) {
String fieldName = requestHeaders.name(i);
if (varyFields.contains(fieldName)) {
result.add(fieldName, requestHeaders.value(i));
}
}
return result.build();
}
/**
* Returns true if {@code fieldName} is an end-to-end HTTP header, as defined by RFC 2616,
* 13.5.1.
*/
static boolean isEndToEnd(String fieldName) {
return !"Connection".equalsIgnoreCase(fieldName)
&& !"Keep-Alive".equalsIgnoreCase(fieldName)
&& !"Proxy-Authenticate".equalsIgnoreCase(fieldName)
&& !"Proxy-Authorization".equalsIgnoreCase(fieldName)
&& !"TE".equalsIgnoreCase(fieldName)
&& !"Trailers".equalsIgnoreCase(fieldName)
&& !"Transfer-Encoding".equalsIgnoreCase(fieldName)
&& !"Upgrade".equalsIgnoreCase(fieldName);
}
/**
* Parse RFC 2617 challenges. This API is only interested in the scheme name and realm.
*/
public static List parseChallenges(Headers responseHeaders, String challengeHeader) {
// auth-scheme = token
// auth-param = token "=" ( token | quoted-string )
// challenge = auth-scheme 1*SP 1#auth-param
// realm = "realm" "=" realm-value
// realm-value = quoted-string
List result = new ArrayList<>();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
if (!challengeHeader.equalsIgnoreCase(responseHeaders.name(i))) {
continue;
}
String value = responseHeaders.value(i);
int pos = 0;
while (pos < value.length()) {
int tokenStart = pos;
pos = HeaderParser.skipUntil(value, pos, " ");
String scheme = value.substring(tokenStart, pos).trim();
pos = HeaderParser.skipWhitespace(value, pos);
// TODO: This currently only handles schemes with a 'realm' parameter;
// It needs to be fixed to handle any scheme and any parameters
// http://code.google.com/p/android/issues/detail?id=11140
if (!value.regionMatches(true, pos, "realm=\"", 0, "realm=\"".length())) {
break; // Unexpected challenge parameter; give up!
}
pos += "realm=\"".length();
int realmStart = pos;
pos = HeaderParser.skipUntil(value, pos, "\"");
String realm = value.substring(realmStart, pos);
pos++; // Consume '"' close quote.
pos = HeaderParser.skipUntil(value, pos, ",");
pos++; // Consume ',' comma.
pos = HeaderParser.skipWhitespace(value, pos);
result.add(new Challenge(scheme, realm));
}
}
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy