diff --git a/lib/activity.php b/lib/activity.php index 5d6230c6df..27f09ab4d4 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -83,6 +83,7 @@ class Activity const CREATOR = 'creator'; const CONTENTNS = 'http://purl.org/rss/1.0/modules/content/'; + const ENCODED = 'encoded'; public $actor; // an ActivityObject public $verb; // a string (the URL) @@ -268,14 +269,21 @@ class Activity $this->title = ActivityUtils::childContent($item, ActivityObject::TITLE, self::RSS); - $contentEl = ActivityUtils::child($item, ActivityUtils::CONTENT, self::CONTENTNS); + $contentEl = ActivityUtils::child($item, self::ENCODED, self::CONTENTNS); if (!empty($contentEl)) { - $this->content = htmlspecialchars_decode($contentEl->textContent, ENT_QUOTES); + // XML node's text content is HTML; no further processing needed. + $this->content = $contentEl->textContent; } else { $descriptionEl = ActivityUtils::child($item, self::DESCRIPTION, self::RSS); if (!empty($descriptionEl)) { - $this->content = htmlspecialchars_decode($descriptionEl->textContent, ENT_QUOTES); + // Per spec, must be plaintext. + // In practice, often there's HTML... but these days good + // feeds are using which is explicitly + // real HTML. + // We'll treat this following spec, and do HTML escaping + // to convert from plaintext to HTML. + $this->content = htmlspecialchars($descriptionEl->textContent); } } diff --git a/lib/activityutils.php b/lib/activityutils.php index a7e99fb11e..401fd7fc28 100644 --- a/lib/activityutils.php +++ b/lib/activityutils.php @@ -213,11 +213,19 @@ class ActivityUtils // slavishly following http://atompub.org/rfc4287.html#rfc.section.4.1.3.3 if (empty($type) || $type == 'text') { - return $el->textContent; + // We have plaintext saved as the XML text content. + // Since we want HTML, we need to escape any special chars. + return htmlspecialchars($el->textContent); } else if ($type == 'html') { + // We have HTML saved as the XML text content. + // No additional processing required once we've got it. $text = $el->textContent; - return htmlspecialchars_decode($text, ENT_QUOTES); + return $text; } else if ($type == 'xhtml') { + // Per spec, the contains a single + // HTML
with XHTML namespace on it as a child node. + // We need to pull all of that
's child nodes and + // serialize them back to an (X)HTML source fragment. $divEl = ActivityUtils::child($el, 'div', 'http://www.w3.org/1999/xhtml'); if (empty($divEl)) { return null; diff --git a/tests/ActivityParseTests.php b/tests/ActivityParseTests.php index 4563da9146..378478d741 100644 --- a/tests/ActivityParseTests.php +++ b/tests/ActivityParseTests.php @@ -32,6 +32,18 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase $this->assertEquals('tag:versioncentral.example.org,2009:/change/1643245', $act->objects[0]->id); } + public function testExample2() + { + global $_example2; + $dom = DOMDocument::loadXML($_example2); + $act = new Activity($dom->documentElement); + + $this->assertFalse(empty($act)); + // Did we handle correctly with a typical payload? + $this->assertEquals("

Geraldine posted a Photo on PhotoPanic

\n " . + "", trim($act->content)); + } + public function testExample3() { global $_example3; @@ -305,6 +317,71 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase } + public function testAtomContent() + { + $tests = array(array("Some regular plain text.", + "Some regular plain text."), + array("<b>this is not HTML</b>", + "<b>this is not HTML</b>"), + array("Some regular plain HTML.", + "Some regular plain HTML."), + array("<b>this is too HTML</b>", + "this is too HTML"), + array("&lt;b&gt;but this is not HTML!&lt;/b&gt;", + "<b>but this is not HTML!</b>"), + array("
Some regular plain XHTML.
", + "Some regular plain XHTML."), + array("
This is some XHTML!
", + "This is some XHTML!"), + array("
<b>This is not some XHTML!</b>
", + "<b>This is not some XHTML!</b>"), + array("
&lt;b&gt;This is not some XHTML either!&lt;/b&gt;
", + "&lt;b&gt;This is not some XHTML either!&lt;/b&gt;")); + foreach ($tests as $data) { + list($source, $output) = $data; + $xml = "" . + "http://example.com/fakeid" . + "Test" . + "Atom content tests" . + $source . + ""; + $dom = DOMDocument::loadXML($xml); + $act = new Activity($dom->documentElement); + + $this->assertFalse(empty($act)); + $this->assertEquals($output, trim($act->content)); + } + } + + public function testRssContent() + { + $tests = array(array("Some regular plain HTML.", + "Some regular plain HTML."), + array("Some <b>exciting bold HTML</b>", + "Some exciting bold HTML"), + array("Some &lt;b&gt;escaped non-HTML.&lt;/b&gt;", + "Some <b>escaped non-HTML.</b>"), + array("Some plain text.", + "Some plain text."), + array("Some <b>non-HTML text</b>", + "Some <b>non-HTML text</b>"), + array("Some &lt;b&gt;double-escaped text&lt;/b&gt;", + "Some &lt;b&gt;double-escaped text&lt;/b&gt;")); + foreach ($tests as $data) { + list($source, $output) = $data; + $xml = "" . + "http://example.com/fakeid" . + "RSS content tests" . + $source . + ""; + $dom = DOMDocument::loadXML($xml); + $act = new Activity($dom->documentElement); + + $this->assertFalse(empty($act)); + $this->assertEquals($output, trim($act->content)); + } + } + } $_example1 = <<