1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the GNU General Public License v3+, or later.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc
.squirreljme
.plugin
.util
;
12 import java
.io
.ByteArrayOutputStream
;
13 import java
.io
.IOException
;
14 import java
.io
.InputStream
;
16 import java
.nio
.charset
.StandardCharsets
;
17 import java
.util
.Collections
;
19 import java
.util
.TreeMap
;
20 import java
.util
.regex
.Pattern
;
23 * Represents a HTTP request.
27 public final class SimpleHTTPRequest
28 implements SimpleHTTPData
30 /** The HTTP method. */
31 public final SimpleHTTPMethod method
;
33 /** The requested path. */
34 public final URI path
;
37 public final Map
<String
, String
> headers
;
39 /** The body of the request. */
40 private final byte[] _body
;
43 * Initializes the HTTP request.
45 * @param __method The method.
46 * @param __path The path.
47 * @param __headers The headers.
48 * @param __body The body, this is optional.
49 * @throws NullPointerException On null arguments.
52 public SimpleHTTPRequest(SimpleHTTPMethod __method
, URI __path
,
53 Map
<String
, String
> __headers
, byte[] __body
)
54 throws NullPointerException
56 if (__method
== null || __path
== null || __headers
== null)
57 throw new NullPointerException("NARG");
59 // Copy everything in the map
60 Map
<String
, String
> map
= new TreeMap
<>(String
.CASE_INSENSITIVE_ORDER
);
61 map
.putAll(__headers
);
63 // Store all the request details
64 this.method
= __method
;
66 this.headers
= Collections
.<String
, String
>unmodifiableMap(map
);
67 this._body
= (__body
== null ?
new byte[0] : __body
.clone());
75 public final String
toString()
78 "{method=%s, path=%s, headers=%s, body=%d bytes}",
79 this.method
, this.path
, this.headers
, this._body
.length
);
83 * Parses the HTTP request.
85 * This does not strictly conform to HTTP but is close enough for it to
88 * @param __in The stream to read from.
89 * @return The request.
90 * @throws IOException On read errors.
91 * @throws SimpleHTTPProtocolException If the protocol is not correct.
92 * @throws NullPointerException On null arguments.
95 @SuppressWarnings("resource")
96 public static SimpleHTTPRequest
parse(InputStream __in
)
97 throws IOException
, NullPointerException
, SimpleHTTPProtocolException
100 throw new NullPointerException("NARG");
102 // HTTP requests are in the following form:
104 // Host: www.example.com
105 SimpleHTTPMethod httpMethod
;
107 Map
<String
, String
> headers
=
108 new TreeMap
<>(String
.CASE_INSENSITIVE_ORDER
);
109 ByteArrayOutputStream body
= new ByteArrayOutputStream();
111 // Read the request line
112 String requestLine
= SimpleHTTPRequest
.__readLine(__in
);
114 // The request line must be in 3 parts
115 String
[] requestSplice
= requestLine
.split(Pattern
.quote(" "));
116 if (requestSplice
.length
< 3)
117 throw new SimpleHTTPProtocolException(
118 "Invalid HTTP request: " + requestLine
);
120 // Use these for later
121 httpMethod
= SimpleHTTPMethod
.valueOf(
122 requestSplice
[0].toUpperCase());
123 httpPath
= URI
.create(requestSplice
[1]);
125 // The following is the header data
128 String headerLine
= SimpleHTTPRequest
.__readLine(__in
);
130 // No more headers to send
131 if (headerLine
.isEmpty())
135 int col
= headerLine
.indexOf(':');
137 throw new SimpleHTTPProtocolException(
138 "Expected colon in header: " + headerLine
);
140 // Store header for later
141 headers
.put(headerLine
.substring(0, col
).trim(),
142 headerLine
.substring(col
+ 1).trim());
145 // The rest is just character data for the body, if there is one
146 if (httpMethod
.hasRequestBody
)
148 // Need to know the content length
149 String contentLengthStr
= headers
.get("Content-Length");
150 if (contentLengthStr
== null)
151 throw new SimpleHTTPProtocolException("No Content-Length");
153 // Parse content length as value
157 contentLength
= Integer
.parseInt(contentLengthStr
, 10);
159 catch (NumberFormatException e
)
161 throw new SimpleHTTPProtocolException(
162 "Invalid Content-Length: " + contentLengthStr
, e
);
165 // Read in all the data
166 for (int i
= 0; i
< contentLength
; i
++)
168 int rc
= __in
.read();
171 throw new SimpleHTTPProtocolException("Got EOF in body");
178 return new SimpleHTTPRequest(httpMethod
, httpPath
, headers
,
183 * Reads an input line from the stream.
185 * @param __in The stream to read from.
186 * @return The read line or {@code null} on end of file.
187 * @throws IOException On read errors.
188 * @throws SimpleHTTPProtocolException If the protocol is not correct.
189 * @throws NullPointerException On null arguments.
192 private static String
__readLine(InputStream __in
)
193 throws IOException
, NullPointerException
, SimpleHTTPProtocolException
196 throw new NullPointerException("NARG");
198 // Write to the buffer
199 try (ByteArrayOutputStream baos
= new ByteArrayOutputStream())
204 int rc
= __in
.read();
206 // For EOF just treat as the end of the data
218 // Append to the buffer
223 return new String(baos
.toByteArray(), StandardCharsets
.UTF_8
);