Skip to content

Commit f7bb13b

Browse files
authored
Merge pull request #18 from rtckit/v0.7.0
v0.7.0
2 parents eb8f2aa + c26ca55 commit f7bb13b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1392
-805
lines changed

composer.json

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
{
22
"name": "rtckit/sip",
3-
"description": "Parser/Renderer for SIP protocol written in PHP",
4-
"version": "0.6.1",
3+
"description": "SIP protocol implementation written in PHP",
4+
"version": "0.7.0",
55
"type": "library",
66
"keywords": [
77
"sip",
88
"session initiation protocol",
9-
"voip"
9+
"voip",
10+
"rfc 3261",
11+
"telephony",
12+
"telco"
1013
],
1114
"homepage": "https://github.com/rtckit/php-sip",
1215
"license": "MIT",
@@ -20,13 +23,17 @@
2023
"issues": "https://github.com/rtckit/php-sip/issues"
2124
},
2225
"require": {
23-
"php": ">=7.4.0"
26+
"php": ">=7.4.0",
27+
"ext-ctype": "*"
2428
},
2529
"require-dev": {
2630
"phpstan/phpstan": "^0.12",
2731
"phpunit/phpunit": "^9.5",
2832
"symfony/yaml": "^5.3",
29-
"vimeo/psalm": "^4.8"
33+
"vimeo/psalm": "^4.10"
34+
},
35+
"suggest": {
36+
"ext-hash": "Enables RFC 8760 authentication via SHA(-512)-256 hashing"
3037
},
3138
"autoload": {
3239
"psr-4": {

src/Auth/Digest/AbstractParams.php

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
<?php
2+
/**
3+
* RTCKit\SIP\Auth\Digest\AbstractParams Class
4+
*/
5+
declare(strict_types = 1);
6+
7+
namespace RTCKit\SIP\Auth\Digest;
8+
9+
use RTCKit\SIP\Response;
10+
use RTCKit\SIP\Auth\ParamsInterface;
11+
use RTCKit\SIP\Exception\InvalidHeaderLineException;
12+
use RTCKit\SIP\Exception\InvalidHeaderParameterException;
13+
14+
/**
15+
* Digest authentication scheme parameters abstract class
16+
*/
17+
abstract class AbstractParams implements ParamsInterface
18+
{
19+
/** @var string Lowercase scheme name */
20+
public const SCHEME_NAME = 'digest';
21+
22+
/** @var string Scheme name using preferred/common case */
23+
public const PREFERRED_CASE = 'Digest';
24+
25+
/** @var string "auth" Quality of protection */
26+
public const QOP_AUTH = 'auth';
27+
28+
/** @var string "auth-int" Quality of protection */
29+
public const QOP_AUTH_INT = 'auth-int';
30+
31+
/** @var string Default hashing algorithm */
32+
public const DEFAULT_ALGORITHM = 'MD5';
33+
34+
/** @var string Authentication realm */
35+
public string $realm;
36+
37+
/** @var string Digest algorithm */
38+
public string $algorithm;
39+
40+
/** @var string Server's number once */
41+
public string $nonce;
42+
43+
/** @var string Server's opaque data blob */
44+
public string $opaque;
45+
46+
/** @var array<string,string> Additional parameters */
47+
public array $extra = [];
48+
49+
final public function __construct() {}
50+
51+
/**
52+
* Parses digest parameters out of a string input
53+
*
54+
* @param string $input Unparsed digest parameters
55+
* @throws InvalidHeaderLineException
56+
* @throws InvalidHeaderParameterException
57+
* @return ParamsInterface Parsed Challenge or Response parameters
58+
*/
59+
public static function parse(string $input): ParamsInterface
60+
{
61+
$orig = $input;
62+
$params = new static;
63+
$challenge = static::class === ChallengeParams::class;
64+
65+
while (strlen($input)) {
66+
$pos = strpos($input, '=');
67+
68+
if ($pos === false) {
69+
throw new InvalidHeaderLineException('Invalid Auth header, valueless parameter', Response::BAD_REQUEST);
70+
}
71+
72+
$pk = rtrim(substr($input, 0, $pos));
73+
$pv = '';
74+
$input = ltrim(substr($input, $pos + 1));
75+
76+
if (!isset($input[0])) {
77+
throw new InvalidHeaderLineException('Invalid Auth header, valueless parameter', Response::BAD_REQUEST);
78+
}
79+
80+
if ($input[0] === '"') {
81+
$offset = 1;
82+
$escQuotes = false;
83+
84+
while (true) {
85+
$pos = strpos($input, '"', $offset);
86+
87+
if ($pos === false) {
88+
throw new InvalidHeaderLineException('Invalid Auth header, unmatched parameter value enclosing', Response::BAD_REQUEST);
89+
}
90+
91+
if ($input[$pos - 1] !== '\\') {
92+
break;
93+
}
94+
95+
$escQuotes = true;
96+
$offset = $pos + 1;
97+
}
98+
99+
$pv = substr($input, 1, $pos - 1);
100+
101+
if ($escQuotes) {
102+
$pv = str_replace('\"', '"', $pv);
103+
}
104+
105+
$input = ltrim(substr($input, $pos + 1));
106+
107+
if (isset($input[0])) {
108+
if ($input[0] !== ',') {
109+
throw new InvalidHeaderLineException('Invalid Auth header, invalid parameter value enclosing', Response::BAD_REQUEST);
110+
}
111+
112+
$input = ltrim(substr($input, 1));
113+
}
114+
} else {
115+
$pos = strpos($input, ',');
116+
117+
if ($pos !== false) {
118+
$pv = rtrim(substr($input, 0, $pos));
119+
$input = ltrim(substr($input, $pos + 1));
120+
} else {
121+
$pv = rtrim($input);
122+
$input = '';
123+
}
124+
}
125+
126+
switch ($pk) {
127+
case 'realm':
128+
$params->realm = $pv;
129+
break;
130+
131+
case 'algorithm':
132+
$params->algorithm = $pv;
133+
break;
134+
135+
case 'nonce':
136+
$params->nonce = $pv;
137+
break;
138+
139+
case 'opaque':
140+
$params->opaque = $pv;
141+
break;
142+
143+
default:
144+
if ($challenge) {
145+
/** @var ChallengeParams $params */
146+
switch ($pk) {
147+
case 'domain':
148+
$params->domain = $pv;
149+
break 2;
150+
151+
case 'stale':
152+
$pv = strtolower($pv);
153+
154+
if ($pv === 'true') {
155+
$params->stale = true;
156+
} else if ($pv === 'false') {
157+
$params->stale = false;
158+
} else {
159+
throw new InvalidHeaderParameterException('Invalid Auth header, non-boolean stale parameter', Response::BAD_REQUEST);
160+
}
161+
break 2;
162+
163+
case 'qop':
164+
$params->qop = explode(',', $pv);
165+
break 2;
166+
}
167+
} else {
168+
/** @var ResponseParams $params */
169+
switch ($pk) {
170+
case 'username':
171+
$params->username = $pv;
172+
break 2;
173+
174+
case 'uri':
175+
$params->uri = $pv;
176+
break 2;
177+
178+
case 'cnonce':
179+
$params->cnonce = $pv;
180+
break 2;
181+
182+
case 'nc':
183+
if (!ctype_xdigit($pv)) {
184+
throw new InvalidHeaderParameterException('Invalid Auth header, non-hexadecimal nc parameter', Response::BAD_REQUEST);
185+
}
186+
187+
$params->nc = $pv;
188+
break 2;
189+
190+
case 'response':
191+
if (!ctype_xdigit($pv)) {
192+
throw new InvalidHeaderParameterException('Invalid Auth header, non-hexadecimal response parameter', Response::BAD_REQUEST);
193+
}
194+
195+
$params->response = $pv;
196+
break 2;
197+
198+
case 'qop':
199+
$params->qop = $pv;
200+
break 2;
201+
}
202+
}
203+
204+
$params->extra[$pk] = $pv;
205+
break;
206+
}
207+
}
208+
209+
return $params;
210+
}
211+
212+
/**
213+
* Renders digest authentication scheme parameters as string
214+
*
215+
* @return string Digest authentication parameters
216+
*/
217+
public function render(): string
218+
{
219+
$params = [];
220+
221+
if (isset($this->realm)) {
222+
$params[] = "realm=\"{$this->realm}\"";
223+
}
224+
225+
if (isset($this->algorithm)) {
226+
$params[] = "algorithm={$this->algorithm}";
227+
}
228+
229+
if (isset($this->nonce)) {
230+
$params[] = "nonce=\"{$this->nonce}\"";
231+
}
232+
233+
if (isset($this->opaque)) {
234+
$params[] = "opaque=\"{$this->opaque}\"";
235+
}
236+
237+
if (static::class === ChallengeParams::class) {
238+
if (isset($this->domain)) {
239+
$params[] = "domain=\"{$this->domain}\"";
240+
}
241+
242+
if (isset($this->stale)) {
243+
$params[] = 'stale=' . ($this->stale ? 'TRUE' : 'FALSE');
244+
}
245+
246+
if (isset($this->qop) && count($this->qop)) {
247+
$params[] = 'qop="' . implode(',', $this->qop) . '"';
248+
}
249+
} else {
250+
if (isset($this->username)) {
251+
$params[] = "username=\"{$this->username}\"";
252+
}
253+
254+
if (isset($this->uri)) {
255+
$params[] = "uri=\"{$this->uri}\"";
256+
}
257+
258+
if (isset($this->response)) {
259+
$params[] = "response=\"{$this->response}\"";
260+
}
261+
262+
if (isset($this->cnonce)) {
263+
$params[] = "cnonce=\"{$this->cnonce}\"";
264+
}
265+
266+
if (isset($this->qop)) {
267+
$params[] = "qop={$this->qop}";
268+
}
269+
270+
if (isset($this->nc)) {
271+
$params[] = "nc={$this->nc}";
272+
}
273+
}
274+
275+
foreach ($this->extra as $pk => $pv) {
276+
$params[] = "{$pk}={$pv}";
277+
}
278+
279+
return implode(',', $params);
280+
}
281+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/**
3+
* RTCKit\SIP\Auth\Digest\ChallengeParams Class
4+
*/
5+
declare(strict_types = 1);
6+
7+
namespace RTCKit\SIP\Auth\Digest;
8+
9+
/**
10+
* Digest challenge parameters class
11+
*/
12+
class ChallengeParams extends AbstractParams
13+
{
14+
/** @var string SIP domain */
15+
public string $domain;
16+
17+
/** @var bool Stale response flag */
18+
public bool $stale;
19+
20+
/** @var array<string> Quality of protection options */
21+
public array $qop = [];
22+
}

0 commit comments

Comments
 (0)