Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 5 | | | |
bc77d7c0 TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
6a488035 TO |
11 | |
12 | /** | |
e647e388 TO |
13 | * This class handles HTTP downloads |
14 | * | |
15 | * FIXME: fetch() and get() report errors differently -- e.g. | |
16 | * fetch() returns fatal and get() returns an error code. Should | |
17 | * refactor both (or get a third-party HTTP library) but don't | |
18 | * want to deal with that so late in the 4.3 dev cycle. | |
6a488035 TO |
19 | * |
20 | * @package CRM | |
ca5cec67 | 21 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 TO |
22 | */ |
23 | class CRM_Utils_HttpClient { | |
24 | ||
25 | const STATUS_OK = 'ok'; | |
26 | const STATUS_WRITE_ERROR = 'write-error'; | |
27 | const STATUS_DL_ERROR = 'dl-error'; | |
28 | ||
3b6f287b TO |
29 | /** |
30 | * @var CRM_Utils_HttpClient | |
31 | */ | |
32 | protected static $singleton; | |
33 | ||
afcc9be0 | 34 | /** |
e97c66ff | 35 | * @var int|null |
50bfb460 | 36 | * seconds; or NULL to use system default |
afcc9be0 | 37 | */ |
021cfda1 | 38 | protected $connectionTimeout; |
afcc9be0 | 39 | |
5bc392e6 EM |
40 | /** |
41 | * @return CRM_Utils_HttpClient | |
42 | */ | |
3b6f287b TO |
43 | public static function singleton() { |
44 | if (!self::$singleton) { | |
45 | self::$singleton = new CRM_Utils_HttpClient(); | |
46 | } | |
47 | return self::$singleton; | |
48 | } | |
49 | ||
5bc392e6 | 50 | /** |
5e21e0f3 BT |
51 | * @param int|null $connectionTimeout |
52 | * seconds; or NULL to use system default | |
5bc392e6 | 53 | */ |
021cfda1 TO |
54 | public function __construct($connectionTimeout = NULL) { |
55 | $this->connectionTimeout = $connectionTimeout; | |
afcc9be0 TO |
56 | } |
57 | ||
6a488035 TO |
58 | /** |
59 | * Download the remote zipfile. | |
60 | * | |
77855840 TO |
61 | * @param string $remoteFile |
62 | * URL of a .zip file. | |
63 | * @param string $localFile | |
64 | * Path at which to store the .zip file. | |
6a488035 | 65 | * @return STATUS_OK|STATUS_WRITE_ERROR|STATUS_DL_ERROR |
ee3db087 SL |
66 | * |
67 | * @throws CRM_Core_Exception | |
6a488035 | 68 | */ |
3b6f287b | 69 | public function fetch($remoteFile, $localFile) { |
6a488035 TO |
70 | // Download extension zip file ... |
71 | if (!function_exists('curl_init')) { | |
ee3db087 | 72 | throw new CRM_Core_Exception('Cannot install this extension - curl is not installed!'); |
6a488035 | 73 | } |
e647e388 TO |
74 | |
75 | list($ch, $caConfig) = $this->createCurl($remoteFile); | |
6a488035 | 76 | if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) { |
ee3db087 | 77 | throw new CRM_Core_Exception('Cannot install this extension - does not support SSL'); |
6a488035 TO |
78 | } |
79 | ||
6a488035 TO |
80 | $fp = @fopen($localFile, "w"); |
81 | if (!$fp) { | |
6a488035 TO |
82 | return self::STATUS_WRITE_ERROR; |
83 | } | |
84 | curl_setopt($ch, CURLOPT_FILE, $fp); | |
85 | ||
86 | curl_exec($ch); | |
87 | if (curl_errno($ch)) { | |
6a488035 TO |
88 | return self::STATUS_DL_ERROR; |
89 | } | |
90 | else { | |
91 | curl_close($ch); | |
92 | } | |
93 | ||
94 | fclose($fp); | |
95 | ||
96 | return self::STATUS_OK; | |
97 | } | |
afcc9be0 TO |
98 | |
99 | /** | |
fe482240 | 100 | * Send an HTTP GET for a remote resource. |
afcc9be0 | 101 | * |
77855840 TO |
102 | * @param string $remoteFile |
103 | * URL of remote file. | |
a6c01b45 CW |
104 | * @return array |
105 | * array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string) | |
afcc9be0 TO |
106 | */ |
107 | public function get($remoteFile) { | |
afcc9be0 TO |
108 | // Download extension zip file ... |
109 | if (!function_exists('curl_init')) { | |
dadcd1ac FG |
110 | // CRM-13805 |
111 | CRM_Core_Session::setStatus( | |
112 | ts('As a result, actions like retrieving the CiviCRM news feed will fail. Talk to your server administrator or hosting company to rectify this.'), | |
113 | ts('Curl is not installed') | |
114 | ); | |
be2fb01f | 115 | return [self::STATUS_DL_ERROR, NULL]; |
afcc9be0 | 116 | } |
e647e388 TO |
117 | |
118 | list($ch, $caConfig) = $this->createCurl($remoteFile); | |
119 | ||
afcc9be0 | 120 | if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) { |
be2fb01f | 121 | return [self::STATUS_DL_ERROR, NULL]; |
afcc9be0 TO |
122 | } |
123 | ||
afcc9be0 | 124 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
afcc9be0 TO |
125 | $data = curl_exec($ch); |
126 | if (curl_errno($ch)) { | |
be2fb01f | 127 | return [self::STATUS_DL_ERROR, $data]; |
afcc9be0 TO |
128 | } |
129 | else { | |
130 | curl_close($ch); | |
131 | } | |
132 | ||
be2fb01f | 133 | return [self::STATUS_OK, $data]; |
e647e388 TO |
134 | } |
135 | ||
49626e3d | 136 | /** |
fe482240 | 137 | * Send an HTTP POST for a remote resource. |
49626e3d | 138 | * |
77855840 TO |
139 | * @param string $remoteFile |
140 | * URL of a .zip file. | |
c490a46a | 141 | * @param array $params |
f4aaa82a | 142 | * |
a6c01b45 CW |
143 | * @return array |
144 | * array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string) | |
49626e3d CW |
145 | */ |
146 | public function post($remoteFile, $params) { | |
147 | // Download extension zip file ... | |
148 | if (!function_exists('curl_init')) { | |
be2fb01f | 149 | return [self::STATUS_DL_ERROR, NULL]; |
49626e3d CW |
150 | } |
151 | ||
152 | list($ch, $caConfig) = $this->createCurl($remoteFile); | |
153 | ||
154 | if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) { | |
be2fb01f | 155 | return [self::STATUS_DL_ERROR, NULL]; |
49626e3d CW |
156 | } |
157 | ||
158 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
e7292422 TO |
159 | curl_setopt($ch, CURLOPT_POST, TRUE); |
160 | curl_setopt($ch, CURLOPT_POST, count($params)); | |
161 | curl_setopt($ch, CURLOPT_POSTFIELDS, $params); | |
49626e3d CW |
162 | $data = curl_exec($ch); |
163 | if (curl_errno($ch)) { | |
be2fb01f | 164 | return [self::STATUS_DL_ERROR, $data]; |
49626e3d CW |
165 | } |
166 | else { | |
167 | curl_close($ch); | |
168 | } | |
169 | ||
be2fb01f | 170 | return [self::STATUS_OK, $data]; |
49626e3d CW |
171 | } |
172 | ||
e647e388 TO |
173 | /** |
174 | * @param string $remoteFile | |
a6c01b45 CW |
175 | * @return array |
176 | * (0 => resource, 1 => CA_Config_Curl) | |
e647e388 TO |
177 | */ |
178 | protected function createCurl($remoteFile) { | |
be2fb01f | 179 | $caConfig = CA_Config_Curl::probe([ |
aaffa79f | 180 | 'verify_peer' => (bool) Civi::settings()->get('verifySSL'), |
be2fb01f | 181 | ]); |
e647e388 TO |
182 | |
183 | $ch = curl_init(); | |
184 | curl_setopt($ch, CURLOPT_URL, $remoteFile); | |
185 | curl_setopt($ch, CURLOPT_HEADER, FALSE); | |
186 | curl_setopt($ch, CURLOPT_ENCODING, 'gzip'); | |
187 | curl_setopt($ch, CURLOPT_VERBOSE, 0); | |
eb066397 | 188 | if ($this->isRedirectSupported()) { |
439b9688 LS |
189 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE); |
190 | } | |
021cfda1 TO |
191 | if ($this->connectionTimeout !== NULL) { |
192 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout); | |
193 | } | |
e647e388 TO |
194 | if (preg_match('/^https:/', $remoteFile) && $caConfig->isEnableSSL()) { |
195 | curl_setopt_array($ch, $caConfig->toCurlOptions()); | |
196 | } | |
afcc9be0 | 197 | |
be2fb01f | 198 | return [$ch, $caConfig]; |
afcc9be0 TO |
199 | } |
200 | ||
5bc392e6 EM |
201 | /** |
202 | * @return bool | |
203 | */ | |
eb066397 | 204 | public function isRedirectSupported() { |
4d03ddb9 | 205 | return (ini_get('open_basedir') == ''); |
eb066397 TO |
206 | } |
207 | ||
6a488035 | 208 | } |