Repository: sveltejs/kit (js)

Issue:
Asset extension matching not consistent between DEV and PREVIEW
### Describe the bug

Preview build of sveltekit doesn't match asset paths the same way as dev build.

### Reproduction

Put let's say image.PNG inside of static then in index.svelte `<img src="/image.png" />` and let's run npm run dev, then run build and preview and the image won't be displayed anymore.

### Logs

_No response_

### System Info

```shell
System:
    OS: Windows 10 10.0.19041
    CPU: (6) x64 Intel(R) Core(TM) i5-8600K CPU @ 3.60GHz
    Memory: 6.21 GB / 15.94 GB
  Binaries:
    Node: 16.15.0 - C:\Program Files\nodejs\node.EXE
    npm: 8.8.0 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.19041.1023.0), Chromium (97.0.1072.76)
  npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.40
    @sveltejs/kit: next => 1.0.0-next.328
    svelte: ^3.44.0 => 3.48.0
```


### Severity

annoyance

### Additional Information

_No response_

Pull request description:
ensure correct case is used for static assets
closes #4931

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [x] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [x] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. All changesets should be `patch` until SvelteKit 1.0


Patch:
diff --git a/.changeset/lovely-flies-care.md b/.changeset/lovely-flies-care.md
new file mode 100644
index 000000000..34b909c7b
--- /dev/null
+++ b/.changeset/lovely-flies-care.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-netlify': patch
+---
+
+Encode binary responses as base64
diff --git a/.changeset/pretty-actors-chew.md b/.changeset/pretty-actors-chew.md
new file mode 100644
index 000000000..ae6d3019f
--- /dev/null
+++ b/.changeset/pretty-actors-chew.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+ensure static assets are only served if correct case is used
diff --git a/packages/adapter-netlify/src/serverless.js b/packages/adapter-netlify/src/serverless.js
index 7d0c0b013..8c4bcff5d 100644
--- a/packages/adapter-netlify/src/serverless.js
+++ b/packages/adapter-netlify/src/serverless.js
@@ -10,7 +10,7 @@ export function init(manifest) {
 	const server = new Server(manifest);
 
 	return async (event, context) => {
-		const rendered = await server.respond(to_request(event), {
+		const response = await server.respond(to_request(event), {
 			platform: { context },
 			getClientAddress() {
 				return event.headers['x-nf-client-connection-ip'];
@@ -18,25 +18,24 @@ export function init(manifest) {
 		});
 
 		const partial_response = {
-			statusCode: rendered.status,
-			...split_headers(rendered.headers)
+			statusCode: response.status,
+			...split_headers(response.headers)
 		};
 
-		// TODO this is probably wrong now?
-		if (rendered.body instanceof Uint8Array) {
+		if (!is_text(response.headers.get('content-type'))) {
 			// Function responses should be strings (or undefined), and responses with binary
 			// content should be base64 encoded and set isBase64Encoded to true.
 			// https://github.com/netlify/functions/blob/main/src/function/response.ts
 			return {
 				...partial_response,
 				isBase64Encoded: true,
-				body: Buffer.from(rendered.body).toString('base64')
+				body: Buffer.from(await response.arrayBuffer()).toString('base64')
 			};
 		}
 
 		return {
 			...partial_response,
-			body: await rendered.text()
+			body: await response.text()
 		};
 	};
 }
@@ -61,3 +60,23 @@ function to_request(event) {
 
 	return new Request(rawUrl, init);
 }
+
+const text_types = new Set([
+	'application/xml',
+	'application/json',
+	'application/x-www-form-urlencoded',
+	'multipart/form-data'
+]);
+
+/**
+ * Decides how the body should be parsed based on its mime type
+ *
+ * @param {string | undefined | null} content_type The `content-type` header of a request/response.
+ * @returns {boolean}
+ */
+function is_text(content_type) {
+	if (!content_type) return true; // defaults to json
+	const type = content_type.split(';')[0].toLowerCase(); // get the mime type
+
+	return type.startsWith('text/') || type.endsWith('+xml') || text_types.has(type);
+}
diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js
index d0e9fd9a3..92b79f29c 100644
--- a/packages/kit/src/core/dev/plugin.js
+++ b/packages/kit/src/core/dev/plugin.js
@@ -195,9 +195,13 @@ export async function create_plugin(config) {
 							const file = config.kit.files.assets + pathname;
 
 							if (fs.existsSync(file) && !fs.statSync(file).isDirectory()) {
-								req.url = encodeURI(pathname); // don't need query/hash
-								asset_server(req, res);
-								return;
+								const has_correct_case = fs.realpathSync.native(file) === path.resolve(file);
+
+								if (has_correct_case) {
+									req.url = encodeURI(pathname); // don't need query/hash
+									asset_server(req, res);
+									return;
+								}
 							}
 						}
 
diff --git a/packages/kit/src/runtime/server/endpoint.js b/packages/kit/src/runtime/server/endpoint.js
index d29003cbc..739111b6d 100644
--- a/packages/kit/src/runtime/server/endpoint.js
+++ b/packages/kit/src/runtime/server/endpoint.js
@@ -22,7 +22,7 @@ const text_types = new Set([
 ]);
 
 /**
- * Decides how the body should be parsed based on its mime type. Should match what's in parse_body
+ * Decides how the body should be parsed based on its mime type
  *
  * @param {string | undefined | null} content_type The `content-type` header of a request/response.
  * @returns {boolean}


Test patch:
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index ab76155f9..674a12e31 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -2570,6 +2570,11 @@ test.describe.parallel('Static files', () => {
 		const response = await request.get(path);
 		expect(response.status()).toBe(200);
 	});
+
+	test('Filenames are case-sensitive', async ({ request }) => {
+		const response = await request.get('/static.JSON');
+		expect(response.status()).toBe(404);
+	});
 });
 
 test.describe.parallel('Matchers', () => {