@remix-run/static-middleware 0.4.9 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/static.d.ts.map +1 -1
- package/dist/lib/static.js +48 -10
- package/package.json +11 -11
- package/src/lib/static.ts +59 -10
package/dist/lib/static.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../src/lib/static.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../src/lib/static.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAEzD,OAAO,EAAkC,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AASnG;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAA;AAE1D;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,cAAc,CAAC;IACnF;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAElC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAA;IAE7C;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,EAAE,CAAA;IAC1B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,UAAU,CAwGtF"}
|
package/dist/lib/static.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
import * as fsp from 'node:fs/promises';
|
|
3
3
|
import { openLazyFile } from '@remix-run/fs';
|
|
4
|
+
import { detectMimeType } from '@remix-run/mime';
|
|
4
5
|
import { createFileResponse as sendFile } from '@remix-run/response/file';
|
|
5
6
|
import { generateDirectoryListing } from "./directory-listing.js";
|
|
6
7
|
/**
|
|
@@ -36,21 +37,42 @@ export function staticFiles(root, options = {}) {
|
|
|
36
37
|
if (filter && !filter(relativePath)) {
|
|
37
38
|
return next();
|
|
38
39
|
}
|
|
40
|
+
let rootRealPath;
|
|
41
|
+
try {
|
|
42
|
+
rootRealPath = await fsp.realpath(root);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return next();
|
|
46
|
+
}
|
|
39
47
|
let targetPath = path.join(root, relativePath);
|
|
40
|
-
let
|
|
48
|
+
let containedTargetPath = await resolveContainedPath(rootRealPath, targetPath);
|
|
49
|
+
if (containedTargetPath == null) {
|
|
50
|
+
return next();
|
|
51
|
+
}
|
|
52
|
+
let file;
|
|
41
53
|
try {
|
|
42
|
-
let stats = await fsp.stat(
|
|
54
|
+
let stats = await fsp.stat(containedTargetPath);
|
|
43
55
|
if (stats.isFile()) {
|
|
44
|
-
|
|
56
|
+
file = {
|
|
57
|
+
realPath: containedTargetPath,
|
|
58
|
+
requestedPath: targetPath,
|
|
59
|
+
};
|
|
45
60
|
}
|
|
46
61
|
else if (stats.isDirectory()) {
|
|
47
62
|
// Try each index file in turn
|
|
48
63
|
for (let indexFile of index) {
|
|
49
64
|
let indexPath = path.join(targetPath, indexFile);
|
|
65
|
+
let containedIndexPath = await resolveContainedPath(rootRealPath, indexPath);
|
|
66
|
+
if (containedIndexPath == null) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
50
69
|
try {
|
|
51
|
-
let indexStats = await fsp.stat(
|
|
70
|
+
let indexStats = await fsp.stat(containedIndexPath);
|
|
52
71
|
if (indexStats.isFile()) {
|
|
53
|
-
|
|
72
|
+
file = {
|
|
73
|
+
realPath: containedIndexPath,
|
|
74
|
+
requestedPath: indexPath,
|
|
75
|
+
};
|
|
54
76
|
break;
|
|
55
77
|
}
|
|
56
78
|
}
|
|
@@ -59,17 +81,20 @@ export function staticFiles(root, options = {}) {
|
|
|
59
81
|
}
|
|
60
82
|
}
|
|
61
83
|
// If no index file found and listFiles is enabled, show directory listing
|
|
62
|
-
if (!
|
|
63
|
-
return generateDirectoryListing(
|
|
84
|
+
if (!file && listFiles) {
|
|
85
|
+
return generateDirectoryListing(containedTargetPath, context.url.pathname);
|
|
64
86
|
}
|
|
65
87
|
}
|
|
66
88
|
}
|
|
67
89
|
catch {
|
|
68
90
|
// Path doesn't exist or isn't accessible, fall through
|
|
69
91
|
}
|
|
70
|
-
if (
|
|
71
|
-
let fileName = path.relative(root,
|
|
72
|
-
let lazyFile = openLazyFile(
|
|
92
|
+
if (file) {
|
|
93
|
+
let fileName = path.relative(root, file.requestedPath);
|
|
94
|
+
let lazyFile = openLazyFile(file.realPath, {
|
|
95
|
+
name: fileName,
|
|
96
|
+
type: detectMimeType(file.requestedPath) ?? '',
|
|
97
|
+
});
|
|
73
98
|
let finalFileOptions = { ...fileOptions };
|
|
74
99
|
// If acceptRanges is a function, evaluate it with the lazyFile
|
|
75
100
|
// Otherwise, pass it directly to sendFile
|
|
@@ -84,3 +109,16 @@ export function staticFiles(root, options = {}) {
|
|
|
84
109
|
return next();
|
|
85
110
|
};
|
|
86
111
|
}
|
|
112
|
+
async function resolveContainedPath(rootRealPath, targetPath) {
|
|
113
|
+
try {
|
|
114
|
+
let realPath = await fsp.realpath(targetPath);
|
|
115
|
+
return isContainedPath(rootRealPath, realPath) ? realPath : null;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function isContainedPath(rootPath, targetPath) {
|
|
122
|
+
let relativePath = path.relative(rootPath, targetPath);
|
|
123
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
|
124
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-run/static-middleware",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
4
4
|
"description": "Middleware for serving static files from the filesystem",
|
|
5
5
|
"author": "Michael Jackson <mjijackson@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,20 +28,20 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^24.6.0",
|
|
30
30
|
"@typescript/native-preview": "7.0.0-dev.20251125.1",
|
|
31
|
-
"@remix-run/assert": "0.2.
|
|
32
|
-
"@remix-run/fetch-router": "0.19.
|
|
33
|
-
"@remix-run/
|
|
34
|
-
"@remix-run/form-data-middleware": "0.3.0",
|
|
35
|
-
"@remix-run/response": "0.3.4",
|
|
31
|
+
"@remix-run/assert": "0.2.1",
|
|
32
|
+
"@remix-run/fetch-router": "0.19.2",
|
|
33
|
+
"@remix-run/form-data-middleware": "0.3.2",
|
|
36
34
|
"@remix-run/mime": "0.4.1",
|
|
37
|
-
"@remix-run/
|
|
35
|
+
"@remix-run/response": "0.3.6",
|
|
36
|
+
"@remix-run/test": "0.4.2",
|
|
37
|
+
"@remix-run/method-override-middleware": "0.1.10"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@remix-run/fetch-router": "^0.19.
|
|
41
|
-
"@remix-run/html-template": "^0.3.0",
|
|
40
|
+
"@remix-run/fetch-router": "^0.19.2",
|
|
42
41
|
"@remix-run/mime": "^0.4.1",
|
|
43
|
-
"@remix-run/fs": "^0.4.
|
|
44
|
-
"@remix-run/response": "^0.3.
|
|
42
|
+
"@remix-run/fs": "^0.4.5",
|
|
43
|
+
"@remix-run/response": "^0.3.6",
|
|
44
|
+
"@remix-run/html-template": "^0.3.1"
|
|
45
45
|
},
|
|
46
46
|
"keywords": [
|
|
47
47
|
"fetch",
|
package/src/lib/static.ts
CHANGED
|
@@ -2,10 +2,16 @@ import * as path from 'node:path'
|
|
|
2
2
|
import * as fsp from 'node:fs/promises'
|
|
3
3
|
import { openLazyFile } from '@remix-run/fs'
|
|
4
4
|
import type { Middleware } from '@remix-run/fetch-router'
|
|
5
|
+
import { detectMimeType } from '@remix-run/mime'
|
|
5
6
|
import { createFileResponse as sendFile, type FileResponseOptions } from '@remix-run/response/file'
|
|
6
7
|
|
|
7
8
|
import { generateDirectoryListing } from './directory-listing.ts'
|
|
8
9
|
|
|
10
|
+
interface StaticFileMatch {
|
|
11
|
+
realPath: string
|
|
12
|
+
requestedPath: string
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
/**
|
|
10
16
|
* Function that determines if HTTP Range requests should be supported for a given file.
|
|
11
17
|
*
|
|
@@ -107,22 +113,45 @@ export function staticFiles(root: string, options: StaticFilesOptions = {}): Mid
|
|
|
107
113
|
return next()
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
let rootRealPath: string
|
|
117
|
+
try {
|
|
118
|
+
rootRealPath = await fsp.realpath(root)
|
|
119
|
+
} catch {
|
|
120
|
+
return next()
|
|
121
|
+
}
|
|
122
|
+
|
|
110
123
|
let targetPath = path.join(root, relativePath)
|
|
111
|
-
let
|
|
124
|
+
let containedTargetPath = await resolveContainedPath(rootRealPath, targetPath)
|
|
125
|
+
if (containedTargetPath == null) {
|
|
126
|
+
return next()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let file: StaticFileMatch | undefined
|
|
112
130
|
|
|
113
131
|
try {
|
|
114
|
-
let stats = await fsp.stat(
|
|
132
|
+
let stats = await fsp.stat(containedTargetPath)
|
|
115
133
|
|
|
116
134
|
if (stats.isFile()) {
|
|
117
|
-
|
|
135
|
+
file = {
|
|
136
|
+
realPath: containedTargetPath,
|
|
137
|
+
requestedPath: targetPath,
|
|
138
|
+
}
|
|
118
139
|
} else if (stats.isDirectory()) {
|
|
119
140
|
// Try each index file in turn
|
|
120
141
|
for (let indexFile of index) {
|
|
121
142
|
let indexPath = path.join(targetPath, indexFile)
|
|
143
|
+
let containedIndexPath = await resolveContainedPath(rootRealPath, indexPath)
|
|
144
|
+
if (containedIndexPath == null) {
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
|
|
122
148
|
try {
|
|
123
|
-
let indexStats = await fsp.stat(
|
|
149
|
+
let indexStats = await fsp.stat(containedIndexPath)
|
|
124
150
|
if (indexStats.isFile()) {
|
|
125
|
-
|
|
151
|
+
file = {
|
|
152
|
+
realPath: containedIndexPath,
|
|
153
|
+
requestedPath: indexPath,
|
|
154
|
+
}
|
|
126
155
|
break
|
|
127
156
|
}
|
|
128
157
|
} catch {
|
|
@@ -131,17 +160,20 @@ export function staticFiles(root: string, options: StaticFilesOptions = {}): Mid
|
|
|
131
160
|
}
|
|
132
161
|
|
|
133
162
|
// If no index file found and listFiles is enabled, show directory listing
|
|
134
|
-
if (!
|
|
135
|
-
return generateDirectoryListing(
|
|
163
|
+
if (!file && listFiles) {
|
|
164
|
+
return generateDirectoryListing(containedTargetPath, context.url.pathname)
|
|
136
165
|
}
|
|
137
166
|
}
|
|
138
167
|
} catch {
|
|
139
168
|
// Path doesn't exist or isn't accessible, fall through
|
|
140
169
|
}
|
|
141
170
|
|
|
142
|
-
if (
|
|
143
|
-
let fileName = path.relative(root,
|
|
144
|
-
let lazyFile = openLazyFile(
|
|
171
|
+
if (file) {
|
|
172
|
+
let fileName = path.relative(root, file.requestedPath)
|
|
173
|
+
let lazyFile = openLazyFile(file.realPath, {
|
|
174
|
+
name: fileName,
|
|
175
|
+
type: detectMimeType(file.requestedPath) ?? '',
|
|
176
|
+
})
|
|
145
177
|
|
|
146
178
|
let finalFileOptions: FileResponseOptions = { ...fileOptions }
|
|
147
179
|
|
|
@@ -159,3 +191,20 @@ export function staticFiles(root: string, options: StaticFilesOptions = {}): Mid
|
|
|
159
191
|
return next()
|
|
160
192
|
}
|
|
161
193
|
}
|
|
194
|
+
|
|
195
|
+
async function resolveContainedPath(
|
|
196
|
+
rootRealPath: string,
|
|
197
|
+
targetPath: string,
|
|
198
|
+
): Promise<string | null> {
|
|
199
|
+
try {
|
|
200
|
+
let realPath = await fsp.realpath(targetPath)
|
|
201
|
+
return isContainedPath(rootRealPath, realPath) ? realPath : null
|
|
202
|
+
} catch {
|
|
203
|
+
return null
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isContainedPath(rootPath: string, targetPath: string): boolean {
|
|
208
|
+
let relativePath = path.relative(rootPath, targetPath)
|
|
209
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath))
|
|
210
|
+
}
|