1212use OC ;
1313
1414class FileUtils {
15-
15+
1616 /**
17- * Sanitize the filename to ensure it is valid, does not exceed length limits.
18- *
19- * @param string $filename The original filename to sanitize.
20- * @param string $id A unique ID to append if necessary to ensure uniqueness.
21- * @param int $recursionDepth The current recursion depth (used to prevent infinite loops).
22- * @return string The sanitized and validated filename.
23- */
17+ * Sanitize the filename to ensure it is valid, does not exceed length limits.
18+ *
19+ * @param string $filename The original filename to sanitize.
20+ * @param string $id A unique ID to append if necessary to ensure uniqueness.
21+ * @param LoggerInterface $logger Logger for logging messages.
22+ * @param int $recursionDepth The current recursion depth (used to prevent infinite loops).
23+ * @param string|null $originalFilename The original filename for logging.
24+ * @return string The sanitized and validated filename.
25+ */
2426 public static function sanitizeFilename (
2527 string $ filename ,
2628 string $ id ,
2729 LoggerInterface $ logger ,
2830 int $ recursionDepth = 0 ,
29- string $ originalFilename = null
31+ ? string $ originalFilename = null
3032 ): string {
31- // Prevent infinite recursion by limiting the depth.
3233 if ($ recursionDepth > 15 ) {
3334 $ filename = 'Untitled_ ' . $ id ;
34- $ logger ->warning ('Maximum recursion depth reached while sanitizing filename: ' . $ originalFilename . ' renaming to ' . $ filename );
35+ $ logger ->warning ('Maximum recursion depth reached while sanitizing filename: ' . ( $ originalFilename ?? $ filename ) . ' renaming to ' . $ filename );
3536 return $ filename ;
3637 }
3738
38- // If the original filename is not provided, use the current filename.
3939 if ($ originalFilename === null ) {
4040 $ originalFilename = $ filename ;
4141 }
4242
43- // Trim leading/trailing whitespace and trailing dots.
44- $ filename = rtrim (trim ($ filename ), '. ' );
45-
46- // Check if trimming altered the filename.
47- $ trimmed = ($ originalFilename !== $ filename );
48-
49- // Helper function to append the ID before the file extension.
50- $ appendIdBeforeExtension = function ($ filename , $ id ) {
51- $ pathInfo = pathinfo ($ filename );
52- if (isset ($ pathInfo ['extension ' ])) {
53- return $ pathInfo ['filename ' ] . '_ ' . $ id . '. ' . $ pathInfo ['extension ' ];
54- } else {
55- return $ filename . '_ ' . $ id ;
43+ // Use Nextcloud 32+ validator if available
44+ if (version_compare (OC ::$ server ->getConfig ()->getSystemValue ('version ' , '0.0.0 ' ), '32.0.0 ' , '>= ' )) {
45+ $ logger ->debug ('Using Nextcloud 32+ filename validator for sanitization. ' );
46+ try {
47+ return OC ::$ server ->get (\OCP \Files \IFilenameValidator::class)->sanitizeFilename ($ filename );
48+ } catch (\InvalidArgumentException $ exception ) {
49+ $ logger ->error ('Unable to sanitize filename: ' . $ filename , ['exception ' => $ exception ]);
50+ return 'Untitled_ ' . $ id ;
5651 }
57- };
52+ } else {
53+ $ logger ->debug ('Using legacy filename sanitization method. ' );
54+ }
55+
56+ // Trim whitespace and trailing dots
57+ $ filename = rtrim (trim ($ filename ), '. ' );
5858
59- // Append the ID if trimming occurred and the ID is not already present.
60- if ($ trimmed && ! str_contains ($ filename , $ id )) {
61- $ filename = $ appendIdBeforeExtension ($ filename , $ id );
59+ // Append ID if needed
60+ if ($ originalFilename !== $ filename && strpos ($ filename , $ id ) === false ) {
61+ $ filename = self :: appendIdBeforeExtension ($ filename , $ id );
6262 }
6363
64- // Ensure the filename length does not exceed the maximum allowed length.
64+ // Enforce max length
6565 $ maxLength = 254 ;
6666 if (mb_strlen ($ filename ) > $ maxLength ) {
67- $ pathInfo = pathinfo ($ filename );
68- $ baseLength = $ maxLength - mb_strlen ($ id ) - 2 ; // Account for '_' and '.'.
69- if (isset ($ pathInfo ['extension ' ])) {
70- $ baseLength -= mb_strlen ($ pathInfo ['extension ' ]);
71- $ filename = mb_substr ($ pathInfo ['filename ' ], 0 , $ baseLength ) . '_ ' . $ id . '. ' . $ pathInfo ['extension ' ];
72- } else {
73- $ filename = mb_substr ($ filename , 0 , $ baseLength ) . '_ ' . $ id ;
74- }
67+ $ filename = self ::truncateAndAppendId ($ filename , $ id , $ maxLength );
7568 }
7669
7770 try {
78- // Validate the filename using the Nextcloud filename validator.
79- \OC ::$ server ->get (\OCP \Files \IFilenameValidator::class)->validateFilename ($ filename );
80-
81- // if recursion depth is greater than 0, log the change.
71+ OC ::$ server ->get (\OCP \Files \IFilenameValidator::class)->validateFilename ($ filename );
8272 if ($ recursionDepth > 0 ) {
8373 $ logger ->info ('Filename sanitized successfully: " ' . $ filename . '" (original: " ' . $ originalFilename . '") ' );
8474 }
85-
8675 return $ filename ;
87- } catch (InvalidPathException $ exception ) {
88- $ logger ->warning ('Invalid filename detected during sanitization: ' . $ filename , ['exception ' => $ exception ]);
76+ } catch (\Throwable $ exception ) {
77+ $ logger ->warning ('Exception during filename validation: ' . $ filename , ['exception ' => $ exception ]);
78+ $ filename = self ::handleFilenameException ($ filename , $ id , $ exception , $ logger );
79+ if (strpos ($ filename , $ id ) === false ) {
80+ $ filename = self ::appendIdBeforeExtension ($ filename , $ id );
81+ }
82+ return self ::sanitizeFilename ($ filename , $ id , $ logger , $ recursionDepth + 1 , $ originalFilename );
8983 }
84+ }
9085
91- // Handle specific exceptions and adjust the filename accordingly.
92- switch (true ) {
93- case $ exception instanceof FileNameTooLongException:
94- $ filename = mb_substr ($ filename , 0 , $ maxLength - mb_strlen ($ id ) - 2 );
95- break ;
96-
97- case $ exception instanceof EmptyFileNameException:
98- $ filename = 'Untitled ' ;
99- break ;
100-
101- case $ exception instanceof InvalidCharacterInPathException:
102- if (preg_match ('/"(.*?)"/ ' , $ exception ->getMessage (), $ matches )) {
103- $ invalidChars = array_merge (str_split ($ matches [1 ]), ['" ' ]);
104- $ filename = str_replace ($ invalidChars , '- ' , $ filename );
105- }
106- break ;
107-
108- case $ exception instanceof InvalidDirectoryException:
109- $ logger ->error ('Invalid directory detected in filename: ' . $ exception ->getMessage ());
110- $ filename = 'Untitled ' ;
111- break ;
112-
113- case $ exception instanceof ReservedWordException:
114- if (preg_match ('/"(.*?)"/ ' , $ exception ->getMessage (), $ matches )) {
115- $ reservedWord = $ matches [1 ];
116- $ filename = str_ireplace ($ reservedWord , '- ' . $ reservedWord . '- ' , $ filename );
117- }
118- break ;
119-
120- default :
121- $ logger ->error ('Unknown exception encountered during filename sanitization: ' . $ filename );
122- $ filename = 'Untitled ' ;
123- break ;
86+ private static function appendIdBeforeExtension (string $ filename , string $ id ): string {
87+ $ pathInfo = pathinfo ($ filename );
88+ if (isset ($ pathInfo ['extension ' ])) {
89+ return $ pathInfo ['filename ' ] . '_ ' . $ id . '. ' . $ pathInfo ['extension ' ];
12490 }
91+ return $ filename . '_ ' . $ id ;
92+ }
12593
126- // Append the ID if the filename was modified and does not already contain the ID.
127- if (!str_contains ($ filename , $ id )) {
128- $ filename = $ appendIdBeforeExtension ($ filename , $ id );
94+ private static function truncateAndAppendId (string $ filename , string $ id , int $ maxLength ): string {
95+ $ pathInfo = pathinfo ($ filename );
96+ $ baseLength = $ maxLength - mb_strlen ($ id ) - 2 ;
97+ if (isset ($ pathInfo ['extension ' ])) {
98+ $ baseLength -= mb_strlen ($ pathInfo ['extension ' ]);
99+ return mb_substr ($ pathInfo ['filename ' ], 0 , $ baseLength ) . '_ ' . $ id . '. ' . $ pathInfo ['extension ' ];
129100 }
101+ return mb_substr ($ filename , 0 , $ baseLength ) . '_ ' . $ id ;
102+ }
130103
131- // Recursively validate the adjusted filename.
132- return self ::sanitizeFilename ($ filename , $ id , $ logger , $ recursionDepth + 1 , $ originalFilename );
104+ private static function handleFilenameException (string $ filename , string $ id , \Throwable $ exception , LoggerInterface $ logger ): string {
105+ if ($ exception instanceof FileNameTooLongException) {
106+ return mb_substr ($ filename , 0 , 254 - mb_strlen ($ id ) - 2 );
107+ }
108+ if ($ exception instanceof EmptyFileNameException) {
109+ return 'Untitled ' ;
110+ }
111+ if ($ exception instanceof InvalidCharacterInPathException) {
112+ if (preg_match ('/"(.*?)"/ ' , $ exception ->getMessage (), $ matches )) {
113+ $ invalidChars = array_merge (str_split ($ matches [1 ]), ['" ' ]);
114+ return str_replace ($ invalidChars , '- ' , $ filename );
115+ }
116+ }
117+ if ($ exception instanceof InvalidDirectoryException) {
118+ $ logger ->error ('Invalid directory detected in filename: ' . $ exception ->getMessage ());
119+ return 'Untitled ' ;
120+ }
121+ if ($ exception instanceof ReservedWordException) {
122+ if (preg_match ('/"(.*?)"/ ' , $ exception ->getMessage (), $ matches )) {
123+ $ reservedWord = $ matches [1 ];
124+ return str_ireplace ($ reservedWord , '- ' . $ reservedWord . '- ' , $ filename );
125+ }
126+ }
127+ $ logger ->error ('Unknown exception encountered during filename sanitization: ' . $ filename );
128+ return 'Untitled ' ;
133129 }
134- }
130+ }
0 commit comments