/** * 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; }