diff --git a/lib/atompubclient.php b/lib/atompubclient.php new file mode 100644 index 0000000000..a693579eab --- /dev/null +++ b/lib/atompubclient.php @@ -0,0 +1,251 @@ +. + * + * @category Cache + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Client class for AtomPub + * + * @category General + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class AtomPubClient +{ + public $url; + private $user, $pass; + + /** + * + * @param string $url collection feed URL + * @param string $user auth username + * @param string $pass auth password + */ + function __construct($url, $user, $pass) + { + $this->url = $url; + $this->user = $user; + $this->pass = $pass; + } + + /** + * Set up an HTTPClient with auth for our resource. + * + * @param string $method + * @return HTTPClient + */ + private function httpClient($method='GET') + { + $client = new HTTPClient($this->url); + $client->setMethod($method); + $client->setAuth($this->user, $this->pass); + return $client; + } + + function get() + { + $client = $this->httpClient('GET'); + $response = $client->send(); + if ($response->isOk()) { + return $response->getBody(); + } else { + throw new Exception("Bogus return code: " . $response->getStatus() . ': ' . $response->getBody()); + } + } + + /** + * Create a new resource by POSTing it to the collection. + * If successful, will return the URL representing the + * canonical location of the new resource. Neat! + * + * @param string $data + * @param string $type defaults to Atom entry + * @return string URL to the created resource + * + * @throws exceptions on failure + */ + function post($data, $type='application/atom+xml;type=entry') + { + $client = $this->httpClient('POST'); + $client->setHeader('Content-Type', $type); + // optional Slug header not used in this case + $client->setBody($data); + $response = $client->send(); + + if ($response->getStatus() != '201') { + throw new Exception("Expected HTTP 201 on POST, got " . $response->getStatus() . ': ' . $response->getBody()); + } + $loc = $response->getHeader('Location'); + $contentLoc = $response->getHeader('Content-Location'); + + if (empty($loc)) { + throw new Exception("AtomPub POST response missing Location header."); + } + if (!empty($contentLoc)) { + if ($loc != $contentLoc) { + throw new Exception("AtomPub POST response Location and Content-Location headers do not match."); + } + + // If Content-Location and Location match, that means the response + // body is safe to interpret as the resource itself. + if ($type == 'application/atom+xml;type=entry') { + self::validateAtomEntry($response->getBody()); + } + } + + return $loc; + } + + /** + * Note that StatusNet currently doesn't allow PUT editing on notices. + * + * @param string $data + * @param string $type defaults to Atom entry + * @return true on success + * + * @throws exceptions on failure + */ + function put($data, $type='application/atom+xml;type=entry') + { + $client = $this->httpClient('PUT'); + $client->setHeader('Content-Type', $type); + $client->setBody($data); + $response = $client->send(); + + if ($response->getStatus() != '200' && $response->getStatus() != '204') { + throw new Exception("Expected HTTP 200 or 204 on PUT, got " . $response->getStatus() . ': ' . $response->getBody()); + } + + return true; + } + + /** + * Delete the resource. + * + * @return true on success + * + * @throws exceptions on failure + */ + function delete() + { + $client = $this->httpClient('DELETE'); + $client->setBody($data); + $response = $client->send(); + + if ($response->getStatus() != '200' && $response->getStatus() != '204') { + throw new Exception("Expected HTTP 200 or 204 on DELETE, got " . $response->getStatus() . ': ' . $response->getBody()); + } + + return true; + } + + /** + * Ensure that the given string is a parseable Atom entry. + * + * @param string $str + * @return boolean + * @throws Exception on invalid input + */ + static function validateAtomEntry($str) + { + if (empty($str)) { + throw new Exception('Bad Atom entry: empty'); + } + $dom = new DOMDocument; + if (!$dom->loadXML($str)) { + throw new Exception('Bad Atom entry: XML is not well formed.'); + } + + $activity = new Activity($dom->documentRoot); + return true; + } + + static function entryEditURL($str) { + $dom = new DOMDocument; + $dom->loadXML($str); + $path = new DOMXPath($dom); + $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); + + $links = $path->query('/atom:entry/atom:link[@rel="edit"]', $dom->documentRoot); + if ($links && $links->length) { + if ($links->length > 1) { + throw new Exception('Bad Atom entry; has multiple rel=edit links.'); + } + $link = $links->item(0); + $url = $link->getAttribute('href'); + return $url; + } else { + throw new Exception('Atom entry lists no rel=edit link.'); + } + } + + static function entryId($str) { + $dom = new DOMDocument; + $dom->loadXML($str); + $path = new DOMXPath($dom); + $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); + + $links = $path->query('/atom:entry/atom:id', $dom->documentRoot); + if ($links && $links->length) { + if ($links->length > 1) { + throw new Exception('Bad Atom entry; has multiple id entries.'); + } + $link = $links->item(0); + $url = $link->textContent; + return $url; + } else { + throw new Exception('Atom entry lists no id.'); + } + } + + static function getEntryInFeed($str, $id) + { + $dom = new DOMDocument; + $dom->loadXML($str); + $path = new DOMXPath($dom); + $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); + + $query = '/atom:feed/atom:entry[atom:id="'.$id.'"]'; + $items = $path->query($query, $dom->documentRoot); + if ($items && $items->length) { + return $items->item(0); + } else { + return null; + } + } +} diff --git a/tests/atompub/atompub_test.php b/tests/atompub/atompub_test.php index e23e4a711b..fb675c778e 100644 --- a/tests/atompub/atompub_test.php +++ b/tests/atompub/atompub_test.php @@ -39,212 +39,6 @@ END_OF_HELP; require_once INSTALLDIR.'/scripts/commandline.inc'; -class AtomPubClient -{ - public $url; - private $user, $pass; - - /** - * - * @param string $url collection feed URL - * @param string $user auth username - * @param string $pass auth password - */ - function __construct($url, $user, $pass) - { - $this->url = $url; - $this->user = $user; - $this->pass = $pass; - } - - /** - * Set up an HTTPClient with auth for our resource. - * - * @param string $method - * @return HTTPClient - */ - private function httpClient($method='GET') - { - $client = new HTTPClient($this->url); - $client->setMethod($method); - $client->setAuth($this->user, $this->pass); - return $client; - } - - function get() - { - $client = $this->httpClient('GET'); - $response = $client->send(); - if ($response->isOk()) { - return $response->getBody(); - } else { - throw new Exception("Bogus return code: " . $response->getStatus() . ': ' . $response->getBody()); - } - } - - /** - * Create a new resource by POSTing it to the collection. - * If successful, will return the URL representing the - * canonical location of the new resource. Neat! - * - * @param string $data - * @param string $type defaults to Atom entry - * @return string URL to the created resource - * - * @throws exceptions on failure - */ - function post($data, $type='application/atom+xml;type=entry') - { - $client = $this->httpClient('POST'); - $client->setHeader('Content-Type', $type); - // optional Slug header not used in this case - $client->setBody($data); - $response = $client->send(); - - if ($response->getStatus() != '201') { - throw new Exception("Expected HTTP 201 on POST, got " . $response->getStatus() . ': ' . $response->getBody()); - } - $loc = $response->getHeader('Location'); - $contentLoc = $response->getHeader('Content-Location'); - - if (empty($loc)) { - throw new Exception("AtomPub POST response missing Location header."); - } - if (!empty($contentLoc)) { - if ($loc != $contentLoc) { - throw new Exception("AtomPub POST response Location and Content-Location headers do not match."); - } - - // If Content-Location and Location match, that means the response - // body is safe to interpret as the resource itself. - if ($type == 'application/atom+xml;type=entry') { - self::validateAtomEntry($response->getBody()); - } - } - - return $loc; - } - - /** - * Note that StatusNet currently doesn't allow PUT editing on notices. - * - * @param string $data - * @param string $type defaults to Atom entry - * @return true on success - * - * @throws exceptions on failure - */ - function put($data, $type='application/atom+xml;type=entry') - { - $client = $this->httpClient('PUT'); - $client->setHeader('Content-Type', $type); - $client->setBody($data); - $response = $client->send(); - - if ($response->getStatus() != '200' && $response->getStatus() != '204') { - throw new Exception("Expected HTTP 200 or 204 on PUT, got " . $response->getStatus() . ': ' . $response->getBody()); - } - - return true; - } - - /** - * Delete the resource. - * - * @return true on success - * - * @throws exceptions on failure - */ - function delete() - { - $client = $this->httpClient('DELETE'); - $client->setBody($data); - $response = $client->send(); - - if ($response->getStatus() != '200' && $response->getStatus() != '204') { - throw new Exception("Expected HTTP 200 or 204 on DELETE, got " . $response->getStatus() . ': ' . $response->getBody()); - } - - return true; - } - - /** - * Ensure that the given string is a parseable Atom entry. - * - * @param string $str - * @return boolean - * @throws Exception on invalid input - */ - static function validateAtomEntry($str) - { - if (empty($str)) { - throw new Exception('Bad Atom entry: empty'); - } - $dom = new DOMDocument; - if (!$dom->loadXML($str)) { - throw new Exception('Bad Atom entry: XML is not well formed.'); - } - - $activity = new Activity($dom->documentRoot); - return true; - } - - static function entryEditURL($str) { - $dom = new DOMDocument; - $dom->loadXML($str); - $path = new DOMXPath($dom); - $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); - - $links = $path->query('/atom:entry/atom:link[@rel="edit"]', $dom->documentRoot); - if ($links && $links->length) { - if ($links->length > 1) { - throw new Exception('Bad Atom entry; has multiple rel=edit links.'); - } - $link = $links->item(0); - $url = $link->getAttribute('href'); - return $url; - } else { - throw new Exception('Atom entry lists no rel=edit link.'); - } - } - - static function entryId($str) { - $dom = new DOMDocument; - $dom->loadXML($str); - $path = new DOMXPath($dom); - $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); - - $links = $path->query('/atom:entry/atom:id', $dom->documentRoot); - if ($links && $links->length) { - if ($links->length > 1) { - throw new Exception('Bad Atom entry; has multiple id entries.'); - } - $link = $links->item(0); - $url = $link->textContent; - return $url; - } else { - throw new Exception('Atom entry lists no id.'); - } - } - - static function getEntryInFeed($str, $id) - { - $dom = new DOMDocument; - $dom->loadXML($str); - $path = new DOMXPath($dom); - $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); - - $query = '/atom:feed/atom:entry[atom:id="'.$id.'"]'; - $items = $path->query($query, $dom->documentRoot); - if ($items && $items->length) { - return $items->item(0); - } else { - return null; - } - } -} - - $user = get_option_value('n', 'nickname'); $pass = get_option_value('p', 'password');