Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#23603 : Allow users to write code when creating a file #24181

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
@@ -0,0 +1,54 @@
package com.dotcms.rest.api.v1.temp;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

/**
*
*
* @author Jose Castro
* @since
*/
@JsonDeserialize(builder = PlainTextFileForm.Builder.class)
public class PlainTextFileForm {

private final String fileName;
private final String fileContent;

public PlainTextFileForm(final Builder builder) {
this.fileName = builder.fileName;
this.fileContent = builder.fileContent;
}

public String fileContent() {
return fileContent;
}

public String fileName() {
return fileName;
}

public static final class Builder {

@JsonProperty(required = true)
private String fileName;
@JsonProperty(required = true)
private String fileContent;

public PlainTextFileForm.Builder fileName(final String fileName) {
this.fileName = fileName;
return this;
}

public PlainTextFileForm.Builder file(final String fileContent) {
this.fileContent = fileContent;
return this;
}

public PlainTextFileForm build() {
return new PlainTextFileForm(this);
}

}

}
69 changes: 45 additions & 24 deletions dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,8 @@
package com.dotcms.rest.api.v1.temp;

import static com.dotcms.storage.FileMetadataAPIImpl.*;

import com.dotcms.rest.exception.BadRequestException;
import com.dotcms.storage.FileMetadataAPIImpl;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import javax.servlet.http.HttpServletRequest;
import org.xbill.DNS.Address;
import org.xbill.DNS.ExtendedResolver;
import org.xbill.DNS.Resolver;
import com.dotcms.http.CircuitBreakerUrl;
import com.dotcms.http.CircuitBreakerUrl.Method;
import com.dotcms.rest.exception.BadRequestException;
import com.dotcms.util.CloseUtils;
import com.dotcms.util.ConversionUtils;
import com.dotcms.util.SecurityUtils;
Expand All @@ -35,7 +14,6 @@
import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.DNSUtil;
import com.dotmarketing.util.FileUtil;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.SecurityLogger;
Expand All @@ -50,9 +28,24 @@
import com.liferay.portal.util.WebKeys;
import com.liferay.util.Encryptor;
import com.liferay.util.StringPool;

import io.vavr.control.Try;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static com.dotcms.storage.FileMetadataAPIImpl.META_TMP;

public class TempFileAPI {

public static final String TEMP_RESOURCE_MAX_AGE_SECONDS = "TEMP_RESOURCE_MAX_AGE_SECONDS";
Expand Down Expand Up @@ -252,6 +245,34 @@ public DotTempFile createTempFileFromUrl(final String incomingFileName,

}

public DotTempFile updateTempFile(final String tempFileId, final String incomingFileName,
jcastro-dotcms marked this conversation as resolved.
Show resolved Hide resolved
final HttpServletRequest request, final InputStream inputStream) throws DotSecurityException {
if ("new".equalsIgnoreCase(tempFileId)) {
jcastro-dotcms marked this conversation as resolved.
Show resolved Hide resolved
return this.createTempFile(incomingFileName, request, inputStream);
}
final Optional<DotTempFile> dotTempFile = this.getTempFile(tempFileId);
if (dotTempFile.isEmpty()) {
return this.createTempFile(incomingFileName, request, inputStream);
}
final File tempFile = dotTempFile.get().file;
final long maxLength = maxFileSize(request);
try (final OutputStream out = new BoundedOutputStream(maxLength,Files.newOutputStream(tempFile.toPath()))) {
int read;
final byte[] bytes = new byte[4096];
while ((read = inputStream.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
return dotTempFile.get();
} catch (final IOException e) {
final String message = APILocator.getLanguageAPI().getStringKey(WebAPILocator.getLanguageWebAPI().getLanguage(request), "temp.file.max.file.size.error").replace("{0}", UtilMethods.prettyByteify(maxLength));
throw new DotStateException(message, e);
} catch (final Exception e) {
throw new DotRuntimeException(e.getMessage(), e);
} finally {
CloseUtils.closeQuietly(inputStream);
}
}

/**
* This method receives a URL and checks if starts with http or https,
* and also makes a request to the URL and if returns 200 the URL is valid,
Expand Down
146 changes: 133 additions & 13 deletions dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,54 @@

import com.dotcms.concurrent.DotConcurrentFactory;
import com.dotcms.concurrent.DotSubmitter;
import com.dotcms.mock.request.DotCMSMockRequest;
import com.dotcms.mock.request.DotCMSMockRequestWithSession;
import com.dotcms.rest.AnonymousAccess;
import com.dotcms.rest.ErrorEntity;
import com.dotcms.rest.InitDataObject;
import com.dotcms.rest.WebResource;
import com.dotcms.rest.annotation.NoCache;
import com.dotcms.rest.api.v1.DotObjectMapperProvider;
import com.dotcms.rest.api.v1.authentication.RequestUtil;
import com.dotcms.rest.api.v1.authentication.ResponseUtil;
import com.dotcms.rest.api.v1.workflow.WorkflowResource;
import com.dotcms.rest.exception.BadRequestException;
import com.dotcms.util.CollectionsUtils;
import com.dotcms.util.SecurityUtils;
import com.dotcms.workflow.form.FireMultipleActionForm;
import com.dotmarketing.beans.Request;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.exception.DoesNotExistException;
import com.dotmarketing.portlets.workflows.business.WorkflowAPI;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.PageMode;
import com.dotmarketing.util.UtilMethods;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.liferay.portal.util.WebKeys;
import com.liferay.util.HttpHeaders;
import com.liferay.util.StringPool;
import io.vavr.control.Try;
import org.apache.commons.lang.time.StopWatch;
import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.server.JSONP;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
Expand Down Expand Up @@ -185,6 +182,119 @@ private void saveMultipleBinary(final FormDataMultiPart body, final HttpServletR
printResponseEntityViewResult(outputStream, objectMapper, completionService, futures);
}

/**
*
* @param request
* @param response
* @param tempFileId
* @param body
* @return
*/
@PUT
@Path("/id/{tempFileId: .*}")
@JSONP
@NoCache
@Produces("application/octet-stream")
@Consumes(MediaType.APPLICATION_JSON)
public final Response updateTempResource(@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
@PathParam("tempFileId") final String tempFileId,
final PlainTextFileForm body) {
this.verifyTempResourceEnabled();
new WebResource.InitBuilder(request, response).requiredAnonAccess(AnonymousAccess.WRITE).rejectWhenNoUser(true).init();
jcastro-dotcms marked this conversation as resolved.
Show resolved Hide resolved
if (!new SecurityUtils().validateReferer(request)) {
throw new BadRequestException("Invalid Origin or referer");
}

final StreamingOutput streamingOutput = output -> {

final ObjectMapper objectMapper = DotObjectMapperProvider.getInstance().getDefaultObjectMapper();
TempFileResource.this.savePlainTextFile(tempFileId, body, request, output, objectMapper);

};

return Response.ok(streamingOutput).header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON).build();
}

protected class TempFileStreamingOutput implements StreamingOutput {

private final String tempFileId;
private final PlainTextFileForm body;
private final HttpServletRequest request;

private TempFileStreamingOutput(final String tempFileId, final PlainTextFileForm body, final HttpServletRequest request) {
this.tempFileId = tempFileId;
this.body = body;
this.request = request;
}

@Override
public void write(final OutputStream output) throws IOException, WebApplicationException {
final ObjectMapper objectMapper = DotObjectMapperProvider.getInstance().getDefaultObjectMapper();
TempFileResource.this.savePlainTextFile(tempFileId, body, request, output, objectMapper);
}
}

private void savePlainTextFile(final String tempFileId, final PlainTextFileForm body, final HttpServletRequest request,
final OutputStream outputStream, final ObjectMapper objectMapper) {
final CompletionService<Object> completionService = this.createCompletionService(2, 5, 100000);
final HttpServletRequest statelessRequest = RequestUtil.INSTANCE.createStatelessRequest(request);
// this triggers the save
final InputStream in = new ByteArrayInputStream(body.fileContent().getBytes());
final Future<Object> future = this.updateTempFileFuture(completionService, statelessRequest, 1,
body.fileName(), in, tempFileId);
/*final Future<Object> future = completionService.submit(() -> {
jcastro-dotcms marked this conversation as resolved.
Show resolved Hide resolved

try {
final InputStream in = new ByteArrayInputStream(body.fileContent().getBytes());
if (in == null) {
return new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), "Invalid Binary Part, index: " + futureIndex,
String.valueOf(futureIndex));
}
final String fileName = body.fileName();
if (fileName == null || fileName.startsWith(".") || fileName.contains("/.")) {

return new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST),
"Invalid Binary Part, Name: " + fileName + ", index: " + futureIndex,
String.valueOf(futureIndex));
}
return this.tempApi.updateTempFile(tempFileId, fileName, statelessRequest, in);
} catch (final Exception e) {
Logger.error(this, e.getMessage(), e);
return new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST),
"Invalid Binary Part, Message: " + e.getMessage() + ", index: " + futureIndex,
String.valueOf(futureIndex));
}

});*/
this.printResponseEntityViewResult(outputStream, objectMapper, completionService, List.of(future));
}

private Future<Object> updateTempFileFuture(final CompletionService<Object> completionService,
final HttpServletRequest statelessRequest, final int futureId,
final String fileName, final InputStream in, final String tempFileId) {
return completionService.submit(() -> {

try {
if (fileName == null || fileName.startsWith(".") || fileName.contains("/.")) {
return new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), "Invalid File Name, " +
"Name: " + fileName + ", index: " + futureId, String.valueOf(futureId));
}
if (in == null) {
return new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), "Invalid Binary Stream, " +
"index: " + futureId, fileName);
}
return this.tempApi.updateTempFile(tempFileId, fileName, statelessRequest, in);
} catch (final Exception e) {
Logger.error(this, e.getMessage(), e);
return new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), "Invalid Binary Part, " +
"Message: " + e.getMessage() + ", index: " + futureId, String.valueOf(futureId));
}

});
}

private void printResponseEntityViewResult(final OutputStream outputStream,
final ObjectMapper objectMapper,
final CompletionService<Object> completionService,
Expand Down Expand Up @@ -274,4 +384,14 @@ private void verifyTempResourceEnabled(){
}
}

/**
*
* @return
*/
private CompletionService<Object> createCompletionService(final int poolSize, final int maxPoolSize, final int queueCapacity) {
final DotSubmitter dotSubmitter = DotConcurrentFactory.getInstance().getSubmitter("TEMP_API_SUBMITTER",
new DotConcurrentFactory.SubmitterConfigBuilder().poolSize(poolSize).maxPoolSize(maxPoolSize).queueCapacity(queueCapacity).build());
return new ExecutorCompletionService<>(dotSubmitter);
}

}
Loading