1 // Copyright 2019 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net.test; 6 7 import org.chromium.net.UrlResponseInfo; 8 9 import java.io.UnsupportedEncodingException; 10 import java.util.AbstractMap; 11 import java.util.ArrayList; 12 import java.util.Arrays; 13 import java.util.Collections; 14 import java.util.List; 15 import java.util.Map; 16 import java.util.Objects; 17 18 // TODO(kirchman): Update this to explain inter-class usage once other classes land. 19 /** 20 * 21 * Fake response model for UrlRequest used by Fake Cronet. 22 */ 23 public class FakeUrlResponse { 24 private final int mHttpStatusCode; 25 // Entries to mAllHeadersList should never be mutated. 26 private final List<Map.Entry<String, String>> mAllHeadersList; 27 private final boolean mWasCached; 28 private final String mNegotiatedProtocol; 29 private final String mProxyServer; 30 private final byte[] mResponseBody; 31 getNullableOrDefault(T nullableObject, T defaultObject)32 private static <T extends Object> T getNullableOrDefault(T nullableObject, T defaultObject) { 33 if (nullableObject != null) { 34 return nullableObject; 35 } 36 return defaultObject; 37 } 38 39 /** 40 * Constructs a {@link FakeUrlResponse} from a {@link FakeUrlResponse.Builder}. 41 * @param builder the {@link FakeUrlResponse.Builder} to create the response from 42 */ FakeUrlResponse(Builder builder)43 private FakeUrlResponse(Builder builder) { 44 mHttpStatusCode = builder.mHttpStatusCode; 45 mAllHeadersList = Collections.unmodifiableList(new ArrayList<>(builder.mAllHeadersList)); 46 mWasCached = builder.mWasCached; 47 mNegotiatedProtocol = builder.mNegotiatedProtocol; 48 mProxyServer = builder.mProxyServer; 49 mResponseBody = builder.mResponseBody; 50 } 51 52 /** 53 * Constructs a {@link FakeUrlResponse} from a {@link UrlResponseInfo}. All nullable fields in 54 * the {@link UrlResponseInfo} are initialized to the default value if the provided value is 55 * null. 56 * 57 * @param info the {@link UrlResponseInfo} used to initialize this object's fields 58 */ FakeUrlResponse(UrlResponseInfo info)59 public FakeUrlResponse(UrlResponseInfo info) { 60 mHttpStatusCode = info.getHttpStatusCode(); 61 mAllHeadersList = Collections.unmodifiableList(new ArrayList<>(info.getAllHeadersAsList())); 62 mWasCached = info.wasCached(); 63 mNegotiatedProtocol = 64 getNullableOrDefault( 65 info.getNegotiatedProtocol(), Builder.DEFAULT_NEGOTIATED_PROTOCOL); 66 mProxyServer = getNullableOrDefault(info.getProxyServer(), Builder.DEFAULT_PROXY_SERVER); 67 mResponseBody = Builder.DEFAULT_RESPONSE_BODY; 68 } 69 70 /** Builds a {@link FakeUrlResponse}. */ 71 public static class Builder { 72 private static final int DEFAULT_HTTP_STATUS_CODE = 200; 73 private static final List<Map.Entry<String, String>> INTERNAL_INITIAL_HEADERS_LIST = 74 new ArrayList<>(); 75 private static final boolean DEFAULT_WAS_CACHED = false; 76 private static final String DEFAULT_NEGOTIATED_PROTOCOL = ""; 77 private static final String DEFAULT_PROXY_SERVER = ""; 78 private static final byte[] DEFAULT_RESPONSE_BODY = new byte[0]; 79 80 private int mHttpStatusCode = DEFAULT_HTTP_STATUS_CODE; 81 // Entries to mAllHeadersList should never be mutated. 82 private List<Map.Entry<String, String>> mAllHeadersList = 83 new ArrayList<>(INTERNAL_INITIAL_HEADERS_LIST); 84 private boolean mWasCached = DEFAULT_WAS_CACHED; 85 private String mNegotiatedProtocol = DEFAULT_NEGOTIATED_PROTOCOL; 86 private String mProxyServer = DEFAULT_PROXY_SERVER; 87 private byte[] mResponseBody = DEFAULT_RESPONSE_BODY; 88 89 /** Constructs a {@link FakeUrlResponse.Builder} with the default parameters. */ Builder()90 public Builder() {} 91 92 /** 93 * Constructs a {@link FakeUrlResponse.Builder} from a source {@link FakeUrlResponse}. 94 * 95 * @param source a {@link FakeUrlResponse} to copy into this {@link FakeUrlResponse.Builder} 96 */ Builder(FakeUrlResponse source)97 private Builder(FakeUrlResponse source) { 98 mHttpStatusCode = source.getHttpStatusCode(); 99 mAllHeadersList = new ArrayList<>(source.getAllHeadersList()); 100 mWasCached = source.getWasCached(); 101 mNegotiatedProtocol = source.getNegotiatedProtocol(); 102 mProxyServer = source.getProxyServer(); 103 mResponseBody = source.getResponseBody(); 104 } 105 106 /** 107 * Sets the HTTP status code. The default value is 200. 108 * 109 * @param httpStatusCode for {@link UrlResponseInfo.getHttpStatusCode()} 110 * @return the builder with the corresponding HTTP status code set 111 */ setHttpStatusCode(int httpStatusCode)112 public Builder setHttpStatusCode(int httpStatusCode) { 113 mHttpStatusCode = httpStatusCode; 114 return this; 115 } 116 117 /** 118 * Adds a response header to built {@link FakeUrlResponse}s. 119 * 120 * @param name the name of the header key, for example, "location" for a redirect header 121 * @param value the header value 122 * @return the builder with the corresponding header set 123 */ addHeader(String name, String value)124 public Builder addHeader(String name, String value) { 125 mAllHeadersList.add(new AbstractMap.SimpleEntry<>(name, value)); 126 return this; 127 } 128 129 /** 130 * Sets result of {@link UrlResponseInfo.wasCached()}. The default wasCached value is false. 131 * 132 * @param wasCached for {@link UrlResponseInfo.wasCached()} 133 * @return the builder with the corresponding wasCached field set 134 */ setWasCached(boolean wasCached)135 public Builder setWasCached(boolean wasCached) { 136 mWasCached = wasCached; 137 return this; 138 } 139 140 /** 141 * Sets result of {@link UrlResponseInfo.getNegotiatedProtocol()}. The default negotiated 142 * protocol is an empty string. 143 * 144 * @param negotiatedProtocol for {@link UrlResponseInfo.getNegotiatedProtocol()} 145 * @return the builder with the corresponding negotiatedProtocol field set 146 */ setNegotiatedProtocol(String negotiatedProtocol)147 public Builder setNegotiatedProtocol(String negotiatedProtocol) { 148 mNegotiatedProtocol = negotiatedProtocol; 149 return this; 150 } 151 152 /** 153 * Sets result of {@link UrlResponseInfo.getProxyServer()}. The default proxy server is an 154 * empty string. 155 * 156 * @param proxyServer for {@link UrlResponseInfo.getProxyServer()} 157 * @return the builder with the corresponding proxyServer field set 158 */ setProxyServer(String proxyServer)159 public Builder setProxyServer(String proxyServer) { 160 mProxyServer = proxyServer; 161 return this; 162 } 163 164 /** 165 * Sets the response body for a response. The default response body is an empty byte array. 166 * 167 * @param body all the information the server returns 168 * @return the builder with the corresponding responseBody field set 169 */ setResponseBody(byte[] body)170 public Builder setResponseBody(byte[] body) { 171 mResponseBody = body; 172 return this; 173 } 174 175 /** 176 * Constructs a {@link FakeUrlResponse} from this {@link FakeUrlResponse.Builder}. 177 * 178 * @return a FakeUrlResponse with all fields set according to this builder 179 */ build()180 public FakeUrlResponse build() { 181 return new FakeUrlResponse(this); 182 } 183 } 184 185 /** 186 * Returns the HTTP status code. 187 * 188 * @return the HTTP status code. 189 */ getHttpStatusCode()190 int getHttpStatusCode() { 191 return mHttpStatusCode; 192 } 193 194 /** 195 * Returns an unmodifiable list of the response header key and value pairs. 196 * 197 * @return an unmodifiable list of response header key and value pairs 198 */ getAllHeadersList()199 List<Map.Entry<String, String>> getAllHeadersList() { 200 return mAllHeadersList; 201 } 202 203 /** 204 * Returns the wasCached value for this response. 205 * 206 * @return the wasCached value for this response 207 */ getWasCached()208 boolean getWasCached() { 209 return mWasCached; 210 } 211 212 /** 213 * Returns the protocol (for example 'quic/1+spdy/3') negotiated with the server. 214 * 215 * @return the protocol negotiated with the server 216 */ getNegotiatedProtocol()217 String getNegotiatedProtocol() { 218 return mNegotiatedProtocol; 219 } 220 221 /** 222 * Returns the proxy server that was used for the request. 223 * 224 * @return the proxy server that was used for the request 225 */ getProxyServer()226 String getProxyServer() { 227 return mProxyServer; 228 } 229 230 /** 231 * Returns the body of the response as a byte array. Used for {@link UrlRequest.Callback} 232 * {@code read()} callback. 233 * 234 * @return the response body 235 */ getResponseBody()236 byte[] getResponseBody() { 237 return mResponseBody; 238 } 239 240 /** 241 * Returns a mutable builder representation of this {@link FakeUrlResponse} 242 * 243 * @return a {@link FakeUrlResponse.Builder} with all fields copied from this instance. 244 */ toBuilder()245 public Builder toBuilder() { 246 return new Builder(this); 247 } 248 249 @Override equals(Object otherObj)250 public boolean equals(Object otherObj) { 251 if (!(otherObj instanceof FakeUrlResponse)) { 252 return false; 253 } 254 FakeUrlResponse other = (FakeUrlResponse) otherObj; 255 return (mHttpStatusCode == other.mHttpStatusCode 256 && mAllHeadersList.equals(other.mAllHeadersList) 257 && mWasCached == other.mWasCached 258 && mNegotiatedProtocol.equals(other.mNegotiatedProtocol) 259 && mProxyServer.equals(other.mProxyServer) 260 && Arrays.equals(mResponseBody, other.mResponseBody)); 261 } 262 263 @Override hashCode()264 public int hashCode() { 265 return Objects.hash( 266 mHttpStatusCode, 267 mAllHeadersList, 268 mWasCached, 269 mNegotiatedProtocol, 270 mProxyServer, 271 Arrays.hashCode(mResponseBody)); 272 } 273 274 @Override toString()275 public String toString() { 276 StringBuilder outputString = new StringBuilder(); 277 outputString.append("HTTP Status Code: " + mHttpStatusCode); 278 outputString.append(" Headers: " + mAllHeadersList.toString()); 279 outputString.append(" Was Cached: " + mWasCached); 280 outputString.append(" Negotiated Protocol: " + mNegotiatedProtocol); 281 outputString.append(" Proxy Server: " + mProxyServer); 282 outputString.append(" Response Body "); 283 try { 284 String bodyString = new String(mResponseBody, "UTF-8"); 285 outputString.append("(UTF-8): " + bodyString); 286 } catch (UnsupportedEncodingException e) { 287 outputString.append("(hexadecimal): " + getHexStringFromBytes(mResponseBody)); 288 } 289 return outputString.toString(); 290 } 291 getHexStringFromBytes(byte[] bytes)292 private String getHexStringFromBytes(byte[] bytes) { 293 StringBuilder bytesToHexStringBuilder = new StringBuilder(); 294 for (byte b : mResponseBody) { 295 bytesToHexStringBuilder.append(String.format("%02x", b)); 296 } 297 return bytesToHexStringBuilder.toString(); 298 } 299 } 300