Intended usecase:
When using a filename in a Content-Disposition header
the value should not contain ; or "
When exporting, avoiding generation of an unexpected double-extension file
/** * Sends header indicating file download. * * @param string $filename Filename to include in headers if empty, * none Content-Disposition header will be sent. * @param string $mimetype MIME type to include in headers. * @param int $length Length of content (optional) * @param bool $no_cache Whether to include no-caching headers. * * @return void */ function PMA_downloadHeader($filename, $mimetype, $length = 0, $no_cache = true) { if ($no_cache) { PMA_noCacheHeader(); } /* Replace all possibly dangerous chars in filename */ $filename = Sanitize::sanitizeFilename($filename); if (!empty($filename)) { header('Content-Description: File Transfer'); header('Content-Disposition: attachment; filename="' . $filename . '"'); } header('Content-Type: ' . $mimetype); // inform the server that compression has been done, // to avoid a double compression (for example with Apache + mod_deflate) $notChromeOrLessThan43 = PMA_USR_BROWSER_AGENT != 'CHROME' || PMA_USR_BROWSER_AGENT == 'CHROME' && PMA_USR_BROWSER_VER < 43; if (strpos($mimetype, 'gzip') !== false && $notChromeOrLessThan43) { header('Content-Encoding: gzip'); } header('Content-Transfer-Encoding: binary'); if ($length > 0) { header('Content-Length: ' . $length); } }
list($save_filename, $message, $file_handle) = PMA_openExportFile($filename, $quick_export); // problem opening export file on server? if (!empty($message)) { PMA_showExportPage($db, $table, $export_type); } } else { /** * Send headers depending on whether the user chose to download a dump file * or not */ if ($asfile) { // Download // (avoid rewriting data containing HTML with anchors and forms; // this was reported to happen under Plesk) @ini_set('url_rewriter.tags', ''); $filename = Sanitize::sanitizeFilename($filename); PMA_downloadHeader($filename, $mime_type); } else { // HTML if ($export_type == 'database') { $num_tables = count($tables); if ($num_tables == 0) { $message = PMA\libraries\Message::error(__('No tables found in database.')); $active_page = 'db_export.php'; include 'db_export.php'; exit; } } list($html, $back_button) = PMA_getHtmlForDisplayedExportHeader($export_type, $db, $table); echo $html; unset($html);
/** * Test for Sanitize::sanitizeFilename * * @return void */ public function testSanitizeFilename() { $this->assertEquals('File_name_123', Sanitize::sanitizeFilename('File_name 123')); }
/** * Return the filename and MIME type for export file * * @param string $export_type type of export * @param string $remember_template whether to remember template * @param ExportPlugin $export_plugin the export plugin * @param string $compression compression asked * @param string $filename_template the filename template * * @return array the filename template and mime type */ function PMA_getExportFilenameAndMimetype($export_type, $remember_template, $export_plugin, $compression, $filename_template) { if ($export_type == 'server') { if (!empty($remember_template)) { $GLOBALS['PMA_Config']->setUserValue('pma_server_filename_template', 'Export/file_template_server', $filename_template); } } elseif ($export_type == 'database') { if (!empty($remember_template)) { $GLOBALS['PMA_Config']->setUserValue('pma_db_filename_template', 'Export/file_template_database', $filename_template); } } else { if (!empty($remember_template)) { $GLOBALS['PMA_Config']->setUserValue('pma_table_filename_template', 'Export/file_template_table', $filename_template); } } $filename = PMA\libraries\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); // 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)); $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 array($filename, $mime_type); }
/** * Sanitizes the file name. * * @param string $file_name file name * @param string $ext extension of the file * * @return string the sanitized file name * @access private */ private function _sanitizeName($file_name, $ext) { $file_name = Sanitize::sanitizeFilename($file_name); // Check if the user already added extension; // get the substring where the extension would be if it was included $extension_start_pos = mb_strlen($file_name) - mb_strlen($ext) - 1; $user_extension = mb_substr($file_name, $extension_start_pos, mb_strlen($file_name)); $required_extension = "." . $ext; if (mb_strtolower($user_extension) != $required_extension) { $file_name .= $required_extension; } return $file_name; }