summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrion Vibber <brion@pobox.com>2010-09-02 14:58:11 -0700
committerBrion Vibber <brion@pobox.com>2010-09-02 14:58:11 -0700
commite365e709c5bab7d593ee1cde26c8bcfdddcc6780 (patch)
treeac221d247a1bc67b660f06df546bc4bbcc8653b5
parent925381707b921315ba76418ed0d1dd50f9548e80 (diff)
parentcbcb9b0080d262a042adedaf632300e86a57e980 (diff)
Merge branch 'master' into testing
-rw-r--r--lib/action.php14
-rw-r--r--lib/theme.php62
-rw-r--r--lib/themeuploader.php19
-rw-r--r--plugins/MobileProfile/MobileProfilePlugin.php2
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');