Test Failed
Push — master ( a1e735...534e7d )
by Bálint
13:42 queued 13s
created

ServiceHost::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 7
c 2
b 0
f 0
dl 0
loc 17
rs 10
cc 2
nc 2
nop 2
1
<?php
2
3
namespace POData\OperationContext;
4
5
use Illuminate\Http\Request;
6
use POData\Common\Messages;
7
use POData\Common\HttpStatus;
8
use POData\Common\ODataConstants;
9
use POData\Common\Url;
10
use POData\Common\UrlFormatException;
11
use POData\Common\ODataException;
12
use POData\Common\Version;
13
use POData\Common\MimeTypes;
14
use POData\OperationContext\Web\Illuminate\IlluminateOperationContext;
15
16
/**
17
 * Class ServiceHost
18
 *
19
 * It uses an IOperationContext implementation to get/set all context related
20
 * headers/stream info It also validates the each header value
21
 *
22
 * @package POData\OperationContext
23
 */
24
Class ServiceHost
25
{
26
    /**
27
     * Holds reference to the underlying operation context.
28
     *
29
     * @var IOperationContext
30
     */
31
    private $_operationContext;
32
33
    /**
34
     * The absolute request Uri as Url instance.
35
     * Note: This will not contain query string
36
     *
37
     * @var Url
38
     */
39
    private $_fullAbsoluteRequestUri;
40
41
    /**
42
     * The absolute request Uri as string
43
     * Note: This will not contain query string
44
     *
45
     * @var string
46
     */
47
    private $_fullAbsoluteRequestUriAsString = null;
48
49
    /**
50
     * The absolute request Uri as Url instance.
51
     * Note: This will not contain query string
52
     *
53
     * @var Url
54
     */
55
    private $_absoluteRequestUri;
56
57
    /**
58
     * The absolute request Uri as string
59
     * Note: This will not contain query string
60
     *
61
     * @var string
62
     */
63
    private $_absoluteRequestUriAsString = null;
64
65
    /**
66
     * The absolute service uri as Url instance.
67
     * Note: This value will be taken from configuration file
68
     *
69
     * @var Url
70
     */
71
    private $_absoluteServiceUri;
72
73
    /**
74
     * The absolute service uri string.
75
     * Note: This value will be taken from configuration file
76
     *
77
     * @var string
78
     */
79
    private $_absoluteServiceUriAsString = null;
80
81
    /**
82
     * Gets reference to the operation context.
83
     *
84
     * @return IOperationContext
85
     */
86
    public function getOperationContext()
87
    {
88
        return $this->_operationContext;
89
    }
90
91
    /**
92
     * @param IOperationContext $context the OperationContext implementation to use.
93
     * If null the IlluminateOperationContex will be used.  Defaults to null.
94
     *
95
     * Currently we are forcing the input request to be of type
96
     * \Illuminate\Http\Request but in the future we could make this more flexible
97
     * if needed.
98
     *
99
     * @param Request $incomingRequest
100
     * @throws ODataException
101
     */
102
    public function __construct(IOperationContext $context = null, Request $incomingRequest)
103
    {
104
        if (is_null($context)) {
105
            $this->_operationContext = new IlluminateOperationContext($incomingRequest);
106
        } else {
107
            $this->_operationContext = $context;
108
        }
109
110
        // getAbsoluteRequestUri can throw UrlFormatException
111
        // let Dispatcher handle it
112
        $this->_absoluteRequestUri = $this->getAbsoluteRequestUri();
113
        $this->_absoluteServiceUri = null;
114
115
        //Dev Note: Andrew Clinton 5/19/16
116
        //_absoluteServiceUri is never being set from what I can tell
117
        //so for now we'll set it as such
118
        $this->setServiceUri($this->_getServiceUri());
119
    }
120
121
    /**
122
     * Gets the absolute request Uri as Url instance
123
     * Note: This method will be called first time from constructor.
124
     *
125
     * @throws ODataException if AbsoluteRequestUri is not a valid URI
126
     *
127
     * @return Url
128
     */
129
    public function getAbsoluteRequestUri()
130
    {
131
        if (is_null($this->_absoluteRequestUri)) {
132
            $this->_absoluteRequestUriAsString = $this->_operationContext->incomingRequest()->getRawUrl();
133
            // Validate the uri first
134
            try {
135
                $this->_fullAbsoluteRequestUriAsString = $this->_absoluteRequestUriAsString;
136
                $this->_fullAbsoluteRequestUri = new Url($this->_fullAbsoluteRequestUriAsString);
137
            } catch (UrlFormatException $exception) {
138
                throw ODataException::createBadRequestError($exception->getMessage());
139
            }
140
141
            $queryStartIndex = strpos($this->_absoluteRequestUriAsString, '?');
142
            if ($queryStartIndex !== false) {
143
                $this->_absoluteRequestUriAsString = substr(
144
                    $this->_absoluteRequestUriAsString,
145
                    0,
146
                    $queryStartIndex
147
                );
148
            }
149
150
            // We need the absolute uri only not associated components
151
            // (query, fragments etc..)
152
            $this->_absoluteRequestUri = new Url($this->_absoluteRequestUriAsString);
153
            $this->_absoluteRequestUriAsString = rtrim($this->_absoluteRequestUriAsString, '/');
154
        }
155
156
        return $this->_absoluteRequestUri;
157
    }
158
159
    /**
160
     * Gets the absolute request Uri as string
161
     * Note: This will not contain query string
162
     *
163
     * @return string
164
     */
165
    public function getAbsoluteRequestUriAsString()
166
    {
167
        return $this->_absoluteRequestUriAsString;
168
    }
169
170
171
    /**
172
     * Sets the service url from which the OData URL is parsed
173
     *
174
     * @param string $serviceUri The service url, absolute or relative.
175
     *
176
     * @return void
177
     *
178
     * @throws ODataException If the base uri in the configuration is malformed.
179
     */
180
    public function setServiceUri($serviceUri)
181
    {
182
        if (is_null($this->_absoluteServiceUri)) {
183
            $isAbsoluteServiceUri = (strpos($serviceUri, 'http://') === 0)
184
                || (strpos($serviceUri, 'https://') === 0);
185
            try {
186
                $this->_absoluteServiceUri = new Url($serviceUri, $isAbsoluteServiceUri);
187
            } catch (UrlFormatException $exception) {
188
                throw ODataException::createInternalServerError(Messages::hostMalFormedBaseUriInConfig());
189
            }
190
191
            $segments = $this->_absoluteServiceUri->getSegments();
192
            $lastSegment = $segments[count($segments) - 1];
193
            $endsWithSvc
194
                = (substr_compare($lastSegment, '.svc', -strlen('.svc'), strlen('.svc')) === 0);
195
            if (!$endsWithSvc
196
                || !is_null($this->_absoluteServiceUri->getQuery())
197
                || !is_null($this->_absoluteServiceUri->getFragment())
198
            ) {
199
                throw ODataException::createInternalServerError(Messages::hostMalFormedBaseUriInConfig(true));
200
            }
201
202
            if (!$isAbsoluteServiceUri) {
203
                $requestUriSegments = $this->_absoluteRequestUri->getSegments();
204
                $i = count($requestUriSegments) - 1;
205
                // Find index of segment in the request uri that end with .svc
206
                // There will be always a .svc segment in the request uri otherwise
207
                // uri redirection will not happen.
208
                for (; $i >= 0; $i--) {
209
                    $endsWithSvc = (substr_compare($requestUriSegments[$i], '.svc', -strlen('.svc'), strlen('.svc')) === 0);
210
                    if ($endsWithSvc) {
211
                        break;
212
                    }
213
                }
214
215
                $j = count($segments) - 1;
216
                $k = $i;
217
                if ($j > $i) {
218
                    throw ODataException::createBadRequestError(
219
                        Messages::hostRequestUriIsNotBasedOnRelativeUriInConfig(
220
                            $this->_absoluteRequestUriAsString,
221
                            $serviceUri
222
                        )
223
                    );
224
                }
225
226
                while ($j >= 0 && ($requestUriSegments[$i] === $segments[$j])) {
227
                    $i--; $j--;
228
                }
229
230
                if ($j != -1) {
231
                    throw ODataException::createBadRequestError(
232
                        Messages::hostRequestUriIsNotBasedOnRelativeUriInConfig(
233
                            $this->_absoluteRequestUriAsString,
234
                            $serviceUri
235
                        )
236
                    );
237
                }
238
239
                $serviceUri = $this->_absoluteRequestUri->getScheme()
240
                    . '://'
241
                    . $this->_absoluteRequestUri->getHost()
242
                    . ':'
243
                    . $this->_absoluteRequestUri->getPort();
244
245
                for ($l = 0; $l <= $k; $l++) {
246
                    $serviceUri .= '/' . $requestUriSegments[$l];
247
                }
248
249
                $this->_absoluteServiceUri = new Url($serviceUri);
250
            }
251
252
            $this->_absoluteServiceUriAsString = $serviceUri;
253
        }
254
    }
255
256
257
    /**
258
     * Gets the absolute Uri to the service as Url instance.
259
     * Note: This will be the value taken from configuration file.
260
     *
261
     * @return Url
262
     */
263
    public function getAbsoluteServiceUri()
264
    {
265
        return $this->_absoluteServiceUri;
266
    }
267
268
    /**
269
     * Gets the absolute Uri to the service as string
270
     * Note: This will be the value taken from configuration file.
271
     *
272
     * @return string
273
     */
274
    public function getAbsoluteServiceUriAsString()
275
    {
276
        return $this->_absoluteServiceUriAsString;
277
    }
278
279
    /**
280
     * Dev Note: Andrew Clinton
281
     * 5/19/16
282
     *
283
     * Currently it doesn't seem that the service URI is ever being built
284
     * so I am doing that here.
285
     *
286
     * return void
287
     */
288
    private function _getServiceUri()
289
    {
290
        if (($pos = strpos($this->_absoluteRequestUriAsString, ".svc")) !== FALSE) {
291
            $serviceUri = substr($this->_absoluteRequestUriAsString, 0, $pos + strlen(".svc"));
292
            return $serviceUri;
293
        }
294
295
        return $this->_absoluteRequestUriAsString;
296
    }
297
298
    /**
299
     * Gets the value for the DataServiceVersion header of the request.
300
     *
301
     * @return string|null
302
     */
303
    public function getRequestVersion()
304
    {
305
        return $this->_operationContext
306
            ->incomingRequest()
307
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_DATA_SERVICE_VERSION);
308
    }
309
310
    /**
311
     * Gets the value of MaxDataServiceVersion header of the request
312
     *
313
     * @return string|null
314
     */
315
    public function getRequestMaxVersion()
316
    {
317
        return $this->_operationContext
318
            ->incomingRequest()
319
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_MAX_DATA_SERVICE_VERSION);
320
    }
321
322
323
    /**
324
     * Get comma separated list of client-supported MIME Accept types
325
     *
326
     * @return string
327
     */
328
    public function getRequestAccept()
329
    {
330
        return $this->_operationContext
331
            ->incomingRequest()
332
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_ACCEPT);
333
    }
334
335
336
    /**
337
     * Get the character set encoding that the client requested
338
     *
339
     * @return string
340
     */
341
    public function getRequestAcceptCharSet()
342
    {
343
        return $this->_operationContext
344
            ->incomingRequest()
345
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_ACCEPT_CHARSET);
346
    }
347
348
349
350
    /**
351
     * Get the value of If-Match header of the request
352
     *
353
     * @return null|string
354
     */
355
    public function getRequestIfMatch()
356
    {
357
        return $this->_operationContext
358
            ->incomingRequest()
359
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_IF_MATCH);
360
    }
361
362
    /**
363
     * Gets the value of If-None-Match header of the request
364
     *
365
     * @return null|string
366
     */
367
    public function getRequestIfNoneMatch()
368
    {
369
        return $this->_operationContext
370
            ->incomingRequest()
371
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_IF_NONE);
372
    }
373
374
    /**
375
     * Gets the value of Content-Type header of the request
376
     *
377
     * @return string
378
     */
379
    public function getRequestContentType()
380
    {
381
        return $this->_operationContext
382
            ->incomingRequest()
383
            ->getRequestHeader(ODataConstants::HTTP_CONTENTTYPE);
384
    }
385
386
    /**
387
     * Set the Cache-Control header on the response
388
     *
389
     * @param string $value The cache-control value.
390
     *
391
     * @return void
392
     *
393
     @ throws InvalidOperation
394
     */
395
    public function setResponseCacheControl($value)
396
    {
397
        $this->_operationContext->outgoingResponse()->setCacheControl($value);
398
    }
399
400
    /**
401
     * Gets the HTTP MIME type of the output stream
402
     *
403
     * @return string
404
     */
405
    public function getResponseContentType()
406
    {
407
        return $this->_operationContext
408
            ->outgoingResponse()
409
            ->getContentType();
410
    }
411
412
    /**
413
     * Sets the HTTP MIME type of the output stream
414
     *
415
     * @param string $value The HTTP MIME type
416
     *
417
     * @return void
418
     */
419
    public function setResponseContentType($value)
420
    {
421
        $this->_operationContext
422
            ->outgoingResponse()
423
            ->setContentType($value);
424
    }
425
426
    /**
427
     * Sets the content length of the output stream
428
     *
429
     * @param string $value The content length
430
     *
431
     * @return void
432
     *
433
     * @throw Exception if $value is not numeric throws notAcceptableError
434
     */
435
    public function setResponseContentLength($value)
436
    {
437
        if (preg_match('/[0-9]+/', $value)) {
438
            $this->_operationContext->outgoingResponse()->setContentLength($value);
439
        } else {
440
            throw ODataException::notAcceptableError(
441
                "ContentLength:$value is invalid"
442
            );
443
        }
444
    }
445
446
    /**
447
     * Gets the value of the ETag header on the response
448
     *
449
     * @return string|null
450
     */
451
    public function getResponseETag()
452
    {
453
        return $this->_operationContext->outgoingResponse()->getETag();
454
    }
455
456
    /**
457
     * Sets the value of the ETag header on the response
458
     *
459
     * @param string $value The ETag value
460
     *
461
     * @return void
462
     */
463
    public function setResponseETag($value)
464
    {
465
        $this->_operationContext->outgoingResponse()->setETag($value);
466
    }
467
468
    /**
469
     * Sets the value Location header on the response
470
     *
471
     * @param string $value The location.
472
     *
473
     * @return void
474
     */
475
    public function setResponseLocation($value)
476
    {
477
        $this->_operationContext->outgoingResponse()->setLocation($value);
478
    }
479
480
    /**
481
     * Sets the value status code header on the response
482
     *
483
     * @param string $value The status code
484
     *
485
     * @return void
486
     */
487
    public function setResponseStatusCode($value)
488
    {
489
        $floor = floor($value/100);
490
        if ($floor >= 1 && $floor <= 5) {
491
            $statusDescription = HttpStatus::getStatusDescription($value);
0 ignored issues
show
Bug introduced by
$value of type string is incompatible with the type integer expected by parameter $statusCode of POData\Common\HttpStatus::getStatusDescription(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

491
            $statusDescription = HttpStatus::getStatusDescription(/** @scrutinizer ignore-type */ $value);
Loading history...
492
            if (!is_null($statusDescription)) {
493
                $statusDescription = ' ' . $statusDescription;
494
            }
495
496
            $this->_operationContext
497
                ->outgoingResponse()->setStatusCode($value . $statusDescription);
498
        } else {
499
            throw ODataException::createInternalServerError(
500
                'Invalid Status Code' . $value
501
            );
502
        }
503
    }
504
505
    /**
506
     * Sets the value status description header on the response
507
     *
508
     * @param string $value The status description
509
     *
510
     * @return void
511
     */
512
    public function setResponseStatusDescription($value)
513
    {
514
        $this->_operationContext
515
            ->outgoingResponse()->setStatusDescription($value);
516
    }
517
518
    /**
519
     * Sets the value stream to be send a response
520
     *
521
     * @param string &$value The stream
522
     *
523
     * @return void
524
     */
525
    public function setResponseStream(&$value)
526
    {
527
        $this->_operationContext->outgoingResponse()->setStream($value);
528
    }
529
530
    /**
531
     * Sets the DataServiceVersion response header
532
     *
533
     * @param string $value The version
534
     *
535
     * @return void
536
     */
537
    public function setResponseVersion($value)
538
    {
539
        $this->_operationContext->outgoingResponse()->setServiceVersion($value);
540
    }
541
542
    /**
543
     * Get the response headers
544
     *
545
     * @return array<headername, headerValue>
546
     */
547
    public function &getResponseHeaders()
548
    {
549
        return $this->_operationContext->outgoingResponse()->getHeaders();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_operation...esponse()->getHeaders() returns the type array<string,string> which is incompatible with the documented return type POData\OperationContext\headerValue[].
Loading history...
550
    }
551
552
    /**
553
     * Add a header to response header collection
554
     *
555
     * @param string $headerName  The name of the header
556
     * @param string $headerValue The value of the header
557
     *
558
     * @return void
559
     */
560
    public function addResponseHeader($headerName, $headerValue)
561
    {
562
        $this->_operationContext
563
            ->outgoingResponse()->addHeader($headerName, $headerValue);
564
    }
565
566
    /**
567
     * Translates the short $format forms into the full mime type forms
568
     * @param Version $responseVersion the version scheme to interpret the short form with
569
     * @param string $format the short $format form
570
     * @return string the full mime type corresponding to the short format form for the given version
571
     */
572
    public static function translateFormatToMime(Version $responseVersion, $format) {
573
        //TODO: should the version switches be off of the requestVersion, not the response version? see #91
574
575
        switch ($format) {
576
577
            case ODataConstants::FORMAT_XML:
578
                $format = MimeTypes::MIME_APPLICATION_XML;
579
                break;
580
581
            case ODataConstants::FORMAT_ATOM:
582
                $format = MimeTypes::MIME_APPLICATION_ATOM;
583
                break;
584
585
            case ODataConstants::FORMAT_VERBOSE_JSON:
586
                if ($responseVersion == Version::v3()) {
587
                    //only translatable in 3.0 systems
588
                    $format = MimeTypes::MIME_APPLICATION_JSON_VERBOSE;
589
                }
590
                break;
591
592
            case ODataConstants::FORMAT_JSON:
593
                if ($responseVersion == Version::v3()) {
594
                    $format = MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META;
595
                } else {
596
                    $format = MimeTypes::MIME_APPLICATION_JSON;
597
                }
598
                break;
599
600
        }
601
602
        return $format . ';q=1.0';
603
    }
604
605
606
    public function getFullAbsoluteRequestUri()
607
    {
608
        return $this->_fullAbsoluteRequestUri;
609
    }
610
611
}
612