1: <?php
2:
3: namespace Mypos\IPC;
4:
5: /**
6: * Base API Class. Contains basic API-connection methods.
7: */
8: abstract class Base
9: {
10: /**
11: * @var string Output format from API for some requests may be XML or JSON
12: */
13: protected $outputFormat = Defines::COMMUNICATION_FORMAT_JSON;
14: /**
15: * @var Config
16: */
17: private $cnf;
18: /**
19: * @var array Params for API Request
20: */
21: private $params = [];
22:
23: /**
24: * Verify signature of API Request params against the API public key
25: *
26: * @param string $data Signed data
27: * @param string $signature Signature in base64 format
28: * @param string $pubKey API public key
29: *
30: * @return boolean
31: */
32: public static function isValidSignature($data, $signature, $pubKey)
33: {
34: $pubKeyId = openssl_get_publickey($pubKey);
35: $res = openssl_verify($data, base64_decode($signature), $pubKeyId, Defines::SIGNATURE_ALGO);
36: openssl_free_key($pubKeyId);
37: if ($res != 1) {
38: return false;
39: }
40:
41: return true;
42: }
43:
44: /**
45: * Return current set output format for API Requests
46: *
47: * @return string
48: */
49: function getOutputFormat()
50: {
51: return $this->outputFormat;
52: }
53:
54: /**
55: * Set current set output format for API Requests
56: *
57: * @param string $outputFormat
58: */
59: function setOutputFormat($outputFormat)
60: {
61: $this->outputFormat = $outputFormat;
62: }
63:
64: /**
65: * Add API request param
66: *
67: * @param string $paramName
68: * @param string $paramValue
69: * @param bool $encrypt
70: */
71: protected function _addPostParam($paramName, $paramValue, $encrypt = false)
72: {
73: $this->params[$paramName] = $encrypt ? $this->_encryptData($paramValue) : Helper::escape(Helper::unescape($paramValue));
74: }
75:
76: /**
77: * Create signature of API Request params against the SID private key
78: *
79: * @param string $data
80: *
81: * @return string base64 encoded signature
82: */
83: private function _encryptData($data)
84: {
85: openssl_public_encrypt($data, $crypted, $this->getCnf()->getEncryptPublicKey(), Defines::ENCRYPT_PADDING);
86:
87: return base64_encode($crypted);
88: }
89:
90: /**
91: * Return IPC\Config object with current IPC configuration
92: *
93: * @return Config
94: */
95: protected function getCnf()
96: {
97: return $this->cnf;
98: }
99:
100: /**
101: * Set Config object with current IPC configuration
102: *
103: * @param Config $cnf
104: */
105: protected function setCnf(Config $cnf)
106: {
107: $this->cnf = $cnf;
108: }
109:
110: /**
111: * Generate HTML form with POST params and auto-submit it
112: */
113: protected function _processHtmlPost()
114: {
115: #Add request signature
116: $this->params['Signature'] = $this->_createSignature();
117:
118: $c = '<body onload="document.ipcForm.submit();">';
119: $c .= '<form id="ipcForm" name="ipcForm" action="'.$this->getCnf()->getIpcURL().'" method="post">';
120: foreach ($this->params as $k => $v) {
121: $c .= "<input type=\"hidden\" name=\"".$k."\" value=\"".$v."\" />\n";
122: }
123: $c .= '</form></body>';
124: echo $c;
125: exit;
126: }
127:
128: /**
129: * Generate HTML form with POST params and auto-submit it
130: */
131: protected function _buildArrayParameters($escapeParameters = false)
132: {
133: $formParameters['ActionUrl'] = $this->getCnf()->getIpcURL();
134: $formParameters['FormData'] = $escapeParameters ? $this->params : array_map(function ($formData) {
135: return Helper::unescape($formData);
136: }, $this->params);
137:
138: #Add request signature
139: $formParameters['FormData']['Signature'] = $this->_createSignature();
140:
141: return $formParameters;
142: }
143:
144: /**
145: * Create signature of API Request params against the SID private key
146: *
147: * @return string base64 encoded signature
148: */
149: private function _createSignature()
150: {
151: $params = $this->params;
152: foreach ($params as $k => $v) {
153: $params[$k] = Helper::unescape($v);
154: }
155: $concData = base64_encode(implode('-', $params));
156: $privKey = openssl_get_privatekey($this->getCnf()->getPrivateKey());
157: openssl_sign($concData, $signature, $privKey, Defines::SIGNATURE_ALGO);
158:
159: return base64_encode($signature);
160: }
161:
162: /**
163: * Send POST Request to API and returns Response object with validated response data
164: *
165: * @return Response
166: * @throws IPC_Exception
167: */
168: protected function _processPost()
169: {
170: $this->params['Signature'] = $this->_createSignature();
171: $url = parse_url($this->getCnf()->getIpcURL());
172: $ssl = "";
173: if (!isset($url['port'])) {
174: if ($url['scheme'] == 'https') {
175: $url['port'] = 443;
176: $ssl = "ssl://";
177: } else {
178: $url['port'] = 80;
179: }
180: }
181: $postData = http_build_query($this->params);
182: $fp = @fsockopen($ssl.$url['host'], $url['port'], $errno, $errstr, 10);
183: if (!$fp) {
184: throw new IPC_Exception('Error connecting IPC URL');
185: } else {
186: $eol = "\r\n";
187: $path = $url['path'].(!(empty($url['query'])) ? ('?'.$url['query']) : '');
188: fwrite($fp, "POST ".$path." HTTP/1.1".$eol);
189: fwrite($fp, "Host: ".$url['host'].$eol);
190: fwrite($fp, "Content-type: application/x-www-form-urlencoded".$eol);
191: fwrite($fp, "Content-length: ".strlen($postData).$eol);
192: fwrite($fp, "Connection: close".$eol.$eol);
193: fwrite($fp, $postData.$eol.$eol);
194:
195: $result = '';
196: while (!feof($fp)) {
197: $result .= @fgets($fp, 1024);
198: }
199: fclose($fp);
200: $result = explode($eol.$eol, $result, 2);
201: $header = isset($result[0]) ? $result[0] : '';
202: $cont = isset($result[1]) ? $result[1] : '';
203:
204: #Проверявам за Transfer-Encoding: chunked
205: if (!empty($cont) && strpos($header, 'Transfer-Encoding: chunked') !== false) {
206: $check = $this->_httpChunkedDecode($cont);
207: if ($check) {
208: $cont = $check;
209: }
210: }
211: if ($cont) {
212: $cont = trim($cont);
213: }
214:
215: return Response::getInstance($this->getCnf(), $cont, $this->outputFormat);
216: }
217: }
218:
219: /**
220: * Alternative of php http-chunked-decode function
221: *
222: * @param string $chunk
223: *
224: * @return mixed
225: */
226: private function _httpChunkedDecode($chunk)
227: {
228: $pos = 0;
229: $len = strlen($chunk);
230: $dechunk = null;
231:
232: while (($pos < $len) && ($chunkLenHex = substr($chunk, $pos, ($newlineAt = strpos($chunk, "\n", $pos + 1)) - $pos))) {
233: if (!$this->_is_hex($chunkLenHex)) {
234: return false;
235: }
236:
237: $pos = $newlineAt + 1;
238: $chunkLen = hexdec(rtrim($chunkLenHex, "\r\n"));
239: $dechunk .= substr($chunk, $pos, $chunkLen);
240: $pos = strpos($chunk, "\n", $pos + $chunkLen) + 1;
241: }
242:
243: return $dechunk;
244: }
245:
246: /**
247: * determine if a string can represent a number in hexadecimal
248: *
249: * @param string $hex
250: *
251: * @return boolean true if the string is a hex, otherwise false
252: */
253: private function _is_hex($hex)
254: {
255: // regex is for weenies
256: $hex = strtolower(trim(ltrim($hex, "0")));
257: if (empty($hex)) {
258: $hex = 0;
259: };
260: $dec = hexdec($hex);
261:
262: return ($hex == dechex($dec));
263: }
264: }
265: