Skip to content

Instantly share code, notes, and snippets.

@droduit
Last active March 14, 2023 13:15
Show Gist options
  • Save droduit/f4364aca7216561ec30a590491dc3848 to your computer and use it in GitHub Desktop.
Save droduit/f4364aca7216561ec30a590491dc3848 to your computer and use it in GitHub Desktop.
Get filename from Content-Disposition header
/**
* Support filename= and filename*= with the priority given to filename*=
* Encoding and locale can optionaly be provided with filename*=
* Return undefined if the content-disposition doesn't contain filename= or filename*=
*/
const { TextDecoder } = require("util");
function getFilenameFromContentDisposition(contentDisposition: string) {
if (!contentDisposition) {
return undefined
}
function findAndNormalizeParameter(parameter: string, contentDispositionParts: string[]) {
return contentDispositionParts.find(n => n.toLowerCase().includes(`${parameter}=`))?.replace(/filename\*?=|"/gi, "")?.trim()
}
function decodeString(encodedString: string, encoding: string) {
const uint8Array = new Uint8Array(encodedString.length);
for (let i = 0; i < encodedString.length; i++) {
uint8Array[i] = encodedString.charCodeAt(i)
}
try {
const decoder = new TextDecoder(encoding.toLowerCase(), { fatal: true });
return decoder.decode(uint8Array)
} catch (typeError) {
return undefined
}
}
function readFilenameStar(contentDispositionParts: string[]) {
const filenameStarParam = findAndNormalizeParameter("filename*", contentDispositionParts)
if (!filenameStarParam) {
return undefined
}
const parts = filenameStarParam.split("'")
if (parts.length === 1) {
return parts[0]
}
const [encoding, _, ...filenameParts] = parts
const filename = filenameParts.join("")
return ["utf-8", "utf8"].includes(encoding?.toLowerCase()) ? filename : decodeString(filename, encoding)
}
function readFilename(contentDispositionParts: string[]) {
return findAndNormalizeParameter("filename", contentDispositionParts)
}
const contentDispositionParts = contentDisposition.split(";")
const filename = readFilenameStar(contentDispositionParts) ?? readFilename(contentDispositionParts)
return !filename ? undefined : decodeURIComponent(filename)
}
// Tests for the getFilenameFromContentDisposition function
it('should return the filename when the value of filename is wrapped in quotes', () => {
const contentDisposition = 'form-data; name="offer"; filename="b968616e-bec7-4e21-8c01-502f9efa639e.pdf"';
const result = getFilenameFromContentDisposition(contentDisposition);
expect(result).toEqual('b968616e-bec7-4e21-8c01-502f9efa639e.pdf');
});
it('should return the filename when the value of filename is not wrapped in quotes', () => {
const contentDisposition = 'form-data; name="offer"; filename=b968616e-bec7-4e21-8c01-502f9efa639e.pdf';
const result = getFilenameFromContentDisposition(contentDisposition);
expect(result).toEqual('b968616e-bec7-4e21-8c01-502f9efa639e.pdf');
});
it('should return the well decoded filename* when the value of filename* is wrapped in quotes', () => {
const contentDisposition = 'form-data; name="offer"; filename*="ex%C3%A4mple.txt"';
const result = getFilenameFromContentDisposition(contentDisposition);
expect(result).toEqual('exämple.txt');
});
it('should return the well decoded filename* when UTF-8 encoding is provided and not wrapped in quotes', () => {
const contentDisposition = 'form-data; name="offer"; filename*=UTF-8\'\'ex%C3%A4mple.txt';
const result = getFilenameFromContentDisposition(contentDisposition);
expect(result).toEqual('exämple.txt');
});
it('should return the filename when both filename and filename* are provided but filename* has an encoding that is not utf-8', () => {
const contentDisposition = 'Inline; filename=example.txt; filename*=ISO-8859-1\'en\'fran\u00E9%C3%A7ois%20rod%C3%AEt%20%25%20bois%20%22\'%20(-.%24%5B%5D_%2B%40*%23%26%3D%3F*%2F).txt';
const result = getFilenameFromContentDisposition(contentDisposition);
expect(result).toEqual('franéçois rodît % bois " (-.$[]_+@*#&=?*/).txt');
});
it('should return undefined when only filename* is provided, but filename* has an encoding that is not utf-8', () => {
const contentDisposition = 'Inline; filename*=ISO-8859-1\'en\'fran%C3%A7ois%20rod%C3%AEt%20%25%20bois%20%22\'%20(-.%24%5B%5D_%2B%40*%23%26%3D%3F*%2F).txt';
const result = getFilenameFromContentDisposition(contentDisposition);
expect(result).toEqual('françois rodît % bois " (-.$[]_+@*#&=?*/).txt');
});
it('should return the filename* when both filename and filename* are provided and the value of filename* is wrapped in quotes', () => {
const contentDisposition = 'form-data; name="offer"; filename="test.txt"; filename*="ex%C3%A4mple.txt"';
const result = getFilenameFromContentDisposition(contentDisposition);
expect(result).toEqual('exämple.txt');
});
it('should return the filename* when both filename and filename* are provided but not in lowercase', () => {
const contentDisposition = 'form-data; name="offer"; filename="test.txt"; FileName*="ex%C3%A4mple.txt"';
const result = getFilenameFromContentDisposition(contentDisposition);
expect(result).toEqual('exämple.txt');
});
it('should return undefined when undefined is given as parameter', () => {
const result = getFilenameFromContentDisposition(undefined);
expect(result).toEqual(undefined);
});
it('should return undefined when the content-disposition header has no filename or filename* parameter', () => {
const contentDisposition = 'form-data; name="field1"';
const result = getFilenameFromContentDisposition(contentDisposition);
expect(result).toEqual(undefined);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment