SoapClient::buildHeaders()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 11
rs 9.9
c 0
b 0
f 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 = [])
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);
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;
75
        $this->__last_request_headers = $headers;
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;
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";
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(
220
            $response,
221
            0,
222
            $info['header_size']
223
        );
224
        $this->__last_response = substr($response, $info['header_size']);
225
    }
226
}
227