package org.rapidoid.http;
/*
* #%L
* rapidoid-http-fast
* %%
* Copyright (C) 2014 - 2017 Nikolche Mihajlovski and contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.junit.Assert;
import org.junit.Test;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.buffer.Buf;
import org.rapidoid.buffer.BufGroup;
import org.rapidoid.data.BufRange;
import org.rapidoid.data.KeyValueRanges;
import org.rapidoid.http.impl.HttpParser;
import org.rapidoid.net.abstracts.Channel;
import org.rapidoid.net.impl.RapidoidHelper;
import org.rapidoid.test.TestCommons;
import org.rapidoid.u.U;
import java.nio.ByteBuffer;
@Authors("Nikolche Mihajlovski")
@Since("2.0.0")
public class HttpParserTest extends TestCommons {
private static final String CRLF = "\r\n";
private static String REQ1 = req("GET /foo/bar?a=5&b&n=%20 HTTP/1.1|Host:www.test.com|Set-Cookie: aaa=2||", CRLF);
private static String REQ2 = req(
"POST /something/else/here?x=abc%20de HTTP/STRANGE|Host:a.b.c.org|:ign|ored:|My-Header: same|My-Header: again|"
+ body("a"), CRLF);
private static String REQ3 = req("PUT /books HTTP/1.0|CoNNectioN: keep-alive | AAAAA: c = 2 |" + body("ab"), CRLF);
private static String REQ4 = req("DELETE /?a&bb=c&d MY-PROTOCOL|" + body("abc"), CRLF);
private static String REQ5 = req("ABCD ///??? HTTP/1.1|" + body("abcd"), CRLF);
private static String REQ6 = req("GET /?x A||", CRLF);
private static final String CONTENT_LENGTH = "CoNtEnT-LenGth";
private static String req(String s, String nl) {
return s.replaceAll("\\|", nl);
}
private static String body(String s) {
String body = "BODY" + s;
return CONTENT_LENGTH + ": " + body.getBytes().length + "||" + body;
}
@Test
public void shouldParseRequest1() {
RapidoidHelper req = parse(REQ1);
BufGroup bufs = new BufGroup(4);
Buf reqbuf = bufs.from(REQ1, "r2");
eq(REQ1, req.verb, "GET");
eq(REQ1, req.path, "/foo/bar");
eqs(REQ1, req.params, "a", "5", "b", "", "n", "%20");
eq(req.params.toMap(reqbuf, true, true, false), U.map("a", "5", "b", "", "n", " "));
eq(REQ1, req.protocol, "HTTP/1.1");
eqs(REQ1, req.headersKV, "Host", "www.test.com", "Set-Cookie", "aaa=2");
isNone(req.body);
}
@Test
public void shouldParseRequest2() {
RapidoidHelper req = parse(REQ2);
eq(REQ2, req.verb, "POST");
eq(REQ2, req.path, "/something/else/here");
eqs(REQ2, req.params, "x", "abc%20de");
eq(REQ2, req.protocol, "HTTP/STRANGE");
eqs(REQ2, req.headersKV, "Host", "a.b.c.org", "", "ign", "ored", "", "My-Header", "same", "My-Header", "again",
CONTENT_LENGTH, "5");
eq(REQ2, req.query, "x=abc%20de");
eq(REQ2, req.body, "BODYa");
}
@Test
public void shouldParseRequest3() {
RapidoidHelper req = parse(REQ3);
eq(REQ3, req.verb, "PUT");
eq(REQ3, req.path, "/books");
eqs(REQ3, req.params);
eq(REQ3, req.protocol, "HTTP/1.0");
eqs(REQ3, req.headersKV, "CoNNectioN", "keep-alive", "AAAAA", "c = 2", CONTENT_LENGTH, "6");
eq(REQ3, req.body, "BODYab");
}
@Test
public void shouldParseRequest4() {
RapidoidHelper req = parse(REQ4);
eq(REQ4, req.verb, "DELETE");
eq(REQ4, req.path, "/");
eqs(REQ4, req.params, "a", "", "bb", "c", "d", "");
eq(REQ4, req.protocol, "MY-PROTOCOL");
eqs(REQ4, req.headersKV, CONTENT_LENGTH, "7");
eq(REQ4, req.body, "BODYabc");
}
@Test
public void shouldParseRequest5() {
RapidoidHelper req = parse(REQ5);
eq(REQ5, req.verb, "ABCD");
eq(REQ5, req.path, "///");
eqs(REQ5, req.params, "??", "");
eq(req.params.toMap(REQ5), U.map("??", ""));
eq(REQ5, req.protocol, "HTTP/1.1");
eqs(REQ5, req.headersKV, CONTENT_LENGTH, "8");
eq(REQ5, req.body, "BODYabcd");
}
@Test
public void shouldParseRequest6() {
RapidoidHelper req = parse(REQ6);
eq(REQ6, req.verb, "GET");
eq(REQ6, req.path, "/");
eqs(REQ6, req.params, "x", "");
eq(REQ6, req.protocol, "A");
eqs(REQ6, req.headersKV);
isNone(req.body);
}
private RapidoidHelper parse(String reqs) {
RapidoidHelper req = new RapidoidHelper();
Buf reqbuf = new BufGroup(1024).from(reqs, "test");
Channel conn = mock(Channel.class);
returns(conn.input(), reqbuf);
returns(conn.helper(), req);
HttpParser parser = new HttpParser();
parser.parse(reqbuf, req);
parser.parseParams(reqbuf, req.params, req.query);
parser.parseHeadersIntoKV(reqbuf, req.headers, req.headersKV, req.cookies, req);
return req;
}
protected void eq(String whole, BufRange range, String expected) {
eq(range.get(whole), expected);
}
protected void eqs(String whole, KeyValueRanges ranges, String... keysAndValues) {
eq(keysAndValues.length % 2, 0);
eq(ranges.count, keysAndValues.length / 2);
for (int i = 0; i < ranges.count; i++) {
BufRange key = ranges.keys[i];
BufRange value = ranges.values[i];
eq(whole, key, keysAndValues[i * 2]);
eq(whole, value, keysAndValues[i * 2 + 1]);
}
}
protected void eq(BufRange range, int start, int length) {
Assert.assertEquals(range.start, start);
Assert.assertEquals(range.length, length);
}
protected void isNone(BufRange range) {
Assert.assertEquals(range.start, -1);
Assert.assertEquals(range.length, 0);
}
protected void eq(Buf buf, String expected) {
eq(buf.size(), expected.getBytes().length);
eq(buf.data(), expected);
byte[] bbytes = new byte[buf.size()];
ByteBuffer bufy = ByteBuffer.wrap(bbytes);
buf.writeTo(bufy);
eq(new String(bbytes), expected);
int size = (int) Math.ceil(expected.length() * 1.0 / buf.unitSize());
isTrue(buf.unitCount() == size || buf.unitCount() == size + 1);
byte[] bytes = expected.getBytes();
synchronized (buf) {
for (int i = 0; i < bytes.length; i++) {
eq((char) buf.get(i), (char) bytes[i]);
}
}
for (int len = 2; len < 10; len++) {
for (int p = 0; p <= buf.size() - len; p++) {
String sub = buf.get(new BufRange(p, len));
eq(sub, expected.substring(p, p + len));
}
}
}
}