diff --git a/modules/qrest/src/dist/deploy/30_qrest_txnmgr.xml b/modules/qrest/src/dist/deploy/30_qrest_txnmgr.xml index 136b9a96b2..351982426c 100644 --- a/modules/qrest/src/dist/deploy/30_qrest_txnmgr.xml +++ b/modules/qrest/src/dist/deploy/30_qrest_txnmgr.xml @@ -7,6 +7,7 @@ + @@ -22,6 +23,10 @@ + + + + diff --git a/modules/qrest/src/main/java/org/jpos/qrest/ExtractFile.java b/modules/qrest/src/main/java/org/jpos/qrest/ExtractFile.java new file mode 100644 index 0000000000..5175cf12a1 --- /dev/null +++ b/modules/qrest/src/main/java/org/jpos/qrest/ExtractFile.java @@ -0,0 +1,106 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2021 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.qrest; + +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; +import io.netty.handler.codec.http.multipart.FileUpload; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; +import io.netty.handler.codec.http.multipart.InterfaceHttpData; +import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType; +import org.jpos.core.Configurable; +import org.jpos.core.Configuration; +import org.jpos.core.ConfigurationException; +import org.jpos.transaction.Context; +import org.jpos.transaction.TransactionParticipant; + +import java.io.*; +import java.nio.channels.FileChannel; + +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.jpos.qrest.Constants.REQUEST; +import static org.jpos.qrest.Constants.RESPONSE; + +/** + * Extracts a file from the request + */ +public class ExtractFile implements TransactionParticipant, Configurable { + + private String ctxKey; + + @Override + public int prepare(long id, Serializable context) { + Context ctx = (Context) context; + FullHttpRequest request = ctx.get(REQUEST); + + try { + ctx.put(ctxKey, getFileFromRequest(request)); + } catch (IOException e) { + ctx.log(e); + ctx.put(RESPONSE, new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_FOUND)); + return ABORTED; + } + + return PREPARED | READONLY | NO_JOIN; + } + + protected File getFileFromRequest(FullHttpRequest httpRequest) throws IOException { + + HttpPostRequestDecoder httpDecoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(true), httpRequest); + httpDecoder.setDiscardThreshold(0); + + httpDecoder.offer(httpRequest); + return readChunk(httpDecoder); + + } + + private File readChunk(HttpPostRequestDecoder httpDecoder) throws IOException { + + for (InterfaceHttpData data : httpDecoder.getBodyHttpDatas()) { + if (data == null) continue; + try { + HttpDataType httpDataType = data.getHttpDataType(); + if (httpDataType == HttpDataType.FileUpload) { + final FileUpload fileUpload = (FileUpload) data; + final File file = File.createTempFile(fileUpload.getFilename(), ".qrest.extract_file"); + if (!file.exists() && !file.createNewFile()) { + throw new IOException("Can't create file " + file.getAbsolutePath()); + } + try (FileInputStream fis = new FileInputStream(fileUpload.getFile()); + FileChannel inputChannel = fis.getChannel(); + FileOutputStream fos = new FileOutputStream(file); + FileChannel outputChannel = fos.getChannel()) { + outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); + } + return file; + } + } finally { + data.release(); + } + } + return null; + } + + @Override + public void setConfiguration(Configuration cfg) throws ConfigurationException { + this.ctxKey = cfg.get("CTX_KEY", "FILE_FROM_REQUEST"); + } +} diff --git a/modules/qrest/src/test/java/org/jpos/qrest/RestTest.java b/modules/qrest/src/test/java/org/jpos/qrest/RestTest.java index ed2d1c8e98..8cc8a77625 100644 --- a/modules/qrest/src/test/java/org/jpos/qrest/RestTest.java +++ b/modules/qrest/src/test/java/org/jpos/qrest/RestTest.java @@ -20,20 +20,23 @@ import io.restassured.RestAssured; import io.restassured.builder.RequestSpecBuilder; +import org.apache.http.entity.ContentType; import org.jpos.q2.Q2; import org.jpos.util.NameRegistrar; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static io.restassured.RestAssured.given; -import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON; +import java.io.File; +import java.nio.file.Files; -import static org.hamcrest.Matchers.*; +import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON; +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; public class RestTest { private static final String BASE_URL = "http://localhost:8081/"; private static Q2 q2; - + @BeforeAll public static void setUp() throws NameRegistrar.NotFoundException { RestAssured.baseURI = BASE_URL; @@ -100,4 +103,20 @@ public void testMultiplesTMs() { .body("name", equalTo("txnmgr2") ); } + + @Test + void testUploadFile() throws Exception { + + File tempFile = File.createTempFile("qrest", "test"); + Files.write(tempFile.toPath(), "hello".getBytes()); + + given().log().all() + .contentType(ContentType.MULTIPART_FORM_DATA.getMimeType()) + .multiPart("file", tempFile, "multipart/form-data") + .post("/test/load_file") + .then() + .statusCode(200) + .assertThat() + .body("content", equalTo("hello")); + } } diff --git a/modules/qrest/src/test/java/org/jpos/qrest/test/participant/DumpFile.java b/modules/qrest/src/test/java/org/jpos/qrest/test/participant/DumpFile.java new file mode 100644 index 0000000000..04c8547048 --- /dev/null +++ b/modules/qrest/src/test/java/org/jpos/qrest/test/participant/DumpFile.java @@ -0,0 +1,39 @@ +package org.jpos.qrest.test.participant; + +import io.netty.handler.codec.http.HttpResponseStatus; +import org.jpos.qrest.Response; +import org.jpos.transaction.Context; +import org.jpos.transaction.TransactionParticipant; + +import java.io.File; +import java.io.Serializable; +import java.nio.file.Files; +import java.util.Collections; + +import static org.jpos.qrest.Constants.RESPONSE; + +/** + * @author Arturo Volpe + * @since 2022-08-03 + */ +public class DumpFile implements TransactionParticipant { + + + @Override + public int prepare(long id, Serializable context) { + + Context ctx = (Context) context; + + File file = ctx.get("FILE_FROM_REQUEST"); + try { + String content = String.join("", Files.readAllLines(file.toPath())); + ctx.put(RESPONSE, new Response(HttpResponseStatus.OK, Collections.singletonMap("content", content))); + return PREPARED | NO_JOIN | READONLY; + } catch (Exception e) { + ctx.log(e); + ctx.put(RESPONSE, new Response(HttpResponseStatus.INTERNAL_SERVER_ERROR, Collections.emptyMap())); + return ABORTED; + } + } + +}