Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.content.InputStreamContentSource;
import org.eclipse.jetty.server.FormFields;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.BufferUtil;
Expand Down Expand Up @@ -145,6 +145,9 @@ public Map<String, String> cookieMap() {

@Override
public Body body() {
if (isFormLike()) {
return Body.empty(this);
}
InputStream in = Content.Source.asInputStream(request);
long len = request.getLength();
if (maxRequestSize > 0) {
Expand Down Expand Up @@ -210,6 +213,23 @@ public QueryString query() {
return query;
}

private boolean isFormLike() {
var contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE);
Comment thread
jknack marked this conversation as resolved.
Resolved
return isFormEncoded(contentType) || isMultipart(contentType);
}

private boolean isFormEncoded(String contentType) {
return contentType != null && contentType.startsWith(MediaType.FORM_URLENCODED);
}

private boolean isMultipart() {
return isMultipart(request.getHeaders().get(HttpHeader.CONTENT_TYPE));
}

private boolean isMultipart(String contentType) {
return contentType != null && contentType.startsWith(MediaType.MULTIPART_FORMDATA);
}

@Override
public Formdata form() {
if (formdata == null) {
Expand All @@ -218,10 +238,8 @@ public Formdata form() {
formParam(request, formdata);

// Files:
String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE);
if (contentType != null
&& MimeTypes.Type.MULTIPART_FORM_DATA.is(
HttpField.getValueParameters(contentType, null))) {
if (isMultipart()) {
String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE);
// Is a multipart... make sure isn't empty
String boundary = MultiPart.extractBoundary(contentType);
if (boundary != null) {
Expand Down Expand Up @@ -724,7 +742,7 @@ private void register(DeleteFileTask deleteFileTask) {

private void formParam(Request request, Formdata form) {
try {
var params = Request.getParameters(request);
var params = FormFields.getFields(request);
for (Fields.Field param : params) {
String name = param.getName();
var values = param.getValues();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,10 +373,21 @@ public Value header() {
return headers;
}

private boolean hasNext(InterfaceHttpPostRequestDecoder decoder) {
try {
return decoder != null && decoder.hasNext();
} catch (HttpPostRequestDecoder.EndOfDataDecoderException ignored) {
return false;
}
}

@Override
public Body body() {
if (decoder != null && decoder.hasNext()) {
return new NettyBody(this, (HttpData) decoder.next(), HttpUtil.getContentLength(req, -1L));
if (hasNext(decoder)) {
if (decoder instanceof HttpRawPostRequestDecoder) {
return new NettyBody(this, (HttpData) decoder.next(), HttpUtil.getContentLength(req, -1L));
}
// form-like, we must use formdata() to get the body, so we return an empty body here.
}
return Body.empty(this);
}
Expand Down Expand Up @@ -900,20 +911,19 @@ private void decodeForm(Formdata form) {
return;
}
try {
while (decoder.hasNext()) {
while (hasNext(decoder)) {
HttpData next = (HttpData) decoder.next();
if (next.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
var fileUpoad =
var fileUpload =
new NettyFileUpload(
router.getTmpdir(), (io.netty.handler.codec.http.multipart.FileUpload) next);
register(new DeleteFileTask(router.getLog(), fileUpoad.getFileName(), fileUpoad::close));
form.put(next.getName(), fileUpoad);
register(
new DeleteFileTask(router.getLog(), fileUpload.getFileName(), fileUpload::close));
form.put(next.getName(), fileUpload);
} else {
form.put(next.getName(), next.getString(UTF_8));
}
}
} catch (HttpPostRequestDecoder.EndOfDataDecoderException x) {
// ignore, silly netty
} catch (Exception x) {
throw SneakyThrows.propagate(x);
}
Expand Down
240 changes: 240 additions & 0 deletions tests/src/test/java/io/jooby/i4031/Issue4031.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.i4031;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import org.junit.jupiter.api.Assertions;

import io.jooby.exception.MissingValueException;
import io.jooby.junit.ServerTest;
import io.jooby.junit.ServerTestRunner;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;

class Issue4031 {

@ServerTest
public void formDataAndQueryParameters(ServerTestRunner runner) {
runner
.define(
app ->
app.post(
"/",
ctx -> {
try {
Assertions.assertAll(
Comment thread
jknack marked this conversation as resolved.
Resolved
() ->
Assertions.assertEquals(
1, ctx.form().size(), "form contains one entry"),
() -> Assertions.assertTrue(ctx.form().isPresent()),
() -> Assertions.assertFalse(ctx.form().isMissing()),
() -> Assertions.assertEquals("bar", ctx.form().get("foo").value()),
() -> Assertions.assertEquals("bar", ctx.form("foo").value()),
() -> Assertions.assertEquals("bar", ctx.form().toMap().get("foo")),
() -> Assertions.assertEquals("bar", ctx.formMap().get("foo")),
() ->
Assertions.assertEquals(
"?parameter1=value1", ctx.query().queryString()),
() -> Assertions.assertEquals("parameter1=value1", ctx.query().value()),
() ->
Assertions.assertEquals("value1", ctx.query("parameter1").value()),
() ->
Assertions.assertEquals("value1", ctx.queryMap().get("parameter1")),
() -> Assertions.assertEquals(1, ctx.queryMap().size()));
return "OK";
} catch (Exception ex) {
ctx.setResponseCode(500);
return ex.getMessage();
}
}))
.ready(
http -> {
http.post(
"/",
new FormBody.Builder().add("foo", "bar").build(),
Map.of("parameter1", "value1"),
rsp -> {
Assertions.assertEquals("OK", rsp.body().string());
Assertions.assertEquals(200, rsp.code());
});

// form with bad media type
http.post(
"/",
customForm(
Map.of("foo", "bar"), "application/x-www-form-urlencoded; charset=utf-8"),
Map.of("parameter1", "value1"),
rsp -> {
Assertions.assertEquals("OK", rsp.body().string());
Assertions.assertEquals(200, rsp.code());
});

http.post(
"/",
customForm(
Map.of("foo", "bar"), "application/x-www-form-urlencoded;charset=iso-8859-1"),
Map.of("parameter1", "value1"),
rsp -> {
Assertions.assertEquals("OK", rsp.body().string());
Assertions.assertEquals(200, rsp.code());
});

// multipart
http.post(
"/",
new MultipartBody.Builder()
.addFormDataPart("foo", "bar")
.setType(MultipartBody.FORM)
.build(),
Map.of("parameter1", "value1"),
rsp -> {
Assertions.assertEquals("OK", rsp.body().string());
Assertions.assertEquals(200, rsp.code());
});
});
}

private static RequestBody customForm(Map<String, Object> form, String mediaType) {
var payload =
form.entrySet().stream()
.map(
entry ->
entry.getKey()
+ "="
+ URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8))
.reduce((a, b) -> a + "&" + b)
.orElse("");

return RequestBody.create(payload, MediaType.parse(mediaType));
}

@ServerTest
public void readFormDataAfterBody(ServerTestRunner runner) {
runner
.define(
app ->
app.post(
"/",
ctx -> {
try {
Assertions.assertAll(
() ->
Assertions.assertEquals(
1, ctx.form().size(), "form contains one entry"),
() -> Assertions.assertTrue(ctx.form().isPresent()),
() -> Assertions.assertFalse(ctx.form().isMissing()),
() -> Assertions.assertEquals("bar", ctx.form().get("foo").value()),
() -> Assertions.assertEquals("bar", ctx.form("foo").value()),
() -> Assertions.assertEquals("bar", ctx.form().toMap().get("foo")),
() -> Assertions.assertEquals("bar", ctx.formMap().get("foo")),
// get body().value() after calling form methods
() ->
Assertions.assertThrows(
MissingValueException.class, () -> ctx.body().value()));
return "OK";
} catch (Exception ex) {
ctx.setResponseCode(500);
return ex.getMessage();
}
}))
.ready(
http -> {
http.post(
"/",
new FormBody.Builder().add("foo", "bar").build(),
rsp -> {
Assertions.assertEquals("OK", rsp.body().string());
Assertions.assertEquals(200, rsp.code());
});

http.post(
"/",
customForm(
Map.of("foo", "bar"), "application/x-www-form-urlencoded; charset=utf-8"),
rsp -> {
Assertions.assertEquals("OK", rsp.body().string());
Assertions.assertEquals(200, rsp.code());
});

http.post(
"/",
new MultipartBody.Builder()
.addFormDataPart("foo", "bar")
.setType(MultipartBody.FORM)
.build(),
rsp -> {
Assertions.assertEquals("OK", rsp.body().string());
Assertions.assertEquals(200, rsp.code());
});
});
}

@ServerTest
public void readFormDataBeforeBody(ServerTestRunner runner) {
runner
.define(
app ->
app.post(
"/",
ctx -> {
try {
Assertions.assertAll(
// get body().value() before calling form methods
() ->
Assertions.assertThrows(
MissingValueException.class, () -> ctx.body().value()),
() ->
Assertions.assertEquals(
1, ctx.form().size(), "form contains one entry"),
() -> Assertions.assertTrue(ctx.form().isPresent()),
() -> Assertions.assertFalse(ctx.form().isMissing()),
() -> Assertions.assertEquals("bar", ctx.form().get("foo").value()),
() -> Assertions.assertEquals("bar", ctx.form("foo").value()),
() -> Assertions.assertEquals("bar", ctx.form().toMap().get("foo")),
() -> Assertions.assertEquals("bar", ctx.formMap().get("foo")));
return "OK";
} catch (Exception ex) {
ctx.setResponseCode(500);
return ex.getMessage();
}
}))
.ready(
http -> {
// http.post(
// "/",
// new FormBody.Builder().add("foo", "bar").build(),
// rsp -> {
// Assertions.assertEquals("OK", rsp.body().string());
// Assertions.assertEquals(200, rsp.code());
// });

http.post(
"/",
customForm(
Map.of("foo", "bar"), "application/x-www-form-urlencoded; charset=utf-8"),
rsp -> {
Assertions.assertEquals("OK", rsp.body().string());
Assertions.assertEquals(200, rsp.code());
});

// http.post(
// "/",
// new MultipartBody.Builder()
// .setType(MultipartBody.FORM)
// .addFormDataPart("foo", "bar")
// .build(),
// rsp -> {
// Assertions.assertEquals("OK", rsp.body().string());
// Assertions.assertEquals(200, rsp.code());
// });
});
}
}
7 changes: 7 additions & 0 deletions tests/src/test/java/io/jooby/test/FeaturedTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,13 @@ public void maxRequestSize(ServerTestRunner runner) {
assertEquals(200, rsp.code());
assertEquals(_8kb, rsp.body().string());
});
client.post(
"/request-size",
RequestBody.create(_4kb, MediaType.get("text/plain")),
rsp -> {
assertEquals(200, rsp.code());
assertEquals(_4kb, rsp.body().string());
});
// Empty
client.post(
"/request-size",
Expand Down
2 changes: 2 additions & 0 deletions tests/src/test/java/io/jooby/test/TestUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class TestUtil {

public static final String _8kb = _16kb.substring(0, _16kb.length() / 2);

public static final String _4kb = _8kb.substring(0, _8kb.length() / 2);

private static String readText(Path file) {
try {
return Files.readString(file);
Expand Down
Loading
Loading