A high-performance, security-hardened MIME type parser and content negotiator optimized for edge computing environments. This library implements HTTP Accept header parsing and content negotiation following RFC 9110 Section 12.5.1.
- 🔒 Security First: Input validation, length limits, and prototype pollution prevention
- ⚡ Blazing Fast: Optimized algorithms, native methods, and optional caching
- 🎯 Zero Dependencies: Completely self-contained, perfect for edge environments
- 📦 Lightweight: ~6KB minified, minimal memory footprint
- 🛡️ Type Safe: Comprehensive JSDoc annotations and error handling
- 💾 Smart Caching: Optional in-memory LRU cache for repeated operations
- 🚀 Edge Ready: Designed specifically for Cloudflare Workers and similar platforms
Since this is a single-file library, you can simply copy mimeparser.js
to your project:
# Download the file
curl -O https://gist.github.com/nishad/8b545c35a9cf24bc9cab5b1a8fdcd01c/raw/mimeparser.js
# Or copy it directly to your project
cp mimeparser.js ./src/lib/
import { negotiate } from './mimeparser.js';
export default {
async fetch(request) {
const accept = request.headers.get('Accept') || '*/*';
// Negotiate the best content type
const contentType = negotiate(
['application/json', 'text/html', 'text/plain'],
accept
);
switch(contentType) {
case 'application/json':
return Response.json({ message: 'Hello World' });
case 'text/html':
return new Response('<h1>Hello World</h1>', {
headers: { 'Content-Type': 'text/html' }
});
default:
return new Response('Hello World', {
headers: { 'Content-Type': 'text/plain' }
});
}
}
};
import { MimeParser } from './mimeparser.js';
// Create a parser with custom configuration
const parser = new MimeParser({
cache: true, // Enable caching (default: true)
cacheSize: 200, // Max cache entries (default: 100)
strict: false // Graceful error handling (default: false)
});
// Use throughout your application
export default {
async fetch(request) {
const accept = request.headers.get('Accept') || '*/*';
const supported = ['application/json', 'application/xml', 'text/html'];
const contentType = parser.negotiate(supported, accept);
// ... rest of your handler
}
};
Finds the best matching MIME type from supported types based on the Accept header.
const contentType = negotiate(
['application/json', 'text/html'],
'text/*;q=0.8, application/*;q=0.5'
);
// Returns: 'text/html'
Parses a MIME type string into its components.
const parsed = parseMimeType('text/html;charset=utf-8');
// Returns: {
// type: 'text',
// subtype: 'html',
// params: { charset: 'utf-8' }
// }
Parses a media range with quality factor.
const range = parseMediaRange('text/*;q=0.8');
// Returns: {
// type: 'text',
// subtype: '*',
// params: { q: '0.8' },
// quality: 0.8
// }
Calculates the quality of a MIME type match.
const q = quality('text/html', 'text/*;q=0.3, text/html;q=0.7');
// Returns: 0.7
Finds the best matching MIME type (alternative to negotiate
).
const best = bestMatch(
['application/json', 'text/xml'],
'text/*;q=0.5, */*;q=0.1'
);
// Returns: 'text/xml'
For more control and stateful caching:
const parser = new MimeParser({
cache: true, // Enable caching
cacheSize: 100, // Maximum cache entries
strict: false // Throw errors vs. graceful fallback
});
// Parse with caching
const parsed = parser.parse('text/html;charset=utf-8');
// Negotiate with caching
const type = parser.negotiate(supported, acceptHeader);
// Calculate quality with caching
const q = parser.quality('text/html', acceptHeader);
// Clear cache when needed
parser.clearCache();
import { negotiate } from './mimeparser.js';
export default {
async fetch(request) {
const accept = request.headers.get('Accept') || 'application/json';
const data = {
name: 'John Doe',
email: '[email protected]'
};
const contentType = negotiate(
['application/json', 'application/xml', 'text/csv'],
accept
);
switch(contentType) {
case 'application/xml':
return new Response(
`<?xml version="1.0"?><user><name>${data.name}</name><email>${data.email}</email></user>`,
{ headers: { 'Content-Type': 'application/xml' }}
);
case 'text/csv':
return new Response(
`name,email\n${data.name},${data.email}`,
{ headers: { 'Content-Type': 'text/csv' }}
);
default:
return Response.json(data);
}
}
};
import { MimeParser } from './mimeparser.js';
const parser = new MimeParser({ cache: true });
export default {
async fetch(request) {
const accept = request.headers.get('Accept') || '*/*';
// Support versioned API responses
const supported = [
'application/vnd.api+json;version=2',
'application/vnd.api+json;version=1',
'application/json'
];
const contentType = parser.negotiate(supported, accept);
const parsed = parser.parse(contentType);
if (parsed?.params?.version === '2') {
return Response.json({
data: { /* v2 response */ },
version: 2
});
} else if (parsed?.params?.version === '1') {
return Response.json({
result: { /* v1 response */ },
version: 1
});
} else {
return Response.json({
/* default response */
});
}
}
};
import { MimeParser } from './mimeparser.js';
// Global parser instance for reuse across requests
const parser = new MimeParser({ cacheSize: 200 });
// Middleware to attach negotiated type
async function contentNegotiationMiddleware(request, env, ctx) {
const accept = request.headers.get('Accept') || '*/*';
const supported = env.SUPPORTED_TYPES || ['application/json'];
request.negotiatedType = parser.negotiate(supported, accept);
return request;
}
export default {
async fetch(request, env, ctx) {
// Apply middleware
request = await contentNegotiationMiddleware(request, env, ctx);
// Use negotiated type in your handlers
if (request.negotiatedType === 'application/json') {
// Handle JSON response
}
// ...
}
};
parseMimeType
: ~0.5ms per operationnegotiate
: ~2ms for complex Accept headers- Memory: Negligible
- Cache hits: ~0.01ms
- Memory: ~200-500 bytes per cache entry
- 100 entries ≈ 20-50KB total
-
Use the default parser for simple use cases:
import { negotiate } from './mimeparser.js';
-
Create a global parser for high-traffic scenarios:
const parser = new MimeParser({ cacheSize: 200 });
-
Disable cache for highly dynamic Accept headers:
const parser = new MimeParser({ cache: false });
-
Use strict mode during development:
const parser = new MimeParser({ strict: true });
- Maximum MIME type length: 255 characters
- Maximum Accept header length: 1000 characters
- Maximum Accept header items: 50
- Prototype Pollution: Blocks
__proto__
,constructor
,prototype
parameters - ReDoS Attacks: No dangerous regex patterns
- Memory Exhaustion: Bounded cache size and input limits
- Malformed Input: Graceful error handling with fallbacks
import { MimeParser, MimeParseError } from './mimeparser.js';
const parser = new MimeParser({ strict: true });
try {
const result = parser.parse(userInput);
} catch (error) {
if (error instanceof MimeParseError) {
console.error(`Parse error: ${error.message} (${error.code})`);
// Handle specific error codes
switch(error.code) {
case 'INVALID_FORMAT':
// Handle format errors
break;
case 'TOO_LONG':
// Handle length violations
break;
// ...
}
}
}
- Cloudflare Workers: ✅ Optimized
- Deno: ✅ Compatible
- Node.js: ✅ Compatible (v14+)
- Bun: ✅ Compatible
- Browsers: ✅ Compatible (ES2020+)
If you're migrating from the original mimeparse library:
// Old API (if needed, implement a wrapper)
const Mimeparse = {
parseMimeType: parseMimeType,
parseMediaRange: parseMediaRange,
quality: quality,
bestMatch: bestMatch
};
// New API (recommended)
import { negotiate, parseMimeType, quality } from './mimeparser.js';
This is a single-file library designed to be copied and adapted. Feel free to:
- Fork the gist
- Make your modifications
- Share improvements back via comments
MIT License - See file header for full license text.
- Author: Nishad Thalhath (2024 - Complete rewrite)
- Original JS Port: J. Chris Anderson (2009)
- Inspired by: Joe Gregorio's Python mimeparse library
For issues, questions, or suggestions, please comment on the GitHub Gist.
Built with ❤️ for the edge computing era