Merge remote-tracking branch 'upstream/4.5' into 4.5-master-2015-02-09-11-44-07
[civicrm-core.git] / CRM / Utils / HttpClient.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * This class handles HTTP downloads
30 *
31 * FIXME: fetch() and get() report errors differently -- e.g.
32 * fetch() returns fatal and get() returns an error code. Should
33 * refactor both (or get a third-party HTTP library) but don't
34 * want to deal with that so late in the 4.3 dev cycle.
35 *
36 * @package CRM
37 * @copyright CiviCRM LLC (c) 2004-2014
38 * $Id$
39 *
40 */
41 class CRM_Utils_HttpClient {
42
43 const STATUS_OK = 'ok';
44 const STATUS_WRITE_ERROR = 'write-error';
45 const STATUS_DL_ERROR = 'dl-error';
46
47 /**
48 * @var CRM_Utils_HttpClient
49 */
50 protected static $singleton;
51
52 /**
53 * @var int|NULL seconds; or NULL to use system default
54 */
55 protected $connectionTimeout;
56
57 /**
58 * @return CRM_Utils_HttpClient
59 */
60 public static function singleton() {
61 if (!self::$singleton) {
62 self::$singleton = new CRM_Utils_HttpClient();
63 }
64 return self::$singleton;
65 }
66
67 /**
68 * @param null $connectionTimeout
69 */
70 public function __construct($connectionTimeout = NULL) {
71 $this->connectionTimeout = $connectionTimeout;
72 }
73
74 /**
75 * Download the remote zipfile.
76 *
77 * @param string $remoteFile
78 * URL of a .zip file.
79 * @param string $localFile
80 * Path at which to store the .zip file.
81 * @return STATUS_OK|STATUS_WRITE_ERROR|STATUS_DL_ERROR
82 */
83 public function fetch($remoteFile, $localFile) {
84 // Download extension zip file ...
85 if (!function_exists('curl_init')) {
86 CRM_Core_Error::fatal('Cannot install this extension - curl is not installed!');
87 }
88
89 list($ch, $caConfig) = $this->createCurl($remoteFile);
90 if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) {
91 CRM_Core_Error::fatal('Cannot install this extension - does not support SSL');
92 }
93
94 $fp = @fopen($localFile, "w");
95 if (!$fp) {
96 CRM_Core_Session::setStatus(ts('Unable to write to %1.<br />Is the location writable?', array(1 => $localFile)), ts('Write Error'), 'error');
97 return self::STATUS_WRITE_ERROR;
98 }
99 curl_setopt($ch, CURLOPT_FILE, $fp);
100
101 curl_exec($ch);
102 if (curl_errno($ch)) {
103 CRM_Core_Session::setStatus(ts('Unable to download extension from %1. Error Message: %2',
104 array(1 => $remoteFile, 2 => curl_error($ch))), ts('Download Error'), 'error');
105 return self::STATUS_DL_ERROR;
106 }
107 else {
108 curl_close($ch);
109 }
110
111 fclose($fp);
112
113 return self::STATUS_OK;
114 }
115
116 /**
117 * Send an HTTP GET for a remote resource.
118 *
119 * @param string $remoteFile
120 * URL of remote file.
121 * @return array
122 * array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string)
123 */
124 public function get($remoteFile) {
125 // Download extension zip file ...
126 if (!function_exists('curl_init')) {
127 // CRM-13805
128 CRM_Core_Session::setStatus(
129 ts('As a result, actions like retrieving the CiviCRM news feed will fail. Talk to your server administrator or hosting company to rectify this.'),
130 ts('Curl is not installed')
131 );
132 return array(self::STATUS_DL_ERROR, NULL);
133 }
134
135 list($ch, $caConfig) = $this->createCurl($remoteFile);
136
137 if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) {
138 //CRM_Core_Error::fatal('Cannot install this extension - does not support SSL');
139 return array(self::STATUS_DL_ERROR, NULL);
140 }
141
142 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
143 $data = curl_exec($ch);
144 if (curl_errno($ch)) {
145 return array(self::STATUS_DL_ERROR, $data);
146 }
147 else {
148 curl_close($ch);
149 }
150
151 return array(self::STATUS_OK, $data);
152 }
153
154 /**
155 * Send an HTTP POST for a remote resource.
156 *
157 * @param string $remoteFile
158 * URL of a .zip file.
159 * @param array $params
160 *
161 * @return array
162 * array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string)
163 */
164 public function post($remoteFile, $params) {
165 // Download extension zip file ...
166 if (!function_exists('curl_init')) {
167 //CRM_Core_Error::fatal('Cannot install this extension - curl is not installed!');
168 return array(self::STATUS_DL_ERROR, NULL);
169 }
170
171 list($ch, $caConfig) = $this->createCurl($remoteFile);
172
173 if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) {
174 //CRM_Core_Error::fatal('Cannot install this extension - does not support SSL');
175 return array(self::STATUS_DL_ERROR, NULL);
176 }
177
178 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
179 curl_setopt($ch, CURLOPT_POST, TRUE);
180 curl_setopt($ch, CURLOPT_POST, count($params));
181 curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
182 $data = curl_exec($ch);
183 if (curl_errno($ch)) {
184 return array(self::STATUS_DL_ERROR, $data);
185 }
186 else {
187 curl_close($ch);
188 }
189
190 return array(self::STATUS_OK, $data);
191 }
192
193 /**
194 * @param string $remoteFile
195 * @return array
196 * (0 => resource, 1 => CA_Config_Curl)
197 */
198 protected function createCurl($remoteFile) {
199 $caConfig = CA_Config_Curl::probe(array(
200 'verify_peer' => (bool) CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL', NULL, TRUE),
201 ));
202
203 $ch = curl_init();
204 curl_setopt($ch, CURLOPT_URL, $remoteFile);
205 curl_setopt($ch, CURLOPT_HEADER, FALSE);
206 curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
207 curl_setopt($ch, CURLOPT_VERBOSE, 0);
208 if ($this->isRedirectSupported()) {
209 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
210 }
211 if ($this->connectionTimeout !== NULL) {
212 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout);
213 }
214 if (preg_match('/^https:/', $remoteFile) && $caConfig->isEnableSSL()) {
215 curl_setopt_array($ch, $caConfig->toCurlOptions());
216 }
217
218 return array($ch, $caConfig);
219 }
220
221 /**
222 * @return bool
223 */
224 public function isRedirectSupported() {
225 return (ini_get('open_basedir') == '') && (ini_get('safe_mode') == 'Off' || ini_get('safe_mode') == '' || ini_get('safe_mode') === FALSE);
226 }
227
228 }