mirror of https://github.com/MISP/MISP
new: [sync] Compressed requests support
parent
50f2738f50
commit
69f901110a
|
@ -78,6 +78,7 @@ class AppController extends Controller
|
|||
),
|
||||
'Security',
|
||||
'ACL',
|
||||
'CompressedRequestHandler',
|
||||
'RestResponse',
|
||||
'Flash',
|
||||
'Toolbox',
|
||||
|
|
|
@ -492,7 +492,7 @@ class ACLComponent extends Component
|
|||
'getSubmodulesStatus' => array(),
|
||||
'getSubmoduleQuickUpdateForm' => array(),
|
||||
'getWorkers' => array(),
|
||||
'getVersion' => array('*'),
|
||||
'getVersion' => array('perm_auth'),
|
||||
'idTranslator' => ['OR' => [
|
||||
'host_org_user',
|
||||
'perm_site_admin',
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
class CompressedRequestHandlerComponent extends Component
|
||||
{
|
||||
// Maximum size of uncompressed data to prevent zip bombs
|
||||
const MAX_SIZE = 1024 * 1024 * 100;
|
||||
|
||||
public function startup(Controller $controller)
|
||||
{
|
||||
$contentEncoding = CakeRequest::header('CONTENT_ENCODING');
|
||||
if (!empty($contentEncoding)) {
|
||||
if ($contentEncoding === 'br') {
|
||||
$controller->request->setInput($this->decodeBrotliEncodedContent($controller));
|
||||
} else if ($contentEncoding === 'gzip') {
|
||||
$controller->request->setInput($this->decodeGzipEncodedContent($controller));
|
||||
} else {
|
||||
throw new MethodNotAllowedException("Unsupported content encoding '$contentEncoding'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function supportedEncodings()
|
||||
{
|
||||
$supportedEncodings = [];
|
||||
if (function_exists('gzdecode') || function_exists('inflate_init')) {
|
||||
$supportedEncodings[] = 'gzip';
|
||||
}
|
||||
if (function_exists('brotli_uncompress') || function_exists('brotli_uncompress_init')) {
|
||||
$supportedEncodings[] = 'br';
|
||||
}
|
||||
return $supportedEncodings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws Exception
|
||||
* @see CakeRequest::_readInput()
|
||||
*/
|
||||
private function decodeGzipEncodedContent(Controller $controller)
|
||||
{
|
||||
if (function_exists('inflate_init')) {
|
||||
// Decompress data on the fly if supported
|
||||
$resource = inflate_init(ZLIB_ENCODING_GZIP);
|
||||
if ($resource === false) {
|
||||
throw new Exception('GZIP incremental uncompress init failed.');
|
||||
}
|
||||
$uncompressed = '';
|
||||
foreach ($this->streamInput() as $data) {
|
||||
$uncompressedChunk = inflate_add($resource, $data);
|
||||
if ($uncompressedChunk === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
$uncompressed .= $uncompressedChunk;
|
||||
if (strlen($uncompressed) > self::MAX_SIZE) {
|
||||
throw new Exception("Uncompressed data are bigger than is limit.");
|
||||
}
|
||||
}
|
||||
$uncompressedChunk = inflate_add($resource, '', ZLIB_FINISH);
|
||||
if ($uncompressedChunk === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
return $uncompressed . $uncompressedChunk;
|
||||
|
||||
} else if (function_exists('gzdecode')) {
|
||||
$decoded = gzdecode($controller->request->input(), self::MAX_SIZE);
|
||||
if ($decoded === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
if (strlen($decoded) >= self::MAX_SIZE) {
|
||||
throw new Exception("Uncompressed data are bigger than is limit.");
|
||||
}
|
||||
return $decoded;
|
||||
} else {
|
||||
throw new MethodNotAllowedException("This server doesn't support GZIP compressed requests.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Controller $controller
|
||||
* @return string
|
||||
* @throws Exception
|
||||
* @see CakeRequest::_readInput()
|
||||
*/
|
||||
private function decodeBrotliEncodedContent(Controller $controller)
|
||||
{
|
||||
if (function_exists('brotli_uncompress_init')) {
|
||||
// Decompress data on the fly if supported
|
||||
$resource = brotli_uncompress_init();
|
||||
if ($resource === false) {
|
||||
throw new Exception('Brotli incremental uncompress init failed.');
|
||||
}
|
||||
$uncompressed = '';
|
||||
foreach ($this->streamInput() as $data) {
|
||||
$uncompressedChunk = brotli_uncompress_add($resource, $data, BROTLI_PROCESS);
|
||||
if ($uncompressedChunk === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
$uncompressed .= $uncompressedChunk;
|
||||
if (strlen($uncompressed) > self::MAX_SIZE) {
|
||||
throw new Exception("Uncompressed data are bigger than is limit.");
|
||||
}
|
||||
}
|
||||
$uncompressedChunk = brotli_uncompress_add($resource, '', BROTLI_FINISH);
|
||||
if ($uncompressedChunk === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
return $uncompressed . $uncompressedChunk;
|
||||
|
||||
} else if (function_exists('brotli_uncompress')) {
|
||||
$decoded = brotli_uncompress($controller->request->input(), self::MAX_SIZE);
|
||||
if ($decoded === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
if (strlen($decoded) >= self::MAX_SIZE) {
|
||||
throw new Exception("Uncompressed data are bigger than is limit.");
|
||||
}
|
||||
return $decoded;
|
||||
} else {
|
||||
throw new MethodNotAllowedException("This server doesn't support brotli compressed requests.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $chunkSize
|
||||
* @return Generator<string>
|
||||
* @throws Exception
|
||||
*/
|
||||
private function streamInput($chunkSize = 8192)
|
||||
{
|
||||
$fh = fopen('php://input', 'rb');
|
||||
if ($fh === false) {
|
||||
throw new Exception("Could not open PHP input for reading.");
|
||||
}
|
||||
while (!feof($fh)) {
|
||||
$data = fread($fh, $chunkSize);
|
||||
if ($data === false) {
|
||||
throw new Exception("Could not read PHP input.");
|
||||
}
|
||||
yield $data;
|
||||
}
|
||||
fclose($fh);
|
||||
}
|
||||
}
|
|
@ -1703,23 +1703,17 @@ class ServersController extends AppController
|
|||
$result['status'] = 8;
|
||||
return new CakeResponse(array('body'=> json_encode($result), 'type' => 'json'));
|
||||
}
|
||||
return new CakeResponse(
|
||||
array(
|
||||
'body'=> json_encode(
|
||||
array(
|
||||
'status' => 1,
|
||||
'local_version' => implode('.', $local_version),
|
||||
'version' => implode('.', $version),
|
||||
'mismatch' => $mismatch,
|
||||
'newer' => $newer,
|
||||
'post' => isset($post) ? $post['status'] : 'too old',
|
||||
'response_encoding' => isset($post['content-encoding']) ? $post['content-encoding'] : null,
|
||||
'client_certificate' => $result['client_certificate'],
|
||||
)
|
||||
),
|
||||
'type' => 'json'
|
||||
)
|
||||
);
|
||||
return $this->RestResponse->viewData([
|
||||
'status' => 1,
|
||||
'local_version' => implode('.', $local_version),
|
||||
'version' => implode('.', $version),
|
||||
'mismatch' => $mismatch,
|
||||
'newer' => $newer,
|
||||
'post' => isset($post) ? $post['status'] : 'too old',
|
||||
'response_encoding' => isset($post['content-encoding']) ? $post['content-encoding'] : null,
|
||||
'request_encoding' => isset($result['info']['request_encoding']) ? $result['info']['request_encoding'] : null,
|
||||
'client_certificate' => $result['client_certificate'],
|
||||
], 'json');
|
||||
} else {
|
||||
$result['status'] = 3;
|
||||
}
|
||||
|
@ -1799,17 +1793,15 @@ class ServersController extends AppController
|
|||
|
||||
public function getVersion()
|
||||
{
|
||||
if (!$this->userRole['perm_auth']) {
|
||||
throw new MethodNotAllowedException('This action requires API access.');
|
||||
}
|
||||
$versionArray = $this->Server->checkMISPVersion();
|
||||
$this->set('response', array(
|
||||
$response = [
|
||||
'version' => $versionArray['major'] . '.' . $versionArray['minor'] . '.' . $versionArray['hotfix'],
|
||||
'perm_sync' => $this->userRole['perm_sync'],
|
||||
'perm_sighting' => $this->userRole['perm_sighting'],
|
||||
'perm_galaxy_editor' => $this->userRole['perm_galaxy_editor'],
|
||||
));
|
||||
$this->set('_serialize', 'response');
|
||||
'request_encoding' => $this->CompressedRequestHandler->supportedEncodings(),
|
||||
];
|
||||
return $this->RestResponse->viewData($response, $this->response->type());
|
||||
}
|
||||
|
||||
public function getPyMISPVersion()
|
||||
|
|
Loading…
Reference in New Issue