Add request body limit
This commit is contained in:
parent
539d3f4faa
commit
bfe2ef6bfa
|
@ -1,12 +1,14 @@
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
import { createWriteStream } from 'fs-extra'
|
import { createWriteStream, remove } from 'fs-extra'
|
||||||
import * as request from 'request'
|
import * as request from 'request'
|
||||||
import { ACTIVITY_PUB, CONFIG } from '../initializers'
|
import { ACTIVITY_PUB, CONFIG } from '../initializers'
|
||||||
import { processImage } from './image-utils'
|
import { processImage } from './image-utils'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import { logger } from './logger'
|
||||||
|
|
||||||
function doRequest <T> (
|
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 }> {
|
): Bluebird<{ response: request.RequestResponse, body: T }> {
|
||||||
if (requestOptions.activityPub === true) {
|
if (requestOptions.activityPub === true) {
|
||||||
if (!Array.isArray(requestOptions.headers)) requestOptions.headers = {}
|
if (!Array.isArray(requestOptions.headers)) requestOptions.headers = {}
|
||||||
|
@ -15,16 +17,29 @@ function doRequest <T> (
|
||||||
|
|
||||||
return new Bluebird<{ response: request.RequestResponse, body: T }>((res, rej) => {
|
return new Bluebird<{ response: request.RequestResponse, body: T }>((res, rej) => {
|
||||||
request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body }))
|
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) => {
|
return new Bluebird<void>((res, rej) => {
|
||||||
const file = createWriteStream(destPath)
|
const file = createWriteStream(destPath)
|
||||||
file.on('finish', () => res())
|
file.on('finish', () => res())
|
||||||
|
|
||||||
request(requestOptions)
|
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)
|
.pipe(file)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -44,3 +59,21 @@ export {
|
||||||
doRequestAndSaveToFile,
|
doRequestAndSaveToFile,
|
||||||
downloadImage
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -116,7 +116,8 @@ function downloadPlaylistSegments (playlistUrl: string, destinationDir: string,
|
||||||
for (const fileUrl of fileUrls) {
|
for (const fileUrl of fileUrls) {
|
||||||
const destPath = join(tmpDirectory, basename(fileUrl))
|
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)
|
clearTimeout(timer)
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
import './core-utils'
|
import './core-utils'
|
||||||
import './comment-model'
|
import './comment-model'
|
||||||
|
import './request'
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
|
@ -3,6 +3,10 @@ import { buildAbsoluteFixturePath, root } from '../miscs/miscs'
|
||||||
import { isAbsolute, join } from 'path'
|
import { isAbsolute, join } from 'path'
|
||||||
import { parse } from 'url'
|
import { parse } from 'url'
|
||||||
|
|
||||||
|
function get4KFileUrl () {
|
||||||
|
return 'https://download.cpy.re/peertube/4k_file.txt'
|
||||||
|
}
|
||||||
|
|
||||||
function makeRawRequest (url: string, statusCodeExpected?: number, range?: string) {
|
function makeRawRequest (url: string, statusCodeExpected?: number, range?: string) {
|
||||||
const { host, protocol, pathname } = parse(url)
|
const { host, protocol, pathname } = parse(url)
|
||||||
|
|
||||||
|
@ -166,6 +170,7 @@ function updateAvatarRequest (options: {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
get4KFileUrl,
|
||||||
makeHTMLRequest,
|
makeHTMLRequest,
|
||||||
makeGetRequest,
|
makeGetRequest,
|
||||||
makeUploadRequest,
|
makeUploadRequest,
|
||||||
|
|
Loading…
Reference in New Issue