Add request body limit

This commit is contained in:
Chocobozzz 2019-02-21 17:19:16 +01:00
parent 539d3f4faa
commit bfe2ef6bfa
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
5 changed files with 93 additions and 5 deletions

View File

@ -1,12 +1,14 @@
import * as Bluebird from 'bluebird'
import { createWriteStream } from 'fs-extra'
import { createWriteStream, remove } from 'fs-extra'
import * as request from 'request'
import { ACTIVITY_PUB, CONFIG } from '../initializers'
import { processImage } from './image-utils'
import { join } from 'path'
import { logger } from './logger'
function doRequest <T> (
requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean }
requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean },
bodyKBLimit = 1000 // 1MB
): Bluebird<{ response: request.RequestResponse, body: T }> {
if (requestOptions.activityPub === true) {
if (!Array.isArray(requestOptions.headers)) requestOptions.headers = {}
@ -15,16 +17,29 @@ function doRequest <T> (
return new Bluebird<{ response: request.RequestResponse, body: T }>((res, rej) => {
request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body }))
.on('data', onRequestDataLengthCheck(bodyKBLimit))
})
}
function doRequestAndSaveToFile (requestOptions: request.CoreOptions & request.UriOptions, destPath: string) {
function doRequestAndSaveToFile (
requestOptions: request.CoreOptions & request.UriOptions,
destPath: string,
bodyKBLimit = 10000 // 10MB
) {
return new Bluebird<void>((res, rej) => {
const file = createWriteStream(destPath)
file.on('finish', () => res())
request(requestOptions)
.on('error', err => rej(err))
.on('data', onRequestDataLengthCheck(bodyKBLimit))
.on('error', err => {
file.close()
remove(destPath)
.catch(err => logger.error('Cannot remove %s after request failure.', destPath, { err }))
return rej(err)
})
.pipe(file)
})
}
@ -44,3 +59,21 @@ export {
doRequestAndSaveToFile,
downloadImage
}
// ---------------------------------------------------------------------------
// Thanks to https://github.com/request/request/issues/2470#issuecomment-268929907 <3
function onRequestDataLengthCheck (bodyKBLimit: number) {
let bufferLength = 0
const bytesLimit = bodyKBLimit * 1000
return function (chunk) {
bufferLength += chunk.length
if (bufferLength > bytesLimit) {
this.abort()
const error = new Error(`Response was too large - aborted after ${bytesLimit} bytes.`)
this.emit('error', error)
}
}
}

View File

@ -116,7 +116,8 @@ function downloadPlaylistSegments (playlistUrl: string, destinationDir: string,
for (const fileUrl of fileUrls) {
const destPath = join(tmpDirectory, basename(fileUrl))
await doRequestAndSaveToFile({ uri: fileUrl }, destPath)
const bodyKBLimit = 10 * 1000 * 1000 // 10GB
await doRequestAndSaveToFile({ uri: fileUrl }, destPath, bodyKBLimit)
}
clearTimeout(timer)

View File

@ -1,2 +1,3 @@
import './core-utils'
import './comment-model'
import './request'

View File

@ -0,0 +1,48 @@
/* tslint:disable:no-unused-expression */
import 'mocha'
import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
import { get4KFileUrl, root, wait } from '../../../shared/utils'
import { join } from 'path'
import { pathExists, remove } from 'fs-extra'
import { expect } from 'chai'
describe('Request helpers', function () {
const destPath1 = join(root(), 'test-output-1.txt')
const destPath2 = join(root(), 'test-output-2.txt')
it('Should throw an error when the bytes limit is exceeded for request', async function () {
try {
await doRequest({ uri: get4KFileUrl() }, 3)
} catch {
return
}
throw new Error('No error thrown by do request')
})
it('Should throw an error when the bytes limit is exceeded for request and save file', async function () {
try {
await doRequestAndSaveToFile({ uri: get4KFileUrl() }, destPath1, 3)
} catch {
await wait(500)
expect(await pathExists(destPath1)).to.be.false
return
}
throw new Error('No error thrown by do request and save to file')
})
it('Should succeed if the file is below the limit', async function () {
await doRequest({ uri: get4KFileUrl() }, 5)
await doRequestAndSaveToFile({ uri: get4KFileUrl() }, destPath2, 5)
expect(await pathExists(destPath2)).to.be.true
})
after(async function () {
await remove(destPath1)
await remove(destPath2)
})
})

View File

@ -3,6 +3,10 @@ import { buildAbsoluteFixturePath, root } from '../miscs/miscs'
import { isAbsolute, join } from 'path'
import { parse } from 'url'
function get4KFileUrl () {
return 'https://download.cpy.re/peertube/4k_file.txt'
}
function makeRawRequest (url: string, statusCodeExpected?: number, range?: string) {
const { host, protocol, pathname } = parse(url)
@ -166,6 +170,7 @@ function updateAvatarRequest (options: {
// ---------------------------------------------------------------------------
export {
get4KFileUrl,
makeHTMLRequest,
makeGetRequest,
makeUploadRequest,