diff --git a/WebContent/WEB-INF/jsp/systemSettings.jsp b/WebContent/WEB-INF/jsp/systemSettings.jsp index 4ed4b1e457..93b596bc86 100644 --- a/WebContent/WEB-INF/jsp/systemSettings.jsp +++ b/WebContent/WEB-INF/jsp/systemSettings.jsp @@ -106,6 +106,9 @@ $set("", settings.); $set("", settings.); + $set("", settings.); + $set("", settings.); + setDisabled($(""), !settings.); setDisabled($(""), !settings. || !settings.); @@ -303,6 +306,8 @@ $get(""), $get(""), $get(""), + $get(""), + $get(""), function(response) { stopImageFader("saveMiscSettingsImg"); if (response.hasMessages) @@ -942,6 +947,20 @@ "/>" type="number" class="formShort"/> + + + + "/>" type="text" class="formShort" style="width: 300px;"/> + + + + + + + "/>" type="text" class="formShort" style="width: 300px;"/> + + + diff --git a/WebContent/WEB-INF/spring-security.xml b/WebContent/WEB-INF/spring-security.xml index a5801b4979..78f3031da1 100644 --- a/WebContent/WEB-INF/spring-security.xml +++ b/WebContent/WEB-INF/spring-security.xml @@ -134,7 +134,6 @@ - @@ -143,7 +142,9 @@ - + + + diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index dec4e5b09a..b5d4a19a22 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -180,6 +180,14 @@ CacheHeaders /resources/* + + CacheHeaders + /uploads/* + + + CacheHeaders + /assets/* + monitoring /* @@ -273,15 +281,15 @@ *.shtm - default + springDispatcher *.png - default + springDispatcher *.jpg - default + springDispatcher *.bmp @@ -313,15 +321,15 @@ *.fla - default + springDispatcher *.gif - default + springDispatcher *.jpeg - default + springDispatcher *.svg diff --git a/scadalts-ui/src/locales/en.json b/scadalts-ui/src/locales/en.json index 912bf53545..32784df538 100644 --- a/scadalts-ui/src/locales/en.json +++ b/scadalts-ui/src/locales/en.json @@ -1059,5 +1059,9 @@ "systemsettings.workitems.reporting.enabled": "Work items reporting enabled", "systemsettings.workitems.reporting.itemspersecond.enabled": "Items per second reporting enabled", "systemsettings.workitems.reporting.itemspersecond.limit": "Items per second reporting limit", - "systemsettings.threads.name.additional.length": "Thread name length" + "systemsettings.threads.name.additional.length": "Thread name length", + "systemsettings.webresource.uploads.path": "Uploaded images save path", + "systemsettings.webresource.graphics.path": "Custom Graphics images path", + "systemsettings.webresource.uploads.path.wrong":"Uploaded images save path must end with 'uploads' or 'uploads{0}' ", + "systemsettings.webresource.graphics.path.wrong": "Custom Graphics images path must end with 'graphics' or 'graphics{0}' " } diff --git a/scadalts-ui/src/views/System/SystemSettings/MiscSettingsComponent.vue b/scadalts-ui/src/views/System/SystemSettings/MiscSettingsComponent.vue index 816f087a31..ec47df397d 100644 --- a/scadalts-ui/src/views/System/SystemSettings/MiscSettingsComponent.vue +++ b/scadalts-ui/src/views/System/SystemSettings/MiscSettingsComponent.vue @@ -93,17 +93,36 @@ dense > + + + + + + diff --git a/scadalts-ui/src/views/System/SystemSettings/index.vue b/scadalts-ui/src/views/System/SystemSettings/index.vue index d98db3349a..ba08aec416 100644 --- a/scadalts-ui/src/views/System/SystemSettings/index.vue +++ b/scadalts-ui/src/views/System/SystemSettings/index.vue @@ -387,7 +387,9 @@ export default { async componentChanged(object) { let idx = this.componentsEdited.findIndex((x) => x.component == object.component); - if (idx == -1 && object.changed) { + if(!object.valid) { + this.componentsEdited = []; + } else if (idx == -1 && object.changed) { this.componentsEdited.push(object); } else if (idx != -1 && !object.changed) { this.componentsEdited.splice(idx, 1); diff --git a/src/br/org/scadabr/vo/exporter/ZIPProjectManager.java b/src/br/org/scadabr/vo/exporter/ZIPProjectManager.java index 1cf930f864..c5256e74a0 100644 --- a/src/br/org/scadabr/vo/exporter/ZIPProjectManager.java +++ b/src/br/org/scadabr/vo/exporter/ZIPProjectManager.java @@ -8,6 +8,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Date; @@ -27,6 +28,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.scada_lts.utils.HttpParameterUtils; +import org.scada_lts.utils.UploadFileUtils; import org.scada_lts.web.mvc.api.json.ExportConfig; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; @@ -40,7 +42,6 @@ import com.serotonin.mango.vo.User; import com.serotonin.mango.web.dwr.EmportDwr; -import static org.scada_lts.utils.PathSecureUtils.getRealPath; import static org.scada_lts.utils.PathSecureUtils.toSecurePath; import static org.scada_lts.utils.UploadFileUtils.*; @@ -91,12 +92,14 @@ public void exportProject(HttpServletRequest request, tempFiles.add(buildJSONFile(JSON_FILE_NAME, includePointValues)); List filesToZip = new ArrayList<>(); - if (includeUploadsFolder) - filesToZip.addAll(getUploadsFolderFiles()); - - if (includeGraphicsFolder) - filesToZip.addAll(getGraphicsFolderFiles()); - + if (includeUploadsFolder) { + for(Path path: UploadFileUtils.getUploadsSystemFilePaths()) + filesToZip.addAll(getUploadsFolderFiles(path)); + } + if (includeGraphicsFolder) { + for(Path path: UploadFileUtils.getGraphicsSystemFilePaths()) + filesToZip.addAll(getGraphicsFolderFiles(path)); + } filesToZip.addAll(tempFiles); ServletOutputStream out = response.getOutputStream(); @@ -157,11 +160,11 @@ public ModelAndView setupToImportProject(HttpServletRequest request, public void importProject() throws Exception { - List graphicsFiles = getGraphicsFiles(); - restoreFiles(graphicsFiles); + List graphicsFiles = getGraphicsFiles(graphicsFolder); + restoreFiles(graphicsFiles, getGraphicsBaseSystemFilePath(getGraphicsSystemFileToWritePath())); - List uploadFiles = getUploadFiles(); - restoreFiles(uploadFiles); + List uploadFiles = getUploadFiles(uploadsFolder); + restoreFiles(uploadFiles, getUploadsBaseSystemFilePath(getUploadsSystemFileToWritePath())); String jsonContent = getJsonContent(); @@ -170,9 +173,7 @@ public void importProject() throws Exception { } - private void restoreFiles(List uploadFiles) { - String appPath = getRealPath(); - + private void restoreFiles(List uploadFiles, Path appPath) { for (ZipEntry zipEntry : uploadFiles) { String entryName = zipEntry.getName(); if(!entryName.isEmpty()) { @@ -208,11 +209,11 @@ private void writeToFile(ZipEntry zipEntry, File file) { } } - private List getUploadFiles() { + private List getUploadFiles(String uploadsFolder) { return filteringUploadFiles(filterZipFiles(uploadsFolder), zipFile); } - private List getGraphicsFiles() { + private List getGraphicsFiles(String graphicsFolder) { return filteringGraphicsFiles(filterZipFiles(graphicsFolder), zipFile); } @@ -241,41 +242,27 @@ private FileToPack buildJSONFile(String packAs, boolean includePointValues) { return file; } - private List getUploadsFolderFiles() { - String uploadFolder = Common.ctx.getServletContext().getRealPath( - FILE_SEPARATOR) - + "uploads"; - + private List getUploadsFolderFiles(Path uploadFolder) { List files = filteringUploadFiles(FileUtil.getFilesOnDirectory(uploadFolder)); - List pack = new ArrayList(); - for (File file : files) { - - String filePartialPath = uploadsFolder + file.getName(); + List pack = new ArrayList<>(); - pack.add(new FileToPack(filePartialPath.substring(0, 7) - + FILE_SEPARATOR + filePartialPath.substring(8), file)); + for (File file : files) { + String[] uploadsBasePath = file.getAbsolutePath().split("uploads"); + String filePartialPath = "uploads" + uploadsBasePath[1]; + pack.add(new FileToPack(filePartialPath, file)); } return pack; } - private List getGraphicsFolderFiles() { - String graphicFolder = Common.ctx.getServletContext().getRealPath( - FILE_SEPARATOR) - + "graphics"; - + private List getGraphicsFolderFiles(Path graphicFolder) { List files = filteringGraphicsFiles(FileUtil.getFilesOnDirectory(graphicFolder)); - List pack = new ArrayList(); + List pack = new ArrayList<>(); for (File file : files) { - - String[] pathDivided = null; - - pathDivided = file.getAbsolutePath().split("graphics"); - - String filePartialPath = "graphics" + pathDivided[1]; - + String[] uploadsBasePath = file.getAbsolutePath().split("graphics"); + String filePartialPath = "graphics" + uploadsBasePath[1]; pack.add(new FileToPack(filePartialPath, file)); } diff --git a/src/br/org/scadabr/vo/exporter/util/FileUtil.java b/src/br/org/scadabr/vo/exporter/util/FileUtil.java index 79a5a5c6aa..5d27bf4ef2 100644 --- a/src/br/org/scadabr/vo/exporter/util/FileUtil.java +++ b/src/br/org/scadabr/vo/exporter/util/FileUtil.java @@ -7,6 +7,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; @@ -65,18 +67,20 @@ public static File createTxtTempFile(String text) { return file; } - public static List getFilesOnDirectory(String directoryName) { - List files = new ArrayList(); + public static List getFilesOnDirectory(Path directoryName) { + List files = new ArrayList<>(); try { - File directory = new File(directoryName); + File directory = directoryName.toFile(); if (directory.exists()) { if (directory.isDirectory()) { String[] filesOnDirectory = directory.list(); - for (String fileName : filesOnDirectory) { - files.addAll(getFilesOnDirectory(directory - .getAbsolutePath() + FILE_SEPARATOR + fileName)); + if(filesOnDirectory != null) { + for (String fileName : filesOnDirectory) { + files.addAll(getFilesOnDirectory(Paths.get(directory + .getAbsolutePath() + FILE_SEPARATOR + fileName))); + } } } else if (directory.isFile()) { files.add(directory); diff --git a/src/com/serotonin/mango/MangoContextListener.java b/src/com/serotonin/mango/MangoContextListener.java index a0538c83ef..d2a55e8e6e 100644 --- a/src/com/serotonin/mango/MangoContextListener.java +++ b/src/com/serotonin/mango/MangoContextListener.java @@ -36,7 +36,6 @@ import com.serotonin.mango.util.BackgroundContext; import com.serotonin.mango.view.DynamicImage; import com.serotonin.mango.view.ImageSet; -import com.serotonin.mango.view.ViewGraphic; import com.serotonin.mango.view.ViewGraphicLoader; import com.serotonin.mango.vo.DataPointVO; import com.serotonin.mango.vo.UserComment; @@ -84,6 +83,8 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import static org.scada_lts.utils.UploadFileUtils.loadGraphics; + public class MangoContextListener implements ServletContextListener { private final Log log = LogFactory.getLog(MangoContextListener.class); @@ -116,8 +117,8 @@ private void initialized(ServletContextEvent evt) { // Create all the stuff we need. constantsInitialize(ctx); freemarkerInitialize(ctx); - imageSetInitialize(ctx); databaseInitialize(ctx); + imageSetInitialize(ctx); highestAlarmLevelServiceInitialize(); dataPointsNameToIdMapping(ctx); @@ -559,16 +560,7 @@ private void imageSetInitialize(ServletContext ctx) { ViewGraphicLoader loader = new ViewGraphicLoader(); List imageSets = new ArrayList(); List dynamicImages = new ArrayList(); - - for (ViewGraphic g : loader.loadViewGraphics(ctx.getRealPath(""))) { - if (g.isImageSet()) - imageSets.add((ImageSet) g); - else if (g.isDynamicImage()) - dynamicImages.add((DynamicImage) g); - else - throw new ShouldNeverHappenException( - "Unknown view graphic type"); - } + loadGraphics(loader, imageSets, dynamicImages); ctx.setAttribute(Common.ContextKeys.IMAGE_SETS, imageSets); ctx.setAttribute(Common.ContextKeys.DYNAMIC_IMAGES, dynamicImages); diff --git a/src/com/serotonin/mango/view/ViewGraphicLoader.java b/src/com/serotonin/mango/view/ViewGraphicLoader.java index 978c34b3d5..c3b182cd7f 100644 --- a/src/com/serotonin/mango/view/ViewGraphicLoader.java +++ b/src/com/serotonin/mango/view/ViewGraphicLoader.java @@ -24,6 +24,7 @@ import java.awt.Toolkit; import java.io.File; import java.io.FileInputStream; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -38,31 +39,32 @@ public class ViewGraphicLoader { private static final Log LOG = LogFactory.getLog(ViewGraphicLoader.class); - private static final String GRAPHICS_PATH = "graphics"; + private Path path; - private String path; - private List viewGraphics; + public List loadViewGraphics(Path path) { + this.path = getGraphicsBaseSystemFilePath(path); + List viewGraphics = new ArrayList<>(); - public List loadViewGraphics(String path) { - this.path = path; - viewGraphics = new ArrayList(); - - File graphicsPath = new File(path, GRAPHICS_PATH); + File graphicsPath = path.toFile(); File[] dirs = graphicsPath.listFiles(); - for (File dir : dirs) { - try { - if (dir.isDirectory()) - loadDirectory(dir, ""); - } - catch (Exception e) { - LOG.warn("Failed to load image set at " + dir, e); + if(dirs != null) { + for (File dir : dirs) { + try { + if (dir.isDirectory()) + viewGraphics.addAll(loadDirectory(dir, "")); + } catch (Exception e) { + LOG.warn("Failed to load image set at " + dir, e); + } } + } else { + LOG.warn("Not exists: " + path); } viewGraphics.sort(Comparator.comparing(ViewGraphic::getName)); return viewGraphics; } - private void loadDirectory(File dir, String baseId) throws Exception { + private List loadDirectory(File dir, String baseId) throws Exception { + List result = new ArrayList<>(); String id = baseId + dir.getName(); String name = id; String typeStr = "imageSet"; @@ -73,7 +75,7 @@ private void loadDirectory(File dir, String baseId) throws Exception { File[] files = dir.listFiles(); Arrays.sort(files); - List imageFiles = new ArrayList(); + List imageUrls = new ArrayList<>(); for (File file : files) { if (file.isDirectory()) loadDirectory(file, id + "."); @@ -95,23 +97,23 @@ else if (isInfoFile(file)) { } else if(isImageBitmap(file)) { // Image file. Subtract the load path from the image path - String imagePath = file.getPath().substring(path.length()); - if(imagePath.startsWith("/") || imagePath.startsWith("\\")) { - imagePath=imagePath.substring(1); + String imageUrl = file.getPath().substring(path.toString().length()); + if(imageUrl.startsWith("/") || imageUrl.startsWith("\\")) { + imageUrl=imageUrl.substring(1); } // Replace Windows-style '\' path separators with '/' - imagePath = imagePath.replaceAll("\\\\", "/"); - imageFiles.add(imagePath); + imageUrl = imageUrl.replaceAll("\\\\", "/"); + imageUrls.add(imageUrl); } else { LOG.warn("File is not supported type: " + file); } } - if (!imageFiles.isEmpty()) { + if (!imageUrls.isEmpty()) { if (width == -1 || height == -1) { - String imagePath = path + File.separator + imageFiles.get(0); - Image image = Toolkit.getDefaultToolkit().getImage(imagePath); + String imageSystemFilePath = path + File.separator + normalizeSeparator(imageUrls.get(0)); + Image image = Toolkit.getDefaultToolkit().getImage(imageSystemFilePath); MediaTracker tracker = new MediaTracker(new Container()); tracker.addImage(image, 0); tracker.waitForID(0); @@ -125,17 +127,18 @@ else if(isImageBitmap(file)) { if (width == -1 || height == -1) throw new Exception("Unable to derive image dimensions"); - String[] imageFileArr = imageFiles.toArray(new String[imageFiles.size()]); + String[] imageUrlsArray = imageUrls.toArray(new String[imageUrls.size()]); ViewGraphic g; if ("imageSet".equals(typeStr)) - g = new ImageSet(id, name, imageFileArr, width, height, textX, textY); + g = new ImageSet(id, name, imageUrlsArray, width, height, textX, textY); else if ("dynamic".equals(typeStr)) - g = new DynamicImage(id, name, imageFileArr[0], width, height, textX, textY); + g = new DynamicImage(id, name, imageUrlsArray[0], width, height, textX, textY); else throw new Exception("Invalid type: " + typeStr); - viewGraphics.add(g); + result.add(g); } + return result; } private String getProperty(Properties props, String key, String defaultValue) { diff --git a/src/com/serotonin/mango/web/dwr/SystemSettingsDwr.java b/src/com/serotonin/mango/web/dwr/SystemSettingsDwr.java index 4802843ad4..aeb9c95690 100644 --- a/src/com/serotonin/mango/web/dwr/SystemSettingsDwr.java +++ b/src/com/serotonin/mango/web/dwr/SystemSettingsDwr.java @@ -43,6 +43,7 @@ import org.scada_lts.mango.service.SystemSettingsService; import org.scada_lts.utils.ColorUtils; import org.scada_lts.web.mvc.api.json.JsonSettingsHttp; +import org.scada_lts.serorepl.utils.StringUtils; import java.io.File; import java.net.SocketTimeoutException; @@ -172,6 +173,10 @@ public Map getSettings() { systemSettingsService.getMiscSettings().getWorkItemsReportingItemsPerSecondLimit()); settings.put(SystemSettingsDAO.THREADS_NAME_ADDITIONAL_LENGTH, systemSettingsService.getMiscSettings().getThreadsNameAdditionalLength()); + settings.put(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, + systemSettingsService.getMiscSettings().getWebResourceGraphicsPath()); + settings.put(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, + systemSettingsService.getMiscSettings().getWebResourceUploadsPath()); return settings; } @@ -323,7 +328,8 @@ public DwrResponseI18n saveMiscSettings(int uiPerformance, String dataPointRtVal boolean viewEnableFullScreen, boolean viewHideShortcutDisableFullScreen, int eventPendingLimit, boolean eventPendingCacheEnabled, boolean workItemsReportingEnabled, boolean workItemsReportingItemsPerSecondEnabled, - int workItemsReportingItemsPerSecondLimit, int threadsNameAdditionalLength) { + int workItemsReportingItemsPerSecondLimit, int threadsNameAdditionalLength, + String webResourceGraphicsPath, String webResourceUploadsPath) { Permissions.ensureAdmin(); SystemSettingsDAO systemSettingsDAO = new SystemSettingsDAO(); DwrResponseI18n response = new DwrResponseI18n(); @@ -365,6 +371,22 @@ public DwrResponseI18n saveMiscSettings(int uiPerformance, String dataPointRtVal systemSettingsDAO.setBooleanValue(SystemSettingsDAO.WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_ENABLED, false); systemSettingsDAO.setIntValue(SystemSettingsDAO.WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_LIMIT, 0); } + if (webResourceGraphicsPath != null && (StringUtils.isEmpty(webResourceGraphicsPath) + || (webResourceGraphicsPath.endsWith("graphics") + || webResourceGraphicsPath.endsWith("graphics" + File.separator)))) { + systemSettingsDAO.setValue(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, webResourceGraphicsPath); + } + else { + response.addContextualMessage(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, "systemsettings.webresource.graphics.path.wrong", File.separator); + } + if (webResourceUploadsPath != null && (StringUtils.isEmpty(webResourceUploadsPath) + || (webResourceUploadsPath.endsWith("uploads") + || webResourceUploadsPath.endsWith("uploads" + File.separator)))) { + systemSettingsDAO.setValue(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, webResourceUploadsPath); + } + else { + response.addContextualMessage(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, "systemsettings.webresource.uploads.path.wrong", File.separator); + } return response; } diff --git a/src/org/scada_lts/dao/SystemSettingsDAO.java b/src/org/scada_lts/dao/SystemSettingsDAO.java index 3d72eaba15..d34044961f 100644 --- a/src/org/scada_lts/dao/SystemSettingsDAO.java +++ b/src/org/scada_lts/dao/SystemSettingsDAO.java @@ -42,6 +42,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import static org.scada_lts.utils.SystemSettingsUtils.WEB_RESOURCE_GRAPHICS_PATH; +import static org.scada_lts.utils.SystemSettingsUtils.WEB_RESOURCE_UPLOADS_PATH; + /** * SystemSettings DAO * @@ -161,6 +164,9 @@ public class SystemSettingsDAO { public static final String WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_ENABLED = "workItemsReportingItemsPerSecondEnabled"; public static final String WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_LIMIT = "workItemsReportingItemsPerSecondLimit"; public static final String THREADS_NAME_ADDITIONAL_LENGTH = "threadsNameAdditionalLength"; + public static final String WEB_RESOURCE_GRAPHICS_PATH = "webResourceGraphicsPath"; + public static final String WEB_RESOURCE_UPLOADS_PATH = "webResourceUploadsPath"; + // @formatter:off private static final String SELECT_SETTING_VALUE_WHERE = "" + "select " @@ -403,6 +409,8 @@ public String getDatabaseSchemaVersion(String key, String defaultValue) { DEFAULT_VALUES.put(WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_ENABLED, SystemSettingsUtils.isWorkItemsReportingItemsPerSecondEnabled()); DEFAULT_VALUES.put(WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_LIMIT, SystemSettingsUtils.getWorkItemsReportingItemsPerSecondLimit()); DEFAULT_VALUES.put(THREADS_NAME_ADDITIONAL_LENGTH, SystemSettingsUtils.getThreadsNameAdditionalLength()); + DEFAULT_VALUES.put(WEB_RESOURCE_GRAPHICS_PATH, SystemSettingsUtils.getWebResourceGraphicsPath()); + DEFAULT_VALUES.put(WEB_RESOURCE_UPLOADS_PATH, SystemSettingsUtils.getWebResourceUploadsPath()); } @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, rollbackFor = SQLException.class) diff --git a/src/org/scada_lts/mango/service/SystemSettingsService.java b/src/org/scada_lts/mango/service/SystemSettingsService.java index fe9d7f370f..8ef54274ab 100644 --- a/src/org/scada_lts/mango/service/SystemSettingsService.java +++ b/src/org/scada_lts/mango/service/SystemSettingsService.java @@ -20,6 +20,7 @@ import org.scada_lts.mango.adapter.MangoEvent; import org.scada_lts.serorepl.utils.DirectoryInfo; import org.scada_lts.serorepl.utils.DirectoryUtils; +import org.scada_lts.serorepl.utils.StringUtils; import org.scada_lts.utils.SystemSettingsUtils; import org.scada_lts.web.mvc.api.AggregateSettings; import org.scada_lts.web.mvc.api.json.*; @@ -139,6 +140,8 @@ public JsonSettingsMisc getMiscSettings() { json.setWorkItemsReportingEnabled(SystemSettingsDAO.getBooleanValue(SystemSettingsDAO.WORK_ITEMS_REPORTING_ENABLED, true)); json.setWorkItemsReportingItemsPerSecondEnabled(SystemSettingsDAO.getBooleanValue(SystemSettingsDAO.WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_ENABLED, true)); json.setWorkItemsReportingItemsPerSecondLimit(SystemSettingsDAO.getIntValue(SystemSettingsDAO.WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_LIMIT, 20000)); + json.setWebResourceGraphicsPath(SystemSettingsDAO.getValue(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH)); + json.setWebResourceUploadsPath(SystemSettingsDAO.getValue(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH)); return json; } @@ -153,6 +156,8 @@ public void saveMiscSettings(JsonSettingsMisc json) { systemSettingsDAO.setBooleanValue(SystemSettingsDAO.WORK_ITEMS_REPORTING_ENABLED, json.isWorkItemsReportingEnabled()); systemSettingsDAO.setBooleanValue(SystemSettingsDAO.WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_ENABLED, json.isWorkItemsReportingItemsPerSecondEnabled()); systemSettingsDAO.setIntValue(SystemSettingsDAO.WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_LIMIT, json.getWorkItemsReportingItemsPerSecondLimit()); + systemSettingsDAO.setValue(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, json.getWebResourceGraphicsPath()); + systemSettingsDAO.setValue(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, json.getWebResourceUploadsPath()); } public SettingsDataRetention getDataRetentionSettings() { @@ -420,6 +425,26 @@ public int getWorkItemsReportingItemsPerSecondLimit() { } } + public String getWebResourceGraphicsPath(){ + String defaultValue = SystemSettingsUtils.getWebResourceGraphicsPath(); + try { + return SystemSettingsDAO.getValue(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, defaultValue); + } catch (Exception e){ + LOG.error(e.getMessage()); + return defaultValue; + } + } + + public String getWebResourceUploadsPath(){ + String defaultValue = SystemSettingsUtils.getWebResourceUploadsPath(); + try { + return SystemSettingsDAO.getValue(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, defaultValue); + } catch (Exception e){ + LOG.error(e.getMessage()); + return defaultValue; + } + } + public int getThreadsNameAdditionalLength() { int defaultValue = SystemSettingsUtils.getThreadsNameAdditionalLength(); try { @@ -432,8 +457,11 @@ public int getThreadsNameAdditionalLength() { private static String getHttpResponseHeaders(JsonSettingsHttp json) { try { + String httpResponseHeaders = json.getHttpResponseHeaders(); + if(StringUtils.isEmpty(httpResponseHeaders)) + return ""; ObjectMapper objectMapper = new ObjectMapper(); - Map headers = SystemSettingsUtils.deserializeMap(json.getHttpResponseHeaders(), objectMapper); + Map headers = SystemSettingsUtils.deserializeMap(httpResponseHeaders, objectMapper); return serializeMap(headers, objectMapper); } catch (Exception e) { throw new RuntimeException(e); diff --git a/src/org/scada_lts/mango/service/ViewService.java b/src/org/scada_lts/mango/service/ViewService.java index 4c583f0d44..373f773d0f 100644 --- a/src/org/scada_lts/mango/service/ViewService.java +++ b/src/org/scada_lts/mango/service/ViewService.java @@ -56,10 +56,9 @@ import static java.util.stream.Collectors.toList; import static org.scada_lts.utils.PathSecureUtils.getPartialPath; -import static org.scada_lts.utils.PathSecureUtils.getRealPath; import static org.scada_lts.utils.PathSecureUtils.toSecurePath; -import static org.scada_lts.utils.UploadFileUtils.filteringUploadFiles; -import static org.scada_lts.utils.UploadFileUtils.isToUploads; +import static org.scada_lts.utils.UploadFileUtils.*; +import static org.scada_lts.utils.StaticImagesUtils.getUploadsSystemFilePath; @Service public class ViewService { @@ -174,7 +173,7 @@ public int saveViewAPI(View view) throws IOException { private void setWidthAndHeight(View view, String backgroundFilename) throws IOException { if (backgroundFilename != null && !backgroundFilename.isEmpty()) { - UploadImage uploadImage = createUploadImage(new File(getBackgroundImagePath(backgroundFilename))); + UploadImage uploadImage = createUploadImage(getUploadsSystemFilePath(Paths.get(backgroundFilename)).toFile()); view.setHeight(uploadImage.getHeight()); view.setWidth(uploadImage.getWidth()); } @@ -210,7 +209,15 @@ public ImageSet getImageSet(String id) { } public List getUploadImages() { - List files = filteringUploadFiles(FileUtil.getFilesOnDirectory(getUploadsPath())); + List files = new ArrayList<>(); + for(Path path: getUploadsSystemFilePaths()) { + files.addAll(getUploadImages(path)); + } + return files; + } + + private List getUploadImages(Path directory) { + List files = filteringUploadFiles(FileUtil.getFilesOnDirectory(directory)); List images = new ArrayList<>(); for (File file : files) { @@ -224,7 +231,7 @@ public Optional uploadBackgroundImage(MultipartFile multipartFile) if(!isToUploads(multipartFile)) { return Optional.empty(); } - Path path = Paths.get(getUploadsPath() + FILE_SEPARATOR + multipartFile.getOriginalFilename()); + Path path = Paths.get(getUploadsSystemFileToWritePath() + FILE_SEPARATOR + multipartFile.getOriginalFilename()); return toSecurePath(path) .flatMap(dist -> transferTo(multipartFile, dist)) .map(this::createUploadImage); @@ -246,14 +253,6 @@ private UploadImage createUploadImage(File file) { return new UploadImage(file.getName(), getPartialPath(file), width, height); } - private String getUploadsPath() { - return getRealPath(FILE_SEPARATOR) + FILE_SEPARATOR + "uploads"; - } - - private String getBackgroundImagePath(String backgroundFilename) { - return getRealPath(FILE_SEPARATOR) + FILE_SEPARATOR + backgroundFilename; - } - public boolean checkUserViewPermissions(User user, View view) { return GetViewsWithAccess.hasViewReadPermission(user, view); } diff --git a/src/org/scada_lts/service/ResourcesService.java b/src/org/scada_lts/service/ResourcesService.java index 228873438b..33d354b1ea 100644 --- a/src/org/scada_lts/service/ResourcesService.java +++ b/src/org/scada_lts/service/ResourcesService.java @@ -17,11 +17,9 @@ */ package org.scada_lts.service; -import com.serotonin.ShouldNeverHappenException; import com.serotonin.mango.Common; import com.serotonin.mango.view.DynamicImage; import com.serotonin.mango.view.ImageSet; -import com.serotonin.mango.view.ViewGraphic; import com.serotonin.mango.view.ViewGraphicLoader; import com.serotonin.mango.web.ContextWrapper; import org.springframework.stereotype.Service; @@ -30,6 +28,8 @@ import java.util.ArrayList; import java.util.List; +import static org.scada_lts.utils.UploadFileUtils.loadGraphics; + /** * Create by at Grzesiek Bylica * @@ -44,16 +44,7 @@ public void refreshImages() { List dynamicImages = new ArrayList(); ServletContext ctx = Common.ctx.getCtx(); - - for (ViewGraphic g : loader.loadViewGraphics(ctx.getRealPath(""))) { - if (g.isImageSet()) - imageSets.add((ImageSet) g); - else if (g.isDynamicImage()) - dynamicImages.add((DynamicImage) g); - else - throw new ShouldNeverHappenException( - "Unknown view graphic type"); - } + loadGraphics(loader, imageSets, dynamicImages); ctx.setAttribute(Common.ContextKeys.IMAGE_SETS, imageSets); ctx.setAttribute(Common.ContextKeys.DYNAMIC_IMAGES, dynamicImages); diff --git a/src/org/scada_lts/svg/SvgProcessingUtils.java b/src/org/scada_lts/svg/SvgProcessingUtils.java index 3603a8a5da..e992a9d446 100644 --- a/src/org/scada_lts/svg/SvgProcessingUtils.java +++ b/src/org/scada_lts/svg/SvgProcessingUtils.java @@ -3,6 +3,7 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.scada_lts.utils.PathSecureUtils; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -24,8 +25,8 @@ import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI; import static org.scada_lts.svg.SvgEnvKeys.*; -import static org.scada_lts.utils.PathSecureUtils.getRealPath; import static org.scada_lts.utils.PathSecureUtils.toSecurePath; +import static org.scada_lts.utils.UploadFileUtils.normalizeSeparator; import static org.scada_lts.utils.xml.XmlUtils.newValidator; final class SvgProcessingUtils { @@ -57,7 +58,7 @@ private static List getSchemas() { .map(Paths::get) .filter(SvgProcessingUtils::isXsdFile) .flatMap(filepath -> - toSecurePath(Paths.get(getRealPath(File.separator) + File.separator + filepath)) + toSecurePath(Paths.get(PathSecureUtils.getAppContextSystemFilePath() + File.separator + normalizeSeparator(filepath.toString()))) .stream()) .collect(Collectors.toList()); } diff --git a/src/org/scada_lts/utils/PathSecureUtils.java b/src/org/scada_lts/utils/PathSecureUtils.java index d838fc83af..8856c4a0d8 100644 --- a/src/org/scada_lts/utils/PathSecureUtils.java +++ b/src/org/scada_lts/utils/PathSecureUtils.java @@ -8,12 +8,16 @@ import java.io.File; import java.net.URLDecoder; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; +import java.util.function.BinaryOperator; import java.util.function.Predicate; +import static org.scada_lts.utils.UploadFileUtils.*; + public final class PathSecureUtils { private PathSecureUtils() {} @@ -21,77 +25,123 @@ private PathSecureUtils() {} private static final Log LOG = LogFactory.getLog(PathSecureUtils.class); public static Optional toSecurePath(Path path) { - return normalizePath(path).map(Path::toFile); + return toSecurePath(path, (originPath, base) -> originPath); + } + + public static Optional toSecurePath(Path path, BinaryOperator reduce) { + return normalizePath(path, reduce).map(Path::toFile); } - public static String getRealPath(String separator) { - if(separator == null) + public static Path getAppContextSystemFilePath(Path folder) { + if(folder == null) throw new NullPointerException(); - return Common.ctx.getServletContext().getRealPath(separator); + String realPath = Common.ctx.getServletContext().getRealPath(normalizeSeparator(folder.toString())); + if(realPath == null) + return Paths.get(""); + return Paths.get(realPath); } - public static String getRealPath() { - return Common.ctx.getServletContext().getRealPath(File.separator); + public static Path getAppContextSystemFilePath() { + return getAppContextSystemFilePath(Paths.get(File.separator)); } public static String getPartialPath(File file) { - return toSecurePath(file.toPath()) - .map(securePath -> securePath.getAbsolutePath().replace(getRealPath(), "")) + return toSecurePath(file.toPath(), (originPath, base) -> Paths.get(normalizeSeparator(originPath.toString().replace(base.toString(), "")))) + .map(File::getPath) .orElse(""); } public static boolean validateFilename(String name) { - String decoded = URLDecoder.decode(name, StandardCharsets.UTF_8); - if(StringUtils.isEmpty(decoded)) + String decoded = decodePath(name); + String ext = FilenameUtils.getExtension(decoded); + if(ext.isEmpty()) return false; - if(!decoded.equals(name)) + String withoutExt = FilenameUtils.removeExtension(decoded); + if(withoutExt.isEmpty()) return false; - if(!validatePath(decoded, path -> true)) + if(decoded.equals(withoutExt)) return false; - String ext = FilenameUtils.getExtension(name); - if(ext.isEmpty()) + if(decoded.contains("..") || decoded.contains("\\")) return false; - String withoutExt = FilenameUtils.removeExtension(name); - if(withoutExt.isEmpty()) + if(decoded.contains("/")) return false; - if(name.equals(withoutExt)) + if(StringUtils.isEmpty(decoded)) return false; - if(name.contains("..") || name.contains("\\")) + if(!decoded.equals(name) && !validateDecoded(name)) return false; - if(name.contains("/")) + if(decoded.length() > 255) return false; - return name.length() < 256; + try { + Paths.get(decoded); + return true; + } catch (Exception ex) { + LOG.warn("Filename is invalid! " + ex.getMessage()); + return false; + } } public static boolean validatePath(String name, Predicate exists) { - String decoded = URLDecoder.decode(name, StandardCharsets.UTF_8); + String decoded = decodePath(name); if(StringUtils.isEmpty(decoded)) return false; - if(!decoded.equals(name)) + String baseName = FilenameUtils.getFullPath(decoded); + if(StringUtils.isEmpty(baseName)) { + if(StringUtils.isEmpty(FilenameUtils.getExtension(name))) { + baseName = name; + } else { + return false; + } + } + + if(!decoded.equals(baseName) && !validateDecoded(baseName)) return false; try { - Path path = Paths.get(decoded).normalize(); + Path path = Paths.get(baseName).normalize(); String normalizedPath = path.toString(); - return exists.test(path) && normalizedPath.equals(decoded); + return exists.test(path) && (baseName.equals(normalizedPath) || baseName.equals(normalizedPath + File.separator)); } catch (Exception ex) { - LOG.error("Path is invalid! " + ex.getMessage()); + LOG.warn("Path is invalid! " + ex.getMessage()); return false; } } - private static Optional normalizePath(Path path) { + private static Optional normalizePath(Path path, BinaryOperator reduce) { if(path == null) { return Optional.empty(); } - String appPath = getRealPath(); - if(appPath == null) { + Path appPath = getAppContextSystemFilePath(); + if(appPath.toString().isEmpty()) { return Optional.empty(); } Path normalizedPath = path.normalize(); if(normalizedPath.startsWith(appPath)) { - return Optional.of(normalizedPath); + return Optional.of(reduce.apply(normalizedPath, appPath)); + } + for(Path uploadsPath: UploadFileUtils.getUploadsSystemFilePaths()) { + if (normalizedPath.startsWith(uploadsPath)) { + return Optional.of(reduce.apply(normalizedPath, getUploadsBaseSystemFilePath(uploadsPath))); + } + } + for(Path graphicsPath: UploadFileUtils.getGraphicsSystemFilePaths()) { + if (normalizedPath.startsWith(graphicsPath)) { + return Optional.of(reduce.apply(normalizedPath, getGraphicsBaseSystemFilePath(graphicsPath))); + } } LOG.warn("Path is invalid!"); return Optional.empty(); } + + private static boolean validateDecoded(String name) { + String withoutWhitespace = name + .replaceAll("\\s", "") + .replace(File.separator, "") + .replace(":", ""); + String withoutWhitespaceEncoded = URLEncoder.encode(withoutWhitespace, StandardCharsets.UTF_8); + String withoutWhitespaceDecoded = URLDecoder.decode(withoutWhitespaceEncoded, StandardCharsets.UTF_8); + return !StringUtils.isEmpty(withoutWhitespaceDecoded) && withoutWhitespaceDecoded.equals(withoutWhitespace); + } + + public static String decodePath(String path) { + return URLDecoder.decode(path, StandardCharsets.UTF_8); + } } diff --git a/src/org/scada_lts/utils/StaticImagesUtils.java b/src/org/scada_lts/utils/StaticImagesUtils.java new file mode 100644 index 0000000000..a366408ef0 --- /dev/null +++ b/src/org/scada_lts/utils/StaticImagesUtils.java @@ -0,0 +1,86 @@ +package org.scada_lts.utils; + +import org.scada_lts.serorepl.utils.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.scada_lts.utils.UploadFileUtils.*; + +public final class StaticImagesUtils { + + private StaticImagesUtils(){} + + public static ResponseEntity getAndSendImage(HttpServletRequest request, HttpServletResponse response) { + File file = getSystemFileByRequest(request); + if(!UploadFileUtils.isToUploads(file)) + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + try { + sendFile(response, file); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + return ResponseEntity.ok().build(); + } + + public static Path getUploadsSystemFilePath(Path fileName) { + for(Path path : getUploadsSystemFilePaths()) { + Path absolutePath = Paths.get(path + getSeparator() + getUploadsBaseSystemFilePath(fileName)); + if(Files.exists(absolutePath)) { + return absolutePath; + } + } + return Paths.get(""); + } + + public static Path getGraphicSystemFilePath(Path fileName) { + for(Path path : getGraphicsSystemFilePaths()) { + Path absolutePath = Paths.get(path + getSeparator() + getGraphicsBaseSystemFilePath(fileName)); + if(Files.exists(absolutePath)) { + return absolutePath; + } + } + return Paths.get(""); + } + + private static void sendFile(HttpServletResponse response, File file) throws IOException { + int bufferSize = 2048; + byte[] data = new byte[bufferSize]; + int len; + try (InputStream inputStream = new FileInputStream(file); + ServletOutputStream output = response.getOutputStream()) { + while ((len = inputStream.read(data, 0, bufferSize)) != -1) { + output.write(data, 0, len); + } + } + } + + private static File getSystemFileByRequest(HttpServletRequest request) { + String url = request.getRequestURI().replace(request.getContextPath(), ""); + Path path = Paths.get(""); + if(url.startsWith("/graphics")) { + path = getGraphicSystemFilePath(Paths.get(url)); + } else if(url.startsWith("/uploads")) { + path = getUploadsSystemFilePath(Paths.get(url)); + } + if(StringUtils.isEmpty(path.toString())) + path = PathSecureUtils.getAppContextSystemFilePath(Paths.get(url)); + return path.toFile(); + } + + private static String getSeparator() { + return File.separator; + } + +} diff --git a/src/org/scada_lts/utils/SystemSettingsUtils.java b/src/org/scada_lts/utils/SystemSettingsUtils.java index 8628ed3a80..eccd214db6 100644 --- a/src/org/scada_lts/utils/SystemSettingsUtils.java +++ b/src/org/scada_lts/utils/SystemSettingsUtils.java @@ -44,6 +44,8 @@ private SystemSettingsUtils() {} public static final String WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_ENABLED_KEY = "workitems.reporting.itemspersecond.enabled"; public static final String WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_LIMIT_KEY = "workitems.reporting.itemspersecond.limit"; public static final String THREADS_NAME_ADDITIONAL_LENGTH_KEY = "threads.name.additional.length"; + public static final String WEB_RESOURCE_GRAPHICS_PATH = "webresource.graphics.path"; + public static final String WEB_RESOURCE_UPLOADS_PATH = "webresource.uploads.path"; private static final org.apache.commons.logging.Log LOG = LogFactory.getLog(SystemSettingsUtils.class); @@ -333,4 +335,22 @@ public static int getThreadsNameAdditionalLength() { return 255; } } + + public static String getWebResourceUploadsPath() { + try { + return ScadaConfig.getInstance().getConf().getProperty(WEB_RESOURCE_UPLOADS_PATH, ""); + } catch (Exception e) { + LOG.error(e.getMessage()); + return ""; + } + } + + public static String getWebResourceGraphicsPath() { + try { + return ScadaConfig.getInstance().getConf().getProperty(WEB_RESOURCE_GRAPHICS_PATH, ""); + } catch (Exception e) { + LOG.error(e.getMessage()); + return ""; + } + } } diff --git a/src/org/scada_lts/utils/UploadFileUtils.java b/src/org/scada_lts/utils/UploadFileUtils.java index 4975fd0e15..53d1b6dbb8 100644 --- a/src/org/scada_lts/utils/UploadFileUtils.java +++ b/src/org/scada_lts/utils/UploadFileUtils.java @@ -1,21 +1,33 @@ package org.scada_lts.utils; +import com.serotonin.ShouldNeverHappenException; +import com.serotonin.mango.view.DynamicImage; +import com.serotonin.mango.view.ImageSet; +import com.serotonin.mango.view.ViewGraphic; +import com.serotonin.mango.view.ViewGraphicLoader; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.scada_lts.mango.service.SystemSettingsService; +import org.scada_lts.serorepl.utils.StringUtils; import org.scada_lts.utils.security.*; import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import java.io.*; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import static org.scada_lts.svg.SvgUtils.isSvg; +import static org.scada_lts.utils.PathSecureUtils.decodePath; +import static org.scada_lts.utils.PathSecureUtils.getAppContextSystemFilePath; import static org.scada_lts.utils.ScadaMimeTypeUtils.*; import static org.scada_lts.utils.xml.XmlUtils.isXml; @@ -25,6 +37,9 @@ public final class UploadFileUtils { private static final String INFO_FILE_NAME = "info.txt"; private static final String IGNORE_THUMBS = "Thumbs.db"; + private static final String GRAPHICS_PATH = File.separator + "graphics"; + private static final String UPLOADS_PATH = File.separator + "uploads"; + private UploadFileUtils() {} public static List filteringGraphicsFiles(List zipEntries, ZipFile zipFile) { @@ -49,13 +64,13 @@ public static boolean isZip(MultipartFile multipartFile) { try { safeMultipartFile = SafeMultipartFile.safe(multipartFile); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } return isZipMimeType(Paths.get(safeMultipartFile.getOriginalFilename())) && !isXml(safeMultipartFile) && !isImageBitmap(safeMultipartFile); } catch (Exception ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } } @@ -67,7 +82,7 @@ public static boolean isToUploads(MultipartFile file) { try { safeMultipartFile = SafeMultipartFile.safe(file); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } String fileName = safeMultipartFile.getOriginalFilename(); @@ -86,7 +101,7 @@ public static boolean isToUploads(File file) { try { safeFile = SafeFile.safe(file); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } if(isThumbsFile(safeFile)) @@ -102,7 +117,7 @@ public static boolean isToGraphics(File file) { try { safeFile = SafeFile.safe(file); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } if(isThumbsFile(safeFile)) @@ -117,7 +132,7 @@ public static boolean isThumbsFile(File file) { try { safeFile = SafeFile.safe(file); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } return isThumbsFile(safeFile); @@ -128,7 +143,7 @@ public static boolean isInfoFile(File file) { try { safeFile = SafeFile.safe(file); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } return isInfoFile(safeFile); @@ -139,12 +154,54 @@ public static boolean isImageBitmap(File file) { try { safeFile = SafeFile.safe(file); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } return isImageBitmap(safeFile); } + public static void loadGraphics(ViewGraphicLoader loader, List imageSets, List dynamicImages) { + for(Path path: getGraphicsSystemFilePaths()) { + loadGraphics(loader, imageSets, dynamicImages, path); + } + } + + public static List getUploadsSystemFilePaths() { + SystemSettingsService systemSettingsService = new SystemSettingsService(); + return getImageSystemFilePaths(systemSettingsService::getWebResourceUploadsPath, UPLOADS_PATH); + } + + public static List getGraphicsSystemFilePaths() { + SystemSettingsService systemSettingsService = new SystemSettingsService(); + return getImageSystemFilePaths(systemSettingsService::getWebResourceGraphicsPath, GRAPHICS_PATH); + } + + public static Path getUploadsSystemFileToWritePath() { + SystemSettingsService systemSettingsService = new SystemSettingsService(); + return getImageSystemFileToWritePath(systemSettingsService::getWebResourceUploadsPath, UPLOADS_PATH); + } + + public static Path getGraphicsSystemFileToWritePath() { + SystemSettingsService systemSettingsService = new SystemSettingsService(); + return getImageSystemFileToWritePath(systemSettingsService::getWebResourceGraphicsPath, GRAPHICS_PATH); + } + + public static Path getGraphicsBaseSystemFilePath(Path path) { + String decoded = decodePath(path.toString()); + if (decoded.startsWith(GRAPHICS_PATH) || decoded.endsWith(GRAPHICS_PATH)) { + return Paths.get(decoded.replace(GRAPHICS_PATH, "")); + } + return Paths.get(decoded); + } + + public static Path getUploadsBaseSystemFilePath(Path path) { + String decoded = decodePath(path.toString()); + if (decoded.startsWith(UPLOADS_PATH) || decoded.endsWith(UPLOADS_PATH)) { + return Paths.get(decoded.replace(UPLOADS_PATH, "")); + } + return Paths.get(decoded); + } + private static boolean isThumbsFile(SafeFile file) { return isThumbsFile(file.getName()); } @@ -156,9 +213,6 @@ private static boolean isInfoFile(SafeFile file) { private static boolean isImageBitmap(SafeFile file) { try { return ImageIO.read(file.toFile()) != null; - } catch (FileNotFoundException ex) { - LOG.error(ex.getMessage()); - return false; } catch (Exception ex) { LOG.warn(ex.getMessage()); return false; @@ -172,14 +226,14 @@ private static boolean isToGraphics(ZipFile zipFile, ZipEntry entry) { try { safeZipFile = SafeZipFile.safe(zipFile); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } SafeZipEntry safeZipEntry; try { safeZipEntry = SafeZipEntry.safe(entry); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } return isToGraphics(safeZipFile, safeZipEntry); @@ -201,14 +255,14 @@ private static boolean isToUploads(ZipFile zipFile, ZipEntry entry) { try { safeZipFile = SafeZipFile.safe(zipFile); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage(), ex); + LOG.warn(ex.getMessage(), ex); return false; } SafeZipEntry safeZipEntry; try { safeZipEntry = SafeZipEntry.safe(entry); } catch (FileNotSafeException ex) { - LOG.error(ex.getMessage()); + LOG.warn(ex.getMessage()); return false; } return isToUploads(safeZipFile, safeZipEntry); @@ -226,9 +280,6 @@ private static boolean isToUploads(SafeZipFile safeZipFile, SafeZipEntry safeZip private static boolean isImageBitmap(SafeMultipartFile file) { try(InputStream inputStream = new ByteArrayInputStream(file.getBytes())) { return ImageIO.read(inputStream) != null; - } catch (FileNotFoundException ex) { - LOG.error(ex.getMessage()); - return false; } catch (Exception ex) { LOG.warn(ex.getMessage()); return false; @@ -238,9 +289,6 @@ private static boolean isImageBitmap(SafeMultipartFile file) { private static boolean isImageBitmap(SafeZipFile zipFile, SafeZipEntry entry) { try(InputStream inputStream = zipFile.getInputStream(entry)) { return ImageIO.read(inputStream) != null; - } catch (FileNotFoundException ex) { - LOG.error(ex.getMessage()); - return false; } catch (Exception ex) { LOG.warn(ex.getMessage()); return false; @@ -260,4 +308,76 @@ private static List filter(List files, Predicate predicate) { .filter(predicate) .collect(Collectors.toList()); } + + private static List getImageSystemFilePaths(Supplier getLocalPath, String folder) { + List paths = new ArrayList<>(); + String normalizeFolder = normalizeSeparator(decodePath(folder)); + String normalizePath = normalizeSeparator(decodePath(getLocalPath.get())); + if (!StringUtils.isEmpty(normalizePath) && (normalizePath.endsWith(normalizeFolder) + || normalizePath.endsWith(normalizeFolder + File.separator))) { + Path path = getAbsoluteResourcePath(normalizePath); + createIfNotExists(path); + paths.add(path); + } + Path path = getAppContextSystemFilePath(Paths.get(normalizeFolder)); + createIfNotExists(path); + paths.add(path); + return paths; + } + + private static Path getImageSystemFileToWritePath(Supplier getLocalPath, String folder) { + Path path; + String normalizedFolder = normalizeSeparator(decodePath(folder)); + String normalizedPath = normalizeSeparator(decodePath(getLocalPath.get())); + if (!StringUtils.isEmpty(normalizedPath) && (normalizedPath.endsWith(normalizedFolder) + || normalizedPath.endsWith(normalizedFolder + File.separator))) { + path = getAbsoluteResourcePath(normalizedPath); + } else { + path = getAppContextSystemFilePath(Paths.get(normalizedFolder)); + } + createIfNotExists(path); + return path; + } + + private static void createIfNotExists(Path path) { + if(!Files.exists(path)) { + path.toFile().mkdirs(); + } + } + + private static void loadGraphics(ViewGraphicLoader loader, + List imageSets, + List dynamicImages, + Path path) { + for (ViewGraphic graphic : loader.loadViewGraphics(path)) { + if (graphic.isImageSet()) + imageSets.add((ImageSet) graphic); + else if (graphic.isDynamicImage()) + dynamicImages.add((DynamicImage) graphic); + else + throw new ShouldNeverHappenException( + "Unknown view graphic type"); + } + } + + private static Path getAbsoluteResourcePath(String path) { + Path normalizedPath = normalizePath(path); + if (!path.equals(normalizedPath.toString())) { + return Path.of(basePath() + File.separator + normalizeSeparator(path)); + } else { + return normalizedPath; + } + } + + private static Path normalizePath(String path) { + return Paths.get(path).toFile().getAbsoluteFile().toPath().normalize(); + } + + public static String normalizeSeparator(String path) { + return path.replace("/", File.separator).replace("\\", File.separator); + } + + private static Path basePath() { + return new File("../").getAbsoluteFile().toPath().normalize(); + } } diff --git a/src/org/scada_lts/utils/security/SafeFile.java b/src/org/scada_lts/utils/security/SafeFile.java index 7125b07560..fcad6a06ba 100644 --- a/src/org/scada_lts/utils/security/SafeFile.java +++ b/src/org/scada_lts/utils/security/SafeFile.java @@ -4,8 +4,7 @@ import java.nio.file.Files; import java.nio.file.Path; -import static org.scada_lts.utils.PathSecureUtils.validateFilename; -import static org.scada_lts.utils.PathSecureUtils.validatePath; +import static org.scada_lts.utils.PathSecureUtils.*; public class SafeFile { @@ -16,7 +15,7 @@ private SafeFile(File file) { } public static SafeFile safe(File file) throws FileNotSafeException { - SafeFile safeFile = new SafeFile(file); + SafeFile safeFile = new SafeFile(new File(decodePath(file.getAbsolutePath()))); if(safeFile.validate()) return safeFile; throw new FileNotSafeException(file.getName()); @@ -31,15 +30,17 @@ public File toFile() { } public Path toPath() { - return file.toPath(); + if(validate()) + return file.toPath(); + return Path.of(""); } private boolean validate() { String absolutePath = file.getAbsolutePath(); String path = file.getPath(); String name = file.getName(); - return validatePath(absolutePath, Files::exists) + return validateFilename(name) && validatePath(path, Files::exists) - && validateFilename(name); + && validatePath(absolutePath, Files::exists); } } diff --git a/src/org/scada_lts/utils/security/SafeMultipartFile.java b/src/org/scada_lts/utils/security/SafeMultipartFile.java index 0dd46e3f25..00b0f661b9 100644 --- a/src/org/scada_lts/utils/security/SafeMultipartFile.java +++ b/src/org/scada_lts/utils/security/SafeMultipartFile.java @@ -39,9 +39,8 @@ public InputStream getInputStream() throws IOException { } private boolean validate() { - String name = multipartFile.getName(); String originalName = multipartFile.getOriginalFilename(); - return validatePath(name, path -> true) && validateFilename(originalName); + return validateFilename(originalName); } } diff --git a/src/org/scada_lts/utils/security/SafeZipFileUtils.java b/src/org/scada_lts/utils/security/SafeZipFileUtils.java index 274dec6688..3d79757a19 100644 --- a/src/org/scada_lts/utils/security/SafeZipFileUtils.java +++ b/src/org/scada_lts/utils/security/SafeZipFileUtils.java @@ -1,5 +1,7 @@ package org.scada_lts.utils.security; +import java.io.File; + import static org.scada_lts.utils.PathSecureUtils.validateFilename; import static org.scada_lts.utils.PathSecureUtils.validatePath; @@ -8,8 +10,8 @@ public final class SafeZipFileUtils { private SafeZipFileUtils() {} public static boolean valid(String absolutePath) { - if(absolutePath.contains("/") || absolutePath.contains("\\")) { - int index = absolutePath.lastIndexOf("/"); + if(absolutePath.contains(File.separator)) { + int index = absolutePath.lastIndexOf(File.separator); String path = absolutePath.substring(0, index); String name = absolutePath.length() < index + 1 ? "" : absolutePath.substring(index + 1); return validatePath(absolutePath, a -> true) diff --git a/src/org/scada_lts/web/mvc/api/ExportConfigAPI.java b/src/org/scada_lts/web/mvc/api/ExportConfigAPI.java index 1e6b65c04e..25cf391f6e 100644 --- a/src/org/scada_lts/web/mvc/api/ExportConfigAPI.java +++ b/src/org/scada_lts/web/mvc/api/ExportConfigAPI.java @@ -20,7 +20,7 @@ public class ExportConfigAPI { private static final Log LOG = LogFactory.getLog(ExportConfigAPI.class); - @RequestMapping(value = "/", method = RequestMethod.GET, + @RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public ResponseEntity export(@RequestParam(required = false) String projectName, @RequestParam(required = false) boolean includeGraphicalFolder, diff --git a/src/org/scada_lts/web/mvc/api/json/JsonSettingsMisc.java b/src/org/scada_lts/web/mvc/api/json/JsonSettingsMisc.java index cc3ab851cb..c1f013ea23 100644 --- a/src/org/scada_lts/web/mvc/api/json/JsonSettingsMisc.java +++ b/src/org/scada_lts/web/mvc/api/json/JsonSettingsMisc.java @@ -14,16 +14,20 @@ public class JsonSettingsMisc implements Serializable { public boolean workItemsReportingItemsPerSecondEnabled; public int workItemsReportingItemsPerSecondLimit; public int threadsNameAdditionalLength; + public String webResourceGraphicsPath; + public String webResourceUploadsPath; public JsonSettingsMisc() {} - public JsonSettingsMisc(int uiPerformance, String dataPointRuntimeValueSynchronized, boolean enableFullScreen, boolean hideShortcutDisableFullScreen, int eventPendingLimit, boolean eventPendingCacheEnabled) { + public JsonSettingsMisc(int uiPerformance, String dataPointRuntimeValueSynchronized, boolean enableFullScreen, boolean hideShortcutDisableFullScreen, int eventPendingLimit, boolean eventPendingCacheEnabled, String webResourceGraphicsPath, String webResourceUploadsPath) { this.uiPerformance = uiPerformance; this.dataPointRuntimeValueSynchronized = dataPointRuntimeValueSynchronized; this.enableFullScreen = enableFullScreen; this.hideShortcutDisableFullScreen = hideShortcutDisableFullScreen; this.eventPendingLimit = eventPendingLimit; this.eventPendingCacheEnabled = eventPendingCacheEnabled; + this.webResourceGraphicsPath = webResourceGraphicsPath; + this.webResourceUploadsPath = webResourceUploadsPath; } public int getUiPerformance() { @@ -105,4 +109,20 @@ public int getThreadsNameAdditionalLength() { public void setThreadsNameAdditionalLength(int threadsNameAdditionalLength) { this.threadsNameAdditionalLength = threadsNameAdditionalLength; } + + public String getWebResourceGraphicsPath() { + return webResourceGraphicsPath; + } + + public void setWebResourceGraphicsPath(String webResourceGraphicsPath) { + this.webResourceGraphicsPath = webResourceGraphicsPath; + } + + public String getWebResourceUploadsPath() { + return webResourceUploadsPath; + } + + public void setWebResourceUploadsPath(String webResourceUploadsPath) { + this.webResourceUploadsPath = webResourceUploadsPath; + } } diff --git a/src/org/scada_lts/web/mvc/controller/StaticImagesController.java b/src/org/scada_lts/web/mvc/controller/StaticImagesController.java new file mode 100644 index 0000000000..fe13941a4e --- /dev/null +++ b/src/org/scada_lts/web/mvc/controller/StaticImagesController.java @@ -0,0 +1,49 @@ +package org.scada_lts.web.mvc.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +import static org.scada_lts.utils.StaticImagesUtils.getAndSendImage; + +@RestController +public class StaticImagesController { + + @GetMapping(value = "/graphics/**") + public ResponseEntity graphics(HttpServletRequest request, HttpServletResponse response) { + return getAndSendImage(request, response); + } + + @GetMapping(value = "/uploads/**") + public ResponseEntity uploads(HttpServletRequest request, HttpServletResponse response) { + return getAndSendImage(request, response); + } + + @GetMapping(value = "/images/**") + public ResponseEntity images(HttpServletRequest request, HttpServletResponse response) { + return getAndSendImage(request, response); + } + + @GetMapping(value = "/img/**") + public ResponseEntity img(HttpServletRequest request, HttpServletResponse response) { + return getAndSendImage(request, response); + } + + @GetMapping(value = "/assets/**") + public ResponseEntity assets(HttpServletRequest request, HttpServletResponse response) { + return getAndSendImage(request, response); + } + + @GetMapping(value = "/resources/**") + public ResponseEntity resources(HttpServletRequest request, HttpServletResponse response) { + return getAndSendImage(request, response); + } + + @GetMapping(value = "/static/**") + public ResponseEntity stat(HttpServletRequest request, HttpServletResponse response) { + return getAndSendImage(request, response); + } +} \ No newline at end of file diff --git a/src/org/scada_lts/web/mvc/controller/ViewEditController.java b/src/org/scada_lts/web/mvc/controller/ViewEditController.java index 35c4bef727..b0a797c360 100644 --- a/src/org/scada_lts/web/mvc/controller/ViewEditController.java +++ b/src/org/scada_lts/web/mvc/controller/ViewEditController.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; @@ -53,6 +54,7 @@ import static com.serotonin.mango.util.ViewControllerUtils.*; import static org.scada_lts.utils.PathSecureUtils.toSecurePath; +import static org.scada_lts.utils.UploadFileUtils.getUploadsSystemFileToWritePath; import static org.scada_lts.utils.UploadFileUtils.isToUploads; @@ -242,11 +244,11 @@ private void upload(HttpServletRequest request, ViewEditForm form, MultipartFile byte[] bytes = file.getBytes(); if (bytes != null && bytes.length > 0) { // Create the path to the upload directory. - String path = request.getSession().getServletContext().getRealPath(uploadDirectory); - LOG.info("ViewEditController:uploadFile: realpath="+path); + Path path = getUploadsSystemFileToWritePath(); + LOG.info("ViewEditController:uploadFile: realpath=" + path); // Make sure the directory exists. - File dir = new File(path); + File dir = path.toFile(); dir.mkdirs(); String fileName = file.getOriginalFilename(); diff --git a/test-resources/svg/jpg File.jpg b/test-resources/svg/jpg File.jpg new file mode 100644 index 0000000000..d95ce982e8 Binary files /dev/null and b/test-resources/svg/jpg File.jpg differ diff --git a/test/org/scada_lts/utils/UploadBackgroundFileUtilsTest.java b/test/org/scada_lts/utils/UploadBackgroundFileUtilsTest.java index 38f01e642f..0e405dd989 100644 --- a/test/org/scada_lts/utils/UploadBackgroundFileUtilsTest.java +++ b/test/org/scada_lts/utils/UploadBackgroundFileUtilsTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.scada_lts.utils.PathSecureUtils.decodePath; @RunWith(Parameterized.class) public class UploadBackgroundFileUtilsTest { @@ -85,6 +86,8 @@ public static List data() { datas.add(new Object[] {"classFile.class.jpg", false}); datas.add(new Object[] {"txt" + File.separator + "info.txt", false}); + datas.add(new Object[] { "jpg File.jpg", true}); + datas.add(new Object[] { "jpg%20File.jpg", true}); return datas; } @@ -133,9 +136,10 @@ public void when_isToUploads_MultipartFile() { } private static MultipartFile toMultipartFile(File file) { - try(FileInputStream input = new FileInputStream(file)) { + File to = new File(decodePath(file.getAbsolutePath())); + try(FileInputStream input = new FileInputStream(to)) { return new MockMultipartFile("fileUpload", file.getName(), - Files.probeContentType(file.toPath()), IOUtils.toByteArray(input)); + Files.probeContentType(to.toPath()), IOUtils.toByteArray(input)); } catch (Exception ex) { return null; } diff --git a/test/org/scada_lts/utils/UploadGraphicsFileUtilsTest.java b/test/org/scada_lts/utils/UploadGraphicsFileUtilsTest.java index b94cdfcfb0..79b82218a9 100644 --- a/test/org/scada_lts/utils/UploadGraphicsFileUtilsTest.java +++ b/test/org/scada_lts/utils/UploadGraphicsFileUtilsTest.java @@ -74,6 +74,8 @@ public static List data() { datas.add(new Object[] {"classFile.class.jpg", false}); datas.add(new Object[] {"txt" + File.separator + "info.txt", false}); + datas.add(new Object[] { "jpg File.jpg", true}); + datas.add(new Object[] { "jpg%20File.jpg", true}); return datas; } diff --git a/webapp-resources/env.properties b/webapp-resources/env.properties index d639defaa0..d26020184e 100644 --- a/webapp-resources/env.properties +++ b/webapp-resources/env.properties @@ -141,4 +141,7 @@ event.pending.update.limit=5001 threads.name.additional.length=255 workitems.reporting.enabled=false workitems.reporting.itemspersecond.enabled=false -workitems.reporting.itemspersecond.limit=20000 \ No newline at end of file +workitems.reporting.itemspersecond.limit=20000 + +webresource.uploads.path=static/uploads +webresource.graphics.path=static/graphics \ No newline at end of file diff --git a/webapp-resources/messages_de.properties b/webapp-resources/messages_de.properties index 2699d9c72c..4cab27f154 100644 --- a/webapp-resources/messages_de.properties +++ b/webapp-resources/messages_de.properties @@ -3331,3 +3331,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" \ No newline at end of file diff --git a/webapp-resources/messages_en.properties b/webapp-resources/messages_en.properties index 1b9ac19cb2..5ef2169d71 100644 --- a/webapp-resources/messages_en.properties +++ b/webapp-resources/messages_en.properties @@ -3334,3 +3334,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" \ No newline at end of file diff --git a/webapp-resources/messages_es.properties b/webapp-resources/messages_es.properties index f0121c30f9..c28c49141f 100644 --- a/webapp-resources/messages_es.properties +++ b/webapp-resources/messages_es.properties @@ -3374,3 +3374,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" \ No newline at end of file diff --git a/webapp-resources/messages_fi.properties b/webapp-resources/messages_fi.properties index 20f927b8cb..b507853d9c 100644 --- a/webapp-resources/messages_fi.properties +++ b/webapp-resources/messages_fi.properties @@ -3460,3 +3460,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" \ No newline at end of file diff --git a/webapp-resources/messages_fr.properties b/webapp-resources/messages_fr.properties index 9bde4e4604..b37b69350f 100644 --- a/webapp-resources/messages_fr.properties +++ b/webapp-resources/messages_fr.properties @@ -3328,3 +3328,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" \ No newline at end of file diff --git a/webapp-resources/messages_lu.properties b/webapp-resources/messages_lu.properties index 6b31432890..0c0b421916 100644 --- a/webapp-resources/messages_lu.properties +++ b/webapp-resources/messages_lu.properties @@ -3347,3 +3347,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" \ No newline at end of file diff --git a/webapp-resources/messages_nl.properties b/webapp-resources/messages_nl.properties index 2789bb0117..5cca22c256 100644 --- a/webapp-resources/messages_nl.properties +++ b/webapp-resources/messages_nl.properties @@ -3450,3 +3450,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" \ No newline at end of file diff --git a/webapp-resources/messages_pl.properties b/webapp-resources/messages_pl.properties index c7b379f1ae..8ee0bad5ce 100644 --- a/webapp-resources/messages_pl.properties +++ b/webapp-resources/messages_pl.properties @@ -3472,4 +3472,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point - +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" diff --git a/webapp-resources/messages_pt.properties b/webapp-resources/messages_pt.properties index a4c21e4947..9a9b785058 100644 --- a/webapp-resources/messages_pt.properties +++ b/webapp-resources/messages_pt.properties @@ -3486,3 +3486,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" \ No newline at end of file diff --git a/webapp-resources/messages_ru.properties b/webapp-resources/messages_ru.properties index 0d416ada3e..1c17ec7c6e 100644 --- a/webapp-resources/messages_ru.properties +++ b/webapp-resources/messages_ru.properties @@ -3482,3 +3482,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" \ No newline at end of file diff --git a/webapp-resources/messages_zh.properties b/webapp-resources/messages_zh.properties index d227bae6b2..b37e3f5cd4 100644 --- a/webapp-resources/messages_zh.properties +++ b/webapp-resources/messages_zh.properties @@ -3435,3 +3435,7 @@ systemsettings.workitems.reporting.itemspersecond.enabled=Items per second repor systemsettings.workitems.reporting.itemspersecond.limit=Items per second reporting limit systemsettings.threads.name.additional.length=Thread name length common.addPoint=Add point +systemsettings.webresource.uploads.path=Uploads images path +systemsettings.webresource.graphics.path=Graphics images path +systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" +systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" \ No newline at end of file