1+ <?php
2+
3+ namespace OCA \Google \Service \Utils ;
4+
5+ use OCP \Files \FileNameTooLongException ;
6+ use OCP \Files \EmptyFileNameException ;
7+ use OCP \Files \InvalidCharacterInPathException ;
8+ use OCP \Files \InvalidDirectoryException ;
9+ use OCP \Files \ReservedWordException ;
10+ use OCP \Files \InvalidPathException ;
11+ use Psr \Log \LoggerInterface ;
12+ use OC ;
13+
14+ class FileUtils {
15+
16+ /**
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+ */
24+ public static function sanitizeFilename (
25+ string $ filename ,
26+ string $ id ,
27+ LoggerInterface $ logger ,
28+ int $ recursionDepth = 0 ,
29+ string $ originalFilename = null
30+ ): string {
31+ // Prevent infinite recursion by limiting the depth.
32+ if ($ recursionDepth > 15 ) {
33+ $ filename = 'Untitled_ ' . $ id ;
34+ $ logger ->warning ('Maximum recursion depth reached while sanitizing filename: ' . $ originalFilename . ' renaming to ' . $ filename );
35+ return $ filename ;
36+ }
37+
38+ // If the original filename is not provided, use the current filename.
39+ if ($ originalFilename === null ) {
40+ $ originalFilename = $ filename ;
41+ }
42+
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 ;
56+ }
57+ };
58+
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 );
62+ }
63+
64+ // Ensure the filename length does not exceed the maximum allowed length.
65+ $ maxLength = 254 ;
66+ 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+ }
75+ }
76+
77+ 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.
82+ if ($ recursionDepth > 0 ) {
83+ $ logger ->info ('Filename sanitized successfully: " ' . $ filename . '" (original: " ' . $ originalFilename . '") ' );
84+ }
85+
86+ return $ filename ;
87+ } catch (InvalidPathException $ exception ) {
88+ $ logger ->warning ('Invalid filename detected during sanitization: ' . $ filename , ['exception ' => $ exception ]);
89+ }
90+
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 ;
124+ }
125+
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 );
129+ }
130+
131+ // Recursively validate the adjusted filename.
132+ return self ::sanitizeFilename ($ filename , $ id , $ logger , $ recursionDepth + 1 , $ originalFilename );
133+ }
134+ }
0 commit comments