Passed
Pull Request — master (#214)
by
unknown
02:14
created

SoapBase::setTemporaryFolder()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 12
cp 0
rs 9.7333
c 0
b 0
f 0
cc 4
nc 6
nop 1
crap 20
1
<?php
2
3
namespace NFePHP\Common\Soap;
4
5
use NFePHP\Common\Certificate;
6
use NFePHP\Common\Exception\RuntimeException;
7
use NFePHP\Common\Strings;
8
use League\Flysystem\Filesystem;
9
use League\Flysystem\Adapter\Local;
10
use Psr\Log\LoggerInterface;
11
12
/**
13
 * Soap base class
14
 *
15
 * @category  NFePHP
16
 * @package   NFePHP\Common\Soap\SoapBase
17
 * @copyright NFePHP Copyright (c) 2017-2019
18
 * @author    Roberto L. Machado <linux.rlm at gmail dot com>
19
 * @license   http://www.gnu.org/licenses/lgpl.txt LGPLv3+
20
 * @license   https://opensource.org/licenses/MIT MIT
21
 * @license   http://www.gnu.org/licenses/gpl.txt GPLv3+
22
 * @link      http://github.com/nfephp-org/sped-nfse for the canonical source repository
23
 */
24
abstract class SoapBase implements SoapInterface
25
{
26
    /**
27
     * @var int
28
     */
29
    protected $soapprotocol = self::SSL_DEFAULT;
30
    /**
31
     * @var int
32
     */
33
    protected $soaptimeout = 20;
34
    /**
35
     * @var string
36
     */
37
    protected $proxyIP;
38
    /**
39
     * @var int
40
     */
41
    protected $proxyPort;
42
    /**
43
     * @var string
44
     */
45
    protected $proxyUser;
46
    /**
47
     * @var string
48
     */
49
    protected $proxyPass;
50
    /**
51
     * @var array
52
     */
53
    protected $prefixes = [1 => 'soapenv', 2 => 'soap'];
54
    /**
55
     * @var Certificate
56
     */
57
    protected $certificate;
58
    /**
59
     * @var LoggerInterface|null
60
     */
61
    protected $logger;
62
    /**
63
     * @var string
64
     */
65
    protected $tempdir;
66
    /**
67
     * @var string
68
     */
69
    protected $certsdir;
70
    /**
71
     * @var string
72
     */
73
    protected $debugdir;
74
    /**
75
     * @var string
76
     */
77
    protected $prifile;
78
    /**
79
     * @var string
80
     */
81
    protected $pubfile;
82
    /**
83
     * @var string
84
     */
85
    protected $certfile;
86
    /**
87
     * @var string
88
     */
89
    protected $casefaz;
90
    /**
91
     * @var bool
92
     */
93
    protected $disablesec = false;
94
    /**
95
     * @var bool
96
     */
97
    protected $disableCertValidation = false;
98
    /**
99
     * @var \League\Flysystem\Adapter\Local
100
     */
101
    protected $adapter;
102
    /**
103
     * @var \League\Flysystem\Filesystem
104
     */
105
    protected $filesystem;
106
    /**
107
     * @var string
108
     */
109
    protected $temppass = '';
110
    /**
111
     * @var bool
112
     */
113
    protected $encriptPrivateKey = false;
114
    /**
115
     * @var integer
116
     */
117
    protected $httpver;
118
    /**
119
     * @var bool
120
     */
121
    protected $debugmode = false;
122
    /**
123
     * @var string
124
     */
125
    public $responseHead;
126
    /**
127
     * @var string
128
     */
129
    public $responseBody;
130
    /**
131
     * @var string
132
     */
133
    public $requestHead;
134
    /**
135
     * @var string
136
     */
137
    public $requestBody;
138
    /**
139
     * @var string
140
     */
141
    public $soaperror;
142
    /**
143
     * @var array
144
     */
145
    public $soapinfo = [];
146
    /**
147
     * @var int
148
     */
149
    public $waitingTime = 45;
150
151
    /**
152
     * SoapBase constructor.
153
     * @param Certificate|null $certificate
154
     * @param LoggerInterface|null $logger
155
     */
156 5
    public function __construct(
157
        Certificate $certificate = null,
158
        LoggerInterface $logger = null
159
    ) {
160 5
        $this->logger = $logger;
161 5
        $this->loadCertificate($certificate);
162 4
    }
163
164
    /**
165
     * Check if certificate is valid to currently used date
166
     * @param Certificate $certificate
167
     * @return void
168
     * @throws Certificate\Exception\Expired
169
     */
170 5
    private function isCertificateExpired(Certificate $certificate = null)
171
    {
172 5
        if (!$this->disableCertValidation) {
173 5
            if (null !== $certificate && $certificate->isExpired()) {
174 1
                throw new Certificate\Exception\Expired($certificate);
175
            }
176
        }
177 4
    }
178
179
    /**
180
     * Destructor
181
     * Clean temporary files
182
     */
183 4
    public function __destruct()
184
    {
185 4
        $this->removeTemporarilyFiles();
186 4
    }
187
188
    /**
189
     * Disables the security checking of host and peer certificates
190
     * @param bool $flag
191
     * @return bool
192
     */
193 1
    public function disableSecurity($flag = false)
194
    {
195 1
        return $this->disablesec = $flag;
196
    }
197
198
    /**
199
     * ONlY for tests
200
     * @param bool $flag
201
     * @return bool
202
     */
203 1
    public function disableCertValidation($flag = true)
204
    {
205 1
        return $this->disableCertValidation = $flag;
206
    }
207
208
    /**
209
     * Force http protocol version
210
     *
211
     * @param null|string $version
212
     */
213
    public function httpVersion($version = null)
214
    {
215
        switch ($version) {
216
            case '1.0':
217
                $this->httpver = CURL_HTTP_VERSION_1_0;
218
                break;
219
            case '1.1':
220
                $this->httpver = CURL_HTTP_VERSION_1_1;
221
                break;
222
            case '2.0':
223
                $this->httpver = CURL_HTTP_VERSION_2_0;
224
                break;
225
            default:
226
                $this->httpver = CURL_HTTP_VERSION_NONE;
227
        }
228
    }
229
230
    /**
231
     * Load path to CA and enable to use on SOAP
232
     * @param string $capath
233
     * @return void
234
     */
235
    public function loadCA($capath)
236
    {
237
        if (is_file($capath)) {
238
            $this->casefaz = $capath;
239
        }
240
    }
241
242
    /**
243
     * Set option to encrypt private key before save in filesystem
244
     * for an additional layer of protection
245
     * @param bool $encript
246
     * @return bool
247
     */
248
    public function setEncriptPrivateKey($encript = true)
249
    {
250
        $this->encriptPrivateKey = $encript;
251
        return $this->encriptPrivateKey;
252
    }
253
254
    /**
255
     * Return J or F from existing type in ASN.1 certificate
256
     * J - pessoa juridica (CNPJ)
257
     * F - pessoa física (CPF)
258
     * @return string
259
     */
260
    public function getTypeOfPersonFromCertificate()
261
    {
262
        $cnpj = $this->certificate->getCnpj();
263
        $type = 'J';
264
        if (substr($cnpj, 0, 1) === 'N') {
265
            //não é CNPJ, então verificar se é CPF
266
            $cpf = $this->certificate->getCpf();
267
            if (substr($cpf, 0, 1) !== 'N') {
268
                $type = 'F';
269
            } else {
270
                //não foi localizado nem CNPJ e nem CPF esse certificado não é usável
271
                //throw new RuntimeException('Faltam elementos CNPJ/CPF no certificado digital.');
272
                $type = '';
273
            }
274
        }
275
        return $type;
276
    }
277
278
    /**
279
     * Set another temporayfolder for saving certificates for SOAP utilization
280
     * @param string | null $folderRealPath
281
     * @return void
282
     */
283
    public function setTemporaryFolder($folderRealPath = null)
284
    {
285
        if (empty($folderRealPath)) {
286
            $path = '/sped-'
287
                . $this->uid()
288
                . '/'
289
                . ($this->getTypeOfPersonFromCertificate() == 'J' ? $this->certificate->getCnpj() : $this->certificate->getCpf())
290
                . '/';
291
            $folderRealPath = sys_get_temp_dir() . $path;
292
        }
293
        if (substr($folderRealPath, -1) !== '/') {
294
            $folderRealPath .= '/';
295
        }
296
        $this->tempdir = $folderRealPath;
297
        $this->setLocalFolder($folderRealPath);
298
    }
299
300
    /**
301
     * Return uid from user
302
     * @return string
303
     */
304
    protected function uid()
305
    {
306
        if (function_exists('posix_getuid')) {
307
            return posix_getuid();
308
        } else {
309
            return getmyuid();
310
        }
311
    }
312
313
    /**
314
     * Set Local folder for flysystem
315
     * @param string $folder
316
     */
317
    protected function setLocalFolder($folder = '')
318
    {
319
        $this->adapter = new Local($folder);
320
        $this->filesystem = new Filesystem($this->adapter);
321
    }
322
323
    /**
324
     * Set debug mode, this mode will save soap envelopes in temporary directory
325
     * @param bool $value
326
     * @return bool
327
     */
328
    public function setDebugMode($value = false)
329
    {
330
        return $this->debugmode = $value;
331
    }
332
333
    /**
334
     * Set certificate class for SSL communications
335
     * @param Certificate $certificate
336
     * @return void
337
     */
338 5
    public function loadCertificate(Certificate $certificate = null)
339
    {
340 5
        $this->isCertificateExpired($certificate);
341 4
        if (null !== $certificate) {
342 1
            $this->certificate = $certificate;
343
        }
344 4
    }
345
346
    /**
347
     * Set logger class
348
     * @param LoggerInterface $logger
349
     * @return LoggerInterface
350
     */
351
    public function loadLogger(LoggerInterface $logger)
352
    {
353
        return $this->logger = $logger;
354
    }
355
356
    /**
357
     * Set timeout for communication
358
     * @param int $timesecs
359
     * @return int
360
     */
361
    public function timeout($timesecs)
362
    {
363
        return $this->soaptimeout = $timesecs;
364
    }
365
366
    /**
367
     * Set security protocol
368
     * @param int $protocol
369
     * @return int
370
     */
371
    public function protocol($protocol = self::SSL_DEFAULT)
372
    {
373
        return $this->soapprotocol = $protocol;
374
    }
375
376
    /**
377
     * Set prefixes
378
     * @param array $prefixes
379
     * @return string[]
380
     */
381
    public function setSoapPrefix($prefixes = [])
382
    {
383
        return $this->prefixes = $prefixes;
384
    }
385
386
    /**
387
     * Set proxy parameters
388
     * @param string $ip
389
     * @param int    $port
390
     * @param string $user
391
     * @param string $password
392
     * @return void
393
     */
394
    public function proxy($ip, $port, $user, $password)
395
    {
396
        $this->proxyIP = $ip;
397
        $this->proxyPort = $port;
398
        $this->proxyUser = $user;
399
        $this->proxyPass = $password;
400
    }
401
402
    /**
403
     * @param string $url
404
     * @param string $operation
405
     * @param string $action
406
     * @param int $soapver
407
     * @param array $parameters
408
     * @param array $namespaces
409
     * @param string $request
410
     * @param null $soapheader
411
     * @return mixed
412
     */
413
    abstract public function send(
414
        $url,
415
        $operation = '',
416
        $action = '',
417
        $soapver = SOAP_1_2,
418
        $parameters = [],
419
        $namespaces = [],
420
        $request = '',
421
        $soapheader = null
422
    );
423
424
    /**
425
     * Mount soap envelope
426
     * @param string $request
427
     * @param array $namespaces
428
     * @param int $soapVer
429
     * @param \SoapHeader $header
430
     * @return string
431
     */
432
    protected function makeEnvelopeSoap(
433
        $request,
434
        $namespaces,
435
        $soapVer = SOAP_1_2,
436
        $header = null
437
    ) {
438
        $prefix = $this->prefixes[$soapVer];
439
        $envelopeAttributes = $this->getStringAttributes($namespaces);
440
        return $this->mountEnvelopString(
441
            $prefix,
442
            $envelopeAttributes,
443
            $this->mountSoapHeaders($prefix, $header),
444
            $request
445
        );
446
    }
447
448
    /**
449
     * Create a envelop string
450
     * @param string $envelopPrefix
451
     * @param string $envelopAttributes
452
     * @param string $header
453
     * @param string $bodyContent
454
     * @return string
455
     */
456
    private function mountEnvelopString(
457
        $envelopPrefix,
458
        $envelopAttributes = '',
459
        $header = '',
460
        $bodyContent = ''
461
    ) {
462
        return sprintf(
463
            '<%s:Envelope %s>' . $header . '<%s:Body>%s</%s:Body></%s:Envelope>',
464
            $envelopPrefix,
465
            $envelopAttributes,
466
            $envelopPrefix,
467
            $bodyContent,
468
            $envelopPrefix,
469
            $envelopPrefix
470
        );
471
    }
472
473
    /**
474
     * Create a haeader tag
475
     * @param string $envelopPrefix
476
     * @param \SoapHeader $header
477
     * @return string
478
     */
479
    private function mountSoapHeaders($envelopPrefix, $header = null)
480
    {
481
        if (null === $header) {
482
            return '';
483
        }
484
        $headerItems = '';
485
        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...
486
            $headerItems .= '<' . $key . '>' . $value . '</' . $key . '>';
487
        }
488
        return sprintf(
489
            '<%s:Header><%s xmlns="%s">%s</%s></%s:Header>',
490
            $envelopPrefix,
491
            $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...
492
            $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...
493
            $headerItems,
494
            $header->name,
495
            $envelopPrefix
496
        );
497
    }
498
499
    /**
500
     * Get attributes
501
     * @param array $namespaces
502
     * @return string
503
     */
504
    private function getStringAttributes($namespaces = [])
505
    {
506
        $envelopeAttributes = '';
507
        foreach ($namespaces as $key => $value) {
508
            $envelopeAttributes .= $key . '="' . $value . '" ';
509
        }
510
        return $envelopeAttributes;
511
    }
512
513
    /**
514
     * Temporarily saves the certificate keys for use cURL or SoapClient
515
     * @return void
516
     */
517
    public function saveTemporarilyKeyFiles()
518
    {
519
        //certs already exists
520
        if (!empty($this->certsdir)) {
521
            return;
522
        }
523
        if (!is_object($this->certificate)) {
524
            throw new RuntimeException(
525
                'Certificate not found.'
526
            );
527
        }
528
        if (empty($this->filesystem)) {
529
            $this->setTemporaryFolder();
530
        }
531
        //clear dir cert
532
        $this->removeTemporarilyFiles();
533
        $this->certsdir = 'certs/';
534
        $this->prifile = $this->randomName();
535
        $this->pubfile = $this->randomName();
536
        $this->certfile = $this->randomName();
537
        $ret = true;
538
        //load private key pem
539
        $private = $this->certificate->privateKey;
540
        if ($this->encriptPrivateKey) {
541
            //replace private key pem with password
542
            $this->temppass = Strings::randomString(16);
543
            //encripta a chave privada entes da gravação do filesystem
544
            openssl_pkey_export(
545
                $this->certificate->privateKey,
546
                $private,
547
                $this->temppass
548
            );
549
        }
550
        $ret &= $this->filesystem->put(
551
            $this->prifile,
552
            $private
553
        );
554
        $ret &= $this->filesystem->put(
555
            $this->pubfile,
556
            $this->certificate->publicKey
557
        );
558
        $ret &= $this->filesystem->put(
559
            $this->certfile,
560
            $private . "{$this->certificate}"
561
        );
562
        if (!$ret) {
563
            throw new RuntimeException(
564
                'Unable to save temporary key files in folder.'
565
            );
566
        }
567
    }
568
569
    /**
570
     * Create a unique random file name
571
     * @param integer $n
572
     * @return string
573
     */
574
    protected function randomName($n = 10)
575
    {
576
        $name = $this->certsdir . Strings::randomString($n) . '.pem';
577
        if (!$this->filesystem->has($name)) {
578
            return $name;
579
        }
580
        $this->randomName($n + 5);
581
    }
582
583
    /**
584
     * Delete all files in folder
585
     * @return void
586
     */
587 4
    public function removeTemporarilyFiles()
588
    {
589 4
        if (empty($this->filesystem) || empty($this->certsdir)) {
590 4
            return;
591
        }
592
        //remove os certificados
593
        $this->filesystem->delete($this->certfile);
594
        $this->filesystem->delete($this->prifile);
595
        $this->filesystem->delete($this->pubfile);
596
        //remove todos os arquivos antigos
597
        $contents = $this->filesystem->listContents($this->certsdir, true);
598
        $dt = new \DateTime();
599
        $tint = new \DateInterval("PT" . $this->waitingTime . "M");
600
        $tint->invert = 1;
601
        $tsLimit = $dt->add($tint)->getTimestamp();
602
        foreach ($contents as $item) {
603
            if ($item['type'] == 'file') {
604
                $timestamp = $this->filesystem->getTimestamp($item['path']);
605
                if ($timestamp < $tsLimit) {
606
                    $this->filesystem->delete($item['path']);
607
                }
608
            }
609
        }
610
    }
611
612
    /**
613
     * Save request envelope and response for debug reasons
614
     * @param string $operation
615
     * @param string $request
616
     * @param string $response
617
     * @return void
618
     */
619
    public function saveDebugFiles($operation, $request, $response)
620
    {
621
        if (!$this->debugmode) {
622
            return;
623
        }
624
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
625
        $now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
626
        $time = substr($now->format("ymdHisu"), 0, 16);
627
        try {
628
            $this->filesystem->put(
629
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
630
                $request
631
            );
632
            $this->filesystem->put(
633
                $this->debugdir . $time . "_" . $operation . "_res.txt",
634
                $response
635
            );
636
        } catch (\Exception $e) {
637
            throw new RuntimeException(
638
                'Unable to create debug files.'
639
            );
640
        }
641
    }
642
}
643