Files
2026-09-01 18:33:56 +00:00

112 lines
3.8 KiB
JavaScript

const toObject = require('to-object-reducer');
const decodeMetadata = require('./tus-metadata').decode;
// TODO: Buffer.from silently ignores invalid bytes :(
// https://github.com/nodejs/node/issues/8569
const base64decode = str => Buffer.from(str, 'base64')
const nonNegativeInteger = (v) => {
// need this because parseInt parses floats too
if (v !== '0' && !v.match(/^[1-9][0-9]*$/)) return
const num = parseInt(v, 10)
if (isNaN(num)) return
if (num >= 0) return num
}
/*
const enum = (possibleVals = []) => (v) => {
if (!possibleVals.includes(v)) return
return v
}
*/
const id = v => v
const parsers = {
'tus-resumable': id,
// The Upload-Offset request and response header indicates a byte offset
// within a resource. The value MUST be a non-negative integer.
'upload-offset': nonNegativeInteger,
// The Upload-Defer-Length request and response header indicates that the
// size of the upload is not known currently and will be transferred later.
// Its value MUST be 1. If the length of an upload is not deferred, this
// header MUST be omitted.
'upload-defer-length': v => {
const num = nonNegativeInteger(v)
if (num === 1) return 1
},
// The Upload-Length request and response header indicates the size of the
// entire upload in bytes. The value MUST be a non-negative integer.
'upload-length': nonNegativeInteger,
'upload-metadata': decodeMetadata,
// The Upload-Checksum request header contains information about the checksum
// of the current body payload. The header MUST consist of the name of the
// used checksum algorithm and the Base64 encoded checksum separated by a
// space.
'upload-checksum': v => {
const parts = v.split(' ')
if (parts.length !== 2) return
const algorithm = parts[0]
const checksum = base64decode(parts[1])
if (!checksum.length) return
return { algorithm, checksum }
},
// The Upload-Concat request and response header MUST be set in both partial
// and final upload creation requests. It indicates whether the upload is
// either a partial or final upload. If the upload is a partial one, the
// header value MUST be partial. In the case of a final upload, its value
// MUST be final followed by a semicolon and a space-separated list of
// partial upload URLs that will be concatenated. The partial uploads URLs
// MAY be absolute or relative and MUST NOT contain spaces as defined in RFC
// 3986.
'upload-concat': v => {
if (v === 'partial') return { partial: true }
const parts = v.split(';')
if (!parts.length) return
const firstPart = parts.shift()
if (firstPart !== 'final') return
const urls = parts.join(';').split(' ')
if (urls.some(url => url === '')) return
// validate urls? nah...
return { final: true, urls }
},
}
const knownHeaders = Object.keys(parsers)
const parseHeader = (tmpHeaderName, str) => {
const headerName = tmpHeaderName.toLowerCase()
if (!knownHeaders.includes(headerName)) return
return parsers[headerName](str)
}
// Pre-computed for speed
const camelCase = {
'tus-resumable': 'tusResumable',
'upload-offset': 'uploadOffset',
'upload-defer-length': 'uploadDeferLength',
'upload-length': 'uploadLength',
'upload-metadata': 'uploadMetadata',
'upload-checksum': 'uploadChecksum',
'upload-concat': 'uploadConcat',
}
const parseHeaders = (headers) => (
Object
.keys(headers)
.filter(headerName => knownHeaders.includes(headerName))
.map(headerName => ([
camelCase[headerName],
parsers[headerName](headers[headerName]),
]))
.reduce(toObject, {})
)
module.exports = () => (req, res, next) => {
req.tus = parseHeaders(req.headers)
next()
};
module.exports.knownHeaders = knownHeaders;
module.exports.parseHeader = parseHeader;
module.exports.parseHeaders = parseHeaders;