Passed
Push — master ( 898805...98aed2 )
by Roberto
04:33 queued 02:14
created

SoapBase::removeTemporarilyFiles()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 26.1045

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 3
cts 17
cp 0.1765
rs 8.5125
c 0
b 0
f 0
cc 6
eloc 16
nc 5
nop 0
crap 26.1045
1
<?php
2
namespace NFePHP\Common\Soap;
3
4
use NFePHP\Common\Certificate;
5
use NFePHP\Common\Exception\RuntimeException;
6
use NFePHP\Common\Strings;
7
use League\Flysystem\Filesystem;
8
use League\Flysystem\Adapter\Local;
9
use Psr\Log\LoggerInterface;
10
11
/**
12
 * Soap base class
13
 *
14
 * @category  NFePHP
15
 * @package   NFePHP\Common\Soap\SoapBase
16
 * @copyright NFePHP Copyright (c) 2017
17
 * @author    Roberto L. Machado <linux.rlm at gmail dot com>
18
 * @license   http://www.gnu.org/licenses/lgpl.txt LGPLv3+
19
 * @license   https://opensource.org/licenses/MIT MIT
20
 * @license   http://www.gnu.org/licenses/gpl.txt GPLv3+
21
 * @link      http://github.com/nfephp-org/sped-nfse for the canonical source repository
22
 */
23
abstract class SoapBase implements SoapInterface
24
{
25
    /**
26
     * @var int
27
     */
28
    protected $soapprotocol = self::SSL_DEFAULT;
29
    /**
30
     * @var int
31
     */
32
    protected $soaptimeout = 20;
33
    /**
34
     * @var string
35
     */
36
    protected $proxyIP;
37
    /**
38
     * @var int
39
     */
40
    protected $proxyPort;
41
    /**
42
     * @var string
43
     */
44
    protected $proxyUser;
45
    /**
46
     * @var string
47
     */
48
    protected $proxyPass;
49
    /**
50
     * @var array
51
     */
52
    protected $prefixes = [1 => 'soapenv', 2 => 'soap'];
53
    /**
54
     * @var Certificate
55
     */
56
    protected $certificate;
57
    /**
58
     * @var LoggerInterface|null
59
     */
60
    protected $logger;
61
    /**
62
     * @var string
63
     */
64
    protected $tempdir;
65
    /**
66
     * @var string
67
     */
68
    protected $certsdir;
69
    /**
70
     * @var string
71
     */
72
    protected $debugdir;
73
    /**
74
     * @var string
75
     */
76
    protected $prifile;
77
    /**
78
     * @var string
79
     */
80
    protected $pubfile;
81
    /**
82
     * @var string
83
     */
84
    protected $certfile;
85
    /**
86
     * @var string
87
     */
88
    protected $casefaz;
89
    /**
90
     * @var bool
91
     */
92
    protected $disablesec = false;
93
    /**
94
     * @var bool
95
     */
96
    protected $disableCertValidation = false;
97
    /**
98
     * @var \League\Flysystem\Adapter\Local
99
     */
100
    protected $adapter;
101
    /**
102
     * @var \League\Flysystem\Filesystem
103
     */
104
    protected $filesystem;
105
    /**
106
     * @var string
107
     */
108
    protected $temppass = '';
109
    /**
110
     * @var bool
111
     */
112
    protected $encriptPrivateKey = false;
113
    /**
114
     * @var bool
115
     */
116
    protected $debugmode = false;
117
    /**
118
     * @var string
119
     */
120
    public $responseHead;
121
    /**
122
     * @var string
123
     */
124
    public $responseBody;
125
    /**
126
     * @var string
127
     */
128
    public $requestHead;
129
    /**
130
     * @var string
131
     */
132
    public $requestBody;
133
    /**
134
     * @var string
135
     */
136
    public $soaperror;
137
    /**
138
     * @var array
139
     */
140
    public $soapinfo = [];
141
    /**
142
     * @var int
143
     */
144
    public $waitingTime = 45;
145
146
    /**
147
     * SoapBase constructor.
148
     * @param Certificate|null $certificate
149
     * @param LoggerInterface|null $logger
150
     */
151 5
    public function __construct(
152
        Certificate $certificate = null,
153
        LoggerInterface $logger = null
154
    ) {
155 5
        $this->logger = $logger;
156 5
        $this->loadCertificate($certificate);
157 4
    }
158
159
    /**
160
     * Check if certificate is valid to currently used date
161
     * @param Certificate $certificate
162
     * @return void
163
     * @throws Certificate\Exception\Expired
164
     */
165 5
    private function isCertificateExpired(Certificate $certificate = null)
166
    {
167 5
        if (!$this->disableCertValidation) {
168 5
            if (null !== $certificate && $certificate->isExpired()) {
169 1
                throw new Certificate\Exception\Expired($certificate);
170
            }
171
        }
172 4
    }
173
174
    /**
175
     * Destructor
176
     * Clean temporary files
177
     */
178 4
    public function __destruct()
179
    {
180 4
        $this->removeTemporarilyFiles();
181 4
    }
182
183
    /**
184
     * Disables the security checking of host and peer certificates
185
     * @param bool $flag
186
     * @return bool
187
     */
188 1
    public function disableSecurity($flag = false)
189
    {
190 1
        return $this->disablesec = $flag;
191
    }
192
193
    /**
194
     * ONlY for tests
195
     * @param bool $flag
196
     * @return bool
197
     */
198 1
    public function disableCertValidation($flag = true)
199
    {
200 1
        return $this->disableCertValidation = $flag;
201
    }
202
203
    /**
204
     * Load path to CA and enable to use on SOAP
205
     * @param string $capath
206
     * @return void
207
     */
208
    public function loadCA($capath)
209
    {
210
        if (is_file($capath)) {
211
            $this->casefaz = $capath;
212
        }
213
    }
214
215
    /**
216
     * Set option to encrypt private key before save in filesystem
217
     * for an additional layer of protection
218
     * @param bool $encript
219
     * @return bool
220
     */
221
    public function setEncriptPrivateKey($encript = true)
222
    {
223
        $this->encriptPrivateKey = $encript;
224
        return $this->encriptPrivateKey;
225
    }
226
   
227
    /**
228
     * Set another temporayfolder for saving certificates for SOAP utilization
229
     * @param string | null $folderRealPath
230
     * @return void
231
     */
232
    public function setTemporaryFolder($folderRealPath = null)
233
    {
234
        if (empty($folderRealPath)) {
235
            $path = '/sped-'
236
                . $this->uid()
237
                .'/'
238
                . $this->certificate->getCnpj()
239
                . '/' ;
240
            $folderRealPath = sys_get_temp_dir().$path;
241
        }
242
        if (substr($folderRealPath, -1) !== '/') {
243
            $folderRealPath .= '/';
244
        }
245
        $this->tempdir = $folderRealPath;
246
        $this->setLocalFolder($folderRealPath);
247
    }
248
    
249
    /**
250
     * Return uid from user
251
     * @return string
252
     */
253
    protected function uid()
254
    {
255
        if (function_exists('posix_getuid')) {
256
            return posix_getuid();
257
        } else {
258
            return getmyuid();
259
        }
260
    }
261
 
262
    /**
263
     * Set Local folder for flysystem
264
     * @param string $folder
265
     */
266
    protected function setLocalFolder($folder = '')
267
    {
268
        $this->adapter = new Local($folder);
269
        $this->filesystem = new Filesystem($this->adapter);
270
    }
271
272
    /**
273
     * Set debug mode, this mode will save soap envelopes in temporary directory
274
     * @param bool $value
275
     * @return bool
276
     */
277
    public function setDebugMode($value = false)
278
    {
279
        return $this->debugmode = $value;
280
    }
281
282
    /**
283
     * Set certificate class for SSL communications
284
     * @param Certificate $certificate
285
     * @return void
286
     */
287 5
    public function loadCertificate(Certificate $certificate = null)
288
    {
289 5
        $this->isCertificateExpired($certificate);
290 4
        if (null !== $certificate) {
291 1
            $this->certificate = $certificate;
292
        }
293 4
    }
294
295
    /**
296
     * Set logger class
297
     * @param LoggerInterface $logger
298
     * @return LoggerInterface
299
     */
300
    public function loadLogger(LoggerInterface $logger)
301
    {
302
        return $this->logger = $logger;
303
    }
304
305
    /**
306
     * Set timeout for communication
307
     * @param int $timesecs
308
     * @return int
309
     */
310
    public function timeout($timesecs)
311
    {
312
        return $this->soaptimeout = $timesecs;
313
    }
314
315
    /**
316
     * Set security protocol
317
     * @param int $protocol
318
     * @return int
319
     */
320
    public function protocol($protocol = self::SSL_DEFAULT)
321
    {
322
        return $this->soapprotocol = $protocol;
323
    }
324
325
    /**
326
     * Set prefixes
327
     * @param array $prefixes
328
     * @return string[]
329
     */
330
    public function setSoapPrefix($prefixes = [])
331
    {
332
        return $this->prefixes = $prefixes;
333
    }
334
335
    /**
336
     * Set proxy parameters
337
     * @param string $ip
338
     * @param int    $port
339
     * @param string $user
340
     * @param string $password
341
     * @return void
342
     */
343
    public function proxy($ip, $port, $user, $password)
344
    {
345
        $this->proxyIP = $ip;
346
        $this->proxyPort = $port;
347
        $this->proxyUser = $user;
348
        $this->proxyPass = $password;
349
    }
350
351
    /**
352
     * @param string $url
353
     * @param string $operation
354
     * @param string $action
355
     * @param int $soapver
356
     * @param array $parameters
357
     * @param array $namespaces
358
     * @param string $request
359
     * @param null $soapheader
360
     * @return mixed
361
     */
362
    abstract public function send(
363
        $url,
364
        $operation = '',
365
        $action = '',
366
        $soapver = SOAP_1_2,
367
        $parameters = [],
368
        $namespaces = [],
369
        $request = '',
370
        $soapheader = null
371
    );
372
373
    /**
374
     * Mount soap envelope
375
     * @param string $request
376
     * @param array $namespaces
377
     * @param int $soapVer
378
     * @param \SoapHeader $header
379
     * @return string
380
     */
381
    protected function makeEnvelopeSoap(
382
        $request,
383
        $namespaces,
384
        $soapVer = SOAP_1_2,
385
        $header = null
386
    ) {
387
        $prefix = $this->prefixes[$soapVer];
388
        $envelopeAttributes = $this->getStringAttributes($namespaces);
389
        return $this->mountEnvelopString(
390
            $prefix,
391
            $envelopeAttributes,
392
            $this->mountSoapHeaders($prefix, $header),
393
            $request
394
        );
395
    }
396
397
    /**
398
     * Create a envelop string
399
     * @param string $envelopPrefix
400
     * @param string $envelopAttributes
401
     * @param string $header
402
     * @param string $bodyContent
403
     * @return string
404
     */
405
    private function mountEnvelopString(
406
        $envelopPrefix,
407
        $envelopAttributes = '',
408
        $header = '',
409
        $bodyContent = ''
410
    ) {
411
        return sprintf(
412
            '<%s:Envelope %s>' . $header . '<%s:Body>%s</%s:Body></%s:Envelope>',
413
            $envelopPrefix,
414
            $envelopAttributes,
415
            $envelopPrefix,
416
            $bodyContent,
417
            $envelopPrefix,
418
            $envelopPrefix
419
        );
420
    }
421
422
    /**
423
     * Create a haeader tag
424
     * @param string $envelopPrefix
425
     * @param \SoapHeader $header
426
     * @return string
427
     */
428
    private function mountSoapHeaders($envelopPrefix, $header = null)
429
    {
430
        if (null === $header) {
431
            return '';
432
        }
433
        $headerItems = '';
434
        foreach ($header->data as $key => $value) {
0 ignored issues
show
Bug introduced by
The property data does not seem to exist in SoapHeader.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
435
            $headerItems .= '<' . $key . '>' . $value . '</' . $key . '>';
436
        }
437
        return sprintf(
438
            '<%s:Header><%s xmlns="%s">%s</%s></%s:Header>',
439
            $envelopPrefix,
440
            $header->name,
0 ignored issues
show
Bug introduced by
The property name does not seem to exist in SoapHeader.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
441
            $header->namespace === null ? '' : $header->namespace,
0 ignored issues
show
Bug introduced by
The property namespace does not seem to exist in SoapHeader.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
442
            $headerItems,
443
            $header->name,
444
            $envelopPrefix
445
        );
446
    }
447
448
    /**
449
     * Get attributes
450
     * @param array $namespaces
451
     * @return string
452
     */
453
    private function getStringAttributes($namespaces = [])
454
    {
455
        $envelopeAttributes = '';
456
        foreach ($namespaces as $key => $value) {
457
            $envelopeAttributes .= $key . '="' . $value . '" ';
458
        }
459
        return $envelopeAttributes;
460
    }
461
462
    /**
463
     * Temporarily saves the certificate keys for use cURL or SoapClient
464
     * @return void
465
     */
466
    public function saveTemporarilyKeyFiles()
467
    {
468
        //certs already exists
469
        if (!empty($this->certsdir)) {
470
            return;
471
        }
472
        if (!is_object($this->certificate)) {
473
            throw new RuntimeException(
474
                'Certificate not found.'
475
            );
476
        }
477
        if (empty($this->filesystem)) {
478
            $this->setTemporaryFolder();
479
        }
480
        //clear dir cert
481
        $this->removeTemporarilyFiles();
482
        $this->certsdir = 'certs/';
483
        $this->prifile = $this->randomName();
484
        $this->pubfile = $this->randomName();
485
        $this->certfile = $this->randomName();
486
        $ret = true;
487
        //load private key pem
488
        $private = $this->certificate->privateKey;
489
        if ($this->encriptPrivateKey) {
490
            //replace private key pem with password
491
            $this->temppass = Strings::randomString(16);
492
            //encripta a chave privada entes da gravação do filesystem
493
            openssl_pkey_export(
494
                $this->certificate->privateKey,
495
                $private,
496
                $this->temppass
497
            );
498
        }
499
        $ret &= $this->filesystem->put(
500
            $this->prifile,
501
            $private
502
        );
503
        $ret &= $this->filesystem->put(
504
            $this->pubfile,
505
            $this->certificate->publicKey
506
        );
507
        $ret &= $this->filesystem->put(
508
            $this->certfile,
509
            $private . "{$this->certificate}"
510
        );
511
        if (!$ret) {
512
            throw new RuntimeException(
513
                'Unable to save temporary key files in folder.'
514
            );
515
        }
516
    }
517
    
518
    /**
519
     * Create a unique random file name
520
     * @param integer $n
521
     * @return string
522
     */
523
    protected function randomName($n = 10)
524
    {
525
        $name = $this->certsdir . Strings::randomString($n) . '.pem';
526
        if (!$this->filesystem->has($name)) {
527
            return $name;
528
        }
529
        $this->randomName($n+5);
530
    }
531
532
    /**
533
     * Delete all files in folder
534
     * @return void
535
     */
536 4
    public function removeTemporarilyFiles()
537
    {
538 4
        if (empty($this->filesystem) || empty($this->certsdir)) {
539 4
            return;
540
        }
541
        //remove os certificados
542
        $this->filesystem->delete($this->certfile);
543
        $this->filesystem->delete($this->prifile);
544
        $this->filesystem->delete($this->pubfile);
545
        //remove todos os arquivos antigos
546
        $contents = $this->filesystem->listContents($this->certsdir, true);
547
        $dt = new \DateTime();
548
        $tint = new \DateInterval("PT".$this->waitingTime."M");
549
        $tint->invert = 1;
550
        $tsLimit = $dt->add($tint)->getTimestamp();
551
        foreach ($contents as $item) {
552
            if ($item['type'] == 'file') {
553
                $timestamp = $this->filesystem->getTimestamp($item['path']);
554
                if ($timestamp < $tsLimit) {
555
                    $this->filesystem->delete($item['path']);
556
                }
557
            }
558
        }
559
    }
560
561
    /**
562
     * Save request envelope and response for debug reasons
563
     * @param string $operation
564
     * @param string $request
565
     * @param string $response
566
     * @return void
567
     */
568
    public function saveDebugFiles($operation, $request, $response)
569
    {
570
        if (!$this->debugmode) {
571
            return;
572
        }
573
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
574
        $now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
575
        $time = substr($now->format("ymdHisu"), 0, 16);
576
        try {
577
            $this->filesystem->put(
578
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
579
                $request
580
            );
581
            $this->filesystem->put(
582
                $this->debugdir . $time . "_" . $operation . "_res.txt",
583
                $response
584
            );
585
        } catch (\Exception $e) {
586
            throw new RuntimeException(
587
                'Unable to create debug files.'
588
            );
589
        }
590
    }
591
}
592