112 lines
3.8 KiB
JavaScript
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;
|