It's always an empty string. Signed-off-by: Maurício Meneghini Fauth <mauricio@mfauth.net>
512 lines
17 KiB
PHP
512 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* Used to render the header of PMA's pages
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace PhpMyAdmin;
|
|
|
|
use PhpMyAdmin\ConfigStorage\Relation;
|
|
use PhpMyAdmin\Console\Console;
|
|
use PhpMyAdmin\Container\ContainerBuilder;
|
|
use PhpMyAdmin\Dbal\DatabaseInterface;
|
|
use PhpMyAdmin\Html\Generator;
|
|
use PhpMyAdmin\I18n\LanguageManager;
|
|
use PhpMyAdmin\Navigation\Navigation;
|
|
use PhpMyAdmin\Theme\ThemeManager;
|
|
|
|
use function array_merge;
|
|
use function htmlspecialchars;
|
|
use function ini_get;
|
|
use function json_encode;
|
|
use function sprintf;
|
|
use function strtolower;
|
|
|
|
use const JSON_HEX_TAG;
|
|
|
|
/**
|
|
* Class used to output the HTTP and HTML headers
|
|
*/
|
|
class Header
|
|
{
|
|
/**
|
|
* Scripts instance
|
|
*/
|
|
private Scripts $scripts;
|
|
/**
|
|
* Menu instance
|
|
*/
|
|
private Menu $menu;
|
|
/**
|
|
* The page title
|
|
*/
|
|
private string $title = '';
|
|
/**
|
|
* The value for the id attribute for the body tag
|
|
*/
|
|
private string $bodyId = '';
|
|
/**
|
|
* Whether to show the top menu
|
|
*/
|
|
private bool $menuEnabled;
|
|
/**
|
|
* Whether to show the warnings
|
|
*/
|
|
private bool $warningsEnabled = true;
|
|
|
|
private UserPreferences $userPreferences;
|
|
|
|
private bool $isTransformationWrapper = false;
|
|
|
|
public function __construct(
|
|
private readonly Template $template,
|
|
private readonly Console $console,
|
|
private readonly Config $config,
|
|
) {
|
|
$dbi = DatabaseInterface::getInstance();
|
|
$this->menuEnabled = $dbi->isConnected();
|
|
$relation = new Relation($dbi);
|
|
$this->menu = new Menu($dbi, $this->template, $this->config, $relation, Current::$database, Current::$table);
|
|
$this->scripts = new Scripts($this->template);
|
|
$this->addDefaultScripts();
|
|
|
|
$this->userPreferences = new UserPreferences($dbi, $relation, $this->template);
|
|
}
|
|
|
|
/**
|
|
* Loads common scripts
|
|
*/
|
|
private function addDefaultScripts(): void
|
|
{
|
|
$this->scripts->addFile('runtime.js');
|
|
$this->scripts->addFile('vendor/jquery/jquery.min.js');
|
|
$this->scripts->addFile('vendor/jquery/jquery-migrate.min.js');
|
|
$this->scripts->addFile('vendor/sprintf.js');
|
|
$this->scripts->addFile('vendor/jquery/jquery-ui.min.js');
|
|
$this->scripts->addFile('vendor/bootstrap/bootstrap.js');
|
|
$this->scripts->addFile('vendor/js.cookie.min.js');
|
|
$this->scripts->addFile('vendor/jquery/jquery.validate.min.js');
|
|
$this->scripts->addFile('vendor/jquery/jquery-ui-timepicker-addon.js');
|
|
$this->scripts->addFile('index.php', ['route' => '/messages', 'l' => Current::$lang]);
|
|
$this->scripts->addFile('shared.js');
|
|
$this->scripts->addFile('menu_resizer.js');
|
|
$this->scripts->addFile('main.js');
|
|
|
|
$this->scripts->addCode($this->getJsParamsCode());
|
|
}
|
|
|
|
/**
|
|
* Returns, as an array, a list of parameters
|
|
* used on the client side
|
|
*
|
|
* @return mixed[]
|
|
*/
|
|
public function getJsParams(): array
|
|
{
|
|
$pftext = $_SESSION['tmpval']['pftext'] ?? '';
|
|
|
|
$params = [
|
|
// Do not add any separator, JS code will decide
|
|
'common_query' => Url::getCommonRaw([], ''),
|
|
'opendb_url' => Url::getFromRoute($this->config->settings['DefaultTabDatabase']),
|
|
'lang' => Current::$lang,
|
|
'server' => Current::$server,
|
|
'table' => Current::$table,
|
|
'db' => Current::$database,
|
|
'token' => $_SESSION[' PMA_token '],
|
|
'text_dir' => LanguageManager::$textDirection->value,
|
|
'LimitChars' => $this->config->settings['LimitChars'],
|
|
'pftext' => $pftext,
|
|
'confirm' => $this->config->settings['Confirm'],
|
|
'LoginCookieValidity' => $this->config->settings['LoginCookieValidity'],
|
|
'session_gc_maxlifetime' => (int) ini_get('session.gc_maxlifetime'),
|
|
'logged_in' => DatabaseInterface::getInstance()->isConnected(),
|
|
'is_https' => $this->config->isHttps(),
|
|
'rootPath' => $this->config->getRootPath(),
|
|
'arg_separator' => Url::getArgSeparator(),
|
|
'version' => Version::VERSION,
|
|
];
|
|
if ($this->config->hasSelectedServer()) {
|
|
$params['auth_type'] = $this->config->selectedServer['auth_type'];
|
|
if (isset($this->config->selectedServer['user'])) {
|
|
$params['user'] = $this->config->selectedServer['user'];
|
|
}
|
|
}
|
|
|
|
return $params;
|
|
}
|
|
|
|
/**
|
|
* Returns, as a string, a list of parameters
|
|
* used on the client side
|
|
*/
|
|
public function getJsParamsCode(): string
|
|
{
|
|
$params = $this->getJsParams();
|
|
|
|
return 'window.Navigation.update(window.CommonParams.setAll(' . json_encode($params, JSON_HEX_TAG) . '));';
|
|
}
|
|
|
|
/**
|
|
* Returns the Scripts object
|
|
*
|
|
* @return Scripts object
|
|
*/
|
|
public function getScripts(): Scripts
|
|
{
|
|
return $this->scripts;
|
|
}
|
|
|
|
/**
|
|
* Returns the Menu object
|
|
*
|
|
* @return Menu object
|
|
*/
|
|
public function getMenu(): Menu
|
|
{
|
|
return $this->menu;
|
|
}
|
|
|
|
/**
|
|
* Setter for the ID attribute in the BODY tag
|
|
*
|
|
* @param string $id Value for the ID attribute
|
|
*/
|
|
public function setBodyId(string $id): void
|
|
{
|
|
$this->bodyId = htmlspecialchars($id);
|
|
}
|
|
|
|
/**
|
|
* Setter for the title of the page
|
|
*
|
|
* @param string $title New title
|
|
*/
|
|
public function setTitle(string $title): void
|
|
{
|
|
$this->title = htmlspecialchars($title);
|
|
}
|
|
|
|
/**
|
|
* Disables the display of the top menu
|
|
*/
|
|
public function disableMenuAndConsole(): void
|
|
{
|
|
$this->menuEnabled = false;
|
|
$this->console->disable();
|
|
}
|
|
|
|
/**
|
|
* Disables the display of the top menu
|
|
*/
|
|
public function disableWarnings(): void
|
|
{
|
|
$this->warningsEnabled = false;
|
|
}
|
|
|
|
/** @return mixed[] */
|
|
public function getDisplay(): array
|
|
{
|
|
/** @var ThemeManager $themeManager */
|
|
$themeManager = ContainerBuilder::getContainer()->get(ThemeManager::class);
|
|
$theme = $themeManager->theme;
|
|
|
|
// The user preferences have been merged at this point
|
|
// so we can conditionally add CodeMirror, other scripts and settings
|
|
if ($this->config->settings['CodemirrorEnable']) {
|
|
$this->scripts->addFile('vendor/codemirror/lib/codemirror.js');
|
|
$this->scripts->addFile('vendor/codemirror/mode/sql/sql.js');
|
|
$this->scripts->addFile('vendor/codemirror/addon/runmode/runmode.js');
|
|
$this->scripts->addFile('vendor/codemirror/addon/hint/show-hint.js');
|
|
$this->scripts->addFile('vendor/codemirror/addon/hint/sql-hint.js');
|
|
if ($this->config->settings['LintEnable']) {
|
|
$this->scripts->addFile('vendor/codemirror/addon/lint/lint.js');
|
|
$this->scripts->addFile('codemirror/addon/lint/sql-lint.js');
|
|
}
|
|
}
|
|
|
|
if ($this->config->config->SendErrorReports !== 'never') {
|
|
$this->scripts->addFile('vendor/tracekit.js');
|
|
$this->scripts->addFile('error_report.js');
|
|
}
|
|
|
|
if ($this->config->settings['enable_drag_drop_import'] === true) {
|
|
$this->scripts->addFile('drag_drop_import.js');
|
|
}
|
|
|
|
if (! $this->config->config->DisableShortcutKeys) {
|
|
$this->scripts->addFile('shortcuts_handler.js');
|
|
}
|
|
|
|
$this->scripts->addCode($this->getVariablesForJavaScript());
|
|
|
|
$this->scripts->addCode(
|
|
'ConsoleEnterExecutes=' . ($this->config->settings['ConsoleEnterExecutes'] ? 'true' : 'false'),
|
|
);
|
|
$this->scripts->addFiles($this->console->getScripts());
|
|
|
|
$dbi = DatabaseInterface::getInstance();
|
|
if ($this->menuEnabled && Current::$server > 0) {
|
|
$navigation = (new Navigation($this->template, new Relation($dbi), $dbi, $this->config))->getDisplay();
|
|
}
|
|
|
|
$customHeader = self::renderHeader();
|
|
|
|
// offer to load user preferences from localStorage
|
|
if (
|
|
$this->config->userPreferences === 'session'
|
|
&& ! isset($_SESSION['userprefs_autoload'])
|
|
) {
|
|
$loadUserPreferences = $this->userPreferences->autoloadGetHeader();
|
|
}
|
|
|
|
if ($this->menuEnabled && Current::$server > 0) {
|
|
$menu = $this->menu->getDisplay();
|
|
}
|
|
|
|
$console = $this->console->getDisplay();
|
|
$messages = $this->getMessage();
|
|
$isLoggedIn = $dbi->isConnected();
|
|
|
|
$this->scripts->addFile('datetimepicker.js');
|
|
$this->scripts->addFile('validator-messages.js');
|
|
|
|
return [
|
|
'lang' => Current::$lang,
|
|
'allow_third_party_framing' => $this->config->settings['AllowThirdPartyFraming'],
|
|
'theme_path' => $theme->getPath(),
|
|
'server' => Current::$server,
|
|
'title' => $this->getPageTitle(),
|
|
'scripts' => $this->scripts->getDisplay(),
|
|
'body_id' => $this->bodyId,
|
|
'navigation' => $navigation ?? '',
|
|
'custom_header' => $customHeader,
|
|
'load_user_preferences' => $loadUserPreferences ?? '',
|
|
'show_hint' => $this->config->settings['ShowHint'],
|
|
'is_warnings_enabled' => $this->warningsEnabled,
|
|
'is_menu_enabled' => $this->menuEnabled,
|
|
'is_logged_in' => $isLoggedIn,
|
|
'menu' => $menu ?? '',
|
|
'console' => $console,
|
|
'messages' => $messages,
|
|
'theme_color_mode' => $theme->getColorMode(),
|
|
'theme_color_modes' => $theme->getColorModes(),
|
|
'theme_id' => $theme->getId(),
|
|
'current_user' => $dbi->getCurrentUserAndHost(),
|
|
'is_mariadb' => $dbi->isMariaDB(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Returns the message to be displayed at the top of
|
|
* the page, including the executed SQL query, if any.
|
|
*/
|
|
public function getMessage(): string
|
|
{
|
|
$retval = '';
|
|
$message = '';
|
|
if (Current::$message !== null) {
|
|
$message = Current::$message;
|
|
Current::$message = null;
|
|
} elseif (! empty($_REQUEST['message'])) {
|
|
$message = $_REQUEST['message'];
|
|
}
|
|
|
|
if ($message !== '') {
|
|
$retval .= Generator::getMessage($message);
|
|
}
|
|
|
|
return $retval;
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
public function getHttpHeaders(): array
|
|
{
|
|
$headers = [];
|
|
|
|
/* Prevent against ClickJacking by disabling framing */
|
|
if (strtolower((string) $this->config->settings['AllowThirdPartyFraming']) === 'sameorigin') {
|
|
$headers['X-Frame-Options'] = 'SAMEORIGIN';
|
|
} elseif ($this->config->settings['AllowThirdPartyFraming'] !== true) {
|
|
$headers['X-Frame-Options'] = 'DENY';
|
|
}
|
|
|
|
$headers['Referrer-Policy'] = 'same-origin';
|
|
|
|
$headers = array_merge($headers, $this->getCspHeaders());
|
|
|
|
/**
|
|
* Re-enable possible disabled XSS filters.
|
|
*
|
|
* @see https://developer.mozilla.org/docs/Web/HTTP/Headers/X-XSS-Protection
|
|
*/
|
|
$headers['X-XSS-Protection'] = '1; mode=block';
|
|
|
|
/**
|
|
* "nosniff", prevents Internet Explorer and Google Chrome from MIME-sniffing
|
|
* a response away from the declared content-type.
|
|
*
|
|
* @see https://developer.mozilla.org/docs/Web/HTTP/Headers/X-Content-Type-Options
|
|
*/
|
|
$headers['X-Content-Type-Options'] = 'nosniff';
|
|
|
|
/**
|
|
* Adobe cross-domain-policies.
|
|
*
|
|
* @see https://www.sentrium.co.uk/labs/application-security-101-http-headers
|
|
*/
|
|
$headers['X-Permitted-Cross-Domain-Policies'] = 'none';
|
|
|
|
/**
|
|
* Robots meta tag.
|
|
*
|
|
* @see https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag
|
|
*/
|
|
$headers['X-Robots-Tag'] = 'noindex, nofollow';
|
|
|
|
/**
|
|
* The HTTP Permissions-Policy header provides a mechanism to allow and deny
|
|
* the use of browser features in a document
|
|
* or within any <iframe> elements in the document.
|
|
*
|
|
* @see https://developer.mozilla.org/docs/Web/HTTP/Headers/Permissions-Policy
|
|
*/
|
|
$headers['Permissions-Policy'] = 'fullscreen=(self), oversized-images=(self), interest-cohort=()';
|
|
|
|
$headers = array_merge($headers, Core::getNoCacheHeaders());
|
|
|
|
/**
|
|
* A different Content-Type is set in {@see \PhpMyAdmin\Controllers\Transformation\WrapperController}.
|
|
*/
|
|
if (! $this->isTransformationWrapper) {
|
|
// Define the charset to be used
|
|
$headers['Content-Type'] = 'text/html; charset=utf-8';
|
|
}
|
|
|
|
return $headers;
|
|
}
|
|
|
|
/**
|
|
* If the page is missing the title, this function
|
|
* will set it to something reasonable
|
|
*/
|
|
public function getPageTitle(): string
|
|
{
|
|
if ($this->title === '') {
|
|
if (Current::$server > 0) {
|
|
if (Current::$table !== '') {
|
|
$tempTitle = $this->config->settings['TitleTable'];
|
|
} elseif (Current::$database !== '') {
|
|
$tempTitle = $this->config->settings['TitleDatabase'];
|
|
} elseif ($this->config->selectedServer['host'] !== '') {
|
|
$tempTitle = $this->config->settings['TitleServer'];
|
|
} else {
|
|
$tempTitle = $this->config->settings['TitleDefault'];
|
|
}
|
|
|
|
$this->title = htmlspecialchars(
|
|
Util::expandUserString($tempTitle),
|
|
);
|
|
} else {
|
|
$this->title = 'phpMyAdmin';
|
|
}
|
|
}
|
|
|
|
return $this->title;
|
|
}
|
|
|
|
/**
|
|
* Get all the CSP allow policy headers
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
private function getCspHeaders(): array
|
|
{
|
|
$mapTileUrl = ' tile.openstreetmap.org';
|
|
$captchaUrl = '';
|
|
$cspAllow = $this->config->settings['CSPAllow'];
|
|
|
|
if (
|
|
! empty($this->config->settings['CaptchaLoginPrivateKey'])
|
|
&& ! empty($this->config->settings['CaptchaLoginPublicKey'])
|
|
&& ! empty($this->config->settings['CaptchaApi'])
|
|
&& ! empty($this->config->settings['CaptchaRequestParam'])
|
|
&& ! empty($this->config->settings['CaptchaResponseParam'])
|
|
) {
|
|
$captchaUrl = ' ' . $this->config->settings['CaptchaCsp'] . ' ';
|
|
}
|
|
|
|
$headers = [];
|
|
|
|
$headers['Content-Security-Policy'] = sprintf(
|
|
'default-src \'self\' %s%s;script-src \'self\' \'unsafe-inline\' \'unsafe-eval\' %s%s;'
|
|
. 'style-src \'self\' \'unsafe-inline\' %s%s;img-src \'self\' data: %s%s%s;object-src \'none\';',
|
|
$captchaUrl,
|
|
$cspAllow,
|
|
$captchaUrl,
|
|
$cspAllow,
|
|
$captchaUrl,
|
|
$cspAllow,
|
|
$cspAllow,
|
|
$mapTileUrl,
|
|
$captchaUrl,
|
|
);
|
|
|
|
$headers['X-Content-Security-Policy'] = sprintf(
|
|
'default-src \'self\' %s%s;options inline-script eval-script;'
|
|
. 'referrer no-referrer;img-src \'self\' data: %s%s%s;object-src \'none\';',
|
|
$captchaUrl,
|
|
$cspAllow,
|
|
$cspAllow,
|
|
$mapTileUrl,
|
|
$captchaUrl,
|
|
);
|
|
|
|
$headers['X-WebKit-CSP'] = sprintf(
|
|
'default-src \'self\' %s%s;script-src \'self\' %s%s \'unsafe-inline\' \'unsafe-eval\';'
|
|
. 'referrer no-referrer;style-src \'self\' \'unsafe-inline\' %s;'
|
|
. 'img-src \'self\' data: %s%s%s;object-src \'none\';',
|
|
$captchaUrl,
|
|
$cspAllow,
|
|
$captchaUrl,
|
|
$cspAllow,
|
|
$captchaUrl,
|
|
$cspAllow,
|
|
$mapTileUrl,
|
|
$captchaUrl,
|
|
);
|
|
|
|
return $headers;
|
|
}
|
|
|
|
private function getVariablesForJavaScript(): string
|
|
{
|
|
$maxInputVars = ini_get('max_input_vars');
|
|
$maxInputVarsValue = $maxInputVars === false || $maxInputVars === '' ? 'false' : (int) $maxInputVars;
|
|
|
|
return $this->template->render('javascript/variables', [
|
|
'first_day_of_calendar' => $this->config->settings['FirstDayOfCalendar'] ?? 0,
|
|
'max_input_vars' => $maxInputVarsValue,
|
|
]);
|
|
}
|
|
|
|
public function setIsTransformationWrapper(bool $isTransformationWrapper): void
|
|
{
|
|
$this->isTransformationWrapper = $isTransformationWrapper;
|
|
}
|
|
|
|
public function getConsole(): Console
|
|
{
|
|
return $this->console;
|
|
}
|
|
|
|
/**
|
|
* Renders user configured footer
|
|
*/
|
|
public static function renderHeader(): string
|
|
{
|
|
return Generator::renderCustom(CUSTOM_HEADER_FILE, 'pma_header');
|
|
}
|
|
}
|