Beispiel #1
0
/**
 * 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;
}