Http.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <?php
  2. namespace Braintree;
  3. use finfo;
  4. /**
  5. * Braintree HTTP Client
  6. * processes Http requests using curl
  7. */
  8. class Http
  9. {
  10. protected $_config;
  11. private $_useClientCredentials = false;
  12. public function __construct($config)
  13. {
  14. $this->_config = $config;
  15. }
  16. public function delete($path, $params = null)
  17. {
  18. $response = $this->_doRequest('DELETE', $path, $this->_buildXml($params));
  19. $responseCode = $response['status'];
  20. if ($responseCode === 200 || $responseCode === 204) {
  21. return true;
  22. } else if ($responseCode === 422) {
  23. return Xml::buildArrayFromXml($response['body']);
  24. } else {
  25. Util::throwStatusCodeException($response['status']);
  26. }
  27. }
  28. public function get($path)
  29. {
  30. $response = $this->_doRequest('GET', $path);
  31. if ($response['status'] === 200) {
  32. return Xml::buildArrayFromXml($response['body']);
  33. } else {
  34. Util::throwStatusCodeException($response['status']);
  35. }
  36. }
  37. public function post($path, $params = null)
  38. {
  39. $response = $this->_doRequest('POST', $path, $this->_buildXml($params));
  40. $responseCode = $response['status'];
  41. if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) {
  42. return Xml::buildArrayFromXml($response['body']);
  43. } else {
  44. Util::throwStatusCodeException($responseCode);
  45. }
  46. }
  47. public function postMultipart($path, $params, $file)
  48. {
  49. $response = $this->_doRequest('POST', $path, $params, $file);
  50. $responseCode = $response['status'];
  51. if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) {
  52. return Xml::buildArrayFromXml($response['body']);
  53. } else {
  54. Util::throwStatusCodeException($responseCode);
  55. }
  56. }
  57. public function put($path, $params = null)
  58. {
  59. $response = $this->_doRequest('PUT', $path, $this->_buildXml($params));
  60. $responseCode = $response['status'];
  61. if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) {
  62. return Xml::buildArrayFromXml($response['body']);
  63. } else {
  64. Util::throwStatusCodeException($responseCode);
  65. }
  66. }
  67. private function _buildXml($params)
  68. {
  69. return empty($params) ? null : Xml::buildXmlFromArray($params);
  70. }
  71. private function _getHeaders()
  72. {
  73. return [
  74. 'Accept: application/xml',
  75. ];
  76. }
  77. private function _getAuthorization()
  78. {
  79. if ($this->_useClientCredentials) {
  80. return [
  81. 'user' => $this->_config->getClientId(),
  82. 'password' => $this->_config->getClientSecret(),
  83. ];
  84. } else if ($this->_config->isAccessToken()) {
  85. return [
  86. 'token' => $this->_config->getAccessToken(),
  87. ];
  88. } else {
  89. return [
  90. 'user' => $this->_config->getPublicKey(),
  91. 'password' => $this->_config->getPrivateKey(),
  92. ];
  93. }
  94. }
  95. public function useClientCredentials()
  96. {
  97. $this->_useClientCredentials = true;
  98. }
  99. private function _doRequest($httpVerb, $path, $requestBody = null, $file = null)
  100. {
  101. return $this->_doUrlRequest($httpVerb, $this->_config->baseUrl() . $path, $requestBody, $file);
  102. }
  103. public function _doUrlRequest($httpVerb, $url, $requestBody = null, $file = null)
  104. {
  105. $curl = curl_init();
  106. curl_setopt($curl, CURLOPT_TIMEOUT, $this->_config->timeout());
  107. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $httpVerb);
  108. curl_setopt($curl, CURLOPT_URL, $url);
  109. if ($this->_config->acceptGzipEncoding()) {
  110. curl_setopt($curl, CURLOPT_ENCODING, 'gzip');
  111. }
  112. if ($this->_config->sslVersion()) {
  113. curl_setopt($curl, CURLOPT_SSLVERSION, $this->_config->sslVersion());
  114. }
  115. $headers = $this->_getHeaders($curl);
  116. $headers[] = 'User-Agent: Braintree PHP Library ' . Version::get();
  117. $headers[] = 'X-ApiVersion: ' . Configuration::API_VERSION;
  118. $authorization = $this->_getAuthorization();
  119. if (isset($authorization['user'])) {
  120. curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
  121. curl_setopt($curl, CURLOPT_USERPWD, $authorization['user'] . ':' . $authorization['password']);
  122. } else if (isset($authorization['token'])) {
  123. $headers[] = 'Authorization: Bearer ' . $authorization['token'];
  124. }
  125. if ($this->_config->sslOn()) {
  126. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
  127. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
  128. curl_setopt($curl, CURLOPT_CAINFO, $this->getCaFile());
  129. }
  130. if (!empty($file)) {
  131. $boundary = "---------------------" . md5(mt_rand() . microtime());
  132. $headers[] = "Content-Type: multipart/form-data; boundary={$boundary}";
  133. $this->prepareMultipart($curl, $requestBody, $file, $boundary);
  134. } else if (!empty($requestBody)) {
  135. $headers[] = 'Content-Type: application/xml';
  136. curl_setopt($curl, CURLOPT_POSTFIELDS, $requestBody);
  137. }
  138. if ($this->_config->isUsingProxy()) {
  139. $proxyHost = $this->_config->getProxyHost();
  140. $proxyPort = $this->_config->getProxyPort();
  141. $proxyType = $this->_config->getProxyType();
  142. $proxyUser = $this->_config->getProxyUser();
  143. $proxyPwd= $this->_config->getProxyPassword();
  144. curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort);
  145. if (!empty($proxyType)) {
  146. curl_setopt($curl, CURLOPT_PROXYTYPE, $proxyType);
  147. }
  148. if ($this->_config->isAuthenticatedProxy()) {
  149. curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUser . ':' . $proxyPwd);
  150. }
  151. }
  152. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  153. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  154. $response = curl_exec($curl);
  155. $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  156. $error_code = curl_errno($curl);
  157. $error = curl_error($curl);
  158. if ($error_code == 28 && $httpStatus == 0) {
  159. throw new Exception\Timeout();
  160. }
  161. curl_close($curl);
  162. if ($this->_config->sslOn()) {
  163. if ($httpStatus == 0) {
  164. throw new Exception\SSLCertificate($error, $error_code);
  165. }
  166. } else if ($error_code) {
  167. throw new Exception\Connection($error, $error_code);
  168. }
  169. return ['status' => $httpStatus, 'body' => $response];
  170. }
  171. function prepareMultipart($ch, $requestBody, $file, $boundary) {
  172. $disallow = ["\0", "\"", "\r", "\n"];
  173. $fileInfo = new finfo(FILEINFO_MIME_TYPE);
  174. $filePath = stream_get_meta_data($file)['uri'];
  175. $data = file_get_contents($filePath);
  176. $mimeType = $fileInfo->buffer($data);
  177. // build normal parameters
  178. foreach ($requestBody as $k => $v) {
  179. $k = str_replace($disallow, "_", $k);
  180. $body[] = implode("\r\n", [
  181. "Content-Disposition: form-data; name=\"{$k}\"",
  182. "",
  183. filter_var($v),
  184. ]);
  185. }
  186. // build file parameter
  187. $splitFilePath = explode(DIRECTORY_SEPARATOR, $filePath);
  188. $filePath = end($splitFilePath);
  189. $filePath = str_replace($disallow, "_", $filePath);
  190. $body[] = implode("\r\n", [
  191. "Content-Disposition: form-data; name=\"file\"; filename=\"{$filePath}\"",
  192. "Content-Type: {$mimeType}",
  193. "",
  194. $data,
  195. ]);
  196. // add boundary for each parameters
  197. array_walk($body, function (&$part) use ($boundary) {
  198. $part = "--{$boundary}\r\n{$part}";
  199. });
  200. // add final boundary
  201. $body[] = "--{$boundary}--";
  202. $body[] = "";
  203. // set options
  204. return curl_setopt_array($ch, [
  205. CURLOPT_POST => true,
  206. CURLOPT_POSTFIELDS => implode("\r\n", $body)
  207. ]);
  208. }
  209. private function getCaFile()
  210. {
  211. static $memo;
  212. if ($memo === null) {
  213. $caFile = $this->_config->caFile();
  214. if (substr($caFile, 0, 7) !== 'phar://') {
  215. return $caFile;
  216. }
  217. $extractedCaFile = sys_get_temp_dir() . '/api_braintreegateway_com.ca.crt';
  218. if (!file_exists($extractedCaFile) || sha1_file($extractedCaFile) != sha1_file($caFile)) {
  219. if (!copy($caFile, $extractedCaFile)) {
  220. throw new Exception\SSLCaFileNotFound();
  221. }
  222. }
  223. $memo = $extractedCaFile;
  224. }
  225. return $memo;
  226. }
  227. }
  228. class_alias('Braintree\Http', 'Braintree_Http');