• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }