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 EM |
50 | /** |
51 | * @param null $connectionTimeout | |
52 | */ | |
021cfda1 TO |
53 | public function __construct($connectionTimeout = NULL) { |
54 | $this->connectionTimeout = $connectionTimeout; | |
afcc9be0 TO |
55 | } |
56 | ||
6a488035 TO |
57 | /** |
58 | * Download the remote zipfile. | |
59 | * | |
77855840 TO |
60 | * @param string $remoteFile |
61 | * URL of a .zip file. | |
62 | * @param string $localFile | |
63 | * Path at which to store the .zip file. | |
6a488035 | 64 | * @return STATUS_OK|STATUS_WRITE_ERROR|STATUS_DL_ERROR |
ee3db087 SL |
65 | * |
66 | * @throws CRM_Core_Exception | |
6a488035 | 67 | */ |
3b6f287b | 68 | public function fetch($remoteFile, $localFile) { |
6a488035 TO |
69 | // Download extension zip file ... |
70 | if (!function_exists('curl_init')) { | |
ee3db087 | 71 | throw new CRM_Core_Exception('Cannot install this extension - curl is not installed!'); |
6a488035 | 72 | } |
e647e388 TO |
73 | |
74 | list($ch, $caConfig) = $this->createCurl($remoteFile); | |
6a488035 | 75 | if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) { |
ee3db087 | 76 | throw new CRM_Core_Exception('Cannot install this extension - does not support SSL'); |
6a488035 TO |
77 | } |
78 | ||
6a488035 TO |
79 | $fp = @fopen($localFile, "w"); |
80 | if (!$fp) { | |
6a488035 TO |
81 | return self::STATUS_WRITE_ERROR; |
82 | } | |
83 | curl_setopt($ch, CURLOPT_FILE, $fp); | |
84 | ||
85 | curl_exec($ch); | |
86 | if (curl_errno($ch)) { | |
6a488035 TO |
87 | return self::STATUS_DL_ERROR; |
88 | } | |
89 | else { | |
90 | curl_close($ch); | |
91 | } | |
92 | ||
93 | fclose($fp); | |
94 | ||
95 | return self::STATUS_OK; | |
96 | } | |
afcc9be0 TO |
97 | |
98 | /** | |
fe482240 | 99 | * Send an HTTP GET for a remote resource. |
afcc9be0 | 100 | * |
77855840 TO |
101 | * @param string $remoteFile |
102 | * URL of remote file. | |
a6c01b45 CW |
103 | * @return array |
104 | * array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string) | |
afcc9be0 TO |
105 | */ |
106 | public function get($remoteFile) { | |
afcc9be0 TO |
107 | // Download extension zip file ... |
108 | if (!function_exists('curl_init')) { | |
dadcd1ac FG |
109 | // CRM-13805 |
110 | CRM_Core_Session::setStatus( | |
111 | ts('As a result, actions like retrieving the CiviCRM news feed will fail. Talk to your server administrator or hosting company to rectify this.'), | |
112 | ts('Curl is not installed') | |
113 | ); | |
be2fb01f | 114 | return [self::STATUS_DL_ERROR, NULL]; |
afcc9be0 | 115 | } |
e647e388 TO |
116 | |
117 | list($ch, $caConfig) = $this->createCurl($remoteFile); | |
118 | ||
afcc9be0 | 119 | if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) { |
be2fb01f | 120 | return [self::STATUS_DL_ERROR, NULL]; |
afcc9be0 TO |
121 | } |
122 | ||
afcc9be0 | 123 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
afcc9be0 TO |
124 | $data = curl_exec($ch); |
125 | if (curl_errno($ch)) { | |
be2fb01f | 126 | return [self::STATUS_DL_ERROR, $data]; |
afcc9be0 TO |
127 | } |
128 | else { | |
129 | curl_close($ch); | |
130 | } | |
131 | ||
be2fb01f | 132 | return [self::STATUS_OK, $data]; |
e647e388 TO |
133 | } |
134 | ||
49626e3d | 135 | /** |
fe482240 | 136 | * Send an HTTP POST for a remote resource. |
49626e3d | 137 | * |
77855840 TO |
138 | * @param string $remoteFile |
139 | * URL of a .zip file. | |
c490a46a | 140 | * @param array $params |
f4aaa82a | 141 | * |
a6c01b45 CW |
142 | * @return array |
143 | * array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string) | |
49626e3d CW |
144 | */ |
145 | public function post($remoteFile, $params) { | |
146 | // Download extension zip file ... | |
147 | if (!function_exists('curl_init')) { | |
be2fb01f | 148 | return [self::STATUS_DL_ERROR, NULL]; |
49626e3d CW |
149 | } |
150 | ||
151 | list($ch, $caConfig) = $this->createCurl($remoteFile); | |
152 | ||
153 | if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) { | |
be2fb01f | 154 | return [self::STATUS_DL_ERROR, NULL]; |
49626e3d CW |
155 | } |
156 | ||
157 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
e7292422 TO |
158 | curl_setopt($ch, CURLOPT_POST, TRUE); |
159 | curl_setopt($ch, CURLOPT_POST, count($params)); | |
160 | curl_setopt($ch, CURLOPT_POSTFIELDS, $params); | |
49626e3d CW |
161 | $data = curl_exec($ch); |
162 | if (curl_errno($ch)) { | |
be2fb01f | 163 | return [self::STATUS_DL_ERROR, $data]; |
49626e3d CW |
164 | } |
165 | else { | |
166 | curl_close($ch); | |
167 | } | |
168 | ||
be2fb01f | 169 | return [self::STATUS_OK, $data]; |
49626e3d CW |
170 | } |
171 | ||
e647e388 TO |
172 | /** |
173 | * @param string $remoteFile | |
a6c01b45 CW |
174 | * @return array |
175 | * (0 => resource, 1 => CA_Config_Curl) | |
e647e388 TO |
176 | */ |
177 | protected function createCurl($remoteFile) { | |
be2fb01f | 178 | $caConfig = CA_Config_Curl::probe([ |
aaffa79f | 179 | 'verify_peer' => (bool) Civi::settings()->get('verifySSL'), |
be2fb01f | 180 | ]); |
e647e388 TO |
181 | |
182 | $ch = curl_init(); | |
183 | curl_setopt($ch, CURLOPT_URL, $remoteFile); | |
184 | curl_setopt($ch, CURLOPT_HEADER, FALSE); | |
185 | curl_setopt($ch, CURLOPT_ENCODING, 'gzip'); | |
186 | curl_setopt($ch, CURLOPT_VERBOSE, 0); | |
eb066397 | 187 | if ($this->isRedirectSupported()) { |
439b9688 LS |
188 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE); |
189 | } | |
021cfda1 TO |
190 | if ($this->connectionTimeout !== NULL) { |
191 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout); | |
192 | } | |
e647e388 TO |
193 | if (preg_match('/^https:/', $remoteFile) && $caConfig->isEnableSSL()) { |
194 | curl_setopt_array($ch, $caConfig->toCurlOptions()); | |
195 | } | |
afcc9be0 | 196 | |
be2fb01f | 197 | return [$ch, $caConfig]; |
afcc9be0 TO |
198 | } |
199 | ||
5bc392e6 EM |
200 | /** |
201 | * @return bool | |
202 | */ | |
eb066397 | 203 | public function isRedirectSupported() { |
8c633404 | 204 | return (ini_get('open_basedir') == '') && (ini_get('safe_mode') == 'Off' || ini_get('safe_mode') == '' || ini_get('safe_mode') === FALSE); |
eb066397 TO |
205 | } |
206 | ||
6a488035 | 207 | } |