Skip to content

Commit

Permalink
Merge pull request #13 from rtckit/v0.4.0
Browse files Browse the repository at this point in the history
v0.4.0
  • Loading branch information
cdosoftei authored Sep 28, 2021
2 parents 637beb1 + 4eff339 commit 24affc9
Show file tree
Hide file tree
Showing 9 changed files with 673 additions and 25 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "rtckit/sip",
"description": "Parser/Renderer for SIP protocol written in PHP",
"version": "0.3.3",
"version": "0.4.0",
"type": "library",
"keywords": [
"sip",
Expand Down
273 changes: 273 additions & 0 deletions src/Header/AuthHeader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
<?php
/**
* RTCKit\SIP\AuthHeader Class
*/
declare(strict_types = 1);

namespace RTCKit\SIP\Header;

use RTCKit\SIP\Response;
use RTCKit\SIP\Exception\InvalidHeaderLineException;
use RTCKit\SIP\Exception\InvalidHeaderParameter;
use RTCKit\SIP\Exception\InvalidHeaderValue;

/**
* Authentication/Authorization Header Class
*/
class AuthHeader
{
public const DIGEST_SCHEME = 'digest';

/** @var list<AuthValue> Authentication/Authorization value(s) */
public array $values = [];

final public function __construct() {}

/**
* Authentication/Authorization field value parser
*
* @param list<string> $hbody Header body
* @throws InvalidHeaderLineException
* @throws InvalidHeaderParameter
* @return AuthHeader
*/
public static function parse(array $hbody): AuthHeader
{
$ret = new static;

foreach ($hbody as $hline) {
$hparts = explode(' ', trim($hline), 2);

if (count($hparts) !== 2) {
throw new InvalidHeaderLineException('Invalid Auth header, missing scheme', Response::BAD_REQUEST);
}

$val = new AuthValue;
$val->scheme = $hparts[0];

$params = ltrim($hparts[1]);

if (strtolower($val->scheme) === self::DIGEST_SCHEME) {
while (strlen($params)) {
$pos = strpos($params, '=');

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

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

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

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

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

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

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

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

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

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

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

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

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

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

switch ($pk) {
case 'username':
$val->username = $pv;
break;

case 'realm':
$val->realm = $pv;
break;

case 'domain':
$val->domain = $pv;
break;

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

case 'uri':
$val->uri = $pv;
break;

case 'response':
$val->response = $pv;
break;

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

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

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

case 'cnonce':
$val->cnonce = $pv;
break;

case 'qop':
$val->qop = $pv;
break;

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

$val->nc = (int)$pv;
break;

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

default:
$val->params[$pk] = $pv;
break;
}
}
} else {
$val->credentials = $params;
}

$ret->values[] = $val;
}

return $ret;
}

/**
* Authentication/Authorization header field value renderer
*
* @param string $hname Header field name
* @throws InvalidHeaderValue
* @return string
*/
public function render(string $hname): string
{
$ret = '';

foreach ($this->values as $key => $value) {
if (!isset($value->scheme)) {
throw new InvalidHeaderValue('Malformed auth header, missing scheme');
}

$params = [];

if (strtolower($value->scheme) === self::DIGEST_SCHEME) {
if (isset($value->username)) {
$params[] = "username=\"{$value->username}\"";
}

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

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

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

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

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

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

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

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

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

if (isset($value->nc)) {
$params[] = sprintf('ns=%08d', $value->nc);
}

if (isset($value->opaque)) {
$params[] = "opaque=\"{$value->opaque}\"";
}
} else {
if (!isset($value->credentials)) {
throw new InvalidHeaderValue('Malformed auth header, missing credentials');
}

$params[] = $value->credentials;
}

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

$paramStr = implode(',', $params);

$ret .= "{$hname}: {$value->scheme} {$paramStr}\r\n";
}

return $ret;
}
}
58 changes: 58 additions & 0 deletions src/Header/AuthValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/**
* RTCKit\SIP\AuthValue Class
*/
declare(strict_types = 1);

namespace RTCKit\SIP\Header;

/**
* Authentication/Authorization Header Field Value Class
*/
class AuthValue
{
/** @var string Authentication scheme */
public string $scheme;

/** @var string Authentication user name */
public string $username;

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

/** @var string SIP domain */
public string $domain;

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

/** @var string SIP URI */
public string $uri;

/** @var string Response hash */
public string $response;

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

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

/** @var string Client's number once */
public string $cnonce;

/** @var string Quality of protection */
public string $qop;

/** @var int Number once count */
public int $nc;

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

/** @var string Generic credentials when attributes aren't used (e.g. Basic) */
public string $credentials;

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

0 comments on commit 24affc9

Please sign in to comment.