Skip to content

Commit

Permalink
Merge pull request #18 from rtckit/v0.7.0
Browse files Browse the repository at this point in the history
v0.7.0
  • Loading branch information
cdosoftei authored Oct 18, 2021
2 parents eb8f2aa + c26ca55 commit f7bb13b
Show file tree
Hide file tree
Showing 55 changed files with 1,392 additions and 805 deletions.
17 changes: 12 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
{
"name": "rtckit/sip",
"description": "Parser/Renderer for SIP protocol written in PHP",
"version": "0.6.1",
"description": "SIP protocol implementation written in PHP",
"version": "0.7.0",
"type": "library",
"keywords": [
"sip",
"session initiation protocol",
"voip"
"voip",
"rfc 3261",
"telephony",
"telco"
],
"homepage": "https://github.com/rtckit/php-sip",
"license": "MIT",
Expand All @@ -20,13 +23,17 @@
"issues": "https://github.com/rtckit/php-sip/issues"
},
"require": {
"php": ">=7.4.0"
"php": ">=7.4.0",
"ext-ctype": "*"
},
"require-dev": {
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^9.5",
"symfony/yaml": "^5.3",
"vimeo/psalm": "^4.8"
"vimeo/psalm": "^4.10"
},
"suggest": {
"ext-hash": "Enables RFC 8760 authentication via SHA(-512)-256 hashing"
},
"autoload": {
"psr-4": {
Expand Down
281 changes: 281 additions & 0 deletions src/Auth/Digest/AbstractParams.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
<?php
/**
* RTCKit\SIP\Auth\Digest\AbstractParams Class
*/
declare(strict_types = 1);

namespace RTCKit\SIP\Auth\Digest;

use RTCKit\SIP\Response;
use RTCKit\SIP\Auth\ParamsInterface;
use RTCKit\SIP\Exception\InvalidHeaderLineException;
use RTCKit\SIP\Exception\InvalidHeaderParameterException;

/**
* Digest authentication scheme parameters abstract class
*/
abstract class AbstractParams implements ParamsInterface
{
/** @var string Lowercase scheme name */
public const SCHEME_NAME = 'digest';

/** @var string Scheme name using preferred/common case */
public const PREFERRED_CASE = 'Digest';

/** @var string "auth" Quality of protection */
public const QOP_AUTH = 'auth';

/** @var string "auth-int" Quality of protection */
public const QOP_AUTH_INT = 'auth-int';

/** @var string Default hashing algorithm */
public const DEFAULT_ALGORITHM = 'MD5';

/** @var string Authentication realm */
public string $realm;

/** @var string Digest algorithm */
public string $algorithm;

/** @var string Server's number once */
public string $nonce;

/** @var string Server's opaque data blob */
public string $opaque;

/** @var array<string,string> Additional parameters */
public array $extra = [];

final public function __construct() {}

/**
* Parses digest parameters out of a string input
*
* @param string $input Unparsed digest parameters
* @throws InvalidHeaderLineException
* @throws InvalidHeaderParameterException
* @return ParamsInterface Parsed Challenge or Response parameters
*/
public static function parse(string $input): ParamsInterface
{
$orig = $input;
$params = new static;
$challenge = static::class === ChallengeParams::class;

while (strlen($input)) {
$pos = strpos($input, '=');

if ($pos === false) {
throw new InvalidHeaderLineException('Invalid Auth header, valueless parameter', Response::BAD_REQUEST);
}

$pk = rtrim(substr($input, 0, $pos));
$pv = '';
$input = ltrim(substr($input, $pos + 1));

if (!isset($input[0])) {
throw new InvalidHeaderLineException('Invalid Auth header, valueless parameter', Response::BAD_REQUEST);
}

if ($input[0] === '"') {
$offset = 1;
$escQuotes = false;

while (true) {
$pos = strpos($input, '"', $offset);

if ($pos === false) {
throw new InvalidHeaderLineException('Invalid Auth header, unmatched parameter value enclosing', Response::BAD_REQUEST);
}

if ($input[$pos - 1] !== '\\') {
break;
}

$escQuotes = true;
$offset = $pos + 1;
}

$pv = substr($input, 1, $pos - 1);

if ($escQuotes) {
$pv = str_replace('\"', '"', $pv);
}

$input = ltrim(substr($input, $pos + 1));

if (isset($input[0])) {
if ($input[0] !== ',') {
throw new InvalidHeaderLineException('Invalid Auth header, invalid parameter value enclosing', Response::BAD_REQUEST);
}

$input = ltrim(substr($input, 1));
}
} else {
$pos = strpos($input, ',');

if ($pos !== false) {
$pv = rtrim(substr($input, 0, $pos));
$input = ltrim(substr($input, $pos + 1));
} else {
$pv = rtrim($input);
$input = '';
}
}

switch ($pk) {
case 'realm':
$params->realm = $pv;
break;

case 'algorithm':
$params->algorithm = $pv;
break;

case 'nonce':
$params->nonce = $pv;
break;

case 'opaque':
$params->opaque = $pv;
break;

default:
if ($challenge) {
/** @var ChallengeParams $params */
switch ($pk) {
case 'domain':
$params->domain = $pv;
break 2;

case 'stale':
$pv = strtolower($pv);

if ($pv === 'true') {
$params->stale = true;
} else if ($pv === 'false') {
$params->stale = false;
} else {
throw new InvalidHeaderParameterException('Invalid Auth header, non-boolean stale parameter', Response::BAD_REQUEST);
}
break 2;

case 'qop':
$params->qop = explode(',', $pv);
break 2;
}
} else {
/** @var ResponseParams $params */
switch ($pk) {
case 'username':
$params->username = $pv;
break 2;

case 'uri':
$params->uri = $pv;
break 2;

case 'cnonce':
$params->cnonce = $pv;
break 2;

case 'nc':
if (!ctype_xdigit($pv)) {
throw new InvalidHeaderParameterException('Invalid Auth header, non-hexadecimal nc parameter', Response::BAD_REQUEST);
}

$params->nc = $pv;
break 2;

case 'response':
if (!ctype_xdigit($pv)) {
throw new InvalidHeaderParameterException('Invalid Auth header, non-hexadecimal response parameter', Response::BAD_REQUEST);
}

$params->response = $pv;
break 2;

case 'qop':
$params->qop = $pv;
break 2;
}
}

$params->extra[$pk] = $pv;
break;
}
}

return $params;
}

/**
* Renders digest authentication scheme parameters as string
*
* @return string Digest authentication parameters
*/
public function render(): string
{
$params = [];

if (isset($this->realm)) {
$params[] = "realm=\"{$this->realm}\"";
}

if (isset($this->algorithm)) {
$params[] = "algorithm={$this->algorithm}";
}

if (isset($this->nonce)) {
$params[] = "nonce=\"{$this->nonce}\"";
}

if (isset($this->opaque)) {
$params[] = "opaque=\"{$this->opaque}\"";
}

if (static::class === ChallengeParams::class) {
if (isset($this->domain)) {
$params[] = "domain=\"{$this->domain}\"";
}

if (isset($this->stale)) {
$params[] = 'stale=' . ($this->stale ? 'TRUE' : 'FALSE');
}

if (isset($this->qop) && count($this->qop)) {
$params[] = 'qop="' . implode(',', $this->qop) . '"';
}
} else {
if (isset($this->username)) {
$params[] = "username=\"{$this->username}\"";
}

if (isset($this->uri)) {
$params[] = "uri=\"{$this->uri}\"";
}

if (isset($this->response)) {
$params[] = "response=\"{$this->response}\"";
}

if (isset($this->cnonce)) {
$params[] = "cnonce=\"{$this->cnonce}\"";
}

if (isset($this->qop)) {
$params[] = "qop={$this->qop}";
}

if (isset($this->nc)) {
$params[] = "nc={$this->nc}";
}
}

foreach ($this->extra as $pk => $pv) {
$params[] = "{$pk}={$pv}";
}

return implode(',', $params);
}
}
22 changes: 22 additions & 0 deletions src/Auth/Digest/ChallengeParams.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
/**
* RTCKit\SIP\Auth\Digest\ChallengeParams Class
*/
declare(strict_types = 1);

namespace RTCKit\SIP\Auth\Digest;

/**
* Digest challenge parameters class
*/
class ChallengeParams extends AbstractParams
{
/** @var string SIP domain */
public string $domain;

/** @var bool Stale response flag */
public bool $stale;

/** @var array<string> Quality of protection options */
public array $qop = [];
}
Loading

0 comments on commit f7bb13b

Please sign in to comment.