@venizia/ignis-docs 0.0.1-5 → 0.0.1-6
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/package.json +5 -4
- package/wiki/public/logo.svg +1 -0
- package/wiki/references/base/models.md +319 -1
- package/wiki/references/components/index.md +3 -1
- package/wiki/references/components/static-asset.md +1289 -0
- package/wiki/references/helpers/storage.md +538 -11
- package/wiki/references/utilities/index.md +1 -1
- package/wiki/references/utilities/request.md +150 -0
|
@@ -64,3 +64,153 @@ export class FileController extends BaseController {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Content-Disposition Utilities
|
|
71
|
+
|
|
72
|
+
These utilities help create secure, RFC-compliant `Content-Disposition` headers for file downloads.
|
|
73
|
+
|
|
74
|
+
### `createContentDispositionHeader`
|
|
75
|
+
|
|
76
|
+
Creates a safe Content-Disposition header with proper filename encoding for file downloads.
|
|
77
|
+
|
|
78
|
+
#### `createContentDispositionHeader(filename: string): string`
|
|
79
|
+
|
|
80
|
+
- `filename` (string): The filename to use in the Content-Disposition header.
|
|
81
|
+
|
|
82
|
+
The function returns a properly formatted `Content-Disposition` header string with both ASCII and UTF-8 encoded filenames for maximum browser compatibility.
|
|
83
|
+
|
|
84
|
+
**Features:**
|
|
85
|
+
- Automatic filename sanitization (removes path components and dangerous characters)
|
|
86
|
+
- UTF-8 encoding support for international characters
|
|
87
|
+
- RFC 5987 compliant
|
|
88
|
+
- Fallback for older browsers
|
|
89
|
+
|
|
90
|
+
**Example:**
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { createContentDispositionHeader } from '@venizia/ignis-helpers';
|
|
94
|
+
|
|
95
|
+
// In a download endpoint
|
|
96
|
+
ctx.header('content-disposition', createContentDispositionHeader('my-document.pdf'));
|
|
97
|
+
|
|
98
|
+
// Output: attachment; filename="my-document.pdf"; filename*=UTF-8''my-document.pdf
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### `sanitizeFilename`
|
|
104
|
+
|
|
105
|
+
Sanitizes a filename for safe use, removing path components and dangerous characters. Useful for HTTP headers (e.g., Content-Disposition) and general file handling.
|
|
106
|
+
|
|
107
|
+
#### `sanitizeFilename(filename: string): string`
|
|
108
|
+
|
|
109
|
+
- `filename` (string): The filename to sanitize.
|
|
110
|
+
|
|
111
|
+
Returns a safe filename suitable for use in headers or filesystem operations.
|
|
112
|
+
|
|
113
|
+
**Features:**
|
|
114
|
+
- Removes path components (prevents directory traversal attacks)
|
|
115
|
+
- Allows only alphanumeric characters, spaces, hyphens, underscores, and dots
|
|
116
|
+
- Replaces dangerous characters with underscores
|
|
117
|
+
- Removes leading dots (prevents hidden files)
|
|
118
|
+
- Replaces consecutive dots with a single dot
|
|
119
|
+
- Removes ".." patterns (additional path traversal protection)
|
|
120
|
+
- Prevents empty filenames and suspicious patterns
|
|
121
|
+
|
|
122
|
+
**Example:**
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { sanitizeFilename } from '@venizia/ignis-helpers';
|
|
126
|
+
|
|
127
|
+
sanitizeFilename('../../etc/passwd'); // Returns: 'passwd'
|
|
128
|
+
sanitizeFilename('my<file>name.txt'); // Returns: 'my_file_name.txt'
|
|
129
|
+
sanitizeFilename('.hidden'); // Returns: 'hidden'
|
|
130
|
+
sanitizeFilename('file...txt'); // Returns: 'file.txt'
|
|
131
|
+
sanitizeFilename('документ.pdf'); // Returns: '_________.pdf'
|
|
132
|
+
sanitizeFilename(''); // Returns: 'download'
|
|
133
|
+
sanitizeFilename('..'); // Returns: 'download'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### `encodeRFC5987`
|
|
139
|
+
|
|
140
|
+
Encodes a filename according to RFC 5987 for use in HTTP headers.
|
|
141
|
+
|
|
142
|
+
#### `encodeRFC5987(filename: string): string`
|
|
143
|
+
|
|
144
|
+
- `filename` (string): The filename to encode.
|
|
145
|
+
|
|
146
|
+
Returns an RFC 5987 encoded string suitable for the `filename*` parameter in Content-Disposition headers.
|
|
147
|
+
|
|
148
|
+
**Example:**
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { encodeRFC5987 } from '@venizia/ignis-helpers';
|
|
152
|
+
|
|
153
|
+
encodeRFC5987('my document.pdf'); // Returns: 'my%20document.pdf'
|
|
154
|
+
encodeRFC5987('файл.txt'); // Returns: '%D1%84%D0%B0%D0%B9%D0%BB.txt'
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Complete File Download Example
|
|
160
|
+
|
|
161
|
+
Here's a complete example combining multipart upload parsing with secure file downloads:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { BaseController, controller } from '@venizia/ignis';
|
|
165
|
+
import { parseMultipartBody, createContentDispositionHeader, HTTP } from '@venizia/ignis-helpers';
|
|
166
|
+
import fs from 'node:fs';
|
|
167
|
+
import path from 'node:path';
|
|
168
|
+
|
|
169
|
+
@controller({ path: '/files' })
|
|
170
|
+
export class FileController extends BaseController {
|
|
171
|
+
override binding() {
|
|
172
|
+
// Upload endpoint
|
|
173
|
+
this.bindRoute({
|
|
174
|
+
configs: { path: '/upload', method: 'post' },
|
|
175
|
+
}).to({
|
|
176
|
+
handler: async (ctx) => {
|
|
177
|
+
const files = await parseMultipartBody({
|
|
178
|
+
context: ctx,
|
|
179
|
+
storage: 'disk',
|
|
180
|
+
uploadDir: './uploads',
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return ctx.json({
|
|
184
|
+
message: 'Files uploaded successfully',
|
|
185
|
+
files: files.map(f => ({ name: f.originalname, size: f.size })),
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Download endpoint
|
|
191
|
+
this.bindRoute({
|
|
192
|
+
configs: { path: '/:filename', method: 'get' },
|
|
193
|
+
}).to({
|
|
194
|
+
handler: async (ctx) => {
|
|
195
|
+
const { filename } = ctx.req.valid('param');
|
|
196
|
+
const filePath = path.join('./uploads', filename);
|
|
197
|
+
|
|
198
|
+
// Read file
|
|
199
|
+
const fileStat = fs.statSync(filePath);
|
|
200
|
+
const fileStream = fs.createReadStream(filePath);
|
|
201
|
+
|
|
202
|
+
// Set secure headers
|
|
203
|
+
ctx.header('content-type', 'application/octet-stream');
|
|
204
|
+
ctx.header('content-length', fileStat.size.toString());
|
|
205
|
+
ctx.header('content-disposition', createContentDispositionHeader(filename));
|
|
206
|
+
ctx.header('x-content-type-options', 'nosniff');
|
|
207
|
+
|
|
208
|
+
return new Response(fileStream, {
|
|
209
|
+
headers: ctx.res.headers,
|
|
210
|
+
status: HTTP.ResultCodes.RS_2.Ok,
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|