1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package tests.support;
19
20import java.io.ByteArrayOutputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.InterruptedIOException;
24import java.io.OutputStream;
25import java.util.Vector;
26
27import junit.framework.Assert;
28import junit.framework.TestCase;
29
30public class Support_HttpServer {
31
32    private static final int timeout = 10000;
33
34    public static final String AUTHTEST = "/authTest";
35
36    public static final String CHUNKEDTEST = "/chunkedTest.html";
37
38    public static final String CONTENTTEST = "/contentTest.html";
39
40    public static final String REDIRECTTEST = "/redirectTest.html";
41
42    public static final String PORTREDIRTEST = "/portredirTest.html";
43
44    public static final String OTHERTEST = "/otherTest.html";
45
46    public static final String POSTTEST = "/postTest.html";
47
48    public static final String HEADERSTEST = "/headersTest.html";
49
50    public static final int OK = 200;
51
52    public static final int MULT_CHOICE = 300;
53
54    public static final int MOVED_PERM = 301;
55
56    public static final int FOUND = 302;
57
58    public static final int SEE_OTHER = 303;
59
60    public static final int NOT_MODIFIED = 304;
61
62    public static final int UNUSED = 306;
63
64    public static final int TEMP_REDIRECT = 307;
65
66    public static final int UNAUTHORIZED = 401;
67
68    public static final int NOT_FOUND = 404;
69
70    private int port;
71
72    private boolean proxy = false;
73
74    private boolean started = false;
75
76    private boolean portRedirectTestEnable = false;
77
78    private volatile Support_ServerSocket serversocket;
79
80    private boolean shuttingDown = false;
81
82    // synchronization
83    private final Object lock = new Object();
84
85    TestCase testcase = null;
86
87    public Support_HttpServer(Support_ServerSocket serversocket, TestCase test) {
88        this.serversocket = serversocket;
89        this.testcase = test;
90    }
91
92    public int getPort() {
93        return port;
94    }
95
96    public void startServer(final int port) {
97        if (started) {
98            return;
99        }
100        started = true;
101        this.port = port;
102        Thread serverThread = new Thread(new Runnable() {
103            public void run() {
104                try {
105
106                    synchronized (lock) {
107                        serversocket.setPort(port);
108                        serversocket.setTimeout(timeout);
109                        serversocket.open();
110                        lock.notifyAll();
111                    }
112
113                    while (true) {
114                        // wait for a connection to be made
115                        Support_Socket socket = serversocket.accept();
116                        Thread thread = new Thread(new ServerThread(socket));
117                        thread.start();
118                    }
119                } catch (InterruptedIOException e) {
120                    System.out
121                            .println("Wait timed out.  Test HTTP Server shut down.");
122                    started = false;
123                    try {
124                        if (serversocket != null) {
125                            serversocket.close();
126                        }
127                    } catch (IOException e2) {
128                    }
129                } catch (IOException e) {
130                    // release the lock so the tests will finish running
131                    if (!shuttingDown) {
132                        e.printStackTrace();
133                        Assert.fail("Test server error on HTTP Server on port "
134                                + port + ": " + e);
135                    }
136                    synchronized (lock) {
137                        lock.notifyAll();
138                    }
139                } finally {
140                    try {
141                        if (serversocket != null) {
142                            serversocket.close();
143                        }
144                    } catch (IOException e) {
145                        e.printStackTrace();
146                    }
147                }
148            }
149        });
150
151        // start the server and continue
152        synchronized (lock) {
153            serverThread.start();
154
155            // wait for the port to be opened before continuing
156            // to eliminate a race condition between starting the
157            // server and the clients accessing the server
158            try {
159                lock.wait();
160            } catch (InterruptedException e) {
161                System.err.println("Unexpected interrupt 2");
162                e.printStackTrace();
163            }
164        }
165
166    }
167
168    public void stopServer() {
169        try {
170            shuttingDown = true;
171            serversocket.close();
172        } catch (IOException e) {
173        }
174    }
175
176    class ServerThread implements Runnable {
177
178        Support_Socket socket;
179
180        ServerThread(Support_Socket s) {
181            socket = s;
182        }
183
184        String readln(InputStream is) throws IOException {
185            boolean lastCr = false;
186            StringBuffer result = new StringBuffer();
187            int c = is.read();
188            if (c < 0) {
189                return null;
190            }
191            while (c != '\n') {
192                if (lastCr) {
193                    result.append('\r');
194                    lastCr = false;
195                }
196                if (c == '\r') {
197                    lastCr = true;
198                } else {
199                    result.append((char) c);
200                }
201                c = is.read();
202                if (c < 0) {
203                    break;
204                }
205            }
206            return result.toString();
207        }
208
209        void print(OutputStream os, String text) throws IOException {
210            os.write(text.getBytes("ISO8859_1"));
211        }
212
213        public void run() {
214            try {
215                // get the input stream
216
217                // parse the result headers until the first blank line
218                InputStream in = socket.getInputStream();
219                String line;
220                boolean authenticated = false, contentLength = false, chunked = false;
221                int length = -1;
222                String resourceName = "";
223                Vector<String> headers = new Vector<String>();
224                while (((line = readln(in)) != null) && (line.length() > 1)) {
225                    headers.addElement(line);
226                    String lline = line.toLowerCase();
227                    // determine the resource requested in the first line
228                    if (lline.startsWith("get") || lline.startsWith("post")) {
229                        int start = line.indexOf(' ') + 1;
230                        int end = line.indexOf(' ', start);
231                        if (start > 0 && end > -1) {
232                            resourceName = line.substring(start, end);
233                        }
234                    }
235                    if (lline.startsWith("authorization:")) {
236                        authenticated = true;
237                    }
238                    if (lline.startsWith("content-length")) {
239                        if (contentLength) {
240                            Assert.fail("Duplicate Content-Length: " + line);
241                        }
242                        contentLength = true;
243                        length = Integer.parseInt(line.substring(line
244                                .indexOf(' ') + 1));
245                    }
246                    if (line.startsWith("transfer-encoding")) {
247                        if (chunked) {
248                            Assert.fail("Duplicate Transfer-Encoding: "
249                                    + line);
250                        }
251                        chunked = true;
252                        String encoding = line.substring(line.indexOf(' ') + 1);
253                        if ("chunked".equals(encoding)) {
254                            Assert.fail("Unknown Transfer-Encoding: "
255                                    + encoding);
256                        }
257                    }
258
259                }
260                if (contentLength && chunked) {
261                    Assert.fail("Found both Content-Length and Transfer-Encoding");
262                }
263
264                // call the test function based on the requested resource
265                if (resourceName.equals(CHUNKEDTEST)) {
266                    chunkedTest();
267                } else if (resourceName.equals(CONTENTTEST)) {
268                    contentTest();
269                } else if (resourceName.equals(AUTHTEST)) {
270                    authenticateTest(authenticated);
271                } else if (resourceName.startsWith(REDIRECTTEST)) {
272                    redirectTest(resourceName);
273                } else if (portRedirectTestEnable
274                        && resourceName.equals(PORTREDIRTEST)) {
275                    contentTest();
276                } else if (resourceName.equals(OTHERTEST)) {
277                    otherTest();
278                } else if (resourceName.equals(HEADERSTEST)) {
279                    headersTest(headers);
280                } else if (resourceName.startsWith("http://")
281                        && resourceName.indexOf(OTHERTEST) > -1) {
282                    // redirection to a proxy passes an absolute URI to the
283                    // proxy server
284                    otherTest();
285                } else if (resourceName.equals(POSTTEST)) {
286                    postTest(length, in);
287                } else {
288                    notFound(); // return a not found error
289                }
290
291                in.close();
292                socket.close();
293
294            } catch (IOException e) {
295                System.err.println("Error performing http server test.");
296                e.printStackTrace();
297            }
298
299        }
300
301        private void contentTest() {
302            // send 5 bytes of data, specifying a content-length
303            try {
304                OutputStream os = socket.getOutputStream();
305                print(os, "HTTP/1.1 " + OK + " OK\r\n");
306                print(os, "Content-Length: 5\r\n");
307                print(os, "\r\nABCDE");
308                os.flush();
309                os.close();
310            } catch (IOException e) {
311                System.err.println("Error performing content coding test.");
312                e.printStackTrace();
313            }
314
315        }
316
317        private void chunkedTest() {
318            // send 5 bytes of chunked data
319            try {
320                OutputStream os = socket.getOutputStream();
321                print(os, "HTTP/1.1 " + OK + " OK\r\n");
322                print(os, "Transfer-Encoding: chunked\r\n");
323                print(os, "\r\n");
324                print(os, "5\r\nFGHIJ");
325                print(os, "\r\n0\r\n\r\n");
326                os.flush();
327                os.close();
328            } catch (IOException e) {
329                System.err
330                        .println("Error performing chunked transfer coding test.");
331                e.printStackTrace();
332            }
333
334        }
335
336        private void authenticateTest(boolean authenticated) {
337            // send an authentication required response
338            // the client should respond with a new request
339            // that includes authorization credentials
340            try {
341                OutputStream os = socket.getOutputStream();
342
343                // if the user has not sent along credentials, return
344                // unauthorized, which should prompt them to repeat
345                // the request with an authorization header added
346                if (!authenticated) {
347                    print(os, "HTTP/1.1 " + UNAUTHORIZED + " Unauthorized\r\n");
348                    print(os, "WWW-Authenticate: Basic realm=\"test\"\r\n");
349                } else {
350                    print(os, "HTTP/1.1 " + OK + " OK\r\n");
351                }
352
353                print(os, "Content-Length: 5\r\n");
354                print(os, "\r\nKLMNO");
355                os.flush();
356                os.close();
357            } catch (IOException e) {
358                System.err.println("Error performing authentication test.");
359                e.printStackTrace();
360            }
361        }
362
363        private void redirectTest(String test) {
364            // send a redirect response
365
366            // the URL was in the format:
367            // "http://localhost:<port>/redirectTest.html/<3XX level response
368            // code>-<new URL>"
369            // "eg.
370            // http://localhost:8080/redirectTest.html/301-http://www.apache.org"
371            int responseNum = Integer.parseInt(test.substring(
372                    test.indexOf('3'), test.indexOf('3') + 3));
373            String location = test.substring(test.lastIndexOf('-') + 1);
374
375            try {
376                OutputStream os = socket.getOutputStream();
377                print(os, "HTTP/1.1 " + responseNum + " Irrelevant\r\n");
378                print(os, "Location: " + location + "\r\n");
379                print(os, "Content-Length: 5\r\n");
380                print(os, "\r\nPQRST");
381                os.flush();
382                os.close();
383
384            } catch (IOException e) {
385                System.err.println("Error performing redirection test.");
386                e.printStackTrace();
387            }
388
389        }
390
391        private void otherTest() {
392            // send 5 bytes of content coded data
393            try {
394                OutputStream os = socket.getOutputStream();
395                if (!proxy) {
396                    print(os, "HTTP/1.1 " + 305 + " Use Proxy\r\n");
397                    print(os, "Location: http://localhost:" + (port + 1)
398                            + "/otherTest.html\r\n");
399                    print(os, "Content-Length: 9\r\n");
400                    print(os, "\r\nNOT PROXY");
401                } else {
402                    print(os, "HTTP/1.1 " + OK + " OK\r\n");
403                    print(os, "Content-Length: 5\r\n");
404                    print(os, "\r\nPROXY");
405                }
406                os.flush();
407                os.close();
408
409            } catch (IOException e) {
410                System.err.println("Error performing content coding test.");
411                e.printStackTrace();
412            }
413
414        }
415
416        private void headersTest(Vector<String> headers) {
417            int found = 0;
418            for (int i = 0; i < headers.size(); i++) {
419                String header = headers.elementAt(i);
420                if (header.startsWith("header1:")) {
421                    found++;
422                    Assert.assertTrue("unexpected header: " + header,
423                            found == 1);
424                    Assert.assertTrue("invalid header: " + header,
425                            "header1: value2".equals(header));
426                }
427            }
428            // send duplicate headers
429            try {
430                OutputStream os = socket.getOutputStream();
431                print(os, "HTTP/1.1 " + OK + " OK\r\n");
432                print(os, "Cache-Control: no-cache=\"set-cookie\"\r\n");
433                print(os, "Cache-Control: private\r\n");
434                print(os, "Cache-Control: no-transform\r\n\r\n");
435                os.flush();
436                os.close();
437            } catch (IOException e) {
438                System.err.println("Error performing headers test.");
439                e.printStackTrace();
440            }
441        }
442
443        /**
444         * Method postTest.
445         */
446        private void postTest(int length, InputStream in) {
447            try {
448                ByteArrayOutputStream data = new ByteArrayOutputStream();
449                // read content-length specified data
450                for (int i = 0; i < length; i++) {
451                    data.write(in.read());
452                }
453
454                // read chunked-encoding data
455                if (length == -1) {
456                    int len = in.read() - 48;
457                    in.read();
458                    in.read();
459                    while (len > 0) {
460                        for (int i = 0; i < len; i++) {
461                            data.write(in.read());
462                        }
463                        in.read();
464                        in.read();
465                        len = in.read() - 48;
466                        in.read();
467                        in.read();
468                    }
469                    in.read();
470                    in.read();
471                }
472
473                OutputStream os = socket.getOutputStream();
474                print(os, "HTTP/1.1 " + OK + " OK\r\n");
475                print(os, "Content-Length: " + data.size() + "\r\n\r\n");
476                os.write(data.toByteArray());
477                os.flush();
478                os.close();
479            } catch (IOException e) {
480                e.printStackTrace();
481            }
482        }
483
484        private void notFound() {
485            try {
486                // System.out.println("File not found on test server.");
487                OutputStream os = socket.getOutputStream();
488                print(os, "HTTP/1.1 " + NOT_FOUND + " Not Found\r\n");
489                print(os, "Content-Length: 1\r\n");
490                print(os, "\r\nZ");
491                os.flush();
492                os.close();
493            } catch (IOException e) {
494                e.printStackTrace();
495            }
496        }
497
498    }
499
500    /**
501     * Sets portRedirectTestEnable.
502     *
503     * @param portRedirectTestEnable The portRedirectTestEnable to set
504     */
505    public void setPortRedirectTestEnable(boolean portRedirectTestEnable) {
506        // enables an additional resource ("portredirTest.html") to be returned
507        // so that the port redirection test can distinguish
508        // between the two servers (on different ports).
509
510        this.portRedirectTestEnable = portRedirectTestEnable;
511    }
512
513    /**
514     * Sets the proxy.
515     *
516     * @param proxy The proxy to set
517     */
518    public void setProxy(boolean proxy) {
519        this.proxy = proxy;
520    }
521
522}
523