Passed
Push — master ( 4ab488...bcfbc7 )
by Bálint
03:58
created

ServiceHost::validateQueryParameters()   B

Complexity

Conditions 11
Paths 10

Size

Total Lines 61
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 34
dl 0
loc 61
c 0
b 0
f 0
rs 7.3166
cc 11
nc 10
nop 0

How to fix   Long Method    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
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 $_absoluteRequestUri;
40
41
    /**
42
     * The absolute request Uri as string
43
     * Note: This will not contain query string
44
     * 
45
     * @var string
46
     */
47
    private $_absoluteRequestUriAsString = null;
48
49
    /**
50
     * The absolute service uri as Url instance.
51
     * Note: This value will be taken from configuration file
52
     * 
53
     * @var Url
54
     */
55
    private $_absoluteServiceUri;
56
57
    /**
58
     * The absolute service uri string.
59
     * Note: This value will be taken from configuration file
60
     * 
61
     * @var string
62
     */
63
    private $_absoluteServiceUriAsString = null;
64
65
    /**
66
     * array of query-string parameters
67
     * 
68
     * @var array(string, string)
69
     */
70
    private $_queryOptions;
71
72
    /**
73
     * Gets reference to the operation context.
74
     * 
75
     * @return IOperationContext
76
     */
77
    public function getOperationContext()
78
    {
79
        return $this->_operationContext;
80
    }
81
82
    /**
83
     * @param IOperationContext $context the OperationContext implementation to use.
84
     * If null the IlluminateOperationContex will be used.  Defaults to null.
85
     *
86
     * Currently we are forcing the input request to be of type
87
     * \Illuminate\Http\Request but in the future we could make this more flexible
88
     * if needed.
89
     *
90
     * @param Request $incomingRequest
91
     * @throws ODataException
92
     */
93
    public function __construct(IOperationContext $context = null, Request $incomingRequest)
94
    {
95
        if (is_null($context)) {
96
            $this->_operationContext = new IlluminateOperationContext($incomingRequest);
97
        } else {
98
            $this->_operationContext = $context;
99
        }
100
101
        // getAbsoluteRequestUri can throw UrlFormatException 
102
        // let Dispatcher handle it
103
        $this->_absoluteRequestUri = $this->getAbsoluteRequestUri();
104
        $this->_absoluteServiceUri = null;
105
106
        //Dev Note: Andrew Clinton 5/19/16
107
        //_absoluteServiceUri is never being set from what I can tell
108
        //so for now we'll set it as such
109
        $this->setServiceUri($this->_getServiceUri());
110
    }
111
112
    /**
113
     * Gets the absolute request Uri as Url instance
114
     * Note: This method will be called first time from constructor.
115
     * 
116
     * @throws ODataException if AbsoluteRequestUri is not a valid URI
117
     * 
118
     * @return Url
119
     */
120
    public function getAbsoluteRequestUri()
121
    {
122
        if (is_null($this->_absoluteRequestUri)) {
123
            $this->_absoluteRequestUriAsString = $this->_operationContext->incomingRequest()->getRawUrl();
124
            // Validate the uri first
125
            try {
126
                new Url($this->_absoluteRequestUriAsString);
127
            } catch (UrlFormatException $exception) {
128
                throw ODataException::createBadRequestError($exception->getMessage());
129
            }
130
131
            $queryStartIndex = strpos($this->_absoluteRequestUriAsString, '?');
132
            if ($queryStartIndex !== false) {
133
                $this->_absoluteRequestUriAsString = substr(
134
                    $this->_absoluteRequestUriAsString,
135
                    0,
136
                    $queryStartIndex
137
                );
138
            }
139
140
            // We need the absolute uri only not associated components 
141
            // (query, fragments etc..)
142
            $this->_absoluteRequestUri = new Url($this->_absoluteRequestUriAsString);
143
            $this->_absoluteRequestUriAsString = rtrim($this->_absoluteRequestUriAsString, '/');
144
        }
145
146
        return $this->_absoluteRequestUri;
147
    }
148
149
    /**
150
     * Gets the absolute request Uri as string
151
     * Note: This will not contain query string
152
     * 
153
     * @return string
154
     */
155
    public function getAbsoluteRequestUriAsString()
156
    {
157
        return $this->_absoluteRequestUriAsString;
158
    }
159
160
161
    /**
162
     * Sets the service url from which the OData URL is parsed
163
     *
164
     * @param string $serviceUri The service url, absolute or relative.
165
     * 
166
     * @return void
167
     * 
168
     * @throws ODataException If the base uri in the configuration is malformed.
169
     */
170
    public function setServiceUri($serviceUri)
171
    {
172
        if (is_null($this->_absoluteServiceUri)) {
173
            $isAbsoluteServiceUri = (strpos($serviceUri, 'http://') === 0)
174
                || (strpos($serviceUri, 'https://') === 0);
175
            try {
176
                $this->_absoluteServiceUri = new Url($serviceUri, $isAbsoluteServiceUri);
177
            } catch (UrlFormatException $exception) {
178
                throw ODataException::createInternalServerError(Messages::hostMalFormedBaseUriInConfig());
179
            }
180
181
            $segments = $this->_absoluteServiceUri->getSegments();
182
            $lastSegment = $segments[count($segments) - 1];
183
            $endsWithSvc
184
                = (substr_compare($lastSegment, '.svc', -strlen('.svc'), strlen('.svc')) === 0);
185
            if (!$endsWithSvc
186
                || !is_null($this->_absoluteServiceUri->getQuery())
187
                || !is_null($this->_absoluteServiceUri->getFragment())
188
            ) {
189
                throw ODataException::createInternalServerError(Messages::hostMalFormedBaseUriInConfig(true));
190
            }
191
192
            if (!$isAbsoluteServiceUri) {
193
                $requestUriSegments = $this->_absoluteRequestUri->getSegments();
194
                $i = count($requestUriSegments) - 1;
195
                // Find index of segment in the request uri that end with .svc
196
                // There will be always a .svc segment in the request uri otherwise
197
                // uri redirection will not happen.
198
                for (; $i >= 0; $i--) {
199
                    $endsWithSvc = (substr_compare($requestUriSegments[$i], '.svc', -strlen('.svc'), strlen('.svc')) === 0);
200
                    if ($endsWithSvc) {
201
                        break;
202
                    }
203
                }
204
                
205
                $j = count($segments) - 1;
206
                $k = $i;
207
                if ($j > $i) {
208
                    throw ODataException::createBadRequestError(
209
                        Messages::hostRequestUriIsNotBasedOnRelativeUriInConfig(
210
                            $this->_absoluteRequestUriAsString,
211
                            $serviceUri
212
                        )
213
                    );
214
                }
215
216
                while ($j >= 0 && ($requestUriSegments[$i] === $segments[$j])) {
217
                    $i--; $j--;
218
                }
219
220
                if ($j != -1) {
221
                    throw ODataException::createBadRequestError(
222
                        Messages::hostRequestUriIsNotBasedOnRelativeUriInConfig(
223
                            $this->_absoluteRequestUriAsString,
224
                            $serviceUri
225
                        )
226
                    );
227
                }
228
229
                $serviceUri = $this->_absoluteRequestUri->getScheme() 
230
                    . '://' 
231
                    . $this->_absoluteRequestUri->getHost()
232
                    . ':' 
233
                    . $this->_absoluteRequestUri->getPort();
234
235
                for ($l = 0; $l <= $k; $l++) {
236
                    $serviceUri .= '/' . $requestUriSegments[$l];
237
                }
238
                
239
                $this->_absoluteServiceUri = new Url($serviceUri);
240
            }
241
242
            $this->_absoluteServiceUriAsString = $serviceUri;
243
        }
244
    }
245
246
247
    /**
248
     * Gets the absolute Uri to the service as Url instance.
249
     * Note: This will be the value taken from configuration file.
250
     * 
251
     * @return Url
252
     */
253
    public function getAbsoluteServiceUri()
254
    {
255
        return $this->_absoluteServiceUri;
256
    }
257
258
    /**
259
     * Gets the absolute Uri to the service as string
260
     * Note: This will be the value taken from configuration file.
261
     * 
262
     * @return string
263
     */
264
    public function getAbsoluteServiceUriAsString()
265
    {
266
        return $this->_absoluteServiceUriAsString;
267
    }
268
269
270
    /**
271
     * This method verfies the client provided url query parameters and check whether
272
     * any of the odata query option specified more than once or check any of the 
273
     * non-odata query parameter start will $ symbol or check any of the odata query 
274
     * option specified with out value. If any of the above check fails throws 
275
     * ODataException, else set _queryOptions member variable
276
     * 
277
     * @return void
278
     * 
279
     * @throws ODataException
280
     */
281
    public function validateQueryParameters()
282
    {
283
        $queryOptions = $this->_operationContext->incomingRequest()->getQueryParameters();
284
285
        reset($queryOptions);
286
        $namesFound = array();
287
        while ($queryOption = current($queryOptions)) {
288
            $optionName = key($queryOption);
289
            $optionValue = current($queryOption);
290
            if (empty($optionName)) {
291
                if (!empty($optionValue)) {
292
                    if ($optionValue[0] == '$') {
293
                        if ($this->_isODataQueryOption($optionValue)) {
294
                            throw ODataException::createBadRequestError(
295
                                Messages::hostODataQueryOptionFoundWithoutValue(
296
                                    $optionValue
297
                                )
298
                            );
299
                        } else {
300
                            throw ODataException::createBadRequestError(
301
                                Messages::hostNonODataOptionBeginsWithSystemCharacter(
302
                                    $optionValue
303
                                )
304
                            );
305
                        }
306
                    }
307
                }
308
            } else {
309
                if ($optionName[0] == '$') {
310
                    if (!$this->_isODataQueryOption($optionName)) {
311
                        throw ODataException::createBadRequestError(
312
                            Messages::hostNonODataOptionBeginsWithSystemCharacter(
313
                                $optionName
314
                            )
315
                        );
316
                    }
317
318
                    if (array_search($optionName, $namesFound) !== false) {
319
                        throw ODataException::createBadRequestError(
320
                            Messages::hostODataQueryOptionCannotBeSpecifiedMoreThanOnce(
321
                                $optionName
322
                            )
323
                        );
324
                    }
325
                    
326
                    if (empty($optionValue) && $optionValue !== '0') {
327
                        throw ODataException::createBadRequestError(
328
                            Messages::hostODataQueryOptionFoundWithoutValue(
329
                                $optionName
330
                            )
331
                        );
332
                    }
333
334
                    $namesFound[] = $optionName;
335
                }
336
            }
337
            
338
            next($queryOptions);
339
        }
340
        
341
        $this->_queryOptions = $queryOptions;
342
    }
343
344
    /**
345
     * Dev Note: Andrew Clinton
346
     * 5/19/16
347
     *
348
     * Currently it doesn't seem that the service URI is ever being built
349
     * so I am doing that here.
350
     *
351
     * return void
352
     */
353
    private function _getServiceUri()
354
    {
355
        if (($pos = strpos($this->_absoluteRequestUriAsString, ".svc")) !== FALSE) {
356
            $serviceUri = substr($this->_absoluteRequestUriAsString, 0, $pos + strlen(".svc"));
357
            return $serviceUri;
358
        }
359
360
        return $this->_absoluteRequestUriAsString;
361
    }
362
363
    /**
364
     * Verifies the given url option is a valid odata query option.
365
     * 
366
     * @param string $optionName option to validate
367
     * 
368
     * @return boolean True if the given option is a valid odata option False otherwise.
369
     *
370
     */
371
    private function _isODataQueryOption($optionName)
372
    {
373
        return ($optionName === ODataConstants::HTTPQUERY_STRING_FILTER ||
374
                $optionName === ODataConstants::HTTPQUERY_STRING_EXPAND ||
375
                $optionName === ODataConstants::HTTPQUERY_STRING_INLINECOUNT ||
376
                $optionName === ODataConstants::HTTPQUERY_STRING_ORDERBY ||
377
                $optionName === ODataConstants::HTTPQUERY_STRING_SELECT ||
378
                $optionName === ODataConstants::HTTPQUERY_STRING_SKIP ||
379
                $optionName === ODataConstants::HTTPQUERY_STRING_SKIPTOKEN ||
380
                $optionName === ODataConstants::HTTPQUERY_STRING_TOP ||
381
                $optionName === ODataConstants::HTTPQUERY_STRING_FORMAT);
382
    }
383
384
    /**
385
     * Gets the value for the specified item in the request query string
386
     * Remark: This method assumes 'validateQueryParameters' has already been 
387
     * called.
388
     * 
389
     * @param string $item The query item to get the value of.
390
     * 
391
     * @return string|null The value for the specified item in the request 
392
     *                     query string NULL if the query option is absent.
393
     */
394
    public function getQueryStringItem($item)
395
    {
396
        foreach ($this->_queryOptions as $queryOption) {
397
            if (array_key_exists($item, $queryOption)) {
398
                return $queryOption[$item];
399
            }
400
        }
401
402
        return null;
403
    }
404
405
    /**
406
     * Gets the value for the DataServiceVersion header of the request.
407
     * 
408
     * @return string|null
409
     */
410
    public function getRequestVersion()
411
    {
412
        return $this->_operationContext
413
            ->incomingRequest()
414
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_DATA_SERVICE_VERSION);
415
    }
416
417
    /**
418
     * Gets the value of MaxDataServiceVersion header of the request
419
     * 
420
     * @return string|null
421
     */
422
    public function getRequestMaxVersion()
423
    {
424
        return $this->_operationContext
425
            ->incomingRequest()
426
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_MAX_DATA_SERVICE_VERSION);
427
    }     
428
429
    
430
    /**
431
     * Get comma separated list of client-supported MIME Accept types
432
     * 
433
     * @return string
434
     */
435
    public function getRequestAccept()
436
    {
437
        return $this->_operationContext
438
            ->incomingRequest()
439
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_ACCEPT);
440
    }
441
442
443
    /**
444
     * Get the character set encoding that the client requested
445
     * 
446
     * @return string
447
     */
448
    public function getRequestAcceptCharSet()
449
    {
450
        return $this->_operationContext
451
            ->incomingRequest()
452
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_ACCEPT_CHARSET);
453
    }
454
        
455
456
457
    /**
458
     * Get the value of If-Match header of the request
459
     * 
460
     * @return string 
461
     */
462
    public function getRequestIfMatch()
463
    {
464
        return $this->_operationContext
465
            ->incomingRequest()
466
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_IF_MATCH);
467
    }
468
        
469
    /**
470
     * Gets the value of If-None-Match header of the request
471
     * 
472
     * @return string
473
     */
474
    public function getRequestIfNoneMatch()
475
    {
476
        return $this->_operationContext
477
            ->incomingRequest()
478
            ->getRequestHeader(ODataConstants::HTTPREQUEST_HEADER_IF_NONE);
479
    }      
480
481
    /**
482
     * Gets the value of Content-Type header of the request
483
     * 
484
     * @return string
485
     */
486
    public function getRequestContentType()
487
    {
488
        return $this->_operationContext
489
            ->incomingRequest()
490
            ->getRequestHeader(ODataConstants::HTTP_CONTENTTYPE);
491
    }
492
493
    /**
494
     * Set the Cache-Control header on the response
495
     * 
496
     * @param string $value The cache-control value.
497
     * 
498
     * @return void
499
     * 
500
     @ throws InvalidOperation
501
     */
502
    public function setResponseCacheControl($value)
503
    {
504
        $this->_operationContext->outgoingResponse()->setCacheControl($value);
505
    }      
506
507
    /**
508
     * Gets the HTTP MIME type of the output stream
509
     * 
510
     * @return string
511
     */
512
    public function getResponseContentType()
513
    {
514
        return $this->_operationContext
515
            ->outgoingResponse()
516
            ->getContentType();
517
    }
518
        
519
    /**
520
     * Sets the HTTP MIME type of the output stream
521
     * 
522
     * @param string $value The HTTP MIME type
523
     * 
524
     * @return void
525
     */
526
    public function setResponseContentType($value)
527
    {
528
        $this->_operationContext
529
            ->outgoingResponse()
530
            ->setContentType($value);
531
    }      
532
        
533
    /**
534
     * Sets the content length of the output stream
535
     * 
536
     * @param string $value The content length
537
     * 
538
     * @return void
539
     * 
540
     * @throw Exception if $value is not numeric throws notAcceptableError
541
     */
542
    public function setResponseContentLength($value)
543
    {
544
        if (preg_match('/[0-9]+/', $value)) {
545
            $this->_operationContext->outgoingResponse()->setContentLength($value);
546
        } else {
547
            throw ODataException::notAcceptableError(
548
                "ContentLength:$value is invalid"
549
            );
550
        }
551
    }
552
    
553
    /**
554
     * Gets the value of the ETag header on the response
555
     * 
556
     * @return string|null
557
     */
558
    public function getResponseETag()
559
    {
560
        return $this->_operationContext->outgoingResponse()->getETag();
561
    }
562
        
563
    /**
564
     * Sets the value of the ETag header on the response
565
     * 
566
     * @param string $value The ETag value
567
     * 
568
     * @return void
569
     */
570
    public function setResponseETag($value)
571
    {
572
        $this->_operationContext->outgoingResponse()->setETag($value);
573
    }
574
575
    /**
576
     * Sets the value Location header on the response
577
     * 
578
     * @param string $value The location.
579
     * 
580
     * @return void
581
     */
582
    public function setResponseLocation($value)
583
    {
584
        $this->_operationContext->outgoingResponse()->setLocation($value);
585
    }
586
587
    /**
588
     * Sets the value status code header on the response
589
     * 
590
     * @param string $value The status code
591
     * 
592
     * @return void
593
     */
594
    public function setResponseStatusCode($value)
595
    {
596
        $floor = floor($value/100);
597
        if ($floor >= 1 && $floor <= 5) {
598
            $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

598
            $statusDescription = HttpStatus::getStatusDescription(/** @scrutinizer ignore-type */ $value);
Loading history...
599
            if (!is_null($statusDescription)) {
600
                $statusDescription = ' ' . $statusDescription;
601
            }
602
603
            $this->_operationContext
604
                ->outgoingResponse()->setStatusCode($value . $statusDescription);
605
        } else {
606
            throw ODataException::createInternalServerError(
607
                'Invalid Status Code' . $value
608
            );
609
        }
610
    }
611
612
    /**
613
     * Sets the value status description header on the response
614
     * 
615
     * @param string $value The status description
616
     * 
617
     * @return void
618
     */
619
    public function setResponseStatusDescription($value)
620
    {
621
        $this->_operationContext
622
            ->outgoingResponse()->setStatusDescription($value);
623
    }
624
625
    /**
626
     * Sets the value stream to be send a response
627
     * 
628
     * @param string &$value The stream
629
     * 
630
     * @return void
631
     */
632
    public function setResponseStream(&$value)
633
    {
634
        $this->_operationContext->outgoingResponse()->setStream($value);
635
    }
636
637
    /**
638
     * Sets the DataServiceVersion response header
639
     * 
640
     * @param string $value The version
641
     * 
642
     * @return void
643
     */
644
    public function setResponseVersion($value)
645
    {
646
        $this->_operationContext->outgoingResponse()->setServiceVersion($value);
647
    }
648
649
    /**
650
     * Get the response headers
651
     * 
652
     * @return array<headername, headerValue>
653
     */
654
    public function &getResponseHeaders()
655
    {
656
        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...
657
    }
658
659
    /**
660
     * Add a header to response header collection
661
     * 
662
     * @param string $headerName  The name of the header
663
     * @param string $headerValue The value of the header
664
     * 
665
     * @return void
666
     */
667
    public function addResponseHeader($headerName, $headerValue)
668
    {
669
        $this->_operationContext
670
            ->outgoingResponse()->addHeader($headerName, $headerValue);
671
    }
672
673
    /**
674
     * Translates the short $format forms into the full mime type forms
675
     * @param Version $responseVersion the version scheme to interpret the short form with
676
     * @param string $format the short $format form
677
     * @return string the full mime type corresponding to the short format form for the given version
678
     */
679
    public static function translateFormatToMime(Version $responseVersion, $format) {
680
        //TODO: should the version switches be off of the requestVersion, not the response version? see #91
681
682
        switch ($format) {
683
684
            case ODataConstants::FORMAT_XML:
685
                $format = MimeTypes::MIME_APPLICATION_XML;
686
                break;
687
688
            case ODataConstants::FORMAT_ATOM:
689
                $format = MimeTypes::MIME_APPLICATION_ATOM;
690
                break;
691
692
            case ODataConstants::FORMAT_VERBOSE_JSON:
693
                if ($responseVersion == Version::v3()) {
694
                    //only translatable in 3.0 systems
695
                    $format = MimeTypes::MIME_APPLICATION_JSON_VERBOSE;
696
                }
697
                break;
698
699
            case ODataConstants::FORMAT_JSON:
700
                if ($responseVersion == Version::v3()) {
701
                    $format = MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META;
702
                } else {
703
                    $format = MimeTypes::MIME_APPLICATION_JSON;
704
                }
705
                break;
706
707
        }
708
709
        return $format . ';q=1.0';
710
    }
711
712
713
714
}