@@ -1230,7 +1230,7 @@ export class ODataV4Plugin implements RuntimePlugin {
12301230
12311231 try {
12321232 const contentType = req . headers [ 'content-type' ] || '' ;
1233- const boundaryMatch = contentType . match ( / b o u n d a r y = ( . + ) / ) ;
1233+ const boundaryMatch = contentType . match ( / b o u n d a r y = [ " ' ] ? ( [ ^ " ' ; , \s \r \n ] + ) [ " ' ] ? / ) ;
12341234
12351235 if ( ! boundaryMatch ) {
12361236 this . sendError ( res , 400 , 'Missing multipart boundary in Content-Type' ) ;
@@ -1292,26 +1292,32 @@ export class ODataV4Plugin implements RuntimePlugin {
12921292 const sections = body . split ( `--${ boundary } ` ) . filter ( s => s . trim ( ) && ! s . startsWith ( '--' ) ) ;
12931293
12941294 for ( const section of sections ) {
1295+ // Check if this is a changeset header (contains boundary definition but no HTTP methods)
12951296 if ( section . includes ( 'Content-Type: multipart/mixed' ) ) {
1296- // This is a changeset - parse nested requests
1297- const changesetBoundaryMatch = section . match ( / b o u n d a r y = ( .+ ) / ) ;
1297+ const changesetBoundaryMatch = section . match ( / b o u n d a r y = [ " ' ] ? ( [ ^ " ' ; , \s \r \n ] + ) [ " ' ] ? / ) ;
12981298 if ( changesetBoundaryMatch ) {
1299- const changesetBoundary = changesetBoundaryMatch [ 1 ] . trim ( ) ;
1300- const changesetRequests = this . parseBatchRequest ( section , changesetBoundary ) ;
1301- parts . push ( { type : 'changeset' , requests : changesetRequests } ) ;
1299+ const changesetBoundary = changesetBoundaryMatch [ 1 ] ;
1300+
1301+ // Only recurse if the boundary is different from current boundary
1302+ // This prevents infinite recursion on header-only sections
1303+ if ( changesetBoundary !== boundary ) {
1304+ const changesetRequests = this . parseBatchRequest ( section , changesetBoundary ) ;
1305+ parts . push ( { type : 'changeset' , requests : changesetRequests } ) ;
1306+ }
13021307 }
1303- } else {
1308+ } else if ( section . match ( / ( G E T | P O S T | P A T C H | P U T | D E L E T E ) \s + / ) ) {
13041309 // Parse individual HTTP request
13051310 const httpMatch = section . match ( / ( G E T | P O S T | P A T C H | P U T | D E L E T E ) \s + ( [ ^ \s ] + ) / ) ;
13061311 if ( httpMatch ) {
13071312 const method = httpMatch [ 1 ] ;
13081313 const url = httpMatch [ 2 ] ;
13091314
13101315 // Extract body if present (for POST/PATCH/PUT)
1316+ // Body is the content after the last \r\n\r\n separator
13111317 let requestBody = '' ;
1312- const bodyMatch = section . match ( / \r \n \r \n ( . + ) / s ) ;
1313- if ( bodyMatch ) {
1314- requestBody = bodyMatch [ 1 ] . trim ( ) ;
1318+ const sectionParts = section . split ( ' \r\n\r\n' ) ;
1319+ if ( sectionParts . length > 1 ) {
1320+ requestBody = sectionParts [ sectionParts . length - 1 ] . trim ( ) ;
13151321 }
13161322
13171323 parts . push ( {
0 commit comments