Skip to content

Commit 126ac89

Browse files
authored
Merge pull request #662 from openo-beta/issue-656-doc-upload
Fix inbox document uploads
2 parents c41c7df + 5e66a3a commit 126ac89

3 files changed

Lines changed: 93 additions & 36 deletions

File tree

src/main/java/ca/openosp/openo/documentManager/IncomingDocUtil.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,10 @@ public static String getAndSetEntryMode(String user_no, String selectedEntryMode
819819
}
820820

821821
public static void doPagesAction(String pdfAction, String queueIdStr, String pdfDir, String pdfName, String pdfPageNumber, String pdfExtractPageNumber, Locale locale) throws Exception {
822+
if (pdfAction == null || pdfAction.trim().isEmpty()) {
823+
return;
824+
}
825+
822826
String filePathName = getIncomingDocumentFilePathName(queueIdStr, pdfDir, pdfName);
823827
ResourceBundle props = ResourceBundle.getBundle("oscarResources", locale);
824828
int degree = 0;

src/main/java/ca/openosp/openo/documentManager/actions/DocumentUpload2Action.java

Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import net.sf.json.JSONArray;
2929
import net.sf.json.JSONObject;
3030

31+
import org.apache.commons.io.FilenameUtils;
3132
import org.apache.commons.io.IOUtils;
3233
import org.apache.logging.log4j.Logger;
3334
import ca.openosp.openo.PMmodule.model.ProgramProvider;
@@ -75,7 +76,7 @@ public String executeUpload() throws Exception {
7576
if (docFile == null) {
7677
map.put("error", 4);
7778
} else if (destination != null && destination.equals("incomingDocs")) {
78-
String fileName = docFile.getName();
79+
String fileName = this.filedataFileName;
7980
if (!fileName.toLowerCase().endsWith(".pdf")) {
8081
map.put("error", props.getString("dms.documentUpload.onlyPdf"));
8182
} else if (docFile.length() == 0) {
@@ -110,7 +111,7 @@ public String executeUpload() throws Exception {
110111
}
111112
} else {
112113
int numberOfPages = 0;
113-
String fileName = MiscUtils.sanitizeFileName(docFile.getName());
114+
String fileName = MiscUtils.sanitizeFileName(this.filedataFileName);
114115
String user = (String) request.getSession().getAttribute("user");
115116
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
116117
EDoc newDoc = new EDoc("", "", fileName, "", user, user, this.getSource(), 'A',
@@ -188,7 +189,7 @@ public int countNumOfPages(String fileName) {
188189
}
189190

190191
/**
191-
* Writes an uploaded file to disk
192+
* Writes an uploaded file to disk with canonical path validation
192193
*
193194
* @param docFile the uploaded file
194195
* @param fileName the name for the file on disk
@@ -199,15 +200,34 @@ private void writeLocalFile(File docFile, String fileName) throws Exception {
199200
FileOutputStream fos = null;
200201
try {
201202
fis = Files.newInputStream(docFile.toPath());
202-
String savePath = OscarProperties.getInstance().getProperty("DOCUMENT_DIR") + "/" + fileName;
203+
String documentDir = OscarProperties.getInstance().getProperty("DOCUMENT_DIR");
204+
if (!documentDir.endsWith(File.separator)) {
205+
documentDir += File.separator;
206+
}
207+
String savePath = documentDir + fileName;
208+
209+
// Validate the canonical path to ensure file ends up in the intended destination
210+
File baseDir = new File(documentDir);
211+
String canonicalBase = baseDir.getCanonicalPath();
212+
213+
File destinationFile = new File(savePath);
214+
String canonicalDest = destinationFile.getCanonicalPath();
215+
216+
// Ensure the canonical path is within the allowed directory
217+
if (!canonicalDest.startsWith(canonicalBase + File.separator) &&
218+
!canonicalDest.equals(canonicalBase)) {
219+
throw new SecurityException("Destination file is outside allowed directory");
220+
}
221+
203222
fos = new FileOutputStream(savePath);
204223
byte[] buf = new byte[128 * 1024];
205224
int i = 0;
206225
while ((i = fis.read(buf)) != -1) {
207226
fos.write(buf, 0, i);
208227
}
209228
} catch (Exception e) {
210-
logger.debug(e.toString());
229+
logger.error("Error writing local file", e);
230+
throw e;
211231
} finally {
212232
if (fis != null)
213233
fis.close();
@@ -217,28 +237,49 @@ private void writeLocalFile(File docFile, String fileName) throws Exception {
217237
}
218238

219239
private boolean writeToIncomingDocs(File docFile, String queueId, String PdfDir, String fileName) {
220-
// Validate inputs to prevent path traversal attacks
221-
// Note: These validations are redundant as inputs are already validated before calling this method,
222-
// but we keep them for defense in depth
223240
if (queueId == null || PdfDir == null || fileName == null) {
224241
logger.error("Invalid parameters provided for writeToIncomingDocs");
225242
return false;
226243
}
227244

228-
// The filename should already be sanitized by the caller, but validate again for defense in depth
229245
// Check that filename doesn't contain path traversal sequences
230246
if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
231247
logger.error("Filename contains invalid path characters");
232248
return false;
233249
}
234-
250+
235251
String parentPath = IncomingDocUtil.getIncomingDocumentFilePath(queueId, PdfDir);
236252
if (!new File(parentPath).exists()) {
237253
return false;
238254
}
239255

240256
String savePath = IncomingDocUtil.getAndCreateIncomingDocumentFilePathName(queueId, PdfDir, fileName);
241-
257+
258+
// Validate the canonical path to ensure file ends up in the intended destination
259+
try {
260+
String incomingDocDir = OscarProperties.getInstance().getProperty("INCOMINGDOCUMENT_DIR");
261+
if (incomingDocDir == null || incomingDocDir.isEmpty()) {
262+
logger.error("INCOMINGDOCUMENT_DIR not configured");
263+
return false;
264+
}
265+
266+
File baseDir = new File(incomingDocDir);
267+
String canonicalBase = baseDir.getCanonicalPath();
268+
269+
File destinationFile = new File(savePath);
270+
String canonicalDest = destinationFile.getCanonicalPath();
271+
272+
// Ensure the canonical path is within the allowed directory
273+
if (!canonicalDest.startsWith(canonicalBase + File.separator) &&
274+
!canonicalDest.equals(canonicalBase)) {
275+
logger.error("Destination file is outside allowed directory: " + canonicalDest);
276+
return false;
277+
}
278+
} catch (IOException e) {
279+
logger.error("Error validating canonical path", e);
280+
return false;
281+
}
282+
242283
// Write the file
243284
try (InputStream fis = Files.newInputStream(docFile.toPath());
244285
FileOutputStream fos = new FileOutputStream(savePath)) {
@@ -253,42 +294,33 @@ private boolean writeToIncomingDocs(File docFile, String queueId, String PdfDir,
253294

254295
/**
255296
* Sanitizes a filename for use in incoming documents to prevent path traversal attacks.
256-
*
297+
* Uses Apache Commons IO FilenameUtils for robust path traversal prevention.
298+
*
257299
* @param fileName the original filename
258300
* @return sanitized filename safe for filesystem operations
259301
*/
260302
private String sanitizeFileNameForIncomingDocs(String fileName) {
261303
if (fileName == null || fileName.trim().isEmpty()) {
262304
return null;
263305
}
264-
265-
// Get just the filename component (removes any path)
266-
File file = new File(fileName);
267-
String baseName = file.getName();
268-
269-
// Remove any path traversal sequences and dangerous characters
270-
baseName = baseName.replaceAll("\\.\\.", "") // Remove ..
271-
.replaceAll("[\\\\/]", "") // Remove any slashes
272-
.replaceAll("\\$", "") // Remove $
273-
.replaceAll("~", "") // Remove ~
274-
.replaceAll(":", "") // Remove colons
275-
.replaceAll("\\|", "") // Remove pipes
276-
.replaceAll("<", "") // Remove less than
277-
.replaceAll(">", "") // Remove greater than
278-
.replaceAll("\"", "") // Remove quotes
279-
.replaceAll("\\*", "") // Remove asterisks
280-
.replaceAll("\\?", ""); // Remove question marks
281-
282-
// Ensure the filename ends with .pdf (case insensitive)
283-
if (!baseName.toLowerCase().endsWith(".pdf")) {
306+
307+
String baseName = FilenameUtils.getName(fileName);
308+
309+
// Ensure baseName doesn't contain any path separators
310+
if (baseName.contains("/") || baseName.contains("\\") || baseName.contains("..")) {
284311
return null;
285312
}
286-
287-
// Additional validation - ensure the filename is not empty after sanitization
288-
if (baseName.trim().isEmpty() || baseName.equals(".pdf")) {
313+
314+
// Reject filenames starting with . (hidden files)
315+
if (baseName.startsWith(".")) {
289316
return null;
290317
}
291-
318+
319+
// Ensure baseName is not empty and ends with .pdf
320+
if (baseName.trim().isEmpty() || !baseName.toLowerCase().endsWith(".pdf") || baseName.equals(".pdf")) {
321+
return null;
322+
}
323+
292324
return baseName;
293325
}
294326

@@ -343,6 +375,8 @@ public String setUploadIncomingDocumentFolder() {
343375
private File docFile;
344376

345377
private File filedata;
378+
private String filedataFileName;
379+
private String filedataContentType;
346380

347381
private String docPublic = "";
348382
private String mode = "";
@@ -480,4 +514,20 @@ public File getFiledata() {
480514
public void setFiledata(File Filedata) {
481515
this.filedata = Filedata;
482516
}
517+
518+
public String getFiledataFileName() {
519+
return filedataFileName;
520+
}
521+
522+
public void setFiledataFileName(String filedataFileName) {
523+
this.filedataFileName = filedataFileName;
524+
}
525+
526+
public String getFiledataContentType() {
527+
return filedataContentType;
528+
}
529+
530+
public void setFiledataContentType(String filedataContentType) {
531+
this.filedataContentType = filedataContentType;
532+
}
483533
}

src/main/java/ca/openosp/openo/documentManager/actions/ManageDocument2Action.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ public class ManageDocument2Action extends ActionSupport {
118118
ACTIONS.put("display", ctx -> { ctx.display(); return null; });
119119
ACTIONS.put("viewAnnotationAcknowledgementTickler", ctx -> { ctx.viewAnnotationAcknowledgementTickler(); return null; });
120120
ACTIONS.put("viewDocumentDescription", ctx -> { ctx.viewDocumentDescription(); return null; });
121+
ACTIONS.put("viewIncomingDocPageAsPdf", ctx -> { ctx.viewIncomingDocPageAsPdf(); return null; });
122+
ACTIONS.put("viewIncomingDocPageAsImage", ctx -> { ctx.viewIncomingDocPageAsImage(); return null; });
123+
ACTIONS.put("displayIncomingDocs", ctx -> { ctx.displayIncomingDocs(); return null; });
121124
ACTIONS.put("documentUpdate", ctx -> { ctx.documentUpdate(); return null; });
122125
ACTIONS.put("documentUpdateAjax", ctx -> { ctx.documentUpdateAjax(); return null; });
123126
// Enable calling the method to remove providers

0 commit comments

Comments
 (0)