1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * This file contains the class moodle_google_curlio.
19 *
20 * @package core_google
21 * @copyright 2013 Frédéric Massart
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27require_once($CFG->libdir . '/filelib.php');
28
29/**
30 * Class moodle_google_curlio.
31 *
32 * The initial purpose of this class is to add support for our
33 * class curl in Google_IO_Curl. It mostly entirely overrides it.
34 *
35 * @package core_google
36 * @copyright 2013 Frédéric Massart
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38*/
39class moodle_google_curlio extends Google_IO_Curl {
40
41    /** @var array associate array of constant value and their name. */
42    private static $constants = null;
43
44    /** @var array options. */
45    private $options = array();
46
47    /**
48     * Send the request via our curl object.
49     *
50     * @param curl $curl prepared curl object.
51     * @param Google_HttpRequest $request The request.
52     * @return string result of the request.
53     */
54    private function do_request($curl, $request) {
55        $url = $request->getUrl();
56        $method = $request->getRequestMethod();
57        switch (strtoupper($method)) {
58            case 'POST':
59                $ret = $curl->post($url, $request->getPostBody());
60                break;
61            case 'GET':
62                $ret = $curl->get($url);
63                break;
64            case 'HEAD':
65                $ret = $curl->head($url);
66                break;
67            case 'PUT':
68                $ret = $curl->put($url);
69                break;
70            default:
71                throw new coding_exception('Unknown request type: ' . $method);
72                break;
73        }
74        return $ret;
75    }
76
77    /**
78     * Execute an API request.
79     *
80     * This is a copy/paste from the parent class that uses Moodle's implementation
81     * of curl. Portions have been removed or altered.
82     *
83     * @param Google_Http_Request $request the http request to be executed
84     * @return Google_Http_Request http request with the response http code, response
85     * headers and response body filled in
86     * @throws Google_IO_Exception on curl or IO error
87     */
88    public function executeRequest(Google_Http_Request $request) {
89        $curl = new curl();
90
91        if ($request->getPostBody()) {
92            $curl->setopt(array('CURLOPT_POSTFIELDS' => $request->getPostBody()));
93        }
94
95        $requestHeaders = $request->getRequestHeaders();
96        if ($requestHeaders && is_array($requestHeaders)) {
97            $curlHeaders = array();
98            foreach ($requestHeaders as $k => $v) {
99                $curlHeaders[] = "$k: $v";
100            }
101            $curl->setopt(array('CURLOPT_HTTPHEADER' => $curlHeaders));
102        }
103
104        $curl->setopt(array('CURLOPT_URL' => $request->getUrl()));
105
106        $curl->setopt(array('CURLOPT_CUSTOMREQUEST' => $request->getRequestMethod()));
107        $curl->setopt(array('CURLOPT_USERAGENT' => $request->getUserAgent()));
108
109        $curl->setopt(array('CURLOPT_FOLLOWLOCATION' => false));
110        $curl->setopt(array('CURLOPT_SSL_VERIFYPEER' => true));
111        $curl->setopt(array('CURLOPT_RETURNTRANSFER' => true));
112        $curl->setopt(array('CURLOPT_HEADER' => true));
113
114        if ($request->canGzip()) {
115            $curl->setopt(array('CURLOPT_ENCODING' => 'gzip,deflate'));
116        }
117
118        $curl->setopt($this->options);
119        $respdata = $this->do_request($curl, $request);
120
121        $infos = $curl->get_info();
122        $respheadersize = $infos['header_size'];
123        $resphttpcode = (int) $infos['http_code'];
124        $curlerrornum = $curl->get_errno();
125        $curlerror = $curl->error;
126
127        if ($curlerrornum != CURLE_OK) {
128            throw new Google_IO_Exception($curlerror);
129        }
130
131        list($responseHeaders, $responseBody) = $this->parseHttpResponse($respdata, $respheadersize);
132        return array($responseBody, $responseHeaders, $resphttpcode);
133    }
134
135    /**
136     * Set curl options.
137     *
138     * We overwrite this method to ensure that the data passed meets
139     * the requirement of our curl implementation and so that the keys
140     * are strings, and not curl constants.
141     *
142     * @param array $optparams Multiple options used by a cURL session.
143     * @return void
144     */
145    public function setOptions($optparams) {
146        $safeparams = array();
147        foreach ($optparams as $name => $value) {
148            if (!is_string($name)) {
149                $name = $this->get_option_name_from_constant($name);
150            }
151            $safeparams[$name] = $value;
152        }
153        $this->options = $options + $this->options;
154    }
155
156    /**
157     * Set the maximum request time in seconds.
158     *
159     * Overridden to use the right option key.
160     *
161     * @param $timeout in seconds
162     */
163    public function setTimeout($timeout) {
164        // Since this timeout is really for putting a bound on the time
165        // we'll set them both to the same. If you need to specify a longer
166        // CURLOPT_TIMEOUT, or a tigher CONNECTTIMEOUT, the best thing to
167        // do is use the setOptions method for the values individually.
168        $this->options['CURLOPT_CONNECTTIMEOUT'] = $timeout;
169        $this->options['CURLOPT_TIMEOUT'] = $timeout;
170    }
171
172    /**
173     * Get the maximum request time in seconds.
174     *
175     * Overridden to use the right option key.
176     *
177     * @return timeout in seconds.
178     */
179    public function getTimeout() {
180       return $this->options['CURLOPT_TIMEOUT'];
181    }
182
183    /**
184     * Return the name of an option based on the constant value.
185     *
186     * @param int $constant value of a CURL constant.
187     * @return string name of the constant if found, or throws exception.
188     * @throws coding_exception when the constant is not found.
189     * @since Moodle 2.5
190     */
191    public function get_option_name_from_constant($constant) {
192        if (is_null(self::$constants)) {
193            $constants = get_defined_constants(true);
194            $constants = isset($constants['curl']) ? $constants['curl'] : array();
195            $constants = array_flip($constants);
196            self::$constants = $constants;
197        }
198        if (isset(self::$constants[$constant])) {
199            return self::$constants[$constant];
200        }
201        throw new coding_exception('Unknown curl constant value: ' . $constant);
202    }
203
204}
205