Skip to content

Commit

Permalink
fix: encode headers and use charset utf-8 on content-type response he…
Browse files Browse the repository at this point in the history
…aders (#88)

Adds encoding to HTTP headers from the client and also adds
`charset=utf-8` to all HTTP `Content-Type` headers to support exotic
characters in client component and server function paths and also in
outlet names. #84
  • Loading branch information
lazarv authored Dec 5, 2024
1 parent e41db2e commit 3e4f69f
Show file tree
Hide file tree
Showing 8 changed files with 44 additions and 29 deletions.
10 changes: 6 additions & 4 deletions packages/react-server/client/ClientProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export const streamOptions = (outlet, remote) => ({
)
? {}
: {
"React-Server-Action": id,
"React-Server-Action": encodeURIComponent(id),
},
});
emit(target, url, (err, result) => {
Expand All @@ -258,8 +258,8 @@ export const streamOptions = (outlet, remote) => ({
body: formData,
headers: {
accept: "application/json",
"React-Server-Action": id,
"React-Server-Outlet": outlet || PAGE_ROOT,
"React-Server-Action": encodeURIComponent(id),
"React-Server-Outlet": encodeURIComponent(outlet || PAGE_ROOT),
},
}
);
Expand Down Expand Up @@ -304,7 +304,9 @@ function getFlightResponse(url, options = {}) {
accept: `text/x-component${
options.standalone && url !== PAGE_ROOT ? ";standalone" : ""
}${options.remote && url !== PAGE_ROOT ? ";remote" : ""}`,
"React-Server-Outlet": options.outlet || PAGE_ROOT,
"React-Server-Outlet": encodeURIComponent(
options.outlet || PAGE_ROOT
),
...options.headers,
},
}),
Expand Down
4 changes: 2 additions & 2 deletions packages/react-server/lib/handlers/error.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function plainResponse(e) {
return new Response(e?.stack ?? null, {
...httpStatus,
headers: {
"Content-Type": "text/plain",
"Content-Type": "text/plain; charset=utf-8",
...(getContext(HTTP_HEADERS) ?? {}),
},
});
Expand Down Expand Up @@ -164,7 +164,7 @@ export default async function errorHandler(err) {
{
...httpStatus,
headers: {
"Content-Type": "text/html",
"Content-Type": "text/html; charset=utf-8",
...(getContext(HTTP_HEADERS) ?? {}),
},
}
Expand Down
7 changes: 5 additions & 2 deletions packages/react-server/lib/handlers/static.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default async function staticHandler(dir, options = {}) {
if (pathname.startsWith("/@source")) {
return new Response(await readFile(pathname.slice(8), "utf8"), {
headers: {
"content-type": "text/plain",
"content-type": "text/plain; charset=utf-8",
},
});
}
Expand Down Expand Up @@ -178,7 +178,10 @@ export default async function staticHandler(dir, options = {}) {
}
return new Response(res, {
headers: {
"content-type": file.mime,
"content-type":
file.mime.includes("text/") || file.mime === "application/json"
? `${file.mime}; charset=utf-8`
: file.mime,
"content-length": file.stats.size,
etag: file.etag,
"cache-control":
Expand Down
2 changes: 1 addition & 1 deletion packages/react-server/lib/start/ssr-handler.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export default async function ssrHandler(root, options = {}) {
return new Response(e?.stack ?? null, {
...httpStatus,
headers: {
"Content-Type": "text/plain",
"Content-Type": "text/plain; charset=utf-8",
...(getContext(HTTP_HEADERS) ?? {}),
},
});
Expand Down
2 changes: 1 addition & 1 deletion packages/react-server/server/RemoteComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async function RemoteComponentLoader({ url, ttl, request = {}, onError }) {
Origin: url.origin,
...request.headers,
Accept: "text/html;remote",
"React-Server-Outlet": url.toString(),
"React-Server-Outlet": encodeURIComponent(url.toString()),
},
}).catch((e) => {
(onError ?? getContext(LOGGER_CONTEXT)?.error)?.(e);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-server/server/redirects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function redirect(url, status = 302) {
{
status,
headers: {
"content-type": "text/html",
"content-type": "text/html; charset=utf-8",
Location: url,
},
}
Expand Down
42 changes: 26 additions & 16 deletions packages/react-server/server/render-rsc.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,22 @@ export async function render(Component) {
const accept = context.request.headers.get("accept");
const remote = accept.includes(";remote");
const standalone = accept.includes(";standalone") || remote;
const outlet = (
context.request.headers.get("react-server-outlet") ?? "PAGE_ROOT"
).replace(/[^a-zA-Z0-9_]/g, "_");
const outlet = decodeURIComponent(
(
context.request.headers.get("react-server-outlet") ?? "PAGE_ROOT"
).replace(/[^a-zA-Z0-9_]/g, "_")
);

const isFormData = context.request.headers
.get("content-type")
?.includes("multipart/form-data");
let formState;
const serverActionHeader =
context.request.headers.get("react-server-action") ?? null;
const serverActionHeader = decodeURIComponent(
context.request.headers.get("react-server-action") ?? null
);
if (
"POST,PUT,PATCH,DELETE".includes(context.request.method) &&
(serverActionHeader || isFormData)
((serverActionHeader && serverActionHeader !== "null") || isFormData)
) {
let action = async function () {
throw new Error("Server action not found");
Expand Down Expand Up @@ -128,7 +131,7 @@ export async function render(Component) {
);
}

if (serverActionHeader) {
if (serverActionHeader && serverActionHeader !== "null") {
const [serverReferenceModule, serverReferenceName] =
serverActionHeader.split("#");
action = async () => {
Expand Down Expand Up @@ -157,6 +160,11 @@ export async function render(Component) {
}

const { data, actionId, error } = await action();
const httpStatus = getContext(HTTP_STATUS) ?? {
status: 200,
statusText: "OK",
};
const httpHeaders = getContext(HTTP_HEADERS) ?? {};

if (!isFormData) {
if (error) {
Expand All @@ -165,9 +173,10 @@ export async function render(Component) {

return resolve(
new Response(JSON.stringify(data), {
status: 200,
...httpStatus,
headers: {
"content-type": "application/json",
"content-type": "application/json; charset=utf-8",
...httpHeaders,
},
})
);
Expand All @@ -182,10 +191,11 @@ export async function render(Component) {
const [result, key] = formState;
return resolve(
new Response(JSON.stringify(result), {
status: 200,
...httpStatus,
headers: {
"React-Server-Action-Key": key,
"content-type": "application/json",
"React-Server-Action-Key": encodeURIComponent(key),
"content-type": "application/json; charset=utf-8",
...httpHeaders,
},
})
);
Expand Down Expand Up @@ -281,7 +291,7 @@ export async function render(Component) {
status: responseFromCache.status,
statusText: responseFromCache.statusText,
headers: {
"content-type": "text/x-component",
"content-type": "text/x-component; charset=utf-8",
"cache-control":
context.request.headers.get("cache-control") ===
"no-cache"
Expand Down Expand Up @@ -352,7 +362,7 @@ export async function render(Component) {
new Response(stream, {
...httpStatus,
headers: {
"content-type": "text/x-component",
"content-type": "text/x-component; charset=utf-8",
"cache-control":
context.request.headers.get("cache-control") ===
"no-cache"
Expand Down Expand Up @@ -406,7 +416,7 @@ export async function render(Component) {
status: responseFromCache.status,
statusText: responseFromCache.statusText,
headers: {
"content-type": "text/html",
"content-type": "text/html; charset=utf-8",
"cache-control":
context.request.headers.get("cache-control") ===
"no-cache"
Expand Down Expand Up @@ -500,7 +510,7 @@ export async function render(Component) {
new Response(responseStream, {
...httpStatus,
headers: {
"content-type": "text/html",
"content-type": "text/html; charset=utf-8",
"cache-control":
context.request.headers.get("cache-control") ===
"no-cache"
Expand Down
4 changes: 2 additions & 2 deletions packages/react-server/server/request.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ export function rewrite(pathname) {
}

export function useOutlet() {
return (
return decodeURIComponent(
getContext(HTTP_CONTEXT)?.request?.headers?.get("react-server-outlet") ??
"PAGE_ROOT"
"PAGE_ROOT"
);
}

0 comments on commit 3e4f69f

Please sign in to comment.