Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions lib/core/opts.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,26 @@ module.exports = (opts) => {
return typeof opts[k] !== 'undefined' && opts[k] !== null;
}

function validateNoCRLF(str) {
if (typeof str === 'string' && (str.includes('\r') || str.includes('\n'))) {
throw new Error('Header is not a string or contains CRLF');
}
}

function addHeader(key, value) {
validateNoCRLF(key);
validateNoCRLF(value);
headers[key] = value;
}

function setHeader(str) {
validateNoCRLF(str);

const m = /^(.+?)\s*:\s*(.*)$/.exec(str);
if (!m) {
headers[str] = true;
addHeader(str, true); // Use addHeader instead of direct assignment
} else {
headers[m[1]] = m[2];
addHeader(m[1], m[2]); // Use addHeader instead of direct assignment
}
}

Expand Down Expand Up @@ -135,7 +149,7 @@ module.exports = (opts) => {
opts[k].forEach(setHeader);
} else if (opts[k] && typeof opts[k] === 'object') {
Object.keys(opts[k]).forEach((key) => {
headers[key] = opts[k][key];
addHeader(key, opts[k][key]); // Uses same validation path
});
} else {
setHeader(opts[k]);
Expand Down
10 changes: 10 additions & 0 deletions lib/http-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ function HttpServer(options) {
}
}

// CRLF injection prevention
for ( const [key, value] of Object.entries(options.headers || {}) ) {
if (typeof key !== 'string' || typeof value !== 'string') {
throw new Error('Header is not a string or contains CRLF');
}
if (key.includes('\r') || key.includes('\n') || value.includes('\r') || value.includes('\n')) {
throw new Error('Header is not a string or contains CRLF');
}
}

this.headers = options.headers || {};
this.headers['Accept-Ranges'] = 'bytes';

Expand Down
20 changes: 20 additions & 0 deletions test/headers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,23 @@ test('H array', (t) => {
t.equal(headers.beep, 'boop');
});
});

// CRLF injection prevention
test('CRLF injection prevention', (t) => {
t.plan(1);

t.throws(() => {
const server = http.createServer(
ecstatic({
root,
H: [
'X-CRLF-Injection: X\r\nContent-Type: text/html',
],
autoIndex: true,
defaultExt: 'html',
})
);

server.close();
}, /Header is not a string or contains CRLF/);
});
Loading