1 /*
   2  * Copyright (c) 2016, 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 
  24 /**
  25  * @test
  26  * @bug 8164704
  27  * @modules jdk.incubator.httpclient
  28  *          jdk.httpserver
  29  *          java.base/sun.net.www
  30  * @run main MessageHeadersTest
  31  * @summary Tests expected behavior of MessageHeader. This test
  32  *          cannot be used to verify 8164704 - it simply verifies
  33  *          the assumptions on which the fix is based.
  34  */
  35 
  36 import java.io.IOException;
  37 import java.io.InputStream;
  38 import java.io.UnsupportedEncodingException;
  39 import java.nio.ByteBuffer;
  40 
  41 
  42 public class MessageHeadersTest {
  43 
  44     static final String BODY =
  45           "This is the body dude,\r\n"
  46         + "not a header!\r\n";
  47 
  48     static final String MESSAGE_OK =
  49           "HTTP/1.1 200 OK\r\n"
  50         + "Content-Length: " + BODY.length() + "\r\n"
  51         + "MY-Folding-Header: YES\r\n"
  52         + " OR\r\n"
  53         + " NO\r\n"
  54         + "\r\n"
  55         + BODY;
  56 
  57     static final String MESSAGE_NOK =
  58           "HTTP/1.1 101 Switching Protocols\r\n"
  59         + "\r\n";
  60 
  61     static final class ByteBufferInputStream extends InputStream {
  62         final ByteBuffer buffer;
  63         int lastRead = -1;
  64         ByteBufferInputStream(ByteBuffer buffer) {
  65             this.buffer = buffer;
  66         }
  67         @Override
  68         public int read() throws IOException {
  69             if (buffer.hasRemaining()) {
  70                 return lastRead = buffer.get();
  71             }
  72             return -1;
  73         }
  74     }
  75 
  76     public static void main(String[] args) throws IOException {
  77         testMessageHeaders(MESSAGE_OK);
  78         testMessageHeaders(MESSAGE_NOK);
  79     }
  80 
  81     /**
  82      * Verifies that MessageHeader behave as we expect.
  83      * @param msg The response string.
  84      * @throws IOException should not happen.
  85      */
  86     static void testMessageHeaders(String msg) throws IOException {
  87         byte[] bytes = msg.getBytes("US-ASCII");
  88         ByteBuffer buffer = ByteBuffer.wrap(bytes);
  89 
  90         // Read status line
  91         String statusLine = readStatusLine(buffer);
  92         System.out.println("StatusLine: " + statusLine);
  93         if (!statusLine.startsWith("HTTP/1.1")) {
  94             throw new AssertionError("bad status line: " + statusLine);
  95         }
  96 
  97         // Wrap the buffer into an input stream and pass
  98         // that to MessageHeader to read the header.
  99         // We have two cases:
 100         //    - MESSAGE_OK: there will be some headers to read,
 101         //    - MESSAGE_NOK: there will be no headers to read.
 102         ByteBufferInputStream bbis = new ByteBufferInputStream(buffer);
 103         sun.net.www.MessageHeader mh = new sun.net.www.MessageHeader(bbis);
 104 
 105         // Now get the expected length of the body
 106         String contentLengthValue = mh.findValue("Content-length");
 107         int contentLength = contentLengthValue == null ? 0
 108             : Integer.parseInt(contentLengthValue);
 109 
 110         // We again have two cases:
 111         //    - MESSAGE_OK:  there should be a Content-length: header
 112         //    - MESSAGE_NOK: there should be no Content-length: header
 113         if (contentLengthValue == null) {
 114             // MESSAGE_NOK has no headers and no body and therefore
 115             // no Content-length: header.
 116             if (!MESSAGE_NOK.equals(msg)) {
 117                 throw new AssertionError("Content-length: header not found");
 118             }
 119             // In that case we expect MessageHeader to read the CR but
 120             // leave the LF in the buffer. We therefore need to consume
 121             // the the LF in order to get an empty (all consumed) buffer.
 122             // This is what ResponseHeaders does.
 123             byte c = buffer.get();
 124             if (c != '\n' || bbis.lastRead != '\r') {
 125                 throw new AssertionError("Unexpected byte sequence for empty body"
 126                         + ": " + bbis.lastRead + " " + c + " expected "
 127                         + (byte)'\r' + " " + (byte)'\n');
 128             }
 129         } else {
 130             if (MESSAGE_NOK.equals(msg)) {
 131                 throw new AssertionError("Content-length: header found in"
 132                           + " error 101 message");
 133             }
 134         }
 135 
 136         // Now read the remaining bytes. It should either be
 137         // the empty string (MESSAGE_NOK) or BODY (MESSAGE_OK),
 138         // and it should not contains any leading CR or LF"
 139         String remaining = readRemainingBytes(buffer);
 140         System.out.println("Body: <<<" + remaining + ">>>");
 141         if (remaining.length() != contentLength) {
 142             throw new AssertionError("Unexpected body length: " + remaining.length()
 143                      + " expected " + contentLengthValue);
 144         }
 145         if (contentLengthValue != null) {
 146             if (!BODY.equals(remaining)) {
 147                 throw new AssertionError("Body does not match!");
 148             }
 149         }
 150     }
 151 
 152     static String readRemainingBytes(ByteBuffer buffer) throws UnsupportedEncodingException {
 153         byte[] res = new byte[buffer.limit() - buffer.position()];
 154         System.arraycopy(buffer.array(), buffer.position(), res, 0, res.length);
 155         buffer.position(buffer.limit());
 156         return new String(res, "US-ASCII");
 157     }
 158 
 159     static String readStatusLine(ByteBuffer buffer) throws IOException {
 160         buffer.mark();
 161         int p = buffer.position();
 162         while(buffer.hasRemaining()) {
 163             char c = (char)buffer.get();
 164             if (c == '\r') {
 165                 c = (char)buffer.get();
 166                 if (c == '\n') {
 167                     byte[] res = new byte[buffer.position() - p -2];
 168                     System.arraycopy(buffer.array(), p, res, 0, res.length);
 169                     return new String(res, "US-ASCII");
 170                 }
 171             }
 172         }
 173         throw new IOException("Status line not found");
 174     }
 175 }