]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/atompubclient.php
Merge remote-tracking branch 'origin/1.0.x' into 1.0.x
[quix0rs-gnu-social.git] / lib / atompubclient.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2011, StatusNet, Inc.
5  *
6  * Client class for AtomPub
7  * 
8  * PHP version 5
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU Affero General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Affero General Public License for more details.
19  *
20  * You should have received a copy of the GNU Affero General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  *
23  * @category  Cache
24  * @package   StatusNet
25  * @author    Evan Prodromou <evan@status.net>
26  * @copyright 2011 StatusNet, Inc.
27  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
28  * @link      http://status.net/
29  */
30
31 if (!defined('STATUSNET')) {
32     // This check helps protect against security problems;
33     // your code file can't be executed directly from the web.
34     exit(1);
35 }
36
37 /**
38  * Client class for AtomPub
39  *
40  * @category  General
41  * @package   StatusNet
42  * @author    Evan Prodromou <evan@status.net>
43  * @copyright 2011 StatusNet, Inc.
44  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
45  * @link      http://status.net/
46  */
47
48 class AtomPubClient
49 {
50     public $url;
51     private $user, $pass;
52
53     /**
54      *
55      * @param string $url collection feed URL
56      * @param string $user auth username
57      * @param string $pass auth password
58      */
59     function __construct($url, $user, $pass)
60     {
61         $this->url = $url;
62         $this->user = $user;
63         $this->pass = $pass;
64     }
65
66     /**
67      * Set up an HTTPClient with auth for our resource.
68      *
69      * @param string $method
70      * @return HTTPClient
71      */
72     private function httpClient($method='GET')
73     {
74         $client = new HTTPClient($this->url);
75         $client->setMethod($method);
76         $client->setAuth($this->user, $this->pass);
77         return $client;
78     }
79
80     function get()
81     {
82         $client = $this->httpClient('GET');
83         $response = $client->send();
84         if ($response->isOk()) {
85             return $response->getBody();
86         } else {
87             throw new Exception("Bogus return code: " . $response->getStatus() . ': ' . $response->getBody());
88         }
89     }
90
91     /**
92      * Create a new resource by POSTing it to the collection.
93      * If successful, will return the URL representing the
94      * canonical location of the new resource. Neat!
95      *
96      * @param string $data
97      * @param string $type defaults to Atom entry
98      * @return string URL to the created resource
99      *
100      * @throws exceptions on failure
101      */
102     function post($data, $type='application/atom+xml;type=entry')
103     {
104         $client = $this->httpClient('POST');
105         $client->setHeader('Content-Type', $type);
106         // optional Slug header not used in this case
107         $client->setBody($data);
108         $response = $client->send();
109
110         if ($response->getStatus() != '201') {
111             throw new Exception("Expected HTTP 201 on POST, got " . $response->getStatus() . ': ' . $response->getBody());
112         }
113         $loc = $response->getHeader('Location');
114         $contentLoc = $response->getHeader('Content-Location');
115
116         if (empty($loc)) {
117             throw new Exception("AtomPub POST response missing Location header.");
118         }
119         if (!empty($contentLoc)) {
120             if ($loc != $contentLoc) {
121                 throw new Exception("AtomPub POST response Location and Content-Location headers do not match.");
122             }
123
124             // If Content-Location and Location match, that means the response
125             // body is safe to interpret as the resource itself.
126             if ($type == 'application/atom+xml;type=entry') {
127                 self::validateAtomEntry($response->getBody());
128             }
129         }
130
131         return $loc;
132     }
133
134     /**
135      * Note that StatusNet currently doesn't allow PUT editing on notices.
136      *
137      * @param string $data
138      * @param string $type defaults to Atom entry
139      * @return true on success
140      *
141      * @throws exceptions on failure
142      */
143     function put($data, $type='application/atom+xml;type=entry')
144     {
145         $client = $this->httpClient('PUT');
146         $client->setHeader('Content-Type', $type);
147         $client->setBody($data);
148         $response = $client->send();
149
150         if ($response->getStatus() != '200' && $response->getStatus() != '204') {
151             throw new Exception("Expected HTTP 200 or 204 on PUT, got " . $response->getStatus() . ': ' . $response->getBody());
152         }
153
154         return true;
155     }
156
157     /**
158      * Delete the resource.
159      *
160      * @return true on success
161      *
162      * @throws exceptions on failure
163      */
164     function delete()
165     {
166         $client = $this->httpClient('DELETE');
167         $client->setBody($data);
168         $response = $client->send();
169
170         if ($response->getStatus() != '200' && $response->getStatus() != '204') {
171             throw new Exception("Expected HTTP 200 or 204 on DELETE, got " . $response->getStatus() . ': ' . $response->getBody());
172         }
173
174         return true;
175     }
176
177     /**
178      * Ensure that the given string is a parseable Atom entry.
179      *
180      * @param string $str
181      * @return boolean
182      * @throws Exception on invalid input
183      */
184     static function validateAtomEntry($str)
185     {
186         if (empty($str)) {
187             throw new Exception('Bad Atom entry: empty');
188         }
189         $dom = new DOMDocument;
190         if (!$dom->loadXML($str)) {
191             throw new Exception('Bad Atom entry: XML is not well formed.');
192         }
193
194         $activity = new Activity($dom->documentRoot);
195         return true;
196     }
197
198     static function entryEditURL($str) {
199         $dom = new DOMDocument;
200         $dom->loadXML($str);
201         $path = new DOMXPath($dom);
202         $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
203
204         $links = $path->query('/atom:entry/atom:link[@rel="edit"]', $dom->documentRoot);
205         if ($links && $links->length) {
206             if ($links->length > 1) {
207                 throw new Exception('Bad Atom entry; has multiple rel=edit links.');
208             }
209             $link = $links->item(0);
210             $url = $link->getAttribute('href');
211             return $url;
212         } else {
213             throw new Exception('Atom entry lists no rel=edit link.');
214         }
215     }
216
217     static function entryId($str) {
218         $dom = new DOMDocument;
219         $dom->loadXML($str);
220         $path = new DOMXPath($dom);
221         $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
222
223         $links = $path->query('/atom:entry/atom:id', $dom->documentRoot);
224         if ($links && $links->length) {
225             if ($links->length > 1) {
226                 throw new Exception('Bad Atom entry; has multiple id entries.');
227             }
228             $link = $links->item(0);
229             $url = $link->textContent;
230             return $url;
231         } else {
232             throw new Exception('Atom entry lists no id.');
233         }
234     }
235
236     static function getEntryInFeed($str, $id)
237     {
238         $dom = new DOMDocument;
239         $dom->loadXML($str);
240         $path = new DOMXPath($dom);
241         $path->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
242
243         $query = '/atom:feed/atom:entry[atom:id="'.$id.'"]';
244         $items = $path->query($query, $dom->documentRoot);
245         if ($items && $items->length) {
246             return $items->item(0);
247         } else {
248             return null;
249         }
250     }
251 }