Skip to content

Commit 7dc903c

Browse files
authored
Patch FlightReplyServer with fixes from ReactFlightClient (#35277)
FlightReplyServer are for client->server and ReactFlightClient is for server->client. They're not 100% symmetrical. We did a number of refactors to ReactFlightClient in PRs like #29823 and #33664 to change the structure of the resolution. This PR brings those changes to synchronize the two approaches. Which addresses deep resolution of cycles and deferred error handling. This also fixes a critical security vulnerability.
1 parent 36df5e8 commit 7dc903c

9 files changed

Lines changed: 712 additions & 278 deletions

File tree

‎packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -344,31 +344,42 @@ function decodeReplyFromBusboy<T>(
344344
// we queue any fields we receive until the previous file is done.
345345
queuedFields.push(name,value);
346346
}else{
347-
resolveField(response,name,value);
347+
try{
348+
resolveField(response,name,value);
349+
}catch(error){
350+
busboyStream.destroy(error);
351+
}
348352
}
349353
});
350354
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
351355
if(encoding.toLowerCase()==='base64'){
352-
thrownewError(
353-
"React doesn't accept base64 encoded file uploads because we don't expect "+
354-
"form data passed from a browser to ever encode data that way. If that's "+
355-
'the wrong assumption, we can easily fix it.',
356+
busboyStream.destroy(
357+
newError(
358+
"React doesn't accept base64 encoded file uploads because we don't expect "+
359+
"form data passed from a browser to ever encode data that way. If that's "+
360+
'the wrong assumption, we can easily fix it.',
361+
),
356362
);
363+
return;
357364
}
358365
pendingFiles++;
359366
constfile=resolveFileInfo(response,name,filename,mimeType);
360367
value.on('data',chunk=>{
361368
resolveFileChunk(response,file,chunk);
362369
});
363370
value.on('end',()=>{
364-
resolveFileComplete(response,name,file);
365-
pendingFiles--;
366-
if(pendingFiles===0){
367-
// Release any queued fields
368-
for(leti=0;i<queuedFields.length;i+=2){
369-
resolveField(response,queuedFields[i],queuedFields[i+1]);
371+
try{
372+
resolveFileComplete(response,name,file);
373+
pendingFiles--;
374+
if(pendingFiles===0){
375+
// Release any queued fields
376+
for(leti=0;i<queuedFields.length;i+=2){
377+
resolveField(response,queuedFields[i],queuedFields[i+1]);
378+
}
379+
queuedFields.length=0;
370380
}
371-
queuedFields.length=0;
381+
}catch(error){
382+
busboyStream.destroy(error);
372383
}
373384
});
374385
});

‎packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
}from'../shared/ReactFlightImportMetadata';
2020
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2121

22+
importhasOwnPropertyfrom'shared/hasOwnProperty';
23+
2224
exporttypeServerManifest={
2325
[string]: Array<string>,
2426
};
@@ -78,7 +80,10 @@ export function preloadModule<T>(
7880

7981
exportfunctionrequireModule<T>(metadata: ClientReference<T>): T{
8082
const moduleExports =parcelRequire(metadata[ID]);
81-
returnmoduleExports[metadata[NAME]];
83+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
84+
returnmoduleExports[metadata[NAME]];
85+
}
86+
return(undefined: any);
8287
}
8388

8489
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -572,31 +572,42 @@ export function decodeReplyFromBusboy<T>(
572572
// we queue any fields we receive until the previous file is done.
573573
queuedFields.push(name,value);
574574
}else{
575-
resolveField(response,name,value);
575+
try{
576+
resolveField(response,name,value);
577+
}catch(error){
578+
busboyStream.destroy(error);
579+
}
576580
}
577581
});
578582
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
579583
if(encoding.toLowerCase()==='base64'){
580-
thrownewError(
581-
"React doesn't accept base64 encoded file uploads because we don't expect "+
582-
"form data passed from a browser to ever encode data that way. If that's "+
583-
'the wrong assumption, we can easily fix it.',
584+
busboyStream.destroy(
585+
newError(
586+
"React doesn't accept base64 encoded file uploads because we don't expect "+
587+
"form data passed from a browser to ever encode data that way. If that's "+
588+
'the wrong assumption, we can easily fix it.',
589+
),
584590
);
591+
return;
585592
}
586593
pendingFiles++;
587594
constfile=resolveFileInfo(response,name,filename,mimeType);
588595
value.on('data',chunk=>{
589596
resolveFileChunk(response,file,chunk);
590597
});
591598
value.on('end',()=>{
592-
resolveFileComplete(response,name,file);
593-
pendingFiles--;
594-
if(pendingFiles===0){
595-
// Release any queued fields
596-
for(leti=0;i<queuedFields.length;i+=2){
597-
resolveField(response,queuedFields[i],queuedFields[i+1]);
599+
try{
600+
resolveFileComplete(response,name,file);
601+
pendingFiles--;
602+
if(pendingFiles===0){
603+
// Release any queued fields
604+
for(leti=0;i<queuedFields.length;i+=2){
605+
resolveField(response,queuedFields[i],queuedFields[i+1]);
606+
}
607+
queuedFields.length=0;
598608
}
599-
queuedFields.length=0;
609+
}catch(error){
610+
busboyStream.destroy(error);
600611
}
601612
});
602613
});

‎packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -245,7 +247,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
245247
// default property of this if it was an ESM interop module.
246248
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
247249
}
248-
return moduleExports[metadata[NAME]];
250+
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
251+
returnmoduleExports[metadata[NAME]];
252+
}
253+
return (undefined: any);
249254
}
250255

251256
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
}from'../shared/ReactFlightImportMetadata';
2525
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2626

27+
importhasOwnPropertyfrom'shared/hasOwnProperty';
28+
2729
exporttypeServerConsumerModuleMap={
2830
[clientId: string]: {
2931
[clientExportName: string]: ClientReference<any>,
@@ -158,7 +160,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
158160
// default property of this if it was an ESM interop module.
159161
returnmoduleExports.default;
160162
}
161-
returnmoduleExports[metadata.name];
163+
if(hasOwnProperty.call(moduleExports,metadata.name)){
164+
returnmoduleExports[metadata.name];
165+
}
166+
return(undefined: any);
162167
}
163168

164169
exportfunctiongetModuleDebugInfo<T>(metadata: ClientReference<T>): null{

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -253,7 +255,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
253255
// default property of this if it was an ESM interop module.
254256
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
255257
}
256-
returnmoduleExports[metadata[NAME]];
258+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
259+
returnmoduleExports[metadata[NAME]];
260+
}
261+
return(undefined: any);
257262
}
258263

259264
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Patch FlightReplyServer with fixes from ReactFlightClient (#35277) · react/react@7dc903c · GitHub
Skip to content

Commit 7dc903c

Browse files
authored
Patch FlightReplyServer with fixes from ReactFlightClient (#35277)
FlightReplyServer are for client->server and ReactFlightClient is for server->client. They're not 100% symmetrical. We did a number of refactors to ReactFlightClient in PRs like #29823 and #33664 to change the structure of the resolution. This PR brings those changes to synchronize the two approaches. Which addresses deep resolution of cycles and deferred error handling. This also fixes a critical security vulnerability.
1 parent 36df5e8 commit 7dc903c

9 files changed

Lines changed: 712 additions & 278 deletions

File tree

‎packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -344,31 +344,42 @@ function decodeReplyFromBusboy<T>(
344344
// we queue any fields we receive until the previous file is done.
345345
queuedFields.push(name,value);
346346
}else{
347-
resolveField(response,name,value);
347+
try{
348+
resolveField(response,name,value);
349+
}catch(error){
350+
busboyStream.destroy(error);
351+
}
348352
}
349353
});
350354
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
351355
if(encoding.toLowerCase()==='base64'){
352-
thrownewError(
353-
"React doesn't accept base64 encoded file uploads because we don't expect "+
354-
"form data passed from a browser to ever encode data that way. If that's "+
355-
'the wrong assumption, we can easily fix it.',
356+
busboyStream.destroy(
357+
newError(
358+
"React doesn't accept base64 encoded file uploads because we don't expect "+
359+
"form data passed from a browser to ever encode data that way. If that's "+
360+
'the wrong assumption, we can easily fix it.',
361+
),
356362
);
363+
return;
357364
}
358365
pendingFiles++;
359366
constfile=resolveFileInfo(response,name,filename,mimeType);
360367
value.on('data',chunk=>{
361368
resolveFileChunk(response,file,chunk);
362369
});
363370
value.on('end',()=>{
364-
resolveFileComplete(response,name,file);
365-
pendingFiles--;
366-
if(pendingFiles===0){
367-
// Release any queued fields
368-
for(leti=0;i<queuedFields.length;i+=2){
369-
resolveField(response,queuedFields[i],queuedFields[i+1]);
371+
try{
372+
resolveFileComplete(response,name,file);
373+
pendingFiles--;
374+
if(pendingFiles===0){
375+
// Release any queued fields
376+
for(leti=0;i<queuedFields.length;i+=2){
377+
resolveField(response,queuedFields[i],queuedFields[i+1]);
378+
}
379+
queuedFields.length=0;
370380
}
371-
queuedFields.length=0;
381+
}catch(error){
382+
busboyStream.destroy(error);
372383
}
373384
});
374385
});

‎packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
}from'../shared/ReactFlightImportMetadata';
2020
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2121

22+
importhasOwnPropertyfrom'shared/hasOwnProperty';
23+
2224
exporttypeServerManifest={
2325
[string]: Array<string>,
2426
};
@@ -78,7 +80,10 @@ export function preloadModule<T>(
7880

7981
exportfunctionrequireModule<T>(metadata: ClientReference<T>): T{
8082
const moduleExports =parcelRequire(metadata[ID]);
81-
returnmoduleExports[metadata[NAME]];
83+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
84+
returnmoduleExports[metadata[NAME]];
85+
}
86+
return(undefined: any);
8287
}
8388

8489
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -572,31 +572,42 @@ export function decodeReplyFromBusboy<T>(
572572
// we queue any fields we receive until the previous file is done.
573573
queuedFields.push(name,value);
574574
}else{
575-
resolveField(response,name,value);
575+
try{
576+
resolveField(response,name,value);
577+
}catch(error){
578+
busboyStream.destroy(error);
579+
}
576580
}
577581
});
578582
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
579583
if(encoding.toLowerCase()==='base64'){
580-
thrownewError(
581-
"React doesn't accept base64 encoded file uploads because we don't expect "+
582-
"form data passed from a browser to ever encode data that way. If that's "+
583-
'the wrong assumption, we can easily fix it.',
584+
busboyStream.destroy(
585+
newError(
586+
"React doesn't accept base64 encoded file uploads because we don't expect "+
587+
"form data passed from a browser to ever encode data that way. If that's "+
588+
'the wrong assumption, we can easily fix it.',
589+
),
584590
);
591+
return;
585592
}
586593
pendingFiles++;
587594
constfile=resolveFileInfo(response,name,filename,mimeType);
588595
value.on('data',chunk=>{
589596
resolveFileChunk(response,file,chunk);
590597
});
591598
value.on('end',()=>{
592-
resolveFileComplete(response,name,file);
593-
pendingFiles--;
594-
if(pendingFiles===0){
595-
// Release any queued fields
596-
for(leti=0;i<queuedFields.length;i+=2){
597-
resolveField(response,queuedFields[i],queuedFields[i+1]);
599+
try{
600+
resolveFileComplete(response,name,file);
601+
pendingFiles--;
602+
if(pendingFiles===0){
603+
// Release any queued fields
604+
for(leti=0;i<queuedFields.length;i+=2){
605+
resolveField(response,queuedFields[i],queuedFields[i+1]);
606+
}
607+
queuedFields.length=0;
598608
}
599-
queuedFields.length=0;
609+
}catch(error){
610+
busboyStream.destroy(error);
600611
}
601612
});
602613
});

‎packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -245,7 +247,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
245247
// default property of this if it was an ESM interop module.
246248
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
247249
}
248-
return moduleExports[metadata[NAME]];
250+
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
251+
returnmoduleExports[metadata[NAME]];
252+
}
253+
return (undefined: any);
249254
}
250255

251256
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
}from'../shared/ReactFlightImportMetadata';
2525
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2626

27+
importhasOwnPropertyfrom'shared/hasOwnProperty';
28+
2729
exporttypeServerConsumerModuleMap={
2830
[clientId: string]: {
2931
[clientExportName: string]: ClientReference<any>,
@@ -158,7 +160,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
158160
// default property of this if it was an ESM interop module.
159161
returnmoduleExports.default;
160162
}
161-
returnmoduleExports[metadata.name];
163+
if(hasOwnProperty.call(moduleExports,metadata.name)){
164+
returnmoduleExports[metadata.name];
165+
}
166+
return(undefined: any);
162167
}
163168

164169
exportfunctiongetModuleDebugInfo<T>(metadata: ClientReference<T>): null{

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -253,7 +255,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
253255
// default property of this if it was an ESM interop module.
254256
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
255257
}
256-
returnmoduleExports[metadata[NAME]];
258+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
259+
returnmoduleExports[metadata[NAME]];
260+
}
261+
return(undefined: any);
257262
}
258263

259264
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Patch FlightReplyServer with fixes from ReactFlightClient (#35277) · react/react@7dc903c · GitHub
Skip to content

Commit 7dc903c

Browse files
authored
Patch FlightReplyServer with fixes from ReactFlightClient (#35277)
FlightReplyServer are for client->server and ReactFlightClient is for server->client. They're not 100% symmetrical. We did a number of refactors to ReactFlightClient in PRs like #29823 and #33664 to change the structure of the resolution. This PR brings those changes to synchronize the two approaches. Which addresses deep resolution of cycles and deferred error handling. This also fixes a critical security vulnerability.
1 parent 36df5e8 commit 7dc903c

9 files changed

Lines changed: 712 additions & 278 deletions

File tree

‎packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -344,31 +344,42 @@ function decodeReplyFromBusboy<T>(
344344
// we queue any fields we receive until the previous file is done.
345345
queuedFields.push(name,value);
346346
}else{
347-
resolveField(response,name,value);
347+
try{
348+
resolveField(response,name,value);
349+
}catch(error){
350+
busboyStream.destroy(error);
351+
}
348352
}
349353
});
350354
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
351355
if(encoding.toLowerCase()==='base64'){
352-
thrownewError(
353-
"React doesn't accept base64 encoded file uploads because we don't expect "+
354-
"form data passed from a browser to ever encode data that way. If that's "+
355-
'the wrong assumption, we can easily fix it.',
356+
busboyStream.destroy(
357+
newError(
358+
"React doesn't accept base64 encoded file uploads because we don't expect "+
359+
"form data passed from a browser to ever encode data that way. If that's "+
360+
'the wrong assumption, we can easily fix it.',
361+
),
356362
);
363+
return;
357364
}
358365
pendingFiles++;
359366
constfile=resolveFileInfo(response,name,filename,mimeType);
360367
value.on('data',chunk=>{
361368
resolveFileChunk(response,file,chunk);
362369
});
363370
value.on('end',()=>{
364-
resolveFileComplete(response,name,file);
365-
pendingFiles--;
366-
if(pendingFiles===0){
367-
// Release any queued fields
368-
for(leti=0;i<queuedFields.length;i+=2){
369-
resolveField(response,queuedFields[i],queuedFields[i+1]);
371+
try{
372+
resolveFileComplete(response,name,file);
373+
pendingFiles--;
374+
if(pendingFiles===0){
375+
// Release any queued fields
376+
for(leti=0;i<queuedFields.length;i+=2){
377+
resolveField(response,queuedFields[i],queuedFields[i+1]);
378+
}
379+
queuedFields.length=0;
370380
}
371-
queuedFields.length=0;
381+
}catch(error){
382+
busboyStream.destroy(error);
372383
}
373384
});
374385
});

‎packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
}from'../shared/ReactFlightImportMetadata';
2020
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2121

22+
importhasOwnPropertyfrom'shared/hasOwnProperty';
23+
2224
exporttypeServerManifest={
2325
[string]: Array<string>,
2426
};
@@ -78,7 +80,10 @@ export function preloadModule<T>(
7880

7981
exportfunctionrequireModule<T>(metadata: ClientReference<T>): T{
8082
const moduleExports =parcelRequire(metadata[ID]);
81-
returnmoduleExports[metadata[NAME]];
83+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
84+
returnmoduleExports[metadata[NAME]];
85+
}
86+
return(undefined: any);
8287
}
8388

8489
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -572,31 +572,42 @@ export function decodeReplyFromBusboy<T>(
572572
// we queue any fields we receive until the previous file is done.
573573
queuedFields.push(name,value);
574574
}else{
575-
resolveField(response,name,value);
575+
try{
576+
resolveField(response,name,value);
577+
}catch(error){
578+
busboyStream.destroy(error);
579+
}
576580
}
577581
});
578582
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
579583
if(encoding.toLowerCase()==='base64'){
580-
thrownewError(
581-
"React doesn't accept base64 encoded file uploads because we don't expect "+
582-
"form data passed from a browser to ever encode data that way. If that's "+
583-
'the wrong assumption, we can easily fix it.',
584+
busboyStream.destroy(
585+
newError(
586+
"React doesn't accept base64 encoded file uploads because we don't expect "+
587+
"form data passed from a browser to ever encode data that way. If that's "+
588+
'the wrong assumption, we can easily fix it.',
589+
),
584590
);
591+
return;
585592
}
586593
pendingFiles++;
587594
constfile=resolveFileInfo(response,name,filename,mimeType);
588595
value.on('data',chunk=>{
589596
resolveFileChunk(response,file,chunk);
590597
});
591598
value.on('end',()=>{
592-
resolveFileComplete(response,name,file);
593-
pendingFiles--;
594-
if(pendingFiles===0){
595-
// Release any queued fields
596-
for(leti=0;i<queuedFields.length;i+=2){
597-
resolveField(response,queuedFields[i],queuedFields[i+1]);
599+
try{
600+
resolveFileComplete(response,name,file);
601+
pendingFiles--;
602+
if(pendingFiles===0){
603+
// Release any queued fields
604+
for(leti=0;i<queuedFields.length;i+=2){
605+
resolveField(response,queuedFields[i],queuedFields[i+1]);
606+
}
607+
queuedFields.length=0;
598608
}
599-
queuedFields.length=0;
609+
}catch(error){
610+
busboyStream.destroy(error);
600611
}
601612
});
602613
});

‎packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -245,7 +247,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
245247
// default property of this if it was an ESM interop module.
246248
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
247249
}
248-
return moduleExports[metadata[NAME]];
250+
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
251+
returnmoduleExports[metadata[NAME]];
252+
}
253+
return (undefined: any);
249254
}
250255

251256
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
}from'../shared/ReactFlightImportMetadata';
2525
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2626

27+
importhasOwnPropertyfrom'shared/hasOwnProperty';
28+
2729
exporttypeServerConsumerModuleMap={
2830
[clientId: string]: {
2931
[clientExportName: string]: ClientReference<any>,
@@ -158,7 +160,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
158160
// default property of this if it was an ESM interop module.
159161
returnmoduleExports.default;
160162
}
161-
returnmoduleExports[metadata.name];
163+
if(hasOwnProperty.call(moduleExports,metadata.name)){
164+
returnmoduleExports[metadata.name];
165+
}
166+
return(undefined: any);
162167
}
163168

164169
exportfunctiongetModuleDebugInfo<T>(metadata: ClientReference<T>): null{

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -253,7 +255,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
253255
// default property of this if it was an ESM interop module.
254256
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
255257
}
256-
returnmoduleExports[metadata[NAME]];
258+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
259+
returnmoduleExports[metadata[NAME]];
260+
}
261+
return(undefined: any);
257262
}
258263

259264
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Patch FlightReplyServer with fixes from ReactFlightClient (#35277) · react/react@7dc903c · GitHub
Skip to content

Commit 7dc903c

Browse files
authored
Patch FlightReplyServer with fixes from ReactFlightClient (#35277)
FlightReplyServer are for client->server and ReactFlightClient is for server->client. They're not 100% symmetrical. We did a number of refactors to ReactFlightClient in PRs like #29823 and #33664 to change the structure of the resolution. This PR brings those changes to synchronize the two approaches. Which addresses deep resolution of cycles and deferred error handling. This also fixes a critical security vulnerability.
1 parent 36df5e8 commit 7dc903c

9 files changed

Lines changed: 712 additions & 278 deletions

File tree

‎packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -344,31 +344,42 @@ function decodeReplyFromBusboy<T>(
344344
// we queue any fields we receive until the previous file is done.
345345
queuedFields.push(name,value);
346346
}else{
347-
resolveField(response,name,value);
347+
try{
348+
resolveField(response,name,value);
349+
}catch(error){
350+
busboyStream.destroy(error);
351+
}
348352
}
349353
});
350354
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
351355
if(encoding.toLowerCase()==='base64'){
352-
thrownewError(
353-
"React doesn't accept base64 encoded file uploads because we don't expect "+
354-
"form data passed from a browser to ever encode data that way. If that's "+
355-
'the wrong assumption, we can easily fix it.',
356+
busboyStream.destroy(
357+
newError(
358+
"React doesn't accept base64 encoded file uploads because we don't expect "+
359+
"form data passed from a browser to ever encode data that way. If that's "+
360+
'the wrong assumption, we can easily fix it.',
361+
),
356362
);
363+
return;
357364
}
358365
pendingFiles++;
359366
constfile=resolveFileInfo(response,name,filename,mimeType);
360367
value.on('data',chunk=>{
361368
resolveFileChunk(response,file,chunk);
362369
});
363370
value.on('end',()=>{
364-
resolveFileComplete(response,name,file);
365-
pendingFiles--;
366-
if(pendingFiles===0){
367-
// Release any queued fields
368-
for(leti=0;i<queuedFields.length;i+=2){
369-
resolveField(response,queuedFields[i],queuedFields[i+1]);
371+
try{
372+
resolveFileComplete(response,name,file);
373+
pendingFiles--;
374+
if(pendingFiles===0){
375+
// Release any queued fields
376+
for(leti=0;i<queuedFields.length;i+=2){
377+
resolveField(response,queuedFields[i],queuedFields[i+1]);
378+
}
379+
queuedFields.length=0;
370380
}
371-
queuedFields.length=0;
381+
}catch(error){
382+
busboyStream.destroy(error);
372383
}
373384
});
374385
});

‎packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
}from'../shared/ReactFlightImportMetadata';
2020
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2121

22+
importhasOwnPropertyfrom'shared/hasOwnProperty';
23+
2224
exporttypeServerManifest={
2325
[string]: Array<string>,
2426
};
@@ -78,7 +80,10 @@ export function preloadModule<T>(
7880

7981
exportfunctionrequireModule<T>(metadata: ClientReference<T>): T{
8082
const moduleExports =parcelRequire(metadata[ID]);
81-
returnmoduleExports[metadata[NAME]];
83+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
84+
returnmoduleExports[metadata[NAME]];
85+
}
86+
return(undefined: any);
8287
}
8388

8489
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -572,31 +572,42 @@ export function decodeReplyFromBusboy<T>(
572572
// we queue any fields we receive until the previous file is done.
573573
queuedFields.push(name,value);
574574
}else{
575-
resolveField(response,name,value);
575+
try{
576+
resolveField(response,name,value);
577+
}catch(error){
578+
busboyStream.destroy(error);
579+
}
576580
}
577581
});
578582
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
579583
if(encoding.toLowerCase()==='base64'){
580-
thrownewError(
581-
"React doesn't accept base64 encoded file uploads because we don't expect "+
582-
"form data passed from a browser to ever encode data that way. If that's "+
583-
'the wrong assumption, we can easily fix it.',
584+
busboyStream.destroy(
585+
newError(
586+
"React doesn't accept base64 encoded file uploads because we don't expect "+
587+
"form data passed from a browser to ever encode data that way. If that's "+
588+
'the wrong assumption, we can easily fix it.',
589+
),
584590
);
591+
return;
585592
}
586593
pendingFiles++;
587594
constfile=resolveFileInfo(response,name,filename,mimeType);
588595
value.on('data',chunk=>{
589596
resolveFileChunk(response,file,chunk);
590597
});
591598
value.on('end',()=>{
592-
resolveFileComplete(response,name,file);
593-
pendingFiles--;
594-
if(pendingFiles===0){
595-
// Release any queued fields
596-
for(leti=0;i<queuedFields.length;i+=2){
597-
resolveField(response,queuedFields[i],queuedFields[i+1]);
599+
try{
600+
resolveFileComplete(response,name,file);
601+
pendingFiles--;
602+
if(pendingFiles===0){
603+
// Release any queued fields
604+
for(leti=0;i<queuedFields.length;i+=2){
605+
resolveField(response,queuedFields[i],queuedFields[i+1]);
606+
}
607+
queuedFields.length=0;
598608
}
599-
queuedFields.length=0;
609+
}catch(error){
610+
busboyStream.destroy(error);
600611
}
601612
});
602613
});

‎packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -245,7 +247,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
245247
// default property of this if it was an ESM interop module.
246248
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
247249
}
248-
return moduleExports[metadata[NAME]];
250+
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
251+
returnmoduleExports[metadata[NAME]];
252+
}
253+
return (undefined: any);
249254
}
250255

251256
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
}from'../shared/ReactFlightImportMetadata';
2525
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2626

27+
importhasOwnPropertyfrom'shared/hasOwnProperty';
28+
2729
exporttypeServerConsumerModuleMap={
2830
[clientId: string]: {
2931
[clientExportName: string]: ClientReference<any>,
@@ -158,7 +160,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
158160
// default property of this if it was an ESM interop module.
159161
returnmoduleExports.default;
160162
}
161-
returnmoduleExports[metadata.name];
163+
if(hasOwnProperty.call(moduleExports,metadata.name)){
164+
returnmoduleExports[metadata.name];
165+
}
166+
return(undefined: any);
162167
}
163168

164169
exportfunctiongetModuleDebugInfo<T>(metadata: ClientReference<T>): null{

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -253,7 +255,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
253255
// default property of this if it was an ESM interop module.
254256
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
255257
}
256-
returnmoduleExports[metadata[NAME]];
258+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
259+
returnmoduleExports[metadata[NAME]];
260+
}
261+
return(undefined: any);
257262
}
258263

259264
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Patch FlightReplyServer with fixes from ReactFlightClient (#35277) · react/react@7dc903c · GitHub
Skip to content

Commit 7dc903c

Browse files
authored
Patch FlightReplyServer with fixes from ReactFlightClient (#35277)
FlightReplyServer are for client->server and ReactFlightClient is for server->client. They're not 100% symmetrical. We did a number of refactors to ReactFlightClient in PRs like #29823 and #33664 to change the structure of the resolution. This PR brings those changes to synchronize the two approaches. Which addresses deep resolution of cycles and deferred error handling. This also fixes a critical security vulnerability.
1 parent 36df5e8 commit 7dc903c

9 files changed

Lines changed: 712 additions & 278 deletions

File tree

‎packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -344,31 +344,42 @@ function decodeReplyFromBusboy<T>(
344344
// we queue any fields we receive until the previous file is done.
345345
queuedFields.push(name,value);
346346
}else{
347-
resolveField(response,name,value);
347+
try{
348+
resolveField(response,name,value);
349+
}catch(error){
350+
busboyStream.destroy(error);
351+
}
348352
}
349353
});
350354
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
351355
if(encoding.toLowerCase()==='base64'){
352-
thrownewError(
353-
"React doesn't accept base64 encoded file uploads because we don't expect "+
354-
"form data passed from a browser to ever encode data that way. If that's "+
355-
'the wrong assumption, we can easily fix it.',
356+
busboyStream.destroy(
357+
newError(
358+
"React doesn't accept base64 encoded file uploads because we don't expect "+
359+
"form data passed from a browser to ever encode data that way. If that's "+
360+
'the wrong assumption, we can easily fix it.',
361+
),
356362
);
363+
return;
357364
}
358365
pendingFiles++;
359366
constfile=resolveFileInfo(response,name,filename,mimeType);
360367
value.on('data',chunk=>{
361368
resolveFileChunk(response,file,chunk);
362369
});
363370
value.on('end',()=>{
364-
resolveFileComplete(response,name,file);
365-
pendingFiles--;
366-
if(pendingFiles===0){
367-
// Release any queued fields
368-
for(leti=0;i<queuedFields.length;i+=2){
369-
resolveField(response,queuedFields[i],queuedFields[i+1]);
371+
try{
372+
resolveFileComplete(response,name,file);
373+
pendingFiles--;
374+
if(pendingFiles===0){
375+
// Release any queued fields
376+
for(leti=0;i<queuedFields.length;i+=2){
377+
resolveField(response,queuedFields[i],queuedFields[i+1]);
378+
}
379+
queuedFields.length=0;
370380
}
371-
queuedFields.length=0;
381+
}catch(error){
382+
busboyStream.destroy(error);
372383
}
373384
});
374385
});

‎packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
}from'../shared/ReactFlightImportMetadata';
2020
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2121

22+
importhasOwnPropertyfrom'shared/hasOwnProperty';
23+
2224
exporttypeServerManifest={
2325
[string]: Array<string>,
2426
};
@@ -78,7 +80,10 @@ export function preloadModule<T>(
7880

7981
exportfunctionrequireModule<T>(metadata: ClientReference<T>): T{
8082
const moduleExports =parcelRequire(metadata[ID]);
81-
returnmoduleExports[metadata[NAME]];
83+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
84+
returnmoduleExports[metadata[NAME]];
85+
}
86+
return(undefined: any);
8287
}
8388

8489
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -572,31 +572,42 @@ export function decodeReplyFromBusboy<T>(
572572
// we queue any fields we receive until the previous file is done.
573573
queuedFields.push(name,value);
574574
}else{
575-
resolveField(response,name,value);
575+
try{
576+
resolveField(response,name,value);
577+
}catch(error){
578+
busboyStream.destroy(error);
579+
}
576580
}
577581
});
578582
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
579583
if(encoding.toLowerCase()==='base64'){
580-
thrownewError(
581-
"React doesn't accept base64 encoded file uploads because we don't expect "+
582-
"form data passed from a browser to ever encode data that way. If that's "+
583-
'the wrong assumption, we can easily fix it.',
584+
busboyStream.destroy(
585+
newError(
586+
"React doesn't accept base64 encoded file uploads because we don't expect "+
587+
"form data passed from a browser to ever encode data that way. If that's "+
588+
'the wrong assumption, we can easily fix it.',
589+
),
584590
);
591+
return;
585592
}
586593
pendingFiles++;
587594
constfile=resolveFileInfo(response,name,filename,mimeType);
588595
value.on('data',chunk=>{
589596
resolveFileChunk(response,file,chunk);
590597
});
591598
value.on('end',()=>{
592-
resolveFileComplete(response,name,file);
593-
pendingFiles--;
594-
if(pendingFiles===0){
595-
// Release any queued fields
596-
for(leti=0;i<queuedFields.length;i+=2){
597-
resolveField(response,queuedFields[i],queuedFields[i+1]);
599+
try{
600+
resolveFileComplete(response,name,file);
601+
pendingFiles--;
602+
if(pendingFiles===0){
603+
// Release any queued fields
604+
for(leti=0;i<queuedFields.length;i+=2){
605+
resolveField(response,queuedFields[i],queuedFields[i+1]);
606+
}
607+
queuedFields.length=0;
598608
}
599-
queuedFields.length=0;
609+
}catch(error){
610+
busboyStream.destroy(error);
600611
}
601612
});
602613
});

‎packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -245,7 +247,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
245247
// default property of this if it was an ESM interop module.
246248
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
247249
}
248-
return moduleExports[metadata[NAME]];
250+
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
251+
returnmoduleExports[metadata[NAME]];
252+
}
253+
return (undefined: any);
249254
}
250255

251256
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
}from'../shared/ReactFlightImportMetadata';
2525
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2626

27+
importhasOwnPropertyfrom'shared/hasOwnProperty';
28+
2729
exporttypeServerConsumerModuleMap={
2830
[clientId: string]: {
2931
[clientExportName: string]: ClientReference<any>,
@@ -158,7 +160,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
158160
// default property of this if it was an ESM interop module.
159161
returnmoduleExports.default;
160162
}
161-
returnmoduleExports[metadata.name];
163+
if(hasOwnProperty.call(moduleExports,metadata.name)){
164+
returnmoduleExports[metadata.name];
165+
}
166+
return(undefined: any);
162167
}
163168

164169
exportfunctiongetModuleDebugInfo<T>(metadata: ClientReference<T>): null{

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -253,7 +255,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
253255
// default property of this if it was an ESM interop module.
254256
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
255257
}
256-
returnmoduleExports[metadata[NAME]];
258+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
259+
returnmoduleExports[metadata[NAME]];
260+
}
261+
return(undefined: any);
257262
}
258263

259264
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Patch FlightReplyServer with fixes from ReactFlightClient (#35277) · react/react@7dc903c · GitHub
Skip to content

Commit 7dc903c

Browse files
authored
Patch FlightReplyServer with fixes from ReactFlightClient (#35277)
FlightReplyServer are for client->server and ReactFlightClient is for server->client. They're not 100% symmetrical. We did a number of refactors to ReactFlightClient in PRs like #29823 and #33664 to change the structure of the resolution. This PR brings those changes to synchronize the two approaches. Which addresses deep resolution of cycles and deferred error handling. This also fixes a critical security vulnerability.
1 parent 36df5e8 commit 7dc903c

9 files changed

Lines changed: 712 additions & 278 deletions

File tree

‎packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -344,31 +344,42 @@ function decodeReplyFromBusboy<T>(
344344
// we queue any fields we receive until the previous file is done.
345345
queuedFields.push(name,value);
346346
}else{
347-
resolveField(response,name,value);
347+
try{
348+
resolveField(response,name,value);
349+
}catch(error){
350+
busboyStream.destroy(error);
351+
}
348352
}
349353
});
350354
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
351355
if(encoding.toLowerCase()==='base64'){
352-
thrownewError(
353-
"React doesn't accept base64 encoded file uploads because we don't expect "+
354-
"form data passed from a browser to ever encode data that way. If that's "+
355-
'the wrong assumption, we can easily fix it.',
356+
busboyStream.destroy(
357+
newError(
358+
"React doesn't accept base64 encoded file uploads because we don't expect "+
359+
"form data passed from a browser to ever encode data that way. If that's "+
360+
'the wrong assumption, we can easily fix it.',
361+
),
356362
);
363+
return;
357364
}
358365
pendingFiles++;
359366
constfile=resolveFileInfo(response,name,filename,mimeType);
360367
value.on('data',chunk=>{
361368
resolveFileChunk(response,file,chunk);
362369
});
363370
value.on('end',()=>{
364-
resolveFileComplete(response,name,file);
365-
pendingFiles--;
366-
if(pendingFiles===0){
367-
// Release any queued fields
368-
for(leti=0;i<queuedFields.length;i+=2){
369-
resolveField(response,queuedFields[i],queuedFields[i+1]);
371+
try{
372+
resolveFileComplete(response,name,file);
373+
pendingFiles--;
374+
if(pendingFiles===0){
375+
// Release any queued fields
376+
for(leti=0;i<queuedFields.length;i+=2){
377+
resolveField(response,queuedFields[i],queuedFields[i+1]);
378+
}
379+
queuedFields.length=0;
370380
}
371-
queuedFields.length=0;
381+
}catch(error){
382+
busboyStream.destroy(error);
372383
}
373384
});
374385
});

‎packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
}from'../shared/ReactFlightImportMetadata';
2020
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2121

22+
importhasOwnPropertyfrom'shared/hasOwnProperty';
23+
2224
exporttypeServerManifest={
2325
[string]: Array<string>,
2426
};
@@ -78,7 +80,10 @@ export function preloadModule<T>(
7880

7981
exportfunctionrequireModule<T>(metadata: ClientReference<T>): T{
8082
const moduleExports =parcelRequire(metadata[ID]);
81-
returnmoduleExports[metadata[NAME]];
83+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
84+
returnmoduleExports[metadata[NAME]];
85+
}
86+
return(undefined: any);
8287
}
8388

8489
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -572,31 +572,42 @@ export function decodeReplyFromBusboy<T>(
572572
// we queue any fields we receive until the previous file is done.
573573
queuedFields.push(name,value);
574574
}else{
575-
resolveField(response,name,value);
575+
try{
576+
resolveField(response,name,value);
577+
}catch(error){
578+
busboyStream.destroy(error);
579+
}
576580
}
577581
});
578582
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
579583
if(encoding.toLowerCase()==='base64'){
580-
thrownewError(
581-
"React doesn't accept base64 encoded file uploads because we don't expect "+
582-
"form data passed from a browser to ever encode data that way. If that's "+
583-
'the wrong assumption, we can easily fix it.',
584+
busboyStream.destroy(
585+
newError(
586+
"React doesn't accept base64 encoded file uploads because we don't expect "+
587+
"form data passed from a browser to ever encode data that way. If that's "+
588+
'the wrong assumption, we can easily fix it.',
589+
),
584590
);
591+
return;
585592
}
586593
pendingFiles++;
587594
constfile=resolveFileInfo(response,name,filename,mimeType);
588595
value.on('data',chunk=>{
589596
resolveFileChunk(response,file,chunk);
590597
});
591598
value.on('end',()=>{
592-
resolveFileComplete(response,name,file);
593-
pendingFiles--;
594-
if(pendingFiles===0){
595-
// Release any queued fields
596-
for(leti=0;i<queuedFields.length;i+=2){
597-
resolveField(response,queuedFields[i],queuedFields[i+1]);
599+
try{
600+
resolveFileComplete(response,name,file);
601+
pendingFiles--;
602+
if(pendingFiles===0){
603+
// Release any queued fields
604+
for(leti=0;i<queuedFields.length;i+=2){
605+
resolveField(response,queuedFields[i],queuedFields[i+1]);
606+
}
607+
queuedFields.length=0;
598608
}
599-
queuedFields.length=0;
609+
}catch(error){
610+
busboyStream.destroy(error);
600611
}
601612
});
602613
});

‎packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -245,7 +247,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
245247
// default property of this if it was an ESM interop module.
246248
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
247249
}
248-
return moduleExports[metadata[NAME]];
250+
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
251+
returnmoduleExports[metadata[NAME]];
252+
}
253+
return (undefined: any);
249254
}
250255

251256
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
}from'../shared/ReactFlightImportMetadata';
2525
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2626

27+
importhasOwnPropertyfrom'shared/hasOwnProperty';
28+
2729
exporttypeServerConsumerModuleMap={
2830
[clientId: string]: {
2931
[clientExportName: string]: ClientReference<any>,
@@ -158,7 +160,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
158160
// default property of this if it was an ESM interop module.
159161
returnmoduleExports.default;
160162
}
161-
returnmoduleExports[metadata.name];
163+
if(hasOwnProperty.call(moduleExports,metadata.name)){
164+
returnmoduleExports[metadata.name];
165+
}
166+
return(undefined: any);
162167
}
163168

164169
exportfunctiongetModuleDebugInfo<T>(metadata: ClientReference<T>): null{

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -253,7 +255,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
253255
// default property of this if it was an ESM interop module.
254256
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
255257
}
256-
returnmoduleExports[metadata[NAME]];
258+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
259+
returnmoduleExports[metadata[NAME]];
260+
}
261+
return(undefined: any);
257262
}
258263

259264
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Patch FlightReplyServer with fixes from ReactFlightClient (#35277) · react/react@7dc903c · GitHub
Skip to content

Commit 7dc903c

Browse files
authored
Patch FlightReplyServer with fixes from ReactFlightClient (#35277)
FlightReplyServer are for client->server and ReactFlightClient is for server->client. They're not 100% symmetrical. We did a number of refactors to ReactFlightClient in PRs like #29823 and #33664 to change the structure of the resolution. This PR brings those changes to synchronize the two approaches. Which addresses deep resolution of cycles and deferred error handling. This also fixes a critical security vulnerability.
1 parent 36df5e8 commit 7dc903c

9 files changed

Lines changed: 712 additions & 278 deletions

File tree

‎packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -344,31 +344,42 @@ function decodeReplyFromBusboy<T>(
344344
// we queue any fields we receive until the previous file is done.
345345
queuedFields.push(name,value);
346346
}else{
347-
resolveField(response,name,value);
347+
try{
348+
resolveField(response,name,value);
349+
}catch(error){
350+
busboyStream.destroy(error);
351+
}
348352
}
349353
});
350354
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
351355
if(encoding.toLowerCase()==='base64'){
352-
thrownewError(
353-
"React doesn't accept base64 encoded file uploads because we don't expect "+
354-
"form data passed from a browser to ever encode data that way. If that's "+
355-
'the wrong assumption, we can easily fix it.',
356+
busboyStream.destroy(
357+
newError(
358+
"React doesn't accept base64 encoded file uploads because we don't expect "+
359+
"form data passed from a browser to ever encode data that way. If that's "+
360+
'the wrong assumption, we can easily fix it.',
361+
),
356362
);
363+
return;
357364
}
358365
pendingFiles++;
359366
constfile=resolveFileInfo(response,name,filename,mimeType);
360367
value.on('data',chunk=>{
361368
resolveFileChunk(response,file,chunk);
362369
});
363370
value.on('end',()=>{
364-
resolveFileComplete(response,name,file);
365-
pendingFiles--;
366-
if(pendingFiles===0){
367-
// Release any queued fields
368-
for(leti=0;i<queuedFields.length;i+=2){
369-
resolveField(response,queuedFields[i],queuedFields[i+1]);
371+
try{
372+
resolveFileComplete(response,name,file);
373+
pendingFiles--;
374+
if(pendingFiles===0){
375+
// Release any queued fields
376+
for(leti=0;i<queuedFields.length;i+=2){
377+
resolveField(response,queuedFields[i],queuedFields[i+1]);
378+
}
379+
queuedFields.length=0;
370380
}
371-
queuedFields.length=0;
381+
}catch(error){
382+
busboyStream.destroy(error);
372383
}
373384
});
374385
});

‎packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
}from'../shared/ReactFlightImportMetadata';
2020
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2121

22+
importhasOwnPropertyfrom'shared/hasOwnProperty';
23+
2224
exporttypeServerManifest={
2325
[string]: Array<string>,
2426
};
@@ -78,7 +80,10 @@ export function preloadModule<T>(
7880

7981
exportfunctionrequireModule<T>(metadata: ClientReference<T>): T{
8082
const moduleExports =parcelRequire(metadata[ID]);
81-
returnmoduleExports[metadata[NAME]];
83+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
84+
returnmoduleExports[metadata[NAME]];
85+
}
86+
return(undefined: any);
8287
}
8388

8489
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -572,31 +572,42 @@ export function decodeReplyFromBusboy<T>(
572572
// we queue any fields we receive until the previous file is done.
573573
queuedFields.push(name,value);
574574
}else{
575-
resolveField(response,name,value);
575+
try{
576+
resolveField(response,name,value);
577+
}catch(error){
578+
busboyStream.destroy(error);
579+
}
576580
}
577581
});
578582
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
579583
if(encoding.toLowerCase()==='base64'){
580-
thrownewError(
581-
"React doesn't accept base64 encoded file uploads because we don't expect "+
582-
"form data passed from a browser to ever encode data that way. If that's "+
583-
'the wrong assumption, we can easily fix it.',
584+
busboyStream.destroy(
585+
newError(
586+
"React doesn't accept base64 encoded file uploads because we don't expect "+
587+
"form data passed from a browser to ever encode data that way. If that's "+
588+
'the wrong assumption, we can easily fix it.',
589+
),
584590
);
591+
return;
585592
}
586593
pendingFiles++;
587594
constfile=resolveFileInfo(response,name,filename,mimeType);
588595
value.on('data',chunk=>{
589596
resolveFileChunk(response,file,chunk);
590597
});
591598
value.on('end',()=>{
592-
resolveFileComplete(response,name,file);
593-
pendingFiles--;
594-
if(pendingFiles===0){
595-
// Release any queued fields
596-
for(leti=0;i<queuedFields.length;i+=2){
597-
resolveField(response,queuedFields[i],queuedFields[i+1]);
599+
try{
600+
resolveFileComplete(response,name,file);
601+
pendingFiles--;
602+
if(pendingFiles===0){
603+
// Release any queued fields
604+
for(leti=0;i<queuedFields.length;i+=2){
605+
resolveField(response,queuedFields[i],queuedFields[i+1]);
606+
}
607+
queuedFields.length=0;
598608
}
599-
queuedFields.length=0;
609+
}catch(error){
610+
busboyStream.destroy(error);
600611
}
601612
});
602613
});

‎packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -245,7 +247,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
245247
// default property of this if it was an ESM interop module.
246248
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
247249
}
248-
return moduleExports[metadata[NAME]];
250+
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
251+
returnmoduleExports[metadata[NAME]];
252+
}
253+
return (undefined: any);
249254
}
250255

251256
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
}from'../shared/ReactFlightImportMetadata';
2525
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2626

27+
importhasOwnPropertyfrom'shared/hasOwnProperty';
28+
2729
exporttypeServerConsumerModuleMap={
2830
[clientId: string]: {
2931
[clientExportName: string]: ClientReference<any>,
@@ -158,7 +160,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
158160
// default property of this if it was an ESM interop module.
159161
returnmoduleExports.default;
160162
}
161-
returnmoduleExports[metadata.name];
163+
if(hasOwnProperty.call(moduleExports,metadata.name)){
164+
returnmoduleExports[metadata.name];
165+
}
166+
return(undefined: any);
162167
}
163168

164169
exportfunctiongetModuleDebugInfo<T>(metadata: ClientReference<T>): null{

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -253,7 +255,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
253255
// default property of this if it was an ESM interop module.
254256
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
255257
}
256-
returnmoduleExports[metadata[NAME]];
258+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
259+
returnmoduleExports[metadata[NAME]];
260+
}
261+
return(undefined: any);
257262
}
258263

259264
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Patch FlightReplyServer with fixes from ReactFlightClient (#35277) · react/react@7dc903c · GitHub
Skip to content

Commit 7dc903c

Browse files
authored
Patch FlightReplyServer with fixes from ReactFlightClient (#35277)
FlightReplyServer are for client->server and ReactFlightClient is for server->client. They're not 100% symmetrical. We did a number of refactors to ReactFlightClient in PRs like #29823 and #33664 to change the structure of the resolution. This PR brings those changes to synchronize the two approaches. Which addresses deep resolution of cycles and deferred error handling. This also fixes a critical security vulnerability.
1 parent 36df5e8 commit 7dc903c

9 files changed

Lines changed: 712 additions & 278 deletions

File tree

‎packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -344,31 +344,42 @@ function decodeReplyFromBusboy<T>(
344344
// we queue any fields we receive until the previous file is done.
345345
queuedFields.push(name,value);
346346
}else{
347-
resolveField(response,name,value);
347+
try{
348+
resolveField(response,name,value);
349+
}catch(error){
350+
busboyStream.destroy(error);
351+
}
348352
}
349353
});
350354
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
351355
if(encoding.toLowerCase()==='base64'){
352-
thrownewError(
353-
"React doesn't accept base64 encoded file uploads because we don't expect "+
354-
"form data passed from a browser to ever encode data that way. If that's "+
355-
'the wrong assumption, we can easily fix it.',
356+
busboyStream.destroy(
357+
newError(
358+
"React doesn't accept base64 encoded file uploads because we don't expect "+
359+
"form data passed from a browser to ever encode data that way. If that's "+
360+
'the wrong assumption, we can easily fix it.',
361+
),
356362
);
363+
return;
357364
}
358365
pendingFiles++;
359366
constfile=resolveFileInfo(response,name,filename,mimeType);
360367
value.on('data',chunk=>{
361368
resolveFileChunk(response,file,chunk);
362369
});
363370
value.on('end',()=>{
364-
resolveFileComplete(response,name,file);
365-
pendingFiles--;
366-
if(pendingFiles===0){
367-
// Release any queued fields
368-
for(leti=0;i<queuedFields.length;i+=2){
369-
resolveField(response,queuedFields[i],queuedFields[i+1]);
371+
try{
372+
resolveFileComplete(response,name,file);
373+
pendingFiles--;
374+
if(pendingFiles===0){
375+
// Release any queued fields
376+
for(leti=0;i<queuedFields.length;i+=2){
377+
resolveField(response,queuedFields[i],queuedFields[i+1]);
378+
}
379+
queuedFields.length=0;
370380
}
371-
queuedFields.length=0;
381+
}catch(error){
382+
busboyStream.destroy(error);
372383
}
373384
});
374385
});

‎packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
}from'../shared/ReactFlightImportMetadata';
2020
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2121

22+
importhasOwnPropertyfrom'shared/hasOwnProperty';
23+
2224
exporttypeServerManifest={
2325
[string]: Array<string>,
2426
};
@@ -78,7 +80,10 @@ export function preloadModule<T>(
7880

7981
exportfunctionrequireModule<T>(metadata: ClientReference<T>): T{
8082
const moduleExports =parcelRequire(metadata[ID]);
81-
returnmoduleExports[metadata[NAME]];
83+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
84+
returnmoduleExports[metadata[NAME]];
85+
}
86+
return(undefined: any);
8287
}
8388

8489
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -572,31 +572,42 @@ export function decodeReplyFromBusboy<T>(
572572
// we queue any fields we receive until the previous file is done.
573573
queuedFields.push(name,value);
574574
}else{
575-
resolveField(response,name,value);
575+
try{
576+
resolveField(response,name,value);
577+
}catch(error){
578+
busboyStream.destroy(error);
579+
}
576580
}
577581
});
578582
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
579583
if(encoding.toLowerCase()==='base64'){
580-
thrownewError(
581-
"React doesn't accept base64 encoded file uploads because we don't expect "+
582-
"form data passed from a browser to ever encode data that way. If that's "+
583-
'the wrong assumption, we can easily fix it.',
584+
busboyStream.destroy(
585+
newError(
586+
"React doesn't accept base64 encoded file uploads because we don't expect "+
587+
"form data passed from a browser to ever encode data that way. If that's "+
588+
'the wrong assumption, we can easily fix it.',
589+
),
584590
);
591+
return;
585592
}
586593
pendingFiles++;
587594
constfile=resolveFileInfo(response,name,filename,mimeType);
588595
value.on('data',chunk=>{
589596
resolveFileChunk(response,file,chunk);
590597
});
591598
value.on('end',()=>{
592-
resolveFileComplete(response,name,file);
593-
pendingFiles--;
594-
if(pendingFiles===0){
595-
// Release any queued fields
596-
for(leti=0;i<queuedFields.length;i+=2){
597-
resolveField(response,queuedFields[i],queuedFields[i+1]);
599+
try{
600+
resolveFileComplete(response,name,file);
601+
pendingFiles--;
602+
if(pendingFiles===0){
603+
// Release any queued fields
604+
for(leti=0;i<queuedFields.length;i+=2){
605+
resolveField(response,queuedFields[i],queuedFields[i+1]);
606+
}
607+
queuedFields.length=0;
598608
}
599-
queuedFields.length=0;
609+
}catch(error){
610+
busboyStream.destroy(error);
600611
}
601612
});
602613
});

‎packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -245,7 +247,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
245247
// default property of this if it was an ESM interop module.
246248
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
247249
}
248-
return moduleExports[metadata[NAME]];
250+
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
251+
returnmoduleExports[metadata[NAME]];
252+
}
253+
return (undefined: any);
249254
}
250255

251256
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
}from'../shared/ReactFlightImportMetadata';
2525
import{prepareDestinationWithChunks}from'react-client/src/ReactFlightClientConfig';
2626

27+
importhasOwnPropertyfrom'shared/hasOwnProperty';
28+
2729
exporttypeServerConsumerModuleMap={
2830
[clientId: string]: {
2931
[clientExportName: string]: ClientReference<any>,
@@ -158,7 +160,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
158160
// default property of this if it was an ESM interop module.
159161
returnmoduleExports.default;
160162
}
161-
returnmoduleExports[metadata.name];
163+
if(hasOwnProperty.call(moduleExports,metadata.name)){
164+
returnmoduleExports[metadata.name];
165+
}
166+
return(undefined: any);
162167
}
163168

164169
exportfunctiongetModuleDebugInfo<T>(metadata: ClientReference<T>): null{

‎packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
addChunkDebugInfo,
3535
}from'react-client/src/ReactFlightClientConfig';
3636

37+
importhasOwnPropertyfrom'shared/hasOwnProperty';
38+
3739
exporttypeServerConsumerModuleMap=null|{
3840
[clientId: string]: {
3941
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -253,7 +255,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
253255
// default property of this if it was an ESM interop module.
254256
returnmoduleExports.__esModule ? moduleExports.default : moduleExports;
255257
}
256-
returnmoduleExports[metadata[NAME]];
258+
if(hasOwnProperty.call(moduleExports,metadata[NAME])){
259+
returnmoduleExports[metadata[NAME]];
260+
}
261+
return(undefined: any);
257262
}
258263

259264
exportfunctiongetModuleDebugInfo<T>(

‎packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,42 @@ function decodeReplyFromBusboy<T>(
564564
// we queue any fields we receive until the previous file is done.
565565
queuedFields.push(name,value);
566566
}else{
567-
resolveField(response,name,value);
567+
try{
568+
resolveField(response,name,value);
569+
}catch(error){
570+
busboyStream.destroy(error);
571+
}
568572
}
569573
});
570574
busboyStream.on('file',(name,value,{filename, encoding, mimeType})=>{
571575
if(encoding.toLowerCase()==='base64'){
572-
thrownewError(
573-
"React doesn't accept base64 encoded file uploads because we don't expect "+
574-
"form data passed from a browser to ever encode data that way. If that's "+
575-
'the wrong assumption, we can easily fix it.',
576+
busboyStream.destroy(
577+
newError(
578+
"React doesn't accept base64 encoded file uploads because we don't expect "+
579+
"form data passed from a browser to ever encode data that way. If that's "+
580+
'the wrong assumption, we can easily fix it.',
581+
),
576582
);
583+
return;
577584
}
578585
pendingFiles++;
579586
constfile=resolveFileInfo(response,name,filename,mimeType);
580587
value.on('data',chunk=>{
581588
resolveFileChunk(response,file,chunk);
582589
});
583590
value.on('end',()=>{
584-
resolveFileComplete(response,name,file);
585-
pendingFiles--;
586-
if(pendingFiles===0){
587-
// Release any queued fields
588-
for(leti=0;i<queuedFields.length;i+=2){
589-
resolveField(response,queuedFields[i],queuedFields[i+1]);
591+
try{
592+
resolveFileComplete(response,name,file);
593+
pendingFiles--;
594+
if(pendingFiles===0){
595+
// Release any queued fields
596+
for(leti=0;i<queuedFields.length;i+=2){
597+
resolveField(response,queuedFields[i],queuedFields[i+1]);
598+
}
599+
queuedFields.length=0;
590600
}
591-
queuedFields.length=0;
601+
}catch(error){
602+
busboyStream.destroy(error);
592603
}
593604
});
594605
});

0 commit comments

Comments
 (0)