2828import net .sf .json .JSONArray ;
2929import net .sf .json .JSONObject ;
3030
31+ import org .apache .commons .io .FilenameUtils ;
3132import org .apache .commons .io .IOUtils ;
3233import org .apache .logging .log4j .Logger ;
3334import 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}
0 commit comments