3 * @copyright Copyright (C) 2010-2022, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
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
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Security\OAuth1;
26 public static function urlencode_rfc3986($input)
28 if (is_array($input)) {
29 return array_map(['Friendica\Security\OAuth1\OAuthUtil', 'urlencode_rfc3986'], $input);
30 } else if (is_scalar($input)) {
34 str_replace('%7E', '~', rawurlencode($input))
42 // This decode function isn't taking into consideration the above
43 // modifications to the encoding process. However, this method doesn't
44 // seem to be used anywhere so leaving it as is.
45 public static function urldecode_rfc3986($string)
47 return urldecode($string);
50 // Utility function for turning the Authorization: header into
51 // parameters, has to do some unescaping
52 // Can filter out any non-oauth parameters if needed (default behaviour)
53 public static function split_header($header, $only_allow_oauth_parameters = true)
55 $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
58 while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
60 $header_name = $matches[2][0];
61 $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];
62 if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) {
63 $params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content);
65 $offset = $match[1] + strlen($match[0]);
68 if (isset($params['realm'])) {
69 unset($params['realm']);
75 // helper to try to sort out headers for people who aren't running apache
76 public static function get_headers()
78 if (function_exists('apache_request_headers')) {
79 // we need this to get the actual Authorization: header
80 // because apache tends to tell us it doesn't exist
81 $headers = apache_request_headers();
83 // sanitize the output of apache_request_headers because
84 // we always want the keys to be Cased-Like-This and arh()
85 // returns the headers in the same case as they are in the
88 foreach ($headers as $key => $value) {
92 ucwords(strtolower(str_replace("-", " ", $key)))
97 // otherwise we don't have apache and are just going to have to hope
98 // that $_SERVER actually contains what we need
100 if (isset($_SERVER['CONTENT_TYPE']))
101 $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
102 if (isset($_ENV['CONTENT_TYPE']))
103 $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
105 foreach ($_SERVER as $key => $value) {
106 if (substr($key, 0, 5) == "HTTP_") {
107 // this is chaos, basically it is just there to capitalize the first
108 // letter of every word that is not an initial HTTP and strip HTTP
113 ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
122 // This function takes a input like a=b&a=c&d=e and returns the parsed
123 // parameters like this
124 // array('a' => array('b','c'), 'd' => 'e')
125 public static function parse_parameters($input)
127 if (!isset($input) || !$input) return [];
129 $pairs = explode('&', $input);
131 $parsed_parameters = [];
132 foreach ($pairs as $pair) {
133 $split = explode('=', $pair, 2);
134 $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
135 $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
137 if (isset($parsed_parameters[$parameter])) {
138 // We have already recieved parameter(s) with this name, so add to the list
139 // of parameters with this name
141 if (is_scalar($parsed_parameters[$parameter])) {
142 // This is the first duplicate, so transform scalar (string) into an array
143 // so we can add the duplicates
144 $parsed_parameters[$parameter] = [$parsed_parameters[$parameter]];
147 $parsed_parameters[$parameter][] = $value;
149 $parsed_parameters[$parameter] = $value;
152 return $parsed_parameters;
155 public static function build_http_query($params)
157 if (!$params) return '';
159 // Urlencode both keys and values
160 $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
161 $values = OAuthUtil::urlencode_rfc3986(array_values($params));
162 $params = array_combine($keys, $values);
164 // Parameters are sorted by name, using lexicographical byte value ordering.
165 // Ref: Spec: 9.1.1 (1)
166 uksort($params, 'strcmp');
169 foreach ($params as $parameter => $value) {
170 if (is_array($value)) {
171 // If two or more parameters share the same name, they are sorted by their value
172 // Ref: Spec: 9.1.1 (1)
174 foreach ($value as $duplicate_value) {
175 $pairs[] = $parameter . '=' . $duplicate_value;
178 $pairs[] = $parameter . '=' . $value;
181 // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
182 // Each name-value pair is separated by an '&' character (ASCII code 38)
183 return implode('&', $pairs);