Passed
Pull Request — master (#255)
by Christopher
03:06
created

ChangeSetParser::handleData()   B

Complexity

Conditions 11
Paths 98

Size

Total Lines 48
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 11
eloc 37
c 4
b 1
f 0
nc 98
nop 0
dl 0
loc 48
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace POData\BatchProcessor;
6
7
use Exception;
8
use POData\BaseService;
9
use POData\Common\ODataException;
10
use POData\Common\UrlFormatException;
11
use POData\OperationContext\HTTPRequestMethod;
12
use POData\OperationContext\ServiceHost;
13
use POData\OperationContext\Web\IncomingRequest;
14
use POData\OperationContext\Web\WebOperationContext;
15
use POData\StringUtility;
16
17
/**
18
 * Class ChangeSetParser.
19
 * @package POData\BatchProcessor
20
 */
21
class ChangeSetParser implements IBatchParser
22
{
23
    protected $data;
24
    protected $changeSetBoundary;
25
    /**
26
     * @var WebOperationContext[]
27
     */
28
    protected $rawRequests = [];
29
    protected $service;
30
    protected $contentIDToLocationLookup = [];
31
32
    /**
33
     * ChangeSetParser constructor.
34
     * @param BaseService $service
35
     * @param $body
36
     */
37
    public function __construct(BaseService $service, $body)
38
    {
39
        $this->service            = $service;
40
        $this->data               = trim(str_replace("\r", '', $body)); // removes windows specific character
41
    }
42
43
    /**
44
     * @return mixed
45
     */
46
    public function getBoundary()
47
    {
48
        return $this->changeSetBoundary;
49
    }
50
51
    /**
52
     * @throws ODataException
53
     * @throws UrlFormatException
54
     * @throws Exception
55
     */
56
    public function process()
57
    {
58
        $raw = $this->getRawRequests();
59
        foreach ($raw as $contentID => &$workingObject) {
60
            foreach ($this->contentIDToLocationLookup as $lookupID => $location) {
61
                if (0 > $lookupID) {
62
                    continue;
63
                }
64
                $workingObject->incomingRequest()->applyContentID($lookupID, $location);
65
            }
66
67
            $this->processSubRequest($workingObject);
68
            if (HTTPRequestMethod::GET() != $workingObject->incomingRequest()->getMethod() &&
69
                strpos($workingObject->incomingRequest()->getRawUrl(), '/$links/') === false) {
70
                if (null === $workingObject->outgoingResponse()->getHeaders()['Location']) {
71
                    $msg = 'Location header not set in subrequest response for ' . $workingObject->incomingRequest()->getMethod()
72
                        . ' request url ' . $workingObject->incomingRequest()->getRawUrl();
73
                    throw new Exception($msg);
74
                }
75
                $this->contentIDToLocationLookup[$contentID] = $workingObject->outgoingResponse()->getHeaders()['Location'];
76
            }
77
        }
78
    }
79
80
    /**
81
     * @return array
82
     */
83
    public function getRawRequests()
84
    {
85
        return $this->rawRequests;
86
    }
87
88
    /**
89
     * @param $newContext
90
     * @throws ODataException
91
     * @throws UrlFormatException
92
     */
93
    protected function processSubRequest(&$newContext)
94
    {
95
        $newHost    = new ServiceHost($newContext);
96
        $oldHost    = $this->getService()->getHost();
97
        $this->getService()->setHost($newHost);
98
        $this->getService()->handleRequest();
99
        $this->getService()->setHost($oldHost);
100
    }
101
102
    /**
103
     * @return BaseService
104
     */
105
    public function getService()
106
    {
107
        return $this->service;
108
    }
109
110
    /**
111
     * @return string
112
     */
113
    public function getResponse()
114
    {
115
        $ODataEOL = $this->getService()->getConfiguration()->getLineEndings();
116
117
        $response = '';
118
        $splitter = false === $this->changeSetBoundary ?
119
            '' :
120
            '--' . $this->changeSetBoundary . $ODataEOL;
121
        $raw = $this->getRawRequests();
122
        foreach ($raw as $contentID => &$workingObject) {
123
            $headers = $workingObject->outgoingResponse()->getHeaders();
124
            $response .= $splitter;
125
126
            $response .= 'Content-Type: application/http' . $ODataEOL;
127
            $response .= 'Content-Transfer-Encoding: binary' . $ODataEOL;
128
            $response .= $ODataEOL;
129
            $response .= 'HTTP/1.1 ' . $headers['Status'] . $ODataEOL;
130
            $response .= 'Content-ID: ' . $contentID . $ODataEOL;
131
132
            foreach ($headers as $headerName => $headerValue) {
133
                if (null !== $headerValue) {
134
                    $response .= $headerName . ': ' . $headerValue . $ODataEOL;
135
                }
136
            }
137
            $response .= $ODataEOL;
138
            $response .= $workingObject->outgoingResponse()->getStream();
139
        }
140
        $response .= trim($splitter);
141
        $response .= false === $this->changeSetBoundary ?
142
            $ODataEOL :
143
            '--' . $ODataEOL;
144
        $response = 'Content-Length: ' .
145
            strlen($response) .
146
            $ODataEOL .
147
            $ODataEOL .
148
            $response;
149
        $response = false === $this->changeSetBoundary ?
150
            $response :
151
            'Content-Type: multipart/mixed; boundary=' .
152
            $this->changeSetBoundary .
153
            $ODataEOL .
154
            $response;
155
        return $response;
156
    }
157
158
    public function handleData()
159
    {
160
        list($headerBlock, $contentBlock) = explode("\n\n", $this->getData(), 2);
161
        $headers                          = self::parse_headers($headerBlock);
162
        $this->changeSetBoundary          = $headers['Content-Type']['boundary'];
163
        $prefix                           = 'HTTP_';
164
        $matches                          = array_filter(explode('--' . $this->changeSetBoundary, $contentBlock));
165
        $contentIDinit                    = -1;
166
        foreach ($matches as $match) {
167
            if ('--' === trim($match)) {
168
                continue;
169
            }
170
171
            list($RequestParams, $requestHeaders, $RequestBody) = explode("\n\n", $match);
172
            $RequestBody                                        = trim($RequestBody);
173
            $requestHeadersArray                                = self::parse_headers($requestHeaders);
174
            list($RequesetType, $RequestPath, $RequestProticol) = explode(' ', $requestHeadersArray['default'], 3);
175
            $contentID                                          = array_key_exists('Content-ID', $requestHeadersArray) ? $requestHeadersArray['Content-ID'] : $contentIDinit;
176
            $inboundRequestHeaders                              = [];
177
            $skip                                               = true;
178
            foreach (explode("\n", $requestHeaders) as $headerLine) {
179
                if ($skip) {
180
                    $skip = false;
181
                    continue;
182
                }   
183
                list($key, $value)            = explode(':', $headerLine);
184
                $name                         = strtr(strtoupper(trim($key)), '-', '_');
185
                $value                        = trim($value);
186
                $name                         = substr($name, 0, strlen($prefix)) === $prefix || $name == 'CONTENT_TYPE' ? $name : $prefix . $name;
187
                $inboundRequestHeaders[$name] = $value;
188
            }
189
            $host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? $_SERVER['SERVER_ADDR'] ?? 'localhost';
190
            $protocol=$_SERVER['PROTOCOL'] = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http';
191
            $this->rawRequests[$contentID] = new WebOperationContext(
192
                new IncomingRequest(
193
                    new HTTPRequestMethod($RequesetType),
194
                    [],
195
                    [],
196
                    $inboundRequestHeaders,
197
                    null,
198
                    $RequestBody,
199
                    $protocol . '://' . $host . $RequestPath
200
                )
201
            );
202
            if ($contentIDinit == $contentID) {
203
                $contentIDinit--;
204
            }
205
            continue;
206
        }
207
    }
208
209
    /**
210
     * @return string
211
     */
212
    public function getData()
213
    {
214
        return $this->data;
215
    }
216
217
218
    private static function parse_headers($headers, $splitter = "\n", $assignmentChar = ':')
219
    {
220
        $results = [];
221
        foreach (array_filter(explode($splitter, trim($headers))) as $line) {
222
            list($key, $value) = strpos($line, $assignmentChar) !== false ? explode($assignmentChar, $line, 2) : ['default', $line];
223
            $key               = trim($key);
224
            $value             = trim($value);
225
            if (strpos($value, ';') !== false) {
226
                $value = self::parse_headers($value, ';', '=');
227
            }
228
            if (isset($results[$key])) {
229
                if (is_array($results[$key])) {
230
                    $results[$key][] = $value;
231
                } else {
232
                    $results[$key] = [$results[$key], $value];
233
                }
234
            } else {
235
                $results[$key] = $value;
236
            }
237
        }
238
        return $results;
239
    }
240
}
241