From 8e7c279c9eaf1db6367f5bbf8e833698f50eca92 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Feb 2011 14:14:32 -0800 Subject: [PATCH 1/5] Fix issue #3035: search highlighting broke URLs in some imported messages (Twitter) Search highlighting was being done with a regex on raw HTML text, followed by a second regex undoing replacements within double-quoted attribute values. This broke on imported Twitter messages, as the way we generate the markup uses single quotes on the attributes, which didn't get matched by the second regex. I've replaced this do-then-undo cycle by dividing up the import HTML into freetext spans and tags; the freetext gets replaced, while the tags are left untouched. --- actions/noticesearch.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/actions/noticesearch.php b/actions/noticesearch.php index d0673420d6..5814dec8af 100644 --- a/actions/noticesearch.php +++ b/actions/noticesearch.php @@ -193,13 +193,20 @@ class SearchNoticeListItem extends NoticeListItem { $options = implode('|', array_map('preg_quote', array_map('htmlspecialchars', $terms), array_fill(0, sizeof($terms), '/'))); $pattern = "/($options)/i"; - $result = preg_replace($pattern, '\\1', $text); + $result = ''; + + /* Divide up into text (highlight me) and tags (don't touch) */ + $chunks = preg_split('/(<[^>]+>)/', $text, 0, PREG_SPLIT_DELIM_CAPTURE); + foreach ($chunks as $i => $chunk) { + if ($i % 2 == 1) { + // odd: delimiter (tag) + $result .= $chunk; + } else { + // even: freetext between tags + $result .= preg_replace($pattern, '\\1', $chunk); + } + } - /* Remove highlighting from inside links, loop incase multiple highlights in links */ - $pattern = '/(\w+="[^"]*)('.$options.')<\/strong>([^"]*")/iU'; - do { - $result = preg_replace($pattern, '\\1\\2\\3', $result, -1, $count); - } while ($count); return $result; } } From 3fb4b92cd6ed09555c558cab7e33c3df988a7bfe Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Feb 2011 15:01:57 -0800 Subject: [PATCH 2/5] Fix ticket #3001: Twitter bridge was replacing original form of @-mentions with canonical form unexpectedly Now using the original text form of @-mentions and #-tags, as in Twitter's own HTMLification. Canonical forms are still used in generating links, where it's polite to match the canonical form. --- plugins/TwitterBridge/twitterimport.php | 29 +++++++++++++------------ 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/plugins/TwitterBridge/twitterimport.php b/plugins/TwitterBridge/twitterimport.php index 475a99efac..2067196d36 100644 --- a/plugins/TwitterBridge/twitterimport.php +++ b/plugins/TwitterBridge/twitterimport.php @@ -583,15 +583,16 @@ class TwitterImport foreach ($toReplace as $part) { list($type, $object) = $part; + $orig = mb_substr($text, $object->indices[0], $object->indices[1] - $object->indices[0]); switch($type) { case self::URL: - $linkText = $this->makeUrlLink($object); + $linkText = $this->makeUrlLink($object, $orig); break; case self::HASHTAG: - $linkText = $this->makeHashtagLink($object); + $linkText = $this->makeHashtagLink($object, $orig); break; case self::MENTION: - $linkText = $this->makeMentionLink($object); + $linkText = $this->makeMentionLink($object, $orig); break; default: continue; @@ -601,32 +602,32 @@ class TwitterImport return $text; } - function makeUrlLink($object) + function makeUrlLink($object, $orig) { - return "{$object->url}"; + return "{$orig}"; } - function makeHashtagLink($object) + function makeHashtagLink($object, $orig) { - return "#" . self::tagLink($object->text); + return "#" . self::tagLink($object->text, substr($orig, 1)); } - function makeMentionLink($object) + function makeMentionLink($object, $orig) { - return "@".self::atLink($object->screen_name, $object->name); + return "@".self::atLink($object->screen_name, $object->name, substr($orig, 1)); } - static function tagLink($tag) + static function tagLink($tag, $orig) { - return "{$tag}"; + return "{$orig}"; } - static function atLink($screenName, $fullName=null) + static function atLink($screenName, $fullName, $orig) { if (!empty($fullName)) { - return "{$screenName}"; + return "{$orig}"; } else { - return "{$screenName}"; + return "{$orig}"; } } From f3c822cc1520720a4bb63e3c610076571d90b520 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Feb 2011 15:52:12 -0800 Subject: [PATCH 3/5] Ticket #3011: Add attachments/uploads and attachments/file_quota to api/statusnet/config.(xml|json) file_quota is adjusted from the defined value to take into account the maximum upload size limits in PHP, or cropped to 0 if uploads are disabled. This can be used by client apps to determine maximum size for an attachment. --- actions/apistatusnetconfig.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/actions/apistatusnetconfig.php b/actions/apistatusnetconfig.php index 771a95baec..b34c6cc544 100644 --- a/actions/apistatusnetconfig.php +++ b/actions/apistatusnetconfig.php @@ -59,7 +59,8 @@ class ApiStatusnetConfigAction extends ApiAction 'notice' => array('contentlimit'), 'throttle' => array('enabled', 'count', 'timespan'), 'xmpp' => array('enabled', 'server', 'port', 'user'), - 'integration' => array('source') + 'integration' => array('source'), + 'attachments' => array('uploads', 'file_quota') ); /** @@ -96,7 +97,7 @@ class ApiStatusnetConfigAction extends ApiAction foreach ($this->keys as $section => $settings) { $this->elementStart($section); foreach ($settings as $setting) { - $value = common_config($section, $setting); + $value = $this->setting($section, $setting); if (is_array($value)) { $value = implode(',', $value); } else if ($value === false || $value == '0') { @@ -125,7 +126,7 @@ class ApiStatusnetConfigAction extends ApiAction $result[$section] = array(); foreach ($settings as $setting) { $result[$section][$setting] - = common_config($section, $setting); + = $this->setting($section, $setting); } } $this->initDocument('json'); @@ -143,6 +144,20 @@ class ApiStatusnetConfigAction extends ApiAction } } + function setting($section, $key) { + $result = common_config($section, $key); + if ($key == 'file_quota') { + // hack: adjust for the live upload limit + if (common_config($section, 'uploads')) { + $max = ImageFile::maxFileSizeInt(); + } else { + $max = 0; + } + return min($result, $max); + } + return $result; + } + /** * Return true if read only. * From 2a42dac72aa3000aa4b442ee722a13e12dda3319 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Feb 2011 16:10:07 -0800 Subject: [PATCH 4/5] Partial implementation for ticket #2442: MobileProfile plugin should allow manual switching between regular and mobile rendering modes http://status.net/open-source/issues/2442 Notes: * Mapstraction causes JavaScript errors in XHTML mode, breaking our code if we're run later so the link doesn't work to get back to Desktop. * not 100% sure how safe feature detection is here? * Currently will be useless but visible links if no JS available; need to fall back to server-side for limited browsers --- lib/action.php | 7 ++-- plugins/MobileProfile/MobileProfilePlugin.php | 36 ++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/lib/action.php b/lib/action.php index 26ebd20932..173e2c2a58 100644 --- a/lib/action.php +++ b/lib/action.php @@ -854,8 +854,11 @@ class Action extends HTMLOutputter // lawsuit function showFooter() { $this->elementStart('div', array('id' => 'footer')); - $this->showSecondaryNav(); - $this->showLicenses(); + if (Event::handle('StartShowInsideFooter', array($this))) { + $this->showSecondaryNav(); + $this->showLicenses(); + Event::handle('EndShowInsideFooter', array($this)); + } $this->elementEnd('div'); } diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index b9c25ab998..ec08fa1e2c 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -68,6 +68,9 @@ class MobileProfilePlugin extends WAP20Plugin $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'])) { $this->serveMobile = true; + } else if (isset($_COOKIE['MobileOverride'])) { + // Cookie override is controlled by link at bottom. + $this->serveMobile = (bool)$_COOKIE['MobileOverride']; } else { // If they like the WAP 2.0 mimetype, serve them MP // @fixme $type is undefined, making this if case useless and spewing errors. @@ -381,9 +384,40 @@ class MobileProfilePlugin extends WAP20Plugin } } - function onStartShowScripts($action) + function onEndShowScripts($action) { + $action->inlineScript(' + $(function() { + $("#mobile-toggle-disable").click(function() { + $.cookie("MobileOverride", "0", {path: "/"}); + window.location.reload(); + return false; + }); + $("#mobile-toggle-enable").click(function() { + $.cookie("MobileOverride", "1", {path: "/"}); + window.location.reload(); + return false; + }); + });' + ); + } + + function onEndShowInsideFooter($action) + { + if ($this->serveMobile) { + // TRANS: Link to switch site layout from mobile to desktop mode. Appears at very bottom of page. + $linkText = _m('Switch to desktop site layout.'); + $key = 'mobile-toggle-disable'; + } else { + // TRANS: Link to switch site layout from desktop to mobile mode. Appears at very bottom of page. + $linkText = _m('Switch to mobile site layout.'); + $key = 'mobile-toggle-enable'; + } + $action->elementStart('p'); + $action->element('a', array('href' => '#', 'id' => $key), $linkText); + $action->elementEnd('p'); + return true; } function _common_path($relative, $ssl=false) From 8eca1b8daccf722b1ad06bdc128f5742bd6b0a80 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 21 Feb 2011 16:18:45 -0800 Subject: [PATCH 5/5] Fix ticket #3057: apply HTML escaping on special characters in Twitter import Changes the replacement of Twitter "entities" from in-place reverse ordering ('to preserve indices') to a forward-facing append-in-chunks that pulls in both the text and link portions, and escapes them all. This unfortunately means first *de*-escaping the < and > that Twitter helpfully adds for us.... and any literal &blah;s that get written. This seems to match Twitter's web UI, however horrid it is. --- plugins/TwitterBridge/twitterimport.php | 42 ++++++++++++++++++++----- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/plugins/TwitterBridge/twitterimport.php b/plugins/TwitterBridge/twitterimport.php index 2067196d36..5ddf5380d7 100644 --- a/plugins/TwitterBridge/twitterimport.php +++ b/plugins/TwitterBridge/twitterimport.php @@ -554,8 +554,8 @@ class TwitterImport } // Move all the entities into order so we can - // replace them in reverse order and thus - // not mess up their indices + // replace them and escape surrounding plaintext + // in order $toReplace = array(); @@ -577,13 +577,23 @@ class TwitterImport } } - // sort in reverse order by key + // sort in forward order by key - krsort($toReplace); + ksort($toReplace); + + $result = ''; + $cursor = 0; foreach ($toReplace as $part) { list($type, $object) = $part; - $orig = mb_substr($text, $object->indices[0], $object->indices[1] - $object->indices[0]); + $start = $object->indices[0]; + $end = $object->indices[1]; + if ($cursor < $start) { + // Copy in the preceding plaintext + $result .= $this->twitEscape(mb_substr($text, $cursor, $start - $cursor)); + $cursor = $start; + } + $orig = $this->twitEscape(mb_substr($text, $start, $end - $start)); switch($type) { case self::URL: $linkText = $this->makeUrlLink($object, $orig); @@ -595,11 +605,29 @@ class TwitterImport $linkText = $this->makeMentionLink($object, $orig); break; default: + $linkText = $orig; continue; } - $text = mb_substr($text, 0, $object->indices[0]) . $linkText . mb_substr($text, $object->indices[1]); + $result .= $linkText; + $cursor = $end; } - return $text; + $last = $this->twitEscape(mb_substr($text, $cursor)); + $result .= $last; + + return $result; + } + + function twitEscape($str) + { + // Twitter seems to preemptive turn < and > into < and > + // but doesn't for &, so while you may have some magic protection + // against XSS by not bothing to escape manually, you still get + // invalid XHTML. Thanks! + // + // Looks like their web interface pretty much sends anything + // through intact, so.... to do equivalent, decode all entities + // and then re-encode the special ones. + return htmlspecialchars(html_entity_decode($str, ENT_COMPAT, 'UTF-8')); } function makeUrlLink($object, $orig)