1: <?php
2: /*
3: * SimpleID
4: *
5: * Copyright (C) Kelvin Mo 2014-2025
6: *
7: * This program is free software; you can redistribute it and/or
8: * modify it under the terms of the GNU General Public
9: * License as published by the Free Software Foundation; either
10: * version 2 of the License, or (at your option) any later version.
11: *
12: * This program is distributed in the hope that it will be useful,
13: * but WITHOUT ANY WARRANTY; without even the implied warranty of
14: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15: * General Public License for more details.
16: *
17: * You should have received a copy of the GNU General Public
18: * License along with this program; if not, write to the Free
19: * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20: */
21:
22: namespace SimpleID\Protocols\OAuth;
23:
24: use \Base;
25: use SimpleID\Protocols\HTTPResponse;
26: use SimpleID\Util\ArrayWrapper;
27:
28: /**
29: * A utility class representing a HTTP request. This class contains
30: * methods which are useful for processing OAuth-related requests.
31: */
32: class Request extends ArrayWrapper {
33: /** @var array<string, string> the HTTP headers */
34: protected $headers = [];
35:
36: /** @var bool whether the request prohibits user intervention */
37: private $immediate = false;
38:
39: /**
40: * Creates a HTTP request.
41: *
42: * @param array<string, mixed> $params the request parameters
43: * @param array<string, string> $headers the HTTP request headers
44: */
45: public function __construct($params = NULL, $headers = NULL) {
46: if ($params == NULL) $params = $_REQUEST;
47: parent::__construct($params);
48:
49: if ($headers == NULL) {
50: // Special cases
51: if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) $this->headers['authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
52: if (isset($_SERVER['PHP_AUTH_TYPE'])) {
53: if (isset($_SERVER['PHP_AUTH_DIGEST'])) {
54: $this->headers['authorization'] = 'Digest ' . $_SERVER['PHP_AUTH_DIGEST'];
55: } elseif (isset($_SERVER['PHP_AUTH_PW'])) {
56: $this->headers['authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW']);
57: }
58: }
59:
60: foreach ($_SERVER as $name => $value) {
61: if (substr($name, 0, 5) != 'HTTP_') continue;
62:
63: $this->headers[HTTPResponse::httpCase(strtr(substr($name, 5), '_', '-'))] = $value;
64: }
65: } else {
66: foreach ($headers as $name => $value) $this->headers[HTTPResponse::httpCase($name)] = $value;
67: }
68: }
69:
70: /**
71: * Returns whether the request requires that no user
72: * interaction is to be made.
73: *
74: * @return bool true if no user interaction is to be made
75: */
76: public function isImmediate() {
77: return $this->immediate;
78: }
79:
80: /**
81: * Sets whether the request requires that no user
82: * interaction is to be made.
83: *
84: * @param bool $immediate true if no user interaction is to be made
85: * @return void
86: */
87: public function setImmediate($immediate) {
88: $this->immediate = $immediate;
89: }
90:
91: /**
92: * Returns the value of a specified header
93: *
94: * @param string $header the header to return
95: * @return string the value of the header
96: */
97: public function getHeader($header) {
98: return ($this->hasHeader($header)) ? $this->headers[HTTPResponse::httpCase($header)] : null;
99: }
100:
101: /**
102: * Returns whether a specified header exists
103: *
104: * @param string $header the header
105: * @return bool true if the header exists
106: */
107: public function hasHeader($header) {
108: return array_key_exists(HTTPResponse::httpCase($header), $this->headers);
109: }
110:
111: /**
112: * Parses and returns the request's `Authorization` header.
113: *
114: * This method extracts the request's `Authorization` header and returns an
115: * array with the following elements:
116: *
117: * - `#scheme` - the authentication scheme, e.g. Basic, Bearer
118: * - `#credentials` - the credentials following the scheme
119: *
120: * If `$parse_credentials` is true, the method will also attempt to parse
121: * the credential information. For the `Basic` scheme, the user name and
122: * password will be returned in the array as `#username` and `#password`
123: * respectively. For other schemes with delimited name-value parameters,
124: * those name-value pairs will be returned.
125: *
126: * @param bool $parse_credentials whether to parse the credential information
127: * @return array<string, string>|null the parsed `Authorization` header, or `null` if none
128: * exists
129: */
130: public function getAuthorizationHeader($parse_credentials = false) {
131: if (!$this->hasHeader('Authorization') || $this->getHeader('Authorization') == '') return null;
132:
133: $results = [];
134:
135: $header = $this->getHeader('Authorization');
136: $array = preg_split('/\s+/', $header, 2);
137: if ($array != false) {
138: list($scheme, $credentials) = $array;
139: } else {
140: return [ '#credentials' => $header ];
141: }
142:
143: $results['#scheme'] = HTTPResponse::httpCase($scheme);
144: $results['#credentials'] = $credentials;
145:
146: if ($parse_credentials) {
147: if ($results['#scheme'] == 'Basic') {
148: list($username, $password) = explode(':', base64_decode($credentials));
149: $results['#username'] = $username;
150: $results['#password'] = $password;
151: } else {
152: $matches = [];
153: preg_match_all('/([-a-zA-Z]+)=\"([^\"]+)\"/', $credentials, $matches, PREG_SET_ORDER);
154: foreach ($matches as $match) $results[strval($match[1])] = $match[2];
155: }
156: }
157:
158: return $results;
159: }
160:
161: /**
162: * Converts a parameter consisting of space-delimited values
163: * into an array of values.
164: *
165: * @param string $param the parameter name to check
166: * @param string $delimiter a regular expression to determine
167: * the delimiter between values
168: * @return array<string>|null an array of values, or `null` if the parameter
169: * is not found
170: */
171: public function paramToArray($param, $delimiter = '/\s+/') {
172: if (!isset($this->container[$param])) return null;
173: $split = preg_split($delimiter, $this->container[$param]);
174: if ($split == false) return $this->container[$param];
175: return $split;
176: }
177:
178: /**
179: * Returns whether a parameter consisting of space-delimited values
180: * contains a specified value.
181: *
182: * @param string $param the parameter name to check
183: * @param string $contains the value
184: * @param string $delimiter a regular expression to determine
185: * the delimiter between values
186: * @return bool true if the parameter contains the value, or `null` if the parameter
187: * is not found
188: */
189: public function paramContains($param, $contains, $delimiter = '/\s+/') {
190: if (!isset($this->container[$param])) return false;
191: $items = $this->paramToArray($param);
192: return in_array($contains, $items);
193: }
194:
195: /**
196: * Remove a specified value from a parameter consisting of space-delimited
197: * values
198: *
199: * @param string $param the parameter name to check
200: * @param string $value the value to remove
201: * @param string $delimiter a regular expression to determine
202: * the delimiter between values
203: * @return void
204: */
205: public function paramRemove($param, $value, $delimiter = '/\s+/') {
206: if (!isset($this->container[$param])) return;
207: if (!$this->paramContains($param, $value)) return;
208:
209: $items = preg_split($delimiter, $this->container[$param]);
210: if ($items == false) $items = $this->container[$param];
211: $items = array_diff($items, [ $value ]);
212:
213: preg_match($delimiter, $this->container[$param], $matches);
214: $this->container[$param] = implode($matches[0], $items);
215: }
216: }
217:
218: ?>
219: