@@ -8,19 +8,24 @@ const fs = require('fs');
88 */
99
1010/**
11- * Testing Order:
11+ * source-processor.js
12+ * └─ isValidPath
13+ * └─ processSourceCode
14+ * └─ processFilename
15+ * └─ processAndSaveFileWithTags
16+ * └─ updateInsertTagsSafely
17+ */
18+
19+ /**
20+ * The order of testing the functions in this file is as follows:
1221 *
13- * 1. `processSourceCode `
22+ * 1. `isValidPath `
1423 * 2. `processFilename`
15- *
16- * This order is important because `processFilename` depends on
17- * `processMarkdown` and we want to make sure that all the dependant
18- * functions are tested first.
24+ * 3. `processSourceCode`
25+ * 4. `processAndSaveFileWithTags`
26+ * 5. `updateInsertTagsSafely`
1927 */
2028
21-
22-
23-
2429/**
2530 * Processes the given source code by replacing insert/include tags with the content
2631 * of the specified files. The file paths are resolved relative to the provided base path.
@@ -69,7 +74,6 @@ ${fileContent}
6974 } ) ;
7075}
7176
72-
7377/**
7478 * Reads the content of a file and processes it by inserting content of other
7579 * files based on insert/include tags. If the file does not exist or is empty,
@@ -82,6 +86,9 @@ ${fileContent}
8286 * @throws {Error } If an error occurs while reading the file
8387 */
8488function processFilename ( filename ) {
89+ /*
90+ * Validate the filename
91+ */
8592 if ( ! filename ) {
8693 throw new Error ( "File name is required" ) ;
8794 }
@@ -96,20 +103,152 @@ function processFilename(filename) {
96103 // Read the content of the file
97104 fileContent = fs . readFileSync ( filename , 'utf8' ) ;
98105 } catch ( error ) {
106+ // If the file does not exist, return null
99107 if ( error . code !== 'ENOENT' ) {
100108 throw error ;
101109 }
110+
111+ return null ;
102112 }
103113
104114 if ( ! fileContent || fileContent . trim ( ) === '' ) {
115+ // If the file is empty, return null
105116 return null ;
106117 }
107118
108- // Process the markdown content by inserting content of other files
119+ // Process the source content by inserting content of other files
109120 return processSourceCode ( fileContent , path . dirname ( filename ) ) ;
110121}
111122
123+ /**
124+ * Processes the source file by replacing insert/include tags with the content
125+ * of the specified files, and then saves the processed content to the target
126+ * file.
127+ *
128+ * @param {string } sourceFilename - The name of the source file to process.
129+ * @param {string } targetFilename - The name of the target file to save the
130+ * processed content to.
131+ * @throws {Error } If the file name is not provided or is not a string.
132+ * @throws {TypeError } If the file name is not a string.
133+ * @throws {Error } If an error occurs while reading or writing the file.
134+ */
135+ function processAndSaveFileWithTags ( sourceFilename , targetFilename ) {
136+ // if (!isValidPath(sourceFilename) || !isValidPath(targetFilename)) {
137+ // throw new TypeError('Invalid file path');
138+ // }
139+ if ( ! sourceFilename ) {
140+ throw new Error ( "Source file name is required" ) ;
141+ }
142+
143+ if ( typeof sourceFilename !== 'string' ) {
144+ throw new TypeError ( "Source file name must be a string" ) ;
145+ }
146+
147+ if ( ! targetFilename ) {
148+ throw new Error ( "Target file name is required" ) ;
149+ }
150+
151+ if ( typeof targetFilename !== 'string' ) {
152+ throw new TypeError ( "Target file name must be a string" ) ;
153+ }
154+
155+ // Read the source file content and process it
156+ // const processedSource = processFilename(path.basename(sourceFilename));
157+ const processedSource = processFilename ( sourceFilename ) ;
158+ if ( ! processedSource ) {
159+ throw new Error ( 'Filename processing failed.' ) ;
160+ }
161+
162+ // Save the processed content to the target file
163+ try {
164+ fs . mkdirSync ( path . dirname ( targetFilename ) , { recursive : true } ) ;
165+ fs . writeFileSync ( targetFilename , processedSource , 'utf8' ) ;
166+ } catch ( error ) {
167+ console . error ( `Error processing file: ${ error . message } ` ) ;
168+ }
169+ }
170+
171+ /**
172+ * Processes the file by replacing insert/include tags with the content of
173+ * other files, and saves the processed content to the same file. This
174+ * function is safe to call concurrently because it uses a temporary file to
175+ * avoid overwriting the original file during processing.
176+ *
177+ * @param {string } filename - The name of the file to process.
178+ * @throws {Error } If the file name is not provided or is not a string.
179+ * @throws {TypeError } If the file name is not a string.
180+ * @throws {Error } If an error occurs while reading or writing the file.
181+ */
182+ function updateInsertTagsSafely ( filename ) {
183+ if ( ! filename ) {
184+ throw new Error ( "File name is required" ) ;
185+ }
186+
187+ if ( typeof filename !== 'string' ) {
188+ throw new TypeError ( "File name must be a string" ) ;
189+ }
190+
191+ const tempFilename = filename + '.tmp' ;
192+
193+ try {
194+ // Process the file and save to a temporary file
195+ processAndSaveFileWithTags ( filename , tempFilename ) ;
196+
197+ // Rename the temporary file to the original filename
198+ fs . renameSync ( tempFilename , filename ) ;
199+ } catch ( error ) {
200+ console . error ( `Error processing file with temp: ${ error . message } ` ) ;
201+ // Cleanup: remove the temporary file if it exists
202+ if ( fs . existsSync ( tempFilename ) ) {
203+ fs . unlinkSync ( tempFilename ) ;
204+ }
205+ }
206+ }
207+
208+ /**
209+ * Checks if the given file path is valid and not maliciously trying to escape
210+ * the base directory.
211+ *
212+ * @param {string } filePath - The file path to check.
213+ * @param {string } [baseDir=process.cwd()] - The base directory to check against.
214+ * @returns {boolean } True if the path is valid, false otherwise.
215+ */
216+ function isValidPath ( filePath , baseDir = process . cwd ( ) ) {
217+ const pathRegex = / ^ [ a - z A - Z 0 - 9 _ \- \/ \\ ] + $ / ;
218+ if ( typeof filePath !== 'string' || ! pathRegex . test ( filePath ) ) {
219+ return false
220+ }
221+
222+ // Make sure the base directory exists and is a directory
223+ if ( ! fs . existsSync ( baseDir ) || ! fs . lstatSync ( baseDir ) . isDirectory ( ) ) {
224+ console . error ( `Invalid base directory: ${ baseDir } ` ) ;
225+ return false ;
226+ }
227+
228+ // Sanitize the file path and base directory by removing any .. and . references
229+ const sanitizedFilePath = path . normalize ( filePath ) . replace ( / ( \. \. ( \/ | \\ | $ ) ) + / , '' ) ;
230+ const sanitizedBaseDir = path . normalize ( baseDir ) . replace ( / ( \. \. ( \/ | \\ | $ ) ) + / , '' ) ;
231+
232+ // Make sure the file path is absolute
233+ if ( ! path . isAbsolute ( sanitizedFilePath ) ) {
234+ return false ;
235+ }
236+
237+ try {
238+ // Resolve the file path and check if it's inside the base directory
239+ const resolvedPath = path . join ( sanitizedBaseDir , sanitizedFilePath ) ;
240+ const realPath = fs . realpathSync ( resolvedPath ) ;
241+ return realPath . startsWith ( sanitizedBaseDir ) ;
242+ } catch ( error ) {
243+ console . error ( `Error resolving path: ${ error . message } ` ) ;
244+ return false ;
245+ }
246+ }
247+
112248module . exports = {
113249 processFilename,
114- processSourceCode
115- }
250+ processSourceCode,
251+ processAndSaveFileWithTags,
252+ isValidPath,
253+ updateInsertTagsSafely,
254+ } ;
0 commit comments