1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.conscrypt; 18 19 import java.io.BufferedReader; 20 import java.io.IOException; 21 import java.io.InputStreamReader; 22 import java.io.OutputStream; 23 import java.net.HttpURLConnection; 24 import java.net.ServerSocket; 25 import java.net.Socket; 26 import java.net.URL; 27 import java.nio.charset.StandardCharsets; 28 import java.util.HashMap; 29 import java.util.Map; 30 import java.util.Objects; 31 import java.util.concurrent.Callable; 32 import javax.net.ssl.HttpsURLConnection; 33 import javax.net.ssl.SSLSocket; 34 import org.conscrypt.javax.net.ssl.TestSSLContext; 35 36 /** 37 * Very basic http server. Literally just enough to do some HTTP 1.1 in order 38 * to test URL connection functionality with the ability to inject faults. 39 */ 40 public class VeryBasicHttpServer { 41 private final TestSSLContext context = TestSSLContext.create(); 42 private final ServerSocket tlsSocket = context.serverSocket; 43 private final ServerSocket plainSocket = new ServerSocket(0); 44 45 VeryBasicHttpServer()46 public VeryBasicHttpServer() throws IOException { 47 } 48 getTlsHostname()49 public String getTlsHostname() { 50 return context.host.getHostName(); 51 } 52 getTlsPort()53 public int getTlsPort() { 54 return tlsSocket.getLocalPort(); 55 } 56 getPlainHostname()57 public String getPlainHostname() { 58 return plainSocket.getInetAddress().getHostName(); 59 } 60 getPlainPort()61 public int getPlainPort() { 62 return plainSocket.getLocalPort(); 63 } 64 runInternal(Op op)65 public void runInternal(Op op) throws Exception { 66 ServerSocket listenSocket = op.useTls() ? tlsSocket : plainSocket; 67 Socket connection = listenSocket.accept(); 68 if (connection instanceof SSLSocket) { 69 ((SSLSocket) connection).setUseClientMode(false); 70 } 71 long delay = op.getPostAcceptDelay(); 72 if (delay > 0) { 73 Thread.sleep(delay); 74 } 75 76 if (op.closeBeforeRead()) { 77 connection.close(); 78 return; 79 } 80 81 Request request = readRequest(connection); 82 process(request, op); 83 connection.close(); 84 } 85 run(Op op)86 public Callable<Void> run(Op op) { 87 return () -> { 88 runInternal(op); 89 return null; 90 }; 91 } 92 opBuilder()93 public Op.Builder opBuilder() { 94 return Op.newBuilder(); 95 } 96 process(Request request, Op op)97 void process(Request request, Op op) throws Exception { 98 String data = op.content.get(request.path); 99 if (data == null) { 100 request.sendStatus(404, request.protocol, "Not found: " + request.path); 101 request.endHeaders(); 102 } else { 103 request.sendStatus(200, request.protocol, "OK"); 104 request.sendHeader("Content-type", "text/plain"); 105 request.sendHeader("Content-Length", data.length()); 106 request.endHeaders(); 107 request.sendString(data); 108 } 109 } 110 readRequest(Socket socket)111 private Request readRequest(Socket socket) throws Exception { 112 Request request = new Request(); 113 request.outputStream = socket.getOutputStream(); 114 115 BufferedReader reader = new BufferedReader( 116 new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); 117 String line = reader.readLine(); 118 String[] words = line.split("\\s+"); 119 checkCondition("Expected 3 words", words.length == 3); 120 request.command = words[0]; 121 request.path = words[1]; 122 request.protocol = words[2]; 123 124 while (true) { 125 line = reader.readLine(); 126 Objects.requireNonNull(line); 127 if (line.isEmpty()) { 128 break; 129 } 130 int separator = line.indexOf(": "); 131 checkCondition("Parse error", separator > 0); 132 String key = line.substring(0, separator); 133 String value = line.substring(separator + 2); 134 request.headers.put(key, value); 135 } 136 return request; 137 } 138 checkCondition(String message, boolean condition)139 public void checkCondition(String message, boolean condition) { 140 if (!condition) { 141 throw new IllegalStateException(message); 142 } 143 } 144 tlsConnection(String filePart)145 public HttpsURLConnection tlsConnection(String filePart) throws Exception { 146 URL url = new URL("https", getTlsHostname(), getTlsPort(), filePart); 147 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 148 connection.setSSLSocketFactory(context.clientContext.getSocketFactory()); 149 return connection; 150 } 151 plainConnection(String filePart)152 public HttpURLConnection plainConnection(String filePart) throws Exception { 153 URL url = new URL("http", getPlainHostname(), getPlainPort(), filePart); 154 return (HttpURLConnection) url.openConnection(); 155 } 156 157 public static class Op { 158 private final Map<String, String> content; 159 private final long postAcceptDelay; 160 private final boolean useTls; 161 private final boolean closeBeforeRead; 162 Op(Map<String, String> content, long postAcceptDelay, boolean useTls, boolean closeBeforeRead)163 Op(Map<String, String> content, long postAcceptDelay, 164 boolean useTls, boolean closeBeforeRead) { 165 this.content = content; 166 this.postAcceptDelay = postAcceptDelay; 167 this.useTls = useTls; 168 this.closeBeforeRead = closeBeforeRead; 169 } 170 getPostAcceptDelay()171 public long getPostAcceptDelay() { 172 return postAcceptDelay; 173 } 174 useTls()175 public boolean useTls() { 176 return useTls; 177 } 178 closeBeforeRead()179 public boolean closeBeforeRead() { 180 return closeBeforeRead; 181 } 182 183 public static class Builder { 184 private final Map<String, String> content = new HashMap<>(); 185 private long postAcceptDelay = 0; 186 private boolean useTls = true; 187 private boolean closeBeforeRead = false; 188 Builder()189 private Builder() {} 190 content(String path, String data)191 public Builder content(String path, String data) { 192 this.content.put(path, data); 193 return this; 194 } 195 postAcceptDelay(long postAcceptDelay)196 public Builder postAcceptDelay(long postAcceptDelay) { 197 this.postAcceptDelay = postAcceptDelay; 198 return this; 199 } 200 noTls()201 public Builder noTls() { 202 useTls = false; 203 return this; 204 } 205 closeBeforeRead()206 public Builder closeBeforeRead() { 207 this.closeBeforeRead = true; 208 return this; 209 } 210 build()211 public Op build() { 212 return new Op(content, postAcceptDelay, useTls, closeBeforeRead); 213 } 214 } newBuilder()215 public static Builder newBuilder() { 216 return new Builder(); 217 } 218 } 219 220 private static class Request { 221 public String command; 222 public String protocol; 223 public String path; 224 public Map<String, String> headers = new HashMap<>(); 225 public OutputStream outputStream; 226 227 @Override toString()228 public String toString() { 229 return String.format("cmd=%s proto=%s path=%s headers=%s", 230 command, protocol, path, headers.toString()); 231 } 232 sendStatus(int result, String proto, String extra)233 public void sendStatus(int result, String proto, String extra) throws Exception { 234 String resultString = java.lang.String.format("%s %d %s\r\n", proto, result, extra); 235 outputStream.write(resultString.getBytes(StandardCharsets.UTF_8)); 236 } 237 sendString(String string)238 public void sendString (String string) throws Exception { 239 outputStream.write(string.getBytes(StandardCharsets.UTF_8)); 240 } 241 endHeaders()242 public void endHeaders() throws Exception { 243 sendString("\r\n"); 244 } 245 sendHeader(String key, String value)246 public void sendHeader(String key, String value) throws Exception { 247 sendString(key + ": " + value + "\r\n"); 248 } 249 sendHeader(String key, Integer value)250 public void sendHeader(String key, Integer value) throws Exception { 251 sendHeader(key, value.toString()); 252 } 253 } 254 }