diff --git a/dotCMS/src/curl-test/Content Resource.postman_collection.json b/dotCMS/src/curl-test/Content Resource.postman_collection.json index 73b1ba8aaee1..86807ce82c0e 100644 --- a/dotCMS/src/curl-test/Content Resource.postman_collection.json +++ b/dotCMS/src/curl-test/Content Resource.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "50e22041-5236-42d7-8d37-b9a39f0457e3", + "_postman_id": "64a156b5-40b4-46ce-8bab-6083e910e48a", "name": "Content Resource", "description": "Content Resource test", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", @@ -1763,15 +1763,6 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "{}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { "raw": "{{serverURL}}/api/v1/workflow/actions/default/fire/UNPUBLISH?inode={{fileInode}}&identifier={{fileId}}", "host": [ @@ -1844,15 +1835,6 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "{}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { "raw": "{{serverURL}}/api/v1/workflow/actions/default/fire/ARCHIVE?inode={{fileInode}}&identifier={{fileId}}", "host": [ @@ -1925,15 +1907,6 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "{}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { "raw": "{{serverURL}}/api/v1/workflow/actions/default/fire/DELETE?inode={{fileInode}}&identifier={{fileId}}", "host": [ diff --git a/dotCMS/src/curl-test/TempAPI.postman_collection.json b/dotCMS/src/curl-test/TempAPI.postman_collection.json index 2c7427f98c45..7e5e963a8dbe 100644 --- a/dotCMS/src/curl-test/TempAPI.postman_collection.json +++ b/dotCMS/src/curl-test/TempAPI.postman_collection.json @@ -1,242 +1,11 @@ { "info": { - "_postman_id": "b65639a1-cec6-4b6c-b8b6-ab483db33b5b", + "_postman_id": "1faea77b-c035-4129-a215-3608487d19b3", "name": "TempAPI", - "description": "Verifies that the Temp File API is working as expected. It allows users to create temporary files in the dotCMS assets folder.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "5403727" + "_exporter_id": "11174695" }, "item": [ - { - "name": "Temp File As Plain Text", - "item": [ - { - "name": "Create Temp File", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "let randomNumber = Math.floor(Math.random() * 100);", - "pm.collectionVariables.set(\"tempFileName\", randomNumber + \"-test-temp-file.txt\");" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Test Temporary File creation HTTP Status must be successful\", function() {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.collectionVariables.set(\"tempFileId\", pm.response.json().tempFiles[0].id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "basic", - "basic": [ - { - "key": "username", - "value": "admin@dotcms.com", - "type": "string" - }, - { - "key": "password", - "value": "admin", - "type": "string" - } - ] - }, - "method": "PUT", - "header": [ - { - "key": "Origin", - "value": "localhost", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"fileName\": \"{{tempFileName}}\",\n \"fileContent\": \"This is the content of the Temporary File.\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{serverURL}}/api/v1/temp/id/new", - "host": [ - "{{serverURL}}" - ], - "path": [ - "api", - "v1", - "temp", - "id", - "new" - ] - } - }, - "response": [] - }, - { - "name": "Update Existing Temp File Content", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Test Temporary File update HTTP Status must be successful\", function() {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Temporary File ID must be the same\", function() {", - " let tempFileId = pm.collectionVariables.get(\"tempFileId\");", - " pm.expect(pm.response.json().tempFiles[0].id).to.eql(tempFileId, \"An error occurred when checking the temp file ID\");", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "basic", - "basic": [ - { - "key": "username", - "value": "admin@dotcms.com", - "type": "string" - }, - { - "key": "password", - "value": "admin", - "type": "string" - } - ] - }, - "method": "PUT", - "header": [ - { - "key": "Origin", - "value": "localhost", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"fileName\": \"{{tempFileName}}\",\n \"fileContent\": \"This is the new content of the Temporary File.\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{serverURL}}/api/v1/temp/id/{{tempFileId}}", - "host": [ - "{{serverURL}}" - ], - "path": [ - "api", - "v1", - "temp", - "id", - "{{tempFileId}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Non-Existent Temp File", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Non-Existing Test Temporary File creation HTTP Status must be successful\", function() {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"New Temporary File ID must NOT match the previous one\", function() {", - " let tempFileId = pm.collectionVariables.get(\"tempFileId\");", - " pm.expect(pm.response.json().tempFiles[0].id).to.not.eql(tempFileId, \"An error occurred when checking different temp file IDs\");", - "});" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "let randomNumber = Math.floor(Math.random() * 100);", - "pm.collectionVariables.set(\"tempFileName\", \"new-\" + randomNumber + \"-test-temp-file.txt\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "basic", - "basic": [ - { - "key": "username", - "value": "admin@dotcms.com", - "type": "string" - }, - { - "key": "password", - "value": "admin", - "type": "string" - } - ] - }, - "method": "PUT", - "header": [ - { - "key": "Origin", - "value": "localhost", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"fileName\": \"{{tempFileName}}\",\n \"fileContent\": \"Here is some test content.\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{serverURL}}/api/v1/temp/id/non-existent-id", - "host": [ - "{{serverURL}}" - ], - "path": [ - "api", - "v1", - "temp", - "id", - "non-existent-id" - ] - } - }, - "response": [] - } - ], - "description": "This request collection creates Temporary Files provided as plain text instead of binary files." - }, { "name": "Upload Multiple with one wrong file", "event": [ @@ -244,36 +13,14 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Checking file names and operation status code\", function () {", - " var jsonData = pm.response.json();", - " let found = false;", - " jsonData.tempFiles.forEach((item) => {", - "", - " if (item.fileName == \"Landscape_2009_romantic_country_garden.jpeg\") {", - " found = true;", - " }", - "", - " });", - " pm.expect(found).to.eq(true, \"Expected image 'Landscape_2009_romantic_country_garden.jpeg' was not found.\")", - " found = false;", - " jsonData.tempFiles.forEach((item) => {", + "var jsonData = pm.response.json();", "", - " if (item.fileName == \"16475687531_eac8a30914_b.jpeg\") {", - " found = true;", - " }", - "", - " });", - " pm.expect(found).to.eq(true, \"Expected image '16475687531_eac8a30914_b.jpeg' was not found.\")", - " found = false;", - " jsonData.tempFiles.forEach((item) => {", - "", - " if (item.errorCode == \"400\") {", - " found = true;", - " }", - "", - " });", - " pm.expect(found).to.eq(true, \"Expected error code '400' not found.\")", + "pm.test(\"File name check\", function () {", + " pm.expect(jsonData.tempFiles[0].fileName).to.eql('Landscape_2009_romantic_country_garden.jpeg');", + " pm.expect(jsonData.tempFiles[1].fileName).to.eql('16475687531_eac8a30914_b.jpeg');", + " pm.expect(jsonData.tempFiles[2].errorCode).to.eql('400');", "});", + "", "" ], "type": "text/javascript" @@ -410,35 +157,5 @@ }, "response": [] } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], - "variable": [ - { - "key": "tempFileName", - "value": "" - }, - { - "key": "tempFileId", - "value": "" - } ] } \ No newline at end of file diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/PlainTextFileForm.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/PlainTextFileForm.java deleted file mode 100644 index 7f08423392a1..000000000000 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/PlainTextFileForm.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.dotcms.rest.api.v1.temp; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -/** - * Provides all the required information of a File Asset that is being provided as plain text. For instance, this can - * be used by the Temp File Resource to create a file with the specified contents, which is what allows Users to create - * a text file -- i.e, TXT, VTL, JS, CSS, and so on -- directly from the dotCMS back-end. - * - * @author Jose Castro - * @since Feb 22nd, 2023 - */ -@JsonDeserialize(builder = PlainTextFileForm.Builder.class) -public class PlainTextFileForm { - - private final String fileName; - private final String fileContent; - - private PlainTextFileForm(final Builder builder) { - this.fileName = builder.fileName; - this.fileContent = builder.fileContent; - } - - public String fileContent() { - return fileContent; - } - - public String fileName() { - return fileName; - } - - /** - * Allows you to build an instance of the {@link PlainTextFileForm} class. - */ - public static final class Builder { - - @JsonProperty(required = true) - private String fileName; - @JsonProperty(required = true) - private String fileContent; - - /** - * Sets the name of the plain text file. - * - * @param fileName The file name. - * - * @return An instance of the class' builder. - */ - public PlainTextFileForm.Builder fileName(final String fileName) { - this.fileName = fileName; - return this; - } - - /** - * Sets the content of the plain text file. - * - * @param fileContent The file's content. - * - * @return An instance of the class' builder. - */ - public PlainTextFileForm.Builder file(final String fileContent) { - this.fileContent = fileContent; - return this; - } - - /** - * Creates an instance of the {@link PlainTextFileForm} class. - * - * @return The instantiated class. - */ - public PlainTextFileForm build() { - return new PlainTextFileForm(this); - } - - } - -} diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java index 5ac958c1b48c..9e0ca422dd68 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileAPI.java @@ -1,8 +1,29 @@ 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; @@ -14,6 +35,7 @@ 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; @@ -21,38 +43,16 @@ import com.dotmarketing.util.UtilMethods; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.liferay.portal.model.User; import com.liferay.portal.util.PortalUtil; 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; +import io.vavr.control.Try; -/** - * This API allows you to create temporary files in dotCMS. This API is very useful for uploading resources that can be - * safely deleted after a given amount of time, and also used by the File Asset edit mode when a new file is being - * uploaded to the repository. - * - * @author Will Ezell - * @since Jul 8th, 2019 - */ public class TempFileAPI { public static final String TEMP_RESOURCE_MAX_AGE_SECONDS = "TEMP_RESOURCE_MAX_AGE_SECONDS"; @@ -60,25 +60,24 @@ public class TempFileAPI { public static final String TEMP_RESOURCE_ALLOW_NO_REFERER = "TEMP_RESOURCE_ALLOW_NO_REFERER"; public static final String TEMP_RESOURCE_MAX_FILE_SIZE = "TEMP_RESOURCE_MAX_FILE_SIZE"; public static final String TEMP_RESOURCE_MAX_FILE_SIZE_ANONYMOUS = "TEMP_RESOURCE_MAX_FILE_SIZE_ANONYMOUS"; - public static final String MAX_FILE_LENGTH_PARAM = "maxFileLength"; public static final String TEMP_RESOURCE_ENABLED = "TEMP_RESOURCE_ENABLED"; public static final String TEMP_RESOURCE_PREFIX = "temp_"; private static final String WHO_CAN_USE_TEMP_FILE = "whoCanUse.tmp"; private static final String TEMP_RESOURCE_BY_URL_ADMIN_ONLY="TEMP_RESOURCE_BY_URL_ADMIN_ONLY"; + + /** - * Returns an empty TempFile of a unique id and file handle that can be used to write and access a temp file. The - * request will be used to create a fingerprint that will be written to the "allowList" and can be used to retrieve - * the temp resource in other requests. - * - * @param incomingFileName The name of the Temporary File. - * @param request The current instance of the {@link HttpServletRequest} - * - * @return The empty {@link DotTempFile}. - * - * @throws DotSecurityException An error occurred when creating the Temporary File. + * Returns an empty TempFile of a unique id and file handle that can be used to write and access a + * temp file. The request will be used to create a fingerprint that will be written to the "allowList" and can + * be used to retreive the temp resource in other requests + * + * @param incomingFileName + * @param request + * @return + * @throws DotSecurityException */ public DotTempFile createEmptyTempFile(final String incomingFileName,final HttpServletRequest request) throws DotSecurityException { final String anon = Try.of(() -> APILocator.getUserAPI().getAnonymousUser().getUserId()).getOrElse("anonymous"); @@ -127,62 +126,77 @@ public DotTempFile createEmptyTempFile(final String incomingFileName,final HttpS } /** - * This method takes a request and based upon it returns the max file size that can be uploaded, based on the user - * uploading. Anonymous users can have smaller file size limitations than authenticated users. A return of -1 means - * unlimited. - * - * @param request The current instance of the {@link HttpServletRequest} - * - * @return The maximum file size allowed by dotCMS. + * This method takes a request and based upon it it returns the max file size that can be + * uploaded, based on the user uploading. Anonymous users can have smaller file size limitations + * than authenticated users. A return of -1 means unlimited. + * @param request + * @return */ @VisibleForTesting public long maxFileSize(final HttpServletRequest request) { - final long requestedMax = ConversionUtils.toLongFromByteCountHumanDisplaySize(request.getParameter(MAX_FILE_LENGTH_PARAM), -1L); - final long systemMax = Config.getLongProperty(TEMP_RESOURCE_MAX_FILE_SIZE, -1L); - final long anonMax = Config.getLongProperty(TEMP_RESOURCE_MAX_FILE_SIZE_ANONYMOUS, -1L); + + + final long requestedMax = ConversionUtils.toLongFromByteCountHumanDisplaySize(request.getParameter("maxFileLength"), -1); + final long systemMax = Config.getLongProperty(TEMP_RESOURCE_MAX_FILE_SIZE, -1l); + final long anonMax = Config.getLongProperty(TEMP_RESOURCE_MAX_FILE_SIZE_ANONYMOUS, -1l); final boolean isAnon = PortalUtil.getUserId(request) == null || UserAPI.CMS_ANON_USER_ID.equals(PortalUtil.getUserId(request)); - final List longs = (isAnon) ? Lists.newArrayList(requestedMax,systemMax,anonMax) : Lists.newArrayList(requestedMax,systemMax); + List longs = (isAnon) ? Lists.newArrayList(requestedMax,systemMax,anonMax) : Lists.newArrayList(requestedMax,systemMax); longs.removeIf(i-> i < 0); Collections.sort(longs); - return longs.isEmpty() ? -1L : longs.get(0); - } + return longs.isEmpty() ? -1l : longs.get(0); + } + + + /** - * Writes an InputStream to a temp file and returns the tempFile with a unique id and file handle that can be used to - * access the temp file. The request will be used to create a fingerprint that will be written to the "allowList" and - * can be used to retrieve the temp resource in other requests. - * - * @param incomingFileName The name of the Temporary File. - * @param request The current instance of the {@link HttpServletRequest} - * @param inputStream The content of the Temporary File. - * - * @return The new {@link DotTempFile} - * - * @throws DotSecurityException An error occurred when creating the Temporary File. + * Writes an InputStream to a temp file and returns the tempFile with a unique id and file handle + * that can be used to access the temp file. The request will be used to create a fingerprint + * that will be written to the "allowList" and can + * be used to retreive the temp resource in other requests + * + * @param incomingFileName + * @param request + * @param inputStream + * @return + * @throws DotSecurityException */ public DotTempFile createTempFile(final String incomingFileName,final HttpServletRequest request, final InputStream inputStream) throws DotSecurityException { + final DotTempFile dotTempFile = this.createEmptyTempFile(incomingFileName, request); - this.writeFile(request, dotTempFile, inputStream); - return dotTempFile; + final File tempFile = dotTempFile.file; + final long maxLength = maxFileSize(request); + + try (final OutputStream out = new BoundedOutputStream(maxLength,Files.newOutputStream(tempFile.toPath()))) { + + + int read = 0; + final byte[] bytes = new byte[4096]; + while ((read = inputStream.read(bytes)) != -1) { + out.write(bytes, 0, read); + } + return dotTempFile; + } catch (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 (Exception e) { + throw new DotRuntimeException(e.getMessage(), e); + } finally { + CloseUtils.closeQuietly(inputStream); + } } /** - * Takes a URL, downloads it and the returns the resulting file as tempFile with a unique id and file handle that can - * be used to access the temp file. The request will be used to create a fingerprint that will be written to the - * "allowList" and can be used to retrieve the temp resource in other requests - * - * @param incomingFileName The name of the Temporary File, if required. - * @param request The current instance of the {@link HttpServletRequest} - * @param url The {@link URL} pointing to the file that must be retrieved. - * @param timeoutSeconds The specified timeout for reading the files via the URL. - * @param maxLength The maximum allowed size of the file being retrieved. - * - * @return The {@link DotTempFile} with the file specified via the URL. - * - * @throws DotSecurityException An error occurred when creating the Temporary File. - * @throws IOException An error occurred retrieving the contents of the file via URL. + * Takes a url, downloads it and the returns the resulting file as tempFile with a unique id and + * file handle that can be used to access the temp file. The request will be used to create a fingerprint + * that will be written to the "allowList" and can be used to retreive the temp resource in other requests + * + * @param incomingFileName + * @param request + * @return + * @throws DotSecurityException */ public DotTempFile createTempFileFromUrl(final String incomingFileName, final HttpServletRequest request, final URL url, final int timeoutSeconds, @@ -239,44 +253,17 @@ public DotTempFile createTempFileFromUrl(final String incomingFileName, } /** - * Updates the contents of an existing Temporary File. If the ID of such a file equals the word {@code "new"} or if - * the ID doesn't exist anymore, a new temporary file with the specified content will be returned instead. - * - * @param request The current instance of the {@link HttpServletRequest}. - * @param tempFileId The ID of the Temporary File. - * @param incomingFileName The actual file name of the Temporary File. This is used only when the original - * Temporary File ID doesn't exist. - * @param inputStream The new content of the Temporary File. - * - * @return The {@link DotTempFile} representing the specified file, or a new one if the ID doesn't exist. - * - * @throws DotSecurityException An error occurred when creating Temporary File. - */ - public DotTempFile upsertTempFile(final HttpServletRequest request, final String tempFileId, final String incomingFileName, final InputStream inputStream) throws DotSecurityException { - if ("new".equalsIgnoreCase(tempFileId)) { - return this.createTempFile(incomingFileName, request, inputStream); - } - final Optional dotTempFile = this.getTempFile(tempFileId); - if (dotTempFile.isEmpty()) { - return this.createTempFile(incomingFileName, request, inputStream); - } - this.writeFile(request, dotTempFile.get(), inputStream); - return dotTempFile.get(); - } - - /** - * 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, if returns any other response will be false. - * - * @param url The specified URL - * + * 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, + * if returns any other response will be false + * @param url * @return boolean if the url is valid or not */ public boolean validUrl(final String url) { if(!(url.toLowerCase().startsWith("http://") || url.toLowerCase().startsWith("https://"))){ - Logger.error(this, String.format("URL [ %s ] does not start with http or https", url)); + Logger.error(this, "URL does not starts with http or https"); return false; } try { @@ -290,15 +277,6 @@ public boolean validUrl(final String url) { return true; } - /** - * Resolves the name of the Temporary File based on either the specified desired name, or by retrieving it from the - * URL. - * - * @param desiredName The specified desired file name. - * @param url The URL that contains the file name. - * - * @return The name of the Temporary File. - */ private String resolveFileName(final String desiredName, final URL url) { final String path=(url!=null)? url.getPath() : UUIDGenerator.shorty(); final String tryFileName = (desiredName!=null) @@ -308,21 +286,10 @@ private String resolveFileName(final String desiredName, final URL url) { : path; return FileUtil.sanitizeFileName(tryFileName); } - - /** - * Creates a {@code whoCanUse.tmp} file for every empty Temporary File. Such a file contains the following - * information: - *
    - *
  • The User ID who created the Temporary File.
  • - *
  • The Session ID.
  • - *
  • The request's fingerprint.
  • - *
- * - * @param parentFolder The folder that this TMP file will be created in. - * @param incomingAccessingList The list of properties that will be included in the file. - * - * @return The temporary permissions file. - */ + + + + private File createTempPermissionFile(final File parentFolder, final List incomingAccessingList) { List accessingList = new ArrayList<>(incomingAccessingList); accessingList.removeIf(Objects::isNull); @@ -337,15 +304,6 @@ private File createTempPermissionFile(final File parentFolder, final List getTempFile(final String tempFileId) { if (tempFileId == null || !tempFileId.startsWith(TEMP_RESOURCE_PREFIX)) { @@ -364,18 +322,12 @@ private Optional getTempFile(final String tempFileId) { return Optional.of(new DotTempFile(tempFileId, tempFile)); } - Logger.error(this, String.format("Temp File '%s' does not exist or its TTL already expired", tempFileId)); + + Logger.error(this,"Temp File does not exists or TTL of the file already expired"); return Optional.empty(); + } - /** - * Determines whether the incoming Access List has "use" permissions over a given Temporary File. - * - * @param incomingAccessingList The Access List being checked. - * @param dotTempFile The {@link DotTempFile} object. - * - * @return If the incoming list matches the existing permission list, returne {@code true}. - */ private boolean canUseTempFile(final List incomingAccessingList, final DotTempFile dotTempFile) { final File tempFile = dotTempFile.file; final List accessingList = new ArrayList<>(incomingAccessingList); @@ -388,7 +340,7 @@ private boolean canUseTempFile(final List incomingAccessingList, final D : new File(tempFile.getParentFile(), WHO_CAN_USE_TEMP_FILE); try { - final List perms = (file.exists()) ? new ObjectMapper().readValue(file, List.class) : List.of(); + final List perms = (file.exists()) ? new ObjectMapper().readValue(file, List.class) : ImmutableList.of(); return !Collections.disjoint(perms, accessingList); } catch (IOException e) { throw new DotStateException(e.getMessage(), e); @@ -396,19 +348,14 @@ private boolean canUseTempFile(final List incomingAccessingList, final D } /** - * Optionally retrieves the Temp Resource. The temp resource will only be returned if: - *
    - *
  1. The {@code accessingList} contains a value that is also contained in the {@code whoCanUse.tmp} file that - * was created when the temp resource was written.
  2. - *
  3. And that the file modification time on the temp resource is newer than the value configured by - * {@link #TEMP_RESOURCE_MAX_AGE_SECONDS}, which defaults to 30m.
  4. - *
- * - * @param accessingList The incoming Access List. - * @param tempFileId The ID of the Temporary File. - * - * @return The optional with the {@link DotTempFile} if the Access List matched the existing one. Otherwise, an empty - * optional will be returned. + * Optionally retreives the Temp Resource. The temp resource will only be returned if 1) the + * accessingList contains a value that is also contained whoCanUse.tmp file that was created when + * the temp resource was written and 2) that the file modification time on the temp resource is + * newer than the value configured by TEMP_RESOURCE_MAX_AGE_SECONDS, which defaults to 30m. + * + * @param accessingList + * @param tempFileId + * @return */ public Optional getTempFile(final List accessingList, final String tempFileId) { Optional tempFile = getTempFile(tempFileId); @@ -419,18 +366,15 @@ public Optional getTempFile(final List accessingList, final } /** - * Optionally retrieves the Temp Resource using the request. The temp resource will only be returned if: - *
    - *
  1. The fingerprint or sessionId is contained in the {@code whoCanUse.tmp} file that was created when the temp - * resource was written.
  2. - *
  3. And that the file modification time on the temp resource is newer than the value configured by - * {@link #TEMP_RESOURCE_MAX_AGE_SECONDS}, which defaults to 30m.
  4. - *
- * - * @param request The current instance of the {@link HttpServletRequest}. - * @param tempFileId The ID of the Temporary File. - * - * @return The optional with the {@link DotTempFile} + * Optionally retreives the Temp Resource using the request. The temp + * resource will only be returned if 1) the fingerprint or sessionId is contained in the whoCanUse.tmp + * file that was created when the temp resource was written and 2) that the file modification time + * on the temp resource is newer than the value configured by TEMP_RESOURCE_MAX_AGE_SECONDS, which + * defaults to 30m. + * + * @param request + * @param tempFileId + * @return */ public Optional getTempFile(final HttpServletRequest request, final String tempFileId) { final String anon = Try.of(() -> APILocator.getUserAPI().getAnonymousUser().getUserId()).getOrElse("anonymous"); @@ -453,11 +397,10 @@ public Optional getTempFile(final HttpServletRequest request, final } /** - * Checks whether the specified Temporary File ID exists or not. - * - * @param tempFileId The ID of the Temporary File. - * - * @return If the Temporary File exists, returns {@code true}. + * returns if a temp resource exits + * + * @param tempFileId + * @return */ public boolean isTempResource(final String tempFileId) { return getTempFile(tempFileId).isPresent(); @@ -473,17 +416,10 @@ public boolean accept(final File pathname) { ; } }; - - /** - * Generates a String representing the fingerprint of the specified {@link HttpServletRequest}. It takes a specified - * set of properties from the request and generates a unique identifier for the request. - * - * @param request The current instance of the {@link HttpServletRequest}. - * - * @return The request's fingerprint. - */ + public String getRequestFingerprint(final HttpServletRequest request) { - final List uniqList = new ArrayList<>(); + + final List uniqList = new ArrayList(); uniqList.add(request.getHeader("User-Agent")); uniqList.add(request.getHeader("Host")); uniqList.add(request.getHeader("Accept-Language")); @@ -504,8 +440,10 @@ public String getRequestFingerprint(final HttpServletRequest request) { } final String fingerPrint = String.join(" , ", uniqList); - Logger.debug(this.getClass(), "Unique browser fingerprint: " + fingerPrint); + Logger.info(this.getClass(), "Unique browser fingerprint: " + fingerPrint); return Encryptor.digest(fingerPrint); + + } /** @@ -526,31 +464,4 @@ public Optional getTempResourceId(final File file){ return Optional.empty(); } - /** - * Writes the specified content in the form of an Input Stream to the specified Temporary File. - * - * @param request The current instance of the {@link HttpServletRequest}. - * @param dotTempFile The {@link DotTempFile} whose content will be overwritten. - * @param inputStream The new file content as an {@link InputStream}. - */ - private void writeFile(final HttpServletRequest request, final DotTempFile dotTempFile, final InputStream inputStream) { - final File tempFile = dotTempFile.file; - final long maxLength = this.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); - } - } 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); - } - } - } diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileResource.java index 22793d24bec5..1bd8fd7ab449 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/temp/TempFileResource.java @@ -2,27 +2,37 @@ 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.FileUtil; 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; @@ -30,20 +40,11 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -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.*; 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; @@ -51,23 +52,16 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import java.util.Map; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.Future; -/** - * This REST Endpoint allows Users to interact with the Temp File API in dotCMS. Temporary Files will live in the system - * for a specified amount of time -- 30 minutes by default. After that they won't be accessible anymore.. - * - * @author Will Ezell - * @since Jul 8th, 2019 - */ @Path("/v1/temp") public class TempFileResource { - public static final String MAX_FILE_LENGTH_PARAM ="maxFileLength"; + public final static String MAX_FILE_LENGTH_PARAM ="maxFileLength"; private final TempFileAPI tempApi; /** @@ -83,17 +77,6 @@ public TempFileResource() { this.tempApi = tempApi; } - /** - * Uploads a binary file to dotCMS via the Temp File API. - * - * @param request The current instance of the {@link HttpServletRequest}. - * @param response The current instance of the {@link HttpServletResponse}. - * @param maxFileLengthString The maximum allowed size for the uploaded file. If not specified, the dotCMS default - * value will be used instead. - * @param body The {@link FormDataMultiPart} object containing the file. - * - * @return A JSON response including important information related to the recently uploaded Temporary File. - */ @POST @JSONP @NoCache @@ -103,14 +86,26 @@ public final Response uploadTempResourceMulti(@Context final HttpServletRequest @Context final HttpServletResponse response, @DefaultValue("-1") @QueryParam(MAX_FILE_LENGTH_PARAM) final String maxFileLengthString, // this is being used later final FormDataMultiPart body) { - this.checkEndpointAccess(request, response); + + verifyTempResourceEnabled(); + + final boolean allowAnonToUseTempFiles = Config + .getBooleanProperty(TempFileAPI.TEMP_RESOURCE_ALLOW_ANONYMOUS, true); + + new WebResource.InitBuilder(request, response) + .requiredAnonAccess(AnonymousAccess.WRITE) + .rejectWhenNoUser(!allowAnonToUseTempFiles) + .init(); + + if (!new SecurityUtils().validateReferer(request)) { + + throw new BadRequestException("Invalid Origin or referer"); + } + return Response.ok(new MultipleBinaryStreamingOutput(body, request)) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON).build(); } - /** - * Streaming Output class used for saving one or more binary files as Temporary Files. - */ protected class MultipleBinaryStreamingOutput implements StreamingOutput { private final FormDataMultiPart body; @@ -118,210 +113,115 @@ protected class MultipleBinaryStreamingOutput implements StreamingOutput { private MultipleBinaryStreamingOutput(final FormDataMultiPart body, final HttpServletRequest request) { + this.body = body; this.request = request; } @Override public void write(final OutputStream output) throws IOException, WebApplicationException { + final ObjectMapper objectMapper = DotObjectMapperProvider.getInstance().getDefaultObjectMapper(); - saveMultipleBinaryFiles(body, request, output, objectMapper); + TempFileResource.this.saveMultipleBinary(body, request, output, objectMapper); } + } - /** - * Saves the binary file or files that are being submitted by the user. A Completion Service is used to improve the - * time it takes to save every Temporary File. - * - * @param body The {@link FormDataMultiPart} containing the file or files that will be saved. - * @param request The current instance of the {@link HttpServletRequest}. - * @param outputStream The streaming output of the response. - * @param objectMapper The {@link ObjectMapper} that transforms the response into a JSON object. - */ - private void saveMultipleBinaryFiles(final FormDataMultiPart body, final HttpServletRequest request, - final OutputStream outputStream, final ObjectMapper objectMapper) { - final CompletionService completionService = createCompletionService(2, 5, 100000); - final List> futures = new ArrayList<>(); - final HttpServletRequest statelessRequest = RequestUtil.INSTANCE.createStatelessRequest(request); - int index = 1; - for (final BodyPart part : body.getBodyParts()) { - // this triggers the save - final int futureIndex = index; - final Future future = completionService.submit(() -> { - - try { - final InputStream in = (part.getEntity() instanceof InputStream) ? (InputStream) part.getEntity() : - Try.of(() -> part.getEntityAs(InputStream.class)).getOrNull(); - final ContentDisposition meta = part.getContentDisposition(); - final Optional errorEntity = validateFileData(in, meta, futureIndex); - return errorEntity.isPresent() ? errorEntity.get() : - tempApi.createTempFile(meta.getFileName(), statelessRequest, in); - } catch (final Exception e) { - final String errorMsg = - "Invalid Binary Part, Message: " + e.getMessage() + ", index: " + futureIndex; - Logger.error(this, errorMsg, e); - return new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), errorMsg, - String.valueOf(futureIndex)); - } + private void saveMultipleBinary(final FormDataMultiPart body, final HttpServletRequest request, + final OutputStream outputStream, final ObjectMapper objectMapper) { - }); - ++index; - futures.add(future); - } - printResponseEntityViewResult(outputStream, objectMapper, completionService, futures); - } + final DotSubmitter dotSubmitter = DotConcurrentFactory.getInstance().getSubmitter("TEMP_API_SUBMITTER", + new DotConcurrentFactory.SubmitterConfigBuilder().poolSize(2).maxPoolSize(5).queueCapacity(100000).build()); + final CompletionService completionService = new ExecutorCompletionService<>(dotSubmitter); + final List> futures = new ArrayList<>(); + final HttpServletRequest statelessRequest = RequestUtil.INSTANCE.createStatelessRequest(request); - /** - * Verifies that the information retrieved for the uploaded binary file is correct and readable. - * - * @param inputStream The content of the binary file as an {@link InputStream} object. - * @param meta The {@link ContentDisposition} containing the file's metadata. - * @param futureIndex The index representing the order in which this file is being processed. - * - * @return An {@link Optional} with the result of the validation. An empty optional means that no errors were - * found. - */ - private Optional validateFileData(final InputStream inputStream, final ContentDisposition meta, - final int futureIndex) { - if (null == inputStream) { - return Optional.of(new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), "Invalid inout" + - " stream Binary Part, index: " + futureIndex, String.valueOf(futureIndex))); - } - if (null == meta) { - return Optional.of(new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), "Invalid metadata Binary Part, index: " + futureIndex, String.valueOf(futureIndex))); - } - final String fileName = meta.getFileName(); - if (UtilMethods.isNotSet(fileName) || fileName.startsWith(StringPool.PERIOD) || fileName.contains("/.")) { - return Optional.of(new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), "Invalid Binary Part, Name: " + fileName + ", index: " + futureIndex, String.valueOf(futureIndex))); - } - return Optional.empty(); - } + int index = 1; + for (final BodyPart part : body.getBodyParts()) { - } + // this triggers the save + final int futureIndex = index; + final Future future = completionService.submit(() -> { - /** - * Updates a specific Temporary File with the provided content as a String. If such a file doesn't exist or has - * expired, a brand new Temporary File with the specified content will be generated instead. - * - * @param request The current instance of the {@link HttpServletRequest}. - * @param response The current instance of the {@link HttpServletResponse}. - * @param tempFileId The ID of the Temporary File that will be updated. - * @param form The {@link PlainTextFileForm} with the required file information. - * - * @return A JSON response including important information related to the recently updated Temporary File. - */ - @PUT - @Path("/id/{tempFileId: .*}") - @JSONP - @NoCache - @Produces("application/octet-stream") - @Consumes(MediaType.APPLICATION_JSON) - public final Response upsertTempResource(@Context final HttpServletRequest request, - @Context final HttpServletResponse response, - @PathParam("tempFileId") final String tempFileId, - final PlainTextFileForm form) { - this.checkEndpointAccess(request, response, false); - final StreamingOutput streamingOutput = output -> { + try { + final InputStream in = (part.getEntity() instanceof InputStream) ? + InputStream.class.cast(part.getEntity()) + : Try.of(() -> part.getEntityAs(InputStream.class)).getOrNull(); - final ObjectMapper objectMapper = DotObjectMapperProvider.getInstance().getDefaultObjectMapper(); - TempFileResource.this.savePlainTextFile(request, objectMapper, output, tempFileId, form); + if (in == null) { - }; - return Response.ok(streamingOutput).header(HttpHeaders.CONTENT_TYPE, - MediaType.APPLICATION_JSON).build(); - } + return new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), "Invalid Binary Part, index: " + futureIndex, + String.valueOf(futureIndex)); + } - /** - * Overwrites a specific Temporary File with new content. - * - * @param request The current instance of the {@link HttpServletRequest}. - * @param objectMapper The {@link ObjectMapper} that transforms the response into a JSON object. - * @param outputStream The streaming output for the response. - * @param tempFileId The ID of the Temporary File that is being overwritten. - * @param form The {@link PlainTextFileForm} object with the information of the submitted file. - */ - private void savePlainTextFile(final HttpServletRequest request, final ObjectMapper objectMapper, final OutputStream outputStream, final String tempFileId, final PlainTextFileForm form) { - final CompletionService completionService = this.createCompletionService(1, 1, 10); - final Future future = this.updateTempFile(completionService, - RequestUtil.INSTANCE.createStatelessRequest(request), tempFileId, form.fileName(), - new ByteArrayInputStream(form.fileContent().getBytes())); - this.printResponseEntityViewResult(outputStream, objectMapper, completionService, List.of(future)); - } + final ContentDisposition meta = part.getContentDisposition(); + if (meta == null) { - /** - * Saves the content of the specified Temporary File. In order to improve the performance, a - * {@link CompletionService} task care of creating a Future task that calls the Temp File API that actually saves - * the new content of the Temporary File. - * - * @param completionService The {@link CompletionService} instance that will update the Temporary File. - * @param statelessRequest A stateless {@link HttpServletRequest} taht is used to call the Temp File API. - * @param tempFileId The ID of the Temporary File that is being overwritten. - * @param fileName The name of the Temporary File. If the file doesn't exist or has expired, this value - * will be used to create the new Temporary File. - * @param in The new content of the file as an {@link InputStream} object. - * - * @return The {@link Future} task that will save the Temporary File. - */ - private Future updateTempFile(final CompletionService completionService, - final HttpServletRequest statelessRequest, - final String tempFileId, final String fileName, final InputStream in) { - return completionService.submit(() -> { - - try { - if (in == null) { - return new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), "Invalid Binary Stream", fileName); + return new ErrorEntity(String.valueOf(HttpServletResponse.SC_BAD_REQUEST), "Invalid Binary Part, index: " + futureIndex, + String.valueOf(futureIndex)); + } + + final String fileName = meta.getFileName(); + 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.createTempFile(fileName, statelessRequest, in); + } catch (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)); } - final String sanitizedFileName = FileUtil.sanitizeFileName(fileName); - return this.tempApi.upsertTempFile(statelessRequest, tempFileId, sanitizedFileName, 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(), fileName); - } + }); - }); + ++index; + futures.add(future); + } + + printResponseEntityViewResult(outputStream, objectMapper, completionService, futures); } - /** - * Returns the basic information of the created Temporary File as a JSON object. The data provided here is very - * useful for the service or user that called this endpoint in order to get a summary of the Temporary File that was - * created. - * - * @param outputStream The streaming output for the response. - * @param objectMapper The {@link ObjectMapper} that transforms the response into a JSON object. - * @param completionService The {@link CompletionService} instance containing the task that saved/updated the - * Temporary File. - * @param futures The list of {@link Future} tasks that were created when saving one or more files. - */ private void printResponseEntityViewResult(final OutputStream outputStream, final ObjectMapper objectMapper, final CompletionService completionService, final List> futures) { + try { + outputStream.write(StringPool.OPEN_CURLY_BRACE.getBytes(StandardCharsets.UTF_8)); ResponseUtil.beginWrapProperty(outputStream, "tempFiles", false); outputStream.write(StringPool.OPEN_BRACKET.getBytes(StandardCharsets.UTF_8)); // now recover the N results for (int i = 0; i < futures.size(); i++) { + try { - Logger.debug(this, "Recovering result " + (i + 1) + " of " + futures.size()); + + Logger.info(this, "Recovering the result " + (i + 1) + " of " + futures.size()); objectMapper.writeValue(outputStream, completionService.take().get()); + if (i < futures.size()-1) { outputStream.write(StringPool.COMMA.getBytes(StandardCharsets.UTF_8)); } - } catch (final InterruptedException e) { - Logger.error(this, "Thread has been interrupted: " + e.getMessage(), e); - Thread.currentThread().interrupt(); - } catch (final ExecutionException | IOException e) { + } catch (InterruptedException | ExecutionException | IOException e) { + Logger.error(this, e.getMessage(), e); } } + outputStream.write(StringPool.CLOSE_BRACKET.getBytes(StandardCharsets.UTF_8)); ResponseUtil.endWrapProperty(outputStream); - } catch (final IOException e) { + } catch (IOException e) { + Logger.error(this, e.getMessage(), e); } } + + @POST @Path("/byUrl") @JSONP @@ -330,8 +230,22 @@ private void printResponseEntityViewResult(final OutputStream outputStream, @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON}) public final Response copyTempFromUrl(@Context final HttpServletRequest request,@Context final HttpServletResponse response, final RemoteUrlForm form) { + try { - this.checkEndpointAccess(request, response); + + verifyTempResourceEnabled(); + + final boolean allowAnonToUseTempFiles = Config + .getBooleanProperty(TempFileAPI.TEMP_RESOURCE_ALLOW_ANONYMOUS, true); + + new WebResource.InitBuilder(request, response) + .requiredAnonAccess(AnonymousAccess.WRITE) + .rejectWhenNoUser(!allowAnonToUseTempFiles) + .init(); + + if (!new SecurityUtils().validateReferer(request)) { + throw new BadRequestException("Invalid Origin or referer"); + } if(!UtilMethods.isSet(form.remoteUrl)){ throw new BadRequestException("No Url passed"); } @@ -339,79 +253,25 @@ public final Response copyTempFromUrl(@Context final HttpServletRequest request, throw new BadRequestException("Invalid url attempted for tempFile : " + form.remoteUrl); } - final List tempFiles = new ArrayList<>(); + final List tempFiles = new ArrayList(); tempFiles.add(tempApi .createTempFileFromUrl(form.fileName, request, new URL(form.remoteUrl), form.urlTimeoutSeconds, form.maxFileLength)); return Response.ok(ImmutableMap.of("tempFiles", tempFiles)).build(); - } catch (final Exception e) { + + } catch (Exception e) { Logger.warnAndDebug(this.getClass(), e); return ResponseUtil.mapExceptionResponse(e); } } - /** - * Utility method that checks that this REST Endpoint can be safely accessed under given circumstances. For - * instance: - *
    - *
  • The Temp File Resources is enabled.
  • - *
  • Whether Anonymous Users can submit Temporary Files or not.
  • - *
  • The origin or referer must be valid for the current HTTP Request.
  • - *
- * - * @param request The current instance of the {@link HttpServletRequest}. - * @param response The current instance of the {@link HttpServletResponse}. - */ - private void checkEndpointAccess(final HttpServletRequest request, final HttpServletResponse response) { - this.checkEndpointAccess(request, response, true); - } - - /** - * Utility method that checks that this REST Endpoint can be safely accessed under given circumstances. For - * instance: - *
    - *
  • The Temp File Resources is enabled.
  • - *
  • The origin or referer must be valid for the current HTTP Request.
  • - *
- * You can explicitly restrict Temp File API access for Anonymous Users as well. - * - * @param request The current instance of the {@link HttpServletRequest}. - * @param response The current instance of the {@link HttpServletResponse}. - * @param allowAnonymousAccess If Anonymous Users are NOT supposed to access a method in this REST Endpoint, set - * this to {@code false}. Otherwise, this method will access the current dotCMS - * configuration to determine if Anonymous Users are able to call a given endpoint - * action or not -- see {@link TempFileAPI#TEMP_RESOURCE_ALLOW_ANONYMOUS}. - */ - private void checkEndpointAccess(final HttpServletRequest request, final HttpServletResponse response, - final boolean allowAnonymousAccess) { + private void verifyTempResourceEnabled(){ if (!Config.getBooleanProperty(TempFileAPI.TEMP_RESOURCE_ENABLED, true)) { - final String message = "Temp Files Resource is not enabled, please change the TEMP_RESOURCE_ENABLED to " + - "true in your properties file"; + final String message = "Temp Files Resource is not enabled, please change the TEMP_RESOURCE_ENABLED to true in your properties file"; Logger.error(this, message); throw new DoesNotExistException(message); } - final boolean allowAnonToUseTempFiles = - allowAnonymousAccess && Config.getBooleanProperty(TempFileAPI.TEMP_RESOURCE_ALLOW_ANONYMOUS, true); - new WebResource.InitBuilder(request, response).requiredAnonAccess(AnonymousAccess.WRITE).rejectWhenNoUser(!allowAnonToUseTempFiles).init(); - if (!new SecurityUtils().validateReferer(request)) { - throw new BadRequestException("Invalid Origin or referer"); - } - } - - /** - * Creates a Completion Service with the specified configuration parameters. - * - * @param poolSize The initial size of the thread pool. - * @param maxPoolSize The maximum number of threads in the pool. - * @param queueCapacity The maximum capacity of the queue that will be processed. - * - * @return The {@link CompletionService} instance. - */ - private CompletionService 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); } } diff --git a/dotCMS/src/main/webapp/html/portlet/ext/contentlet/field/edit_field.jsp b/dotCMS/src/main/webapp/html/portlet/ext/contentlet/field/edit_field.jsp index 4b2e7bddec7c..e2932e8d1fe4 100644 --- a/dotCMS/src/main/webapp/html/portlet/ext/contentlet/field/edit_field.jsp +++ b/dotCMS/src/main/webapp/html/portlet/ext/contentlet/field/edit_field.jsp @@ -820,13 +820,26 @@ - <% if (UtilMethods.isSet(value)) { - final boolean canUserWriteToContentlet = APILocator.getPermissionAPI().doesUserHavePermission(contentlet, PermissionAPI.PERMISSION_WRITE, user); - if (canUserWriteToContentlet && resourceLink.isEditableAsText() && InodeUtils.isSet(binInode)) { %> - <%@ include file="/html/portlet/ext/contentlet/field/edit_file_asset_text_inc.jsp"%> - <% } %> - <% } else { %> - <%@ include file="/html/portlet/ext/contentlet/field/edit_file_asset_text_inc.jsp"%> + + <% + + if(UtilMethods.isSet(value) && UtilMethods.isSet(resourceLink)){ + + boolean canUserWriteToContentlet = APILocator.getPermissionAPI().doesUserHavePermission(contentlet,PermissionAPI.PERMISSION_WRITE, user); + + %> + + <%if(canUserWriteToContentlet){%> + <% if (resourceLink.isEditableAsText()) { %> + <% + if (InodeUtils.isSet(binInode) && canUserWriteToContentlet) { + + %> + <%@ include file="/html/portlet/ext/contentlet/field/edit_file_asset_text_inc.jsp"%> + <% } %> + <% } %> + + <% } %> <% } %> diff --git a/dotCMS/src/main/webapp/html/portlet/ext/contentlet/field/edit_file_asset_text_inc.jsp b/dotCMS/src/main/webapp/html/portlet/ext/contentlet/field/edit_file_asset_text_inc.jsp index 34c9026d69b9..8cc52f635c65 100644 --- a/dotCMS/src/main/webapp/html/portlet/ext/contentlet/field/edit_file_asset_text_inc.jsp +++ b/dotCMS/src/main/webapp/html/portlet/ext/contentlet/field/edit_file_asset_text_inc.jsp @@ -1,16 +1,5 @@ -<%@ page import="java.io.File" %> -<%@ page import="com.dotmarketing.portlets.contentlet.model.Contentlet" %> -<%@ page import="org.apache.commons.io.FileUtils" %> +<%String contents =UtilMethods.htmlifyString(FileUtils.readFileToString(contentlet.getBinary(field.getVelocityVarName()))); %> -<% - final File file = contentlet.getBinary(field.getVelocityVarName()); - String contents = ""; - String fileExtension = "txt"; - if (null != file) { - contents = UtilMethods.htmlifyString(FileUtils.readFileToString(file)); - fileExtension = UtilMethods.getFileExtension(file.getName()); - } -%>