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