1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.incubator.http;
  24 
  25 import java.io.IOException;
  26 import java.io.UnsupportedEncodingException;
  27 import java.nio.ByteBuffer;
  28 import java.nio.channels.SocketChannel;
  29 import java.util.Optional;
  30 import java.util.concurrent.CompletableFuture;
  31 import org.testng.annotations.Test;
  32 import jdk.incubator.http.internal.common.ByteBufferReference;
  33 
  34 @Test
  35 public class ResponseHeadersTest {
  36 
  37     static final String BODY =
  38           "This is the body dude,\r\n"
  39         + "not a header!\r\n";
  40 
  41     static final String MESSAGE_OK =
  42           "HTTP/1.1 200 OK\r\n"
  43         + "Content-Length: " + BODY.length() + "\r\n"
  44         + "MY-Folding-Header: YES\r\n"
  45         + " OR\r\n"
  46         + " NO\r\n"
  47         + "\r\n"
  48         + BODY;
  49 
  50     static final String MESSAGE_NOK =
  51           "HTTP/1.1 101 Switching Protocols\r\n"
  52         + "\r\n";
  53 
  54     //public static void main(String[] args) throws IOException {
  55     //    new  ResponseHeadersTest().test();
  56     //}
  57 
  58     @Test
  59     public void test() throws IOException {
  60         testResponseHeaders(MESSAGE_OK);
  61         testResponseHeaders(MESSAGE_NOK);
  62     }
  63 
  64     /**
  65      * Verifies that ResponseHeaders behave as we expect.
  66      * @param msg The response string.
  67      * @throws IOException should not happen.
  68      */
  69     static void testResponseHeaders(String msg) throws IOException {
  70         byte[] bytes = msg.getBytes("US-ASCII");
  71         ByteBuffer buffer = ByteBuffer.wrap(bytes);
  72 
  73         // Read status line
  74         String statusLine = readStatusLine(buffer);
  75         System.out.println("StatusLine: " + statusLine);
  76         if (!statusLine.startsWith("HTTP/1.1")) {
  77             throw new AssertionError("bad status line: " + statusLine);
  78         }
  79 
  80         // We have two cases:
  81         //    - MESSAGE_OK: there will be some headers to read,
  82         //    - MESSAGE_NOK: there will be no headers to read.
  83         HttpHeaders headers = createResponseHeaders(buffer);
  84 
  85         // Now get the expected length of the body
  86         Optional<String> contentLengthValue = headers.firstValue("Content-length");
  87         int contentLength = contentLengthValue.map(Integer::parseInt).orElse(0);
  88 
  89         // We again have two cases:
  90         //    - MESSAGE_OK:  there should be a Content-length: header
  91         //    - MESSAGE_NOK: there should be no Content-length: header
  92         if (contentLengthValue.isPresent()) {
  93             // MESSAGE_NOK has no headers and no body and therefore
  94             // no Content-length: header.
  95             if (MESSAGE_NOK.equals(msg)) {
  96                 throw new AssertionError("Content-length: header found in"
  97                           + " error 101 message");
  98             }
  99         } else {
 100             if (!MESSAGE_NOK.equals(msg)) {
 101                 throw new AssertionError("Content-length: header not found");
 102             }
 103         }
 104 
 105         // Now read the remaining bytes. It should either be
 106         // the empty string (MESSAGE_NOK) or BODY (MESSAGE_OK),
 107         // and it should not contains any leading CR or LF"
 108         String remaining = readRemainingBytes(buffer);
 109         System.out.println("Body: <<<" + remaining + ">>>");
 110         if (remaining.length() != contentLength) {
 111             throw new AssertionError("Unexpected body length: " + remaining.length()
 112                      + " expected " + contentLengthValue);
 113         }
 114         if (contentLengthValue.isPresent()) {
 115             if (!BODY.equals(remaining)) {
 116                 throw new AssertionError("Body does not match!");
 117             }
 118         }
 119     }
 120 
 121     static String readRemainingBytes(ByteBuffer buffer) throws UnsupportedEncodingException {
 122         byte[] res = new byte[buffer.limit() - buffer.position()];
 123         System.arraycopy(buffer.array(), buffer.position(), res, 0, res.length);
 124         buffer.position(buffer.limit());
 125         return new String(res, "US-ASCII");
 126     }
 127 
 128     static String readStatusLine(ByteBuffer buffer) throws IOException {
 129         buffer.mark();
 130         int p = buffer.position();
 131         while(buffer.hasRemaining()) {
 132             char c = (char)buffer.get();
 133             if (c == '\r') {
 134                 c = (char)buffer.get();
 135                 if (c == '\n') {
 136                     byte[] res = new byte[buffer.position() - p -2];
 137                     System.arraycopy(buffer.array(), p, res, 0, res.length);
 138                     return new String(res, "US-ASCII");
 139                 }
 140             }
 141         }
 142         throw new IOException("Status line not found");
 143     }
 144 
 145     private static final class HttpConnectionStub extends HttpConnection {
 146         public HttpConnectionStub() {
 147             super(null, null);
 148         }
 149         @Override
 150         public void connect() throws IOException, InterruptedException {
 151             throw new AssertionError("Bad test assumption: should not have reached here!");
 152         }
 153         @Override
 154         public CompletableFuture<Void> connectAsync() {
 155             throw new AssertionError("Bad test assumption: should not have reached here!");
 156         }
 157         @Override
 158         boolean connected() {
 159             throw new AssertionError("Bad test assumption: should not have reached here!");
 160         }
 161         @Override
 162         boolean isSecure() {
 163             throw new AssertionError("Bad test assumption: should not have reached here!");
 164         }
 165         @Override
 166         boolean isProxied() {
 167             throw new AssertionError("Bad test assumption: should not have reached here!");
 168         }
 169         @Override
 170         CompletableFuture<Void> whenReceivingResponse() {
 171             throw new AssertionError("Bad test assumption: should not have reached here!");
 172         }
 173         @Override
 174         SocketChannel channel() {
 175             throw new AssertionError("Bad test assumption: should not have reached here!");
 176         }
 177         @Override
 178         ConnectionPool.CacheKey cacheKey() {
 179             throw new AssertionError("Bad test assumption: should not have reached here!");
 180         }
 181         @Override
 182         long write(ByteBuffer[] buffers, int start, int number) throws IOException {
 183             throw new AssertionError("Bad test assumption: should not have reached here!");
 184         }
 185         @Override
 186         long write(ByteBuffer buffer) throws IOException {
 187             throw new AssertionError("Bad test assumption: should not have reached here!");
 188         }
 189         @Override
 190         void writeAsync(ByteBufferReference[] buffers) throws IOException {
 191             throw new AssertionError("Bad test assumption: should not have reached here!");
 192         }
 193         @Override
 194         void writeAsyncUnordered(ByteBufferReference[] buffers) throws IOException {
 195             throw new AssertionError("Bad test assumption: should not have reached here!");
 196         }
 197         @Override
 198         void flushAsync() throws IOException {
 199             throw new AssertionError("Bad test assumption: should not have reached here!");
 200         }
 201         @Override
 202         public void close() {
 203             throw new AssertionError("Bad test assumption: should not have reached here!");
 204         }
 205         @Override
 206         void shutdownInput() throws IOException {
 207             throw new AssertionError("Bad test assumption: should not have reached here!");
 208         }
 209         @Override
 210         void shutdownOutput() throws IOException {
 211             throw new AssertionError("Bad test assumption: should not have reached here!");
 212         }
 213         @Override
 214         protected ByteBuffer readImpl() throws IOException {
 215             throw new AssertionError("Bad test assumption: should not have reached here!");
 216         }
 217     }
 218 
 219     public static HttpHeaders createResponseHeaders(ByteBuffer buffer)
 220         throws IOException{
 221         return new ResponseHeaders(new HttpConnectionStub(), buffer);
 222     }
 223 }