@@ -216,6 +216,104 @@ function addAlertsToResponse(response, alerts) {
216216 return response ;
217217}
218218
219+ // Buffer processing functions for atomic conversion (no file system)
220+ async function processEapConfigFromBuffer ( fileContent , obfuscationLevel , certHandling ) {
221+ console . log ( '[processEapConfigFromBuffer] Processing EAP config from buffer' ) ;
222+
223+ try {
224+ // Parse the EAP XML content
225+ const xml2js = require ( 'xml2js' ) ;
226+ const parser = new xml2js . Parser ( { explicitArray : false , mergeAttrs : true } ) ;
227+ const result = await parser . parseStringPromise ( fileContent ) ;
228+
229+ // Apply obfuscation if needed
230+ let processedResult = result ;
231+ if ( obfuscationLevel !== 'none' ) {
232+ processedResult = obfuscatePasswords ( result , obfuscationLevel ) ;
233+ }
234+
235+ // Convert to YAML and JSON
236+ const yaml = require ( 'js-yaml' ) ;
237+ const yamlContent = yaml . dump ( processedResult , {
238+ indent : 2 ,
239+ lineWidth : - 1 ,
240+ noRefs : true ,
241+ sortKeys : false
242+ } ) ;
243+
244+ return {
245+ yaml : yamlContent ,
246+ json : JSON . stringify ( processedResult , null , 2 ) ,
247+ original : fileContent
248+ } ;
249+ } catch ( error ) {
250+ throw new Error ( 'Failed to process EAP config: ' + error . message ) ;
251+ }
252+ }
253+
254+ async function processPlistFromBuffer ( fileContent , obfuscationLevel , certHandling ) {
255+ console . log ( '[processPlistFromBuffer] Processing plist from buffer' ) ;
256+
257+ try {
258+ const plist = require ( 'plist' ) ;
259+ const parsedData = plist . parse ( fileContent ) ;
260+
261+ // Apply obfuscation if needed
262+ let processedResult = parsedData ;
263+ if ( obfuscationLevel !== 'none' ) {
264+ processedResult = obfuscatePasswords ( parsedData , obfuscationLevel ) ;
265+ }
266+
267+ // Convert to YAML and JSON
268+ const yaml = require ( 'js-yaml' ) ;
269+ const yamlContent = yaml . dump ( processedResult , {
270+ indent : 2 ,
271+ lineWidth : - 1 ,
272+ noRefs : true ,
273+ sortKeys : false
274+ } ) ;
275+
276+ return {
277+ yaml : yamlContent ,
278+ json : JSON . stringify ( processedResult , null , 2 ) ,
279+ original : fileContent
280+ } ;
281+ } catch ( error ) {
282+ throw new Error ( 'Failed to process plist: ' + error . message ) ;
283+ }
284+ }
285+
286+ async function processYamlFromBuffer ( fileContent , obfuscationLevel ) {
287+ console . log ( '[processYamlFromBuffer] Processing YAML from buffer' ) ;
288+
289+ try {
290+ const yaml = require ( 'js-yaml' ) ;
291+ const parsedData = yaml . load ( fileContent ) ;
292+
293+ // Apply obfuscation if needed
294+ let processedResult = parsedData ;
295+ if ( obfuscationLevel !== 'none' ) {
296+ processedResult = obfuscatePasswords ( parsedData , obfuscationLevel ) ;
297+ }
298+
299+ // Convert back to YAML and JSON
300+ const yamlContent = yaml . dump ( processedResult , {
301+ indent : 2 ,
302+ lineWidth : - 1 ,
303+ noRefs : true ,
304+ sortKeys : false
305+ } ) ;
306+
307+ return {
308+ yaml : yamlContent ,
309+ json : JSON . stringify ( processedResult , null , 2 ) ,
310+ original : fileContent
311+ } ;
312+ } catch ( error ) {
313+ throw new Error ( 'Failed to process YAML: ' + error . message ) ;
314+ }
315+ }
316+
219317// Utility function to generate suggested download filenames based on original filename
220318function generateSuggestedFilenames ( originalFilename ) {
221319 if ( ! originalFilename ) {
@@ -578,6 +676,111 @@ router.post('/test-upload', (req, res) => {
578676 } ) ;
579677} ) ;
580678
679+ // NEW ARCHITECTURE: Atomic upload + convert (eliminates race condition entirely)
680+ router . post ( '/upload-and-convert' , ( req , res ) => {
681+ // Set CORS headers
682+ res . header ( 'Access-Control-Allow-Origin' , req . headers . origin || '*' ) ;
683+ res . header ( 'Access-Control-Allow-Methods' , 'POST, OPTIONS' ) ;
684+ res . header ( 'Access-Control-Allow-Headers' , 'Content-Type, Authorization, X-Requested-With, Accept' ) ;
685+
686+ console . log ( '[SERVER /upload-and-convert] Atomic conversion request received' ) ;
687+
688+ const memoryUpload = multer ( {
689+ storage : multer . memoryStorage ( ) ,
690+ limits : {
691+ fileSize : 100 * 1024 * 1024 , // 100MB limit
692+ fieldSize : 100 * 1024 * 1024 ,
693+ fields : 10 ,
694+ files : 1
695+ }
696+ } ) . single ( 'yamlFile' ) ;
697+
698+ memoryUpload ( req , res , async ( err ) => {
699+ if ( err ) {
700+ console . error ( '[SERVER /upload-and-convert] Multer error:' , err ) ;
701+ return res . status ( 400 ) . json ( { error : 'Upload failed: ' + err . message } ) ;
702+ }
703+
704+ if ( ! req . file ) {
705+ return res . status ( 400 ) . json ( { error : 'No file uploaded' } ) ;
706+ }
707+
708+ try {
709+ console . log ( '[SERVER /upload-and-convert] Processing file:' , req . file . originalname ) ;
710+
711+ // Process directly from memory buffer - NO FILE SYSTEM INTERACTION
712+ const fileContent = req . file . buffer . toString ( 'utf8' ) ;
713+ const fileExtension = path . extname ( req . file . originalname ) . toLowerCase ( ) ;
714+
715+ // Get conversion parameters from request body (parsed from multipart)
716+ const obfuscationLevel = req . body . obfuscationLevel || 'none' ;
717+ const certHandling = req . body . certHandling || 'preserve' ;
718+
719+ console . log ( '[SERVER /upload-and-convert] Obfuscation level:' , obfuscationLevel ) ;
720+ console . log ( '[SERVER /upload-and-convert] Cert handling:' , certHandling ) ;
721+
722+ // Generate suggested filenames
723+ const suggestedFilenames = generateSuggestedFilenames ( req . file . originalname ) ;
724+
725+ // Detect file type and convert directly from memory
726+ let convertedData ;
727+ let originalData ;
728+
729+ if ( fileExtension === '.eap-config' || fileContent . includes ( '<EapHostConfig' ) ) {
730+ console . log ( '[SERVER /upload-and-convert] Processing EAP config file' ) ;
731+
732+ // Parse EAP config directly from buffer
733+ const result = await processEapConfigFromBuffer ( fileContent , obfuscationLevel , certHandling ) ;
734+ convertedData = result ;
735+ originalData = fileContent ;
736+
737+ } else if ( fileExtension === '.mobileconfig' || fileContent . includes ( '<?xml' ) || fileContent . includes ( '<plist' ) ) {
738+ console . log ( '[SERVER /upload-and-convert] Processing mobileconfig/plist file' ) ;
739+
740+ // Parse plist directly from buffer
741+ const result = await processPlistFromBuffer ( fileContent , obfuscationLevel , certHandling ) ;
742+ convertedData = result ;
743+ originalData = fileContent ;
744+
745+ } else if ( fileExtension === '.yaml' || fileExtension === '.yml' ) {
746+ console . log ( '[SERVER /upload-and-convert] Processing YAML file' ) ;
747+
748+ // Parse YAML directly from buffer
749+ const result = await processYamlFromBuffer ( fileContent , obfuscationLevel ) ;
750+ convertedData = result ;
751+ originalData = fileContent ;
752+
753+ } else {
754+ throw new Error ( 'Unsupported file type for atomic conversion' ) ;
755+ }
756+
757+ console . log ( '[SERVER /upload-and-convert] Conversion completed successfully' ) ;
758+
759+ // Return the complete conversion result
760+ return res . status ( 200 ) . json ( {
761+ success : true ,
762+ message : 'File converted successfully' ,
763+ originalFilename : req . file . originalname ,
764+ suggestedFilenames : suggestedFilenames ,
765+ data : {
766+ yaml : convertedData . yaml || convertedData ,
767+ json : convertedData . json || JSON . stringify ( convertedData , null , 2 ) ,
768+ original : originalData
769+ } ,
770+ conversionTime : new Date ( ) . toISOString ( ) ,
771+ raceConditionEliminated : true
772+ } ) ;
773+
774+ } catch ( error ) {
775+ console . error ( '[SERVER /upload-and-convert] Conversion error:' , error ) ;
776+ return res . status ( 500 ) . json ( {
777+ error : 'Conversion failed: ' + error . message ,
778+ originalFilename : req . file ?. originalname
779+ } ) ;
780+ }
781+ } ) ;
782+ } ) ;
783+
581784// Health check endpoint specifically for upload testing
582785router . get ( '/upload-health' , ( req , res ) => {
583786 const health = {
0 commit comments