From 1b56c8e0edf245c47ad625a9abb4f5815f8733dc Mon Sep 17 00:00:00 2001 From: Saurabh Srivastava Date: Wed, 10 Apr 2019 13:52:46 +0530 Subject: [PATCH 1/5] Fix 15064 - instead of using free_query parameter, now updating export_type to raw -- more modular approach Signed-off-by: Saurabh Srivastava --- .../Controllers/Database/ExportController.php | 8 ++- .../classes/Controllers/ExportController.php | 22 ++++++ libraries/classes/Display/Export.php | 2 +- libraries/classes/Display/Results.php | 6 ++ libraries/classes/Export.php | 40 +++++++++++ .../classes/Plugins/Export/ExportCsv.php | 14 ++++ .../classes/Plugins/Export/ExportJson.php | 70 +++++++++++++++++++ .../classes/Plugins/Export/ExportLatex.php | 14 ++++ .../Plugins/Export/ExportMediawiki.php | 18 ++++- .../classes/Plugins/Export/ExportOds.php | 14 ++++ .../classes/Plugins/Export/ExportOdt.php | 23 +++++- .../classes/Plugins/Export/ExportPdf.php | 23 ++++++ .../classes/Plugins/Export/ExportPhparray.php | 14 ++++ .../classes/Plugins/Export/ExportSql.php | 21 ++++++ .../classes/Plugins/Export/ExportTexytext.php | 18 ++++- .../classes/Plugins/Export/ExportYaml.php | 14 ++++ libraries/classes/Plugins/ExportPlugin.php | 16 +++++ templates/display/export/option_header.twig | 2 + 18 files changed, 332 insertions(+), 7 deletions(-) diff --git a/libraries/classes/Controllers/Database/ExportController.php b/libraries/classes/Controllers/Database/ExportController.php index f8765a38e2..ab66e85353 100644 --- a/libraries/classes/Controllers/Database/ExportController.php +++ b/libraries/classes/Controllers/Database/ExportController.php @@ -168,10 +168,16 @@ final class ExportController extends AbstractController $unlim_num_rows = 0; } + if (isset($_POST['raw_query'])) { + $export_type = 'raw'; + } else { + $export_type = 'table'; + } + $displayExport = new DisplayExport(); $this->response->addHTML( $displayExport->getDisplay( - 'database', + $export_type, $db, $table, $sql_query, diff --git a/libraries/classes/Controllers/ExportController.php b/libraries/classes/Controllers/ExportController.php index df594d411d..61fe1005ff 100644 --- a/libraries/classes/Controllers/ExportController.php +++ b/libraries/classes/Controllers/ExportController.php @@ -334,6 +334,10 @@ final class ExportController extends AbstractController 'db' => $db, 'table' => $table, ]); + } elseif ($export_type === 'raw') { + $err_url = Url::getFromRoute('/server/export', [ + 'sql_query' => $sql_query, + ]); } else { Core::fatalError(__('Bad parameters!')); } @@ -409,6 +413,11 @@ final class ExportController extends AbstractController $mime_type = ''; } + // For raw query export, filename will be export.extension + if ($export_type === 'raw') { + $filename = 'export.' . $export_plugin->getProperties()->getExtension(); + } + // Open file on server if needed if ($save_on_server) { list($save_filename, $message, $file_handle) = $this->export->openFile( @@ -482,6 +491,10 @@ final class ExportController extends AbstractController $whatStrucOrData = $GLOBALS[$what . '_structure_or_data']; + if ($export_type === 'raw') { + $whatStrucOrData = 'raw'; + } + /** * Builds the dump */ @@ -556,6 +569,15 @@ final class ExportController extends AbstractController $separate_files ); } + } elseif ($export_type === 'raw') { + Export::exportRaw( + $whatStrucOrData, + $export_plugin, + $crlf, + $err_url, + $sql_query, + $export_type + ); } else { // We export just one table // $allrows comes from the form when "Dump all rows" has been selected diff --git a/libraries/classes/Display/Export.php b/libraries/classes/Display/Export.php index 7a89c52605..e53c43d65a 100644 --- a/libraries/classes/Display/Export.php +++ b/libraries/classes/Display/Export.php @@ -552,7 +552,7 @@ class Export $html .= $this->getHtmlForOptionsSelection($exportType, $multiValues); $tableObject = new Table($table, $db); - if (strlen($table) > 0 && empty($numTables) && ! $tableObject->isMerge()) { + if (strlen($table) > 0 && empty($numTables) && ! $tableObject->isMerge() && $exportType != 'raw') { $html .= $this->getHtmlForOptionsRows($db, $table, $unlimNumRows); } diff --git a/libraries/classes/Display/Results.php b/libraries/classes/Display/Results.php index b3a3c83a5b..90b5ef7b0e 100644 --- a/libraries/classes/Display/Results.php +++ b/libraries/classes/Display/Results.php @@ -4933,6 +4933,12 @@ class Results $_url_params['single_table'] = 'true'; } + // In case this query doesn't involve any tables, + // implies only raw query is to be exported + if (! $analyzed_sql_results['select_tables']) { + $_url_params['raw_query'] = 'true'; + } + if (! $header_shown) { $results_operations_html .= $header; $header_shown = true; diff --git a/libraries/classes/Export.php b/libraries/classes/Export.php index de0862c483..637d8c4b10 100644 --- a/libraries/classes/Export.php +++ b/libraries/classes/Export.php @@ -313,6 +313,14 @@ class Export $filename_template ); } + } elseif ($export_type == 'raw') { + if (! empty($remember_template)) { + $GLOBALS['PMA_Config']->setUserValue( + 'pma_raw_filename_template', + 'Export/file_template_raw', + $filename_template + ); + } } else { if (! empty($remember_template)) { $GLOBALS['PMA_Config']->setUserValue( @@ -890,6 +898,38 @@ class Export } } + /** + * Export raw query + * + * @param string $whatStrucOrData whether to export structure for each table or raw + * @param ExportPlugin $export_plugin the selected export plugin + * @param string $crlf end of line character(s) + * @param string $err_url the URL in case of error + * @param string $sql_query the query to be executed + * @param string $export_type the export type + * + * @return void + */ + public static function exportRaw( + string $whatStrucOrData, + ExportPlugin $export_plugin, + string $crlf, + string $err_url, + string $sql_query, + string $export_type + ): void { + // In case the we need to dump just the raw query + if ($whatStrucOrData == 'raw') { + if (! $export_plugin->exportRawQuery( + $err_url, + $sql_query, + $crlf + )) { + return; + } + } + } + /** * Export at the table level * diff --git a/libraries/classes/Plugins/Export/ExportCsv.php b/libraries/classes/Plugins/Export/ExportCsv.php index 5d8aa848c7..317fa1a641 100644 --- a/libraries/classes/Plugins/Export/ExportCsv.php +++ b/libraries/classes/Plugins/Export/ExportCsv.php @@ -338,4 +338,18 @@ class ExportCsv extends ExportPlugin return true; } + + /** + * Outputs result of raw query in CSV format + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the end of line sequence + * + * @return bool if succeeded + */ + public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool + { + return $this->exportData('', '', $crlf, $err_url, $sql_query); + } } diff --git a/libraries/classes/Plugins/Export/ExportJson.php b/libraries/classes/Plugins/Export/ExportJson.php index 826c1cdd2f..f8588bebf5 100644 --- a/libraries/classes/Plugins/Export/ExportJson.php +++ b/libraries/classes/Plugins/Export/ExportJson.php @@ -287,4 +287,74 @@ class ExportJson extends ExportPlugin return true; } + + /** + * Outputs result raw query in JSON format + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the end of line sequence + * + * @return bool if succeeded + */ + public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool + { + $buffer = $this->encode( + [ + 'type' => 'raw', + 'data' => '@@DATA@@', + ] + ); + list($header, $footer) = explode('"@@DATA@@"', $buffer); + + if (! $this->export->outputHandler($header . $crlf . '[' . $crlf)) { + return false; + } + + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $columns_cnt = $GLOBALS['dbi']->numFields($result); + + $columns = []; + for ($i = 0; $i < $columns_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + $columns[$i] = stripslashes($col_as); + } + + $record_cnt = 0; + while ($record = $GLOBALS['dbi']->fetchRow($result)) { + $record_cnt++; + + if ($record_cnt > 1) { + if (! $this->export->outputHandler(',' . $crlf)) { + return false; + } + } + + $data = []; + + for ($i = 0; $i < $columns_cnt; $i++) { + $data[$columns[$i]] = $record[$i]; + } + + $encodedData = $this->encode($data); + if (! $encodedData) { + return false; + } + if (! $this->export->outputHandler($encodedData)) { + return false; + } + } + + if (! $this->export->outputHandler($crlf . ']' . $crlf . $footer . $crlf)) { + return false; + } + + $GLOBALS['dbi']->freeResult($result); + + return true; + } } diff --git a/libraries/classes/Plugins/Export/ExportLatex.php b/libraries/classes/Plugins/Export/ExportLatex.php index 0a8079d7d0..11c88c648d 100644 --- a/libraries/classes/Plugins/Export/ExportLatex.php +++ b/libraries/classes/Plugins/Export/ExportLatex.php @@ -439,6 +439,20 @@ class ExportLatex extends ExportPlugin return true; } // end getTableLaTeX + /** + * Outputs result raw query + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the seperator for a file + * + * @return bool if succeeded + */ + public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool + { + return $this->exportData('', '', $crlf, $err_url, $sql_query); + } + /** * Outputs table's structure * diff --git a/libraries/classes/Plugins/Export/ExportMediawiki.php b/libraries/classes/Plugins/Export/ExportMediawiki.php index 0012af3246..ddd88844c7 100644 --- a/libraries/classes/Plugins/Export/ExportMediawiki.php +++ b/libraries/classes/Plugins/Export/ExportMediawiki.php @@ -289,7 +289,9 @@ class ExportMediawiki extends ExportPlugin // Print data comment $output = $this->_exportComment( - 'Table data for ' . Util::backquote($table_alias) + $table_alias != '' + ? 'Table data for ' . Util::backquote($table_alias) + : 'Query results' ); // Begin the table construction @@ -348,6 +350,20 @@ class ExportMediawiki extends ExportPlugin return $this->export->outputHandler($output); } + /** + * Outputs result raw query in MediaWiki format + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the end of line sequence + * + * @return bool if succeeded + */ + public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool + { + return $this->exportData('', '', $crlf, $err_url, $sql_query); + } + /** * Outputs comments containing info about the exported tables * diff --git a/libraries/classes/Plugins/Export/ExportOds.php b/libraries/classes/Plugins/Export/ExportOds.php index ad84f84c3f..91f04afe1f 100644 --- a/libraries/classes/Plugins/Export/ExportOds.php +++ b/libraries/classes/Plugins/Export/ExportOds.php @@ -337,4 +337,18 @@ class ExportOds extends ExportPlugin return true; } + + /** + * Outputs result raw query in ODS format + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the end of line sequence + * + * @return bool if succeeded + */ + public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool + { + return $this->exportData('', '', $crlf, $err_url, $sql_query); + } } diff --git a/libraries/classes/Plugins/Export/ExportOdt.php b/libraries/classes/Plugins/Export/ExportOdt.php index 32f346904b..b01584549f 100644 --- a/libraries/classes/Plugins/Export/ExportOdt.php +++ b/libraries/classes/Plugins/Export/ExportOdt.php @@ -260,9 +260,12 @@ class ExportOdt extends ExportPlugin $GLOBALS['odt_buffer'] .= '' - . __('Dumping data for table') . ' ' . htmlspecialchars($table_alias) - . '' + . ' text:is-list-header="true">'; + $table_alias != '' + ? $GLOBALS['odt_buffer'] .= __('Dumping data for table') . ' ' . htmlspecialchars($table_alias) + : $GLOBALS['odt_buffer'] .= __('Dumping data for query result'); + $GLOBALS['odt_buffer'] + .= '' . '' . 'exportData('', '', $crlf, $err_url, $sql_query); + } + /** * Returns a stand-in CREATE definition to resolve view dependencies * diff --git a/libraries/classes/Plugins/Export/ExportPdf.php b/libraries/classes/Plugins/Export/ExportPdf.php index 4a73b76051..5cfd5c4282 100644 --- a/libraries/classes/Plugins/Export/ExportPdf.php +++ b/libraries/classes/Plugins/Export/ExportPdf.php @@ -232,6 +232,29 @@ class ExportPdf extends ExportPlugin return true; } // end of the 'PMA_exportData()' function + /** + * Outputs result of raw query in PDF format + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the end of line sequence + * + * @return bool if succeeded + */ + public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool + { + $pdf = $this->_getPdf(); + $attr = [ + 'dbAlias' => '----', + 'tableAlias' => '----', + 'purpose' => __('Query result data'), + ]; + $pdf->setAttributes($attr); + $pdf->mysqlReport($sql_query); + + return true; + } + /** * Outputs table structure * diff --git a/libraries/classes/Plugins/Export/ExportPhparray.php b/libraries/classes/Plugins/Export/ExportPhparray.php index 60f6a4cffb..9a436fd457 100644 --- a/libraries/classes/Plugins/Export/ExportPhparray.php +++ b/libraries/classes/Plugins/Export/ExportPhparray.php @@ -249,4 +249,18 @@ class ExportPhparray extends ExportPlugin return true; } + + /** + * Outputs result of raw query as PHP array + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the end of line sequence + * + * @return bool if succeeded + */ + public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool + { + return $this->exportData('', '', $crlf, $err_url, $sql_query); + } } diff --git a/libraries/classes/Plugins/Export/ExportSql.php b/libraries/classes/Plugins/Export/ExportSql.php index 1bf95ef2e4..472d13e6a1 100644 --- a/libraries/classes/Plugins/Export/ExportSql.php +++ b/libraries/classes/Plugins/Export/ExportSql.php @@ -90,6 +90,13 @@ class ExportSql extends ExportPlugin $hide_sql = true; } + // In case we have `raw_query` parameter set, + // we initialize SQL option + if (isset($_REQUEST['raw_query'])) { + $hide_structure = false; + $hide_sql = false; + } + if (! $hide_sql) { $exportPluginProperties = new ExportPluginProperties(); $exportPluginProperties->setText('SQL'); @@ -2006,6 +2013,20 @@ class ExportSql extends ExportPlugin return $schema_create; } // end of the '_getTableComments()' function + /** + * Outputs raw query + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the seperator for a file + * + * @return bool if succeeded + */ + public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool + { + return $this->export->outputHandler($sql_query); + } + /** * Outputs table's structure * diff --git a/libraries/classes/Plugins/Export/ExportTexytext.php b/libraries/classes/Plugins/Export/ExportTexytext.php index 6cee2ba2c4..3fc369c573 100644 --- a/libraries/classes/Plugins/Export/ExportTexytext.php +++ b/libraries/classes/Plugins/Export/ExportTexytext.php @@ -186,7 +186,9 @@ class ExportTexytext extends ExportPlugin $this->initAlias($aliases, $db_alias, $table_alias); if (! $this->export->outputHandler( - '== ' . __('Dumping data for table') . ' ' . $table_alias . "\n\n" + $table_alias != '' + ? '== ' . __('Dumping data for table') . ' ' . $table_alias . "\n\n" + : '==' . __('Dumping data for query result') . "\n\n" ) ) { return false; @@ -245,6 +247,20 @@ class ExportTexytext extends ExportPlugin return true; } + /** + * Outputs result raw query in TexyText format + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the end of line sequence + * + * @return bool if succeeded + */ + public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool + { + return $this->exportData('', '', $crlf, $err_url, $sql_query); + } + /** * Returns a stand-in CREATE definition to resolve view dependencies * diff --git a/libraries/classes/Plugins/Export/ExportYaml.php b/libraries/classes/Plugins/Export/ExportYaml.php index 1098e17d2d..2e6b126f81 100644 --- a/libraries/classes/Plugins/Export/ExportYaml.php +++ b/libraries/classes/Plugins/Export/ExportYaml.php @@ -219,4 +219,18 @@ class ExportYaml extends ExportPlugin return true; } // end getTableYAML + + /** + * Outputs result raw query in YAML format + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the end of line sequence + * + * @return bool if succeeded + */ + public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool + { + return $this->exportData('', '', $crlf, $err_url, $sql_query); + } } diff --git a/libraries/classes/Plugins/ExportPlugin.php b/libraries/classes/Plugins/ExportPlugin.php index ee7b93a7bf..c87aa6b4c6 100644 --- a/libraries/classes/Plugins/ExportPlugin.php +++ b/libraries/classes/Plugins/ExportPlugin.php @@ -139,6 +139,22 @@ abstract class ExportPlugin return true; } + /** + * Outputs for raw query + * + * @param string $err_url the url to go back in case of error + * @param string $sql_query the rawquery to output + * @param string $crlf the seperator for a file + * + * @return bool if succeeded + */ + public function exportRawQuery( + string $err_url, + string $sql_query, + string $crlf + ): bool { + } + /** * Outputs table's structure * diff --git a/templates/display/export/option_header.twig b/templates/display/export/option_header.twig index 579a17315e..d3641827a1 100644 --- a/templates/display/export/option_header.twig +++ b/templates/display/export/option_header.twig @@ -5,6 +5,8 @@ {% trans 'Exporting databases from the current server' %} {% elseif export_type == 'database' %} {{ 'Exporting tables from "%s" database'|trans|format(db) }} + {% elseif export_type == 'raw' %} + {{ 'Exporting raw query' }} {% else %} {{ 'Exporting rows from "%s" table'|trans|format(table) }} {% endif %} From 0adb361d3c1b822a6ab423bdec3397fd8a92878a Mon Sep 17 00:00:00 2001 From: William Desportes Date: Thu, 23 Jan 2020 13:05:05 +0100 Subject: [PATCH 2/5] Some code fixes Signed-off-by: William Desportes --- libraries/classes/Display/Export.php | 2 +- libraries/classes/Export.php | 2 +- templates/display/export/option_header.twig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/classes/Display/Export.php b/libraries/classes/Display/Export.php index e53c43d65a..74af369ab1 100644 --- a/libraries/classes/Display/Export.php +++ b/libraries/classes/Display/Export.php @@ -552,7 +552,7 @@ class Export $html .= $this->getHtmlForOptionsSelection($exportType, $multiValues); $tableObject = new Table($table, $db); - if (strlen($table) > 0 && empty($numTables) && ! $tableObject->isMerge() && $exportType != 'raw') { + if (strlen($table) > 0 && empty($numTables) && ! $tableObject->isMerge() && $exportType !== 'raw') { $html .= $this->getHtmlForOptionsRows($db, $table, $unlimNumRows); } diff --git a/libraries/classes/Export.php b/libraries/classes/Export.php index 637d8c4b10..53edd6485a 100644 --- a/libraries/classes/Export.php +++ b/libraries/classes/Export.php @@ -919,7 +919,7 @@ class Export string $export_type ): void { // In case the we need to dump just the raw query - if ($whatStrucOrData == 'raw') { + if ($whatStrucOrData === 'raw') { if (! $export_plugin->exportRawQuery( $err_url, $sql_query, diff --git a/templates/display/export/option_header.twig b/templates/display/export/option_header.twig index d3641827a1..a7e614eac3 100644 --- a/templates/display/export/option_header.twig +++ b/templates/display/export/option_header.twig @@ -6,7 +6,7 @@ {% elseif export_type == 'database' %} {{ 'Exporting tables from "%s" database'|trans|format(db) }} {% elseif export_type == 'raw' %} - {{ 'Exporting raw query' }} + {% trans 'Exporting raw query' %} {% else %} {{ 'Exporting rows from "%s" table'|trans|format(table) }} {% endif %} From 501397a5d6be6f7a58e64bdc170f879ff5d44ba5 Mon Sep 17 00:00:00 2001 From: William Desportes Date: Thu, 23 Jan 2020 13:24:06 +0100 Subject: [PATCH 3/5] Add a message when the plugin does not support the export method Example: - no db, table selected. - Type a raw query, export using codegen. - See error message CodeGen can not export if it does not have db and table params Signed-off-by: William Desportes --- libraries/classes/Export.php | 3 +++ libraries/classes/Plugins/ExportPlugin.php | 1 + 2 files changed, 4 insertions(+) diff --git a/libraries/classes/Export.php b/libraries/classes/Export.php index 53edd6485a..f4b21bcfaa 100644 --- a/libraries/classes/Export.php +++ b/libraries/classes/Export.php @@ -925,6 +925,9 @@ class Export $sql_query, $crlf )) { + $GLOBALS['message'] = Message::error( + __('Exporting a raw query is not supported for this export method.') + ); return; } } diff --git a/libraries/classes/Plugins/ExportPlugin.php b/libraries/classes/Plugins/ExportPlugin.php index c87aa6b4c6..1d82a4282e 100644 --- a/libraries/classes/Plugins/ExportPlugin.php +++ b/libraries/classes/Plugins/ExportPlugin.php @@ -153,6 +153,7 @@ abstract class ExportPlugin string $sql_query, string $crlf ): bool { + return false; } /** From a7a7b73b554e9f5d58ecd6eae859ee7699d3f505 Mon Sep 17 00:00:00 2001 From: William Desportes Date: Thu, 23 Jan 2020 13:34:38 +0100 Subject: [PATCH 4/5] Support return back from raw export Signed-off-by: William Desportes --- libraries/classes/Controllers/Database/ExportController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/classes/Controllers/Database/ExportController.php b/libraries/classes/Controllers/Database/ExportController.php index ab66e85353..9545f9f84d 100644 --- a/libraries/classes/Controllers/Database/ExportController.php +++ b/libraries/classes/Controllers/Database/ExportController.php @@ -168,7 +168,8 @@ final class ExportController extends AbstractController $unlim_num_rows = 0; } - if (isset($_POST['raw_query'])) { + $isReturnBackFromRawExport = isset($_POST['export_type']) && $_POST['export_type'] === 'raw'; + if (isset($_POST['raw_query']) || $isReturnBackFromRawExport) { $export_type = 'raw'; } else { $export_type = 'table'; From ba8946d5f82647d12dd3a30216d64cca748ecd5e Mon Sep 17 00:00:00 2001 From: William Desportes Date: Thu, 23 Jan 2020 14:01:22 +0100 Subject: [PATCH 5/5] Move some code to getFinalFilenameAndMimetypeForFilename and add tests Signed-off-by: William Desportes --- .../classes/Controllers/ExportController.php | 2 +- libraries/classes/Export.php | 81 ++++++++++++------- test/classes/ExportTest.php | 26 ++++++ 3 files changed, 78 insertions(+), 31 deletions(-) diff --git a/libraries/classes/Controllers/ExportController.php b/libraries/classes/Controllers/ExportController.php index 61fe1005ff..df1f508280 100644 --- a/libraries/classes/Controllers/ExportController.php +++ b/libraries/classes/Controllers/ExportController.php @@ -415,7 +415,7 @@ final class ExportController extends AbstractController // For raw query export, filename will be export.extension if ($export_type === 'raw') { - $filename = 'export.' . $export_plugin->getProperties()->getExtension(); + [$filename ] = $this->export->getFinalFilenameAndMimetypeForFilename($export_plugin, $compression, 'export'); } // Open file on server if needed diff --git a/libraries/classes/Export.php b/libraries/classes/Export.php index f4b21bcfaa..0049c590a2 100644 --- a/libraries/classes/Export.php +++ b/libraries/classes/Export.php @@ -279,6 +279,52 @@ class Export return $memory_limit; } + /** + * 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 + $extensionStartPos = mb_strlen($filename) - mb_strlen( + $exportPlugin->getProperties()->getExtension() + ) - 1; + $userExtension = mb_substr( + $filename, + $extensionStartPos, + mb_strlen($filename) + ); + $requiredExtension = '.' . $exportPlugin->getProperties()->getExtension(); + if (mb_strtolower($userExtension) != $requiredExtension) { + $filename .= $requiredExtension; + } + $mime_type = $exportPlugin->getProperties()->getMimeType(); + + // If dump is going to be compressed, set correct mime_type and add + // compression to extension + if ($compression === 'gzip') { + $filename .= '.gz'; + $mime_type = 'application/x-gzip'; + } elseif ($compression === 'zip') { + $filename .= '.zip'; + $mime_type = 'application/zip'; + } + return [ + $filename, + $mime_type, + ]; + } + /** * Return the filename and MIME type for export file * @@ -333,38 +379,13 @@ class Export $filename = Util::expandUserString($filename_template); // 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, $replaceDots = true); + $filename = Sanitize::sanitizeFilename($filename, true); - // 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 - $extension_start_pos = mb_strlen($filename) - mb_strlen( - $export_plugin->getProperties()->getExtension() - ) - 1; - $user_extension = mb_substr( - $filename, - $extension_start_pos, - mb_strlen($filename) + return $this->getFinalFilenameAndMimetypeForFilename( + $export_plugin, + $compression, + $filename ); - $required_extension = '.' . $export_plugin->getProperties()->getExtension(); - if (mb_strtolower($user_extension) != $required_extension) { - $filename .= $required_extension; - } - $mime_type = $export_plugin->getProperties()->getMimeType(); - - // If dump is going to be compressed, set correct mime_type and add - // compression to extension - if ($compression == 'gzip') { - $filename .= '.gz'; - $mime_type = 'application/x-gzip'; - } elseif ($compression == 'zip') { - $filename .= '.zip'; - $mime_type = 'application/zip'; - } - return [ - $filename, - $mime_type, - ]; } /** diff --git a/test/classes/ExportTest.php b/test/classes/ExportTest.php index 27f3b46b68..caee5f0341 100644 --- a/test/classes/ExportTest.php +++ b/test/classes/ExportTest.php @@ -7,6 +7,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Tests; use PhpMyAdmin\Export; +use PhpMyAdmin\Plugins\Export\ExportPhparray; use PHPUnit\Framework\TestCase; /** @@ -103,4 +104,29 @@ class ExportTest extends TestCase $actual = $this->export->mergeAliases($aliases1, $aliases2); $this->assertEquals($expected, $actual); } + + /** + * Test for getFinalFilenameAndMimetypeForFilename + * + * @return void + */ + public function testGetFinalFilenameAndMimetypeForFilename() + { + $exportPlugin = new ExportPhparray(); + $finalFileName = $this->export->getFinalFilenameAndMimetypeForFilename($exportPlugin, 'zip', 'myfilename'); + $this->assertSame([ + 'myfilename.php.zip', + 'application/zip', + ], $finalFileName); + $finalFileName = $this->export->getFinalFilenameAndMimetypeForFilename($exportPlugin, 'gzip', 'myfilename'); + $this->assertSame([ + 'myfilename.php.gz', + 'application/x-gzip', + ], $finalFileName); + $finalFileName = $this->export->getFinalFilenameAndMimetypeForFilename($exportPlugin, 'gzip', 'export.db1.table1.file'); + $this->assertSame([ + 'export.db1.table1.file.php.gz', + 'application/x-gzip', + ], $finalFileName); + } }