Skip to content
Open
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
110 changes: 108 additions & 2 deletions src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,114 @@
'use strict';

/* eslint-disable no-console */

const { Server } = require('node:http');
const fs = require('node:fs');
const zlib = require('node:zlib');

const { formidable } = require('formidable');
const { pipeline } = require('node:stream');

function createServer() {
/* Write your code here */
// Return instance of http.Server class
const server = new Server();

server.on('request', async (req, res) => {
const path = req.url;
const method = req.method;

if (method === 'GET' && path === '/') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');

const file = fs.createReadStream('public/index.html');

file.on('error', () => {
res.statusCode = 404;
res.end('No such file');
});

file.on('close', () => {
console.log('connection close');
file.destroy();
});

pipeline(file, res, (err) => {
Comment on lines +23 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server serves public/index.html with a read stream but handles stream errors with an event listener plus pipeline callback. Consider using pipeline exclusively (and handling its error) to make the flow more robust and avoid double handling; at minimum, ensure the file.on('close') handler won't conflict with pipeline. This is an improvement suggestion rather than a task-blocking bug. (lines 23, 25, 30, 35)

if (err) {
res.end();
}
});
} else if (method === 'POST' && path === '/compress') {
const form = formidable({});

form.parse(req, (err, fields, files) => {
if (err) {
res.statusCode = 400;
res.end();

return;
}

if (!files.file?.[0] || !fields.compressionType?.[0]) {
res.statusCode = 400;
res.end();

return;
}

const uploadedFilePath = files.file[0].filepath;
const readStream = fs.createReadStream(uploadedFilePath);
const compressionType = fields.compressionType[0];
const fileName = files.file[0].originalFilename;
Comment on lines +51 to +61
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Form parsing uses formidable and expects arrays (files.file[0], fields.compressionType[0]). This is fine only if formidable is configured to return arrays; default behavior may provide single values. Verify that the tests/invocation provide fields as arrays; otherwise access files.file and fields.compressionType directly. If you intend to rely on arrays, document or configure formidable accordingly.

Comment on lines +51 to +61
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using array indexing for files.file and fields.compressionType assumes formidable returns arrays. This is brittle if formidable configuration changes — access values in a shape-agnostic way (e.g., handle both arrays and single values) or configure/document formidable to always return arrays. This relates to requirements to validate the form and could cause false 400 responses. (Around lines 51, 58, 60, 61)


let fileCompression;
let extension = '';

switch (compressionType) {
case 'gzip':
fileCompression = zlib.createGzip();
extension = '.gz';

break;
case 'deflate':
fileCompression = zlib.createDeflate();
extension = '.dfl';

break;
case 'br':
fileCompression = zlib.createBrotliCompress();
extension = '.br';

break;
default:
res.statusCode = 400;
res.end();

return;
}
res.statusCode = 200;
res.setHeader('Content-Type', 'application/octet-stream');

res.setHeader(
'Content-Disposition',
`attachment; filename=${fileName}${extension}`,
Comment on lines +91 to +93
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Content-Disposition header uses an unquoted filename. Filenames containing spaces or special characters may break downloads. Wrap the filename in quotes like attachment; filename="${fileName}${extension}". (lines 91-93)

);
Comment on lines +91 to +94
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You set Content-Disposition header to attachment; filename=${fileName}${extension} without escaping or wrapping filename. If fileName contains spaces or special chars the header can become invalid. Wrap the filename in double quotes: attachment; filename="${fileName}${extension}".


pipeline(readStream, fileCompression, res, (error) => {
if (error) {
res.end();
}
});
});
} else if (method === 'GET' && path === '/compress') {
res.statusCode = 400;
res.end();
} else {
res.statusCode = 404;
res.end();
}
});

return server;
}

module.exports = {
Expand Down
Loading