Completed
Push — master ( 0f43cb...7121ae )
by Alex
18s queued 14s
created

ChangeSetParser::handleData()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 4
eloc 14
c 4
b 1
f 0
nc 4
nop 0
dl 0
loc 18
rs 9.7998
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
16
/**
17
 * Class ChangeSetParser.
18
 * @package POData\BatchProcessor
19
 */
20
class ChangeSetParser implements IBatchParser
21
{
22
    protected $data;
23
    protected $changeSetBoundary;
24
    /**
25
     * @var WebOperationContext[]
26
     */
27
    protected $rawRequests = [];
28
    protected $service;
29
    protected $contentIDToLocationLookup = [];
30
31
    /**
32
     * ChangeSetParser constructor.
33
     * @param BaseService $service
34
     * @param $body
35
     */
36
    public function __construct(BaseService $service, $body)
37
    {
38
        $this->service            = $service;
39
        $this->data               = trim(str_replace("\r", '', $body)); // removes windows specific character
40
    }
41
42
    /**
43
     * @return mixed
44
     */
45
    public function getBoundary()
46
    {
47
        return $this->changeSetBoundary;
48
    }
49
50
    /**
51
     * @throws ODataException
52
     * @throws UrlFormatException
53
     * @throws Exception
54
     */
55
    public function process()
56
    {
57
        $raw = $this->getRawRequests();
58
        foreach ($raw as $contentID => &$workingObject) {
59
            /** @var IncomingChangeSetRequest $request */
60
            $request = $workingObject->incomingRequest();
61
            foreach ($this->contentIDToLocationLookup as $lookupID => $location) {
62
                if (0 > $lookupID) {
63
                    continue;
64
                }
65
                $request->applyContentID($lookupID, $location);
66
            }
67
68
            $this->processSubRequest($workingObject);
69
            if (HTTPRequestMethod::GET() != $workingObject->incomingRequest()->getMethod() &&
70
                strpos($workingObject->incomingRequest()->getRawUrl(), '/$links/') === false) {
71
                if (null === $workingObject->outgoingResponse()->getHeaders()['Location']) {
72
                    $msg = 'Location header not set in subrequest response for ' . $request->getMethod()
73
                        . ' request url ' . $request->getRawUrl();
74
                    throw new Exception($msg);
75
                }
76
                $this->contentIDToLocationLookup[$contentID] = $workingObject->outgoingResponse()->getHeaders()['Location'];
77
            }
78
        }
79
    }
80
81
    /**
82
     * @return WebOperationContext[]
83
     */
84
    public function getRawRequests()
85
    {
86
        return $this->rawRequests;
87
    }
88
89
    /**
90
     * @param $newContext
91
     * @throws ODataException
92
     * @throws UrlFormatException
93
     */
94
    protected function processSubRequest(&$newContext)
95
    {
96
        $newHost    = new ServiceHost($newContext);
97
        $oldHost    = $this->getService()->getHost();
98
        $this->getService()->setHost($newHost);
99
        $this->getService()->handleRequest();
100
        $this->getService()->setHost($oldHost);
101
    }
102
103
    /**
104
     * @return BaseService
105
     */
106
    public function getService()
107
    {
108
        return $this->service;
109
    }
110
111
    /**
112
     * @return string
113
     */
114
    public function getResponse()
115
    {
116
        $ODataEOL = $this->getService()->getConfiguration()->getLineEndings();
117
118
        $response = '';
119
        $splitter = false === $this->changeSetBoundary ?
120
            '' :
121
            '--' . $this->changeSetBoundary . $ODataEOL;
122
        $raw = $this->getRawRequests();
123
        foreach ($raw as $contentID => &$workingObject) {
124
            $headers = $workingObject->outgoingResponse()->getHeaders();
125
            $response .= $splitter;
126
127
            $response .= 'Content-Type: application/http' . $ODataEOL;
128
            $response .= 'Content-Transfer-Encoding: binary' . $ODataEOL;
129
            $response .= $ODataEOL;
130
            $response .= 'HTTP/1.1 ' . $headers['Status'] . $ODataEOL;
131
            $response .= 'Content-ID: ' . $contentID . $ODataEOL;
132
133
            foreach ($headers as $headerName => $headerValue) {
134
                if (null !== $headerValue) {
135
                    $response .= $headerName . ': ' . $headerValue . $ODataEOL;
136
                }
137
            }
138
            $response .= $ODataEOL;
139
            $response .= $workingObject->outgoingResponse()->getStream();
140
        }
141
        $response .= trim($splitter);
142
        $response .= false === $this->changeSetBoundary ?
143
            $ODataEOL :
144
            '--' . $ODataEOL;
145
        $response = 'Content-Length: ' .
146
            strlen($response) .
147
            $ODataEOL .
148
            $ODataEOL .
149
            $response;
150
        $response = false === $this->changeSetBoundary ?
151
            $response :
152
            'Content-Type: multipart/mixed; boundary=' .
153
            $this->changeSetBoundary .
154
            $ODataEOL .
155
            $response;
156
        return $response;
157
    }
158
159
    public function handleData()
160
    {
161
        list($headerBlock, $contentBlock) = explode("\n\n", $this->getData(), 2);
162
        $headers                          = self::parse_headers($headerBlock);
163
        $this->changeSetBoundary          = $headers['Content-Type']['boundary'];
164
        $matches                          = array_filter(explode('--' . $this->changeSetBoundary, $contentBlock));
165
        $contentIDinit                    = -1;
166
        foreach ($matches as $match) {
167
            if ('--' === trim($match)) {
168
                continue;
169
            }
170
            $request   = new IncomingChangeSetRequest($match);
171
            $contentID = $request->getContentId() ?? $contentIDinit;
172
            $this->rawRequests[$contentID] = new WebOperationContext($request);
173
            if ($contentIDinit == $contentID) {
174
                $contentIDinit--;
175
            }
176
            continue;
177
        }
178
    }
179
180
    /**
181
     * @return string
182
     */
183
    public function getData()
184
    {
185
        return $this->data;
186
    }
187
    private static function parse_headers($headers, $splitter = "\n", $assignmentChar = ':')
188
    {
189
        $results = [];
190
        foreach (array_filter(explode($splitter, trim($headers))) as $line) {
191
            list($key, $value) = strpos($line, $assignmentChar) !== false ? explode($assignmentChar, $line, 2) : ['default', $line];
192
            $key               = trim($key);
193
            $value             = trim($value);
194
            if (strpos($value, ';') !== false) {
195
                $value = self::parse_headers($value, ';', '=');
196
            }
197
            $results[$key] = $value;
198
        }
199
        return $results;
200
    }
201
}
202