isGzHandlerEnabled())
|| $GLOBALS['save_on_server']
|| $GLOBALS['config']->get('PMA_USR_BROWSER_AGENT') === 'CHROME');
}
/**
* Output handler for all exports, if needed buffering, it stores data into
* $this->dumpBuffer, otherwise it prints them out.
*
* @param string $line the insert statement
*/
public function outputHandler(string $line): bool
{
$GLOBALS['time_start'] ??= null;
$GLOBALS['save_filename'] ??= null;
// Kanji encoding convert feature
if ($GLOBALS['output_kanji_conversion']) {
$line = Encoding::kanjiStrConv($line, $GLOBALS['knjenc'], $GLOBALS['xkana'] ?? '');
}
// If we have to buffer data, we will perform everything at once at the end
if ($GLOBALS['buffer_needed']) {
$this->dumpBuffer .= $line;
if ($GLOBALS['onfly_compression']) {
$this->dumpBufferLength += strlen($line);
if ($this->dumpBufferLength > $GLOBALS['memory_limit']) {
if ($GLOBALS['output_charset_conversion']) {
$this->dumpBuffer = Encoding::convertString('utf-8', $GLOBALS['charset'], $this->dumpBuffer);
}
if ($GLOBALS['compression'] === 'gzip' && $this->gzencodeNeeded()) {
// as a gzipped file
// without the optional parameter level because it bugs
$this->dumpBuffer = (string) gzencode($this->dumpBuffer);
}
if ($GLOBALS['save_on_server']) {
$writeResult = @fwrite($GLOBALS['file_handle'], $this->dumpBuffer);
// Here, use strlen rather than mb_strlen to get the length
// in bytes to compare against the number of bytes written.
if ($writeResult != strlen($this->dumpBuffer)) {
$GLOBALS['message'] = Message::error(
__('Insufficient space to save the file %s.'),
);
$GLOBALS['message']->addParam($GLOBALS['save_filename']);
return false;
}
} else {
echo $this->dumpBuffer;
}
$this->dumpBuffer = '';
$this->dumpBufferLength = 0;
}
} else {
$timeNow = time();
if ($GLOBALS['time_start'] >= $timeNow + 30) {
$GLOBALS['time_start'] = $timeNow;
header('X-pmaPing: Pong');
}
}
} elseif ($GLOBALS['asfile']) {
if ($GLOBALS['output_charset_conversion']) {
$line = Encoding::convertString('utf-8', $GLOBALS['charset'], $line);
}
if ($GLOBALS['save_on_server'] && mb_strlen($line) > 0) {
if ($GLOBALS['file_handle'] !== null) {
$writeResult = @fwrite($GLOBALS['file_handle'], $line);
} else {
$writeResult = false;
}
// Here, use strlen rather than mb_strlen to get the length
// in bytes to compare against the number of bytes written.
if (! $writeResult || $writeResult != strlen($line)) {
$GLOBALS['message'] = Message::error(
__('Insufficient space to save the file %s.'),
);
$GLOBALS['message']->addParam($GLOBALS['save_filename']);
return false;
}
$timeNow = time();
if ($GLOBALS['time_start'] >= $timeNow + 30) {
$GLOBALS['time_start'] = $timeNow;
header('X-pmaPing: Pong');
}
} else {
// We export as file - output normally
echo $line;
}
} else {
// We export as html - replace special chars
echo htmlspecialchars($line, ENT_COMPAT);
}
return true;
}
/**
* Returns HTML containing the footer for a displayed export
*
* @param string $exportType the export type
* @param string $db the database name
* @param string $table the table name
*
* @return string the HTML output
*/
public function getHtmlForDisplayedExportFooter(
string $exportType,
string $db,
string $table,
): string {
/**
* Close the html tags and add the footers for on-screen export
*/
return ''
. ' '
. '
'
// bottom back button
. $this->getHTMLForBackButton($exportType, $db, $table)
. $this->getHTMLForRefreshButton($exportType)
. ''
. '' . "\n";
}
/**
* Computes the memory limit for export
*
* @return int the memory limit
*/
public function getMemoryLimit(): int
{
$memoryLimit = trim((string) ini_get('memory_limit'));
$memoryLimitNumber = (int) substr($memoryLimit, 0, -1);
$lowerLastChar = strtolower(substr($memoryLimit, -1));
// 2 MB as default
if ($memoryLimit === '' || $memoryLimit === '0' || $memoryLimit === '-1') {
$memoryLimit = 2 * 1024 * 1024;
} elseif ($lowerLastChar === 'm') {
$memoryLimit = $memoryLimitNumber * 1024 * 1024;
} elseif ($lowerLastChar === 'k') {
$memoryLimit = $memoryLimitNumber * 1024;
} elseif ($lowerLastChar === 'g') {
$memoryLimit = $memoryLimitNumber * 1024 * 1024 * 1024;
} else {
$memoryLimit = (int) $memoryLimit;
}
// Some of memory is needed for other things and as threshold.
// During export I had allocated (see memory_get_usage function)
// approx 1.2MB so this comes from that.
if ($memoryLimit > 1500000) {
$memoryLimit -= 1500000;
}
// Some memory is needed for compression, assume 1/3
$memoryLimit /= 8;
return $memoryLimit;
}
/**
* Returns the filename and MIME type for a compression and an export plugin
*
* @param ExportPlugin $exportPlugin the export plugin
* @param string $compression compression asked
* @param string $filename the filename
*
* @return string[] the filename and mime type
*/
public function getFinalFilenameAndMimetypeForFilename(
ExportPlugin $exportPlugin,
string $compression,
string $filename,
): array {
// Grab basic dump extension and mime type
// Check if the user already added extension;
// get the substring where the extension would be if it was included
$requiredExtension = '.' . $exportPlugin->getProperties()->getExtension();
$extensionLength = mb_strlen($requiredExtension);
$userExtension = mb_substr($filename, -$extensionLength);
if (mb_strtolower($userExtension) !== $requiredExtension) {
$filename .= $requiredExtension;
}
$mediaType = $exportPlugin->getProperties()->getMimeType();
// If dump is going to be compressed, set correct mime_type and add
// compression to extension
if ($compression === 'gzip') {
$filename .= '.gz';
$mediaType = 'application/x-gzip';
} elseif ($compression === 'zip') {
$filename .= '.zip';
$mediaType = 'application/zip';
}
return [$filename, $mediaType];
}
/**
* Return the filename and MIME type for export file
*
* @param string $exportType type of export
* @param string $rememberTemplate whether to remember template
* @param ExportPlugin $exportPlugin the export plugin
* @param string $compression compression asked
* @param string $filenameTemplate the filename template
*
* @return string[] the filename template and mime type
*/
public function getFilenameAndMimetype(
string $exportType,
string $rememberTemplate,
ExportPlugin $exportPlugin,
string $compression,
string $filenameTemplate,
): array {
if ($exportType === 'server') {
if ($rememberTemplate !== '' && $rememberTemplate !== '0') {
$GLOBALS['config']->setUserValue(
'pma_server_filename_template',
'Export/file_template_server',
$filenameTemplate,
);
}
} elseif ($exportType === 'database') {
if ($rememberTemplate !== '' && $rememberTemplate !== '0') {
$GLOBALS['config']->setUserValue(
'pma_db_filename_template',
'Export/file_template_database',
$filenameTemplate,
);
}
} elseif ($exportType === 'raw') {
if ($rememberTemplate !== '' && $rememberTemplate !== '0') {
$GLOBALS['config']->setUserValue(
'pma_raw_filename_template',
'Export/file_template_raw',
$filenameTemplate,
);
}
} else {
if ($rememberTemplate !== '' && $rememberTemplate !== '0') {
$GLOBALS['config']->setUserValue(
'pma_table_filename_template',
'Export/file_template_table',
$filenameTemplate,
);
}
}
$filename = Util::expandUserString($filenameTemplate);
// remove dots in filename (coming from either the template or already
// part of the filename) to avoid a remote code execution vulnerability
$filename = Sanitize::sanitizeFilename($filename, true);
return $this->getFinalFilenameAndMimetypeForFilename($exportPlugin, $compression, $filename);
}
/**
* Open the export file
*
* @param string $filename the export filename
* @param bool $quickExport whether it's a quick export or not
*
* @return array the full save filename, possible message and the file handle
*/
public function openFile(string $filename, bool $quickExport): array
{
$fileHandle = null;
$message = '';
$doNotSaveItOver = true;
if (isset($_POST['quick_export_onserver_overwrite'])) {
$doNotSaveItOver = $_POST['quick_export_onserver_overwrite'] !== 'saveitover';
}
$saveFilename = Util::userDir((string) ($GLOBALS['cfg']['SaveDir'] ?? ''))
. preg_replace('@[/\\\\]@', '_', $filename);
if (
@file_exists($saveFilename)
&& ((! $quickExport && empty($_POST['onserver_overwrite']))
|| ($quickExport
&& $doNotSaveItOver))
) {
$message = Message::error(
__(
'File %s already exists on server, change filename or check overwrite option.',
),
);
$message->addParam($saveFilename);
} elseif (@is_file($saveFilename) && ! @is_writable($saveFilename)) {
$message = Message::error(
__(
'The web server does not have permission to save the file %s.',
),
);
$message->addParam($saveFilename);
} else {
$fileHandle = @fopen($saveFilename, 'w');
if ($fileHandle === false) {
$message = Message::error(
__(
'The web server does not have permission to save the file %s.',
),
);
$message->addParam($saveFilename);
}
}
return [$saveFilename, $message, $fileHandle];
}
/**
* Close the export file
*
* @param resource $fileHandle the export file handle
* @param string $dumpBuffer the current dump buffer
* @param string $saveFilename the export filename
*
* @return Message a message object (or empty string)
*/
public function closeFile(
$fileHandle,
string $dumpBuffer,
string $saveFilename,
): Message {
$writeResult = @fwrite($fileHandle, $dumpBuffer);
fclose($fileHandle);
// Here, use strlen rather than mb_strlen to get the length
// in bytes to compare against the number of bytes written.
if ($dumpBuffer !== '' && $writeResult !== strlen($dumpBuffer)) {
return new Message(
__('Insufficient space to save the file %s.'),
Message::ERROR,
[$saveFilename],
);
}
return new Message(
__('Dump has been saved to file %s.'),
Message::SUCCESS,
[$saveFilename],
);
}
/**
* Compress the export buffer
*
* @param array|string $dumpBuffer the current dump buffer
* @param string $compression the compression mode
* @param string $filename the filename
*/
public function compress(array|string $dumpBuffer, string $compression, string $filename): array|string|bool
{
if ($compression === 'zip' && function_exists('gzcompress')) {
$zipExtension = new ZipExtension();
$filename = substr($filename, 0, -4); // remove extension (.zip)
$dumpBuffer = $zipExtension->createFile($dumpBuffer, $filename);
} elseif ($compression === 'gzip' && $this->gzencodeNeeded() && is_string($dumpBuffer)) {
// without the optional parameter level because it bugs
$dumpBuffer = gzencode($dumpBuffer);
}
return $dumpBuffer;
}
/**
* Saves the dump buffer for a particular table in an array
* Used in separate files export
*
* @param string $objectName the name of current object to be stored
* @param bool $append optional boolean to append to an existing index or not
*/
public function saveObjectInBuffer(string $objectName, bool $append = false): void
{
if ($this->dumpBuffer !== '') {
if ($append && isset($this->dumpBufferObjects[$objectName])) {
$this->dumpBufferObjects[$objectName] .= $this->dumpBuffer;
} else {
$this->dumpBufferObjects[$objectName] = $this->dumpBuffer;
}
}
// Re - initialize
$this->dumpBuffer = '';
$this->dumpBufferLength = 0;
}
/**
* Returns HTML containing the header for a displayed export
*
* @param string $exportType the export type
* @param string $db the database name
* @param string $table the table name
*
* @return string the generated HTML and back button
*/
public function getHtmlForDisplayedExportHeader(
string $exportType,
string $db,
string $table,
): string {
/**
* Displays a back button with all the $_POST data in the URL
*/
return '