diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..bc3ab5c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..a7e7421 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,53 @@ +name: CI + +on: + push: + pull_request: + +jobs: + StaticAnalysis: + name: StaticAnalysis (PHP ${{ matrix.php }} on ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-20.04 + php: + - 7.4 + - 8.0 + - 8.1 + - 8.2 + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + - run: composer install + - run: composer phpstan + - run: composer psalm -- --output-format=github --shepherd + + Coverage: + name: Coverage (PHP ${{ matrix.php }} on ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-20.04 + php: + - 7.4 + - 8.0 + - 8.1 + - 8.2 + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + - run: composer install + - uses: paambaati/codeclimate-action@v4.0.0 + env: + CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}} + with: + coverageCommand: php -d memory_limit=-1 ./vendor/bin/phpunit -c ./etc/phpunit.xml.dist --coverage-clover clover.xml + coverageLocations: clover.xml:clover + debug: true diff --git a/Makefile b/Makefile deleted file mode 100644 index e6d8e17..0000000 --- a/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -# Not much to see here ... just a bunch of developer helpers -REPOSITORY=rtckit/php-sip -RUN_CMD=docker run --name php-sip --rm -it -v `pwd`/reports:/opt/php-sip/reports:rw ${REPOSITORY} -RUN_PHP_CMD=${RUN_CMD} php -d memory_limit=-1 - -image: - docker build -t ${REPOSITORY} -f ./etc/Dockerfile . - -debug-image: - docker build -t ${REPOSITORY}-debug -f ./etc/Dockerfile.xdebug . - -test: image - ${RUN_PHP_CMD} -d memory_limit=-1 ./vendor/bin/phpunit -c ./etc/phpunit.xml.dist --debug - -cover: image - sudo rm -rf reports/coverage - ${RUN_PHP_CMD} ./vendor/bin/phpunit -c ./etc/phpunit.xml.dist --coverage-text --coverage-html=reports/coverage - -stan: image - ${RUN_PHP_CMD} ./vendor/bin/phpstan analyse -c ./etc/phpstan.neon -n -vvv --ansi --level=max src - -psalm: image - ${RUN_PHP_CMD} ./vendor/bin/psalm --config=./etc/psalm.xml --show-info=true - # ${RUN_PHP_CMD} ./vendor/bin/psalm --config=./etc/psalm.xml --php-version=8.0 - -ci: stan psalm cover - -examples: image - ${RUN_PHP_CMD} ./examples/01-parse-request.php - ${RUN_PHP_CMD} ./examples/02-render-request.php - ${RUN_PHP_CMD} ./examples/03-parse-response.php - ${RUN_PHP_CMD} ./examples/04-render-response.php - ${RUN_PHP_CMD} ./examples/05-stream-parse.php - ${RUN_PHP_CMD} ./examples/99-crude-benchmark.php - -profiler: debug-image - ${RUN_CMD}-debug php -d memory_limit=-1 ./examples/99-crude-benchmark.php - -clean: - rm -rf `cat .gitignore` diff --git a/README.md b/README.md index 78257ca..2731b4e 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,10 @@ [RFC 3261](https://tools.ietf.org/html/rfc3261) compliant SIP parsing and rendering library for PHP 7.4. -[![Build Status](https://travis-ci.com/rtckit/php-sip.svg?branch=main)](https://travis-ci.com/rtckit/php-sip) +[![CI Status](https://github.com/rtckit/php-sip/workflows/CI/badge.svg)](https://github.com/rtckit/php-sip/actions/workflows/ci.yaml) +[![Psalm Type Coverage](https://shepherd.dev/github/rtckit/php-sip/coverage.svg)](https://shepherd.dev/github/rtckit/php-sip) [![Latest Stable Version](https://poser.pugx.org/rtckit/sip/v/stable.png)](https://packagist.org/packages/rtckit/sip) +[![Installs on Packagist](https://img.shields.io/packagist/dt/rtckit/sip?color=blue&label=Installs%20on%20Packagist)](https://packagist.org/packages/rtckit/sip) [![Test Coverage](https://api.codeclimate.com/v1/badges/aff5ee8e8ef3b51689c2/test_coverage)](https://codeclimate.com/github/rtckit/php-sip/test_coverage) [![Maintainability](https://api.codeclimate.com/v1/badges/aff5ee8e8ef3b51689c2/maintainability)](https://codeclimate.com/github/rtckit/php-sip/maintainability) [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE) @@ -193,7 +195,7 @@ In order to ensure high code quality, **RTCKit\SIP** uses [PHPStan](https://gith ```sh php -d memory_limit=-1 ./vendor/bin/phpstan analyse -c ./etc/phpstan.neon -n -vvv --ansi --level=max src -php -d memory_limit=-1 ./vendor/bin/psalm --config=./etc/psalm.xml --show-info=true +php -d memory_limit=-1 ./vendor/bin/psalm --config=./etc/psalm.xml ``` ## License diff --git a/composer.json b/composer.json index 01d2de1..c66e73b 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "rtckit/sip", "description": "SIP protocol implementation written in PHP", - "version": "0.7.0", + "version": "0.7.1", "type": "library", "keywords": [ "sip", @@ -27,10 +27,10 @@ "ext-ctype": "*" }, "require-dev": { - "phpstan/phpstan": "^0.12", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5", "symfony/yaml": "^5.3", - "vimeo/psalm": "^4.10" + "vimeo/psalm": "^5.11" }, "suggest": { "ext-hash": "Enables RFC 8760 authentication via SHA(-512)-256 hashing" @@ -44,5 +44,18 @@ "psr-4": { "RTCKit\\SIP\\": "tests/" } + }, + "config": { + "allow-plugins": false, + "platform": { + "php": "7.4" + } + }, + "scripts": { + "phpstan": "php -d memory_limit=-1 ./vendor/bin/phpstan analyse -c ./etc/phpstan.neon -n -vvv --ansi --level=max src", + "psalm": "php -d memory_limit=-1 ./vendor/bin/psalm --config=./etc/psalm.xml", + "test": "php -d memory_limit=-1 ./vendor/bin/phpunit -c ./etc/phpunit.xml.dist --debug", + "coverage": "php -d extension=pcov -d memory_limit=-1 ./vendor/bin/phpunit -c ./etc/phpunit.xml.dist --coverage-text --coverage-html=reports/coverage", + "profile": "php -d memory_limit=-1 ./examples/99-crude-benchmark.php" } } diff --git a/etc/Dockerfile b/etc/Dockerfile deleted file mode 100644 index e583a33..0000000 --- a/etc/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -FROM composer:2.1.3 as composer - -WORKDIR /opt/php-sip - -COPY composer.* /opt/php-sip/ - -RUN composer install --no-scripts --no-suggest --no-interaction --prefer-dist --optimize-autoloader - -COPY . /opt/php-sip - -RUN composer dump-autoload --optimize --classmap-authoritative - -FROM php:7-cli-alpine - -# Build and install pcov -ARG PHP_PCOV_RELEASE=3546be8 -RUN cd /tmp && \ - curl https://codeload.github.com/krakjoe/pcov/tar.gz/$PHP_PCOV_RELEASE | tar xvz && \ - cd /tmp/pcov-$PHP_PCOV_RELEASE && \ - apk --no-cache add $PHPIZE_DEPS && \ - phpize && \ - ./configure && \ - make && \ - make install && \ - echo "extension=pcov.so" > /usr/local/etc/php/conf.d/pcov.ini -# Remove build dependencies -RUN apk --purge del $PHPIZE_DEPS && \ - rm -rf /tmp/* - -WORKDIR /opt/php-sip - -COPY . /opt/php-sip - -COPY --from=composer /opt/php-sip/vendor /opt/php-sip/vendor - -CMD ["php", "-a"] diff --git a/etc/Dockerfile.xdebug b/etc/Dockerfile.xdebug deleted file mode 100644 index 0d62748..0000000 --- a/etc/Dockerfile.xdebug +++ /dev/null @@ -1,29 +0,0 @@ -FROM composer:2.1.3 as composer - -WORKDIR /opt/php-sip - -COPY composer.* /opt/php-sip/ - -RUN composer install --no-scripts --no-suggest --no-interaction --prefer-dist --optimize-autoloader - -COPY . /opt/php-sip - -RUN composer dump-autoload --optimize --classmap-authoritative - -FROM php:7-cli-alpine - -ARG XDEBUG_RELEASE=2.9.8 -RUN apk add --no-cache $PHPIZE_DEPS && \ - pecl install xdebug-$XDEBUG_RELEASE && \ - docker-php-ext-enable xdebug && \ - echo 'xdebug.profiler_enable=1' > /usr/local/etc/php/conf.d/xdebug.ini && \ - echo 'xdebug.profiler_output_dir=/opt/php-sip/reports' >> /usr/local/etc/php/conf.d/xdebug.ini && \ - echo 'xdebug.trace_output_name=cachegrind.out.%t' >> /usr/local/etc/php/conf.d/xdebug.ini - -WORKDIR /opt/php-sip - -COPY . /opt/php-sip - -COPY --from=composer /opt/php-sip/vendor /opt/php-sip/vendor - -CMD ["php", "-a"] diff --git a/etc/psalm.xml b/etc/psalm.xml index 1a6b6aa..f7b2a17 100644 --- a/etc/psalm.xml +++ b/etc/psalm.xml @@ -1,9 +1,10 @@ diff --git a/src/Auth/Digest/AbstractParams.php b/src/Auth/Digest/AbstractParams.php index 022b90b..8b3ac18 100644 --- a/src/Auth/Digest/AbstractParams.php +++ b/src/Auth/Digest/AbstractParams.php @@ -142,7 +142,8 @@ public static function parse(string $input): ParamsInterface default: if ($challenge) { - /** @var ChallengeParams $params */ + assert($params instanceof ChallengeParams); + switch ($pk) { case 'domain': $params->domain = $pv; @@ -165,7 +166,8 @@ public static function parse(string $input): ParamsInterface break 2; } } else { - /** @var ResponseParams $params */ + assert($params instanceof ResponseParams); + switch ($pk) { case 'username': $params->username = $pv; diff --git a/src/Header/ContactHeader.php b/src/Header/ContactHeader.php index ba9b018..e944c95 100644 --- a/src/Header/ContactHeader.php +++ b/src/Header/ContactHeader.php @@ -37,6 +37,31 @@ public static function parse(array $hbody): ContactHeader { $ret = new static; + $input = $hbody; + $hbody = []; + + foreach ($input as $hline) { + $segments = explode(',', $hline); + $buffer = $comma = ''; + + foreach ($segments as $segment) { + $buffer .= $comma . $segment; + + if (!(substr_count($segment, '"') % 2) && (substr_count($segment, '<') === substr_count($segment, '>'))) { + $hbody[] = $buffer; + $buffer = $comma = ''; + + continue; + } + + $comma = ','; + } + + if (isset($buffer[0])) { + $hbody[] = $buffer; + } + } + foreach ($hbody as $hline) { $val = new ContactValue; @@ -47,7 +72,7 @@ public static function parse(array $hbody): ContactHeader $qfrom = null; $afrom = null; $base = 0; - $addr = null; + $addr = false; for ($i = 0; $i <= $len; $i++) { if (!$quoted) { @@ -174,7 +199,7 @@ public static function parse(array $hbody): ContactHeader } } - if (!is_null($addr)) { + if (is_string($addr)) { $val->uri = URI::parse($addr); $ret->values[] = $val; $addr = null; @@ -200,7 +225,7 @@ public static function parse(array $hbody): ContactHeader } } - if (!is_null($addr)) { + if (is_string($addr)) { $val->uri = URI::parse($addr); $ret->values[] = $val; } diff --git a/src/Header/NameAddrHeader.php b/src/Header/NameAddrHeader.php index 1b63975..4e1f01f 100644 --- a/src/Header/NameAddrHeader.php +++ b/src/Header/NameAddrHeader.php @@ -175,7 +175,7 @@ public static function parse(array $hbody): NameAddrHeader } } - if (is_null($addr)) { + if (!is_string($addr)) { throw new InvalidHeaderLineException('Invalid name-addr line, missing addr-spec', Response::BAD_REQUEST); } diff --git a/src/Message.php b/src/Message.php index c973fd9..64fb28b 100644 --- a/src/Message.php +++ b/src/Message.php @@ -174,7 +174,7 @@ public static function parse(string $text, bool $ignoreBody = false): Message $hvalue .= $lines[$i]; } else { - if (isset($hname, $hvalue)) { + if (isset($hname, $hvalue) && strlen($hvalue)) { $headers[$hname][] = $hvalue; } @@ -199,7 +199,7 @@ public static function parse(string $text, bool $ignoreBody = false): Message } } - if (isset($hname, $hvalue[0])) { + if (isset($hname, $hvalue) && strlen($hvalue)) { $headers[$hname][] = $hvalue; } diff --git a/src/Response.php b/src/Response.php index 0cee474..2756aef 100644 --- a/src/Response.php +++ b/src/Response.php @@ -373,7 +373,7 @@ public function __construct(?string $startLine = null) $sttsLine = explode(' ', $startLine, 3); - if (!isset($sttsLine[2])) { + if (count($sttsLine) !== 3) { throw new InvalidMessageStartLineException('Malformed Status-Line: ' . $startLine, self::BAD_REQUEST); } diff --git a/src/StreamParser.php b/src/StreamParser.php index 4fe2c7b..2d16f6c 100644 --- a/src/StreamParser.php +++ b/src/StreamParser.php @@ -59,6 +59,8 @@ public function process(string $chunk, ?array &$messages): int break; } + assert(count($blocks) === 2); + $this->message = Message::parse($this->buffer, true); $this->buffer = $blocks[1]; } else { @@ -91,7 +93,7 @@ public function process(string $chunk, ?array &$messages): int unset($this->message); $status = self::SUCCESS; - } while ($status === self::SUCCESS); + } while (true); return (isset($messages[0])) ? self::SUCCESS diff --git a/tests/RFC4475/S31101Test.php b/tests/RFC4475/S31101Test.php index 33e03c2..5d57134 100644 --- a/tests/RFC4475/S31101Test.php +++ b/tests/RFC4475/S31101Test.php @@ -33,7 +33,7 @@ public function testShouldParseProperly() $this->assertEquals('Quoted string \\"\\"', $msg->contact->values[0]->name); /* an empty subject */ - $this->assertEquals('', $msg->subject->values[0]); + $this->assertCount(0, $msg->subject->values); /* both comma separated and separately listed header field values */ /* a mix of short and long form for the same header field name */ diff --git a/tests/fixtures/misc/gh-21.yaml b/tests/fixtures/misc/gh-21.yaml new file mode 100644 index 0000000..139ebb5 --- /dev/null +++ b/tests/fixtures/misc/gh-21.yaml @@ -0,0 +1,13 @@ +# https://github.com/rtckit/php-sip/issues/21 +- |+ + SIP/2.0 200 OK + Via: SIP/2.0/TCP 192.0.2.15;branch=z9hG4bKnuiqisi + From: Bob ;tag=7F94778B653B + To: Bob ;tag=6AF99445E44A + Call-ID: 16CB75F21C70 + CSeq: 1 REGISTER + Supported: path, outbound + Require: outbound + Contact: ;expires=111;received="sip:e52@78.102.16.243:55497";reg-id=1;+sip.instance="",;expires=258;received="sip:e52@78.102.16.243:17127";reg-id=2;+sip.instance="<65be01c100f2ccd762df61057569f037bd46a367>" + Path: + Content-Length: 0