1 : <?php
2 : class EpiOAuth
3 : {
4 : public $version = '1.0';
5 :
6 : protected $requestTokenUrl;
7 : protected $accessTokenUrl;
8 : protected $authenticateUrl;
9 : protected $authorizeUrl;
10 : protected $consumerKey;
11 : protected $consumerSecret;
12 : protected $token;
13 : protected $tokenSecret;
14 : protected $signatureMethod;
15 : protected $useSSL = false;
16 : protected $headers = array();
17 : protected $userAgent = 'EpiOAuth (http://github.com/jmathai/twitter-async/tree/)';
18 : protected $connectionTimeout = 5;
19 : protected $requestTimeout = 30;
20 :
21 : public function addHeader($header)
22 : {
23 22 : if(is_array($header) && !empty($header))
24 22 : $this->headers = array_merge($this->headers, $header);
25 0 : elseif(!empty($header))
26 0 : $this->headers[] = $header;
27 22 : }
28 :
29 : public function getAccessToken($params = null)
30 : {
31 0 : $resp = $this->httpRequest('GET', $this->getUrl($this->accessTokenUrl), $params);
32 0 : return new EpiOAuthResponse($resp);
33 : }
34 :
35 : public function getAuthenticateUrl($token = null, $params = null)
36 : {
37 1 : $token = $token ? $token : $this->getRequestToken($params);
38 1 : $addlParams = empty($params) ? '' : '&'.http_build_query($params, '', '&');
39 1 : return $this->getUrl($this->authenticateUrl) . '?oauth_token=' . $token->oauth_token . $addlParams;
40 : }
41 :
42 : public function getAuthorizeUrl($token = null, $params = null)
43 : {
44 2 : $token = $token ? $token : $this->getRequestToken($params);
45 2 : return $this->getUrl($this->authorizeUrl) . '?oauth_token=' . $token->oauth_token;
46 : }
47 :
48 : // DEPRECATED in favor of getAuthorizeUrl()
49 : public function getAuthorizationUrl($token = null)
50 : {
51 1 : return $this->getAuthorizeUrl($token);
52 : }
53 :
54 : public function getRequestToken($params = null)
55 : {
56 4 : $resp = $this->httpRequest('GET', $this->getUrl($this->requestTokenUrl), $params);
57 4 : return new EpiOAuthResponse($resp);
58 : }
59 :
60 : public function getUrl($url)
61 : {
62 27 : if($this->useSSL === true)
63 27 : return preg_replace('/^http:/', 'https:', $url);
64 :
65 25 : return $url;
66 : }
67 :
68 : public function httpRequest($method = null, $url = null, $params = null, $isMultipart = false)
69 : {
70 22 : if(empty($method) || empty($url))
71 22 : return false;
72 :
73 22 : if(empty($params['oauth_signature']))
74 22 : $params = $this->prepareParameters($method, $url, $params);
75 :
76 : switch($method)
77 : {
78 22 : case 'GET':
79 16 : return $this->httpGet($url, $params);
80 : break;
81 9 : case 'POST':
82 9 : return $this->httpPost($url, $params, $isMultipart);
83 : break;
84 : }
85 0 : }
86 :
87 : public function setTimeout($requestTimeout = null, $connectionTimeout = null)
88 : {
89 0 : if($requestTimeout !== null)
90 0 : $this->requestTimeout = floatval($requestTimeout);
91 0 : if($connectionTimeout !== null)
92 0 : $this->connectionTimeout = floatval($connectionTimeout);
93 0 : }
94 :
95 : public function setToken($token = null, $secret = null)
96 : {
97 29 : $this->token = $token;
98 29 : $this->tokenSecret = $secret;
99 29 : }
100 :
101 : public function useSSL($use = false)
102 : {
103 2 : $this->useSSL = (bool)$use;
104 2 : }
105 :
106 : protected function addDefaultHeaders($url, $oauthHeaders)
107 : {
108 22 : $_h = array('Expect:');
109 22 : $urlParts = parse_url($url);
110 22 : $oauth = 'Authorization: OAuth realm="' . $urlParts['scheme'] . '://' . $urlParts['host'] . $urlParts['path'] . '",';
111 22 : foreach($oauthHeaders as $name => $value)
112 : {
113 22 : $oauth .= "{$name}=\"{$value}\",";
114 22 : }
115 22 : $_h[] = substr($oauth, 0, -1);
116 22 : $_h[] = "User-Agent: {$this->userAgent}";
117 22 : $this->addHeader($_h);
118 22 : }
119 :
120 : protected function buildHttpQueryRaw($params)
121 : {
122 27 : $retval = '';
123 27 : foreach((array)$params as $key => $value)
124 27 : $retval .= "{$key}={$value}&";
125 27 : $retval = substr($retval, 0, -1);
126 27 : return $retval;
127 : }
128 :
129 : protected function curlInit($url)
130 : {
131 22 : $ch = curl_init($url);
132 22 : curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
133 22 : curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
134 22 : curl_setopt($ch, CURLOPT_TIMEOUT, $this->requestTimeout);
135 22 : curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout);
136 22 : curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
137 22 : if($this->useSSL === true)
138 22 : {
139 1 : curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
140 1 : curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
141 1 : }
142 22 : return $ch;
143 : }
144 :
145 : protected function encode_rfc3986($string)
146 : {
147 27 : return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode(($string))));
148 : }
149 :
150 : protected function generateNonce()
151 : {
152 27 : if(isset($this->nonce)) // for unit testing
153 27 : return $this->nonce;
154 :
155 22 : return md5(uniqid(rand(), true));
156 : }
157 :
158 : // parameters should already have been passed through prepareParameters
159 : // no need to double encode
160 : protected function generateSignature($method = null, $url = null, $params = null)
161 : {
162 27 : if(empty($method) || empty($url))
163 27 : return false;
164 :
165 : // concatenating and encode
166 27 : $concatenatedParams = $this->encode_rfc3986($this->buildHttpQueryRaw($params));
167 :
168 : // normalize url
169 27 : $normalizedUrl = $this->encode_rfc3986($this->normalizeUrl($url));
170 27 : $method = $this->encode_rfc3986($method); // don't need this but why not?
171 :
172 27 : $signatureBaseString = "{$method}&{$normalizedUrl}&{$concatenatedParams}";
173 27 : return $this->signString($signatureBaseString);
174 : }
175 :
176 : protected function httpGet($url, $params = null)
177 : {
178 16 : if(count($params['request']) > 0)
179 16 : {
180 9 : $url .= '?';
181 9 : foreach($params['request'] as $k => $v)
182 : {
183 9 : $url .= "{$k}={$v}&";
184 9 : }
185 9 : $url = substr($url, 0, -1);
186 9 : }
187 16 : $this->addDefaultHeaders($url, $params['oauth']);
188 16 : $ch = $this->curlInit($url);
189 16 : $resp = $this->curl->addCurl($ch);
190 :
191 16 : return $resp;
192 : }
193 :
194 : protected function httpPost($url, $params = null, $isMultipart)
195 : {
196 9 : $this->addDefaultHeaders($url, $params['oauth']);
197 9 : $ch = $this->curlInit($url);
198 9 : curl_setopt($ch, CURLOPT_POST, 1);
199 : // php's curl extension automatically sets the content type
200 : // based on whether the params are in string or array form
201 : if($isMultipart)
202 9 : curl_setopt($ch, CURLOPT_POSTFIELDS, $params['request']);
203 : else
204 7 : curl_setopt($ch, CURLOPT_POSTFIELDS, $this->buildHttpQueryRaw($params['request']));
205 9 : $resp = $this->curl->addCurl($ch);
206 9 : return $resp;
207 : }
208 :
209 : protected function normalizeUrl($url = null)
210 : {
211 27 : $urlParts = parse_url($url);
212 27 : $scheme = strtolower($urlParts['scheme']);
213 27 : $host = strtolower($urlParts['host']);
214 27 : $port = isset($urlParts['port']) ? intval($urlParts['port']) : 0;
215 :
216 27 : $retval = strtolower($scheme) . '://' . strtolower($host);
217 :
218 27 : if(!empty($port) && (($scheme === 'http' && $port != 80) || ($scheme === 'https' && $port != 443)))
219 27 : $retval .= ":{$port}";
220 :
221 27 : $retval .= $urlParts['path'];
222 27 : if(!empty($urlParts['query']))
223 27 : {
224 0 : $retval .= "?{$urlParts['query']}";
225 0 : }
226 :
227 27 : return $retval;
228 : }
229 :
230 : protected function prepareParameters($method = null, $url = null, $params = null)
231 : {
232 27 : if(empty($method) || empty($url))
233 27 : return false;
234 :
235 27 : $oauth['oauth_consumer_key'] = $this->consumerKey;
236 27 : $oauth['oauth_token'] = $this->token;
237 27 : $oauth['oauth_nonce'] = $this->generateNonce();
238 27 : $oauth['oauth_timestamp'] = !isset($this->timestamp) ? time() : $this->timestamp; // for unit test
239 27 : $oauth['oauth_signature_method'] = $this->signatureMethod;
240 27 : $oauth['oauth_version'] = $this->version;
241 : // encode all oauth values
242 27 : foreach($oauth as $k => $v)
243 27 : $oauth[$k] = $this->encode_rfc3986($v);
244 : // encode all non '@' params
245 : // keep sigParams for signature generation (exclude '@' params)
246 : // rename '@key' to 'key'
247 27 : $sigParams = array();
248 27 : $hasFile = false;
249 27 : if(is_array($params))
250 27 : {
251 21 : foreach($params as $k => $v)
252 : {
253 21 : if(strncmp('@',$k,1) !== 0)
254 21 : {
255 20 : $sigParams[$k] = $this->encode_rfc3986($v);
256 20 : $params[$k] = $this->encode_rfc3986($v);
257 20 : }
258 : else
259 : {
260 2 : $params[substr($k, 1)] = $v;
261 2 : unset($params[$k]);
262 2 : $hasFile = true;
263 : }
264 21 : }
265 :
266 21 : if($hasFile === true)
267 21 : $sigParams = array();
268 21 : }
269 :
270 27 : $sigParams = array_merge($oauth, (array)$sigParams);
271 :
272 : // sorting
273 27 : ksort($sigParams);
274 :
275 : // signing
276 27 : $oauth['oauth_signature'] = $this->encode_rfc3986($this->generateSignature($method, $url, $sigParams));
277 27 : return array('request' => $params, 'oauth' => $oauth);
278 : }
279 :
280 : protected function signString($string = null)
281 : {
282 27 : $retval = false;
283 27 : switch($this->signatureMethod)
284 : {
285 27 : case 'HMAC-SHA1':
286 27 : $key = $this->encode_rfc3986($this->consumerSecret) . '&' . $this->encode_rfc3986($this->tokenSecret);
287 27 : $retval = base64_encode(hash_hmac('sha1', $string, $key, true));
288 27 : break;
289 0 : }
290 :
291 27 : return $retval;
292 : }
293 :
294 : public function __construct($consumerKey, $consumerSecret, $signatureMethod='HMAC-SHA1')
295 : {
296 29 : $this->consumerKey = $consumerKey;
297 29 : $this->consumerSecret = $consumerSecret;
298 29 : $this->signatureMethod = $signatureMethod;
299 29 : $this->curl = EpiCurl::getInstance();
300 29 : }
301 : }
302 :
303 : class EpiOAuthResponse
304 : {
305 : private $__resp;
306 :
307 : public function __construct($resp)
308 : {
309 4 : $this->__resp = $resp;
310 4 : }
311 :
312 : public function __get($name)
313 : {
314 4 : if($this->__resp->code != 200)
315 4 : EpiOAuthException::raise($this->__resp->data, $this->__resp->code);
316 :
317 4 : parse_str($this->__resp->data, $result);
318 4 : foreach($result as $k => $v)
319 : {
320 4 : $this->$k = $v;
321 4 : }
322 :
323 4 : return $result[$name];
324 : }
325 : }
326 :
327 : class EpiOAuthException extends Exception
328 : {
329 : public static function raise($message, $code)
330 : {
331 : switch($code)
332 : {
333 1 : case 400:
334 0 : throw new EpiOAuthBadRequestException($message, $code);
335 1 : case 401:
336 0 : throw new EpiOAuthUnauthorizedException($message, $code);
337 1 : default:
338 1 : throw new EpiOAuthException($message, $code);
339 1 : }
340 : }
341 : }
342 :
343 :
344 : class EpiOAuthBadRequestException extends EpiOAuthException{}
345 : class EpiOAuthUnauthorizedException extends EpiOAuthException{}
|