Completed
Push — master ( c27b09...b6c3ee )
by James
01:26
created

SoapClient::cleanResponse()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 14
nc 3
nop 0
1
<?php
2
/**
3
 * Contains \JamesIArmes\PhpNtlm\NTLMSoapClient.
4
 */
5
6
namespace jamesiarmes\PhpNtlm;
7
8
/**
9
 * Soap Client using Microsoft's NTLM Authentication.
10
 *
11
 * @package php-ntlm\Soap
12
 */
13
class SoapClient extends \SoapClient
14
{
15
    /**
16
     * cURL resource used to make the SOAP request
17
     *
18
     * @var resource
19
     */
20
    protected $ch;
21
22
    /**
23
     * Options passed to the client constructor.
24
     *
25
     * @var array
26
     */
27
    protected $options;
28
29
    /**
30
     * {@inheritdoc}
31
     *
32
     * Additional options:
33
     * - user (string): The user to authenticate with.
34
     * - password (string): The password to use when authenticating the user.
35
     * - curlopts (array): Array of options to set on the curl handler when
36
     *   making the request.
37
     * - strip_bad_chars (boolean, default true): Whether or not to strip
38
     *   invalid characters from the XML response. This can lead to content
39
     *   being returned differently than it actually is on the host service, but
40
     *   can also prevent the "looks like we got no XML document" SoapFault when
41
     *   the response includes invalid characters.
42
     * - warn_on_bad_chars (boolean, default false): Trigger a warning if bad
43
     *   characters are stripped. This has no affect unless strip_bad_chars is
44
     *   true.
45
     */
46
    public function __construct($wsdl, array $options = null)
47
    {
48
        // Set missing indexes to their default value.
49
        $options += array(
50
            'user' => null,
51
            'password' => null,
52
            'curlopts' => array(),
53
            'strip_bad_chars' => true,
54
            'warn_on_bad_chars' => false,
55
        );
56
        $this->options = $options;
57
58
        // Verify that a user name and password were entered.
59
        if (empty($options['user']) || empty($options['password'])) {
60
            throw new \BadMethodCallException(
61
                'A username and password is required.'
62
            );
63
        }
64
65
        parent::__construct($wsdl, $options);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SoapClient as the method __construct() does only exist in the following sub-classes of SoapClient: jamesiarmes\PhpNtlm\SoapClient. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
72
    {
73
        $headers = $this->buildHeaders($action);
74
        $this->__last_request = $request;
1 ignored issue
show
Bug introduced by
The property __last_request does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
75
        $this->__last_request_headers = $headers;
1 ignored issue
show
Bug introduced by
The property __last_request_headers does not seem to exist. Did you mean __last_request?

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...
76
77
        // Only reinitialize curl handle if the location is different.
78
        if (!$this->ch
79
            || curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL) != $location) {
80
            $this->ch = curl_init($location);
81
        }
82
83
        curl_setopt_array($this->ch, $this->curlOptions($action, $request));
84
        $response = curl_exec($this->ch);
85
86
        // TODO: Add some real error handling.
87
        // If the response if false than there was an error and we should throw
88
        // an exception.
89
        if ($response === false) {
90
            $this->__last_response = $this->__last_response_headers = false;
2 ignored issues
show
Bug introduced by
The property __last_response does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
The property __last_response_headers does not seem to exist. Did you mean __last_response?

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...
91
            throw new \RuntimeException(
92
                'Curl error: ' . curl_error($this->ch),
93
                curl_errno($this->ch)
94
            );
95
        }
96
97
        $this->parseResponse($response);
98
        $this->cleanResponse();
99
100
        return $this->__last_response;
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function __getLastRequestHeaders()
107
    {
108
        return implode("\n", $this->__last_request_headers) . "\n";
1 ignored issue
show
Bug introduced by
The property __last_request_headers does not seem to exist. Did you mean __last_request?

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...
109
    }
110
111
    /**
112
     * Returns the response code from the last request
113
     *
114
     * @return integer
115
     *
116
     * @throws \BadMethodCallException
117
     *   If no cURL resource has been initialized.
118
     */
119
    public function getResponseCode()
120
    {
121
        if (empty($this->ch)) {
122
            throw new \BadMethodCallException('No cURL resource has been '
123
                . 'initialized. This is probably because no request has not '
124
                . 'been made.');
125
        }
126
127
        return curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
128
    }
129
130
    /**
131
     * Builds the headers for the request.
132
     *
133
     * @param string $action
134
     *   The SOAP action to be performed.
135
     */
136
    protected function buildHeaders($action)
137
    {
138
        return array(
139
            'Method: POST',
140
            'Connection: Keep-Alive',
141
            'User-Agent: PHP-SOAP-CURL',
142
            'Content-Type: text/xml; charset=utf-8',
143
            "SOAPAction: \"$action\"",
144
            'Expect: 100-continue',
145
        );
146
    }
147
148
    /**
149
     * Cleans the response body by stripping bad characters if instructed to.
150
     */
151
    protected function cleanResponse()
152
    {
153
        // If the option to strip bad characters is not set, then we shouldn't
154
        // do anything here.
155
        if (!$this->options['strip_bad_chars']) {
156
            return;
157
        }
158
159
        // Strip invalid characters from the XML response body.
160
        $count = 0;
161
        $this->__last_response = preg_replace(
162
            '/(?!&#x0?(9|A|D))(&#x[0-1]?[0-9A-F];)/',
163
            ' ',
164
            $this->__last_response,
165
            -1,
166
            $count
167
        );
168
169
        // If the option to warn on bad characters is set, and some characters
170
        // were stripped, then trigger a warning.
171
        if ($this->options['warn_on_bad_chars'] && $count > 0) {
172
            trigger_error(
173
                'Invalid characters were stripped from the XML SOAP response.',
174
                E_USER_WARNING
175
            );
176
        }
177
    }
178
179
    /**
180
     * Builds an array of curl options for the request
181
     *
182
     * @param string $action
183
     *   The SOAP action to be performed.
184
     * @param string $request
185
     *   The XML SOAP request.
186
     * @return array
187
     *   Array of curl options.
188
     */
189
    protected function curlOptions($action, $request)
190
    {
191
        $options = $this->options['curlopts'] + array(
192
            CURLOPT_SSL_VERIFYPEER => true,
193
            CURLOPT_RETURNTRANSFER => true,
194
            CURLOPT_HTTPHEADER => $this->buildHeaders($action),
195
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
196
            CURLOPT_HTTPAUTH => CURLAUTH_BASIC | CURLAUTH_NTLM,
197
            CURLOPT_USERPWD => $this->options['user'] . ':'
198
                               . $this->options['password'],
199
        );
200
201
        // We shouldn't allow these options to be overridden.
202
        $options[CURLOPT_HEADER] = true;
203
        $options[CURLOPT_POST] = true;
204
        $options[CURLOPT_POSTFIELDS] = $request;
205
206
        return $options;
207
    }
208
209
    /**
210
     * Pareses the response from a successful request.
211
     *
212
     * @param string $response
213
     *   The response from the cURL request, including headers and body.
214
     */
215
    public function parseResponse($response)
216
    {
217
        // Parse the response and set the last response and headers.
218
        $info = curl_getinfo($this->ch);
219
        $this->__last_response_headers = substr(
1 ignored issue
show
Bug introduced by
The property __last_response_headers does not seem to exist. Did you mean __last_response?

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...
220
            $response,
221
            0,
222
            $info['header_size']
223
        );
224
        $this->__last_response = substr($response, $info['header_size']);
225
    }
226
}
227