diff options
-rw-r--r-- | lib/action.php | 14 | ||||
-rw-r--r-- | lib/theme.php | 62 | ||||
-rw-r--r-- | lib/themeuploader.php | 19 | ||||
-rw-r--r-- | plugins/MobileProfile/MobileProfilePlugin.php | 2 |
4 files changed, 93 insertions, 4 deletions
diff --git a/lib/action.php b/lib/action.php index 1d85f19e9..c86dd2d86 100644 --- a/lib/action.php +++ b/lib/action.php @@ -203,7 +203,7 @@ class Action extends HTMLOutputter // lawsuit if (Event::handle('StartShowStatusNetStyles', array($this)) && Event::handle('StartShowLaconicaStyles', array($this))) { - $this->cssLink('css/display.css',null, 'screen, projection, tv, print'); + $this->primaryCssLink(null, 'screen, projection, tv, print'); Event::handle('EndShowStatusNetStyles', array($this)); Event::handle('EndShowLaconicaStyles', array($this)); } @@ -251,6 +251,18 @@ class Action extends HTMLOutputter // lawsuit } } + function primaryCssLink($mainTheme=null, $media=null) + { + // If the currently-selected theme has dependencies on other themes, + // we'll need to load their display.css files as well in order. + $theme = new Theme($mainTheme); + $baseThemes = $theme->getDeps(); + foreach ($baseThemes as $baseTheme) { + $this->cssLink('css/display.css', $baseTheme, $media); + } + $this->cssLink('css/display.css', $mainTheme, $media); + } + /** * Show javascript headers * diff --git a/lib/theme.php b/lib/theme.php index a9d0cbc84..992fce870 100644 --- a/lib/theme.php +++ b/lib/theme.php @@ -54,6 +54,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { class Theme { + var $name = null; var $dir = null; var $path = null; @@ -70,6 +71,10 @@ class Theme if (empty($name)) { $name = common_config('site', 'theme'); } + if (!self::validName($name)) { + throw new ServerException("Invalid theme name."); + } + $this->name = $name; // Check to see if it's in the local dir @@ -178,6 +183,58 @@ class Theme } /** + * Fetch a list of other themes whose CSS needs to be pulled in before + * this theme's, based on following the theme.ini 'include' settings. + * (May be empty if this theme has no include dependencies.) + * + * @return array of strings with theme names + */ + function getDeps() + { + $chain = $this->doGetDeps(array($this->name)); + array_pop($chain); // Drop us back off + return $chain; + } + + protected function doGetDeps($chain) + { + $data = $this->getMetadata(); + if (!empty($data['include'])) { + $include = $data['include']; + + // Protect against cycles! + if (!in_array($include, $chain)) { + try { + $theme = new Theme($include); + array_unshift($chain, $include); + return $theme->doGetDeps($chain); + } catch (Exception $e) { + common_log(LOG_ERR, + "Exception while fetching theme dependencies " . + "for $this->name: " . $e->getMessage()); + } + } + } + return $chain; + } + + /** + * Pull data from the theme's theme.ini file. + * @fixme calling getFile will fall back to default theme, this may be unsafe. + * + * @return associative array of strings + */ + function getMetadata() + { + $iniFile = $this->getFile('theme.ini'); + if (file_exists($iniFile)) { + return parse_ini_file($iniFile); + } else { + return array(); + } + } + + /** * Gets the full path of a file in a theme dir based on its relative name * * @param string $relative relative path within the theme directory @@ -285,4 +342,9 @@ class Theme return $instroot; } + + static function validName($name) + { + return preg_match('/^[a-z0-9][a-z0-9_-]*$/i', $name); + } } diff --git a/lib/themeuploader.php b/lib/themeuploader.php index abf0658d3..5a48e884e 100644 --- a/lib/themeuploader.php +++ b/lib/themeuploader.php @@ -192,37 +192,52 @@ class ThemeUploader if (in_array(strtolower($ext), $skip)) { return true; } + if ($filename == '' || substr($filename, 0, 1) == '.') { + // Skip Unix-style hidden files + return true; + } + if ($filename == '__MACOSX') { + // Skip awful metadata files Mac OS X slips in for you. + // Thanks Apple! + return true; + } return false; } protected function validateFile($filename, $ext) { $this->validateFileOrFolder($filename); - $this->validateExtension($ext); + $this->validateExtension($filename, $ext); // @fixme validate content } protected function validateFileOrFolder($name) { if (!preg_match('/^[a-z0-9_\.-]+$/i', $name)) { + common_log(LOG_ERR, "Bad theme filename: $name"); $msg = _("Theme contains invalid file or folder name. " . "Stick with ASCII letters, digits, underscore, and minus sign."); throw new ClientException($msg); } if (preg_match('/\.(php|cgi|asp|aspx|js|vb)\w/i', $name)) { + common_log(LOG_ERR, "Unsafe theme filename: $name"); $msg = _("Theme contains unsafe file extension names; may be unsafe."); throw new ClientException($msg); } return true; } - protected function validateExtension($ext) + protected function validateExtension($base, $ext) { $allowed = array('css', // CSS may need validation 'png', 'gif', 'jpg', 'jpeg', 'svg', // SVG images/fonts may need validation 'ttf', 'eot', 'woff'); if (!in_array(strtolower($ext), $allowed)) { + if ($ext == 'ini' && $base == 'theme') { + // theme.ini exception + return true; + } $msg = sprintf(_("Theme contains file of type '.%s', " . "which is not allowed."), $ext); diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 6076bbde0..72a6a04fb 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -241,7 +241,7 @@ class MobileProfilePlugin extends WAP20Plugin return true; } - $action->cssLink('css/display.css'); + $action->primaryCssLink(); if (file_exists(Theme::file('css/mp-screen.css'))) { $action->cssLink('css/mp-screen.css', null, 'screen'); |