public function test_format_postdata_for_curlcall() { // POST params with just simple types. $postdatatoconvert = array('userid' => 1, 'roleid' => 22, 'name' => 'john'); $expectedresult = "userid=1&roleid=22&name=john"; $postdata = format_postdata_for_curlcall($postdatatoconvert); $this->assertEquals($expectedresult, $postdata); // POST params with a string containing & character. $postdatatoconvert = array('name' => 'john&emilie', 'roleid' => 22); $expectedresult = "name=john%26emilie&roleid=22"; // Urlencode: '%26' => '&'. $postdata = format_postdata_for_curlcall($postdatatoconvert); $this->assertEquals($expectedresult, $postdata); // POST params with an empty value. $postdatatoconvert = array('name' => null, 'roleid' => 22); $expectedresult = "name=&roleid=22"; $postdata = format_postdata_for_curlcall($postdatatoconvert); $this->assertEquals($expectedresult, $postdata); // POST params with complex types. $postdatatoconvert = array('users' => array(array('id' => 2, 'customfields' => array(array('type' => 'Color', 'value' => 'violet'))))); $expectedresult = "users[0][id]=2&users[0][customfields][0][type]=Color&users[0][customfields][0][value]=violet"; $postdata = format_postdata_for_curlcall($postdatatoconvert); $this->assertEquals($expectedresult, $postdata); // POST params with other complex types. $postdatatoconvert = array('members' => array(array('groupid' => 1, 'userid' => 1), array('groupid' => 1, 'userid' => 2))); $expectedresult = "members[0][groupid]=1&members[0][userid]=1&members[1][groupid]=1&members[1][userid]=2"; $postdata = format_postdata_for_curlcall($postdatatoconvert); $this->assertEquals($expectedresult, $postdata); }
/** * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present. * Due to security concerns only downloads from http(s) sources are supported. * * @category files * @param string $url file url starting with http(s):// * @param array $headers http headers, null if none. If set, should be an * associative array of header name => value pairs. * @param array $postdata array means use POST request with given parameters * @param bool $fullresponse return headers, responses, etc in a similar way snoopy does * (if false, just returns content) * @param int $timeout timeout for complete download process including all file transfer * (default 5 minutes) * @param int $connecttimeout timeout for connection to server; this is the timeout that * usually happens if the remote server is completely down (default 20 seconds); * may not work when using proxy * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked. * Only use this when already in a trusted location. * @param string $tofile store the downloaded content to file instead of returning it. * @param bool $calctimeout false by default, true enables an extra head request to try and determine * filesize and appropriately larger timeout based on $CFG->curltimeoutkbitrate * @return stdClass|string|bool stdClass object if $fullresponse is true, false if request failed, true * if file downloaded into $tofile successfully or the file content as a string. */ function download_file_content($url, $headers = null, $postdata = null, $fullresponse = false, $timeout = 300, $connecttimeout = 20, $skipcertverify = false, $tofile = NULL, $calctimeout = false) { global $CFG; // Only http and https links supported. if (!preg_match('|^https?://|i', $url)) { if ($fullresponse) { $response = new stdClass(); $response->status = 0; $response->headers = array(); $response->response_code = 'Invalid protocol specified in url'; $response->results = ''; $response->error = 'Invalid protocol specified in url'; return $response; } else { return false; } } $options = array(); $headers2 = array(); if (is_array($headers)) { foreach ($headers as $key => $value) { if (is_numeric($key)) { $headers2[] = $value; } else { $headers2[] = "{$key}: {$value}"; } } } if ($skipcertverify) { $options['CURLOPT_SSL_VERIFYPEER'] = false; } else { $options['CURLOPT_SSL_VERIFYPEER'] = true; } $options['CURLOPT_CONNECTTIMEOUT'] = $connecttimeout; $options['CURLOPT_FOLLOWLOCATION'] = 1; $options['CURLOPT_MAXREDIRS'] = 5; // Use POST if requested. if (is_array($postdata)) { $postdata = format_postdata_for_curlcall($postdata); } else { if (empty($postdata)) { $postdata = null; } } // Optionally attempt to get more correct timeout by fetching the file size. if (!isset($CFG->curltimeoutkbitrate)) { // Use very slow rate of 56kbps as a timeout speed when not set. $bitrate = 56; } else { $bitrate = $CFG->curltimeoutkbitrate; } if ($calctimeout and !isset($postdata)) { $curl = new curl(); $curl->setHeader($headers2); $curl->head($url, $postdata, $options); $info = $curl->get_info(); $error_no = $curl->get_errno(); if (!$error_no && $info['download_content_length'] > 0) { // No curl errors - adjust for large files only - take max timeout. $timeout = max($timeout, ceil($info['download_content_length'] * 8 / ($bitrate * 1024))); } } $curl = new curl(); $curl->setHeader($headers2); $options['CURLOPT_RETURNTRANSFER'] = true; $options['CURLOPT_NOBODY'] = false; $options['CURLOPT_TIMEOUT'] = $timeout; if ($tofile) { $fh = fopen($tofile, 'w'); if (!$fh) { if ($fullresponse) { $response = new stdClass(); $response->status = 0; $response->headers = array(); $response->response_code = 'Can not write to file'; $response->results = false; $response->error = 'Can not write to file'; return $response; } else { return false; } } $options['CURLOPT_FILE'] = $fh; } if (isset($postdata)) { $content = $curl->post($url, $postdata, $options); } else { $content = $curl->get($url, null, $options); } if ($tofile) { fclose($fh); @chmod($tofile, $CFG->filepermissions); } /* // Try to detect encoding problems. if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) { curl_setopt($ch, CURLOPT_ENCODING, 'none'); $result = curl_exec($ch); } */ $info = $curl->get_info(); $error_no = $curl->get_errno(); $rawheaders = $curl->get_raw_response(); if ($error_no) { $error = $content; if (!$fullresponse) { debugging("cURL request for \"{$url}\" failed with: {$error} ({$error_no})", DEBUG_ALL); return false; } $response = new stdClass(); if ($error_no == 28) { $response->status = '-100'; // Mimic snoopy. } else { $response->status = '0'; } $response->headers = array(); $response->response_code = $error; $response->results = false; $response->error = $error; return $response; } if ($tofile) { $content = true; } if (empty($info['http_code'])) { // For security reasons we support only true http connections (Location: file:// exploit prevention). $response = new stdClass(); $response->status = '0'; $response->headers = array(); $response->response_code = 'Unknown cURL error'; $response->results = false; // do NOT change this, we really want to ignore the result! $response->error = 'Unknown cURL error'; } else { $response = new stdClass(); $response->status = (string) $info['http_code']; $response->headers = $rawheaders; $response->results = $content; $response->error = ''; // There might be multiple headers on redirect, find the status of the last one. $firstline = true; foreach ($rawheaders as $line) { if ($firstline) { $response->response_code = $line; $firstline = false; } if (trim($line, "\r\n") === '') { $firstline = true; } } } if ($fullresponse) { return $response; } if ($info['http_code'] != 200) { debugging("cURL request for \"{$url}\" failed, HTTP response code: " . $response->response_code, DEBUG_ALL); return false; } return $response->results; }
/** * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present. * Due to security concerns only downloads from http(s) sources are supported. * * @param string $url file url starting with http(s):// * @param array $headers http headers, null if none. If set, should be an * associative array of header name => value pairs. * @param array $postdata array means use POST request with given parameters * @param bool $fullresponse return headers, responses, etc in a similar way snoopy does * (if false, just returns content) * @param int $timeout timeout for complete download process including all file transfer * (default 5 minutes) * @param int $connecttimeout timeout for connection to server; this is the timeout that * usually happens if the remote server is completely down (default 20 seconds); * may not work when using proxy * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked. * Only use this when already in a trusted location. * @param string $tofile store the downloaded content to file instead of returning it. * @param bool $calctimeout false by default, true enables an extra head request to try and determine * filesize and appropriately larger timeout based on $CFG->curltimeoutkbitrate * @return mixed false if request failed or content of the file as string if ok. True if file downloaded into $tofile successfully. */ function download_file_content($url, $headers = null, $postdata = null, $fullresponse = false, $timeout = 300, $connecttimeout = 20, $skipcertverify = false, $tofile = NULL, $calctimeout = false) { global $CFG; // some extra security $newlines = array("\r", "\n"); if (is_array($headers)) { foreach ($headers as $key => $value) { $headers[$key] = str_replace($newlines, '', $value); } } $url = str_replace($newlines, '', $url); if (!preg_match('|^https?://|i', $url)) { if ($fullresponse) { $response = new stdClass(); $response->status = 0; $response->headers = array(); $response->response_code = 'Invalid protocol specified in url'; $response->results = ''; $response->error = 'Invalid protocol specified in url'; return $response; } else { return false; } } // check if proxy (if used) should be bypassed for this url $proxybypass = is_proxybypass($url); if (!($ch = curl_init($url))) { debugging('Can not init curl.'); return false; } // set extra headers if (is_array($headers)) { $headers2 = array(); foreach ($headers as $key => $value) { $headers2[] = "{$key}: {$value}"; } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers2); } if ($skipcertverify) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); } // use POST if requested if (is_array($postdata)) { $postdata = format_postdata_for_curlcall($postdata); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata); } curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connecttimeout); if (!ini_get('open_basedir') and !ini_get('safe_mode')) { // TODO: add version test for '7.10.5' curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_MAXREDIRS, 5); } if (!empty($CFG->proxyhost) and !$proxybypass) { // SOCKS supported in PHP5 only if (!empty($CFG->proxytype) and $CFG->proxytype == 'SOCKS5') { if (defined('CURLPROXY_SOCKS5')) { curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); } else { curl_close($ch); if ($fullresponse) { $response = new stdClass(); $response->status = '0'; $response->headers = array(); $response->response_code = 'SOCKS5 proxy is not supported in PHP4'; $response->results = ''; $response->error = 'SOCKS5 proxy is not supported in PHP4'; return $response; } else { debugging("SOCKS5 proxy is not supported in PHP4.", DEBUG_ALL); return false; } } } curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false); if (empty($CFG->proxyport)) { curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost); } else { curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost . ':' . $CFG->proxyport); } if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) { curl_setopt($ch, CURLOPT_PROXYUSERPWD, $CFG->proxyuser . ':' . $CFG->proxypassword); if (defined('CURLOPT_PROXYAUTH')) { // any proxy authentication if PHP 5.1 curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM); } } } // set up header and content handlers $received = new stdClass(); $received->headers = array(); // received headers array $received->tofile = $tofile; $received->fh = null; curl_setopt($ch, CURLOPT_HEADERFUNCTION, partial('download_file_content_header_handler', $received)); if ($tofile) { curl_setopt($ch, CURLOPT_WRITEFUNCTION, partial('download_file_content_write_handler', $received)); } if (!isset($CFG->curltimeoutkbitrate)) { //use very slow rate of 56kbps as a timeout speed when not set $bitrate = 56; } else { $bitrate = $CFG->curltimeoutkbitrate; } // try to calculate the proper amount for timeout from remote file size. // if disabled or zero, we won't do any checks nor head requests. if ($calctimeout && $bitrate > 0) { //setup header request only options curl_setopt_array($ch, array(CURLOPT_RETURNTRANSFER => false, CURLOPT_NOBODY => true)); curl_exec($ch); $info = curl_getinfo($ch); $err = curl_error($ch); if ($err === '' && $info['download_content_length'] > 0) { //no curl errors $timeout = max($timeout, ceil($info['download_content_length'] * 8 / ($bitrate * 1024))); //adjust for large files only - take max timeout. } //reinstate affected curl options curl_setopt_array($ch, array(CURLOPT_RETURNTRANSFER => true, CURLOPT_NOBODY => false)); } curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); $result = curl_exec($ch); // try to detect encoding problems if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) { curl_setopt($ch, CURLOPT_ENCODING, 'none'); $result = curl_exec($ch); } if ($received->fh) { fclose($received->fh); } if (curl_errno($ch)) { $error = curl_error($ch); $error_no = curl_errno($ch); curl_close($ch); if ($fullresponse) { $response = new stdClass(); if ($error_no == 28) { $response->status = '-100'; // mimic snoopy } else { $response->status = '0'; } $response->headers = array(); $response->response_code = $error; $response->results = false; $response->error = $error; return $response; } else { debugging("cURL request for \"{$url}\" failed with: {$error} ({$error_no})", DEBUG_ALL); return false; } } else { $info = curl_getinfo($ch); curl_close($ch); if (empty($info['http_code'])) { // for security reasons we support only true http connections (Location: file:// exploit prevention) $response = new stdClass(); $response->status = '0'; $response->headers = array(); $response->response_code = 'Unknown cURL error'; $response->results = false; // do NOT change this, we really want to ignore the result! $response->error = 'Unknown cURL error'; } else { $response = new stdClass(); $response->status = (string) $info['http_code']; $response->headers = $received->headers; $response->response_code = $received->headers[0]; $response->results = $result; $response->error = ''; } if ($fullresponse) { return $response; } else { if ($info['http_code'] != 200) { debugging("cURL request for \"{$url}\" failed, HTTP response code: " . $response->response_code, DEBUG_ALL); return false; } else { return $response->results; } } } }
/** * Execute client WS request with token authentication * @param string $functionname * @param array $params * @param bool $json * @return mixed */ public function call($functionname, $params, $json = false) { if ($this->type == 'oauth') { $url = $this->serverurl . '?wsfunction=' . $functionname; $body = ''; $options = array(); if ($json) { $url .= '&alt=json'; $body = json_encode($params); } else { $body = format_postdata_for_curlcall($params); } // setup the client side OAuth $oauth_options = array('consumer_key' => $this->consumer->consumer_key, 'consumer_secret' => $this->consumer->consumer_secret, 'server_uri' => 'http://example.com/webservice/rest/server.php', 'request_token_uri' => 'http://example.com/maharadev/webservice/oauthv1.php/request_token', 'authorize_uri' => 'http://example.com/webservice/oauthv1.php/authorize', 'access_token_uri' => 'http://example.com/webservice/oauthv1.php/access_token'); $store = OAuthStore::instance("Session", $oauth_options, true); $store->addServerToken($this->consumer->consumer_key, 'access', $this->token['token'], $this->token['token_secret'], 1); $request = new OAuthRequester($url, 'POST', $options, $body); $result = $request->doRequest(0); if ($result['code'] != 200) { throw new Exception('REST OAuth error: ' . var_export($result, true)); } $result = $result['body']; if ($json) { $values = (array) json_decode($result, true); return $values; } } else { // do a JSON based call - just soooo easy compared to XML/SOAP if ($json) { $data = json_encode($params); $url = $this->serverurl . '?' . $this->auth . '&wsfunction=' . $functionname . '&alt=json'; $result = file_get_contents($url, false, stream_context_create(array('http' => array('method' => 'POST', 'header' => "Content-Type: application/json\r\nConnection: close\r\nContent-Length: " . strlen($data) . "\r\n", 'content' => $data)))); $values = (array) json_decode($result, true); return $values; } // default to parsing HTTP parameters $result = webservice_download_file_content($this->serverurl . '?' . $this->auth . '&wsfunction=' . $functionname, null, $params); } //after the call, for those not using JSON, parseout the results // from REST XML response to PHP $xml2array = new webservice_xml2array($result); $raw = $xml2array->getResult(); if (isset($raw['EXCEPTION'])) { $debug = isset($raw['EXCEPTION']['DEBUGINFO']) ? $raw['EXCEPTION']['DEBUGINFO']['#text'] : ''; throw new Exception('REST error: ' . $raw['EXCEPTION']['MESSAGE']['#text'] . ' (' . $raw['EXCEPTION']['@class'] . ') ' . $debug); } $result = array(); if (isset($raw['RESPONSE'])) { $node = $raw['RESPONSE']; if (isset($node['MULTIPLE'])) { $result = self::recurse_structure($node['MULTIPLE']); } else { if (isset($raw['RESPONSE']['SINGLE'])) { $result = $raw['RESPONSE']['SINGLE']; } else { // empty result ? $result = $raw['RESPONSE']; } } } return $result; }
/** * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present. * Due to security concerns only downloads from http(s) sources are supported. * * @param string $url file url starting with http(s):// * @param array $headers http headers, null if none. If set, should be an * associative array of header name => value pairs. * @param array $postdata array means use POST request with given parameters * @param bool $fullresponse return headers, responses, etc in a similar way snoopy does * (if false, just returns content) * @param int $timeout timeout for complete download process including all file transfer * (default 5 minutes) * @param int $connecttimeout timeout for connection to server; this is the timeout that * usually happens if the remote server is completely down (default 20 seconds); * may not work when using proxy * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked. * Only use this when already in a trusted location. * @param string $tofile store the downloaded content to file instead of returning it. * @param bool $calctimeout false by default, true enables an extra head request to try and determine * filesize and appropriately larger timeout based on get_config('curltimeoutkbitrate') * @return mixed false if request failed or content of the file as string if ok. True if file downloaded into $tofile successfully. */ function webservice_download_file_content($url, $headers = null, $postdata = null, $fullresponse = false, $timeout = 300, $connecttimeout = 20, $skipcertverify = false, $tofile = NULL, $calctimeout = false) { // some extra security $newlines = array("\r", "\n"); if (is_array($headers)) { foreach ($headers as $key => $value) { $headers[$key] = str_replace($newlines, '', $value); } } $url = str_replace($newlines, '', $url); if (!preg_match('|^https?://|i', $url)) { if ($fullresponse) { $response = new stdClass(); $response->status = 0; $response->headers = array(); $response->response_code = 'Invalid protocol specified in url'; $response->results = ''; $response->error = 'Invalid protocol specified in url'; return $response; } else { return false; } } // build up CURL options $options = array(); // set extra headers if (is_array($headers)) { $headers2 = array(); foreach ($headers as $key => $value) { $headers2[] = "{$key}: {$value}"; } $options[CURLOPT_HTTPHEADER] = $headers2; } if ($skipcertverify) { $options[CURLOPT_SSL_VERIFYPEER] = false; $options[CURLOPT_SSL_VERIFYHOST] = false; } // use POST if requested if (is_array($postdata)) { $postdata = format_postdata_for_curlcall($postdata); $options[CURLOPT_POST] = true; $options[CURLOPT_POSTFIELDS] = $postdata; } $options[CURLOPT_RETURNTRANSFER] = true; $options[CURLOPT_HEADER] = false; $options[CURLOPT_CONNECTTIMEOUT] = $connecttimeout; if (!ini_get('open_basedir') and !ini_get('safe_mode')) { // TODO: add version test for '7.10.5' $options[CURLOPT_FOLLOWLOCATION] = true; $options[CURLOPT_MAXREDIRS] = 5; } // set up header and content handlers $received = new stdClass(); // received headers array $received->headers = array(); $received->tofile = $tofile; $received->fh = null; $options[CURLOPT_HEADERFUNCTION] = ws_partial('download_file_content_header_handler', $received); if ($tofile) { $options[CURLOPT_WRITEFUNCTION] = ws_partial('download_file_content_write_handler', $received); } $options[CURLOPT_TIMEOUT] = $timeout; $options[CURLOPT_URL] = $url; $result = webservice_http_request($options); // reformat the results $errno = $result->errno; $error = $result->error; $info = $result->info; $result = $result->data; if ($received->fh) { fclose($received->fh); } if ($errno) { if ($fullresponse) { $response = new stdClass(); if ($errno == 28) { $response->status = '-100'; // mimic snoopy } else { $response->status = '0'; } $response->headers = array(); $response->response_code = $error; $response->results = false; $response->error = $error; return $response; } else { debugging("cURL request for \"{$url}\" failed with: {$error} ({$errno})", DEBUG_DEVELOPER); return false; } } else { if (empty($info['http_code'])) { // for security reasons we support only true http connections (Location: file:// exploit prevention) $response = new stdClass(); $response->status = '0'; $response->headers = array(); $response->response_code = 'Unknown cURL error'; $response->results = false; // do NOT change this, we really want to ignore the result! $response->error = 'Unknown cURL error'; } else { $response = new stdClass(); $response->status = (string) $info['http_code']; $response->headers = $received->headers; $response->response_code = $received->headers[0]; $response->results = $result; $response->error = ''; } if ($fullresponse) { return $response; } else { if ($info['http_code'] != 200) { debugging("cURL request for \"{$url}\" failed, HTTP response code: " . $response->response_code, DEBUG_DEVELOPER); return false; } else { return $response->results; } } } }
/** * Post request. * * Overridden to convert the data to a string, else curl will set the wrong headers. * * @param string $url The URL. * @param array|string $params The parameters. * @param array $options The options. * @return bool */ public function post($url, $params = '', $options = array()) { return parent::post($url, format_postdata_for_curlcall($params), $options); }