aim_server_static
Static file serving middleware for the Aim framework with security built-in.
Overview
aim_server_static provides secure static file serving for the Aim framework. This package was designed as a separate module to keep applications that don't need static file serving lightweight while providing essential security features like path traversal protection and dotfile restrictions.
Features
- 📁 Directory Serving - Serve entire directories with automatic MIME type detection
- 🔒 Path Traversal Protection - Built-in security against
../attacks - 🙈 Dotfile Control - Prevent access to sensitive hidden files (
.env,.git, etc.) - 📄 Index File Support - Automatic
index.htmlserving for directories - 🎯 URL Prefix Matching - Serve static files under specific URL paths
- 📥 Single File Serving - Context extension for serving individual files
- 💾 Download Support - Force file downloads with custom filenames
- 🎨 Automatic MIME Types - Smart content-type detection for common file formats
Installation
Add aim_server_static to your pubspec.yaml:
dependencies:
aim_server: ^0.0.5
aim_server_static: ^0.0.1
Then run:
dart pub get
Usage
Basic Static File Serving
Serve files from a directory:
import 'package:aim_server/aim_server.dart';
import 'package:aim_server_static/aim_server_static.dart';
void main() async {
final app = Aim();
// Serve files from the 'public' directory
app.use(serveStatic('./public'));
app.get('/api/hello', (c) => c.json({'message': 'Hello!'}));
await app.serve(port: 8080);
}
File structure:
public/
├── index.html
├── styles.css
└── images/
└── logo.png
Requests:
GET /index.html→public/index.htmlGET /styles.css→public/styles.cssGET /images/logo.png→public/images/logo.png
URL Prefix Matching
Serve static files under a specific URL path:
app.use(serveStatic('./assets', path: '/static'));
Requests:
GET /static/style.css→assets/style.cssGET /static/images/logo.png→assets/images/logo.pngGET /api/data→ Not handled by static middleware (passes to next handler)
Index File Support
Automatically serve index.html for directory requests:
app.use(serveStatic('./public', index: 'index.html'));
Requests:
GET /→public/index.htmlGET /about/→public/about/index.html
Dotfile Protection
Control access to hidden files (files starting with .):
import 'package:aim_server_static/aim_server_static.dart';
// Ignore dotfiles (default) - return 404
app.use(serveStatic('./public', dotFiles: DotFiles.ignore));
// Deny dotfiles - return 403 Forbidden
app.use(serveStatic('./public', dotFiles: DotFiles.deny));
// Allow dotfiles - serve them normally
app.use(serveStatic('./public', dotFiles: DotFiles.allow));
Protected files:
.env- Environment variables.git/- Git repository.htaccess- Apache configuration.DS_Store- macOS metadata
Single File Serving
Use the c.file() extension method to serve individual files:
app.get('/download/manual', (c) {
return c.file('docs/manual.pdf');
});
// Force download with custom filename
app.get('/download/report', (c) {
return c.file('reports/2024-report.pdf',
download: true,
filename: 'Annual-Report-2024.pdf',
);
});
// Serve user avatars
app.get('/users/:id/avatar', (c) {
final id = c.param('id');
return c.file('avatars/$id.png');
});
Complete Example
import 'dart:io';
import 'package:aim_server/aim_server.dart';
import 'package:aim_server_static/aim_server_static.dart';
void main() async {
final app = Aim();
// Serve static assets under /static
app.use(serveStatic('./public',
path: '/static',
index: 'index.html',
dotFiles: DotFiles.deny,
));
// Single file downloads
app.get('/download/manual', (c) => c.file('docs/manual.pdf', download: true));
// API routes
app.get('/api/status', (c) => c.json({'status': 'ok'}));
await app.serve(host: InternetAddress.anyIPv4, port: 8080);
print('Server running on http://localhost:8080');
}
API Reference
serveStatic(String root, {String? path, String? index, DotFiles dotFiles})
Creates a static file serving middleware.
Parameters:
root(required) - Root directory to serve files from (e.g.,'./public')path(optional) - URL prefix for serving files (e.g.,'/static')index(optional) - Index file name for directory requests (e.g.,'index.html')dotFiles(optional) - How to handle dotfiles (default:DotFiles.ignore)
Returns: Middleware<E>
c.file(String path, {bool download, String? filename})
Context extension method for serving a single file.
Parameters:
path(required) - Relative path to the file from current directorydownload(optional) - Force download instead of displaying in browser (default:false)filename(optional) - Custom filename for downloads
Returns: Future<Response>
Throws:
ArgumentError- If absolute path is provided or path traversal is detected
DotFiles Enum
Controls how dotfiles (files starting with .) are handled:
DotFiles.allow- Serve dotfiles normallyDotFiles.deny- Return 403 Forbidden for dotfilesDotFiles.ignore- Return 404 Not Found for dotfiles (default)
Security
aim_server_static includes several built-in security features:
Path Traversal Protection
Prevents access to files outside the root directory:
// ❌ Blocked: GET /static/../../../etc/passwd
// ❌ Blocked: GET /static/%2e%2e/secret.txt
The middleware normalizes paths and validates they remain within the specified root directory.
Dotfile Protection
Prevents access to hidden files that may contain sensitive information:
// ❌ Blocked (with DotFiles.deny or ignore):
// GET /static/.env
// GET /static/.git/config
Absolute Path Rejection
The c.file() method rejects absolute paths to prevent unintended file access:
// ❌ Throws ArgumentError
c.file('/etc/passwd');
MIME Types
Automatic MIME type detection for common file formats:
| Extension | MIME Type |
|---|---|
.html |
text/html |
.css |
text/css |
.js |
application/javascript |
.json |
application/json |
.png |
image/png |
.jpg, .jpeg |
image/jpeg |
.gif |
image/gif |
.svg |
image/svg+xml |
.pdf |
application/pdf |
.txt |
text/plain |
| (unknown) | application/octet-stream |
Contributing
Contributions are welcome! Please see the main repository for contribution guidelines.
License
See the LICENSE file in the main repository.