├── .gitignore ├── README.md ├── index.php ├── log.php ├── plugin.php ├── plugins ├── .gitignore ├── jsonbody.php └── testplugin.php ├── requests ├── Requests.php └── Requests │ ├── Auth.php │ ├── Auth │ └── Basic.php │ ├── Exception.php │ ├── Exception │ ├── HTTP.php │ └── HTTP │ │ ├── 400.php │ │ ├── 401.php │ │ ├── 402.php │ │ ├── 403.php │ │ ├── 404.php │ │ ├── 405.php │ │ ├── 406.php │ │ ├── 407.php │ │ ├── 408.php │ │ ├── 409.php │ │ ├── 410.php │ │ ├── 411.php │ │ ├── 412.php │ │ ├── 413.php │ │ ├── 414.php │ │ ├── 415.php │ │ ├── 416.php │ │ ├── 417.php │ │ ├── 418.php │ │ ├── 428.php │ │ ├── 429.php │ │ ├── 431.php │ │ ├── 500.php │ │ ├── 501.php │ │ ├── 502.php │ │ ├── 503.php │ │ ├── 504.php │ │ ├── 505.php │ │ ├── 511.php │ │ └── Unknown.php │ ├── Hooks.php │ ├── IDNAEncoder.php │ ├── IPv6.php │ ├── IRI.php │ ├── Response.php │ ├── Response │ └── Headers.php │ ├── Transport.php │ └── Transport │ ├── cURL.php │ └── fsockopen.php ├── settings.php ├── wp-admin ├── options-discussion.php ├── options-general.php └── profile.php └── xmlrpc.php /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ifttt-webhook 2 | ============= 3 | 4 | A webhook middleware for the ifttt.com service 5 | 6 | #How To Use 7 | 1. Change your ifttt.com wordpress server to . 8 | 2. You can use any username/password combination you want. ifttt will accept the authentication irrespective of what details you enter here. These details will be passed along by the webhook as well, so that you may use these as your authentication medium, perhaps. 9 | 3. Create a recipe in ifttt which would post to your "wordpress channel". In the "Tags" field, use the webhook url that you want to use. 10 | 11 | ![Connecting to ifttt-webhook](http://i.imgur.com/RA0Jb.png "You can type in any username/password you want") 12 | 13 | Any username/password combination will be accepted, and passed through to the webhook url. A blank password is considered valid, but ifttt invalidates a blank username. 14 | 15 | ![Screenshot of a channel](http://i.imgur.com/5FaU1.png "Sample Channel for use as a webhook") 16 | 17 | Make sure that the url you specify accepts POST requests. The url is only picked up from the tags field, and all other fields are passed through to the webhook url. 18 | 19 | Plugin support 20 | -------------- 21 | 22 | This fork supports plugins that allow protocol specific modification of the sent webhook payload in order to match whatever format the endpoint wants. 23 | 24 | To do this specify a **plugin:xxxx** category, where **xxxx** is the name of the class (extending "Plugin") and also the name of the .php file in the plugins directory. See the plugins/testplugin.php file for example. 25 | 26 | #How It Works 27 | ifttt uses wordpress-xmlrpc to communicate with the wordpress blog. We present a fake-xmlrpc interface on the webadress, which causes ifttt to be fooled into thinking of this as a genuine wordpress blog. The only action that ifttt allows for wordpress are posting, which are instead used for powering webhooks. All the other fields (title, description, categories) along with the username/password credentials are passed along by the webhook. Do not use the "Create a photo post" action for wordpress, as ifttt manually adds a `` tag in the description pointing to what url you pass. Its better to pass the url in clear instead (using body/category/title fields). 28 | 29 | #Why 30 | There has been a lot of [call](http://blog.jazzychad.net/2012/08/05/ifttt-needs-webhooks-stat.html) for a ifttt-webhook. I had asked about it pretty early on, but ifttt has yet to create such a channel. It was fun to build and will allow me to hookup ifttt with things like [partychat][pc], [github](gh) and many other awesome services for which ifttt is yet to build a channel. You can build a postmarkapp.com like email-to-webhook service using ifttt alone. Wordpress seems to be the only channel on ifttt that supports custom domains, and hence can be used as a middleware. 31 | 32 | #Payload 33 | The following information is passed along by the webhook in the raw body of the post request in json encoded format. 34 | 35 | { 36 | user: "username specified in ifttt", 37 | password: "password specified in ifttt", 38 | title: "title generated for the recipe in ifttt", 39 | categories:['array','of','categories','passed'], 40 | description:"Body of the blog post as created in ifttt recipe" 41 | } 42 | 43 | To get the data from the POST request, you can use any of the following: 44 | 45 | $data = json_decode(file_get_contents('php://input')); #php 46 | data = JSON.parse(request.body.read) #ruby-sinatra 47 | 48 | Testing 49 | ------- 50 | GitHub has a handy guide for testing webhooks than you might find useful: https://help.github.com/articles/testing-webhooks 51 | 52 | #Licence 53 | Licenced under GPL. Some portions of the code are from wordpress itself. You should probably host this on your own server, instead of using `ifttt.captnemo.in`. I recommend using [phpfog](https://phpfog.com/?a_aid=64682331 "My Affiliate Link") for excellent php hosting. 54 | 55 | #Custom Use 56 | Just clone the git repo to some place, and use that as the wordpress installation location in ifttt.com channel settings. 57 | 58 | [pc]: http://partychat-hooks.appspot.com/ "Partychat Hooks" 59 | [gh]: https://help.github.com/articles/post-receive-hooks/ "Github Post receive hooks" 60 | 61 | #About this Fork 62 | This is a modification of the original repo created by Captn3mo, made by Marcus Povey . 63 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | execute ($plugin, $object, $raw); 44 | } 45 | 46 | __log("Plugin is invalid"); 47 | 48 | return false; 49 | } 50 | -------------------------------------------------------------------------------- /plugins/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapkyca/ifttt-webhook/207849c543a60c05c065210da0b07239dd53d94c/plugins/.gitignore -------------------------------------------------------------------------------- /plugins/jsonbody.php: -------------------------------------------------------------------------------- 1 | description}'"); 16 | 17 | $json = json_decode($object->description); 18 | if (!$json) { 19 | __log("Invalid JSON payload '$json'", 'ERROR'); 20 | return false; 21 | } 22 | 23 | return $json; 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugins/testplugin.php: -------------------------------------------------------------------------------- 1 | 10, 292 | 'useragent' => 'php-requests/' . self::VERSION, 293 | 'redirected' => 0, 294 | 'redirects' => 10, 295 | 'follow_redirects' => true, 296 | 'blocking' => true, 297 | 'type' => $type, 298 | 'filename' => false, 299 | 'auth' => false, 300 | 'idn' => true, 301 | 'hooks' => null, 302 | 'transport' => null, 303 | ); 304 | $options = array_merge($defaults, $options); 305 | 306 | if (empty($options['hooks'])) { 307 | $options['hooks'] = new Requests_Hooks(); 308 | } 309 | 310 | // Special case for simple basic auth 311 | if (is_array($options['auth'])) { 312 | $options['auth'] = new Requests_Auth_Basic($options['auth']); 313 | } 314 | if ($options['auth'] !== false) { 315 | $options['auth']->register($options['hooks']); 316 | } 317 | 318 | $options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options)); 319 | 320 | if ($options['idn'] !== false) { 321 | $iri = new Requests_IRI($url); 322 | $iri->host = Requests_IDNAEncoder::encode($iri->ihost); 323 | $url = $iri->uri; 324 | } 325 | 326 | if (!empty($options['transport'])) { 327 | $transport = $options['transport']; 328 | 329 | if (is_string($options['transport'])) { 330 | $transport = new $transport(); 331 | } 332 | } 333 | else { 334 | $transport = self::get_transport(); 335 | } 336 | $response = $transport->request($url, $headers, $data, $options); 337 | 338 | $options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options)); 339 | 340 | return self::parse_response($response, $url, $headers, $data, $options); 341 | } 342 | 343 | /** 344 | * HTTP response parser 345 | * 346 | * @throws Requests_Exception On missing head/body separator (`requests.no_crlf_separator`) 347 | * @throws Requests_Exception On missing head/body separator (`noversion`) 348 | * @throws Requests_Exception On missing head/body separator (`toomanyredirects`) 349 | * 350 | * @param string $headers Full response text including headers and body 351 | * @param string $url Original request URL 352 | * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects 353 | * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects 354 | * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects 355 | * @return Requests_Response 356 | */ 357 | protected static function parse_response($headers, $url, $req_headers, $req_data, $options) { 358 | $return = new Requests_Response(); 359 | if (!$options['blocking']) { 360 | return $return; 361 | } 362 | 363 | $return->raw = $headers; 364 | $return->url = $url; 365 | 366 | if (!$options['filename']) { 367 | if (strpos($headers, "\r\n\r\n") === false) { 368 | // Crap! 369 | throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator'); 370 | } 371 | 372 | $headers = explode("\r\n\r\n", $headers, 2); 373 | $return->body = array_pop($headers); 374 | $headers = $headers[0]; 375 | } 376 | else { 377 | $return->body = ''; 378 | } 379 | // Pretend CRLF = LF for compatibility (RFC 2616, section 19.3) 380 | $headers = str_replace("\r\n", "\n", $headers); 381 | // Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2) 382 | $headers = preg_replace('/\n[ \t]/', ' ', $headers); 383 | $headers = explode("\n", $headers); 384 | preg_match('#^HTTP/1\.\d[ \t]+(\d+)#i', array_shift($headers), $matches); 385 | if (empty($matches)) { 386 | throw new Requests_Exception('Response could not be parsed', 'noversion', $headers); 387 | } 388 | $return->status_code = (int) $matches[1]; 389 | if ($return->status_code >= 200 && $return->status_code < 300) { 390 | $return->success = true; 391 | } 392 | 393 | foreach ($headers as $header) { 394 | list($key, $value) = explode(':', $header, 2); 395 | $value = trim($value); 396 | preg_replace('#(\s+)#i', ' ', $value); 397 | $return->headers[$key] = $value; 398 | } 399 | if (isset($return->headers['transfer-encoding'])) { 400 | $return->body = self::decode_chunked($return->body); 401 | unset($return->headers['transfer-encoding']); 402 | } 403 | if (isset($return->headers['content-encoding'])) { 404 | $return->body = self::decompress($return->body); 405 | } 406 | 407 | //fsockopen and cURL compatibility 408 | if (isset($return->headers['connection'])) { 409 | unset($return->headers['connection']); 410 | } 411 | 412 | if ((in_array($return->status_code, array(300, 301, 302, 303, 307)) || $return->status_code > 307 && $return->status_code < 400) && $options['follow_redirects'] === true) { 413 | if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) { 414 | if ($return->status_code === 303) { 415 | $options['type'] = Requests::GET; 416 | } 417 | $options['redirected']++; 418 | $location = $return->headers['location']; 419 | if (strpos ($location, '/') === 0) { 420 | // relative redirect, for compatibility make it absolute 421 | $location = Requests_IRI::absolutize($url, $location); 422 | } 423 | $redirected = self::request($location, $req_headers, $req_data, false, $options); 424 | $redirected->history[] = $return; 425 | return $redirected; 426 | } 427 | elseif ($options['redirected'] >= $options['redirects']) { 428 | throw new Requests_Exception('Too many redirects', 'toomanyredirects', $return); 429 | } 430 | } 431 | 432 | $return->redirects = $options['redirected']; 433 | 434 | $options['hooks']->dispatch('requests.after_request', array(&$return, $req_headers, $req_data, $options)); 435 | return $return; 436 | } 437 | 438 | /** 439 | * Decoded a chunked body as per RFC 2616 440 | * 441 | * @see http://tools.ietf.org/html/rfc2616#section-3.6.1 442 | * @param string $data Chunked body 443 | * @return string Decoded body 444 | */ 445 | protected static function decode_chunked($data) { 446 | if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($data))) { 447 | return $data; 448 | } 449 | 450 | $decoded = ''; 451 | $encoded = $data; 452 | 453 | while (true) { 454 | $is_chunked = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches ); 455 | if (!$is_chunked) { 456 | // Looks like it's not chunked after all 457 | return $data; 458 | } 459 | 460 | $length = hexdec(trim($matches[1])); 461 | if ($length === 0) { 462 | // Ignore trailer headers 463 | return $decoded; 464 | } 465 | 466 | $chunk_length = strlen($matches[0]); 467 | $decoded .= $part = substr($encoded, $chunk_length, $length); 468 | $encoded = substr($encoded, $chunk_length + $length + 2); 469 | 470 | if (trim($encoded) === '0' || empty($encoded)) { 471 | return $decoded; 472 | } 473 | } 474 | 475 | // We'll never actually get down here 476 | // @codeCoverageIgnoreStart 477 | } 478 | // @codeCoverageIgnoreEnd 479 | 480 | /** 481 | * Convert a key => value array to a 'key: value' array for headers 482 | * 483 | * @param array $array Dictionary of header values 484 | * @return array List of headers 485 | */ 486 | public static function flattern($array) { 487 | $return = array(); 488 | foreach ($array as $key => $value) { 489 | $return[] = "$key: $value"; 490 | } 491 | return $return; 492 | } 493 | 494 | /** 495 | * Decompress an encoded body 496 | * 497 | * Implements gzip, compress and deflate. Guesses which it is by attempting 498 | * to decode. 499 | * 500 | * @todo Make this smarter by defaulting to whatever the headers say first 501 | * @param string $data Compressed data in one of the above formats 502 | * @return string Decompressed string 503 | */ 504 | protected static function decompress($data) { 505 | if (substr($data, 0, 2) !== "\x1f\x8b") { 506 | // Not actually compressed. Probably cURL ruining this for us. 507 | return $data; 508 | } 509 | 510 | if (function_exists('gzdecode') && ($decoded = gzdecode($data)) !== false) { 511 | return $decoded; 512 | } 513 | elseif (function_exists('gzinflate') && ($decoded = @gzinflate($data)) !== false) { 514 | return $decoded; 515 | } 516 | elseif (($decoded = self::compatible_gzinflate($data)) !== false) { 517 | return $decoded; 518 | } 519 | elseif (function_exists('gzuncompress') && ($decoded = @gzuncompress($data)) !== false) { 520 | return $decoded; 521 | } 522 | 523 | return $data; 524 | } 525 | 526 | /** 527 | * Decompress deflated string while staying compatible with the majority of servers. 528 | * 529 | * Certain servers will return deflated data with headers which PHP's gziniflate() 530 | * function cannot handle out of the box. The following function lifted from 531 | * http://au2.php.net/manual/en/function.gzinflate.php#77336 will attempt to deflate 532 | * the various return forms used. 533 | * 534 | * @link http://au2.php.net/manual/en/function.gzinflate.php#77336 535 | * 536 | * @param string $gzData String to decompress. 537 | * @return string|bool False on failure. 538 | */ 539 | protected static function compatible_gzinflate($gzData) { 540 | if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) { 541 | $i = 10; 542 | $flg = ord( substr($gzData, 3, 1) ); 543 | if ( $flg > 0 ) { 544 | if ( $flg & 4 ) { 545 | list($xlen) = unpack('v', substr($gzData, $i, 2) ); 546 | $i = $i + 2 + $xlen; 547 | } 548 | if ( $flg & 8 ) 549 | $i = strpos($gzData, "\0", $i) + 1; 550 | if ( $flg & 16 ) 551 | $i = strpos($gzData, "\0", $i) + 1; 552 | if ( $flg & 2 ) 553 | $i = $i + 2; 554 | } 555 | return gzinflate( substr($gzData, $i, -8) ); 556 | } else { 557 | return false; 558 | } 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /requests/Requests/Auth.php: -------------------------------------------------------------------------------- 1 | user, $this->pass) = $args; 46 | } 47 | } 48 | 49 | /** 50 | * Register the necessary callbacks 51 | * 52 | * @see curl_before_send 53 | * @see fsockopen_header 54 | * @param Requests_Hooks $hooks Hook system 55 | */ 56 | public function register(Requests_Hooks &$hooks) { 57 | $hooks->register('curl.before_send', array(&$this, 'curl_before_send')); 58 | $hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header')); 59 | } 60 | 61 | /** 62 | * Set cURL parameters before the data is sent 63 | * 64 | * @param resource $handle cURL resource 65 | */ 66 | public function curl_before_send(&$handle) { 67 | curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 68 | curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString()); 69 | } 70 | 71 | /** 72 | * Add extra headers to the request before sending 73 | * 74 | * @param string $out HTTP header string 75 | */ 76 | public function fsockopen_header(&$out) { 77 | $out .= "Authorization: Basic " . base64_encode($this->getAuthString()) . "\r\n"; 78 | } 79 | 80 | /** 81 | * Get the authentication string (user:pass) 82 | * 83 | * @return string 84 | */ 85 | public function getAuthString() { 86 | return $this->user . ':' . $this->pass; 87 | } 88 | } -------------------------------------------------------------------------------- /requests/Requests/Exception.php: -------------------------------------------------------------------------------- 1 | type = $type; 40 | $this->data = $data; 41 | } 42 | 43 | /** 44 | * Like {@see getCode()}, but a string code. 45 | * 46 | * @codeCoverageIgnore 47 | * @return string 48 | */ 49 | public function getType() { 50 | return $this->type; 51 | } 52 | 53 | /** 54 | * Gives any relevant data 55 | * 56 | * @codeCoverageIgnore 57 | * @return mixed 58 | */ 59 | public function getData() { 60 | return $this->data; 61 | } 62 | } -------------------------------------------------------------------------------- /requests/Requests/Exception/HTTP.php: -------------------------------------------------------------------------------- 1 | reason = $reason; 40 | } 41 | 42 | $message = sprintf('%d %s', $this->code, $this->reason); 43 | parent::__construct($message, 'httpresponse', $data, $this->code); 44 | } 45 | 46 | /** 47 | * Get the status message 48 | */ 49 | public function get_reason() { 50 | return $this->reason; 51 | } 52 | 53 | /** 54 | * Get the correct exception class for a given error code 55 | * 56 | * @param int $code HTTP status code 57 | * @return string Exception class name to use 58 | */ 59 | public static function get_class($code) { 60 | $class = sprintf('Requests_Exception_HTTP_%d', $code); 61 | if (class_exists($class)) { 62 | return $class; 63 | } 64 | 65 | return 'Requests_Exception_HTTP_Unknown'; 66 | } 67 | } -------------------------------------------------------------------------------- /requests/Requests/Exception/HTTP/400.php: -------------------------------------------------------------------------------- 1 | code = $data->status_code; 40 | } 41 | 42 | parent::__construct($reason, $data); 43 | } 44 | } -------------------------------------------------------------------------------- /requests/Requests/Hooks.php: -------------------------------------------------------------------------------- 1 | 0 is executed later 29 | */ 30 | public function register($hook, $callback, $priority = 0) { 31 | if (!isset($this->hooks[$hook])) { 32 | $this->hooks[$hook] = array(); 33 | } 34 | if (!isset($this->hooks[$hook][$priority])) { 35 | $this->hooks[$hook][$priority] = array(); 36 | } 37 | 38 | $this->hooks[$hook][$priority][] = $callback; 39 | } 40 | 41 | /** 42 | * Dispatch a message 43 | * 44 | * @param string $hook Hook name 45 | * @param array $parameters Parameters to pass to callbacks 46 | * @return boolean Successfulness 47 | */ 48 | public function dispatch($hook, $parameters = array()) { 49 | if (empty($this->hooks[$hook])) { 50 | return false; 51 | } 52 | 53 | foreach ($this->hooks[$hook] as $priority => $hooked) { 54 | foreach ($hooked as $callback) { 55 | call_user_func_array($callback, $parameters); 56 | } 57 | } 58 | 59 | return true; 60 | } 61 | } -------------------------------------------------------------------------------- /requests/Requests/IDNAEncoder.php: -------------------------------------------------------------------------------- 1 | 0) { 177 | if ($position + $length > $strlen) { 178 | throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); 179 | } 180 | for ($position++; $remaining > 0; $position++) { 181 | $value = ord($input[$position]); 182 | 183 | // If it is invalid, count the sequence as invalid and reprocess the current byte: 184 | if (($value & 0xC0) !== 0x80) { 185 | throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); 186 | } 187 | 188 | $character |= ($value & 0x3F) << (--$remaining * 6); 189 | } 190 | $position--; 191 | } 192 | 193 | if ( 194 | // Non-shortest form sequences are invalid 195 | $length > 1 && $character <= 0x7F 196 | || $length > 2 && $character <= 0x7FF 197 | || $length > 3 && $character <= 0xFFFF 198 | // Outside of range of ucschar codepoints 199 | // Noncharacters 200 | || ($character & 0xFFFE) === 0xFFFE 201 | || $character >= 0xFDD0 && $character <= 0xFDEF 202 | || ( 203 | // Everything else not in ucschar 204 | $character > 0xD7FF && $character < 0xF900 205 | || $character < 0x20 206 | || $character > 0x7E && $character < 0xA0 207 | || $character > 0xEFFFD 208 | ) 209 | ) { 210 | throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); 211 | } 212 | 213 | $codepoints[] = $character; 214 | } 215 | 216 | return $codepoints; 217 | } 218 | 219 | /** 220 | * RFC3492-compliant encoder 221 | * 222 | * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code 223 | * @throws Requests_Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`) 224 | * 225 | * @param string $input UTF-8 encoded string to encode 226 | * @return string Punycode-encoded string 227 | */ 228 | public static function punycode_encode($input) { 229 | $output = ''; 230 | # let n = initial_n 231 | $n = self::BOOTSTRAP_INITIAL_N; 232 | # let delta = 0 233 | $delta = 0; 234 | # let bias = initial_bias 235 | $bias = self::BOOTSTRAP_INITIAL_BIAS; 236 | # let h = b = the number of basic code points in the input 237 | $h = $b = 0; // see loop 238 | # copy them to the output in order 239 | $codepoints = self::utf8_to_codepoints($input); 240 | 241 | foreach ($codepoints as $char) { 242 | if ($char < 128) { 243 | // Character is valid ASCII 244 | // TODO: this should also check if it's valid for a URL 245 | $output .= chr($char); 246 | $h++; 247 | } 248 | // Check if the character is non-ASCII, but below initial n 249 | // This never occurs for Punycode, so ignore in coverage 250 | // @codeCoverageIgnoreStart 251 | elseif ($char < $n) { 252 | throw new Requests_Exception('Invalid character', 'idna.character_outside_domain', $char); 253 | } 254 | // @codeCoverageIgnoreEnd 255 | else { 256 | $extended[$char] = true; 257 | } 258 | } 259 | $extended = array_keys($extended); 260 | sort($extended); 261 | $b = $h; 262 | # [copy them] followed by a delimiter if b > 0 263 | if (strlen($output) > 0) { 264 | $output .= '-'; 265 | } 266 | # {if the input contains a non-basic code point < n then fail} 267 | # while h < length(input) do begin 268 | while ($h < count($codepoints)) { 269 | # let m = the minimum code point >= n in the input 270 | $m = array_shift($extended); 271 | //printf('next code point to insert is %s' . PHP_EOL, dechex($m)); 272 | # let delta = delta + (m - n) * (h + 1), fail on overflow 273 | $delta += ($m - $n) * ($h + 1); 274 | # let n = m 275 | $n = $m; 276 | # for each code point c in the input (in order) do begin 277 | for ($num = 0; $num < count($codepoints); $num++) { 278 | $c = $codepoints[$num]; 279 | # if c < n then increment delta, fail on overflow 280 | if ($c < $n) { 281 | $delta++; 282 | } 283 | # if c == n then begin 284 | elseif ($c === $n) { 285 | # let q = delta 286 | $q = $delta; 287 | # for k = base to infinity in steps of base do begin 288 | for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) { 289 | # let t = tmin if k <= bias {+ tmin}, or 290 | # tmax if k >= bias + tmax, or k - bias otherwise 291 | if ($k <= ($bias + self::BOOTSTRAP_TMIN)) { 292 | $t = self::BOOTSTRAP_TMIN; 293 | } 294 | elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) { 295 | $t = self::BOOTSTRAP_TMAX; 296 | } 297 | else { 298 | $t = $k - $bias; 299 | } 300 | # if q < t then break 301 | if ($q < $t) { 302 | break; 303 | } 304 | # output the code point for digit t + ((q - t) mod (base - t)) 305 | $digit = $t + (($q - $t) % (self::BOOTSTRAP_BASE - $t)); 306 | //printf('needed delta is %d, encodes as "%s"' . PHP_EOL, $delta, self::digit_to_char($digit)); 307 | $output .= self::digit_to_char($digit); 308 | # let q = (q - t) div (base - t) 309 | $q = floor(($q - $t) / (self::BOOTSTRAP_BASE - $t)); 310 | # end 311 | } 312 | # output the code point for digit q 313 | $output .= self::digit_to_char($q); 314 | //printf('needed delta is %d, encodes as "%s"' . PHP_EOL, $delta, self::digit_to_char($q)); 315 | # let bias = adapt(delta, h + 1, test h equals b?) 316 | $bias = self::adapt($delta, $h + 1, $h === $b); 317 | //printf('bias becomes %d' . PHP_EOL, $bias); 318 | # let delta = 0 319 | $delta = 0; 320 | # increment h 321 | $h++; 322 | # end 323 | } 324 | # end 325 | } 326 | # increment delta and n 327 | $delta++; 328 | $n++; 329 | # end 330 | } 331 | 332 | return $output; 333 | } 334 | 335 | /** 336 | * Convert a digit to its respective character 337 | * 338 | * @see http://tools.ietf.org/html/rfc3492#section-5 339 | * @throws Requests_Exception On invalid digit (`idna.invalid_digit`) 340 | * 341 | * @param int $digit Digit in the range 0-35 342 | * @return string Single character corresponding to digit 343 | */ 344 | protected static function digit_to_char($digit) { 345 | // @codeCoverageIgnoreStart 346 | // As far as I know, this never happens, but still good to be sure. 347 | if ($digit < 0 || $digit > 35) { 348 | throw new Requests_Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit); 349 | } 350 | // @codeCoverageIgnoreEnd 351 | $digits = 'abcdefghijklmnopqrstuvwxyz0123456789'; 352 | return substr($digits, $digit, 1); 353 | } 354 | 355 | /** 356 | * Adapt the bias 357 | * 358 | * @see http://tools.ietf.org/html/rfc3492#section-6.1 359 | * @param int $delta 360 | * @param int $numpoints 361 | * @param bool $firsttime 362 | * @return int New bias 363 | */ 364 | protected static function adapt($delta, $numpoints, $firsttime) { 365 | # function adapt(delta,numpoints,firsttime): 366 | # if firsttime then let delta = delta div damp 367 | if ($firsttime) { 368 | $delta = floor($delta / self::BOOTSTRAP_DAMP); 369 | } 370 | # else let delta = delta div 2 371 | else { 372 | $delta = floor($delta / 2); 373 | } 374 | # let delta = delta + (delta div numpoints) 375 | $delta += floor($delta / $numpoints); 376 | # let k = 0 377 | $k = 0; 378 | # while delta > ((base - tmin) * tmax) div 2 do begin 379 | $max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2); 380 | while ($delta > $max) { 381 | # let delta = delta div (base - tmin) 382 | $delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN)); 383 | # let k = k + base 384 | $k += self::BOOTSTRAP_BASE; 385 | # end 386 | } 387 | # return k + (((base - tmin + 1) * delta) div (delta + skew)) 388 | return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW)); 389 | } 390 | } -------------------------------------------------------------------------------- /requests/Requests/IPv6.php: -------------------------------------------------------------------------------- 1 | FF01:0:0:0:0:0:0:101 28 | * ::1 -> 0:0:0:0:0:0:0:1 29 | * 30 | * @author Alexander Merz 31 | * @author elfrink at introweb dot nl 32 | * @author Josh Peck 33 | * @copyright 2003-2005 The PHP Group 34 | * @license http://www.opensource.org/licenses/bsd-license.php 35 | * @param string $ip An IPv6 address 36 | * @return string The uncompressed IPv6 address 37 | */ 38 | public static function uncompress($ip) 39 | { 40 | $c1 = -1; 41 | $c2 = -1; 42 | if (substr_count($ip, '::') === 1) 43 | { 44 | list($ip1, $ip2) = explode('::', $ip); 45 | if ($ip1 === '') 46 | { 47 | $c1 = -1; 48 | } 49 | else 50 | { 51 | $c1 = substr_count($ip1, ':'); 52 | } 53 | if ($ip2 === '') 54 | { 55 | $c2 = -1; 56 | } 57 | else 58 | { 59 | $c2 = substr_count($ip2, ':'); 60 | } 61 | if (strpos($ip2, '.') !== false) 62 | { 63 | $c2++; 64 | } 65 | // :: 66 | if ($c1 === -1 && $c2 === -1) 67 | { 68 | $ip = '0:0:0:0:0:0:0:0'; 69 | } 70 | // ::xxx 71 | else if ($c1 === -1) 72 | { 73 | $fill = str_repeat('0:', 7 - $c2); 74 | $ip = str_replace('::', $fill, $ip); 75 | } 76 | // xxx:: 77 | else if ($c2 === -1) 78 | { 79 | $fill = str_repeat(':0', 7 - $c1); 80 | $ip = str_replace('::', $fill, $ip); 81 | } 82 | // xxx::xxx 83 | else 84 | { 85 | $fill = ':' . str_repeat('0:', 6 - $c2 - $c1); 86 | $ip = str_replace('::', $fill, $ip); 87 | } 88 | } 89 | return $ip; 90 | } 91 | 92 | /** 93 | * Compresses an IPv6 address 94 | * 95 | * RFC 4291 allows you to compress concecutive zero pieces in an address to 96 | * '::'. This method expects a valid IPv6 address and compresses consecutive 97 | * zero pieces to '::'. 98 | * 99 | * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 100 | * 0:0:0:0:0:0:0:1 -> ::1 101 | * 102 | * @see uncompress() 103 | * @param string $ip An IPv6 address 104 | * @return string The compressed IPv6 address 105 | */ 106 | public static function compress($ip) 107 | { 108 | // Prepare the IP to be compressed 109 | $ip = self::uncompress($ip); 110 | $ip_parts = self::split_v6_v4($ip); 111 | 112 | // Replace all leading zeros 113 | $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]); 114 | 115 | // Find bunches of zeros 116 | if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) 117 | { 118 | $max = 0; 119 | $pos = null; 120 | foreach ($matches[0] as $match) 121 | { 122 | if (strlen($match[0]) > $max) 123 | { 124 | $max = strlen($match[0]); 125 | $pos = $match[1]; 126 | } 127 | } 128 | 129 | $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max); 130 | } 131 | 132 | if ($ip_parts[1] !== '') 133 | { 134 | return implode(':', $ip_parts); 135 | } 136 | else 137 | { 138 | return $ip_parts[0]; 139 | } 140 | } 141 | 142 | /** 143 | * Splits an IPv6 address into the IPv6 and IPv4 representation parts 144 | * 145 | * RFC 4291 allows you to represent the last two parts of an IPv6 address 146 | * using the standard IPv4 representation 147 | * 148 | * Example: 0:0:0:0:0:0:13.1.68.3 149 | * 0:0:0:0:0:FFFF:129.144.52.38 150 | * 151 | * @param string $ip An IPv6 address 152 | * @return array [0] contains the IPv6 represented part, and [1] the IPv4 represented part 153 | */ 154 | private static function split_v6_v4($ip) 155 | { 156 | if (strpos($ip, '.') !== false) 157 | { 158 | $pos = strrpos($ip, ':'); 159 | $ipv6_part = substr($ip, 0, $pos); 160 | $ipv4_part = substr($ip, $pos + 1); 161 | return array($ipv6_part, $ipv4_part); 162 | } 163 | else 164 | { 165 | return array($ip, ''); 166 | } 167 | } 168 | 169 | /** 170 | * Checks an IPv6 address 171 | * 172 | * Checks if the given IP is a valid IPv6 address 173 | * 174 | * @param string $ip An IPv6 address 175 | * @return bool true if $ip is a valid IPv6 address 176 | */ 177 | public static function check_ipv6($ip) 178 | { 179 | $ip = self::uncompress($ip); 180 | list($ipv6, $ipv4) = self::split_v6_v4($ip); 181 | $ipv6 = explode(':', $ipv6); 182 | $ipv4 = explode('.', $ipv4); 183 | if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) 184 | { 185 | foreach ($ipv6 as $ipv6_part) 186 | { 187 | // The section can't be empty 188 | if ($ipv6_part === '') 189 | return false; 190 | 191 | // Nor can it be over four characters 192 | if (strlen($ipv6_part) > 4) 193 | return false; 194 | 195 | // Remove leading zeros (this is safe because of the above) 196 | $ipv6_part = ltrim($ipv6_part, '0'); 197 | if ($ipv6_part === '') 198 | $ipv6_part = '0'; 199 | 200 | // Check the value is valid 201 | $value = hexdec($ipv6_part); 202 | if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) 203 | return false; 204 | } 205 | if (count($ipv4) === 4) 206 | { 207 | foreach ($ipv4 as $ipv4_part) 208 | { 209 | $value = (int) $ipv4_part; 210 | if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) 211 | return false; 212 | } 213 | } 214 | return true; 215 | } 216 | else 217 | { 218 | return false; 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /requests/Requests/IRI.php: -------------------------------------------------------------------------------- 1 | array( 108 | 'port' => 674 109 | ), 110 | 'dict' => array( 111 | 'port' => 2628 112 | ), 113 | 'file' => array( 114 | 'ihost' => 'localhost' 115 | ), 116 | 'http' => array( 117 | 'port' => 80, 118 | 'ipath' => '/' 119 | ), 120 | 'https' => array( 121 | 'port' => 443, 122 | 'ipath' => '/' 123 | ), 124 | ); 125 | 126 | /** 127 | * Return the entire IRI when you try and read the object as a string 128 | * 129 | * @return string 130 | */ 131 | public function __toString() 132 | { 133 | return $this->get_iri(); 134 | } 135 | 136 | /** 137 | * Overload __set() to provide access via properties 138 | * 139 | * @param string $name Property name 140 | * @param mixed $value Property value 141 | */ 142 | public function __set($name, $value) 143 | { 144 | if (method_exists($this, 'set_' . $name)) 145 | { 146 | call_user_func(array($this, 'set_' . $name), $value); 147 | } 148 | elseif ( 149 | $name === 'iauthority' 150 | || $name === 'iuserinfo' 151 | || $name === 'ihost' 152 | || $name === 'ipath' 153 | || $name === 'iquery' 154 | || $name === 'ifragment' 155 | ) 156 | { 157 | call_user_func(array($this, 'set_' . substr($name, 1)), $value); 158 | } 159 | } 160 | 161 | /** 162 | * Overload __get() to provide access via properties 163 | * 164 | * @param string $name Property name 165 | * @return mixed 166 | */ 167 | public function __get($name) 168 | { 169 | // isset() returns false for null, we don't want to do that 170 | // Also why we use array_key_exists below instead of isset() 171 | $props = get_object_vars($this); 172 | 173 | if ( 174 | $name === 'iri' || 175 | $name === 'uri' || 176 | $name === 'iauthority' || 177 | $name === 'authority' 178 | ) 179 | { 180 | $return = $this->{"get_$name"}(); 181 | } 182 | elseif (array_key_exists($name, $props)) 183 | { 184 | $return = $this->$name; 185 | } 186 | // host -> ihost 187 | elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) 188 | { 189 | $return = $this->$prop; 190 | } 191 | // ischeme -> scheme 192 | elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) 193 | { 194 | $return = $this->$prop; 195 | } 196 | else 197 | { 198 | trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); 199 | $return = null; 200 | } 201 | 202 | if ($return === null && isset($this->normalization[$this->scheme][$name])) 203 | { 204 | return $this->normalization[$this->scheme][$name]; 205 | } 206 | else 207 | { 208 | return $return; 209 | } 210 | } 211 | 212 | /** 213 | * Overload __isset() to provide access via properties 214 | * 215 | * @param string $name Property name 216 | * @return bool 217 | */ 218 | public function __isset($name) 219 | { 220 | if (method_exists($this, 'get_' . $name) || isset($this->$name)) 221 | { 222 | return true; 223 | } 224 | else 225 | { 226 | return false; 227 | } 228 | } 229 | 230 | /** 231 | * Overload __unset() to provide access via properties 232 | * 233 | * @param string $name Property name 234 | */ 235 | public function __unset($name) 236 | { 237 | if (method_exists($this, 'set_' . $name)) 238 | { 239 | call_user_func(array($this, 'set_' . $name), ''); 240 | } 241 | } 242 | 243 | /** 244 | * Create a new IRI object, from a specified string 245 | * 246 | * @param string $iri 247 | */ 248 | public function __construct($iri = null) 249 | { 250 | $this->set_iri($iri); 251 | } 252 | 253 | /** 254 | * Create a new IRI object by resolving a relative IRI 255 | * 256 | * Returns false if $base is not absolute, otherwise an IRI. 257 | * 258 | * @param IRI|string $base (Absolute) Base IRI 259 | * @param IRI|string $relative Relative IRI 260 | * @return IRI|false 261 | */ 262 | public static function absolutize($base, $relative) 263 | { 264 | if (!($relative instanceof Requests_IRI)) 265 | { 266 | $relative = new Requests_IRI($relative); 267 | } 268 | if (!$relative->is_valid()) 269 | { 270 | return false; 271 | } 272 | elseif ($relative->scheme !== null) 273 | { 274 | return clone $relative; 275 | } 276 | else 277 | { 278 | if (!($base instanceof Requests_IRI)) 279 | { 280 | $base = new Requests_IRI($base); 281 | } 282 | if ($base->scheme !== null && $base->is_valid()) 283 | { 284 | if ($relative->get_iri() !== '') 285 | { 286 | if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) 287 | { 288 | $target = clone $relative; 289 | $target->scheme = $base->scheme; 290 | } 291 | else 292 | { 293 | $target = new Requests_IRI; 294 | $target->scheme = $base->scheme; 295 | $target->iuserinfo = $base->iuserinfo; 296 | $target->ihost = $base->ihost; 297 | $target->port = $base->port; 298 | if ($relative->ipath !== '') 299 | { 300 | if ($relative->ipath[0] === '/') 301 | { 302 | $target->ipath = $relative->ipath; 303 | } 304 | elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') 305 | { 306 | $target->ipath = '/' . $relative->ipath; 307 | } 308 | elseif (($last_segment = strrpos($base->ipath, '/')) !== false) 309 | { 310 | $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; 311 | } 312 | else 313 | { 314 | $target->ipath = $relative->ipath; 315 | } 316 | $target->ipath = $target->remove_dot_segments($target->ipath); 317 | $target->iquery = $relative->iquery; 318 | } 319 | else 320 | { 321 | $target->ipath = $base->ipath; 322 | if ($relative->iquery !== null) 323 | { 324 | $target->iquery = $relative->iquery; 325 | } 326 | elseif ($base->iquery !== null) 327 | { 328 | $target->iquery = $base->iquery; 329 | } 330 | } 331 | $target->ifragment = $relative->ifragment; 332 | } 333 | } 334 | else 335 | { 336 | $target = clone $base; 337 | $target->ifragment = null; 338 | } 339 | $target->scheme_normalization(); 340 | return $target; 341 | } 342 | else 343 | { 344 | return false; 345 | } 346 | } 347 | } 348 | 349 | /** 350 | * Parse an IRI into scheme/authority/path/query/fragment segments 351 | * 352 | * @param string $iri 353 | * @return array 354 | */ 355 | private function parse_iri($iri) 356 | { 357 | $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); 358 | if (preg_match('/^((?P[^:\/?#]+):)?(\/\/(?P[^\/?#]*))?(?P[^?#]*)(\?(?P[^#]*))?(#(?P.*))?$/', $iri, $match)) 359 | { 360 | if ($match[1] === '') 361 | { 362 | $match['scheme'] = null; 363 | } 364 | if (!isset($match[3]) || $match[3] === '') 365 | { 366 | $match['authority'] = null; 367 | } 368 | if (!isset($match[5])) 369 | { 370 | $match['path'] = ''; 371 | } 372 | if (!isset($match[6]) || $match[6] === '') 373 | { 374 | $match['query'] = null; 375 | } 376 | if (!isset($match[8]) || $match[8] === '') 377 | { 378 | $match['fragment'] = null; 379 | } 380 | return $match; 381 | } 382 | else 383 | { 384 | trigger_error('This should never happen', E_USER_ERROR); 385 | die; 386 | } 387 | } 388 | 389 | /** 390 | * Remove dot segments from a path 391 | * 392 | * @param string $input 393 | * @return string 394 | */ 395 | private function remove_dot_segments($input) 396 | { 397 | $output = ''; 398 | while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') 399 | { 400 | // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, 401 | if (strpos($input, '../') === 0) 402 | { 403 | $input = substr($input, 3); 404 | } 405 | elseif (strpos($input, './') === 0) 406 | { 407 | $input = substr($input, 2); 408 | } 409 | // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, 410 | elseif (strpos($input, '/./') === 0) 411 | { 412 | $input = substr($input, 2); 413 | } 414 | elseif ($input === '/.') 415 | { 416 | $input = '/'; 417 | } 418 | // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, 419 | elseif (strpos($input, '/../') === 0) 420 | { 421 | $input = substr($input, 3); 422 | $output = substr_replace($output, '', strrpos($output, '/')); 423 | } 424 | elseif ($input === '/..') 425 | { 426 | $input = '/'; 427 | $output = substr_replace($output, '', strrpos($output, '/')); 428 | } 429 | // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, 430 | elseif ($input === '.' || $input === '..') 431 | { 432 | $input = ''; 433 | } 434 | // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer 435 | elseif (($pos = strpos($input, '/', 1)) !== false) 436 | { 437 | $output .= substr($input, 0, $pos); 438 | $input = substr_replace($input, '', 0, $pos); 439 | } 440 | else 441 | { 442 | $output .= $input; 443 | $input = ''; 444 | } 445 | } 446 | return $output . $input; 447 | } 448 | 449 | /** 450 | * Replace invalid character with percent encoding 451 | * 452 | * @param string $string Input string 453 | * @param string $extra_chars Valid characters not in iunreserved or 454 | * iprivate (this is ASCII-only) 455 | * @param bool $iprivate Allow iprivate 456 | * @return string 457 | */ 458 | private function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false) 459 | { 460 | // Normalize as many pct-encoded sections as possible 461 | $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array(&$this, 'remove_iunreserved_percent_encoded'), $string); 462 | 463 | // Replace invalid percent characters 464 | $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string); 465 | 466 | // Add unreserved and % to $extra_chars (the latter is safe because all 467 | // pct-encoded sections are now valid). 468 | $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; 469 | 470 | // Now replace any bytes that aren't allowed with their pct-encoded versions 471 | $position = 0; 472 | $strlen = strlen($string); 473 | while (($position += strspn($string, $extra_chars, $position)) < $strlen) 474 | { 475 | $value = ord($string[$position]); 476 | 477 | // Start position 478 | $start = $position; 479 | 480 | // By default we are valid 481 | $valid = true; 482 | 483 | // No one byte sequences are valid due to the while. 484 | // Two byte sequence: 485 | if (($value & 0xE0) === 0xC0) 486 | { 487 | $character = ($value & 0x1F) << 6; 488 | $length = 2; 489 | $remaining = 1; 490 | } 491 | // Three byte sequence: 492 | elseif (($value & 0xF0) === 0xE0) 493 | { 494 | $character = ($value & 0x0F) << 12; 495 | $length = 3; 496 | $remaining = 2; 497 | } 498 | // Four byte sequence: 499 | elseif (($value & 0xF8) === 0xF0) 500 | { 501 | $character = ($value & 0x07) << 18; 502 | $length = 4; 503 | $remaining = 3; 504 | } 505 | // Invalid byte: 506 | else 507 | { 508 | $valid = false; 509 | $length = 1; 510 | $remaining = 0; 511 | } 512 | 513 | if ($remaining) 514 | { 515 | if ($position + $length <= $strlen) 516 | { 517 | for ($position++; $remaining; $position++) 518 | { 519 | $value = ord($string[$position]); 520 | 521 | // Check that the byte is valid, then add it to the character: 522 | if (($value & 0xC0) === 0x80) 523 | { 524 | $character |= ($value & 0x3F) << (--$remaining * 6); 525 | } 526 | // If it is invalid, count the sequence as invalid and reprocess the current byte: 527 | else 528 | { 529 | $valid = false; 530 | $position--; 531 | break; 532 | } 533 | } 534 | } 535 | else 536 | { 537 | $position = $strlen - 1; 538 | $valid = false; 539 | } 540 | } 541 | 542 | // Percent encode anything invalid or not in ucschar 543 | if ( 544 | // Invalid sequences 545 | !$valid 546 | // Non-shortest form sequences are invalid 547 | || $length > 1 && $character <= 0x7F 548 | || $length > 2 && $character <= 0x7FF 549 | || $length > 3 && $character <= 0xFFFF 550 | // Outside of range of ucschar codepoints 551 | // Noncharacters 552 | || ($character & 0xFFFE) === 0xFFFE 553 | || $character >= 0xFDD0 && $character <= 0xFDEF 554 | || ( 555 | // Everything else not in ucschar 556 | $character > 0xD7FF && $character < 0xF900 557 | || $character < 0xA0 558 | || $character > 0xEFFFD 559 | ) 560 | && ( 561 | // Everything not in iprivate, if it applies 562 | !$iprivate 563 | || $character < 0xE000 564 | || $character > 0x10FFFD 565 | ) 566 | ) 567 | { 568 | // If we were a character, pretend we weren't, but rather an error. 569 | if ($valid) 570 | $position--; 571 | 572 | for ($j = $start; $j <= $position; $j++) 573 | { 574 | $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1); 575 | $j += 2; 576 | $position += 2; 577 | $strlen += 2; 578 | } 579 | } 580 | } 581 | 582 | return $string; 583 | } 584 | 585 | /** 586 | * Callback function for preg_replace_callback. 587 | * 588 | * Removes sequences of percent encoded bytes that represent UTF-8 589 | * encoded characters in iunreserved 590 | * 591 | * @param array $match PCRE match 592 | * @return string Replacement 593 | */ 594 | private function remove_iunreserved_percent_encoded($match) 595 | { 596 | // As we just have valid percent encoded sequences we can just explode 597 | // and ignore the first member of the returned array (an empty string). 598 | $bytes = explode('%', $match[0]); 599 | 600 | // Initialize the new string (this is what will be returned) and that 601 | // there are no bytes remaining in the current sequence (unsurprising 602 | // at the first byte!). 603 | $string = ''; 604 | $remaining = 0; 605 | 606 | // Loop over each and every byte, and set $value to its value 607 | for ($i = 1, $len = count($bytes); $i < $len; $i++) 608 | { 609 | $value = hexdec($bytes[$i]); 610 | 611 | // If we're the first byte of sequence: 612 | if (!$remaining) 613 | { 614 | // Start position 615 | $start = $i; 616 | 617 | // By default we are valid 618 | $valid = true; 619 | 620 | // One byte sequence: 621 | if ($value <= 0x7F) 622 | { 623 | $character = $value; 624 | $length = 1; 625 | } 626 | // Two byte sequence: 627 | elseif (($value & 0xE0) === 0xC0) 628 | { 629 | $character = ($value & 0x1F) << 6; 630 | $length = 2; 631 | $remaining = 1; 632 | } 633 | // Three byte sequence: 634 | elseif (($value & 0xF0) === 0xE0) 635 | { 636 | $character = ($value & 0x0F) << 12; 637 | $length = 3; 638 | $remaining = 2; 639 | } 640 | // Four byte sequence: 641 | elseif (($value & 0xF8) === 0xF0) 642 | { 643 | $character = ($value & 0x07) << 18; 644 | $length = 4; 645 | $remaining = 3; 646 | } 647 | // Invalid byte: 648 | else 649 | { 650 | $valid = false; 651 | $remaining = 0; 652 | } 653 | } 654 | // Continuation byte: 655 | else 656 | { 657 | // Check that the byte is valid, then add it to the character: 658 | if (($value & 0xC0) === 0x80) 659 | { 660 | $remaining--; 661 | $character |= ($value & 0x3F) << ($remaining * 6); 662 | } 663 | // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: 664 | else 665 | { 666 | $valid = false; 667 | $remaining = 0; 668 | $i--; 669 | } 670 | } 671 | 672 | // If we've reached the end of the current byte sequence, append it to Unicode::$data 673 | if (!$remaining) 674 | { 675 | // Percent encode anything invalid or not in iunreserved 676 | if ( 677 | // Invalid sequences 678 | !$valid 679 | // Non-shortest form sequences are invalid 680 | || $length > 1 && $character <= 0x7F 681 | || $length > 2 && $character <= 0x7FF 682 | || $length > 3 && $character <= 0xFFFF 683 | // Outside of range of iunreserved codepoints 684 | || $character < 0x2D 685 | || $character > 0xEFFFD 686 | // Noncharacters 687 | || ($character & 0xFFFE) === 0xFFFE 688 | || $character >= 0xFDD0 && $character <= 0xFDEF 689 | // Everything else not in iunreserved (this is all BMP) 690 | || $character === 0x2F 691 | || $character > 0x39 && $character < 0x41 692 | || $character > 0x5A && $character < 0x61 693 | || $character > 0x7A && $character < 0x7E 694 | || $character > 0x7E && $character < 0xA0 695 | || $character > 0xD7FF && $character < 0xF900 696 | ) 697 | { 698 | for ($j = $start; $j <= $i; $j++) 699 | { 700 | $string .= '%' . strtoupper($bytes[$j]); 701 | } 702 | } 703 | else 704 | { 705 | for ($j = $start; $j <= $i; $j++) 706 | { 707 | $string .= chr(hexdec($bytes[$j])); 708 | } 709 | } 710 | } 711 | } 712 | 713 | // If we have any bytes left over they are invalid (i.e., we are 714 | // mid-way through a multi-byte sequence) 715 | if ($remaining) 716 | { 717 | for ($j = $start; $j < $len; $j++) 718 | { 719 | $string .= '%' . strtoupper($bytes[$j]); 720 | } 721 | } 722 | 723 | return $string; 724 | } 725 | 726 | private function scheme_normalization() 727 | { 728 | if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) 729 | { 730 | $this->iuserinfo = null; 731 | } 732 | if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) 733 | { 734 | $this->ihost = null; 735 | } 736 | if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) 737 | { 738 | $this->port = null; 739 | } 740 | if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) 741 | { 742 | $this->ipath = ''; 743 | } 744 | if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) 745 | { 746 | $this->iquery = null; 747 | } 748 | if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) 749 | { 750 | $this->ifragment = null; 751 | } 752 | } 753 | 754 | /** 755 | * Check if the object represents a valid IRI. This needs to be done on each 756 | * call as some things change depending on another part of the IRI. 757 | * 758 | * @return bool 759 | */ 760 | public function is_valid() 761 | { 762 | $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; 763 | if ($this->ipath !== '' && 764 | ( 765 | $isauthority && ( 766 | $this->ipath[0] !== '/' || 767 | substr($this->ipath, 0, 2) === '//' 768 | ) || 769 | ( 770 | $this->scheme === null && 771 | !$isauthority && 772 | strpos($this->ipath, ':') !== false && 773 | (strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/')) 774 | ) 775 | ) 776 | ) 777 | { 778 | return false; 779 | } 780 | 781 | return true; 782 | } 783 | 784 | /** 785 | * Set the entire IRI. Returns true on success, false on failure (if there 786 | * are any invalid characters). 787 | * 788 | * @param string $iri 789 | * @return bool 790 | */ 791 | private function set_iri($iri) 792 | { 793 | static $cache; 794 | if (!$cache) 795 | { 796 | $cache = array(); 797 | } 798 | 799 | if ($iri === null) 800 | { 801 | return true; 802 | } 803 | elseif (isset($cache[$iri])) 804 | { 805 | list($this->scheme, 806 | $this->iuserinfo, 807 | $this->ihost, 808 | $this->port, 809 | $this->ipath, 810 | $this->iquery, 811 | $this->ifragment, 812 | $return) = $cache[$iri]; 813 | return $return; 814 | } 815 | else 816 | { 817 | $parsed = $this->parse_iri((string) $iri); 818 | 819 | $return = $this->set_scheme($parsed['scheme']) 820 | && $this->set_authority($parsed['authority']) 821 | && $this->set_path($parsed['path']) 822 | && $this->set_query($parsed['query']) 823 | && $this->set_fragment($parsed['fragment']); 824 | 825 | $cache[$iri] = array($this->scheme, 826 | $this->iuserinfo, 827 | $this->ihost, 828 | $this->port, 829 | $this->ipath, 830 | $this->iquery, 831 | $this->ifragment, 832 | $return); 833 | return $return; 834 | } 835 | } 836 | 837 | /** 838 | * Set the scheme. Returns true on success, false on failure (if there are 839 | * any invalid characters). 840 | * 841 | * @param string $scheme 842 | * @return bool 843 | */ 844 | private function set_scheme($scheme) 845 | { 846 | if ($scheme === null) 847 | { 848 | $this->scheme = null; 849 | } 850 | elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) 851 | { 852 | $this->scheme = null; 853 | return false; 854 | } 855 | else 856 | { 857 | $this->scheme = strtolower($scheme); 858 | } 859 | return true; 860 | } 861 | 862 | /** 863 | * Set the authority. Returns true on success, false on failure (if there are 864 | * any invalid characters). 865 | * 866 | * @param string $authority 867 | * @return bool 868 | */ 869 | private function set_authority($authority) 870 | { 871 | static $cache; 872 | if (!$cache) 873 | $cache = array(); 874 | 875 | if ($authority === null) 876 | { 877 | $this->iuserinfo = null; 878 | $this->ihost = null; 879 | $this->port = null; 880 | return true; 881 | } 882 | elseif (isset($cache[$authority])) 883 | { 884 | list($this->iuserinfo, 885 | $this->ihost, 886 | $this->port, 887 | $return) = $cache[$authority]; 888 | 889 | return $return; 890 | } 891 | else 892 | { 893 | $remaining = $authority; 894 | if (($iuserinfo_end = strrpos($remaining, '@')) !== false) 895 | { 896 | $iuserinfo = substr($remaining, 0, $iuserinfo_end); 897 | $remaining = substr($remaining, $iuserinfo_end + 1); 898 | } 899 | else 900 | { 901 | $iuserinfo = null; 902 | } 903 | if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) 904 | { 905 | if (($port = substr($remaining, $port_start + 1)) === false) 906 | { 907 | $port = null; 908 | } 909 | $remaining = substr($remaining, 0, $port_start); 910 | } 911 | else 912 | { 913 | $port = null; 914 | } 915 | 916 | $return = $this->set_userinfo($iuserinfo) && 917 | $this->set_host($remaining) && 918 | $this->set_port($port); 919 | 920 | $cache[$authority] = array($this->iuserinfo, 921 | $this->ihost, 922 | $this->port, 923 | $return); 924 | 925 | return $return; 926 | } 927 | } 928 | 929 | /** 930 | * Set the iuserinfo. 931 | * 932 | * @param string $iuserinfo 933 | * @return bool 934 | */ 935 | private function set_userinfo($iuserinfo) 936 | { 937 | if ($iuserinfo === null) 938 | { 939 | $this->iuserinfo = null; 940 | } 941 | else 942 | { 943 | $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); 944 | $this->scheme_normalization(); 945 | } 946 | 947 | return true; 948 | } 949 | 950 | /** 951 | * Set the ihost. Returns true on success, false on failure (if there are 952 | * any invalid characters). 953 | * 954 | * @param string $ihost 955 | * @return bool 956 | */ 957 | private function set_host($ihost) 958 | { 959 | if ($ihost === null) 960 | { 961 | $this->ihost = null; 962 | return true; 963 | } 964 | elseif (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') 965 | { 966 | if (Requests_IPv6::check_ipv6(substr($ihost, 1, -1))) 967 | { 968 | $this->ihost = '[' . Requests_IPv6::compress(substr($ihost, 1, -1)) . ']'; 969 | } 970 | else 971 | { 972 | $this->ihost = null; 973 | return false; 974 | } 975 | } 976 | else 977 | { 978 | $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); 979 | 980 | // Lowercase, but ignore pct-encoded sections (as they should 981 | // remain uppercase). This must be done after the previous step 982 | // as that can add unescaped characters. 983 | $position = 0; 984 | $strlen = strlen($ihost); 985 | while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) 986 | { 987 | if ($ihost[$position] === '%') 988 | { 989 | $position += 3; 990 | } 991 | else 992 | { 993 | $ihost[$position] = strtolower($ihost[$position]); 994 | $position++; 995 | } 996 | } 997 | 998 | $this->ihost = $ihost; 999 | } 1000 | 1001 | $this->scheme_normalization(); 1002 | 1003 | return true; 1004 | } 1005 | 1006 | /** 1007 | * Set the port. Returns true on success, false on failure (if there are 1008 | * any invalid characters). 1009 | * 1010 | * @param string $port 1011 | * @return bool 1012 | */ 1013 | private function set_port($port) 1014 | { 1015 | if ($port === null) 1016 | { 1017 | $this->port = null; 1018 | return true; 1019 | } 1020 | elseif (strspn($port, '0123456789') === strlen($port)) 1021 | { 1022 | $this->port = (int) $port; 1023 | $this->scheme_normalization(); 1024 | return true; 1025 | } 1026 | else 1027 | { 1028 | $this->port = null; 1029 | return false; 1030 | } 1031 | } 1032 | 1033 | /** 1034 | * Set the ipath. 1035 | * 1036 | * @param string $ipath 1037 | * @return bool 1038 | */ 1039 | private function set_path($ipath) 1040 | { 1041 | static $cache; 1042 | if (!$cache) 1043 | { 1044 | $cache = array(); 1045 | } 1046 | 1047 | $ipath = (string) $ipath; 1048 | 1049 | if (isset($cache[$ipath])) 1050 | { 1051 | $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; 1052 | } 1053 | else 1054 | { 1055 | $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); 1056 | $removed = $this->remove_dot_segments($valid); 1057 | 1058 | $cache[$ipath] = array($valid, $removed); 1059 | $this->ipath = ($this->scheme !== null) ? $removed : $valid; 1060 | } 1061 | 1062 | $this->scheme_normalization(); 1063 | return true; 1064 | } 1065 | 1066 | /** 1067 | * Set the iquery. 1068 | * 1069 | * @param string $iquery 1070 | * @return bool 1071 | */ 1072 | private function set_query($iquery) 1073 | { 1074 | if ($iquery === null) 1075 | { 1076 | $this->iquery = null; 1077 | } 1078 | else 1079 | { 1080 | $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); 1081 | $this->scheme_normalization(); 1082 | } 1083 | return true; 1084 | } 1085 | 1086 | /** 1087 | * Set the ifragment. 1088 | * 1089 | * @param string $ifragment 1090 | * @return bool 1091 | */ 1092 | private function set_fragment($ifragment) 1093 | { 1094 | if ($ifragment === null) 1095 | { 1096 | $this->ifragment = null; 1097 | } 1098 | else 1099 | { 1100 | $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); 1101 | $this->scheme_normalization(); 1102 | } 1103 | return true; 1104 | } 1105 | 1106 | /** 1107 | * Convert an IRI to a URI (or parts thereof) 1108 | * 1109 | * @return string 1110 | */ 1111 | private function to_uri($string) 1112 | { 1113 | static $non_ascii; 1114 | if (!$non_ascii) 1115 | { 1116 | $non_ascii = implode('', range("\x80", "\xFF")); 1117 | } 1118 | 1119 | $position = 0; 1120 | $strlen = strlen($string); 1121 | while (($position += strcspn($string, $non_ascii, $position)) < $strlen) 1122 | { 1123 | $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1); 1124 | $position += 3; 1125 | $strlen += 2; 1126 | } 1127 | 1128 | return $string; 1129 | } 1130 | 1131 | /** 1132 | * Get the complete IRI 1133 | * 1134 | * @return string 1135 | */ 1136 | private function get_iri() 1137 | { 1138 | if (!$this->is_valid()) 1139 | { 1140 | return false; 1141 | } 1142 | 1143 | $iri = ''; 1144 | if ($this->scheme !== null) 1145 | { 1146 | $iri .= $this->scheme . ':'; 1147 | } 1148 | if (($iauthority = $this->get_iauthority()) !== null) 1149 | { 1150 | $iri .= '//' . $iauthority; 1151 | } 1152 | $iri .= $this->ipath; 1153 | if ($this->iquery !== null) 1154 | { 1155 | $iri .= '?' . $this->iquery; 1156 | } 1157 | if ($this->ifragment !== null) 1158 | { 1159 | $iri .= '#' . $this->ifragment; 1160 | } 1161 | 1162 | return $iri; 1163 | } 1164 | 1165 | /** 1166 | * Get the complete URI 1167 | * 1168 | * @return string 1169 | */ 1170 | private function get_uri() 1171 | { 1172 | return $this->to_uri($this->get_iri()); 1173 | } 1174 | 1175 | /** 1176 | * Get the complete iauthority 1177 | * 1178 | * @return string 1179 | */ 1180 | private function get_iauthority() 1181 | { 1182 | if ($this->iuserinfo !== null || $this->ihost !== null || $this->port !== null) 1183 | { 1184 | $iauthority = ''; 1185 | if ($this->iuserinfo !== null) 1186 | { 1187 | $iauthority .= $this->iuserinfo . '@'; 1188 | } 1189 | if ($this->ihost !== null) 1190 | { 1191 | $iauthority .= $this->ihost; 1192 | } 1193 | if ($this->port !== null) 1194 | { 1195 | $iauthority .= ':' . $this->port; 1196 | } 1197 | return $iauthority; 1198 | } 1199 | else 1200 | { 1201 | return null; 1202 | } 1203 | } 1204 | 1205 | /** 1206 | * Get the complete authority 1207 | * 1208 | * @return string 1209 | */ 1210 | private function get_authority() 1211 | { 1212 | $iauthority = $this->get_iauthority(); 1213 | if (is_string($iauthority)) 1214 | return $this->to_uri($iauthority); 1215 | else 1216 | return $iauthority; 1217 | } 1218 | } 1219 | -------------------------------------------------------------------------------- /requests/Requests/Response.php: -------------------------------------------------------------------------------- 1 | headers = new Requests_Response_Headers(); 21 | } 22 | 23 | /** 24 | * Response body 25 | * @var string 26 | */ 27 | public $body = ''; 28 | 29 | /** 30 | * Raw HTTP data from the transport 31 | * @var string 32 | */ 33 | public $raw = ''; 34 | 35 | /** 36 | * Headers, as an associative array 37 | * @var array 38 | */ 39 | public $headers = array(); 40 | 41 | /** 42 | * Status code, false if non-blocking 43 | * @var integer|boolean 44 | */ 45 | public $status_code = false; 46 | 47 | /** 48 | * Whether the request succeeded or not 49 | * @var boolean 50 | */ 51 | public $success = false; 52 | 53 | /** 54 | * Number of redirects the request used 55 | * @var integer 56 | */ 57 | public $redirects = 0; 58 | 59 | /** 60 | * URL requested 61 | * @var string 62 | */ 63 | public $url = ''; 64 | 65 | /** 66 | * Previous requests (from redirects) 67 | * @var array Array of Requests_Response objects 68 | */ 69 | public $history = array(); 70 | 71 | /** 72 | * Throws an exception if the request was not successful 73 | * 74 | * @throws Requests_Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`) 75 | * @throws Requests_Exception_HTTP On non-successful status code. Exception class corresponds to code (e.g. {@see Requests_Exception_HTTP_404}) 76 | * @param boolean $allow_redirects Set to false to throw on a 3xx as well 77 | */ 78 | public function throw_for_status($allow_redirects = true) { 79 | if ($this->status_code >= 300 && $this->status_code < 400) { 80 | if (!$allow_redirects) { 81 | throw new Requests_Exception('Redirection not allowed', 'response.no_redirects', $this); 82 | } 83 | } 84 | 85 | elseif (!$this->success) { 86 | $exception = Requests_Exception_HTTP::get_class($this->status_code); 87 | throw new $exception(null, $this); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /requests/Requests/Response/Headers.php: -------------------------------------------------------------------------------- 1 | data[$key]); 30 | } 31 | 32 | /** 33 | * Get the given header 34 | * 35 | * @param string $key 36 | * @return string Header value 37 | */ 38 | public function offsetGet($key) { 39 | $key = strtolower($key); 40 | return isset($this->data[$key]) ? $this->data[$key] : null; 41 | } 42 | 43 | /** 44 | * Set the given header 45 | * 46 | * @throws Requests_Exception On attempting to use headers dictionary as list (`invalidset`) 47 | * 48 | * @param string $key Header name 49 | * @param string $value Header value 50 | */ 51 | public function offsetSet($key, $value) { 52 | if ($key === null) { 53 | throw new Requests_Exception('Headers is a dictionary, not a list', 'invalidset'); 54 | } 55 | 56 | $key = strtolower($key); 57 | 58 | if (isset($this->data[$key])) { 59 | // RFC2616 notes that multiple headers must be able to 60 | // be combined like this. We should use a smarter way though (arrays 61 | // internally, e.g.) 62 | $value = $this->data[$key] . ',' . $value; 63 | } 64 | 65 | $this->data[$key] = $value; 66 | } 67 | 68 | /** 69 | * Unset the given header 70 | * 71 | * @param string $key 72 | */ 73 | public function offsetUnset($key) { 74 | unset($this->data[strtolower($key)]); 75 | } 76 | 77 | /** 78 | * Get an interator for the data 79 | * 80 | * @return ArrayIterator 81 | */ 82 | public function getIterator() { 83 | return new ArrayIterator($this->data); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /requests/Requests/Transport.php: -------------------------------------------------------------------------------- 1 | version = $curl['version']; 57 | $this->fp = curl_init(); 58 | 59 | curl_setopt($this->fp, CURLOPT_HEADER, false); 60 | curl_setopt($this->fp, CURLOPT_RETURNTRANSFER, 1); 61 | if (version_compare($this->version, '7.10.5', '>=')) { 62 | curl_setopt($this->fp, CURLOPT_ENCODING, ''); 63 | } 64 | curl_setopt ($this->fp, CURLOPT_SSL_VERIFYHOST, 0); 65 | curl_setopt ($this->fp, CURLOPT_SSL_VERIFYPEER, 0); 66 | } 67 | 68 | /** 69 | * Perform a request 70 | * 71 | * @throws Requests_Exception On a cURL error (`curlerror`) 72 | * 73 | * @param string $url URL to request 74 | * @param array $headers Associative array of request headers 75 | * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD 76 | * @param array $options Request options, see {@see Requests::response()} for documentation 77 | * @return string Raw HTTP result 78 | */ 79 | public function request($url, $headers = array(), $data = array(), $options = array()) { 80 | $options['hooks']->dispatch('curl.before_request', array(&$this->fp)); 81 | 82 | $headers = Requests::flattern($headers); 83 | if (in_array($options['type'], array(Requests::HEAD, Requests::GET, Requests::DELETE)) & !empty($data)) { 84 | $url = self::format_get($url, $data); 85 | } 86 | 87 | switch ($options['type']) { 88 | case Requests::POST: 89 | curl_setopt($this->fp, CURLOPT_POST, true); 90 | curl_setopt($this->fp, CURLOPT_POSTFIELDS, $data); 91 | break; 92 | case Requests::PATCH: 93 | case Requests::PUT: 94 | curl_setopt($this->fp, CURLOPT_CUSTOMREQUEST, $options['type']); 95 | curl_setopt($this->fp, CURLOPT_POSTFIELDS, $data); 96 | break; 97 | case Requests::DELETE: 98 | curl_setopt($this->fp, CURLOPT_CUSTOMREQUEST, 'DELETE'); 99 | break; 100 | case Requests::HEAD: 101 | curl_setopt($this->fp, CURLOPT_NOBODY, true); 102 | break; 103 | } 104 | 105 | curl_setopt($this->fp, CURLOPT_URL, $url); 106 | curl_setopt($this->fp, CURLOPT_TIMEOUT, $options['timeout']); 107 | curl_setopt($this->fp, CURLOPT_CONNECTTIMEOUT, $options['timeout']); 108 | curl_setopt($this->fp, CURLOPT_REFERER, $url); 109 | curl_setopt($this->fp, CURLOPT_USERAGENT, $options['useragent']); 110 | curl_setopt($this->fp, CURLOPT_HTTPHEADER, $headers); 111 | 112 | if (true === $options['blocking']) { 113 | curl_setopt($this->fp, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers')); 114 | } 115 | 116 | $options['hooks']->dispatch('curl.before_send', array(&$this->fp)); 117 | 118 | if ($options['filename'] !== false) { 119 | $stream_handle = fopen($options['filename'], 'wb'); 120 | curl_setopt($this->fp, CURLOPT_FILE, $stream_handle); 121 | } 122 | 123 | $response = curl_exec($this->fp); 124 | 125 | $options['hooks']->dispatch('curl.after_send', array(&$fake_headers)); 126 | 127 | if ($options['blocking'] === false) { 128 | curl_close($this->fp); 129 | $fake_headers = ''; 130 | $options['hooks']->dispatch('curl.after_request', array(&$fake_headers)); 131 | return false; 132 | } 133 | if ($options['filename'] !== false) { 134 | fclose($stream_handle); 135 | $this->headers = trim($this->headers); 136 | } 137 | else { 138 | $this->headers .= $response; 139 | } 140 | 141 | if (curl_errno($this->fp) === 23 || curl_errno($this->fp) === 61) { 142 | curl_setopt($this->fp, CURLOPT_ENCODING, 'none'); 143 | $this->headers = curl_exec($this->fp); 144 | } 145 | if (curl_errno($this->fp)) { 146 | throw new Requests_Exception('cURL error ' . curl_errno($this->fp) . ': ' . curl_error($this->fp), 'curlerror', $this->fp); 147 | return; 148 | } 149 | $this->info = curl_getinfo($this->fp); 150 | curl_close($this->fp); 151 | 152 | $options['hooks']->dispatch('curl.after_request', array(&$this->headers)); 153 | return $this->headers; 154 | } 155 | 156 | /** 157 | * Collect the headers as they are received 158 | * 159 | * @param resource $handle cURL resource 160 | * @param string $headers Header string 161 | * @return integer Length of provided header 162 | */ 163 | protected function stream_headers($handle, $headers) { 164 | // Why do we do this? cURL will send both the final response and any 165 | // interim responses, such as a 100 Continue. We don't need that. 166 | // (We may want to keep this somewhere just in case) 167 | if ($this->done_headers) { 168 | $this->headers = ''; 169 | $this->done_headers = false; 170 | } 171 | $this->headers .= $headers; 172 | 173 | if ($headers === "\r\n") { 174 | $this->done_headers = true; 175 | } 176 | return strlen($headers); 177 | } 178 | 179 | /** 180 | * Format a URL given GET data 181 | * 182 | * @param string $url 183 | * @param array|object $data Data to build query using, see {@see http://php.net/http_build_query} 184 | * @return string URL with data 185 | */ 186 | protected static function format_get($url, $data) { 187 | if (!empty($data)) { 188 | $url_parts = parse_url($url); 189 | if (empty($url_parts['query'])) { 190 | $query = $url_parts['query'] = ''; 191 | } 192 | else { 193 | $query = $url_parts['query']; 194 | } 195 | 196 | $query .= '&' . http_build_query($data, null, '&'); 197 | $query = trim($query, '&'); 198 | 199 | if (empty($url_parts['query'])) { 200 | $url .= '?' . $query; 201 | } 202 | else { 203 | $url = str_replace($url_parts['query'], $query, $url); 204 | } 205 | } 206 | return $url; 207 | } 208 | 209 | /** 210 | * Whether this transport is valid 211 | * 212 | * @codeCoverageIgnore 213 | * @return boolean True if the transport is valid, false otherwise. 214 | */ 215 | public static function test() { 216 | return (function_exists('curl_init') && function_exists('curl_exec')); 217 | } 218 | } -------------------------------------------------------------------------------- /requests/Requests/Transport/fsockopen.php: -------------------------------------------------------------------------------- 1 | dispatch('fsockopen.before_request'); 44 | 45 | $url_parts = parse_url($url); 46 | $host = $url_parts['host']; 47 | if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { 48 | $host = 'ssl://' . $host; 49 | $url_parts['port'] = 443; 50 | } 51 | if (!isset($url_parts['port'])) { 52 | $url_parts['port'] = 80; 53 | } 54 | $fp = @fsockopen($host, $url_parts['port'], $errno, $errstr, $options['timeout']); 55 | if (!$fp) { 56 | throw new Requests_Exception($errstr, 'fsockopenerror'); 57 | return; 58 | } 59 | 60 | $request_body = ''; 61 | $out = ''; 62 | switch ($options['type']) { 63 | case Requests::POST: 64 | case Requests::PUT: 65 | case Requests::PATCH: 66 | if (isset($url_parts['path'])) { 67 | $path = $url_parts['path']; 68 | if (isset($url_parts['query'])) { 69 | $path .= '?' . $url_parts['query']; 70 | } 71 | } 72 | else { 73 | $path = '/'; 74 | } 75 | $out = $options['type'] . " $path HTTP/1.0\r\n"; 76 | if (is_array($data)) { 77 | $request_body = http_build_query($data, null, '&'); 78 | } 79 | else { 80 | $request_body = $data; 81 | } 82 | if (empty($headers['Content-Length'])) { 83 | $headers['Content-Length'] = strlen($request_body); 84 | } 85 | if (empty($headers['Content-Type'])) { 86 | $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; 87 | } 88 | break; 89 | case Requests::HEAD: 90 | case Requests::GET: 91 | case Requests::DELETE: 92 | $get = self::format_get($url_parts, $data); 93 | $out = $options['type'] . " $get HTTP/1.0\r\n"; 94 | break; 95 | } 96 | $out .= "Host: {$url_parts['host']}\r\n"; 97 | $out .= "User-Agent: {$options['useragent']}\r\n"; 98 | $accept_encoding = $this->accept_encoding(); 99 | if (!empty($accept_encoding)) { 100 | $out .= "Accept-Encoding: $accept_encoding\r\n"; 101 | } 102 | 103 | $headers = Requests::flattern($headers); 104 | $out .= implode($headers, "\r\n"); 105 | 106 | $options['hooks']->dispatch('fsockopen.after_headers', array(&$out)); 107 | 108 | $out .= "\r\nConnection: Close\r\n\r\n" . $request_body; 109 | 110 | $options['hooks']->dispatch('fsockopen.before_send', array(&$out)); 111 | 112 | fwrite($fp, $out); 113 | $options['hooks']->dispatch('fsockopen.after_send', array(&$fake_headers)); 114 | 115 | if (!$options['blocking']) { 116 | fclose($fp); 117 | $fake_headers = ''; 118 | $options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers)); 119 | return ''; 120 | } 121 | stream_set_timeout($fp, $options['timeout']); 122 | 123 | $this->info = stream_get_meta_data($fp); 124 | 125 | $this->headers = ''; 126 | $this->info = stream_get_meta_data($fp); 127 | if (!$options['filename']) { 128 | while (!feof($fp)) { 129 | $this->info = stream_get_meta_data($fp); 130 | if ($this->info['timed_out']) { 131 | throw new Requests_Exception('fsocket timed out', 'timeout'); 132 | } 133 | 134 | $this->headers .= fread($fp, 1160); 135 | } 136 | } 137 | else { 138 | $download = fopen($options['filename'], 'wb'); 139 | $doingbody = false; 140 | $response = ''; 141 | while (!feof($fp)) { 142 | $this->info = stream_get_meta_data($fp); 143 | if ($this->info['timed_out']) { 144 | throw new Requests_Exception('fsocket timed out', 'timeout'); 145 | } 146 | 147 | $block = fread($fp, 1160); 148 | if ($doingbody) { 149 | fwrite($download, $block); 150 | } 151 | else { 152 | $response .= $block; 153 | if (strpos($response, "\r\n\r\n")) { 154 | list($this->headers, $block) = explode("\r\n\r\n", $response, 2); 155 | $doingbody = true; 156 | fwrite($download, $block); 157 | } 158 | } 159 | } 160 | fclose($download); 161 | } 162 | fclose($fp); 163 | 164 | $options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers)); 165 | return $this->headers; 166 | } 167 | 168 | /** 169 | * Retrieve the encodings we can accept 170 | * 171 | * @return string Accept-Encoding header value 172 | */ 173 | protected static function accept_encoding() { 174 | $type = array(); 175 | if (function_exists('gzinflate')) { 176 | $type[] = 'deflate;q=1.0'; 177 | } 178 | 179 | if (function_exists('gzuncompress')) { 180 | $type[] = 'compress;q=0.5'; 181 | } 182 | 183 | $type[] = 'gzip;q=0.5'; 184 | 185 | return implode(', ', $type); 186 | } 187 | 188 | /** 189 | * Format a URL given GET data 190 | * 191 | * @param array $url_parts 192 | * @param array|object $data Data to build query using, see {@see http://php.net/http_build_query} 193 | * @return string URL with data 194 | */ 195 | protected static function format_get($url_parts, $data) { 196 | if (!empty($data)) { 197 | if (empty($url_parts['query'])) 198 | $url_parts['query'] = ''; 199 | 200 | $url_parts['query'] .= '&' . http_build_query($data, null, '&'); 201 | $url_parts['query'] = trim($url_parts['query'], '&'); 202 | } 203 | if (isset($url_parts['path'])) { 204 | if (isset($url_parts['query'])) { 205 | $get = "$url_parts[path]?$url_parts[query]"; 206 | } 207 | else { 208 | $get = $url_parts['path']; 209 | } 210 | } 211 | else { 212 | $get = '/'; 213 | } 214 | return $get; 215 | } 216 | 217 | /** 218 | * Whether this transport is valid 219 | * 220 | * @codeCoverageIgnore 221 | * @return boolean True if the transport is valid, false otherwise. 222 | */ 223 | public static function test() { 224 | return function_exists('fsockopen'); 225 | } 226 | } -------------------------------------------------------------------------------- /settings.php: -------------------------------------------------------------------------------- 1 | read the documentation!"); 19 | 20 | switch ($xml->methodName) { 21 | 22 | //wordpress blog verification 23 | case 'mt.supportedMethods': 24 | success('metaWeblog.getRecentPosts'); 25 | break; 26 | //first authentication request from ifttt 27 | case 'metaWeblog.getRecentPosts': 28 | //send a blank blog response 29 | //this also makes sure that the channel is never triggered 30 | success(''); 31 | break; 32 | 33 | case 'metaWeblog.newPost': 34 | __log("Processing newpost payload"); 35 | 36 | //@see http://codex.wordpress.org/XML-RPC_WordPress_API/Posts#wp.newPost 37 | $obj = new stdClass; 38 | //get the parameters from xml 39 | $obj->user = (string) $xml->params->param[1]->value->string; 40 | $obj->pass = (string) $xml->params->param[2]->value->string; 41 | 42 | //@see content in the wordpress docs 43 | $content = $xml->params->param[3]->value->struct->member; 44 | foreach ($content as $data) { 45 | switch ((string) $data->name) { 46 | //we use the tags field for providing webhook URL 47 | case 'mt_keywords': 48 | $url = $data->xpath('value/array/data/value/string'); 49 | $url = (string) $url[0]; 50 | break; 51 | 52 | //the passed categories are parsed into an array 53 | case 'categories': 54 | $categories = array(); 55 | foreach ($data->xpath('value/array/data/value/string') as $cat) 56 | array_push($categories, (string) $cat); 57 | $obj->categories = $categories; 58 | break; 59 | 60 | //this is used for title/description 61 | default: 62 | $obj->{$data->name} = (string) $data->value->string; 63 | } 64 | } 65 | 66 | // Plugin details 67 | if ($ALLOW_PLUGINS) { 68 | 69 | __log("Plugins are permitted"); 70 | 71 | foreach ($obj->categories as $category) { 72 | if (strpos($category, 'plugin:') !== false) 73 | $__PLUGIN = $category; 74 | } 75 | 76 | // If we allow plugins, pass the constructed object to 77 | if ($__PLUGIN) { 78 | $processed = executePlugin($__PLUGIN, $obj, $content); 79 | if ($processed) 80 | $obj = $processed; 81 | else 82 | { 83 | __log("Plugin was invalid"); 84 | failure(400); 85 | } 86 | } 87 | else 88 | { 89 | __log("No valid plugin specified"); 90 | failure(400); 91 | } 92 | } 93 | 94 | //Make the webrequest 95 | //Only if we have a valid url 96 | if ($url) 97 | { 98 | if (valid_url($url, true)) { 99 | // Load Requests Library 100 | include('requests/Requests.php'); 101 | Requests::register_autoloader(); 102 | 103 | $headers = array('Content-Type' => 'application/json'); 104 | $response = Requests::post($url, $headers, json_encode($obj)); 105 | 106 | if ($response->success) 107 | success('' . $response->status_code . ''); 108 | else 109 | failure($response->status_code); 110 | } 111 | else { 112 | //since the url was invalid, we return 400 (Bad Request) 113 | failure(400); 114 | } 115 | } else 116 | success('No forward url, but will assume data was handled locally'); 117 | } 118 | 119 | /** Copied from wordpress */ 120 | function success($innerXML) { 121 | 122 | __log("Success!"); 123 | 124 | $xml = << 126 | 127 | 128 | 129 | 130 | $innerXML 131 | 132 | 133 | 134 | 135 | 136 | EOD; 137 | output($xml); 138 | } 139 | 140 | function output($xml) { 141 | $length = strlen($xml); 142 | header('Connection: close'); 143 | header('Content-Length: ' . $length); 144 | header('Content-Type: text/xml'); 145 | header('Date: ' . date('r')); 146 | echo $xml; 147 | exit; 148 | } 149 | 150 | function failure($status) { 151 | 152 | __log("Failure: $status", 'ERROR'); 153 | 154 | $xml = << 156 | 157 | 158 | 159 | 160 | 161 | faultCode 162 | $status 163 | 164 | 165 | faultString 166 | Request was not successful. 167 | 168 | 169 | 170 | 171 | 172 | 173 | EOD; 174 | output($xml); 175 | } 176 | 177 | /** Used from drupal */ 178 | function valid_url($url, $absolute = FALSE) { 179 | if ($absolute) { 180 | return (bool) preg_match(" 181 | /^ # Start at the beginning of the text 182 | (?:https?):\/\/ # Look for ftp, http, https or feed schemes 183 | (?: # Userinfo (optional) which is typically 184 | (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password 185 | (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination 186 | )? 187 | (?: 188 | (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address 189 | |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address 190 | ) 191 | (?::[0-9]+)? # Server port number (optional) 192 | (?:[\/|\?] 193 | (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional) 194 | *)? 195 | $/xi", $url); 196 | } else { 197 | return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url); 198 | } 199 | } 200 | --------------------------------------------------------------------------------