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 TO |
64 | * @return STATUS_OK|STATUS_WRITE_ERROR|STATUS_DL_ERROR |
65 | */ | |
3b6f287b | 66 | public function fetch($remoteFile, $localFile) { |
6a488035 TO |
67 | // Download extension zip file ... |
68 | if (!function_exists('curl_init')) { | |
69 | CRM_Core_Error::fatal('Cannot install this extension - curl is not installed!'); | |
70 | } | |
e647e388 TO |
71 | |
72 | list($ch, $caConfig) = $this->createCurl($remoteFile); | |
6a488035 TO |
73 | if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) { |
74 | CRM_Core_Error::fatal('Cannot install this extension - does not support SSL'); | |
75 | } | |
76 | ||
6a488035 TO |
77 | $fp = @fopen($localFile, "w"); |
78 | if (!$fp) { | |
6a488035 TO |
79 | return self::STATUS_WRITE_ERROR; |
80 | } | |
81 | curl_setopt($ch, CURLOPT_FILE, $fp); | |
82 | ||
83 | curl_exec($ch); | |
84 | if (curl_errno($ch)) { | |
6a488035 TO |
85 | return self::STATUS_DL_ERROR; |
86 | } | |
87 | else { | |
88 | curl_close($ch); | |
89 | } | |
90 | ||
91 | fclose($fp); | |
92 | ||
93 | return self::STATUS_OK; | |
94 | } | |
afcc9be0 TO |
95 | |
96 | /** | |
fe482240 | 97 | * Send an HTTP GET for a remote resource. |
afcc9be0 | 98 | * |
77855840 TO |
99 | * @param string $remoteFile |
100 | * URL of remote file. | |
a6c01b45 CW |
101 | * @return array |
102 | * array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string) | |
afcc9be0 TO |
103 | */ |
104 | public function get($remoteFile) { | |
afcc9be0 TO |
105 | // Download extension zip file ... |
106 | if (!function_exists('curl_init')) { | |
dadcd1ac FG |
107 | // CRM-13805 |
108 | CRM_Core_Session::setStatus( | |
109 | ts('As a result, actions like retrieving the CiviCRM news feed will fail. Talk to your server administrator or hosting company to rectify this.'), | |
110 | ts('Curl is not installed') | |
111 | ); | |
be2fb01f | 112 | return [self::STATUS_DL_ERROR, NULL]; |
afcc9be0 | 113 | } |
e647e388 TO |
114 | |
115 | list($ch, $caConfig) = $this->createCurl($remoteFile); | |
116 | ||
afcc9be0 | 117 | if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) { |
50bfb460 | 118 | // CRM_Core_Error::fatal('Cannot install this extension - does not support SSL'); |
be2fb01f | 119 | return [self::STATUS_DL_ERROR, NULL]; |
afcc9be0 TO |
120 | } |
121 | ||
afcc9be0 | 122 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
afcc9be0 TO |
123 | $data = curl_exec($ch); |
124 | if (curl_errno($ch)) { | |
be2fb01f | 125 | return [self::STATUS_DL_ERROR, $data]; |
afcc9be0 TO |
126 | } |
127 | else { | |
128 | curl_close($ch); | |
129 | } | |
130 | ||
be2fb01f | 131 | return [self::STATUS_OK, $data]; |
e647e388 TO |
132 | } |
133 | ||
49626e3d | 134 | /** |
fe482240 | 135 | * Send an HTTP POST for a remote resource. |
49626e3d | 136 | * |
77855840 TO |
137 | * @param string $remoteFile |
138 | * URL of a .zip file. | |
c490a46a | 139 | * @param array $params |
f4aaa82a | 140 | * |
a6c01b45 CW |
141 | * @return array |
142 | * array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string) | |
49626e3d CW |
143 | */ |
144 | public function post($remoteFile, $params) { | |
145 | // Download extension zip file ... | |
146 | if (!function_exists('curl_init')) { | |
147 | //CRM_Core_Error::fatal('Cannot install this extension - curl is not installed!'); | |
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()) { | |
50bfb460 | 154 | // CRM_Core_Error::fatal('Cannot install this extension - does not support SSL'); |
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() { |
8c633404 | 205 | return (ini_get('open_basedir') == '') && (ini_get('safe_mode') == 'Off' || ini_get('safe_mode') == '' || ini_get('safe_mode') === FALSE); |
eb066397 TO |
206 | } |
207 | ||
6a488035 | 208 | } |