diff --git a/.appveyor.yml b/.appveyor.yml index 52639fc..9ac6bd8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,43 +1,147 @@ -version: '{branch}.{build}' +# See https://github.com/sergeyklay/php-appveyor branches: only: - - master - -image: - - Visual Studio 2015 + - master + - appveyor + - w32 + - /^v\d+\.\d+\.\d+$/ environment: - PHP_SDK_BINARY_TOOLS_URL: https://windows.php.net/downloads/php-sdk - PHP_SDK_BINARY_TOOLS_PACKAGE: php-sdk-binary-tools-20110915.zip - PHP_DEPS_URL: https://windows.php.net/downloads/php-sdk + EXTNAME: http_message matrix: - - PHP_REL: 7.0 - ARCHITECTURE: x64 - ARCH: amd64 - ZTS_STATE: disable + - PHP_VERSION: 7.2 + BUILD_TYPE: Win32 + VC_VERSION: vc15 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + + - PHP_VERSION: 7.2 + BUILD_TYPE: nts-Win32 + VC_VERSION: vc15 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + + - PHP_VERSION: 7.3 + BUILD_TYPE: Win32 + VC_VERSION: vc15 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + + - PHP_VERSION: 7.3 + BUILD_TYPE: nts-Win32 + VC_VERSION: vc15 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + + - PHP_VERSION: 7.4 + BUILD_TYPE: Win32 + VC_VERSION: vc15 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - PHP_BUILD_CRT: vc14 - PHP_DEPS_PACKAGE: deps-7.0-vc14-x64.7z -# Uncomment the following two lines to use RDP to debug "stuck" builds -#init: -# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) - + - PHP_VERSION: 7.4 + BUILD_TYPE: nts-Win32 + VC_VERSION: vc15 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + + PHP_SDK_VERSION: 2.2.0 + PHP_AVM: https://raw.githubusercontent.com/sergeyklay/php-appveyor/master/php-appveyor.psm1 + + TEST_PHP_EXECUTABLE: C:\php\php.exe + NO_INTERACTION: 1 + REPORT_EXIT_STATUS: 1 + +matrix: + fast_finish: true + +cache: + - 'C:\Downloads -> .appveyor.yml' + +platform: + - x86 + - x64 + +init: + - ps: $DebugPreference = 'SilentlyContinue' # Continue + - ps: >- + if ($env:APPVEYOR_REPO_TAG -eq "true") { + Update-AppveyorBuild -Version "$($Env:APPVEYOR_REPO_TAG_NAME.TrimStart("v"))" + } else { + Update-AppveyorBuild -Version "${Env:APPVEYOR_REPO_BRANCH}-$($Env:APPVEYOR_REPO_COMMIT.Substring(0, 7))" + } + install: - - cmd: cinst wget - - cmd: >- - "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %ARCH% + - ps: Import-Module .\.ci\php-appveyor.psm1 + + - ps: InstallPhpSdk $Env:PHP_SDK_VERSION $Env:VC_VERSION $Env:PLATFORM + - ps: InstallPhp $Env:PHP_VERSION $Env:BUILD_TYPE $Env:VC_VERSION $Env:PLATFORM + - ps: InstallPhpDevPack $Env:PHP_VERSION $Env:BUILD_TYPE $Env:VC_VERSION $Env:PLATFORM + + - ps: >- + InstallPeclExtension ` + -Name psr ` + -Version 1.0.1 ` + -PhpVersion $Env:PHP_VERSION ` + -BuildType $Env:BUILD_TYPE ` + -VC $Env:VC_VERSION ` + -Platform $Env:PLATFORM ` + -Headers $true ` + -Enable $true build_script: - - .appveyor\build.cmd + - ps: Import-Module .\.ci\appveyor.psm1 + - ps: InitializeBuildVars + - cmd: '"%VSDEVCMD%" -arch=%PLATFORM%' + - cmd: '"%VCVARSALL%" %ARCH%' + - cmd: C:\php-sdk\bin\phpsdk_setvars + - cmd: C:\php-devpack\phpize + - cmd: configure.bat --with-prefix=C:\php --with-php-build=C:\php-devpack --disable-all --enable-http-message + - cmd: nmake 2> compile-errors.log 1> compile.log + - ps: InitializeReleaseVars test_script: - - .appveyor\test.cmd + - cmd: nmake test + +after_build: + - ps: Set-Location "${Env:APPVEYOR_BUILD_FOLDER}" + - ps: >- + PrepareReleasePackage ` + -PhpVersion $Env:PHP_VERSION ` + -BuildType $Env:BUILD_TYPE ` + -Platform $Env:PLATFORM ` + -ConverMdToHtml $true ` + -ReleaseFiles "${Env:RELEASE_FOLDER}\php_${Env:EXTNAME}.dll",` + "${Env:APPVEYOR_BUILD_FOLDER}\CREDITS",` + "${Env:APPVEYOR_BUILD_FOLDER}\LICENSE" + +on_failure : + - ps: >- + If (Test-Path -Path "${Env:APPVEYOR_BUILD_FOLDER}\compile-errors.log") { + Get-Content -Path "${Env:APPVEYOR_BUILD_FOLDER}\compile-errors.log" + } + + If (Test-Path -Path "${Env:APPVEYOR_BUILD_FOLDER}\compile.log") { + Get-Content -Path "${Env:APPVEYOR_BUILD_FOLDER}\compile.log" + } + + Get-ChildItem "${Env:APPVEYOR_BUILD_FOLDER}\tests" -Recurse -Filter *.diff | Foreach-Object { + [Environment]::NewLine + Write-Output $_.FullName + Get-Content -Path $_.FullName + } -# Uncomment the following two lines to use RDP on finish, blocking -# until the "lock" file on the VM desktop is deleted. -#on_finish: -# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) +artifacts: + - path: '.\$(RELEASE_ZIPBALL).zip' + name: '$(EXTNAME)' + type: zip +deploy: + release: v$(appveyor_build_version) + description: 'v$(appveyor_build_version)' + provider: GitHub + auth_token: + secure: 9kSFTreufYy0WOpd+eYAFa+2IjSxe1idbVAzehcTS/womadyGxVG6qTKpJnXDmTh + artifact: '$(RELEASE_ZIPBALL).zip' + draft: false + prerelease: false + force_update: true + on: + branch: master + APPVEYOR_REPO_TAG: true diff --git a/.appveyor/build.cmd b/.appveyor/build.cmd deleted file mode 100644 index 5292e5d..0000000 --- a/.appveyor/build.cmd +++ /dev/null @@ -1,51 +0,0 @@ -@echo off - -setlocal enableextensions enabledelayedexpansion - - REM TODO: build external libraries - - REM set up PHP - mkdir C:\projects\php-sdk >NUL 2>NUL - cd C:\projects\php-sdk - wget %PHP_SDK_BINARY_TOOLS_URL%/%PHP_SDK_BINARY_TOOLS_PACKAGE% --no-check-certificate -q -O %PHP_SDK_BINARY_TOOLS_PACKAGE% - 7z x -y %PHP_SDK_BINARY_TOOLS_PACKAGE% - cmd /c bin\phpsdk_buildtree.bat phpdev - pushd phpdev - ren vc9 vc14 - pushd vc14\x64 - git clone https://git.php.net/repository/php-src.git - cd php-src - git checkout PHP-%PHP_REL% - cd .. - wget %PHP_DEPS_URL%/%PHP_DEPS_PACKAGE% --no-check-certificate -q -O %PHP_DEPS_PACKAGE% - 7z x -y %PHP_DEPS_PACKAGE% - popd - popd - - REM copy the extension into the PHP tree - mkdir c:\projects\php-sdk\phpdev\vc14\x64\php-src\ext\skeleton - xcopy c:\projects\skeleton-php-ext\*.* c:\projects\php-sdk\phpdev\vc14\x64\php-src\ext\skeleton /s/e/v - pushd c:\projects\php-sdk\phpdev\vc14\x64\php-src\ext\skeleton - del /q CREDITS - popd - - REM The bison utility is needed for the PHP build, so add MSYS to the path. - REM Note: Add to the end to ensure MSVC tools are found firts. - set PATH=%PATH%;c:\MinGW\msys\1.0\bin - - REM perform the build - cmd /c bin\phpsdk_setvars.bat - pushd phpdev\vc14\x64\php-src - cmd /c buildconf --force - cmd /c configure --disable-all --enable-cli --with-skeleton=shared - nmake - popd - - REM TODO: debugging - - dir php_skeleton.dll /s - dir php.exe /s - dir php*.dll /s - -endlocal - diff --git a/.appveyor/test.cmd b/.appveyor/test.cmd deleted file mode 100644 index 255d475..0000000 --- a/.appveyor/test.cmd +++ /dev/null @@ -1,16 +0,0 @@ -@echo off - -setlocal enableextensions enabledelayedexpansion - - cd C:\projects\php-sdk\phpdev\vc14\x64\php-src - - pushd ext\skeleton - echo [PHP] > php.ini - echo extension_dir = "ext" >> php.ini - echo extension=php_skeleton.dll >> php.ini - popd - - set TEST_PHP_EXECUTABLE=C:\projects\php-sdk\phpdev\vc14\x64\php-src\x64\Release_TS\php.exe - %TEST_PHP_EXECUTABLE% run-tests.php ext\skeleton -q --show-diff - -endlocal diff --git a/.ci/appveyor.psm1 b/.ci/appveyor.psm1 new file mode 100644 index 0000000..e22786b --- /dev/null +++ b/.ci/appveyor.psm1 @@ -0,0 +1,57 @@ +Function InitializeBuildVars { + switch ($Env:VC_VERSION) { + 'vc14' { + If (-not (Test-Path $Env:VS120COMNTOOLS)) { + Throw'The VS120COMNTOOLS environment variable is not set. Check your VS installation' + } + + $Env:VSDEVCMD = ($Env:VS120COMNTOOLS -replace '\\$', '') + '\VsDevCmd.bat' + Break + } + 'vc15' { + If (-not (Test-Path $Env:VS140COMNTOOLS)) { + Throw'The VS140COMNTOOLS environment variable is not set. Check your VS installation' + } + + $Env:VSDEVCMD = ($Env:VS140COMNTOOLS -replace '\\$', '') + '\VsDevCmd.bat' + Break + } + default { + $Env:VSDEVCMD = Get-ChildItem -Path "${Env:ProgramFiles(x86)}" -Filter "VsDevCmd.bat" -Recurse -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName } + + If ("$Env:VSDEVCMD" -eq "") { + Throw 'Unable to find VsDevCmd. Check your VS installation' + } + } + } + + If ($Env:PLATFORM -eq 'x64') { + $Env:ARCH = 'x86_amd64' + } Else { + $Env:ARCH = 'x86' + } + + $Env:ENABLE_EXT = "--enable-{0}" -f ("${Env:EXTNAME}" -replace "_","-") + + $SearchInFolder = (Get-Item $Env:VSDEVCMD).Directory.Parent.Parent.FullName + $Env:VCVARSALL = Get-ChildItem -Path "$SearchInFolder" -Filter "vcvarsall.bat" -Recurse -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName } +} + +Function InitializeReleaseVars { + If ($Env:PLATFORM -eq 'x86') { + If ($Env:BUILD_TYPE -Match "nts-Win32") { + $Env:RELEASE_SUBFOLDER = "Release" + } Else { + $Env:RELEASE_SUBFOLDER = "Release_TS" + } + } Else { + If ($Env:BUILD_TYPE -Match "nts-Win32") { + $Env:RELEASE_SUBFOLDER = "${Env:PLATFORM}\Release" + } Else { + $Env:RELEASE_SUBFOLDER = "${Env:PLATFORM}\Release_TS" + } + } + + $Env:RELEASE_FOLDER = "${Env:APPVEYOR_BUILD_FOLDER}\${Env:RELEASE_SUBFOLDER}" + $Env:RELEASE_ZIPBALL = "${Env:EXTNAME}_${Env:PLATFORM}_${Env:VC_VERSION}_php${Env:PHP_VERSION}_${Env:APPVEYOR_BUILD_VERSION}" +} diff --git a/.ci/php-appveyor.psm1 b/.ci/php-appveyor.psm1 new file mode 100644 index 0000000..de0c921 --- /dev/null +++ b/.ci/php-appveyor.psm1 @@ -0,0 +1,567 @@ +# This file is part of the php-appveyor.psm1 project. +# +# (c) Serghei Iakovlev +# +# For the full copyright and license information, please view +# the LICENSE file that was distributed with this source code. + +# $ErrorActionPreference = "Stop" + +function InstallPhpSdk { + param ( + [Parameter(Mandatory=$true)] [System.String] $Version, + [Parameter(Mandatory=$true)] [System.String] $VC, + [Parameter(Mandatory=$true)] [System.String] $Platform, + [Parameter(Mandatory=$false)] [System.String] $InstallPath = "C:\php-sdk" + ) + + Write-Debug "Install PHP SDK binary tools: ${Version}" + SetupPrerequisites + + $FileName = "php-sdk-${Version}" + $RemoteUrl = "https://github.com/Microsoft/php-sdk-binary-tools/archive/$%7BFileName%7D.zip" + $Archive = "C:\Downloads\${FileName}.zip" + + if (-not (Test-Path "${InstallPath}\bin\php\php.exe")) { + if (-not (Test-Path $Archive)) { + DownloadFile -RemoteUrl $RemoteUrl -Destination $Archive + } + + $UnzipPath = "${Env:Temp}\php-sdk-binary-tools-${FileName}" + if (-not (Test-Path "${UnzipPath}")) { + Expand-Item7zip -Archive $Archive -Destination $Env:Temp + } + + Move-Item -Path $UnzipPath -Destination $InstallPath + } + + EnsureRequiredDirectoriesPresent ` + -Directories bin,lib,include ` + -Prefix "${InstallPath}\phpdev\${VC}\${Platform}" +} + +function InstallPhp { + param ( + [Parameter(Mandatory=$true)] [System.String] $Version, + [Parameter(Mandatory=$true)] [System.String] $BuildType, + [Parameter(Mandatory=$true)] [System.String] $VC, + [Parameter(Mandatory=$true)] [System.String] $Platform, + [Parameter(Mandatory=$false)] [System.String] $InstallPath = "C:\php" + ) + + SetupPrerequisites + $FullVersion = SetupPhpVersionString -Pattern $Version + + Write-Debug "Install PHP v${FullVersion}" + + $ReleasesPart = "releases" + if ([System.Convert]::ToDecimal($Version) -lt 7.1) { + $ReleasesPart = "releases/archives" + } + + $RemoteUrl = "http://windows.php.net/downloads/%7B0%7D/php-%7B1%7D-%7B2%7D-%7B3%7D-%7B4%7D.zip" -f + $ReleasesPart, $FullVersion, $BuildType, $VC, $Platform + + $Archive = "C:\Downloads\php-${FullVersion}-${BuildType}-${VC}-${Platform}.zip" + + if (-not (Test-Path "${InstallPath}\php.exe")) { + if (-not (Test-Path $Archive)) { + DownloadFile $RemoteUrl $Archive + } + + Expand-Item7zip $Archive $InstallPath + } + + if (-not (Test-Path "${InstallPath}\php.ini")) { + Copy-Item "${InstallPath}\php.ini-development" "${InstallPath}\php.ini" + } +} + +function InstallPhpDevPack { + param ( + [Parameter(Mandatory=$true)] [System.String] $PhpVersion, + [Parameter(Mandatory=$true)] [System.String] $BuildType, + [Parameter(Mandatory=$true)] [System.String] $VC, + [Parameter(Mandatory=$true)] [System.String] $Platform, + [Parameter(Mandatory=$false)] [System.String] $InstallPath = "C:\php-devpack" + ) + + SetupPrerequisites + $Version = SetupPhpVersionString -Pattern $PhpVersion + + Write-Debug "Install PHP Dev for PHP v${Version}" + + $ReleasesPart = "releases" + if ([System.Convert]::ToDecimal($PhpVersion) -lt 7.1) { + $ReleasesPart = "releases/archives" + } + + $RemoteUrl = "http://windows.php.net/downloads/%7B0%7D/php-devel-pack-%7B1%7D-%7B2%7D-%7B3%7D-%7B4%7D.zip" -f + $ReleasesPart, $Version, $BuildType, $VC, $Platform + + $Archive = "C:\Downloads\php-devel-pack-${Version}-${BuildType}-${VC}-${Platform}.zip" + + if (-not (Test-Path "${InstallPath}\phpize.bat")) { + if (-not (Test-Path $Archive)) { + DownloadFile $RemoteUrl $Archive + } + + $UnzipPath = "${Env:Temp}\php-${Version}-devel-${VC}-${Platform}" + if (-not (Test-Path "${UnzipPath}\phpize.bat")) { + Expand-Item7zip $Archive $Env:Temp + } + + if (Test-Path "${InstallPath}") { + Move-Item -Path "${UnzipPath}\*" -Destination $InstallPath + } else { + Move-Item -Path $UnzipPath -Destination $InstallPath + } + } +} + +function InstallPeclExtension { + param ( + [Parameter(Mandatory=$true)] [System.String] $Name, + [Parameter(Mandatory=$true)] [System.String] $Version, + [Parameter(Mandatory=$true)] [System.String] $PhpVersion, + [Parameter(Mandatory=$true)] [System.String] $BuildType, + [Parameter(Mandatory=$true)] [System.String] $VC, + [Parameter(Mandatory=$true)] [System.String] $Platform, + [Parameter(Mandatory=$false)] [System.String] $InstallPath = "C:\php\ext", + [Parameter(Mandatory=$false)] [System.Boolean] $Headers = $false, + [Parameter(Mandatory=$false)] [System.Boolean] $Enable = $false + ) + + SetupPrerequisites + + $BaseUri = "https://windows.php.net/downloads/pecl/releases/$%7BName%7D/$%7BVersion%7D" + $LocalPart = "php_${Name}-${Version}-${PhpVersion}" + + if ($BuildType -Match "nts-Win32") { + $TS = "nts" + } else { + $TS = "ts" + } + + $RemoteUrl = "${BaseUri}/${LocalPart}-${TS}-${VC}-${Platform}.zip" + $DestinationPath = "C:\Downloads\${LocalPart}-${TS}-${VC}-${Platform}.zip" + + if (-not (Test-Path "${InstallPath}\php_${Name}.dll")) { + if (-not (Test-Path $DestinationPath)) { + DownloadFile $RemoteUrl $DestinationPath + } + + Expand-Item7zip $DestinationPath $InstallPath + } + + if ($Headers) { + InstallPeclHeaders -Name $Name -Version $Version + } + + if ($Enable) { + EnablePhpExtension -Name $Name + } +} + +Function InstallPeclHeaders { + Param( + [Parameter(Mandatory=$true)] [System.String] $Name, + [Parameter(Mandatory=$true)] [System.String] $Version, + [Parameter(Mandatory=$false)] [System.String] $InstallPath = 'C:\php-devpack\include\ext\' + ) + + $RemoteUrl = "https://pecl.php.net/get/$%7BName%7D-$%7BVersion%7D.tgz" + $DownloadFile = "C:\Downloads\${Name}-${Version}.tgz" + + If (-not [System.IO.File]::Exists($DownloadFile)) { + DownloadFile $RemoteUrl $DownloadFile + } + + Ensure7ZipIsInstalled + + Expand-Item7zip $DownloadFile "${Env:Temp}" + + Write-Debug "Copy header files to ${InstallPath}\${Name}" + + New-Item -Path "${InstallPath}" -Name "${Name}" -ItemType "directory" | Out-Null + Copy-Item "${Env:Temp}\${Name}-${Version}\*.h" -Destination "${InstallPath}\${Name}" -Recurse +} + +function InstallComposer { + param ( + [Parameter(Mandatory=$false)] [System.String] $PhpInstallPath = 'C:\php', + [Parameter(Mandatory=$false)] [System.String] $InstallPath = '.' + ) + + $InstallPath = Resolve-Path $InstallPath + + $ComposerBatch = "${InstallPath}\composer.bat" + $ComposerPhar = "${InstallPath}\composer.phar" + + if (-not (Test-Path -Path $ComposerPhar)) { + DownloadFile "https://getcomposer.org/composer.phar" "${ComposerPhar}" + + Write-Output '@echo off' | Out-File -Encoding "ASCII" $ComposerBatch + Write-Output "${PhpInstallPath}\php.exe `"${ComposerPhar}`" %*" | Out-File -Encoding "ASCII" -Append $ComposerBatch + } +} + +function EnablePhpExtension { + param ( + [Parameter(Mandatory=$true)] [System.String] $Name, + [Parameter(Mandatory=$false)] [System.String] $PhpInstallPath = 'C:\php', + [Parameter(Mandatory=$false)] [System.String] $ExtPath = 'C:\php\ext', + [Parameter(Mandatory=$false)] [System.String] $PrintableName = '' + ) + + $FullyQualifiedExtensionPath = "${ExtPath}\php_${Name}.dll" + + $IniFile = "${PhpInstallPath}\php.ini" + $PhpExe = "${PhpInstallPath}\php.exe" + + if (-not (Test-Path $IniFile)) { + throw "Unable to locate ${IniFile}" + } + + if (-not (Test-Path "${ExtPath}")) { + throw "Unable to locate ${ExtPath} direcory" + } + + Write-Debug "Add `"extension = ${FullyQualifiedExtensionPath}`" to the ${IniFile}" + Write-Output "extension = ${FullyQualifiedExtensionPath}" | Out-File -Encoding "ASCII" -Append $IniFile + + if (Test-Path -Path "${PhpExe}") { + if ($PrintableName) { + Write-Debug "Minimal load test using command: ${PhpExe} --ri `"${PrintableName}`"" + $Result = (& "${PhpExe}" --ri "${PrintableName}") + } else { + Write-Debug "Minimal load test using command: ${PhpExe} --ri ${Name}" + $Result = (& "${PhpExe}" --ri $Name) + } + + $ExitCode = $LASTEXITCODE + if ($ExitCode -ne 0) { + throw "An error occurred while enabling ${Name} at ${IniFile}. ${Result}" + } + } +} + +function TuneUpPhp { + param ( + [Parameter(Mandatory=$false)] [System.String] $MemoryLimit = '256M', + [Parameter(Mandatory=$false)] [System.String[]] $DefaultExtensions = @(), + [Parameter(Mandatory=$false)] [System.String] $IniFile = 'C:\php\php.ini', + [Parameter(Mandatory=$false)] [System.String] $ExtPath = 'C:\php\ext' + ) + + Write-Debug "Tune up PHP using file `"${IniFile}`"" + + if (-not (Test-Path $IniFile)) { + throw "Unable to locate ${IniFile} file" + } + + if (-not (Test-Path $ExtPath)) { + throw "Unable to locate ${ExtPath} direcory" + } + + Write-Output "" | Out-File -Encoding "ASCII" -Append $IniFile + + Write-Output "extension_dir = ${ExtPath}" | Out-File -Encoding "ASCII" -Append $IniFile + Write-Output "memory_limit = ${MemoryLimit}" | Out-File -Encoding "ASCII" -Append $IniFile + + if ($DefaultExtensions.count -gt 0) { + Write-Output "" | Out-File -Encoding "ASCII" -Append $IniFile + + foreach ($Ext in $DefaultExtensions) { + Write-Output "extension = php_${Ext}.dll" | Out-File -Encoding "ASCII" -Append $IniFile + } + } +} + +function PrepareReleaseNote { + param ( + [Parameter(Mandatory=$true)] [System.String] $PhpVersion, + [Parameter(Mandatory=$true)] [System.String] $BuildType, + [Parameter(Mandatory=$true)] [System.String] $Platform, + [Parameter(Mandatory=$false)] [System.String] $ReleaseFile, + [Parameter(Mandatory=$false)] [System.String] $ReleaseDirectory, + [Parameter(Mandatory=$false)] [System.String] $BasePath + ) + + $Destination = "${BasePath}\${ReleaseDirectory}" + + if (-not (Test-Path $Destination)) { + New-Item -ItemType Directory -Force -Path "${Destination}" | Out-Null + } + + $ReleaseFile = "${Destination}\${ReleaseFile}" + $ReleaseDate = Get-Date -Format o + + $Image = $Env:APPVEYOR_BUILD_WORKER_IMAGE + $Version = $Env:APPVEYOR_BUILD_VERSION + $Commit = $Env:APPVEYOR_REPO_COMMIT + $CommitDate = $Env:APPVEYOR_REPO_COMMIT_TIMESTAMP + + Write-Output "Release date: ${ReleaseDate}" | Out-File -Encoding "ASCII" -Append "${ReleaseFile}" + Write-Output "Release version: ${Version}" | Out-File -Encoding "ASCII" -Append "${ReleaseFile}" + Write-Output "Git commit: ${Commit}" | Out-File -Encoding "ASCII" -Append "${ReleaseFile}" + Write-Output "Commit date: ${CommitDate}" | Out-File -Encoding "ASCII" -Append "${ReleaseFile}" + Write-Output "Build type: ${BuildType}" | Out-File -Encoding "ASCII" -Append "${ReleaseFile}" + Write-Output "Platform: ${Platform}" | Out-File -Encoding "ASCII" -Append "${ReleaseFile}" + Write-Output "Target PHP version: ${PhpVersion}" | Out-File -Encoding "ASCII" -Append "${ReleaseFile}" + Write-Output "Build worker image: ${Image}" | Out-File -Encoding "ASCII" -Append "${ReleaseFile}" +} + +function PrepareReleasePackage { + param ( + [Parameter(Mandatory=$true)] [System.String] $PhpVersion, + [Parameter(Mandatory=$true)] [System.String] $BuildType, + [Parameter(Mandatory=$true)] [System.String] $Platform, + [Parameter(Mandatory=$false)] [System.String] $ZipballName = '', + [Parameter(Mandatory=$false)] [System.String[]] $ReleaseFiles = @(), + [Parameter(Mandatory=$false)] [System.String] $ReleaseFile = 'RELEASE.txt', + [Parameter(Mandatory=$false)] [System.Boolean] $ConverMdToHtml = $false, + [Parameter(Mandatory=$false)] [System.String] $BasePath = '.' + ) + + $BasePath = Resolve-Path $BasePath + $ReleaseDirectory = "${Env:APPVEYOR_PROJECT_NAME}-${Env:APPVEYOR_BUILD_ID}-${Env:APPVEYOR_JOB_ID}-${Env:APPVEYOR_JOB_NUMBER}" + + PrepareReleaseNote ` + -PhpVersion $PhpVersion ` + -BuildType $BuildType ` + -Platform $Platform ` + -ReleaseFile $ReleaseFile ` + -ReleaseDirectory $ReleaseDirectory ` + -BasePath $BasePath + + $ReleaseDestination = "${BasePath}\${ReleaseDirectory}" + + $CurrentPath = Resolve-Path '.' + + if ($ConverMdToHtml) { + InstallReleaseDependencies + FormatReleaseFiles -ReleaseDirectory $ReleaseDirectory + } + + if ($ReleaseFiles.count -gt 0) { + foreach ($File in $ReleaseFiles) { + Copy-Item "${File}" "${ReleaseDestination}" + Write-Debug "Copy ${File} to ${ReleaseDestination}" + } + } + + if (!$ZipballName) { + if (!$Env:RELEASE_ZIPBALL) { + throw "Required parameter `"ZipballName`" is missing" + } else { + $ZipballName = $Env:RELEASE_ZIPBALL; + } + } + + Ensure7ZipIsInstalled + + Set-Location "${ReleaseDestination}" + $Output = (& 7z a "${ZipballName}.zip" *) + $ExitCode = $LASTEXITCODE + + $DirectoryContents = Get-ChildItem -Path "${ReleaseDestination}" + Write-Debug ($DirectoryContents | Out-String) + + if ($ExitCode -ne 0) { + Set-Location "${CurrentPath}" + throw "An error occurred while creating release zippbal: `"${ZipballName}`". ${Output}" + } + + Move-Item "${ZipballName}.zip" -Destination "${BasePath}" + Set-Location "${CurrentPath}" +} + +function FormatReleaseFiles { + param ( + [Parameter(Mandatory=$true)] [System.String] $ReleaseDirectory, + [Parameter(Mandatory=$false)] [System.String] $BasePath = '.' + ) + + EnsurePandocIsInstalled + + $CurrentPath = (Get-Item -Path ".\" -Verbose).FullName + + $BasePath = Resolve-Path $BasePath + Set-Location "${BasePath}" + + Get-ChildItem (Get-Item -Path ".\" -Verbose).FullName *.md | + ForEach-Object{ + $BaseName = $_.BaseName + pandoc -f markdown -t html5 "${BaseName}.md" > "${BasePath}\${ReleaseDirectory}\${BaseName}.html" + } + + Set-Location "${CurrentPath}" +} + +function SetupPhpVersionString { + param ( + [Parameter(Mandatory=$true)] [String] $Pattern + ) + + $RemoteUrl = 'http://windows.php.net/downloads/releases/sha256sum.txt' + $Destination = "${Env:Temp}\php-sha256sum.txt" + + if (-not (Test-Path $Destination)) { + DownloadFile $RemoteUrl $Destination + } + + $VersionString = Get-Content $Destination | Where-Object { + $_ -match "php-($Pattern\.\d+)-src" + } | ForEach-Object { $matches[1] } + + if ($VersionString -NotMatch '\d+\.\d+\.\d+' -or $null -eq $VersionString) { + throw "Unable to obtain PHP version string using pattern 'php-($Pattern\.\d+)-src'" + } + + Write-Output $VersionString.Split(' ')[-1] +} + +function SetupPrerequisites { + Ensure7ZipIsInstalled + EnsureRequiredDirectoriesPresent -Directories C:\Downloads +} + +function Ensure7ZipIsInstalled { + if (-not (Get-Command "7z" -ErrorAction SilentlyContinue)) { + $7zipInstallationDirectory = "${Env:ProgramFiles}\7-Zip" + + if (-not (Test-Path "${7zipInstallationDirectory}")) { + throw "The 7-zip file archiver is needed to use this module" + } + + $Env:Path += ";$7zipInstallationDirectory" + } +} + +function InstallReleaseDependencies { + EnsureChocolateyIsInstalled + $Output = (choco install -y --no-progress pandoc) + $ExitCode = $LASTEXITCODE + + if ($ExitCode -ne 0) { + throw "An error occurred while installing pandoc. ${Output}" + } +} + +function EnsureChocolateyIsInstalled { + if (-not (Get-Command "choco" -ErrorAction SilentlyContinue)) { + $ChocolateyInstallationDirectory = "${Env:ChocolateyInstall}\bin" + + if (-not (Test-Path "$ChocolateyInstallationDirectory")) { + throw "The choco is needed to use this module" + } + + $Env:Path += ";$ChocolateyInstallationDirectory" + } +} + +function EnsurePandocIsInstalled { + if (-not (Get-Command "pandoc" -ErrorAction SilentlyContinue)) { + $PandocInstallationDirectory = "${Env:ChocolateyInstall}\bin" + + if (-not (Test-Path "$PandocInstallationDirectory")) { + throw "The pandoc is needed to use this module" + } + + $Env:Path += ";$PandocInstallationDirectory" + } + + $Output = (& "pandoc" -v) + $ExitCode = $LASTEXITCODE + + if ($ExitCode -ne 0) { + throw "An error occurred while self testing pandoc. ${Output}" + } +} + +function EnsureRequiredDirectoriesPresent { + param ( + [Parameter(Mandatory=$true)] [String[]] $Directories, + [Parameter(Mandatory=$false)] [String] $Prefix = "" + ) + + foreach ($Dir in $Directories) { + if (-not (Test-Path $Dir)) { + if ($Prefix) { + New-Item -ItemType Directory -Force -Path "${Prefix}\${Dir}" | Out-Null + } else { + New-Item -ItemType Directory -Force -Path "${Dir}" | Out-Null + } + + } + } +} + +function DownloadFile { + param ( + [Parameter(Mandatory=$true)] [System.String] $RemoteUrl, + [Parameter(Mandatory=$true)] [System.String] $Destination + ) + + $RetryMax = 5 + $RetryCount = 0 + $Completed = $false + + $WebClient = New-Object System.Net.WebClient + $WebClient.Headers.Add('User-Agent', 'AppVeyor PowerShell Script') + + Write-Debug "Downloading: '${RemoteUrl}' => '${Destination}' ..." + + while (-not $Completed) { + try { + $WebClient.DownloadFile($RemoteUrl, $Destination) + $Completed = $true + } catch { + if ($RetryCount -ge $RetryMax) { + $ErrorMessage = $_.Exception.Message + Write-Error -Message "${ErrorMessage}" + $Completed = $true + } else { + $RetryCount++ + } + } + } +} + +function Expand-Item7zip { + param( + [Parameter(Mandatory=$true)] [System.String] $Archive, + [Parameter(Mandatory=$true)] [System.String] $Destination + ) + + if (-not (Test-Path -Path $Archive -PathType Leaf)) { + throw "Specified archive file does not exist: ${Archive}" + } + + Write-Debug "Unzipping ${Archive} to ${Destination} ..." + + if (-not (Test-Path -Path $Destination -PathType Container)) { + New-Item $Destination -ItemType Directory | Out-Null + } + + $ExitCode = 0 + + if ("${Archive}" -like "*.tgz") { + $Output = (& 7z x -tgzip "$Archive" -bd -y "-o${Env:Temp}") + $ExitCode = $LASTEXITCODE + + if ($ExitCode -eq 0) { + $Archive = "${Env:Temp}/{0}.tar" -f [System.IO.Path]::GetFileNameWithoutExtension($Archive) + } + } + + if ($ExitCode -eq 0) { + $Output = (& 7z x "$Archive" "-o$Destination" -aoa -bd -y -r) + $ExitCode = $LASTEXITCODE + } + + if ($ExitCode -ne 0) { + throw "An error occurred while unzipping '${Archive}' to '${Destination}'. ${Output}" + } +} diff --git a/.gitignore b/.gitignore index 4c82adf..8448412 100644 --- a/.gitignore +++ b/.gitignore @@ -19,18 +19,23 @@ mkinstalldirs run-tests.php Makefile Makefile.* +!Makefile.frag libtool *~ modules/ .libs/ *.la *.lo +tests/**/*.* +!tests/**/*.phpt +!tests/smoke.php php_test_results_*.txt -tests/* -!tests/*.phpt http_message.loT *.loT configure.ac cmake-build-* vendor composer.lock +tmp-php.ini +*.gpg +/*.tgz diff --git a/.travis.yml b/.travis.yml index 05c47f1..960f0a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,22 @@ language: php php: - - 7.0 - - 7.1 - 7.2 + - 7.3 + - 7.4snapshot - nightly matrix: allow_failures: - - php: 7.0 - php: nightly - branches: only: - master + - travis + - /^v\d+\.\d+\.\d+$/ before_install: - # Install external libraries + - pecl install --nodeps psr install: - phpize @@ -29,3 +29,29 @@ before_script: script: make test +after_failure: + - | + for FILE in $(find tests -name '*.diff'); do + echo "$FILE" + cat "$FILE" + echo + done + +before_deploy: + - openssl aes-256-cbc -K $encrypted_36ea4391f664_key -iv $encrypted_36ea4391f664_iv -in sign.gpg.enc -out sign.gpg -d + - gpg --import sign.gpg + - rm sign.gpg + - pecl package + - export RELEASE_PACKAGE=$(ls http_message-*.tgz) +# - echo | pecl sign "$RELEASE_PACKAGE" + +deploy: + provider: releases + api_key: + secure: "HuYxK89LGMINNzDyrA9xXVzQB7YKo563Q1EVkAOrrv7cDKbfVLTPHfI9huB8XnzDJgp1EhrXA3hMKeJdpbtzoEhiaVDc/eeFcCubhf+VEsMbke9TOYIEx7mSNDpV0igyWDR1bt+8GrCFkP04NMzzlqU+KoW0ceK/xCaMRhNWiskf3Z+e2Xne3yEVd64A61xtyPMbaOIqqmjkmqCyrN9DT+5g+zZVMz1UssdfRqMCKTyHcaYYFayQwSZgmAxZO3WKoHnd5ydoii2XIBHto7RESqAoyemw/Ksw7YdplxsZMfbCsmiSuwrlmd1t/BX7QIULeJsVwPiv7MWJWjh8VRIiFCw5FdZ+n2kLfL32MY8r4hl7Uo9nEkNaIYRBX0ryV5XXRU/2q1nuz8Knaz++Q+tPodmMp8vzmDGl2cguq9KuKbVEbiFjR+PO96YFlKdtDosUQFjbSbQOFDQdstPFSZr5s/PKPVj5Va33PyyrLI9TMZwgWLPzMEqerGXy8meigeZ0r0m3DG4slzxhxE9y30fFEx9fWPNR3AN/1NGi7WTsqonPQitu1M7YyKCdMqjWJkr++geapJwKVX/poxExQ4i0TEkgqGWmV9nJehGJ5nS8s2tH4PR8+3bA2gvNoqBO+jmd8CLjkPGbCPu8OlvdwXmeKJYsX382iGG8CxrDDPhGlr4=" + file: "$RELEASE_PACKAGE" + skip_cleanup: true + name: "$TRAVIS_TAG" + prerelease: false + on: + tags: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 685f779..ea3d6b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,20 @@ cmake_minimum_required(VERSION 3.8) -project(http_message) +project(http_message C) -add_custom_target(extension COMMAND phpize && ./configure && make && make install - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) +add_compile_definitions(HAVE_HTTP_MESSAGE) set(SOURCE_FILES php_http_message - http_message.c message.c request.c uri.c server_request.c response.c stream.c) + http_message.c + message.c + request.c + server_request.c + response.c + uri.c + stream.c + uploaded_file.c + factory.c + emitter.c +) execute_process ( COMMAND php-config --include-dir @@ -18,6 +27,12 @@ message("Using source directory: ${PHP_SOURCE}") include_directories(${PHP_SOURCE}) include_directories(${PHP_SOURCE}/main) include_directories(${PHP_SOURCE}/Zend) +include_directories(${PHP_SOURCE}/TSRM) include_directories(${PROJECT_SOURCE_DIR}) -add_executable(http_message ${SOURCE_FILES}) +add_custom_target(configure + COMMAND phpize && ./configure + DEPENDS ${SOURCE_FILES} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) + +add_library(___ EXCLUDE_FROM_ALL ${SOURCE_FILES}) diff --git a/Makefile.frag b/Makefile.frag new file mode 100644 index 0000000..8177ecd --- /dev/null +++ b/Makefile.frag @@ -0,0 +1,25 @@ +PHP_TEST_SHARED_EXTENSIONS = ` \ + echo "-d extension=$(EXTENSION_DIR)/psr.so"; \ + if test "x$(PHP_MODULES)" != "x"; then \ + for i in $(PHP_MODULES)""; do \ + . $$i; $(top_srcdir)/build/shtool echo -n -- " -d extension=$$dlname"; \ + done; \ + fi; \ + if test "x$(PHP_ZEND_EX)" != "x"; then \ + for i in $(PHP_ZEND_EX)""; do \ + . $$i; $(top_srcdir)/build/shtool echo -n -- " -d $(ZEND_EXT_TYPE)=$(top_builddir)/modules/$$dlname"; \ + done; \ + fi` + +clean-tests: + rm -f tests/*/*.diff tests/*/*.exp tests/*/*.log tests/*/*.out tests/*/*.php tests/*/*.sh + +mrproper: clean + rm -rf autom4te.cache build modules vendor + rm -f acinclude.m4 aclocal.m4 config.guess config.h config.h.in config.log config.nice config.status config.sub \ + configure configure.ac install-sh libtool ltmain.sh Makefile Makefile.fragments Makefile.global \ + Makefile.objects missing mkinstalldirs run-tests.php + +package.xml: php_$(PHP_PECL_EXTENSION).h + $(PHP_EXECUTABLE) build-packagexml.php + diff --git a/README.md b/README.md index ff61dc8..77c7b27 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,31 @@ -# PSR-7 HTTP Message as Pecl extension +![improved PHP library](https://user-images.githubusercontent.com/100821/46372249-e5eb7500-c68a-11e8-801a-2ee57da3e5e3.png) -[![Build Status](https://travis-ci.org/jasny/http_message-php-ext.svg?branch=master)](https://travis-ci.org/jasny/http_message-php-ext) -[![Build status](https://ci.appveyor.com/api/projects/status/7rof1vr8mv4kam17/branch/master?svg=true)](https://ci.appveyor.com/project/jasny/http_message-php-ext/branch/master) +# PSR-7 HTTP Message as PHP extension -[PSR-7 HTTP Message](https://www.php-fig.org/psr/psr-7/) implementation as PHP extension written in C. +[![Build Status](https://travis-ci.org/improved-php-library/http-message.svg?branch=master)](https://travis-ci.org/improved-php-library/http-message) +[![Build status](https://ci.appveyor.com/api/projects/status/7rof1vr8mv4kam17/branch/master?svg=true)](https://ci.appveyor.com/project/jasny/http-message/branch/master) + +[PSR-7 HTTP Message](https://www.php-fig.org/psr/psr-7/) implementation as PHP extension written in C. Includes a +[PSR-17](https://www.php-fig.org/psr/psr-17/) compatilbe factory and an emitter. --- ## Requirements -* PHP 7.x +* PHP 7.2+ +* [psr extension](https://github.com/jbboehr/php-psr) ## Installation +The extension is [available from pecl](https://pecl.php.net/package/http_message). + + pecl install psr + pecl install http_message-beta + +### Manual build + +Instead of installing this extension from pecl, you can build it manually + phpize ./configure make @@ -26,4 +39,20 @@ Add the following line to your `php.ini` To try out the extension, you can run the following command php -a -d extension=modules/http_message.so + +# Usage + +```php +use HttpMessage\Emitter; +use HttpMessage\Factory; +use HttpMessage\ServerRequest; + +$request = new ServerRequest($_SERVER, $_COOKIE, $_GET, $_POST, $_FILES); + +$handler = new App\Psr15Handler(); // Any PSR-15 handler. +$response = $handler->handle($request); + +$emitter = new Emitter(); +$emitter->emit($response); +``` diff --git a/autoconf/pecl.m4 b/autoconf/pecl.m4 new file mode 100644 index 0000000..ffa45ac --- /dev/null +++ b/autoconf/pecl.m4 @@ -0,0 +1,415 @@ + +yes() { + true +} +no() { + false +} +dnl +dnl PECL_INIT(name) +dnl +dnl Start configuring the PECL extension. +dnl +AC_DEFUN([PECL_INIT], [dnl + m4_define([PECL_NAME],[$1])dnl +])dnl +dnl +dnl +dnl PECL_VAR(name) +dnl +AC_DEFUN([PECL_VAR], [dnl +AS_TR_CPP([PHP_]PECL_NAME[_$1])dnl +])dnl +dnl +dnl PECL_CACHE_VAR(name) +dnl +AC_DEFUN([PECL_CACHE_VAR], [dnl +AS_TR_SH([PECL_cv_$1])dnl +])dnl +dnl +dnl PECL_SAVE_VAR(name) +dnl +AC_DEFUN([PECL_SAVE_VAR], [dnl +AS_TR_SH([PECL_sv_$1])dnl +])dnl +dnl +dnl PECL_DEFINE(what, to[, desc]) +dnl +AC_DEFUN([PECL_DEFINE], [dnl + AC_DEFINE(PECL_VAR([$1]), ifelse([$2],,1,[$2]), ifelse([$3],,[ ],[$3])) +])dnl +dnl +dnl PECL_DEFINE_UQ(what, to[, desc]) +dnl +AC_DEFUN([PECL_DEFINE_UQ], [dnl + AC_DEFINE_UNQUOTED(PECL_VAR([$1]), [$2], ifelse([$3],,[ ],[$3])) +])dnl +dnl +dnl PECL_DEFINE_SH(what, to[, desc]) +dnl +AC_DEFUN([PECL_DEFINE_SH], [dnl + PECL_VAR([$1])=$2 + PECL_DEFINE_UQ([$1], [$2], [$3]) +]) +dnl +dnl PECL_DEFINE_FN(fn) +dnl +AC_DEFUN([PECL_DEFINE_FN], [ + AC_DEFINE(AS_TR_CPP([HAVE_$1]), [1], [ ]) +]) +dnl +dnl PECL_SAVE_ENV(var, ns) +dnl +AC_DEFUN([PECL_SAVE_ENV], [ + PECL_SAVE_VAR([$2_$1])=[$]$1 +]) +dnl +dnl PECL_RESTORE_ENV(var, ns) +dnl +AC_DEFUN([PECL_RESTORE_ENV], [ + $1=$PECL_SAVE_VAR([$2_$1]) +]) +dnl +dnl PECL_EVAL_LIBLINE(libline) +dnl +AC_DEFUN([PECL_EVAL_LIBLINE], [ + PECL_SAVE_ENV(ext_shared, pecl) + ext_shared=no + PHP_EVAL_LIBLINE([$1], _pecl_eval_libline_dummy) + PECL_RESTORE_ENV(ext_shared, pecl) +]) +dnl +dnl PECL_PROG_EGREP +dnl +dnl Checks for an egrep. Defines $EGREP. +dnl +AC_DEFUN([PECL_PROG_EGREP], [ + ifdef([AC_PROG_EGREP], [ + AC_PROG_EGREP + ], [ + AC_CHECK_PROG(EGREP, egrep, egrep) + ]) +]) +dnl +dnl PECL_PROG_AWK +dnl +dnl Checks for an awk. Defines $AWK. +dnl +AC_DEFUN([PECL_PROG_AWK], [ + ifdef([AC_PROG_AWK], [ + AC_PROG_AWK + ], [ + AC_CHECK_PROG(AWK, awk, awk) + ]) +]) +dnl +dnl PECL_PROG_SED +dnl +dnl Checks for the sed program. Defines $SED. +dnl +AC_DEFUN([PECL_PROG_SED], [ + ifdef([AC_PROG_SED], [ + AC_PROG_SED + ], [ + ifdef([LT_AC_PROG_SED], [ + LT_AC_PROG_SED + ], [ + AC_CHECK_PROG(SED, sed, sed) + ]) + ]) +]) +dnl +dnl PECL_PROG_PKGCONFIG +dnl +dnl Checks for pkg-config program and defines $PKG_CONFIG (to false if not found). +dnl +AC_DEFUN([PECL_PROG_PKGCONFIG], [ + if test -z "$PKG_CONFIG"; then + AC_PATH_PROG([PKG_CONFIG], [pkg-config], [false]) + fi +]) +dnl +dnl PECL_HAVE_PHP_EXT(name[, code-if-yes[, code-if-not]]) +dnl +dnl Check whether ext/$name is enabled in $PHP_EXECUTABLE (PECL build) +dnl or if $PHP_ is defined to anything else than "no" (in-tree build). +dnl Defines shell var PECL_VAR(HAVE_EXT_) to true or false. +dnl +AC_DEFUN([PECL_HAVE_PHP_EXT], [ + AC_REQUIRE([PECL_PROG_EGREP])dnl + AC_CACHE_CHECK([whether ext/$1 is enabled], PECL_CACHE_VAR([HAVE_EXT_$1]), [ + PECL_CACHE_VAR([HAVE_EXT_$1])=no + if test -x "$PHP_EXECUTABLE"; then + if $PHP_EXECUTABLE -m | $EGREP -q ^$1\$; then + PECL_CACHE_VAR([HAVE_EXT_$1])=yes + fi + elif test -n "$AS_TR_CPP([PHP_$1])" && test "$AS_TR_CPP([PHP_$1])" != "no"; then + PECL_CACHE_VAR([HAVE_EXT_$1])=yes + fi + ]) + if $PECL_CACHE_VAR([HAVE_EXT_$1]); then + PECL_VAR([HAVE_EXT_$1])=true + PECL_DEFINE([HAVE_EXT_$1]) + $2 + else + PECL_VAR([HAVE_EXT_$1])=false + $3 + fi +]) +dnl +dnl PECL_HAVE_PHP_EXT_HEADER(ext[, header]) +dnl +dnl Check where to find a header for ext and add the found dir to $INCLUDES. +dnl If header is not specified php_.h is assumed. +dnl Defines shell var PHP__EXT__INCDIR to the found dir. +dnl Defines PHP__HAVE_
to the found path. +dnl +AC_DEFUN([PECL_HAVE_PHP_EXT_HEADER], [dnl + AC_REQUIRE([PECL_PROG_SED])dnl + m4_define([EXT_HEADER], ifelse([$2],,php_$1.h,[$2]))dnl + AC_CACHE_CHECK([for EXT_HEADER of ext/$1], PECL_CACHE_VAR([EXT_$1]_INCDIR), [ + for i in $(printf "%s" "$INCLUDES" | $SED -e's/-I//g') $abs_srcdir ../$1; do + if test -d $i; then + for j in $i/EXT_HEADER $i/ext/$1/EXT_HEADER; do + if test -f $j; then + PECL_CACHE_VAR([EXT_$1]_INCDIR)=$(dirname "$j") + break + fi + done + fi + done + ]) + PECL_VAR([EXT_$1]_INCDIR)=$PECL_CACHE_VAR([EXT_$1]_INCDIR) + PHP_ADD_INCLUDE([$PECL_VAR([EXT_$1]_INCDIR)]) + PECL_DEFINE_UQ([HAVE_]EXT_HEADER, "$PECL_VAR([EXT_$1]_INCDIR)/EXT_HEADER") +]) +dnl +dnl PECL_HAVE_CONST(header, const[, type=int[, code-if-yes[, code-if-mno]]]) +dnl +AC_DEFUN([PECL_HAVE_CONST], [dnl + AC_REQUIRE([PECL_PROG_EGREP])dnl + AC_CACHE_CHECK([for $2 in $1], PECL_CACHE_VAR([HAVE_$1_$2]), [ + AC_TRY_COMPILE([ + #include "$1" + ], [ + ]ifelse([$3],,int,[$3])[ _c = $2; + (void) _c; + ], [ + PECL_CACHE_VAR([HAVE_$1_$2])=yes + ], [ + PECL_CACHE_VAR([HAVE_$1_$2])=no + ]) + ]) + if $PECL_CACHE_VAR([HAVE_$1_$2]); then + PECL_DEFINE([HAVE_$2]) + $4 + else + ifelse([$5],,:,[$5]) + fi +]) +dnl +dnl _PECL_TR_VERSION(version) +dnl +AC_DEFUN([_PECL_TR_VERSION], [dnl +AC_REQUIRE([PECL_PROG_AWK])dnl +$(printf "%s" $1 | $AWK -F "[.]" '{print $[]1*1000000 + $[]2*10000 + $[]3*100 + $[]4}') +]) +dnl +dnl PECL_CHECKED_VERSION(name) +dnl +dnl Shell var name of an already checked version. +dnl +AC_DEFUN([PECL_CHECKED_VERSION], [PECL_VAR([$1][_VERSION])]) +dnl +dnl PECL_HAVE_VERSION(name, min-version[, code-if-yes[, code-if-not]]) +dnl +dnl Perform a min-version check while in an PECL_CHECK_* block. +dnl Expands AC_MSG_ERROR when code-if-not is empty and the version check fails. +dnl +AC_DEFUN([PECL_HAVE_VERSION], [ + aversion=_PECL_TR_VERSION([$PECL_CHECKED_VERSION([$1])]) + mversion=_PECL_TR_VERSION([$2]) + AC_MSG_CHECKING([whether $1 version $PECL_CHECKED_VERSION([$1]) >= $2]) + if test -z "$aversion" || test "$aversion" -lt "$mversion"; then + ifelse($4,,AC_MSG_ERROR([no]), [ + AC_MSG_RESULT([no]) + $4 + ]) + else + AC_MSG_RESULT([ok]) + $3 + fi +]) +dnl +dnl PECL_CHECK_CUSTOM(name, path, header, lib, version) +dnl +AC_DEFUN([PECL_CHECK_CUSTOM], [ + PECL_SAVE_ENV([CPPFLAGS], [$1]) + PECL_SAVE_ENV([LDFLAGS], [$1]) + PECL_SAVE_ENV([LIBS], [$1]) + + AC_MSG_CHECKING([for $1]) + AC_CACHE_VAL(PECL_CACHE_VAR([$1_prefix]), [ + for path in $2 /usr/local /usr /opt; do + if test "$path" = "" || test "$path" = "yes" || test "$path" = "no"; then + continue + elif test -f "$path/include/$3"; then + PECL_CACHE_VAR([$1_prefix])="$path" + break + fi + done + ]) + if test -n "$PECL_CACHE_VAR([$1_prefix])"; then + CPPFLAGS="-I$PECL_CACHE_VAR([$1_prefix])/include" + LDFLAGS="-L$PECL_CACHE_VAR([$1_prefix])/$PHP_LIBDIR" + LIBS="-l$4" + PECL_EVAL_LIBLINE([$LDFLAGS $LIBS]) + + AC_CACHE_VAL(PECL_CACHE_VAR([$1_version]), [ + pushd $PECL_CACHE_VAR([$1_prefix]) >/dev/null + PECL_CACHE_VAR([$1_version])=$5 + popd >/dev/null + ]) + PECL_CHECKED_VERSION([$1])=$PECL_CACHE_VAR([$1_version]) + + if test -n "$PECL_CHECKED_VERSION([$1])"; then + PECL_VAR([HAVE_$1])=true + PECL_DEFINE([HAVE_$1]) + PECL_DEFINE_UQ($1[_VERSION], "$PECL_CHECKED_VERSION([$1])") + else + PECL_VAR([HAVE_$1])=false + fi + else + PECL_VAR([HAVE_$1])=false + fi + AC_MSG_RESULT([${PECL_CHECKED_VERSION([$1]):-no}]) +]) +dnl +dnl PECL_CHECK_CONFIG(name, prog-config, version-flag, cppflags-flag, ldflags-flag, libs-flag) +dnl +AC_DEFUN([PECL_CHECK_CONFIG], [ + PECL_SAVE_ENV([CPPFLAGS], [$1]) + PECL_SAVE_ENV([LDFLAGS], [$1]) + PECL_SAVE_ENV([LIBS], [$1]) + + + AC_MSG_CHECKING([for $1]) + ifelse($2, [$PKG_CONFIG $1], [ + AC_CACHE_VAL(PECL_CACHE_VAR([$1_exists]), [ + if $($2 --exists); then + PECL_CACHE_VAR([$1_exists])=yes + else + PECL_CACHE_VAR([$1_exists])=no + fi + ]) + if $PECL_CACHE_VAR([$1_exists]); then + ]) + AC_CACHE_VAL(PECL_CACHE_VAR([$1_version]), [ + PECL_CACHE_VAR([$1_version])=$($2 $3) + ]) + PECL_CHECKED_VERSION([$1])=$PECL_CACHE_VAR([$1_version]) + AC_CACHE_VAL(PECL_CACHE_VAR([$1_cppflags]), [ + PECL_CACHE_VAR([$1_cppflags])=$($2 $4) + ]) + CPPFLAGS=$PECL_CACHE_VAR([$1_cppflags]) + AC_CACHE_VAL(PECL_CACHE_VAR([$1_ldflags]), [ + PECL_CACHE_VAR([$1_ldflags])=$($2 $5) + ]) + LDFLAGS=$PECL_CACHE_VAR([$1_ldflags]) + AC_CACHE_VAL(PECL_CACHE_VAR([$1_libs]), [ + PECL_CACHE_VAR([$1_libs])=$($2 $6) + ]) + LIBS=$PECL_CACHE_VAR([$1_libs]) + PECL_EVAL_LIBLINE([$LDFLAGS $LIBS]) + ifelse($2, [$PKG_CONFIG $1], [ + fi + ]) + + if test -n "$PECL_CHECKED_VERSION([$1])"; then + PECL_VAR([HAVE_$1])=true + PECL_DEFINE([HAVE_$1]) + PECL_DEFINE_UQ([$1_VERSION], "$PECL_CHECKED_VERSION([$1])") + else + PECL_VAR([HAVE_$1])=false + fi + + AC_MSG_RESULT([${PECL_CHECKED_VERSION([$1]):-no}]) +]) +dnl +dnl PECL_CHECK_PKGCONFIG(pkg[, additional-pkg-config-path]) +dnl +AC_DEFUN([PECL_CHECK_PKGCONFIG], [dnl + AC_REQUIRE([PECL_PROG_PKGCONFIG])dnl + ifelse($2,,, [ + PECL_SAVE_VAR(pkgconfig_path)="$PKG_CONFIG_PATH" + if test -d "$2"; then + export PKG_CONFIG_PATH="$2/lib/pkgconfig:$PKG_CONFIG_PATH" + fi + ]) + PECL_CHECK_CONFIG([$1], [$PKG_CONFIG $1], [--modversion], [--cflags-only-I], [--libs-only-L], [--libs-only-l]) + ifelse($2,,, [ + PKG_CONFIG_PATH="$PECL_SAVE_VAR(pkgconfig_path)" + ]) +]) +dnl +dnl PECL_CHECK_DONE(name, success[, incline, libline]) +dnl +AC_DEFUN([PECL_CHECK_DONE], [ + if $2; then + incline=$CPPFLAGS + libline="$LDFLAGS $LIBS" + PECL_DEFINE([HAVE_$1]) + else + incline=$3 + libline=$4 + fi + + PECL_RESTORE_ENV([CPPFLAGS], [$1]) + PECL_RESTORE_ENV([LDFLAGS], [$1]) + PECL_RESTORE_ENV([LIBS], [$1]) + + PHP_EVAL_INCLINE([$incline]) + PHP_EVAL_LIBLINE([$libline], AS_TR_CPP(PECL_NAME[_SHARED_LIBADD])) +]) + +dnl +dnl PECL_CHECK_CA([additional-ca-paths,[ additional-ca-bundles]]) +dnl +AC_DEFUN([PECL_CHECK_CA], [ + AC_CACHE_CHECK([for default CA path], PECL_CACHE_VAR([CAPATH]), [ + PECL_VAR([CAPATH])= + for ca_path in $1 \ + /etc/ssl/certs \ + /System/Library/OpenSSL + do + # check if it's actually a hashed directory + if test -d "$ca_path" && ls "$ca_path"/@<:@0-9a-f@:>@@<:@0-9a-f@:>@@<:@0-9a-f@:>@@<:@0-9a-f@:>@@<:@0-9a-f@:>@@<:@0-9a-f@:>@@<:@0-9a-f@:>@@<:@0-9a-f@:>@.0 >/dev/null 2>&1; then + PECL_CACHE_VAR([CAPATH])=$ca_path + break + fi + done + ]) + if test -n "$PECL_CACHE_VAR([CAPATH])"; then + PECL_DEFINE_SH([CAPATH], "$PECL_CACHE_VAR([CAPATH])") + fi + + AC_CACHE_CHECK([for default CA info], PECL_CACHE_VAR([CAINFO]), [ + for ca_info in $2 \ + /etc/ssl/{cert,ca-bundle}.pem \ + /{etc,usr/share}/ssl/certs/ca-{bundle,ceritifcates}.crt \ + /etc/{pki/ca-trust,ca-certificates}/extracted/pem/tls-ca-bundle.pem \ + /etc/pki/tls/certs/ca-bundle{,.trust}.crt \ + /usr/local/etc/{,open}ssl/cert.pem \ + /usr/local/share/certs/ca-root-nss.crt \ + /{usr,usr/local,opt}/local/share/curl/curl-ca-bundle.crt + do + if test -f "$ca_info"; then + PECL_CACHE_VAR([CAINFO])=$ca_info + break + fi + done + ]) + if test -n "$PECL_CACHE_VAR([CAINFO])"; then + PECL_DEFINE_SH([CAINFO], "$PECL_CACHE_VAR([CAINFO])") + fi +]) diff --git a/autoconf/php-executable.m4 b/autoconf/php-executable.m4 new file mode 100644 index 0000000..2e6e16e --- /dev/null +++ b/autoconf/php-executable.m4 @@ -0,0 +1,7 @@ +dnl +dnl Generate run-php bash script +dnl +AC_CONFIG_COMMANDS_POST([ + rm -f build/php + ln -s "$PHP_EXECUTABLE" build/php +]) diff --git a/build-packagexml.php b/build-packagexml.php new file mode 100644 index 0000000..0784030 --- /dev/null +++ b/build-packagexml.php @@ -0,0 +1,453 @@ + + * @license PHP License + * @license MIT + */ + +declare (strict_types = 1); + +set_exception_handler(function (Throwable $exception): void { + $msg = ($exception instanceof RuntimeException ? "" : "Unexpected error: ") . $exception->getMessage(); + + fwrite(STDERR, "\033[0;31m" /* red */ . $msg . "\033[0m" . "\n"); + exit(1); +}); + +if (!extension_loaded('dom') || !extension_loaded('simplexml') || !extension_loaded('spl')) { + throw new RuntimeException("Following extensions are required: DOM, SimpleXML and SPL"); +} + +// 1. Load package.xml and create release +$package = (function (): PackageXMLElement { + return file_exists('package.xml') + ? simplexml_load_file('package.xml', PackageXMLElement::class) + : PackageXMLElement::create(); +})(); + +$release = new Release(); + +// 2. Process php_{extname}.h +(function () use ($package, $release): void { + $extname = (string)$package->name ?: getenv('PHP_PECL_EXTENSION') ?: null; + + if ($extname !== null) { + $filename = "php_{$extname}.h"; + + if (!file_exists($filename)) { + throw new RuntimeException("{$filename} not found"); + } + } else { + $filename = glob('php_*.h')[0] ?? null; + + if ($filename === null) { + throw new RuntimeException("Couldn't find the main header file (php_*.h)"); + } + + $extname = preg_replace('/^php_(.+)\.h$/', '$1', $filename); + } + + if ((string)$package->name === '') { + $package->name = $extname; + } + + + $macroPrefix = strtoupper(pathinfo($filename, PATHINFO_FILENAME)); + $contents = file_get_contents($filename); + + preg_match_all("/^[ \t]*#define\s+{$macroPrefix}_(?\w+)[ \t]+\"(?.+)\"/m", $contents, $matches, + PREG_PATTERN_ORDER); + $macros = array_combine($matches['key'], $matches['value']); + + // Package name + if (isset($macros['EXTNAME'])) { + if ((string)$package->name !== '' && (string)$package->name !== $macros['EXTNAME']) { + throw new RuntimeException("Package name '{$package->name}' (package.xml) doesn't match " + . "{$macroPrefix}_EXTNAME '{$macros['EXTNAME']}' ($filename)"); + } + } + + // Release version + if (!isset($macros['VERSION'])) { + throw new RuntimeException("$filename does not contain {$macroPrefix}_VERSION macro"); + } + $release->version = $macros['VERSION']; + + if (strpos($release->version, 'dev') !== false) { + throw new RuntimeException("Development versions shouldn't be released ({$macros['VERSION']}). " + . "Please change {$macroPrefix}_VERSION in $filename."); + } + if ($release->existsIn($package->changelog)) { + throw new RuntimeException("Version {$macros['VERSION']} already released. " + . "Please change {$macroPrefix}_VERSION in $filename."); + } +})(); + +// 3. Copy info from package to release +(function () use ($package, $release): void { + $release->license = (string)$package->license; + $release->licenseUri = (string)$package->license['uri']; + + if (strpos($release->version, 'alpha') !== false) { + $release->stability = 'alpha'; + } else if (strpos($release->version, 'beta') !== false || strpos($release->version, 'RC') !== false) { + $release->stability = 'beta'; + } else if (substr($release->version, 0, 1) === '0') { + $release->stability = ((string)$package->stability->release) ?: 'alpha'; + } else { + $release->stability = 'stable'; + } + + if ($release->isMajor((string)$package->version->release)) { + $release->apiVersion = $release->version; + $release->apiStability = $release->stability; + } else { + $release->apiVersion = (string)$package->version->api; + $release->apiStability = (string)$package->stability->api; + } +})(); + +// 4. Get release notes from stdin +$release->notes = (function (string $name, string $version): string { + if (function_exists('posix_isatty') && posix_isatty(STDIN)) { + fwrite(STDOUT, "Enter the release notes for $name $version (end with Ctrl-D):\n"); + } + + $notes = ''; + + do { + $notes .= fread(STDIN, 1024); + } while (!feof(STDIN)); + + return trim($notes); +})((string)$package->name, $release->version); + +// 5. Add release to package +if (!isset($package->changelog)) { + $package->addChild('changelog'); +} +$release->update($package->changelog->prependChild('release')); +$release->update($package); + +// 6. Removes files that no longer exist from package.xml +(function () use ($package): void { + $fileElements = $package->xpath('p:contents//p:file'); + + foreach ($fileElements as $element) { + $path = join("/", array_map('strval', $element->xpath('ancestor-or-self::*[@name!="/"]/@name'))); + + if (!file_exists($path)) { + unset($element[0]); + } + }; +})(); + +// 7. Add new files to package.xml +(function () use ($package): void { + $ext = ['c', 'h', 'phpt']; + + $dir = new RecursiveDirectoryIterator('.', FilesystemIterator::SKIP_DOTS | FilesystemIterator::CURRENT_AS_PATHNAME); + $itDir = new RecursiveIteratorIterator($dir); + $itReg = new RegexIterator($itDir, '~^./(.+\.(?:' . join('|', $ext) . '))$~', RegexIterator::GET_MATCH); + $files = iterable_column($itReg, 1); + + $newFiles = new CallbackFilterIterator($files, function ($path) use ($package): bool { + $file = basename($path); + $dirs = dirname($path) !== '.' ? explode('/', dirname($path)) : []; + array_unshift($dirs, '/'); + + $xpath = 'p:dir[@name="' . join('"]/p:dir[@name="', $dirs) . '"]/p:file[@name="' . $file . '"]'; + return count($package->contents->xpath($xpath)) === 0; + }); + + if (is_dir('.git')) { + $newFiles = gitignore_filter($newFiles); + } + + foreach ($newFiles as $file) { + $dirs = dirname($file) !== '.' ? explode('/', dirname($file)) : []; + + $dirElement = array_reduce($dirs, function (SimpleXMLElement $parent, string $dir): SimpleXMLElement { + $cur = $parent->xpath('p:dir[@name="' . $dir . '"]')[0] ?? null; + + if ($cur === null) { + $cur = $parent->addChild('dir'); + $cur['name'] = $dir; + } + + return $cur; + }, $package->contents->dir[0]); + + $fileElement = $dirElement->addChild('file'); + $fileElement['name'] = basename($file); + $fileElement['role'] = pathinfo($file, PATHINFO_EXTENSION) === 'phpt' ? 'test' : 'src'; + } +})(); + +// 8. Remove empty dirs from package.xml +(function () use ($package): void { + $emptyDirs = $package->xpath('p:contents//p:dir[not(descendant::*[local-name()="file"])]'); + + foreach (array_reverse($emptyDirs) as $element) { // reverse so children are deleted first + unset($element[0]); + } +})(); + +// 9. Sort files +(function () use ($package): void { + $dirElements = $package->xpath('p:contents//p:dir'); + + $sorter = function(SimpleXMLElement $first, SimpleXMLElement $second): int { + return + (($first->getName() !== 'file') <=> ($second->getName() !== 'file')) ?: + strcasecmp((string)$first['name'], (string)$second['name']); + }; + + foreach ($dirElements as $element) { + $element->sort($sorter); + } +})(); + +// 10. Save package.xml +$package->asXML('package.xml'); + +// :) friendly message +echo "\033[0;32m", /* green */ "Updated package.xml for {$package->name} {$release->version}", "\033[0;0m", "\n"; + +// ================= classes and functions ==================== + +class Release +{ + public $version; + public $apiVersion; + public $stability; + public $apiStability; + + public $license; + public $licenseUri; + public $notes; + + public function isMajor(string $oldVersion): bool + { + if ($oldVersion === '') { + return true; + } + + [$major, $minor] = explode('.', $this->version); + [$oldMajor, $oldMinor] = explode('.', $oldVersion); + + return ($major !== $oldMajor) || ($major === '0' && $minor !== $oldMinor); + } + + public function existsIn(SimpleXMLElement $changelog): bool + { + if (!isset($changelog->release)) { + return false; + } + + foreach ($changelog->release as $release) { + if ((string)($release->version->release) === $this->version) { + return true; + } + } + + return false; + } + + public function update(SimpleXMLElement $element) + { + $noTime = isset($element->date) && !isset($element->time); + + $element->date = date('Y-m-d'); + if (!$noTime) { + $element->time = date('H:i:s'); + } + + $element->version->release = $this->version; + $element->version->api = $this->apiVersion; + $element->stability->release = $this->stability; + $element->stability->api = $this->apiStability; + + $element->license = $this->license; + $element->license['uri'] = $this->licenseUri; + + $indent = str_repeat(' ', count($element->xpath('ancestor::*')) + 1); + $element->notes = "\n" . $this->notes . "\n$indent"; + } +}; + +class PackageXMLElement extends SimpleXMLElement +{ + private const BLANK_PACKAGE = << + + + pecl.php.net + + + + + + + yes + + + + + + + + + + + MIT License + + + + + + + + + + + + + + + + + + 7.0.0 + + + 1.4.3 + + + + + + + +XML; + + public static function create(): self + { + return new static(self::BLANK_PACKAGE); + } + + public function prependChild($name, $value = '') + { + $dom = dom_import_simplexml($this); + + $new = $dom->insertBefore( + $dom->ownerDocument->createElement($name, $value), + $dom->firstChild + ); + + return simplexml_import_dom($new, __CLASS__); + } + + public function sort(callable $sorter): void + { + if ($this->count() === 0) { + return; + } + + $children = iterator_to_array($this->children(), false); + usort($children, $sorter); + + $dom = dom_import_simplexml($this); + + while ($dom->hasChildNodes()) { + $dom->removeChild($dom->firstChild); + } + + foreach ($children as $child) { + $dom->appendChild(dom_import_simplexml($child)); + } + } + + public function asXML($filename = null) + { + $dom = new DOMDocument("1.0"); + $dom->preserveWhiteSpace = false; + $dom->formatOutput = true; + $dom->loadXML(parent::asXML()); + + $xml = $dom->saveXML(); + + // Go from 2 space indentation to 1. + $xml = preg_replace('~^([ ]+)\1<(?!/notes>)~m', '\1<', $xml); + + if ($filename === null) { + return $xml; + } + + if (file_exists($filename)) { + rename($filename, $filename . '~'); + } + file_put_contents($filename, $xml); + } + + public function xpath($xpath) + { + $this->registerXPathNamespace('p', 'http://pear.php.net/dtd/package-2.0'); + + return parent::xpath($xpath); + } +} + +function iterable_column(iterable $iterable, $column): Generator +{ + foreach ($iterable as $key => $item) { + yield $key => $item[$column]; + } +} + +function gitignore_filter(iterable $files): Generator +{ + $spec = [ + ["pipe", "r"], + ["pipe", "w"], + ["pipe", "w"], + ]; + + $process = proc_open('git check-ignore --non-matching --verbose --stdin', $spec, $pipes); + stream_set_timeout($pipes[1], 1); + stream_set_blocking($pipes[2], false); + + foreach ($files as $file) { + fwrite($pipes[0], $file . "\n"); + $line = fgets($pipes[1], 1024); + + if ($line === false || substr($line, 0, 2) === '::') { + yield $file; + } + } + + $err = fread($pipes[2], 10000); + + fclose($pipes[0]); + fclose($pipes[1]); + fclose($pipes[2]); + + $ret = proc_close($process); + + if ($ret !== 0 && $err !== '') { + fwrite(STDERR, "\033[0;31m" /* red */ . $err . "\033[0m" /* no color */ . "\n"); + throw new RuntimeException("Checking .gitignore failed"); + } +} + diff --git a/composer.json b/composer.json deleted file mode 100644 index 17f6005..0000000 --- a/composer.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "require-dev": { - "phpunit/phpunit": "^7.5", - "php-http/psr7-integration-tests": "dev-master", - "http-interop/http-factory-tests": "dev-master" - } -} - diff --git a/config.m4 b/config.m4 index fd7df05..6bfded3 100644 --- a/config.m4 +++ b/config.m4 @@ -1,14 +1,19 @@ dnl $Id$ dnl config.m4 for extension http_message -PHP_ARG_WITH(http_message, for http_message support, -[ --with-http_message Include http_message support]) +sinclude(./autoconf/pecl.m4) +sinclude(./autoconf/php-executable.m4) + +PECL_INIT([http_message]) + +PHP_ARG_ENABLE(http-message, whether to enable http-message, [ --enable-http-message Enable http_message]) if test "$PHP_HTTP_MESSAGE" != "no"; then - # TODO: Load external depenencies here + PECL_HAVE_PHP_EXT([psr], [PECL_HAVE_PHP_EXT_HEADER([psr])], [AC_MSG_ERROR([please install and enable the psr extension])]) - AC_DEFINE(HAVE_HTTP_MESSAGE, 1, [Whether you have http_message support]) - PHP_NEW_EXTENSION(http_message, http_message.c message.c request.c server_request.c response.c stream.c uri.c, - $ext_shared) -fi + AC_DEFINE(HAVE_HTTP_MESSAGE, 1, [Whether you have http_message]) + PHP_NEW_EXTENSION(http_message, http_message.c message.c request.c server_request.c response.c stream.c uri.c uploaded_file.c factory.c emitter.c, $ext_shared) + PHP_ADD_MAKEFILE_FRAGMENT + PHP_INSTALL_HEADERS([ext/http_message], [php_http_message.h]) +fi diff --git a/config.w32 b/config.w32 index 4feb5bc..13006d3 100644 --- a/config.w32 +++ b/config.w32 @@ -1,13 +1,10 @@ // $Id$ // vim:ft=javascript -ARG_WITH("http_message", "Include http_message support", "no"); +ARG_ENABLE("http-message", "enable http_message", "no"); if (PHP_HTTP_MESSAGE != "no") { - if (true /* TODO: check external libs */) { - EXTENSION("http_message", "http_message.c message.c request.c server_request.c response.c stream.c uri.c"); - AC_DEFINE('HAVE_HTTP_MESSAGE', 1 , 'Have http_message support'); - PHP_INSTALL_HEADERS("ext/http_message/", "php_http_message.h"); - } + EXTENSION("http_message", "http_message.c message.c request.c server_request.c response.c stream.c uri.c uploaded_file.c factory.c emitter.c"); + AC_DEFINE('HAVE_HTTP_MESSAGE', 1 , 'enable http_message support'); + PHP_INSTALL_HEADERS("ext/http_message/", "php_http_message.h"); } - diff --git a/emitter.c b/emitter.c new file mode 100644 index 0000000..7003918 --- /dev/null +++ b/emitter.c @@ -0,0 +1,214 @@ +/* + +----------------------------------------------------------------------+ + | HTTP Message PHP extension - Stream class | + +----------------------------------------------------------------------+ + | Copyright (c) 2019 Arnold Daniels | + +----------------------------------------------------------------------+ + | Permission is hereby granted, free of charge, to any person | + | obtaining a copy of this software and associated documentation files | + | (the "Software"), to deal in the Software without restriction, | + | including without limitation the rights to use, copy, modify, merge, | + | publish, distribute, sublicense, and/or sell copies of the Software, | + | and to permit persons to whom the Software is furnished to do so, | + | subject to the following conditions: | + | | + | The above copyright notice and this permission notice shall be | + | included in all copies or substantial portions of the Software. | + | | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | + | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | + | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | + | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | + | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | + | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | + | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | + | SOFTWARE. | + +----------------------------------------------------------------------+ + | Author: Arnold Daniels | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "SAPI.h" +#include "php.h" +#include "macros.h" +#include "response.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "ext/spl/spl_exceptions.h" + +#if HAVE_HTTP_MESSAGE + +zend_class_entry *HttpMessage_Emitter_ce = NULL; + +int assert_no_headers_sent() +{ + if (!SG(headers_sent)) { + return SUCCESS; + } + + const char *file = php_output_get_start_filename(); + int line = php_output_get_start_lineno(); + + if (file != NULL) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Cannot send session cookie - headers already sent by (output started at %s:%d)", file, line); + } else { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot send session cookie - headers already sent"); + } + + return FAILURE; +} + +int read_response_body(zval *response, zval *contents) +{ + zval body; + + ZVAL_NULL(&body); + ZVAL_NULL(contents); + + zend_call_method_with_0_params(PROPERTY_ARG(response), NULL, NULL, "getBody", &body); + + if (EXPECTED(Z_TYPE(body) == IS_OBJECT)) { + zend_call_method_with_0_params(PROPERTY_ARG(&body), NULL, NULL, "__toString", contents); + } + + if (UNEXPECTED(Z_TYPE_P(contents) != IS_STRING)) { + return FAILURE; // Something went wrong, assuming an error was already thrown. + } + + return SUCCESS; +} + +void emit_status(zval *response) +{ + zval version, status_code, reason_phrase; + zend_long code; + char *phrase = NULL; + size_t phrase_len = 0; + sapi_header_line ctr = {NULL, 0, 0}; + + ZVAL_NULL(&status_code); + ZVAL_NULL(&reason_phrase); + + zend_call_method_with_0_params(PROPERTY_ARG(response), NULL, NULL, "getProtocolVersion", &version); + zend_call_method_with_0_params(PROPERTY_ARG(response), NULL, NULL, "getStatusCode", &status_code); + zend_call_method_with_0_params(PROPERTY_ARG(response), NULL, NULL, "getReasonPhrase", &reason_phrase); + + code = Z_LVAL(status_code); + + if (Z_STRLEN(reason_phrase) > 0) { + phrase = Z_STRVAL(reason_phrase); + phrase_len = Z_STRLEN(reason_phrase); + } else { + phrase = (char *)get_status_string((int)code); + phrase_len = strlen(phrase); + } + + ctr.line_len = Z_STRLEN(version) + phrase_len + 10; + ctr.line = emalloc(ctr.line_len); + zend_sprintf((char *)ctr.line, "HTTP/%.*s %3lu %.*s", (int)Z_STRLEN(version), Z_STRVAL(version), code, (int)phrase_len, phrase); + ctr.response_code = code; + + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + + efree((char *)ctr.line); +} + +void emit_header(zend_string *header_name, zend_array *header_lines) +{ + zval *header_line; + size_t max_header_size = 256; + sapi_header_line ctr = {NULL, 0, 0}; + ctr.line = emalloc(256); + + ZEND_HASH_FOREACH_VAL(header_lines, header_line) { + ctr.line_len = ZSTR_LEN(header_name) + Z_STRLEN_P(header_line) + 2; + + // Reuse ctr: resize if needed. + if (ctr.line_len >= max_header_size) { + efree((char *)ctr.line); + max_header_size = (ctr.line_len + 255) - ((ctr.line_len + 255) % 256); + ctr.line = emalloc(max_header_size); + } + + zend_sprintf((char *)ctr.line, "%s: %s", ZSTR_VAL(header_name), Z_STRVAL_P(header_line)); + sapi_header_op(SAPI_HEADER_ADD, &ctr); + } ZEND_HASH_FOREACH_END(); + + efree((char *)ctr.line); +} + +void emit_headers(zval *response) +{ + zval headers; + zval *header_lines; + zend_string *header_name; + zend_long index; + + zend_call_method_with_0_params(PROPERTY_ARG(response), NULL, NULL, "getHeaders", &headers); + + ZEND_HASH_FOREACH_KEY_VAL(Z_ARR(headers), index, header_name, header_lines) { + if (UNEXPECTED(header_name == NULL)) { + zend_error(E_WARNING, "Unexpected response header key '%ld': header names should not be numeric", index); + continue; + } + + emit_header(header_name, Z_ARR_P(header_lines)); + } ZEND_HASH_FOREACH_END(); +} + +ZEND_BEGIN_ARG_INFO_EX(arginfo_HttpMessageEmitter_emit, 0, 0, 0) + ZEND_ARG_OBJ_INFO(0, serverParams, Psr\\Http\\Message\\ResponseInterface, 0) +ZEND_END_ARG_INFO() + +PHP_METHOD(Emitter, emit) +{ + zval *response = NULL; + zval contents; + + zend_class_entry *response_interface = HTTP_MESSAGE_PSR_INTERFACE("response"); + + if (UNEXPECTED(response_interface == NULL)) { + zend_throw_error(NULL, "Psr\\Http\\Message\\ResponseInterface not found"); + return; + } + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) + Z_PARAM_OBJECT_OF_CLASS(response, response_interface) + ZEND_PARSE_PARAMETERS_END(); + + if (UNEXPECTED(assert_no_headers_sent() == FAILURE)) { + return; + } + + if (UNEXPECTED(read_response_body(response, &contents) == FAILURE)) { + return; + } + + emit_headers(response); + emit_status(response); + zend_write(Z_STRVAL(contents), Z_STRLEN(contents)); +} + +/* Define HttpMessage\Emitter class */ + +static const zend_function_entry methods[] = { + PHP_ME(Emitter, emit, arginfo_HttpMessageEmitter_emit, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +PHP_MINIT_FUNCTION(http_message_emitter) +{ + zend_class_entry ce; + + INIT_NS_CLASS_ENTRY(ce, "HttpMessage", "Emitter", methods); + HttpMessage_Emitter_ce = zend_register_internal_class(&ce); + + return SUCCESS; +} + +#endif diff --git a/factory.c b/factory.c new file mode 100644 index 0000000..003e063 --- /dev/null +++ b/factory.c @@ -0,0 +1,273 @@ +/* + +----------------------------------------------------------------------+ + | HTTP Message PHP extension - Factory methods | + +----------------------------------------------------------------------+ + | Copyright (c) 2019 Arnold Daniels | + +----------------------------------------------------------------------+ + | Permission is hereby granted, free of charge, to any person | + | obtaining a copy of this software and associated documentation files | + | (the "Software"), to deal in the Software without restriction, | + | including without limitation the rights to use, copy, modify, merge, | + | publish, distribute, sublicense, and/or sell copies of the Software, | + | and to permit persons to whom the Software is furnished to do so, | + | subject to the following conditions: | + | | + | The above copyright notice and this permission notice shall be | + | included in all copies or substantial portions of the Software. | + | | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | + | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | + | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | + | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | + | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | + | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | + | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | + | SOFTWARE. | + +----------------------------------------------------------------------+ + | Author: Arnold Daniels | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "php.h" +#include "php_http_message.h" +#include "macros.h" +#include "response.h" +#include "stream.h" +#include "uploaded_file.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_string.h" +#include "ext/psr/psr_http_factory.h" +#include "ext/spl/spl_exceptions.h" + +#if HAVE_HTTP_MESSAGE + +zend_class_entry *HttpMessage_Factory_ce = NULL; + +int uri_param_as_object(zval *uri) +{ + zval uri_str; + zend_class_entry *uri_interface = HTTP_MESSAGE_PSR_INTERFACE("uri"); + + if (uri_interface == NULL) { + zend_throw_error(NULL, "Psr\\Http\\Message\\UriInterface not found"); + return FAILURE; + } + + if (Z_TYPE_P(uri) == IS_STRING) { + ZVAL_COPY(&uri_str, uri); + NEW_OBJECT_CONSTRUCT(uri, HttpMessage_Uri_ce, 1, &uri_str); + } else if (UNEXPECTED(Z_TYPE_P(uri) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(uri), uri_interface))) { + custom_parameter_type_error(1, "a string or object that implements Psr\\Http\\Message\\UriInterface", uri); + return FAILURE; + } + + return SUCCESS; +} + +PHP_METHOD(Factory, createRequest) +{ + zend_string *method = NULL; + zval *uri = NULL; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2) + Z_PARAM_STR(method) + Z_PARAM_ZVAL(uri) + ZEND_PARSE_PARAMETERS_END(); + + if (uri_param_as_object(uri) == FAILURE) return; + + NEW_OBJECT_CONSTRUCT(return_value, HttpMessage_Request_ce, 0); + zend_update_property_str(HttpMessage_Request_ce, PROPERTY_ARG(return_value), ZEND_STRL("method"), method); + zend_update_property(HttpMessage_Request_ce, PROPERTY_ARG(return_value), ZEND_STRL("uri"), uri); +} + +PHP_METHOD(Factory, createResponse) +{ + zend_long code = 200; + zend_bool code_is_null = 0; + zend_string *phrase = NULL; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_EX(code, code_is_null, 0, 0) + Z_PARAM_STR(phrase) + ZEND_PARSE_PARAMETERS_END(); + + NEW_OBJECT_CONSTRUCT(return_value, HttpMessage_Response_ce, 0); + response_set_status(return_value, !code_is_null ? code : 200, phrase); +} + +PHP_METHOD(Factory, createServerRequest) +{ + zend_string *method = NULL; + zval *uri = NULL, *serverParams = NULL; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 3) + Z_PARAM_STR(method) + Z_PARAM_ZVAL(uri) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_EX(serverParams, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + if (uri_param_as_object(uri) == FAILURE) return; + + if (serverParams == NULL) { + NEW_OBJECT_CONSTRUCT(return_value, HttpMessage_ServerRequest_ce, 0); + } else { + NEW_OBJECT_CONSTRUCT(return_value, HttpMessage_ServerRequest_ce, 1, serverParams); + } + + zend_update_property_str(HttpMessage_ServerRequest_ce, PROPERTY_ARG(return_value), ZEND_STRL("method"), method); + zend_update_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(return_value), ZEND_STRL("uri"), uri); +} + +PHP_METHOD(Factory, createStream) +{ + php_stream *stream; + zval resource; + zend_string *content = NULL; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STR(content) + ZEND_PARSE_PARAMETERS_END(); + + if (open_temp_stream(&resource) == FAILURE) return; + + if (content != NULL && ZSTR_LEN(content) > 0) { + php_stream_from_zval(stream, &resource); + php_stream_write(stream, ZSTR_VAL(content), ZSTR_LEN(content)); + } + + NEW_OBJECT_CONSTRUCT(return_value, HttpMessage_Stream_ce, 1, &resource); +} + +PHP_METHOD(Factory, createStreamFromFile) +{ + char *file = NULL, *mode = NULL; + size_t file_len = 0, mode_len = 0; + php_stream *stream; + zval resource; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 2) + Z_PARAM_STRING(file, file_len) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(mode, mode_len) + ZEND_PARSE_PARAMETERS_END(); + + file[file_len] = '\0'; + stream = php_stream_open_wrapper(file, mode != NULL ? mode : "r", 0, NULL); + + if (stream == NULL) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Failed to open '%s' stream", file); + return; + } + + php_stream_to_zval(stream, &resource); + NEW_OBJECT_CONSTRUCT(return_value, HttpMessage_Stream_ce, 1, &resource); +} + +PHP_METHOD(Factory, createStreamFromResource) +{ + zval *resource = NULL; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) + Z_PARAM_ZVAL(resource) + ZEND_PARSE_PARAMETERS_END(); + + NEW_OBJECT_CONSTRUCT(return_value, HttpMessage_Stream_ce, 1, resource); +} + +PHP_METHOD(Factory, createUploadedFile) +{ + zval *stream = NULL; + zend_long size = -1, error = 0; + zend_string *clientFilename = NULL, *clientMediaType = NULL; + zend_bool size_is_null = 1; + zend_class_entry *stream_interface = HTTP_MESSAGE_PSR_INTERFACE("stream"); + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 5) + Z_PARAM_OBJECT_OF_CLASS(stream, stream_interface) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_EX(size, size_is_null, 1, 0) + Z_PARAM_LONG(error) + Z_PARAM_STR_EX(clientFilename, 1, 0) + Z_PARAM_STR_EX(clientMediaType, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + NEW_OBJECT(return_value, HttpMessage_UploadedFile_ce); + construct_uploaded_file(return_value, stream, NULL, size_is_null ? -1 : size, error, clientFilename, + clientMediaType, -1); +} + +PHP_METHOD(Factory, createUri) +{ + zend_string *uri = NULL; + zval zuri; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STR_EX(uri, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + if (uri == NULL) { + NEW_OBJECT_CONSTRUCT(return_value, HttpMessage_Uri_ce, 0); + } else { + ZVAL_STR(&zuri, uri); + NEW_OBJECT_CONSTRUCT(return_value, HttpMessage_Uri_ce, 1, &zuri); + } +} + +/* Define HttpMessage\Factory class */ + +static const zend_function_entry methods[] = { + HTTP_MESSAGE_ME_EX(Factory, RequestFactory, createRequest) + HTTP_MESSAGE_ME_EX(Factory, ResponseFactory, createResponse) + HTTP_MESSAGE_ME_EX(Factory, ServerRequestFactory, createServerRequest) + HTTP_MESSAGE_ME_EX(Factory, StreamFactory, createStream) + HTTP_MESSAGE_ME_EX(Factory, StreamFactory, createStreamFromFile) + HTTP_MESSAGE_ME_EX(Factory, StreamFactory, createStreamFromResource) + HTTP_MESSAGE_ME_EX(Factory, UploadedFileFactory, createUploadedFile) + HTTP_MESSAGE_ME_EX(Factory, UriFactory, createUri) + PHP_FE_END +}; + +PHP_MINIT_FUNCTION(http_message_factory) +{ + zend_class_entry ce; + zend_class_entry *request_factory = HTTP_MESSAGE_PSR_INTERFACE("requestfactory"); + zend_class_entry *response_factory = HTTP_MESSAGE_PSR_INTERFACE("responsefactory"); + zend_class_entry *serverrequest_factory = HTTP_MESSAGE_PSR_INTERFACE("serverrequestfactory"); + zend_class_entry *stream_factory = HTTP_MESSAGE_PSR_INTERFACE("streamfactory"); + zend_class_entry *uploadedfile_factory = HTTP_MESSAGE_PSR_INTERFACE("uploadedfilefactory"); + zend_class_entry *uri_factory = HTTP_MESSAGE_PSR_INTERFACE("urifactory"); + + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND_EX(request_factory, "Factory", "RequestFactory"); + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND_EX(response_factory, "Factory", "ResponseFactory"); + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND_EX(serverrequest_factory, "Factory", "ServerRequestFactory"); + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND_EX(stream_factory, "Factory", "StreamFactory"); + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND_EX(uploadedfile_factory, "Factory", "UploadedFileFactory"); + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND_EX(uri_factory, "Factory", "UriFactory"); + + if (UNEXPECTED( + HttpMessage_Request_ce == NULL || HttpMessage_Response_ce == NULL || HttpMessage_ServerRequest_ce == NULL || + HttpMessage_Stream_ce == NULL || HttpMessage_UploadedFile_ce == NULL || HttpMessage_Uri_ce == NULL + )) { + return FAILURE; + } + + INIT_NS_CLASS_ENTRY(ce, "HttpMessage", "Factory", methods); + + HttpMessage_Factory_ce = zend_register_internal_class(&ce); + zend_class_implements(HttpMessage_Factory_ce, 6, request_factory, response_factory, serverrequest_factory, + stream_factory, uploadedfile_factory, uri_factory); + + return SUCCESS; +} + +#endif diff --git a/http_message.c b/http_message.c index c8463d7..76ec02c 100644 --- a/http_message.c +++ b/http_message.c @@ -40,8 +40,31 @@ #if HAVE_HTTP_MESSAGE +static const zend_module_dep deps[] = { + ZEND_MOD_REQUIRED("psr") + {NULL, NULL, NULL} +}; + +PHP_MINIT_FUNCTION(http_message) +{ + int success = + PHP_MINIT(http_message_message)(INIT_FUNC_ARGS_PASSTHRU) + + PHP_MINIT(http_message_request)(INIT_FUNC_ARGS_PASSTHRU) + + PHP_MINIT(http_message_serverrequest)(INIT_FUNC_ARGS_PASSTHRU) + + PHP_MINIT(http_message_response)(INIT_FUNC_ARGS_PASSTHRU) + + PHP_MINIT(http_message_stream)(INIT_FUNC_ARGS_PASSTHRU) + + PHP_MINIT(http_message_uri)(INIT_FUNC_ARGS_PASSTHRU) + + PHP_MINIT(http_message_uploadedfile)(INIT_FUNC_ARGS_PASSTHRU) + + PHP_MINIT(http_message_factory)(INIT_FUNC_ARGS_PASSTHRU) + + PHP_MINIT(http_message_emitter)(INIT_FUNC_ARGS_PASSTHRU); + + return ZEND_NORMALIZE_BOOL(success); +} + zend_module_entry http_message_module_entry = { - STANDARD_MODULE_HEADER, + STANDARD_MODULE_HEADER_EX, + NULL, + deps, PHP_HTTP_MESSAGE_EXTNAME, NULL, PHP_MINIT(http_message), @@ -53,18 +76,6 @@ zend_module_entry http_message_module_entry = { STANDARD_MODULE_PROPERTIES }; -PHP_MINIT_FUNCTION(http_message) -{ - PHP_MINIT(http_message_message)(INIT_FUNC_ARGS_PASSTHRU); - PHP_MINIT(http_message_request)(INIT_FUNC_ARGS_PASSTHRU); - PHP_MINIT(http_message_serverrequest)(INIT_FUNC_ARGS_PASSTHRU); - PHP_MINIT(http_message_response)(INIT_FUNC_ARGS_PASSTHRU); - PHP_MINIT(http_message_stream)(INIT_FUNC_ARGS_PASSTHRU); - PHP_MINIT(http_message_uri)(INIT_FUNC_ARGS_PASSTHRU); - - return SUCCESS; -} - #ifdef COMPILE_DL_HTTP_MESSAGE ZEND_GET_MODULE(http_message) #endif diff --git a/macros.h b/macros.h index 43ade64..4be105b 100644 --- a/macros.h +++ b/macros.h @@ -1,6 +1,7 @@ /* +----------------------------------------------------------------------+ | HTTP Message PHP extension | + | Generic macros for this library | +----------------------------------------------------------------------+ | Copyright (c) 2019 Arnold Daniels | +----------------------------------------------------------------------+ @@ -32,17 +33,120 @@ #ifndef HTTP_MESSAGE_MACROS_H #define HTTP_MESSAGE_MACROS_H +#define EXPAND( x ) x + +#if PHP_VERSION_ID < 80000 +#define PROPERTY_ARG(zv) (zv) +#else +#define PROPERTY_ARG(zv) Z_OBJ_P(zv) +#endif + ZEND_BEGIN_ARG_INFO_EX(arginfo_none, 0, 0, 0) ZEND_END_ARG_INFO() #define INIT_ARRAY_PROPERTY(className, property, rv) \ - array_init(zend_read_property(className, getThis(), ZEND_STRL(property), 0, &rv)); + array_init(zend_read_property(className, PROPERTY_ARG(getThis()), ZEND_STRL(property), 0, &rv)) + +#define SET_ARRAY_PROPERTY(className, property, zval, rv) \ + if (zval == NULL) array_init(zend_read_property(className, PROPERTY_ARG(getThis()), ZEND_STRL(property), 0, &rv)); \ + else zend_update_property(className, PROPERTY_ARG(getThis()), ZEND_STRL(property), zval) + +#define SET_STRING_PROPERTY(className, property, val) \ + if (val != NULL) zend_update_property_stringl(className, PROPERTY_ARG(getThis()), ZEND_STRL(property), val, strlen(val)) -#define SET_STRING_PROPERTY(className, property, value) \ - zend_update_property_stringl(className, getThis(), ZEND_STRL(property), value ?: "", value ? strlen(value) : 0); +#define SET_STR_PROPERTY(className, property, val) \ + if (val != NULL) zend_update_property_str(className, PROPERTY_ARG(getThis()), ZEND_STRL(property), val) + +#if PHP_VERSION_ID < 70300 +#define SET_URI_PROPERTY(className, property, val) SET_STRING_PROPERTY(className, property, val) +#else +#define SET_URI_PROPERTY(className, property, val) SET_STR_PROPERTY(className, property, val) +#endif + +#define HTTP_MESSAGE_ME_EX(className, interfaceName, method) \ + PHP_ME(className, method, arginfo_PsrHttpMessage ## interfaceName ## Interface_ ## method, ZEND_ACC_PUBLIC) #define HTTP_MESSAGE_ME(className, method) \ - PHP_ME(className, method, arginfo_PsrHttpMessage ## className ## Interface_ ## method, ZEND_ACC_PUBLIC) + HTTP_MESSAGE_ME_EX(className, className, method) + +#define HTTP_MESSAGE_PSR_INTERFACE(interfaceName) \ + get_internal_ce(ZEND_STRL("psr\\http\\message\\" interfaceName "interface")) + +#define NEW_OBJECT(zval, ce) \ + object_init_ex(zval, ce); \ + if (EXPECTED(zval != NULL)) object_properties_init(Z_OBJ_P(zval), ce) + +#define NEW_OBJECT_CONSTRUCT(zval, ce, argc, ...) \ + NEW_OBJECT(zval, ce); \ + if (EXPECTED(zval != NULL)) \ + EXPAND(zend_call_method_with_## argc ##_params(PROPERTY_ARG(zval), ce, &ce->constructor, "__construct", NULL, ## __VA_ARGS__)) + +#define IS_STREAM_RESOURCE(zstream) \ + ( \ + Z_TYPE_P(zstream) == IS_RESOURCE && \ + (Z_RES_P(zstream)->type == php_file_le_stream() || Z_RES_P(zstream)->type == php_file_le_pstream()) \ + ) + +#define ARRAY_ADD(arr, index, key) \ + key == NULL ? zend_hash_index_add_empty_element(arr, index) : zend_hash_add_empty_element(arr, key) + +#define ARRAY_GET(arr, index, key) \ + arr != NULL ? (key == NULL ? zend_hash_index_find(arr, index) : zend_hash_find(arr, key)) : NULL + +#define COPY_PROPERTY_FROM_ARRAY(arr, key, object, className, property, type, val) \ + val = zend_hash_str_find(arr, ZEND_STRL(key)); \ + if (val != NULL && EXPECTED(Z_TYPE_P(val) == type)) { \ + zend_update_property(className, PROPERTY_ARG(object), ZEND_STRL(property), val); \ + } + +#define Z_ARR_P_NULL(zval) zval != NULL ? Z_ARR_P(zval) : NULL + +#define Z_STRCMP(zval, match, def) \ + (zval == NULL || Z_TYPE_P(zval) != IS_STRING || Z_STRLEN_P(zval) == 0 ? def : \ + strncmp(match, Z_STRVAL_P(zval), Z_STRLEN_P(zval))) + +#define Z_STRVAL_P_NULL(zval) zval != NULL ? Z_STRVAL_P(zval) : NULL +#define Z_STRLEN_P_NULL(zval) zval != NULL ? Z_STRLEN_P(zval) : 0 + +#define ZSTR_VAL_LEN(zstr) zstr != NULL ? ZSTR_VAL(zstr) : NULL, zstr != NULL ? ZSTR_LEN(zstr) : 0 + +#define STRLEN_NULL(str) str != NULL ? strlen(str) : 0 + +#define STR_CLOSE(str, len) str[len] = '\0' // Is buffer big enough? yolo! + +static zend_always_inline zend_class_entry* get_internal_ce(const char *class_name, size_t class_name_len) +{ + zend_class_entry* temp_ce; + + if ((temp_ce = zend_hash_str_find_ptr(CG(class_table), class_name, class_name_len)) == NULL) { + return NULL; + } + + return temp_ce; +} + +static zend_always_inline void custom_parameter_type_error(int num, char *expected, zval *arg) +{ + const char *space; + const char *class_name; + + if (EG(exception)) { + return; + } + class_name = get_active_class_name(&space); + zend_type_error("%s%s%s() expects parameter %d to be %s, %s given", + class_name, space, get_active_function_name(), num, expected, zend_zval_type_name(arg)); +} + +#define ASSERT_HTTP_MESSAGE_INTERFACE_FOUND_EX(ce, className, psrClassName) \ + if (UNEXPECTED(ce == NULL)) { \ + zend_error(E_CORE_WARNING, \ + "Failed to initialize 'HttpMessage\\%s': 'Psr\\Http\\Message\\%sInterace' not found", \ + className, psrClassName); \ + return FAILURE; \ + } +#define ASSERT_HTTP_MESSAGE_INTERFACE_FOUND(ce, className) \ + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND_EX(ce, className, className) #endif //HTTP_MESSAGE_MACROS_H diff --git a/message.c b/message.c index dd40cfa..5d4d38a 100644 --- a/message.c +++ b/message.c @@ -33,20 +33,43 @@ #endif #include "php.h" -#include "php_ini.h" #include "php_http_message.h" #include "macros.h" #include "zend_exceptions.h" #include "zend_interfaces.h" #include "zend_string.h" -#include "ext/standard/info.h" #include "ext/standard/php_string.h" #include "ext/psr/psr_http_message.h" #if HAVE_HTTP_MESSAGE -zend_class_entry *HttpMessage_Message_ce; +zend_class_entry *HttpMessage_Message_ce = NULL; +void add_header(zval *object, zend_string *name, zend_string *value, zend_bool added) +{ + zval rv, *headers_prop, *header_values; + HashTable *headers; + + headers_prop = zend_read_property(HttpMessage_Message_ce, PROPERTY_ARG(object), ZEND_STRL("headers"), 0, &rv); + + if (UNEXPECTED(Z_TYPE_P(headers_prop) != IS_ARRAY)) { + return; // Shouldn't happen + } + + headers = zend_array_dup(Z_ARR_P(headers_prop)); + + header_values = zend_hash_find(headers, name); + if (header_values == NULL) { + header_values = zend_hash_add_empty_element(headers, name); + array_init(header_values); + } else if (!added) { + ZVAL_DEREF(header_values); + array_init(header_values); + } + add_next_index_str(header_values, zend_string_copy(value)); + + ZVAL_ARR(headers_prop, headers); +} /* __construct */ @@ -55,13 +78,8 @@ PHP_METHOD(Message, __construct) zval rv, *body; /* $this->body = new Stream() */ - body = zend_read_property(HttpMessage_Request_ce, getThis(), ZEND_STRL("body"), 0, &rv); - object_init_ex(body, HttpMessage_Stream_ce); - if (body != NULL && Z_TYPE_P(body) == IS_OBJECT) { /* Should always be true */ - zend_call_method_with_0_params( - body, HttpMessage_Stream_ce, &HttpMessage_Stream_ce->constructor, "__construct", NULL - ); - } + body = zend_read_property(HttpMessage_Message_ce, PROPERTY_ARG(getThis()), ZEND_STRL("body"), 0, &rv); + NEW_OBJECT_CONSTRUCT(body, HttpMessage_Stream_ce, 0); INIT_ARRAY_PROPERTY(HttpMessage_Message_ce, "headers", rv); } @@ -73,25 +91,22 @@ PHP_METHOD(Message, getProtocolVersion) { zval rv, *value; - value = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("protocolVersion"), 0, &rv); + value = zend_read_property(HttpMessage_Message_ce, PROPERTY_ARG(getThis()), ZEND_STRL("protocolVersion"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Message, withProtocolVersion) { - char *value; - size_t value_len; + zend_string *value; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(value, value_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(value) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property_stringl( - HttpMessage_Message_ce, return_value, ZEND_STRL("protocolVersion"), value, value_len - ); + zend_update_property_str(HttpMessage_Message_ce, PROPERTY_ARG(return_value), ZEND_STRL("protocolVersion"), value); } @@ -101,7 +116,7 @@ PHP_METHOD(Message, getHeaders) { zval rv, *headers; - headers = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("headers"), 0, &rv); + headers = zend_read_property(HttpMessage_Message_ce, PROPERTY_ARG(getThis()), ZEND_STRL("headers"), 0, &rv); RETURN_ZVAL(headers, 1, 0); } @@ -109,17 +124,15 @@ PHP_METHOD(Message, getHeaders) PHP_METHOD(Message, hasHeader) { zval rv, *headers; - char *name; - size_t name_len; + zend_string *name; zend_bool exists; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(name, name_len) - ZEND_PARSE_PARAMETERS_END_EX(); - - headers = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("headers"), 0, &rv); + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); - exists = zend_hash_str_exists(Z_ARRVAL_P(headers), name, name_len); + headers = zend_read_property(HttpMessage_Message_ce, PROPERTY_ARG(getThis()), ZEND_STRL("headers"), 0, &rv); + exists = zend_hash_exists(Z_ARRVAL_P(headers), name); RETVAL_BOOL(exists); } @@ -127,18 +140,18 @@ PHP_METHOD(Message, hasHeader) PHP_METHOD(Message, getHeader) { zval rv, *headers, *header_values; - char *name; - size_t name_len; + zend_string *name; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(name, name_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); - headers = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("headers"), 0, &rv); + headers = zend_read_property(HttpMessage_Message_ce, PROPERTY_ARG(getThis()), ZEND_STRL("headers"), 0, &rv); + header_values = zend_hash_find(Z_ARRVAL_P(headers), name); - header_values = zend_hash_str_find(Z_ARRVAL_P(headers), name, name_len); - if (header_values != NULL) { - array_init(header_values); + if (header_values == NULL) { + array_init(return_value); + return; } RETURN_ZVAL(header_values, 1, 0); @@ -146,94 +159,75 @@ PHP_METHOD(Message, getHeader) PHP_METHOD(Message, getHeaderLine) { - zval rv, *headers, *header_values, *header_string; - char *name; - size_t name_len; - zend_string *glue; + zval rv, *headers, *header_values; + zend_string *name, *glue; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(name, name_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); - headers = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("headers"), 0, &rv); - header_values = zend_hash_str_find(Z_ARRVAL_P(headers), name, name_len); + headers = zend_read_property(HttpMessage_Message_ce, PROPERTY_ARG(getThis()), ZEND_STRL("headers"), 0, &rv); + header_values = zend_hash_find(Z_ARRVAL_P(headers), name); - glue = zend_string_init(ZEND_STRL(", "), 0); - php_implode(glue, header_values, header_string); + if (header_values == NULL) { + RETURN_EMPTY_STRING(); + } + glue = zend_string_init(ZEND_STRL(", "), 0); +#if PHP_VERSION_ID < 80000 + php_implode(glue, header_values, return_value); +#else + php_implode(glue, Z_ARRVAL_P(header_values), return_value); +#endif zend_string_free(glue); - - RETURN_ZVAL(header_string, 0, 0) } PHP_METHOD(Message, withHeader) { - zval rv, *headers, new_headers, header_values; - char *name; - size_t name_len; - zend_string *value; + zend_string *name = NULL, *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2) - Z_PARAM_STRING(name, name_len) + Z_PARAM_STR(name) Z_PARAM_STR(value) - ZEND_PARSE_PARAMETERS_END_EX(); - - headers = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("headers"), 0, &rv); - ZVAL_COPY(&new_headers, headers); + ZEND_PARSE_PARAMETERS_END(); - array_init(&header_values); - add_next_index_str(&header_values, value); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - add_assoc_zval_ex(&new_headers, name, name_len, &header_values); - - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); - - zend_update_property(HttpMessage_Message_ce, return_value, ZEND_STRL("headers"), &new_headers); + add_header(return_value, name, value, 0); } PHP_METHOD(Message, withAddedHeader) { - zval rv, *headers, *header_values; - char *name; - size_t name_len; - zend_string *value; + zend_string *name = NULL, *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2) - Z_PARAM_STRING(name, name_len) + Z_PARAM_STR(name) Z_PARAM_STR(value) - ZEND_PARSE_PARAMETERS_END_EX(); - - headers = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("headers"), 0, &rv); + ZEND_PARSE_PARAMETERS_END(); - header_values = zend_hash_str_find(Z_ARRVAL_P(headers), name, name_len); - if (header_values == NULL) { - array_init(header_values); - } - add_next_index_str(header_values, value); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - add_assoc_zval_ex(headers, name, name_len, header_values); - - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); - - zend_update_property(HttpMessage_Message_ce, return_value, ZEND_STRL("headers"), headers); + add_header(return_value, name, value, 1); } PHP_METHOD(Message, withoutHeader) { - zval rv, *headers; - char *name; - size_t name_len; + zval rv, *headers_prop; + HashTable *headers; + zend_string *name = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(name, name_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); - headers = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("headers"), 0, &rv); - zend_hash_str_del(Z_ARRVAL_P(headers), name, name_len); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + headers_prop = zend_read_property(HttpMessage_Message_ce, PROPERTY_ARG(return_value), ZEND_STRL("headers"), 0, &rv); - zend_update_property(HttpMessage_Message_ce, return_value, ZEND_STRL("headers"), headers); + headers = zend_array_dup(Z_ARR_P(headers_prop)); + zend_hash_del(headers, name); + + ZVAL_ARR(headers_prop, headers); } @@ -243,7 +237,7 @@ PHP_METHOD(Message, getBody) { zval rv, *value; - value = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("body"), 0, &rv); + value = zend_read_property(HttpMessage_Message_ce, PROPERTY_ARG(getThis()), ZEND_STRL("body"), 0, &rv); RETURN_ZVAL(value, 1, 0); } @@ -251,14 +245,20 @@ PHP_METHOD(Message, getBody) PHP_METHOD(Message, withBody) { zval *value; + zend_class_entry *stream_interface = HTTP_MESSAGE_PSR_INTERFACE("stream"); + + if (stream_interface == NULL) { + zend_throw_error(NULL, "Psr\\Http\\Message\\StreamInterface not found"); + return; + } ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_OBJECT_OF_CLASS(value, PsrHttpMessageStreamInterface_ce_ptr) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_OBJECT_OF_CLASS(value, stream_interface) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property(HttpMessage_Message_ce, return_value, ZEND_STRL("body"), value); + zend_update_property(HttpMessage_Message_ce, PROPERTY_ARG(return_value), ZEND_STRL("body"), value); } @@ -283,18 +283,22 @@ static const zend_function_entry message_functions[] = { PHP_MINIT_FUNCTION(http_message_message) { zend_class_entry ce; + zend_class_entry *interface = HTTP_MESSAGE_PSR_INTERFACE("message"); + + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND(interface, "Message"); + INIT_NS_CLASS_ENTRY(ce, "HttpMessage", "Message", message_functions); HttpMessage_Message_ce = zend_register_internal_class(&ce); HttpMessage_Message_ce->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; - zend_class_implements(HttpMessage_Message_ce, 1, PsrHttpMessageMessageInterface_ce_ptr); + zend_class_implements(HttpMessage_Message_ce, 1, interface); /* Properties */ zend_declare_property_string( - HttpMessage_Message_ce, ZEND_STRL("protocolVersion"), "1.1", ZEND_ACC_PROTECTED + HttpMessage_Message_ce, ZEND_STRL("protocolVersion"), "1.1", ZEND_ACC_PRIVATE ); - zend_declare_property_null(HttpMessage_Message_ce, ZEND_STRL("headers"), ZEND_ACC_PROTECTED); - zend_declare_property_null(HttpMessage_Message_ce, ZEND_STRL("body"), ZEND_ACC_PROTECTED); + zend_declare_property_null(HttpMessage_Message_ce, ZEND_STRL("headers"), ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_Message_ce, ZEND_STRL("body"), ZEND_ACC_PRIVATE); return SUCCESS; } diff --git a/package.xml b/package.xml new file mode 100644 index 0000000..b6c89f2 --- /dev/null +++ b/package.xml @@ -0,0 +1,394 @@ + + + http_message + pecl.php.net + PSR-7 HTTP Message implementation + PSR-7 compatible HTTP Message implementation as PHP extension + + Arnold Daniels + jasny + jasny@php.net + yes + + 2020-12-27 + + 1.0.0 + 1.0.0 + + + stable + stable + + MIT License + +Release as v1.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.2.0 + + + 1.4.3 + + + psr + 0.6.0 + + + + http_message + + + + 2020-12-27 + + + 1.0.0 + 1.0.0 + + + stable + stable + + MIT License + +Release as v1.0.0 + + + + 2020-08-25 + + + 0.2.2 + 0.2.0 + + + beta + beta + + MIT License + +Fixed HttpMessage\Factory::createUri() + + + + 2019-11-07 + + + 0.2.1 + 0.2.0 + + + beta + beta + + MIT License + +Added `HttpMessage\Emitter`. +Emitter outputs header and body. +XDebug is needed to test emitter. + +Fixed segfault for `withHeader()` when using a non-string var. + + + + 2019-09-05 + + + 0.2.0 + 0.2.0 + + + beta + beta + + MIT License + +Added Factory class that implements all PSR-17 interfaces. +All properties private. +Allow filename and mode for Stream constructor. +Allow StreamInterface object for UploadedFile constructor. +Move uploaded file copies a stream if stream is supplied. +Fixes and cleanup. + + + + 2019-07-28 + + + 0.1.1 + 0.1.0 + + + beta + beta + + MIT License + +Fixed default value issue with ServerRequest::getAttribute(). The method only accepted one argument. + + + + 2019-07-10 + + + 0.1.0 + 0.1.0 + + + beta + beta + + MIT License + +Initial release. + + + + diff --git a/php_http_message.h b/php_http_message.h index 5846a2f..0e6ea6e 100644 --- a/php_http_message.h +++ b/php_http_message.h @@ -31,10 +31,7 @@ #ifndef PHP_HTTP_MESSAGE_H #define PHP_HTTP_MESSAGE_H 1 -/* Temp for cmake */ -#define HAVE_HTTP_MESSAGE 1 - -#define PHP_HTTP_MESSAGE_VERSION "0.0.1" +#define PHP_HTTP_MESSAGE_VERSION "1.0.0" #define PHP_HTTP_MESSAGE_EXTNAME "http_message" #ifdef PHP_WIN32 @@ -45,15 +42,15 @@ # define PHP_HTTP_MESSAGE_API #endif -static PHP_MINFO_FUNCTION(http_message); -static PHP_MINIT_FUNCTION(http_message); - extern PHP_MINIT_FUNCTION(http_message_message); extern PHP_MINIT_FUNCTION(http_message_request); extern PHP_MINIT_FUNCTION(http_message_serverrequest); extern PHP_MINIT_FUNCTION(http_message_response); extern PHP_MINIT_FUNCTION(http_message_stream); extern PHP_MINIT_FUNCTION(http_message_uri); +extern PHP_MINIT_FUNCTION(http_message_uploadedfile); +extern PHP_MINIT_FUNCTION(http_message_factory); +extern PHP_MINIT_FUNCTION(http_message_emitter); extern zend_module_entry http_message_module_entry; @@ -63,6 +60,8 @@ extern zend_class_entry *HttpMessage_ServerRequest_ce; extern zend_class_entry *HttpMessage_Response_ce; extern zend_class_entry *HttpMessage_Stream_ce; extern zend_class_entry *HttpMessage_Uri_ce; +extern zend_class_entry *HttpMessage_UploadedFile_ce; +extern zend_class_entry *HttpMessage_Factory_ce; +extern zend_class_entry *HttpMessage_Emitter_ce; #endif - diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index 813111e..0000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - tests/phpunit/ - - - - diff --git a/request.c b/request.c index 328faf6..63e4af6 100644 --- a/request.c +++ b/request.c @@ -33,42 +33,37 @@ #endif #include "php.h" -#include "php_ini.h" #include "php_http_message.h" #include "macros.h" #include "zend_exceptions.h" #include "zend_interfaces.h" -#include "ext/standard/info.h" +#include "zend_smart_str.h" #include "ext/psr/psr_http_message.h" #if HAVE_HTTP_MESSAGE -zend_class_entry *HttpMessage_Request_ce; +zend_class_entry *HttpMessage_Request_ce = NULL; /* __construct */ +/* unused ZEND_BEGIN_ARG_INFO_EX(arginfo_Request_construct, 0, 0, 0) ZEND_END_ARG_INFO() +*/ PHP_METHOD(Request, __construct) { - zval rv, *uri, uri_string; + zval rv, *uri; /* parent::__construct() */ zend_call_method_with_0_params( - getThis(), HttpMessage_Message_ce, &HttpMessage_Message_ce->constructor, "__construct", NULL + PROPERTY_ARG(getThis()), HttpMessage_Message_ce, &HttpMessage_Message_ce->constructor, "__construct", NULL ); /* $this->uri = new Uri() */ - uri = zend_read_property(HttpMessage_Request_ce, getThis(), ZEND_STRL("uri"), 0, &rv); - object_init_ex(uri, HttpMessage_Uri_ce); - if (uri != NULL && Z_TYPE_P(uri) == IS_OBJECT) { /* Should always be true */ - ZVAL_EMPTY_STRING(&uri_string); - zend_call_method_with_1_params( - uri, HttpMessage_Uri_ce, &HttpMessage_Uri_ce->constructor, "__construct", NULL, &uri_string - ); - } + uri = zend_read_property(HttpMessage_Request_ce, PROPERTY_ARG(getThis()), ZEND_STRL("uri"), 0, &rv); + NEW_OBJECT(uri, HttpMessage_Uri_ce); } @@ -76,27 +71,50 @@ PHP_METHOD(Request, __construct) PHP_METHOD(Request, getRequestTarget) { - zval rv, *value; + zval rv, *value, *uri, path, query; + smart_str buf = {0}; - value = zend_read_property(HttpMessage_Request_ce, getThis(), ZEND_STRL("requestTarget"), 0, &rv); + value = zend_read_property(HttpMessage_Request_ce, PROPERTY_ARG(getThis()), ZEND_STRL("requestTarget"), 0, &rv); - RETURN_ZVAL(value, 1, 0); + if (!ZVAL_IS_NULL(value)) { + RETURN_ZVAL(value, 1, 0); + } + + uri = zend_read_property(HttpMessage_Request_ce, PROPERTY_ARG(getThis()), ZEND_STRL("uri"), 0, &rv); + zend_call_method_with_0_params(PROPERTY_ARG(uri), NULL, NULL, "getPath", &path); + zend_call_method_with_0_params(PROPERTY_ARG(uri), NULL, NULL, "getQuery", &query); + + if (UNEXPECTED(Z_TYPE(path) != IS_STRING) || Z_STRLEN(path) == 0) { + RETURN_STRING("/"); + } + + if (Z_TYPE(query) != IS_STRING || Z_STRLEN(query) == 0) { + RETURN_ZVAL(&path, 1, 0); + } + + smart_str_appendl(&buf, Z_STRVAL(path), Z_STRLEN(path)); + smart_str_appends(&buf, "?"); + smart_str_appendl(&buf, Z_STRVAL(query), Z_STRLEN(query)); + + RETVAL_STR_COPY(buf.s); + zend_string_release(buf.s); } PHP_METHOD(Request, withRequestTarget) { - char *value; - size_t value_len; + zend_string *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(value, value_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR_EX(value, 1, 0) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property_stringl( - HttpMessage_Request_ce, return_value, ZEND_STRL("requestTarget"), value, value_len - ); + if (EXPECTED(value != NULL)) { + zend_update_property_str(HttpMessage_Request_ce, PROPERTY_ARG(return_value), ZEND_STRL("requestTarget"), value); + } else { + zend_update_property_null(HttpMessage_Request_ce, PROPERTY_ARG(return_value), ZEND_STRL("requestTarget")); + } } @@ -106,25 +124,22 @@ PHP_METHOD(Request, getMethod) { zval rv, *value; - value = zend_read_property(HttpMessage_Request_ce, getThis(), ZEND_STRL("method"), 0, &rv); + value = zend_read_property(HttpMessage_Request_ce, PROPERTY_ARG(getThis()), ZEND_STRL("method"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Request, withMethod) { - char *value; - size_t value_len; + zend_string *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(value, value_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(value) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property_stringl( - HttpMessage_Request_ce, return_value, ZEND_STRL("method"), value, value_len - ); + zend_update_property_str(HttpMessage_Request_ce, PROPERTY_ARG(return_value), ZEND_STRL("method"), value); } @@ -134,22 +149,28 @@ PHP_METHOD(Request, getUri) { zval rv, *value; - value = zend_read_property(HttpMessage_Request_ce, getThis(), ZEND_STRL("uri"), 0, &rv); + value = zend_read_property(HttpMessage_Request_ce, PROPERTY_ARG(getThis()), ZEND_STRL("uri"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Request, withUri) { - zval *value; + zval *value = NULL; + zend_class_entry *uri_interface = HTTP_MESSAGE_PSR_INTERFACE("uri"); + + if (uri_interface == NULL) { + zend_throw_error(NULL, "Psr\\Http\\Message\\UriInterface not found"); + return; + } ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_OBJECT_OF_CLASS(value, PsrHttpMessageUriInterface_ce_ptr) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_OBJECT_OF_CLASS(value, uri_interface) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property(HttpMessage_Request_ce, return_value, ZEND_STRL("uri"), value); + zend_update_property(HttpMessage_Request_ce, PROPERTY_ARG(return_value), ZEND_STRL("uri"), value); } @@ -169,15 +190,20 @@ static const zend_function_entry request_functions[] = { PHP_MINIT_FUNCTION(http_message_request) { zend_class_entry ce; + zend_class_entry *interface = HTTP_MESSAGE_PSR_INTERFACE("request"); + + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND(interface, "Request"); + if (HttpMessage_Message_ce == NULL) return FAILURE; + INIT_NS_CLASS_ENTRY(ce, "HttpMessage", "Request", request_functions); HttpMessage_Request_ce = zend_register_internal_class_ex(&ce, HttpMessage_Message_ce); - zend_class_implements(HttpMessage_Request_ce, 1, PsrHttpMessageRequestInterface_ce_ptr); + zend_class_implements(HttpMessage_Request_ce, 1, interface); /* Properties */ - zend_declare_property_string(HttpMessage_Request_ce, ZEND_STRL("requestTarget"), "/", ZEND_ACC_PROTECTED); - zend_declare_property_string(HttpMessage_Request_ce, ZEND_STRL("method"), "", ZEND_ACC_PROTECTED); - zend_declare_property_null(HttpMessage_Request_ce, ZEND_STRL("uri"), ZEND_ACC_PROTECTED); + zend_declare_property_null(HttpMessage_Request_ce, ZEND_STRL("requestTarget"), ZEND_ACC_PRIVATE); + zend_declare_property_string(HttpMessage_Request_ce, ZEND_STRL("method"), "", ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_Request_ce, ZEND_STRL("uri"), ZEND_ACC_PRIVATE); return SUCCESS; } diff --git a/response.c b/response.c index 510ea0a..9787501 100644 --- a/response.c +++ b/response.c @@ -33,43 +33,65 @@ #endif #include "php.h" -#include "php_ini.h" +#include "ext/psr/psr_http_message.h" +#include "ext/spl/spl_exceptions.h" #include "php_http_message.h" #include "macros.h" +#include "response.h" #include "zend_exceptions.h" #include "zend_interfaces.h" -#include "http_status_codes.h" -#include "ext/standard/info.h" -#include "ext/psr/psr_http_message.h" #if HAVE_HTTP_MESSAGE zend_class_entry *HttpMessage_Response_ce; +int response_set_status(zval *obj, zend_long code, zend_string *phrase) +{ + const char *suggested_phrase; -/* Helper function to get reason phrase from status code */ + if (code < 100 || code > 999) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "Invalid HTTP status code %ld", code); + return FAILURE; + } -static int status_comp(const void *a, const void *b) -{ - const http_response_status_code_pair *pa = (const http_response_status_code_pair *) a; - const http_response_status_code_pair *pb = (const http_response_status_code_pair *) b; + zend_update_property_long(HttpMessage_Response_ce, PROPERTY_ARG(obj), ZEND_STRL("statusCode"), code); - if (pa->code < pb->code) { - return -1; - } else if (pa->code > pb->code) { - return 1; + if (phrase != NULL) { + zend_update_property_str(HttpMessage_Response_ce, PROPERTY_ARG(obj), ZEND_STRL("reasonPhrase"), phrase); + } else { + suggested_phrase = get_status_string((int)code); + zend_update_property_stringl( + HttpMessage_Response_ce, PROPERTY_ARG(obj), ZEND_STRL("reasonPhrase"), + suggested_phrase, strlen(suggested_phrase) + ); } - return 0; + return SUCCESS; } -static const char *get_status_string(int code) +/* __construct */ + +/* unused +ZEND_BEGIN_ARG_INFO_EX(arginfo_HttpMessageServerRequest_construct, 0, 0, 0) + ZEND_ARG_TYPE_INFO(0, statusCode, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, reasonPhrase, IS_STRING, 0) +ZEND_END_ARG_INFO() +*/ + +PHP_METHOD(Response, __construct) { - http_response_status_code_pair needle = {code, NULL}, *result = NULL; + zend_long code = 0; + zend_string *phrase = NULL; - result = bsearch(&needle, http_status_map, http_status_map_len, sizeof(needle), status_comp); + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(code); + Z_PARAM_STR_EX(phrase, 1, 0) + ZEND_PARSE_PARAMETERS_END(); - return result ? result->str : ""; + if (code > 0) { + response_set_status(getThis(), code, phrase); + } } @@ -79,7 +101,7 @@ PHP_METHOD(Response, getStatusCode) { zval rv, *value; - value = zend_read_property(HttpMessage_Response_ce, getThis(), ZEND_STRL("statusCode"), 0, &rv); + value = zend_read_property(HttpMessage_Response_ce, PROPERTY_ARG(getThis()), ZEND_STRL("statusCode"), 0, &rv); RETURN_ZVAL(value, 1, 0); } @@ -88,37 +110,25 @@ PHP_METHOD(Response, getReasonPhrase) { zval rv, *value; - value = zend_read_property(HttpMessage_Response_ce, getThis(), ZEND_STRL("reasonPhrase"), 0, &rv); + value = zend_read_property(HttpMessage_Response_ce, PROPERTY_ARG(getThis()), ZEND_STRL("reasonPhrase"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Response, withStatus) { - zend_long code; - char *phrase; - size_t phrase_len; - const char *suggested_phrase; + zend_long code = 0; + zend_string *phrase = NULL; - ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 2) Z_PARAM_LONG(code) - Z_PARAM_STRING(phrase, phrase_len) - ZEND_PARSE_PARAMETERS_END_EX(); - - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + Z_PARAM_OPTIONAL + Z_PARAM_STR_EX(phrase, 1, 0) + ZEND_PARSE_PARAMETERS_END(); - zend_update_property_long(HttpMessage_Response_ce, return_value, ZEND_STRL("statusCode"), code); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - if (phrase_len > 0) { - zend_update_property_stringl( - HttpMessage_Response_ce, return_value, ZEND_STRL("reasonPhrase"), phrase, phrase_len - ); - } else { - suggested_phrase = get_status_string((int)code); - zend_update_property_stringl( - HttpMessage_Response_ce, return_value, ZEND_STRL("reasonPhrase"), ZEND_STRL(suggested_phrase) - ); - } + response_set_status(return_value, code, phrase); } @@ -134,14 +144,19 @@ static const zend_function_entry response_functions[] = { PHP_MINIT_FUNCTION(http_message_response) { zend_class_entry ce; + zend_class_entry *interface = HTTP_MESSAGE_PSR_INTERFACE("response"); + + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND(interface, "Response"); + if (HttpMessage_Message_ce == NULL) return FAILURE; + INIT_NS_CLASS_ENTRY(ce, "HttpMessage", "Response", response_functions); HttpMessage_Response_ce = zend_register_internal_class_ex(&ce, HttpMessage_Message_ce); - zend_class_implements(HttpMessage_Response_ce, 1, PsrHttpMessageResponseInterface_ce_ptr); + zend_class_implements(HttpMessage_Response_ce, 1, interface); /* Properties */ - zend_declare_property_long(HttpMessage_Response_ce, ZEND_STRL("statusCode"), 0, ZEND_ACC_PROTECTED); - zend_declare_property_string(HttpMessage_Response_ce, ZEND_STRL("reasonPhrase"), "", ZEND_ACC_PROTECTED); + zend_declare_property_long(HttpMessage_Response_ce, ZEND_STRL("statusCode"), 0, ZEND_ACC_PRIVATE); + zend_declare_property_string(HttpMessage_Response_ce, ZEND_STRL("reasonPhrase"), "", ZEND_ACC_PRIVATE); return SUCCESS; } diff --git a/response.h b/response.h new file mode 100644 index 0000000..d7aab2f --- /dev/null +++ b/response.h @@ -0,0 +1,62 @@ +/* + +----------------------------------------------------------------------+ + | HTTP Message PHP extension | + | Helper function to get reason phrase from status code | + +----------------------------------------------------------------------+ + | Copyright (c) 2019 Arnold Daniels | + +----------------------------------------------------------------------+ + | Permission is hereby granted, free of charge, to any person | + | obtaining a copy of this software and associated documentation files | + | (the "Software"), to deal in the Software without restriction, | + | including without limitation the rights to use, copy, modify, merge, | + | publish, distribute, sublicense, and/or sell copies of the Software, | + | and to permit persons to whom the Software is furnished to do so, | + | subject to the following conditions: | + | | + | The above copyright notice and this permission notice shall be | + | included in all copies or substantial portions of the Software. | + | | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | + | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | + | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | + | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | + | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | + | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | + | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | + | SOFTWARE. | + +----------------------------------------------------------------------+ + | Author: Arnold Daniels | + +----------------------------------------------------------------------+ +*/ + +#ifndef HTTP_MESSAGE_RESPONSE_H +#define HTTP_MESSAGE_RESPONSE_H + +#include "http_status_codes.h" + +static int status_comp(const void *a, const void *b) +{ + const http_response_status_code_pair *pa = (const http_response_status_code_pair *) a; + const http_response_status_code_pair *pb = (const http_response_status_code_pair *) b; + + if (pa->code < pb->code) { + return -1; + } else if (pa->code > pb->code) { + return 1; + } + + return 0; +} + +static const char *get_status_string(int code) +{ + http_response_status_code_pair needle = {code, NULL}, *result = NULL; + + result = bsearch(&needle, http_status_map, http_status_map_len, sizeof(needle), status_comp); + + return result ? result->str : ""; +} + +int response_set_status(zval *obj, zend_long code, zend_string *phrase); + +#endif //HTTP_MESSAGE_RESPONSE_H diff --git a/server_request.c b/server_request.c index ca6167e..daa9a51 100644 --- a/server_request.c +++ b/server_request.c @@ -32,19 +32,155 @@ # include "config.h" #endif -#include "php.h" -#include "php_ini.h" +#include +#include "ext/spl/spl_exceptions.h" +#include "ext/psr/psr_http_message.h" #include "php_http_message.h" #include "macros.h" +#include "uploaded_file.h" #include "zend_exceptions.h" #include "zend_interfaces.h" -#include "ext/standard/info.h" -#include "ext/psr/psr_http_message.h" #if HAVE_HTTP_MESSAGE -zend_class_entry *HttpMessage_ServerRequest_ce; +zend_class_entry *HttpMessage_ServerRequest_ce = NULL; + +int assert_uploaded_files(HashTable *array) +{ + zval *entry; + zend_class_entry *interface = HTTP_MESSAGE_PSR_INTERFACE("uploadedfile"); + + if (interface == NULL) { + zend_throw_error(NULL, "Psr\\Http\\Message\\UploadedFileInterface not found"); + return FAILURE; + } + + ZEND_HASH_FOREACH_VAL(array, entry) { + if (Z_TYPE_P(entry) == IS_OBJECT && EXPECTED(instanceof_function(Z_OBJCE_P(entry), interface))) { + continue; + } + + if (UNEXPECTED(Z_TYPE_P(entry) != IS_ARRAY || assert_uploaded_files(Z_ARR_P(entry)) == FAILURE)) { // recursion + zend_throw_exception(spl_ce_InvalidArgumentException, + "Expected all elements to implement Psr\\Http\\Message\\UploadedFileInterface", 0); + return FAILURE; + } + } ZEND_HASH_FOREACH_END(); + + return SUCCESS; +} + +void add_header_from_param(HashTable *headers, char *key, size_t keylen, zval *val) +{ + zval *new_header, valcpy; + char header[256]; + size_t i; + zend_bool lower; // Turn next char to lower case + + if (UNEXPECTED(keylen > 255)) { + php_error_docref(NULL, E_WARNING, "Ignoring header '%s'; field to long", key); + return; + } + + strncpy(header, key, keylen); + header[keylen] = '\0'; + + for (i = 0, lower = 0; i < keylen; i++) { + if (lower && header[i] >= 65 && header[i] <= 90) { // is ASCII A-Z + header[i] += 32; // to lower case + } + + if (header[i] == '_') { + header[i] = '-'; + lower = 0; + } else { + lower = 1; + } + } + + new_header = zend_hash_str_add_empty_element(headers, header, keylen); + + array_init(new_header); + ZVAL_COPY(&valcpy, val); + add_next_index_zval(new_header, &valcpy); +} +void init_headers_from_params(zval *object, HashTable *serverParams) +{ + zval rv, *val; + HashTable *headers; + zend_long index; + zend_string *key; + + headers = Z_ARR_P(zend_read_property(HttpMessage_Message_ce, PROPERTY_ARG(object), ZEND_STRL("headers"), 0, &rv)); + + ZEND_HASH_FOREACH_KEY_VAL(serverParams, index, key, val) { + (void)index; /* NOOP, to avoid unused warning */ + if (UNEXPECTED(key == NULL)) continue; + + if (ZSTR_LEN(key) > 5 && strncmp("HTTP_", ZSTR_VAL(key), 5) == 0 && EXPECTED(Z_TYPE_P(val) == IS_STRING)) { + add_header_from_param(headers, ZSTR_VAL(key) + 5, ZSTR_LEN(key) - 5, val); + } + } ZEND_HASH_FOREACH_END(); + + val = zend_hash_str_find(serverParams, ZEND_STRL("CONTENT_TYPE")); + if (val != NULL && EXPECTED(Z_TYPE_P(val) == IS_STRING)) { + add_header_from_param(headers, ZEND_STRL("CONTENT_TYPE"), val); + } + + val = zend_hash_str_find(serverParams, ZEND_STRL("CONTENT_LENGTH")); + if (val != NULL && EXPECTED(Z_TYPE_P(val) == IS_LONG)) { + add_header_from_param(headers, ZEND_STRL("CONTENT_LENGTH"), val); + } +} + +void init_uri_from_params(zval *object, HashTable *serverParams) +{ + zval rv, *uri, *tmp, *request_target, *protocol, *https, *user, *pass; + zend_long port = -1, default_port = -1; + zend_bool is_http, is_https; + + uri = zend_read_property(HttpMessage_Request_ce, PROPERTY_ARG(object), ZEND_STRL("uri"), 0, &rv); + + request_target = zend_hash_str_find(serverParams, ZEND_STRL("REQUEST_URI")); + zend_call_method(PROPERTY_ARG(uri), HttpMessage_Uri_ce, &HttpMessage_Uri_ce->constructor, ZEND_STRL("__construct"), NULL, + request_target == NULL ? 0 : 1, request_target, NULL); + + COPY_PROPERTY_FROM_ARRAY(serverParams, "HTTP_HOST", uri, HttpMessage_Uri_ce, "host", IS_STRING, tmp); + COPY_PROPERTY_FROM_ARRAY(serverParams, "QUERY_STRING", uri, HttpMessage_Uri_ce, "query", IS_STRING, tmp); + + tmp = zend_hash_str_find(serverParams, ZEND_STRL("SERVER_PORT")); + if (tmp != NULL && EXPECTED(Z_TYPE_P(tmp) == IS_LONG && Z_LVAL_P(tmp) > 0)) { + port = Z_LVAL_P(tmp); + } + + protocol = zend_hash_str_find(serverParams, ZEND_STRL("SERVER_PROTOCOL")); + https = zend_hash_str_find(serverParams, ZEND_STRL("HTTPS")); + is_https = https != NULL && Z_STRCMP(https, "off", 0) != 0; + is_http = protocol != NULL && Z_TYPE_P(protocol) == IS_STRING + ? (strncmp("HTTP/", Z_STRVAL_P(protocol), 5) == 0) + : (port > 0 && port == (is_https ? 443 : 80)); + + if (!is_http) { + // do nothing + } else if (!is_https) { + default_port = 80; + zend_update_property_stringl(HttpMessage_Uri_ce, PROPERTY_ARG(uri), ZEND_STRL("scheme"), ZEND_STRL("http")); + } else { + default_port = 443; + zend_update_property_stringl(HttpMessage_Uri_ce, PROPERTY_ARG(uri), ZEND_STRL("scheme"), ZEND_STRL("https")); + } + + if (port != default_port && port > 0) { + zend_update_property_long(HttpMessage_Uri_ce, PROPERTY_ARG(uri), ZEND_STRL("port"), port); + } + + user = zend_hash_str_find(serverParams, ZEND_STRL("PHP_AUTH_USER")); + if (user != NULL) { + pass = zend_hash_str_find(serverParams, ZEND_STRL("PHP_AUTH_PASS")); + uri_set_userinfo(uri, Z_STRVAL_P(user), Z_STRLEN_P(user), Z_STRVAL_P_NULL(pass), Z_STRLEN_P_NULL(pass)); + } +} /* __construct */ @@ -52,23 +188,51 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_HttpMessageServerRequest_construct, 0, 0, 0) ZEND_ARG_TYPE_INFO(0, serverParams, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, cookieParams, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, queryParams, IS_ARRAY, 0) - ZEND_ARG_INFO(0, parsedBody) + ZEND_ARG_TYPE_INFO(0, postParams, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, files, IS_ARRAY, 0) ZEND_END_ARG_INFO() PHP_METHOD(ServerRequest, __construct) { - zval rv; + zval rv, *uploadedFiles, *val; + zval *serverParams = NULL, *cookieParams = NULL, *queryParams = NULL, *post = NULL, *files = NULL; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 5) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_EX(serverParams, 1, 0) + Z_PARAM_ARRAY_EX(cookieParams, 1, 0) + Z_PARAM_ARRAY_EX(queryParams, 1, 0) + Z_PARAM_ARRAY_EX(post, 1, 0) + Z_PARAM_ARRAY_EX(files, 1, 0) + ZEND_PARSE_PARAMETERS_END(); /* parent::__construct() */ zend_call_method_with_0_params( - getThis(), HttpMessage_Request_ce, &HttpMessage_Request_ce->constructor, "__construct", NULL + PROPERTY_ARG(getThis()), HttpMessage_Request_ce, &HttpMessage_Request_ce->constructor, "__construct", NULL ); - INIT_ARRAY_PROPERTY(HttpMessage_ServerRequest_ce, "serverParams", rv); - INIT_ARRAY_PROPERTY(HttpMessage_ServerRequest_ce, "cookieParams", rv); - INIT_ARRAY_PROPERTY(HttpMessage_ServerRequest_ce, "queryParams", rv); - INIT_ARRAY_PROPERTY(HttpMessage_ServerRequest_ce, "uploadedFiles", rv); + SET_ARRAY_PROPERTY(HttpMessage_ServerRequest_ce, "serverParams", serverParams, rv); + SET_ARRAY_PROPERTY(HttpMessage_ServerRequest_ce, "cookieParams", cookieParams, rv); + SET_ARRAY_PROPERTY(HttpMessage_ServerRequest_ce, "queryParams", queryParams, rv); + + if (files != NULL) { + uploadedFiles = zend_read_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(getThis()), ZEND_STRL("uploadedFiles"), 0, &rv); + create_uploaded_files(uploadedFiles, Z_ARR_P(files)); + } else { + INIT_ARRAY_PROPERTY(HttpMessage_ServerRequest_ce, "uploadedFiles", rv); + } + + if (post != NULL) { + zend_update_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(getThis()), ZEND_STRL("parsedBody"), post); + } + + if (serverParams != NULL) { + COPY_PROPERTY_FROM_ARRAY(Z_ARR_P(serverParams), "REQUEST_METHOD", getThis(), HttpMessage_ServerRequest_ce, + "method", IS_STRING, val); + init_headers_from_params(getThis(), Z_ARR_P(serverParams)); + init_uri_from_params(getThis(), Z_ARR_P(serverParams)); + } + INIT_ARRAY_PROPERTY(HttpMessage_ServerRequest_ce, "attributes", rv); } @@ -79,7 +243,7 @@ PHP_METHOD(ServerRequest, getServerParams) { zval rv, *value; - value = zend_read_property(HttpMessage_ServerRequest_ce, getThis(), ZEND_STRL("serverParams"), 0, &rv); + value = zend_read_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(getThis()), ZEND_STRL("serverParams"), 0, &rv); RETURN_ZVAL(value, 1, 0); } @@ -91,7 +255,7 @@ PHP_METHOD(ServerRequest, getCookieParams) { zval rv, *value; - value = zend_read_property(HttpMessage_ServerRequest_ce, getThis(), ZEND_STRL("cookieParams"), 0, &rv); + value = zend_read_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(getThis()), ZEND_STRL("cookieParams"), 0, &rv); RETURN_ZVAL(value, 1, 0); } @@ -102,11 +266,11 @@ PHP_METHOD(ServerRequest, withCookieParams) ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) Z_PARAM_ARRAY(value); - ZEND_PARSE_PARAMETERS_END_EX(); + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property(HttpMessage_ServerRequest_ce, return_value, ZEND_STRL("cookieParams"), value); + zend_update_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(return_value), ZEND_STRL("cookieParams"), value); } @@ -116,7 +280,7 @@ PHP_METHOD(ServerRequest, getQueryParams) { zval rv, *value; - value = zend_read_property(HttpMessage_ServerRequest_ce, getThis(), ZEND_STRL("queryParams"), 0, &rv); + value = zend_read_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(getThis()), ZEND_STRL("queryParams"), 0, &rv); RETURN_ZVAL(value, 1, 0); } @@ -127,21 +291,21 @@ PHP_METHOD(ServerRequest, withQueryParams) ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) Z_PARAM_ARRAY(value); - ZEND_PARSE_PARAMETERS_END_EX(); + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property(HttpMessage_ServerRequest_ce, return_value, ZEND_STRL("queryParams"), value); + zend_update_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(return_value), ZEND_STRL("queryParams"), value); } -/* queryParams */ +/* uploadedFiles */ PHP_METHOD(ServerRequest, getUploadedFiles) { zval rv, *value; - value = zend_read_property(HttpMessage_ServerRequest_ce, getThis(), ZEND_STRL("uploadedFiles"), 0, &rv); + value = zend_read_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(getThis()), ZEND_STRL("uploadedFiles"), 0, &rv); RETURN_ZVAL(value, 1, 0); } @@ -152,11 +316,15 @@ PHP_METHOD(ServerRequest, withUploadedFiles) ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) Z_PARAM_ARRAY(value); - ZEND_PARSE_PARAMETERS_END_EX(); + ZEND_PARSE_PARAMETERS_END(); + + if (UNEXPECTED(assert_uploaded_files(Z_ARR_P(value)) == FAILURE)) { + return; + } - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property(HttpMessage_ServerRequest_ce, return_value, ZEND_STRL("uploadedFiles"), value); + zend_update_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(return_value), ZEND_STRL("uploadedFiles"), value); } @@ -166,22 +334,26 @@ PHP_METHOD(ServerRequest, getParsedBody) { zval rv, *value; - value = zend_read_property(HttpMessage_ServerRequest_ce, getThis(), ZEND_STRL("parsedBody"), 0, &rv); + value = zend_read_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(getThis()), ZEND_STRL("parsedBody"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(ServerRequest, withParsedBody) { - zval *value; + zval *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_ARRAY_OR_OBJECT(value, 0, 0); - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_ARRAY_OR_OBJECT_EX(value, 1, 0); + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property(HttpMessage_ServerRequest_ce, return_value, ZEND_STRL("parsedBody"), value); + if (EXPECTED(value != NULL)) { + zend_update_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(return_value), ZEND_STRL("parsedBody"), value); + } else { + zend_update_property_null(HttpMessage_ServerRequest_ce, PROPERTY_ARG(return_value), ZEND_STRL("parsedBody")); + } } @@ -191,105 +363,116 @@ PHP_METHOD(ServerRequest, getAttributes) { zval rv, *attributes; - attributes = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("attributes"), 0, &rv); + attributes = zend_read_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(getThis()), ZEND_STRL("attributes"), 0, &rv); RETURN_ZVAL(attributes, 1, 0); } PHP_METHOD(ServerRequest, getAttribute) { - zval rv, *attributes, *value, *default_value; - char *name; - size_t name_len; + zval rv, *attributes, *value, *default_value = NULL; + zend_string *name; - ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(name, name_len) + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 2) + Z_PARAM_STR(name) + Z_PARAM_OPTIONAL Z_PARAM_ZVAL(default_value) - ZEND_PARSE_PARAMETERS_END_EX(); + ZEND_PARSE_PARAMETERS_END(); - attributes = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("attributes"), 0, &rv); + attributes = zend_read_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(getThis()), ZEND_STRL("attributes"), 0, &rv); - value = zend_hash_str_find(Z_ARRVAL_P(attributes), name, name_len); + value = zend_hash_find(Z_ARRVAL_P(attributes), name); /* value is only NULL if the entry wasn't found. A null value is still a zval. */ - RETURN_ZVAL(value != NULL ? value : default_value, 1, 0); + if (value != NULL) { + RETURN_ZVAL(value, 1, 0); + } else if (default_value != NULL) { + RETURN_ZVAL(default_value, 1, 0); + } else { + RETURN_NULL(); + } } PHP_METHOD(ServerRequest, withAttribute) { - zval rv, *value, *attributes, new_attributes; - char *name; - size_t name_len; + zval rv, *value, *attributes_prop; + HashTable *attributes; + zend_string *name; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2) - Z_PARAM_STRING(name, name_len) + Z_PARAM_STR(name) Z_PARAM_ZVAL(value) - ZEND_PARSE_PARAMETERS_END_EX(); - - attributes = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("attributes"), 0, &rv); - ZVAL_COPY(&new_attributes, attributes); + ZEND_PARSE_PARAMETERS_END(); - add_assoc_zval_ex(&new_attributes, name, name_len, value); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + attributes_prop = zend_read_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(return_value), ZEND_STRL("attributes"), 0, &rv); + attributes = zend_array_dup(Z_ARR_P(attributes_prop)); - zend_update_property(HttpMessage_Message_ce, return_value, ZEND_STRL("attributes"), &new_attributes); + zend_symtable_update(attributes, name, value); + ZVAL_ARR(attributes_prop, attributes); } PHP_METHOD(ServerRequest, withoutAttribute) { - zval rv, *attributes; - char *name; - size_t name_len; + zend_string *name; + zval rv, *attributes_prop; + HashTable *attributes; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(name, name_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); - attributes = zend_read_property(HttpMessage_Message_ce, getThis(), ZEND_STRL("attributes"), 0, &rv); - zend_hash_str_del(Z_ARRVAL_P(attributes), name, name_len); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + attributes_prop = zend_read_property(HttpMessage_ServerRequest_ce, PROPERTY_ARG(return_value), ZEND_STRL("attributes"), 0, &rv); + attributes = zend_array_dup(Z_ARR_P(attributes_prop)); - zend_update_property(HttpMessage_Message_ce, return_value, ZEND_STRL("attributes"), attributes); + zend_symtable_del(attributes, name); + ZVAL_ARR(attributes_prop, attributes); } /* Define HttpMessage\ServerRequest class */ static const zend_function_entry request_functions[] = { - PHP_ME(ServerRequest, __construct, arginfo_HttpMessageServerRequest_construct, ZEND_ACC_PUBLIC) - HTTP_MESSAGE_ME(ServerRequest, getServerParams) - HTTP_MESSAGE_ME(ServerRequest, getCookieParams) - HTTP_MESSAGE_ME(ServerRequest, withCookieParams) - HTTP_MESSAGE_ME(ServerRequest, getQueryParams) - HTTP_MESSAGE_ME(ServerRequest, withQueryParams) - HTTP_MESSAGE_ME(ServerRequest, getUploadedFiles) - HTTP_MESSAGE_ME(ServerRequest, withUploadedFiles) - HTTP_MESSAGE_ME(ServerRequest, getParsedBody) - HTTP_MESSAGE_ME(ServerRequest, withParsedBody) - HTTP_MESSAGE_ME(ServerRequest, getAttributes) - HTTP_MESSAGE_ME(ServerRequest, getAttribute) - HTTP_MESSAGE_ME(ServerRequest, withAttribute) - HTTP_MESSAGE_ME(ServerRequest, withoutAttribute) - PHP_FE_END + PHP_ME(ServerRequest, __construct, arginfo_HttpMessageServerRequest_construct, ZEND_ACC_PUBLIC) + HTTP_MESSAGE_ME(ServerRequest, getServerParams) + HTTP_MESSAGE_ME(ServerRequest, getCookieParams) + HTTP_MESSAGE_ME(ServerRequest, withCookieParams) + HTTP_MESSAGE_ME(ServerRequest, getQueryParams) + HTTP_MESSAGE_ME(ServerRequest, withQueryParams) + HTTP_MESSAGE_ME(ServerRequest, getUploadedFiles) + HTTP_MESSAGE_ME(ServerRequest, withUploadedFiles) + HTTP_MESSAGE_ME(ServerRequest, getParsedBody) + HTTP_MESSAGE_ME(ServerRequest, withParsedBody) + HTTP_MESSAGE_ME(ServerRequest, getAttributes) + HTTP_MESSAGE_ME(ServerRequest, getAttribute) + HTTP_MESSAGE_ME(ServerRequest, withAttribute) + HTTP_MESSAGE_ME(ServerRequest, withoutAttribute) + PHP_FE_END }; PHP_MINIT_FUNCTION(http_message_serverrequest) { zend_class_entry ce; + zend_class_entry *interface = HTTP_MESSAGE_PSR_INTERFACE("serverrequest"); + + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND(interface, "ServerRequest"); + if (HttpMessage_Request_ce == NULL) return FAILURE; + INIT_NS_CLASS_ENTRY(ce, "HttpMessage", "ServerRequest", request_functions); HttpMessage_ServerRequest_ce = zend_register_internal_class_ex(&ce, HttpMessage_Request_ce); - zend_class_implements(HttpMessage_ServerRequest_ce, 1, PsrHttpMessageServerRequestInterface_ce_ptr); + zend_class_implements(HttpMessage_ServerRequest_ce, 1, interface); /* Properties */ - zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("serverParams"), ZEND_ACC_PROTECTED); - zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("cookieParams"), ZEND_ACC_PROTECTED); - zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("queryParams"), ZEND_ACC_PROTECTED); - zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("uploadedFiles"), ZEND_ACC_PROTECTED); - zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("parsedBody"), ZEND_ACC_PROTECTED); - zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("attributes"), ZEND_ACC_PROTECTED); + zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("serverParams"), ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("cookieParams"), ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("queryParams"), ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("uploadedFiles"), ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("parsedBody"), ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_ServerRequest_ce, ZEND_STRL("attributes"), ZEND_ACC_PRIVATE); return SUCCESS; } diff --git a/sign.gpg.enc b/sign.gpg.enc new file mode 100644 index 0000000..54b7188 Binary files /dev/null and b/sign.gpg.enc differ diff --git a/stream.c b/stream.c index 976c976..226558f 100644 --- a/stream.c +++ b/stream.c @@ -33,98 +33,167 @@ #endif #include "php.h" -#include "php_ini.h" -#include "php_http_message.h" #include "macros.h" -#include "php_streams.h" +#include "stream.h" #include "zend_exceptions.h" #include "zend_interfaces.h" -#include "http_status_codes.h" -#include "ext/standard/info.h" #include "ext/psr/psr_http_message.h" #include "ext/spl/spl_exceptions.h" #if HAVE_HTTP_MESSAGE -zend_class_entry *HttpMessage_Stream_ce; +zend_class_entry *HttpMessage_Stream_ce = NULL; -zend_bool string_contains_char(char *haystack, char chr) +void stream_seek(zval *this, zend_long offset, zend_long whence, zval* return_value) { - char *p = strchr(haystack, chr); + zval rv, *resource; + php_stream *stream; + + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(this), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Stream is %s", + Z_TYPE_P(resource) == IS_RESOURCE ? "closed" : "detached"); + return; + } + + if (whence > 3) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Invalid value for whence"); + return; + } - return (p != NULL); + php_stream_from_zval(stream, resource); + + if (!stream->ops->seek || (stream->flags & PHP_STREAM_FLAG_NO_SEEK) != 0) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Stream is not seekable"); + return; + }; + + php_stream_seek(stream, offset, whence); } /* __construct */ ZEND_BEGIN_ARG_INFO_EX(arginfo_HttpMessageStream_construct, 0, 0, 0) - ZEND_ARG_TYPE_INFO(0, uri, IS_RESOURCE, 0) + ZEND_ARG_INFO(0, uri) ZEND_END_ARG_INFO() PHP_METHOD(Stream, __construct) { - zval rv, *zstream = NULL, newstream; + zval *input = NULL, resource; + char *file = NULL, *mode = NULL; + size_t mode_len = 0; php_stream *stream; - ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 2) Z_PARAM_OPTIONAL - Z_PARAM_RESOURCE(zstream) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_ZVAL(input) + Z_PARAM_STRING(mode, mode_len) + ZEND_PARSE_PARAMETERS_END(); - if (zstream == NULL) { - RETURN_NULL(); + if (UNEXPECTED(input != NULL && Z_TYPE_P(input) != IS_STRING && Z_TYPE_P(input) != IS_RESOURCE)) { + zend_type_error("Expected parameter 1 to be a string or resource, %s given ", zend_zval_type_name(input)); + return; + } - stream = php_stream_fopen("php://temp", "r+", NULL); - ZVAL_RES(&newstream, (stream)->res); // php_stream_to_zval(stream, &newstream); // segfault ? - zstream = &newstream; - } else if (Z_RES_P(zstream)->type != php_file_le_stream() && Z_RES_P(zstream)->type != php_file_le_pstream()) { - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Resource is not a stream"); + if (input == NULL) { + if (open_temp_stream(&resource) == FAILURE) return; + } else if (Z_TYPE_P(input) == IS_STRING) { + file = Z_STRVAL_P(input); + file[Z_STRLEN_P(input)] = '\0'; + stream = php_stream_open_wrapper(file, mode != NULL ? mode : "r", 0, NULL); + + if (stream == NULL) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Failed to open '%s' stream", file); + return; + } + + php_stream_to_zval(stream, &resource); + } else if (EXPECTED(IS_STREAM_RESOURCE(input))) { + ZVAL_COPY(&resource, input); + } else { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "Resource is not a stream"); + return; } - zend_update_property(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream"), zstream); + zend_update_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), &resource); } PHP_METHOD(Stream, __toString) { + zval rv, *resource; + php_stream *stream; + zend_string *contents; + + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { + RETURN_EMPTY_STRING(); + } + + php_stream_from_zval(stream, resource); + + if (!string_contains_char(stream->mode, 'r') && !string_contains_char(stream->mode, '+')) { + RETURN_EMPTY_STRING(); + } + + if ((stream->ops->seek) && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { + php_stream_seek(stream, 0, SEEK_SET); + } + + // Special case for 'php://input'. Need to reopen the resource, because seek doesn't work. + if ( + stream->wrapper != NULL && + strcmp(stream->wrapper->wops->label, "PHP") == 0 && + strcmp(stream->ops->label, "Input") == 0 + ) { + stream = php_stream_open_wrapper(stream->orig_path, stream->mode, 0, NULL); + php_stream_to_zval(stream, resource); + } + + if ((contents = php_stream_copy_to_mem(stream, (ssize_t)PHP_STREAM_COPY_ALL, 0))) { + RETURN_STR(contents); + } else { + RETURN_EMPTY_STRING(); + } } PHP_METHOD(Stream, close) { - zval rv, *zstream; + zval rv, *resource; php_stream *stream; - zstream = zend_read_property(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream"), 0, &rv); - if (Z_TYPE_P(zstream) != IS_RESOURCE) { - RETURN_NULL(); + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (Z_TYPE_P(resource) != IS_RESOURCE) { + return; } - php_stream_from_zval(stream, zstream); - php_stream_close(stream); + if (IS_STREAM_RESOURCE(resource)) { + php_stream_from_zval(stream, resource); + php_stream_close(stream); + } } PHP_METHOD(Stream, detach) { - zval rv, *zstream; - - zstream = zend_read_property(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream"), 0, &rv); + zval rv, *resource; - zend_update_property_null(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream")); + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + ZVAL_COPY(return_value, resource); - RETURN_ZVAL(zstream, 1, 0); + zend_update_property_null(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream")); } PHP_METHOD(Stream, getSize) { - zval rv, *zstream; + zval rv, *resource; php_stream *stream; php_stream_statbuf ssb; - zstream = zend_read_property(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream"), 0, &rv); - if (Z_TYPE_P(zstream) != IS_RESOURCE) { + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { RETURN_NULL(); } - php_stream_from_zval(stream, zstream); + php_stream_from_zval(stream, resource); php_stream_stat(stream, &ssb); RETURN_LONG(ssb.sb.st_size); @@ -132,16 +201,18 @@ PHP_METHOD(Stream, getSize) PHP_METHOD(Stream, tell) { - zval rv, *zstream; + zval rv, *resource; php_stream *stream; size_t pos; - zstream = zend_read_property(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream"), 0, &rv); - if (Z_TYPE_P(zstream) != IS_RESOURCE) { - zend_throw_exception_ex(spl_ce_RuntimeException, 0, "The stream has been detached"); + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Stream is %s", + Z_TYPE_P(resource) == IS_RESOURCE ? "closed" : "detached"); + return; } - php_stream_from_zval(stream, zstream); + php_stream_from_zval(stream, resource); pos = php_stream_tell(stream); RETURN_LONG(pos); @@ -149,16 +220,16 @@ PHP_METHOD(Stream, tell) PHP_METHOD(Stream, eof) { - zval rv, *zstream; + zval rv, *resource; php_stream *stream; zend_bool eof; - zstream = zend_read_property(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream"), 0, &rv); - if (Z_TYPE_P(zstream) != IS_RESOURCE) { + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { RETURN_TRUE; } - php_stream_from_zval(stream, zstream); + php_stream_from_zval(stream, resource); eof = php_stream_eof(stream); RETURN_BOOL(eof); @@ -166,16 +237,16 @@ PHP_METHOD(Stream, eof) PHP_METHOD(Stream, isSeekable) { - zval rv, *zstream; + zval rv, *resource; php_stream *stream; zend_bool seekable; - zstream = zend_read_property(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream"), 0, &rv); - if (Z_TYPE_P(zstream) != IS_RESOURCE) { + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { RETURN_FALSE; } - php_stream_from_zval(stream, zstream); + php_stream_from_zval(stream, resource); seekable = (stream->ops->seek) && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0; RETURN_BOOL(seekable); @@ -183,128 +254,219 @@ PHP_METHOD(Stream, isSeekable) PHP_METHOD(Stream, seek) { - zval rv, *zstream; - php_stream *stream; - size_t offset, whence; + zend_long offset = 0, whence = SEEK_SET; - ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 2) Z_PARAM_LONG(offset) + Z_PARAM_OPTIONAL Z_PARAM_LONG(whence) - ZEND_PARSE_PARAMETERS_END_EX(); - - zstream = zend_read_property(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream"), 0, &rv); - if (Z_TYPE_P(zstream) != IS_RESOURCE) { - RETURN_FALSE; - } - - php_stream_from_zval(stream, zstream); + ZEND_PARSE_PARAMETERS_END(); - php_stream_seek(stream, offset, whence); + stream_seek(getThis(), offset, whence, return_value); } PHP_METHOD(Stream, rewind) { - zval offset; - - ZVAL_LONG(&offset, 0); - - zend_call_method_with_1_params(getThis(), HttpMessage_Stream_ce, NULL, "seek", return_value, &offset); + stream_seek(getThis(), 0, SEEK_SET, return_value); } PHP_METHOD(Stream, isWritable) { - zval rv, *zstream; + zval rv, *resource; php_stream *stream; - zend_bool writable; - zstream = zend_read_property(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream"), 0, &rv); - if (Z_TYPE_P(zstream) != IS_RESOURCE) { + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { RETURN_FALSE; } - php_stream_from_zval(stream, zstream); - writable = !string_contains_char(stream->mode, 'r') || string_contains_char(stream->mode, '+'); + php_stream_from_zval(stream, resource); - RETURN_BOOL(writable); + RETURN_BOOL(stream_is_writable(stream)); } PHP_METHOD(Stream, write) { + zval rv, *resource; + char *input = NULL; + size_t len = 0, ret = 0; + php_stream *stream; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) + Z_PARAM_STRING(input, len) + ZEND_PARSE_PARAMETERS_END(); + + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Stream is %s", + Z_TYPE_P(resource) == IS_RESOURCE ? "closed" : "detached"); + return; + } + + php_stream_from_zval(stream, resource); + + if (!stream_is_writable(stream)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Stream not writable"); + return; + } + + if (len > 0) { + ret = php_stream_write(stream, input, len); + } + + RETURN_LONG(ret); } PHP_METHOD(Stream, isReadable) { - zval rv, *zstream; + zval rv, *resource; php_stream *stream; - zend_bool writable; - zstream = zend_read_property(HttpMessage_Stream_ce, getThis(), ZEND_STRL("stream"), 0, &rv); - if (Z_TYPE_P(zstream) != IS_RESOURCE) { + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { RETURN_FALSE; } - php_stream_from_zval(stream, zstream); - writable = string_contains_char(stream->mode, 'r') || string_contains_char(stream->mode, '+'); + php_stream_from_zval(stream, resource); - RETURN_BOOL(writable); + RETURN_BOOL(stream_is_readable(stream)); } PHP_METHOD(Stream, read) { + zval rv, *resource; + php_stream *stream; + zend_long len = 0; + zend_string *contents; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) + Z_PARAM_LONG(len) + ZEND_PARSE_PARAMETERS_END(); + + if (len < 0) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Length parameter must be equal or greater than 0"); + return; + } + + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Stream is %s", + Z_TYPE_P(resource) == IS_RESOURCE ? "closed" : "detached"); + return; + } + + php_stream_from_zval(stream, resource); + + if (!string_contains_char(stream->mode, 'r') && !string_contains_char(stream->mode, '+')) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Stream not readable"); + return; + } + + if ((contents = php_stream_copy_to_mem(stream, (size_t)len, 0))) { + RETURN_STR(contents); + } else { + RETURN_EMPTY_STRING(); + } } PHP_METHOD(Stream, getContents) { + zval rv, *resource; + php_stream *stream; + zend_string *contents; + + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + if (!IS_STREAM_RESOURCE(resource)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Stream is %s", + Z_TYPE_P(resource) == IS_RESOURCE ? "closed" : "detached"); + return; + } + + php_stream_from_zval(stream, resource); + + if (!string_contains_char(stream->mode, 'r') && !string_contains_char(stream->mode, '+')) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Stream not readable"); + return; + } + + if ((contents = php_stream_copy_to_mem(stream, (size_t)PHP_STREAM_COPY_ALL, 0))) { + RETURN_STR(contents); + } else { + RETURN_EMPTY_STRING(); + } } PHP_METHOD(Stream, getMetadata) { - zval fname, zkey; - zend_string *key; + zval rv, fname, *resource, *zvalue; + zend_string *key = NULL; - ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) + resource = zend_read_property(HttpMessage_Stream_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) + Z_PARAM_OPTIONAL Z_PARAM_STR(key) - ZEND_PARSE_PARAMETERS_END_EX(); + ZEND_PARSE_PARAMETERS_END(); + + if (!IS_STREAM_RESOURCE(resource)) { + if (key == NULL) { + array_init(return_value); + } else { + RETVAL_NULL(); + } + return; + } ZVAL_STRING(&fname, "stream_get_meta_data"); - ZVAL_STR(&zkey, key); + call_user_function(NULL, NULL, &fname, return_value, 1, resource); - call_user_function(NULL, NULL, &fname, return_value, 1, &zkey); + if (key != NULL) { + zvalue = zend_hash_find(Z_ARR(*return_value), key); + + if (zvalue == NULL) { + ZVAL_NULL(return_value); + } else { + ZVAL_COPY(return_value, zvalue); + } + } } /* Define HttpMessage\Stream class */ static const zend_function_entry stream_functions[] = { - PHP_ME(Stream, __construct, arginfo_HttpMessageStream_construct, ZEND_ACC_PUBLIC) - HTTP_MESSAGE_ME(Stream, __toString) - HTTP_MESSAGE_ME(Stream, close) - HTTP_MESSAGE_ME(Stream, detach) - HTTP_MESSAGE_ME(Stream, getSize) - HTTP_MESSAGE_ME(Stream, tell) - HTTP_MESSAGE_ME(Stream, eof) - HTTP_MESSAGE_ME(Stream, isSeekable) - HTTP_MESSAGE_ME(Stream, seek) - HTTP_MESSAGE_ME(Stream, rewind) - HTTP_MESSAGE_ME(Stream, isWritable) - HTTP_MESSAGE_ME(Stream, write) - HTTP_MESSAGE_ME(Stream, isReadable) - HTTP_MESSAGE_ME(Stream, read) - HTTP_MESSAGE_ME(Stream, getContents) - HTTP_MESSAGE_ME(Stream, getMetadata) - PHP_FE_END + PHP_ME(Stream, __construct, arginfo_HttpMessageStream_construct, ZEND_ACC_PUBLIC) + HTTP_MESSAGE_ME(Stream, __toString) + HTTP_MESSAGE_ME(Stream, close) + HTTP_MESSAGE_ME(Stream, detach) + HTTP_MESSAGE_ME(Stream, getSize) + HTTP_MESSAGE_ME(Stream, tell) + HTTP_MESSAGE_ME(Stream, eof) + HTTP_MESSAGE_ME(Stream, isSeekable) + HTTP_MESSAGE_ME(Stream, seek) + HTTP_MESSAGE_ME(Stream, rewind) + HTTP_MESSAGE_ME(Stream, isWritable) + HTTP_MESSAGE_ME(Stream, write) + HTTP_MESSAGE_ME(Stream, isReadable) + HTTP_MESSAGE_ME(Stream, read) + HTTP_MESSAGE_ME(Stream, getContents) + HTTP_MESSAGE_ME(Stream, getMetadata) + PHP_FE_END }; PHP_MINIT_FUNCTION(http_message_stream) { zend_class_entry ce; + zend_class_entry *interface = HTTP_MESSAGE_PSR_INTERFACE("stream"); + + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND(interface, "Stream"); + INIT_NS_CLASS_ENTRY(ce, "HttpMessage", "Stream", stream_functions); HttpMessage_Stream_ce = zend_register_internal_class(&ce); - zend_class_implements(HttpMessage_Stream_ce, 1, PsrHttpMessageStreamInterface_ce_ptr); + zend_class_implements(HttpMessage_Stream_ce, 1, interface); /* Properties */ - zend_declare_property_null(HttpMessage_Stream_ce, ZEND_STRL("stream"), ZEND_ACC_PROTECTED); + zend_declare_property_null(HttpMessage_Stream_ce, ZEND_STRL("stream"), ZEND_ACC_PRIVATE); return SUCCESS; } diff --git a/stream.h b/stream.h new file mode 100644 index 0000000..fa6d3c2 --- /dev/null +++ b/stream.h @@ -0,0 +1,68 @@ +/* + +----------------------------------------------------------------------+ + | HTTP Message PHP extension | + | Helper function to work with streams | + +----------------------------------------------------------------------+ + | Copyright (c) 2019 Arnold Daniels | + +----------------------------------------------------------------------+ + | Permission is hereby granted, free of charge, to any person | + | obtaining a copy of this software and associated documentation files | + | (the "Software"), to deal in the Software without restriction, | + | including without limitation the rights to use, copy, modify, merge, | + | publish, distribute, sublicense, and/or sell copies of the Software, | + | and to permit persons to whom the Software is furnished to do so, | + | subject to the following conditions: | + | | + | The above copyright notice and this permission notice shall be | + | included in all copies or substantial portions of the Software. | + | | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | + | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | + | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | + | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | + | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | + | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | + | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | + | SOFTWARE. | + +----------------------------------------------------------------------+ + | Author: Arnold Daniels | + +----------------------------------------------------------------------+ +*/ + +#ifndef HTTP_MESSAGE_STREAM_H +#define HTTP_MESSAGE_STREAM_H + +#include "php_streams.h" + +static int open_temp_stream(zval *zstream) +{ + php_stream *stream = php_stream_open_wrapper("php://temp", "w+", 0, NULL); + + if (stream == NULL) { + zend_throw_error(NULL, "Failed to open 'php://temp' stream"); + return FAILURE; + } + + php_stream_to_zval(stream, zstream); + + return SUCCESS; +} + +zend_always_inline static zend_bool string_contains_char(char *haystack, char chr) +{ + char *p = strchr(haystack, chr); + + return (p != NULL); +} + +zend_always_inline static zend_bool stream_is_writable(php_stream *stream) +{ + return !string_contains_char(stream->mode, 'r') || string_contains_char(stream->mode, '+'); +} + +zend_always_inline static zend_bool stream_is_readable(php_stream *stream) +{ + return string_contains_char(stream->mode, 'r') || string_contains_char(stream->mode, '+'); +} + +#endif //HTTP_MESSAGE_STREAM_H diff --git a/tests/Emitter/emit_001.phpt b/tests/Emitter/emit_001.phpt new file mode 100644 index 0000000..32ce78a --- /dev/null +++ b/tests/Emitter/emit_001.phpt @@ -0,0 +1,12 @@ +--TEST-- +Emitter::emit() with an empty response +--FILE-- +emit($response); + +?> +--EXPECTHEADERS-- +--EXPECT-- diff --git a/tests/Emitter/emit_002.phpt b/tests/Emitter/emit_002.phpt new file mode 100644 index 0000000..87b0a4b --- /dev/null +++ b/tests/Emitter/emit_002.phpt @@ -0,0 +1,14 @@ +--TEST-- +Emitter::emit() with a response body +--FILE-- +getBody()->write("Hello World"); + +$emitter->emit($response); + +?> +--EXPECTHEADERS-- +--EXPECT-- +Hello World diff --git a/tests/Emitter/emit_003.phpt b/tests/Emitter/emit_003.phpt new file mode 100644 index 0000000..e709216 --- /dev/null +++ b/tests/Emitter/emit_003.phpt @@ -0,0 +1,16 @@ +--TEST-- +Emitter::emit() with a response status +--INI-- +--FILE-- +withStatus(200, "Ok"); +$response->getBody()->write("Hello World"); + +$emitter->emit($response); +?> +--EXPECTHEADERS-- +HTTP/1.1 200 OK +--EXPECT-- +Hello World diff --git a/tests/Emitter/emit_004.phpt b/tests/Emitter/emit_004.phpt new file mode 100644 index 0000000..4f9d8b7 --- /dev/null +++ b/tests/Emitter/emit_004.phpt @@ -0,0 +1,18 @@ +--TEST-- +Emitter::emit() with a response with headers +--FILE-- +withHeader("Content-Length", 14) + ->withHeader("Foo", "bar"); +$response->getBody()->write("Page not found"); + +$emitter->emit($response); + +?> +--EXPECTHEADERS-- +Content-Length: 14 +Foo: bar +--EXPECT-- +Page not found diff --git a/tests/Emitter/emit_005.phpt b/tests/Emitter/emit_005.phpt new file mode 100644 index 0000000..6afbf35 --- /dev/null +++ b/tests/Emitter/emit_005.phpt @@ -0,0 +1,18 @@ +--TEST-- +Emitter::emit() with a response with duplicate headers +--FILE-- +withAddedHeader("X-Message", "This is a") + ->withAddedHeader("X-Message", "log message") + ->withAddedHeader("X-Message", str_repeat("x", 1000)); + +$emitter->emit($response); + +?> +--EXPECTHEADERS-- +X-Message: This is a +X-Message: log message +X-Message: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +--EXPECT-- diff --git a/tests/Emitter/emit_006.phpt b/tests/Emitter/emit_006.phpt new file mode 100644 index 0000000..8d3c649 --- /dev/null +++ b/tests/Emitter/emit_006.phpt @@ -0,0 +1,16 @@ +--TEST-- +Emitter::emit() with a response with response status +--FILE-- +withStatus(404) + ->withHeader("Foo", "Bar"); + +$emitter->emit($response); + +?> +--EXPECTHEADERS-- +HTTP/1.1 404 Not Found +Foo: Bar +--EXPECT-- diff --git a/tests/Emitter/emit_007.phpt b/tests/Emitter/emit_007.phpt new file mode 100644 index 0000000..6b430f2 --- /dev/null +++ b/tests/Emitter/emit_007.phpt @@ -0,0 +1,17 @@ +--TEST-- +Emitter::emit() with a response with response status and custom reason +--FILE-- +withProtocolVersion("1.0") + ->withStatus(404, "Please go away") + ->withHeader("Foo", "Bar"); + +$emitter->emit($response); + +?> +--EXPECTHEADERS-- +HTTP/1.0 404 Please go away +Foo: Bar +--EXPECT-- diff --git a/tests/Factory/createRequest_001.phpt b/tests/Factory/createRequest_001.phpt new file mode 100644 index 0000000..c13a881 --- /dev/null +++ b/tests/Factory/createRequest_001.phpt @@ -0,0 +1,44 @@ +--TEST-- +Factory::createRequest() +--FILE-- +createRequest('GET', 'https://www.example.com/'); + +var_dump($request); + +?> +--EXPECTF-- +object(HttpMessage\Request)#%d (6) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["requestTarget":"HttpMessage\Request":private]=> + NULL + ["method":"HttpMessage\Request":private]=> + string(3) "GET" + ["uri":"HttpMessage\Request":private]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(5) "https" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(15) "www.example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } +} diff --git a/tests/Factory/createRequest_002.phpt b/tests/Factory/createRequest_002.phpt new file mode 100644 index 0000000..c7ca40e --- /dev/null +++ b/tests/Factory/createRequest_002.phpt @@ -0,0 +1,48 @@ +--TEST-- +Factory::createRequest() with Uri object +--FILE-- +createRequest('GET', $uri); + +var_dump($request); +var_dump($request->getUri() === $uri); + +?> +--EXPECTF-- +object(HttpMessage\Request)#%d (6) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["requestTarget":"HttpMessage\Request":private]=> + NULL + ["method":"HttpMessage\Request":private]=> + string(3) "GET" + ["uri":"HttpMessage\Request":private]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(5) "https" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(15) "www.example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } +} +bool(true) diff --git a/tests/Factory/createResponse_001.phpt b/tests/Factory/createResponse_001.phpt new file mode 100644 index 0000000..8ea4c49 --- /dev/null +++ b/tests/Factory/createResponse_001.phpt @@ -0,0 +1,27 @@ +--TEST-- +Factory::createResponse() without arguments +--FILE-- +createResponse(); + +var_dump($response); + +?> +--EXPECTF-- +object(HttpMessage\Response)#%d (5) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["statusCode":"HttpMessage\Response":private]=> + int(200) + ["reasonPhrase":"HttpMessage\Response":private]=> + string(2) "OK" +} diff --git a/tests/Factory/createResponse_002.phpt b/tests/Factory/createResponse_002.phpt new file mode 100644 index 0000000..bac0c42 --- /dev/null +++ b/tests/Factory/createResponse_002.phpt @@ -0,0 +1,36 @@ +--TEST-- +Factory::createResponse() with status code +--FILE-- +createResponse(400); +var_dump($response); + +var_dump($factory->createResponse(100)->getStatusCode()); +var_dump($factory->createResponse(100)->getReasonPhrase()); +var_dump($factory->createResponse(200)->getReasonPhrase()); +var_dump($factory->createResponse(404)->getReasonPhrase()); + +?> +--EXPECTF-- +object(HttpMessage\Response)#%d (5) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["statusCode":"HttpMessage\Response":private]=> + int(400) + ["reasonPhrase":"HttpMessage\Response":private]=> + string(11) "Bad Request" +} +int(100) +string(8) "Continue" +string(2) "OK" +string(9) "Not Found" diff --git a/tests/Factory/createResponse_003.phpt b/tests/Factory/createResponse_003.phpt new file mode 100644 index 0000000..3018474 --- /dev/null +++ b/tests/Factory/createResponse_003.phpt @@ -0,0 +1,27 @@ +--TEST-- +Factory::createResponse() with status code and reason phrase +--FILE-- +createResponse(400, "Foo Bar"); +var_dump($response); + +?> +--EXPECTF-- +object(HttpMessage\Response)#%d (5) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["statusCode":"HttpMessage\Response":private]=> + int(400) + ["reasonPhrase":"HttpMessage\Response":private]=> + string(7) "Foo Bar" +} \ No newline at end of file diff --git a/tests/Factory/createResponse_004.phpt b/tests/Factory/createResponse_004.phpt new file mode 100644 index 0000000..b02e157 --- /dev/null +++ b/tests/Factory/createResponse_004.phpt @@ -0,0 +1,27 @@ +--TEST-- +Factory::createResponse() with unknown status code +--FILE-- +createResponse(990); +var_dump($response); + +?> +--EXPECTF-- +object(HttpMessage\Response)#%d (5) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["statusCode":"HttpMessage\Response":private]=> + int(990) + ["reasonPhrase":"HttpMessage\Response":private]=> + string(0) "" +} \ No newline at end of file diff --git a/tests/Factory/createResponse_err01.phpt b/tests/Factory/createResponse_err01.phpt new file mode 100644 index 0000000..5c741ec --- /dev/null +++ b/tests/Factory/createResponse_err01.phpt @@ -0,0 +1,22 @@ +--TEST-- +Factory::createResponse() with invalid arguments +--FILE-- +createResponse('ok'); +} catch (TypeError $e) { + echo strtr($e->getMessage(), ['integer' => 'int']), "\n"; +} + +try { + $factory->createResponse(200, ['foo', 'bar']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +%sttpMessage\Factory::createResponse()%sint, string given +%sttpMessage\Factory::createResponse()%sstring, array given diff --git a/tests/Factory/createResponse_err02.phpt b/tests/Factory/createResponse_err02.phpt new file mode 100644 index 0000000..cc262bc --- /dev/null +++ b/tests/Factory/createResponse_err02.phpt @@ -0,0 +1,15 @@ +--TEST-- +Factory::createResponse() with invalid status code +--FILE-- +createResponse(-1); +} catch (InvalidArgumentException $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Invalid HTTP status code -1 \ No newline at end of file diff --git a/tests/Factory/createServerRequest_001.phpt b/tests/Factory/createServerRequest_001.phpt new file mode 100644 index 0000000..f25134a --- /dev/null +++ b/tests/Factory/createServerRequest_001.phpt @@ -0,0 +1,80 @@ +--TEST-- +Factory::createServerRequest() +--FILE-- +createServerRequest('GET', 'https://www.example.com/'); + +var_dump($request); + +?> +--EXPECTF-- +object(HttpMessage\ServerRequest)#%d (14) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["requestTarget":"HttpMessage\Request":private]=> + NULL + ["method":"HttpMessage\Request":private]=> + string(0) "" + ["uri":"HttpMessage\Request":private]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(0) "" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(0) "" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } + ["serverParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["cookieParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["queryParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["uploadedFiles":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["parsedBody":"HttpMessage\ServerRequest":private]=> + NULL + ["attributes":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["method"]=> + string(3) "GET" + ["uri"]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(5) "https" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(15) "www.example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } +} \ No newline at end of file diff --git a/tests/Factory/createServerRequest_002.phpt b/tests/Factory/createServerRequest_002.phpt new file mode 100644 index 0000000..a409e72 --- /dev/null +++ b/tests/Factory/createServerRequest_002.phpt @@ -0,0 +1,84 @@ +--TEST-- +Factory::createServerRequest() with Uri object +--FILE-- +createServerRequest('GET', $uri); + +var_dump($request); +var_dump($request->getUri() === $uri); + +?> +--EXPECTF-- +object(HttpMessage\ServerRequest)#%d (14) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["requestTarget":"HttpMessage\Request":private]=> + NULL + ["method":"HttpMessage\Request":private]=> + string(0) "" + ["uri":"HttpMessage\Request":private]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(0) "" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(0) "" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } + ["serverParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["cookieParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["queryParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["uploadedFiles":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["parsedBody":"HttpMessage\ServerRequest":private]=> + NULL + ["attributes":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["method"]=> + string(3) "GET" + ["uri"]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(5) "https" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(15) "www.example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } +} +bool(false) \ No newline at end of file diff --git a/tests/Factory/createServerRequest_003.phpt b/tests/Factory/createServerRequest_003.phpt new file mode 100644 index 0000000..e8a2243 --- /dev/null +++ b/tests/Factory/createServerRequest_003.phpt @@ -0,0 +1,150 @@ +--TEST-- +Factory::createServerRequest() with server params +--FILE-- + 'HTTP/1.1', + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', + 'CONTENT_LENGTH' => 1324, + 'HTTP_ACCEPT' => 'text/html', + 'HTTP_ACCEPT_CHARSET' => 'utf-8', + 'HTTP_ACCEPT_ENCODING' => 'zip, deflate', + 'PHP_AUTH_USER' => 'john', + 'PHP_AUTH_PASS' => 'secret', + 'SERVER_PORT' => 80, +]; + +$factory = new HttpMessage\Factory(); +$request = $factory->createServerRequest('QUERY', 'https://www.example.com/foo/bar/22?q=1', $params); + +var_dump($request); + +?> +--EXPECTF-- +object(HttpMessage\ServerRequest)#%d (14) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(6) { + ["Host"]=> + array(1) { + [0]=> + string(11) "example.com" + } + ["Accept"]=> + array(1) { + [0]=> + string(9) "text/html" + } + ["Accept-Charset"]=> + array(1) { + [0]=> + string(5) "utf-8" + } + ["Accept-Encoding"]=> + array(1) { + [0]=> + string(12) "zip, deflate" + } + ["Content-Type"]=> + array(1) { + [0]=> + string(33) "application/x-www-form-urlencoded" + } + ["Content-Length"]=> + array(1) { + [0]=> + int(1324) + } + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["requestTarget":"HttpMessage\Request":private]=> + NULL + ["method":"HttpMessage\Request":private]=> + string(0) "" + ["uri":"HttpMessage\Request":private]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(4) "http" + ["userInfo":"HttpMessage\Uri":private]=> + string(11) "john:secret" + ["host":"HttpMessage\Uri":private]=> + string(11) "example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(6) "/foos/" + ["query":"HttpMessage\Uri":private]=> + string(5) "bar=1" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } + ["serverParams":"HttpMessage\ServerRequest":private]=> + array(12) { + ["SERVER_PROTOCOL"]=> + string(8) "HTTP/1.1" + ["REQUEST_METHOD"]=> + string(4) "POST" + ["HTTP_HOST"]=> + string(11) "example.com" + ["REQUEST_URI"]=> + string(12) "/foos/?bar=1" + ["CONTENT_TYPE"]=> + string(33) "application/x-www-form-urlencoded" + ["CONTENT_LENGTH"]=> + int(1324) + ["HTTP_ACCEPT"]=> + string(9) "text/html" + ["HTTP_ACCEPT_CHARSET"]=> + string(5) "utf-8" + ["HTTP_ACCEPT_ENCODING"]=> + string(12) "zip, deflate" + ["PHP_AUTH_USER"]=> + string(4) "john" + ["PHP_AUTH_PASS"]=> + string(6) "secret" + ["SERVER_PORT"]=> + int(80) + } + ["cookieParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["queryParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["uploadedFiles":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["parsedBody":"HttpMessage\ServerRequest":private]=> + NULL + ["attributes":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["method"]=> + string(5) "QUERY" + ["uri"]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(5) "https" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(15) "www.example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(11) "/foo/bar/22" + ["query":"HttpMessage\Uri":private]=> + string(3) "q=1" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } +} \ No newline at end of file diff --git a/tests/Factory/createServerRequest_err01.phpt b/tests/Factory/createServerRequest_err01.phpt new file mode 100644 index 0000000..004abc4 --- /dev/null +++ b/tests/Factory/createServerRequest_err01.phpt @@ -0,0 +1,36 @@ +--TEST-- +Factory::createServerRequest() with invalid arguments +--FILE-- +createServerRequest(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $request = $factory->createServerRequest(42); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $request = $factory->createServerRequest("GET", []); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $request = $factory->createServerRequest("GET", "http://www.example.com/", "hello"); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\Factory::createServerRequest() expects at least 2 %s, 0 given +HttpMessage\Factory::createServerRequest() expects at least 2 %s, 1 given +HttpMessage\Factory::createServerRequest() expects parameter 1 to be a string or object that implements Psr\Http\Message\UriInterface, array given +%sttpMessage\Factory::createServerRequest()%sarray, string given diff --git a/tests/Factory/createStreamFromFile_001.phpt b/tests/Factory/createStreamFromFile_001.phpt new file mode 100644 index 0000000..e732c28 --- /dev/null +++ b/tests/Factory/createStreamFromFile_001.phpt @@ -0,0 +1,43 @@ +--TEST-- +Factory::createStreamFromFile() +--FILE-- +createStreamFromFile(__FILE__); + +var_dump($stream); + +$resource = $stream->detach(); +var_dump(stream_get_meta_data($resource)); + +fseek($resource, 0); +var_dump(fread($resource, 16)); + +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +array(9) { + ["timed_out"]=> + bool(false) + ["blocked"]=> + bool(true) + ["eof"]=> + bool(false) + ["wrapper_type"]=> + string(9) "plainfile" + ["stream_type"]=> + string(5) "STDIO" + ["mode"]=> + string(1) "r" + ["unread_bytes"]=> + int(0) + ["seekable"]=> + bool(true) + ["uri"]=> + string(%d) "%screateStreamFromFile_001.php" +} +string(16) "createStreamFromFile([]); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream = $factory->createStreamFromFile(__FILE__, []); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +%sttpMessage\Factory::createStreamFromFile()%sstring, array given +%sttpMessage\Factory::createStreamFromFile()%sstring, array given diff --git a/tests/Factory/createStreamFromFile_003.phpt b/tests/Factory/createStreamFromFile_003.phpt new file mode 100644 index 0000000..28a92d1 --- /dev/null +++ b/tests/Factory/createStreamFromFile_003.phpt @@ -0,0 +1,38 @@ +--TEST-- +Factory::createStreamFromFile() with PHP stream +--FILE-- +createStreamFromFile('php://memory', 'w+'); + +var_dump($stream); + +$resource = $stream->detach(); +var_dump(stream_get_meta_data($resource)); + +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +array(9) { + ["timed_out"]=> + bool(false) + ["blocked"]=> + bool(true) + ["eof"]=> + bool(false) + ["wrapper_type"]=> + string(3) "PHP" + ["stream_type"]=> + string(6) "MEMORY" + ["mode"]=> + string(3) "w+b" + ["unread_bytes"]=> + int(0) + ["seekable"]=> + bool(true) + ["uri"]=> + string(12) "php://memory" +} \ No newline at end of file diff --git a/tests/Factory/createStreamFromFile_err01.phpt b/tests/Factory/createStreamFromFile_err01.phpt new file mode 100644 index 0000000..0d8b8f1 --- /dev/null +++ b/tests/Factory/createStreamFromFile_err01.phpt @@ -0,0 +1,22 @@ +--TEST-- +Factory::createStreamFromFile() with invalid args +--FILE-- +createStreamFromFile([]); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $factory->createStreamFromFile(__FILE__, []); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +%sttpMessage\Factory::createStreamFromFile()%sstring, array given +%sttpMessage\Factory::createStreamFromFile()%sstring, array given diff --git a/tests/Factory/createStreamFromFile_err02.phpt b/tests/Factory/createStreamFromFile_err02.phpt new file mode 100644 index 0000000..ffba733 --- /dev/null +++ b/tests/Factory/createStreamFromFile_err02.phpt @@ -0,0 +1,15 @@ +--TEST-- +Factory::createStreamFromFile() with non-existing file +--FILE-- +createStreamFromFile(__DIR__ . '/not/exists'); +} catch (RuntimeException $e) { + echo strtr($e->getMessage(), [DIRECTORY_SEPARATOR => '/']), "\n"; +} + +?> +--EXPECTF-- +Failed to open '%s/Factory/not/exists' stream \ No newline at end of file diff --git a/tests/Factory/createStreamFromResource_001.phpt b/tests/Factory/createStreamFromResource_001.phpt new file mode 100644 index 0000000..868e5ec --- /dev/null +++ b/tests/Factory/createStreamFromResource_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +Factory::createStreamFromResource() +--FILE-- +createStreamFromResource($resource); + +var_dump($stream); +var_dump($stream->detach() === $resource); +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +bool(true) \ No newline at end of file diff --git a/tests/Factory/createStreamFromResource_err01.phpt b/tests/Factory/createStreamFromResource_err01.phpt new file mode 100644 index 0000000..73ce60c --- /dev/null +++ b/tests/Factory/createStreamFromResource_err01.phpt @@ -0,0 +1,15 @@ +--TEST-- +Factory::createStreamFromResource() with invalid argument +--FILE-- +createStreamFromResource([]); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Expected parameter 1 to be a string or resource, array given \ No newline at end of file diff --git a/tests/Factory/createStreamFromResource_err02.phpt b/tests/Factory/createStreamFromResource_err02.phpt new file mode 100644 index 0000000..ce9104f --- /dev/null +++ b/tests/Factory/createStreamFromResource_err02.phpt @@ -0,0 +1,18 @@ +--TEST-- +Factory::createStreamFromResource() with closed stream +--FILE-- +createStreamFromResource($resource); +} catch (InvalidArgumentException $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Resource is not a stream diff --git a/tests/Factory/createStream_001.phpt b/tests/Factory/createStream_001.phpt new file mode 100644 index 0000000..ae3067e --- /dev/null +++ b/tests/Factory/createStream_001.phpt @@ -0,0 +1,36 @@ +--TEST-- +Factory::createStream() +--FILE-- +createStream(); + +var_dump($stream); + +$resource = $stream->detach(); +var_dump(stream_get_meta_data($resource)); + +fseek($resource, 0); +var_dump(fread($resource, 1024)); + +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +array(6) { + ["wrapper_type"]=> + string(3) "PHP" + ["stream_type"]=> + string(4) "TEMP" + ["mode"]=> + string(3) "w+b" + ["unread_bytes"]=> + int(0) + ["seekable"]=> + bool(true) + ["uri"]=> + string(10) "php://temp" +} +string(0) "" \ No newline at end of file diff --git a/tests/Factory/createStream_002.phpt b/tests/Factory/createStream_002.phpt new file mode 100644 index 0000000..6a3c160 --- /dev/null +++ b/tests/Factory/createStream_002.phpt @@ -0,0 +1,36 @@ +--TEST-- +Factory::createStream() with content +--FILE-- +createStream("hello"); + +var_dump($stream); + +$resource = $stream->detach(); +var_dump(stream_get_meta_data($resource)); + +fseek($resource, 0); +var_dump(fread($resource, 1024)); + +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +array(6) { + ["wrapper_type"]=> + string(3) "PHP" + ["stream_type"]=> + string(4) "TEMP" + ["mode"]=> + string(3) "w+b" + ["unread_bytes"]=> + int(0) + ["seekable"]=> + bool(true) + ["uri"]=> + string(10) "php://temp" +} +string(5) "hello" \ No newline at end of file diff --git a/tests/Factory/createStream_err01.phpt b/tests/Factory/createStream_err01.phpt new file mode 100644 index 0000000..be23af2 --- /dev/null +++ b/tests/Factory/createStream_err01.phpt @@ -0,0 +1,15 @@ +--TEST-- +Factory::createStream() with invalid argument +--FILE-- +createStream([]); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +%sttpMessage\Factory::createStream()%sstring, array given diff --git a/tests/Factory/createUploadedFile_001.phpt b/tests/Factory/createUploadedFile_001.phpt new file mode 100644 index 0000000..f435dc7 --- /dev/null +++ b/tests/Factory/createUploadedFile_001.phpt @@ -0,0 +1,50 @@ +--TEST-- +Factory::createUploadedFile() with a stream +--FILE-- +createUploadedFile($stream); + +var_dump($upload); +var_dump($upload->getSize()); +var_dump($upload->getError()); +var_dump($upload->getClientFilename()); +var_dump($upload->getClientMediaType()); + +var_dump($upload->getStream() === $stream); +var_dump($upload->getStream()); + +?> +--EXPECTF-- +object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["file":"HttpMessage\UploadedFile":private]=> + NULL + ["size":"HttpMessage\UploadedFile":private]=> + NULL + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) +} +NULL +int(0) +NULL +NULL +bool(true) +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} \ No newline at end of file diff --git a/tests/Factory/createUploadedFile_002.phpt b/tests/Factory/createUploadedFile_002.phpt new file mode 100644 index 0000000..94eb11c --- /dev/null +++ b/tests/Factory/createUploadedFile_002.phpt @@ -0,0 +1,50 @@ +--TEST-- +Factory::createUploadedFile() +--FILE-- +createUploadedFile($stream, 99, UPLOAD_ERR_OK, 'myfile.txt', 'text/plain'); + +var_dump($upload); +var_dump($upload->getSize()); +var_dump($upload->getError()); +var_dump($upload->getClientFilename()); +var_dump($upload->getClientMediaType()); + +var_dump($upload->getStream() === $stream); +var_dump($upload->getStream()); + +?> +--EXPECTF-- +object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["file":"HttpMessage\UploadedFile":private]=> + NULL + ["size":"HttpMessage\UploadedFile":private]=> + int(99) + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + string(10) "myfile.txt" + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + string(10) "text/plain" + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) +} +int(99) +int(0) +string(10) "myfile.txt" +string(10) "text/plain" +bool(true) +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} \ No newline at end of file diff --git a/tests/Factory/createUploadedFile_003.phpt b/tests/Factory/createUploadedFile_003.phpt new file mode 100644 index 0000000..d6ab638 --- /dev/null +++ b/tests/Factory/createUploadedFile_003.phpt @@ -0,0 +1,43 @@ +--TEST-- +Factory::createUploadedFile() with a stream and error +--FILE-- +createUploadedFile($stream, NULL, UPLOAD_ERR_INI_SIZE); + +var_dump($upload); +var_dump($upload->getSize()); +var_dump($upload->getError()); +var_dump($upload->getClientFilename()); +var_dump($upload->getClientMediaType()); + +?> +--CLEAN-- + +--EXPECTF-- +object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + NULL + ["size":"HttpMessage\UploadedFile":private]=> + NULL + ["error":"HttpMessage\UploadedFile":private]=> + int(1) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) +} +NULL +int(1) +NULL +NULL \ No newline at end of file diff --git a/tests/Factory/createUploadedFile_err01.phpt b/tests/Factory/createUploadedFile_err01.phpt new file mode 100644 index 0000000..0bc72b9 --- /dev/null +++ b/tests/Factory/createUploadedFile_err01.phpt @@ -0,0 +1,51 @@ +--TEST-- +Factory::createUploadedFile() with invalid argument +--FILE-- +createUploadedFile('foo'); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $factory->createUploadedFile((object)[]); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $factory->createUploadedFile($stream, [], UPLOAD_ERR_OK, 'myfile.txt', 'text/plain'); +} catch (TypeError $e) { + echo strtr($e->getMessage(), ['integer' => 'int']), "\n"; +} + +try { + $factory->createUploadedFile($stream, 99, [], 'myfile.txt', 'text/plain'); +} catch (TypeError $e) { + echo strtr($e->getMessage(), ['integer' => 'int']), "\n"; +} + +try { + $factory->createUploadedFile($stream, 99, UPLOAD_ERR_OK, [], 'text/plain'); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $factory->createUploadedFile($stream, 99, UPLOAD_ERR_OK, 'myfile.txt', []); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +%sttpMessage\Factory::createUploadedFile()%s Psr\Http\Message\StreamInterface, string given +%sttpMessage\Factory::createUploadedFile()%s Psr\Http\Message\StreamInterface,%sstdClass given +%sttpMessage\Factory::createUploadedFile()%s, array given +%sttpMessage\Factory::createUploadedFile()%sint, array given +%sttpMessage\Factory::createUploadedFile()%s, array given +%sttpMessage\Factory::createUploadedFile()%s, array given diff --git a/tests/Factory/createUploadedFile_err02.phpt b/tests/Factory/createUploadedFile_err02.phpt new file mode 100644 index 0000000..5065000 --- /dev/null +++ b/tests/Factory/createUploadedFile_err02.phpt @@ -0,0 +1,20 @@ +--TEST-- +Factory::createUploadedFile() with non-readable stream +--FILE-- +createUploadedFile($stream); +} catch (InvalidArgumentException $e) { + echo $e->getMessage(), "\n"; +} + +?> +--CLEAN-- + +--EXPECT-- +Stream provided for uploaded file is not readable \ No newline at end of file diff --git a/tests/Factory/createUri_001.phpt b/tests/Factory/createUri_001.phpt new file mode 100644 index 0000000..2520dd6 --- /dev/null +++ b/tests/Factory/createUri_001.phpt @@ -0,0 +1,28 @@ +--TEST-- +Factory::createUri() +--FILE-- +createUri(); + +var_dump($uri); + +?> +--EXPECTF-- +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(0) "" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(0) "" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" +} + diff --git a/tests/Factory/createUri_002.phpt b/tests/Factory/createUri_002.phpt new file mode 100644 index 0000000..4a880e0 --- /dev/null +++ b/tests/Factory/createUri_002.phpt @@ -0,0 +1,28 @@ +--TEST-- +Factory::createUri() +--FILE-- +createUri("https://example.com/path?foo=1&bar=2#frag"); + +var_dump($uri); + +?> +--EXPECTF-- +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(5) "https" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(11) "example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(5) "/path" + ["query":"HttpMessage\Uri":private]=> + string(11) "foo=1&bar=2" + ["fragment":"HttpMessage\Uri":private]=> + string(4) "frag" +} + diff --git a/tests/Message/body_001.phpt b/tests/Message/body_001.phpt new file mode 100644 index 0000000..8c0d394 --- /dev/null +++ b/tests/Message/body_001.phpt @@ -0,0 +1,24 @@ +--TEST-- +Message body +--FILE-- +withBody($stream); + +var_dump(get_class($response->getBody())); +var_dump($response->getBody()->getMetadata('uri')); + +var_dump(get_class($newResponse->getBody())); +var_dump($newResponse->getBody() === $stream); +var_dump($newResponse->getBody()->getMetadata('uri')); + +?> +--EXPECT-- +string(18) "HttpMessage\Stream" +string(10) "php://temp" +string(18) "HttpMessage\Stream" +bool(true) +string(12) "php://memory" \ No newline at end of file diff --git a/tests/Message/body_err01.phpt b/tests/Message/body_err01.phpt new file mode 100644 index 0000000..720ff6a --- /dev/null +++ b/tests/Message/body_err01.phpt @@ -0,0 +1,23 @@ +--TEST-- +Message::withBody() with invalid arguments +--FILE-- +withBody($resource); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $response->withBody(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +%sttpMessage\Message::withBody()%s Psr\Http\Message\StreamInterface, resource given +HttpMessage\Message::withBody() expects exactly 1 %s, 0 given diff --git a/tests/Message/headers_001.phpt b/tests/Message/headers_001.phpt new file mode 100644 index 0000000..67c6a6f --- /dev/null +++ b/tests/Message/headers_001.phpt @@ -0,0 +1,68 @@ +--TEST-- +Message headers +--FILE-- +withHeader('Foo', 'bar'); +$moreResponse = $fooResponse->withHeader('More', 'red'); + +var_dump($response->getHeaders()); +var_dump($response->hasHeader('Foo')); +var_dump($response->getHeader('Foo')); +var_dump($response->getHeaderLine('Foo')); + +echo "\n"; +var_dump($fooResponse->getHeaders()); +var_dump($fooResponse->hasHeader('Foo')); +var_dump($fooResponse->getHeader('Foo')); +var_dump($fooResponse->getHeaderLine('Foo')); + +echo "\n"; +var_dump($moreResponse->getHeaders()); +var_dump($moreResponse->hasHeader('More')); +var_dump($moreResponse->getHeader('More')); +var_dump($moreResponse->getHeaderLine('More')); +var_dump($moreResponse->getHeaderLine('Foo')); + +?> +--EXPECT-- +array(0) { +} +bool(false) +array(0) { +} +string(0) "" + +array(1) { + ["Foo"]=> + array(1) { + [0]=> + string(3) "bar" + } +} +bool(true) +array(1) { + [0]=> + string(3) "bar" +} +string(3) "bar" + +array(2) { + ["Foo"]=> + array(1) { + [0]=> + string(3) "bar" + } + ["More"]=> + array(1) { + [0]=> + string(3) "red" + } +} +bool(true) +array(1) { + [0]=> + string(3) "red" +} +string(3) "red" +string(3) "bar" \ No newline at end of file diff --git a/tests/Message/headers_002.phpt b/tests/Message/headers_002.phpt new file mode 100644 index 0000000..f52ca9d --- /dev/null +++ b/tests/Message/headers_002.phpt @@ -0,0 +1,68 @@ +--TEST-- +Message headers; added header / without header +--FILE-- +withHeader('Foo', 'bar') + ->withAddedHeader('Foo', 'baz'); +$fooResponse = $foofooResponse->withHeader('Foo', 'box'); +$response = $foofooResponse->withoutHeader('Foo'); + +var_dump($foofooResponse->getHeaders()); +var_dump($foofooResponse->hasHeader('Foo')); +var_dump($foofooResponse->getHeader('Foo')); +var_dump($foofooResponse->getHeaderLine('Foo')); + +echo "\n"; +var_dump($fooResponse->getHeaders()); +var_dump($fooResponse->hasHeader('Foo')); +var_dump($fooResponse->getHeader('Foo')); +var_dump($fooResponse->getHeaderLine('Foo')); + +echo "\n"; +var_dump($response->getHeaders()); +var_dump($response->hasHeader('Foo')); +var_dump($response->getHeader('Foo')); +var_dump($response->getHeaderLine('Foo')); + +?> +--EXPECT-- +array(1) { + ["Foo"]=> + array(2) { + [0]=> + string(3) "bar" + [1]=> + string(3) "baz" + } +} +bool(true) +array(2) { + [0]=> + string(3) "bar" + [1]=> + string(3) "baz" +} +string(8) "bar, baz" + +array(1) { + ["Foo"]=> + array(1) { + [0]=> + string(3) "box" + } +} +bool(true) +array(1) { + [0]=> + string(3) "box" +} +string(3) "box" + +array(0) { +} +bool(false) +array(0) { +} +string(0) "" diff --git a/tests/Message/headers_003.phpt b/tests/Message/headers_003.phpt new file mode 100644 index 0000000..c12d0fe --- /dev/null +++ b/tests/Message/headers_003.phpt @@ -0,0 +1,19 @@ +--TEST-- +Message header with numeric value +--FILE-- +withHeader('Content-Length', 100); + +var_dump($response->getHeaders()); + +?> +--EXPECT-- +array(1) { + ["Content-Length"]=> + array(1) { + [0]=> + string(3) "100" + } +} \ No newline at end of file diff --git a/tests/Message/headers_err01.phpt b/tests/Message/headers_err01.phpt new file mode 100644 index 0000000..797aff0 --- /dev/null +++ b/tests/Message/headers_err01.phpt @@ -0,0 +1,29 @@ +--TEST-- +Message::withHeader() with invalid arguments +--FILE-- +withHeader('Foo', ['a', 'b']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $response->withHeader(['Foo' => 'bar']); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $response->withHeader(['Foo' => 'bar'], null); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\Message::withHeader()%sstring, array given +HttpMessage\Message::withHeader() expects exactly 2 %s, 1 given +HttpMessage\Message::withHeader()%sstring, array given diff --git a/tests/Message/headers_err02.phpt b/tests/Message/headers_err02.phpt new file mode 100644 index 0000000..a9e5c9c --- /dev/null +++ b/tests/Message/headers_err02.phpt @@ -0,0 +1,29 @@ +--TEST-- +Message::withAddedHeader() with invalid arguments +--FILE-- +withAddedHeader('Foo', ['a', 'b']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $response->withAddedHeader(['Foo' => 'bar']); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $response->withAddedHeader(['Foo' => 'bar'], null); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\Message::withAddedHeader()%sstring, array given +HttpMessage\Message::withAddedHeader() expects exactly 2 %s, 1 given +HttpMessage\Message::withAddedHeader()%sstring, array given diff --git a/tests/Message/headers_err03.phpt b/tests/Message/headers_err03.phpt new file mode 100644 index 0000000..5888ab3 --- /dev/null +++ b/tests/Message/headers_err03.phpt @@ -0,0 +1,22 @@ +--TEST-- +Message::withoutHeader() with invalid arguments +--FILE-- +withoutHeader(['Foo' => 'bar']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $response->withoutHeader(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\Message::withoutHeader()%sstring, array given +HttpMessage\Message::withoutHeader() expects exactly 1 %s, 0 given diff --git a/tests/Message/headers_err04.phpt b/tests/Message/headers_err04.phpt new file mode 100644 index 0000000..b6a6762 --- /dev/null +++ b/tests/Message/headers_err04.phpt @@ -0,0 +1,22 @@ +--TEST-- +Message::getHeader() with invalid arguments +--FILE-- +getHeader(['Foo' => 'bar']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $response->getHeader(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\Message::getHeader()%sstring, array given +HttpMessage\Message::getHeader() expects exactly 1 %s, 0 given diff --git a/tests/Message/headers_err05.phpt b/tests/Message/headers_err05.phpt new file mode 100644 index 0000000..c2f2a58 --- /dev/null +++ b/tests/Message/headers_err05.phpt @@ -0,0 +1,22 @@ +--TEST-- +Message::getHeaderLine() with invalid arguments +--FILE-- +getHeaderLine(['Foo' => 'bar']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $response->getHeaderLine(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\Message::getHeaderLine()%sstring, array given +HttpMessage\Message::getHeaderLine() expects exactly 1 %s, 0 given diff --git a/tests/Message/protocolVersion_001.phpt b/tests/Message/protocolVersion_001.phpt new file mode 100644 index 0000000..241c1b1 --- /dev/null +++ b/tests/Message/protocolVersion_001.phpt @@ -0,0 +1,17 @@ +--TEST-- +Message::withProtocolVersion() +--FILE-- +withProtocolVersion('1.0'); +$response20 = $response10->withProtocolVersion('2.0'); + +var_dump($response10->getProtocolVersion()); +var_dump($response11->getProtocolVersion()); +var_dump($response20->getProtocolVersion()); + +?> +--EXPECT-- +string(3) "1.0" +string(3) "1.1" +string(3) "2.0" \ No newline at end of file diff --git a/tests/Message/protocolVersion_002.phpt b/tests/Message/protocolVersion_002.phpt new file mode 100644 index 0000000..f618671 --- /dev/null +++ b/tests/Message/protocolVersion_002.phpt @@ -0,0 +1,11 @@ +--TEST-- +Message::withProtocolVersion() with float +--FILE-- +withProtocolVersion(2.0); + +var_dump($response->getProtocolVersion()); +?> +--EXPECT-- +string(1) "2" \ No newline at end of file diff --git a/tests/Message/protocolVersion_err01.phpt b/tests/Message/protocolVersion_err01.phpt new file mode 100644 index 0000000..0f40115 --- /dev/null +++ b/tests/Message/protocolVersion_err01.phpt @@ -0,0 +1,21 @@ +--TEST-- +Message::withProtocolVersion() with invalid argument +--FILE-- +withProtocolVersion(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $response->withProtocolVersion(['foo', 'bar']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Message::withProtocolVersion() expects exactly 1 %s, 0 given +HttpMessage\Message::withProtocolVersion()%sstring, array given diff --git a/tests/Request/__construct_001.phpt b/tests/Request/__construct_001.phpt new file mode 100644 index 0000000..e929012 --- /dev/null +++ b/tests/Request/__construct_001.phpt @@ -0,0 +1,49 @@ +--TEST-- +Create a Request +--FILE-- +getRequestTarget()); +var_dump((string)$request->getUri()); +var_dump($request->getBody()->getMetadata('uri')); + +?> +--EXPECTF-- +object(HttpMessage\Request)#%d (6) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["requestTarget":"HttpMessage\Request":private]=> + NULL + ["method":"HttpMessage\Request":private]=> + string(0) "" + ["uri":"HttpMessage\Request":private]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(0) "" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(0) "" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } +} +string(1) "/" +string(0) "" +string(10) "php://temp" \ No newline at end of file diff --git a/tests/Request/method_001.phpt b/tests/Request/method_001.phpt new file mode 100644 index 0000000..0cd8c1a --- /dev/null +++ b/tests/Request/method_001.phpt @@ -0,0 +1,17 @@ +--TEST-- +Request::withMethod() +--FILE-- +withMethod('GET'); +$postRequest = $getRequest->withMethod('POST'); + +var_dump($request->getMethod()); +var_dump($getRequest->getMethod()); +var_dump($postRequest->getMethod()); + +?> +--EXPECT-- +string(0) "" +string(3) "GET" +string(4) "POST" \ No newline at end of file diff --git a/tests/Request/method_err01.phpt b/tests/Request/method_err01.phpt new file mode 100644 index 0000000..95c155f --- /dev/null +++ b/tests/Request/method_err01.phpt @@ -0,0 +1,22 @@ +--TEST-- +Request::withMethod() with invalid arguments +--FILE-- +withMethod(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $request->withMethod(['foo', 'bar']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\Request::withMethod() expects exactly 1 %s, 0 given +HttpMessage\Request::withMethod()%sstring, array given diff --git a/tests/Request/requestTarget_001.phpt b/tests/Request/requestTarget_001.phpt new file mode 100644 index 0000000..3b50b89 --- /dev/null +++ b/tests/Request/requestTarget_001.phpt @@ -0,0 +1,14 @@ +--TEST-- +Request::withRequestTarget() +--FILE-- +withRequestTarget('/foo'); + +var_dump($request->getRequestTarget()); +var_dump($newRequest->getRequestTarget()); + +?> +--EXPECT-- +string(1) "/" +string(4) "/foo" \ No newline at end of file diff --git a/tests/Request/requestTarget_002.phpt b/tests/Request/requestTarget_002.phpt new file mode 100644 index 0000000..42fb9aa --- /dev/null +++ b/tests/Request/requestTarget_002.phpt @@ -0,0 +1,19 @@ +--TEST-- +Request::withRequestTarget() using Uri object +--FILE-- +withUri(new HttpMessage\Uri('http://www.example.com/foo?color=red')); + +$fooRequest = $request->withRequestTarget('/bar'); +$defaultRequest = $fooRequest->withRequestTarget(null); + +var_dump($request->getRequestTarget()); +var_dump($fooRequest->getRequestTarget()); +var_dump($defaultRequest->getRequestTarget()); + +?> +--EXPECT-- +string(14) "/foo?color=red" +string(4) "/bar" +string(14) "/foo?color=red" \ No newline at end of file diff --git a/tests/Request/requestTarget_err01.phpt b/tests/Request/requestTarget_err01.phpt new file mode 100644 index 0000000..97f976e --- /dev/null +++ b/tests/Request/requestTarget_err01.phpt @@ -0,0 +1,22 @@ +--TEST-- +Request::withRequestTarget() with invalid arguments +--FILE-- +withRequestTarget(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $request->withRequestTarget(['foo', 'bar']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\Request::withRequestTarget() expects exactly 1 %s, 0 given +HttpMessage\Request::withRequestTarget()%sstring, array given diff --git a/tests/Request/uri_001.phpt b/tests/Request/uri_001.phpt new file mode 100644 index 0000000..e44328f --- /dev/null +++ b/tests/Request/uri_001.phpt @@ -0,0 +1,16 @@ +--TEST-- +Request::withUri() +--FILE-- +withUri($uri); + +var_dump($request->getUri() !== $uri); +var_dump($newRequest->getUri() === $uri); + +?> +--EXPECT-- +bool(true) +bool(true) \ No newline at end of file diff --git a/tests/Request/uri_err01.phpt b/tests/Request/uri_err01.phpt new file mode 100644 index 0000000..8803d73 --- /dev/null +++ b/tests/Request/uri_err01.phpt @@ -0,0 +1,22 @@ +--TEST-- +Request::withUri() with invalid arguments +--FILE-- +withUri(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $request->withUri('http://www.example.com/'); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\Request::withUri() expects exactly 1 %s, 0 given +%sttpMessage\Request::withUri()%sPsr\Http\Message\UriInterface, string given diff --git a/tests/Response/__construct_001.phpt b/tests/Response/__construct_001.phpt new file mode 100644 index 0000000..a112db0 --- /dev/null +++ b/tests/Response/__construct_001.phpt @@ -0,0 +1,27 @@ +--TEST-- +Create a Response +--FILE-- +getBody()->getMetadata('uri')); +?> +--EXPECTF-- +object(HttpMessage\Response)#%d (5) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["statusCode":"HttpMessage\Response":private]=> + int(0) + ["reasonPhrase":"HttpMessage\Response":private]=> + string(0) "" +} +string(10) "php://temp" \ No newline at end of file diff --git a/tests/Response/status_001.phpt b/tests/Response/status_001.phpt new file mode 100644 index 0000000..1ab889a --- /dev/null +++ b/tests/Response/status_001.phpt @@ -0,0 +1,16 @@ +--TEST-- +Response::withStatus() +--FILE-- +withStatus(200); + +var_dump($response->getStatusCode()); +var_dump($newResponse->getStatusCode()); +var_dump($newResponse->getReasonPhrase()); + +?> +--EXPECT-- +int(0) +int(200) +string(2) "OK" \ No newline at end of file diff --git a/tests/Response/status_002.phpt b/tests/Response/status_002.phpt new file mode 100644 index 0000000..f793d2f --- /dev/null +++ b/tests/Response/status_002.phpt @@ -0,0 +1,14 @@ +--TEST-- +Response::withStatus() with custom reason phrase +--FILE-- +withStatus(200, "ALL CLEAR"); +var_dump($newResponse->getStatusCode()); +var_dump($newResponse->getReasonPhrase()); + +?> +--EXPECT-- +int(200) +string(9) "ALL CLEAR" \ No newline at end of file diff --git a/tests/Response/status_003.phpt b/tests/Response/status_003.phpt new file mode 100644 index 0000000..f31dfab --- /dev/null +++ b/tests/Response/status_003.phpt @@ -0,0 +1,69 @@ +--TEST-- +Response::withStatus() with all known status codes +--FILE-- +withStatus($code); + echo $newResponse->getStatusCode(), ' ', $newResponse->getReasonPhrase(), "\n"; +} + +?> +--EXPECT-- +100 Continue +101 Switching Protocols +200 OK +201 Created +202 Accepted +203 Non-Authoritative Information +204 No Content +205 Reset Content +206 Partial Content +300 Multiple Choices +301 Moved Permanently +302 Found +303 See Other +304 Not Modified +305 Use Proxy +307 Temporary Redirect +308 Permanent Redirect +400 Bad Request +401 Unauthorized +402 Payment Required +403 Forbidden +404 Not Found +405 Method Not Allowed +406 Not Acceptable +407 Proxy Authentication Required +408 Request Timeout +409 Conflict +410 Gone +411 Length Required +412 Precondition Failed +413 Request Entity Too Large +414 Request-URI Too Long +415 Unsupported Media Type +416 Requested Range Not Satisfiable +417 Expectation Failed +426 Upgrade Required +428 Precondition Required +429 Too Many Requests +431 Request Header Fields Too Large +451 Unavailable For Legal Reasons +500 Internal Server Error +501 Not Implemented +502 Bad Gateway +503 Service Unavailable +504 Gateway Timeout +505 HTTP Version Not Supported +506 Variant Also Negotiates +511 Network Authentication Required diff --git a/tests/Response/status_004.phpt b/tests/Response/status_004.phpt new file mode 100644 index 0000000..75e0b0f --- /dev/null +++ b/tests/Response/status_004.phpt @@ -0,0 +1,14 @@ +--TEST-- +Response::withStatus() with an unknown status code +--FILE-- +withStatus(990); +var_dump($newResponse->getStatusCode()); +var_dump($newResponse->getReasonPhrase()); + +?> +--EXPECT-- +int(990) +string(0) "" \ No newline at end of file diff --git a/tests/Response/status_err01.phpt b/tests/Response/status_err01.phpt new file mode 100644 index 0000000..75c6a82 --- /dev/null +++ b/tests/Response/status_err01.phpt @@ -0,0 +1,29 @@ +--TEST-- +Response::withStatus() with invalid arguments +--FILE-- +withStatus(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $response->withStatus('ok'); +} catch (TypeError $e) { + echo strtr($e->getMessage(), ['integer' => 'int']), "\n"; +} + +try { + $response->withStatus(200, ['foo', 'bar']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\Response::withStatus() expects at least 1 %s, 0 given +HttpMessage\Response::withStatus()%sint, string given +HttpMessage\Response::withStatus()%sstring, array given diff --git a/tests/Response/status_err02.phpt b/tests/Response/status_err02.phpt new file mode 100644 index 0000000..f5040de --- /dev/null +++ b/tests/Response/status_err02.phpt @@ -0,0 +1,15 @@ +--TEST-- +Response::withStatus() with invalid status code +--FILE-- +withStatus(-1); +} catch (InvalidArgumentException $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Invalid HTTP status code -1 diff --git a/tests/ServerRequest/__construct_001.phpt b/tests/ServerRequest/__construct_001.phpt new file mode 100644 index 0000000..48a6405 --- /dev/null +++ b/tests/ServerRequest/__construct_001.phpt @@ -0,0 +1,60 @@ +--TEST-- +Create a ServerRequest without any arguments +--FILE-- + +--EXPECTF-- +object(HttpMessage\ServerRequest)#%d (12) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(0) { + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["requestTarget":"HttpMessage\Request":private]=> + NULL + ["method":"HttpMessage\Request":private]=> + string(0) "" + ["uri":"HttpMessage\Request":private]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(0) "" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(0) "" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } + ["serverParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["cookieParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["queryParams":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["uploadedFiles":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["parsedBody":"HttpMessage\ServerRequest":private]=> + NULL + ["attributes":"HttpMessage\ServerRequest":private]=> + array(0) { + } +} \ No newline at end of file diff --git a/tests/ServerRequest/__construct_002.phpt b/tests/ServerRequest/__construct_002.phpt new file mode 100644 index 0000000..e551e9b --- /dev/null +++ b/tests/ServerRequest/__construct_002.phpt @@ -0,0 +1,128 @@ +--TEST-- +Create a ServerRequest with params +--FILE-- + 'POST', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/', + ], + ['session' => 999], + ['id' => '42', 'persist' => 'yes'], + ['id' => 42, 'date' => '2019-01-01', 'categories' => [22, 44]], + [ + 'document' => [ + 'tmp_name' => sys_get_temp_dir() . '/uploadedfile', + 'size' => 2822, + 'error' => UPLOAD_ERR_OK, + 'name' => 'document.pdf', + 'type' => 'application/pdf', + ], + ] +); + +var_dump($request); + +?> +--EXPECTF-- +object(HttpMessage\ServerRequest)#%d (13) { + ["protocolVersion":"HttpMessage\Message":private]=> + string(3) "1.1" + ["headers":"HttpMessage\Message":private]=> + array(1) { + ["Host"]=> + array(1) { + [0]=> + string(11) "example.com" + } + } + ["body":"HttpMessage\Message":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["requestTarget":"HttpMessage\Request":private]=> + NULL + ["method":"HttpMessage\Request":private]=> + string(0) "" + ["uri":"HttpMessage\Request":private]=> + object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(0) "" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(11) "example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(6) "/foos/" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" + } + ["serverParams":"HttpMessage\ServerRequest":private]=> + array(3) { + ["REQUEST_METHOD"]=> + string(4) "POST" + ["HTTP_HOST"]=> + string(11) "example.com" + ["REQUEST_URI"]=> + string(6) "/foos/" + } + ["cookieParams":"HttpMessage\ServerRequest":private]=> + array(1) { + ["session"]=> + int(999) + } + ["queryParams":"HttpMessage\ServerRequest":private]=> + array(2) { + ["id"]=> + string(2) "42" + ["persist"]=> + string(3) "yes" + } + ["uploadedFiles":"HttpMessage\ServerRequest":private]=> + array(1) { + ["document"]=> + object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + string(%d) "%suploadedfile" + ["size":"HttpMessage\UploadedFile":private]=> + int(2822) + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + string(12) "document.pdf" + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + string(15) "application/pdf" + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) + } + } + ["parsedBody":"HttpMessage\ServerRequest":private]=> + array(3) { + ["id"]=> + int(42) + ["date"]=> + string(10) "2019-01-01" + ["categories"]=> + array(2) { + [0]=> + int(22) + [1]=> + int(44) + } + } + ["attributes":"HttpMessage\ServerRequest":private]=> + array(0) { + } + ["method"]=> + string(4) "POST" +} \ No newline at end of file diff --git a/tests/ServerRequest/__construct_003.phpt b/tests/ServerRequest/__construct_003.phpt new file mode 100644 index 0000000..e3712b2 --- /dev/null +++ b/tests/ServerRequest/__construct_003.phpt @@ -0,0 +1,270 @@ +--TEST-- +Create a ServerRequest with multiple uploaded files +--FILE-- + [ + 'tmp_name' => sys_get_temp_dir() . '/uploadedfile01', + 'size' => 2822, + 'error' => UPLOAD_ERR_OK, + 'name' => 'document.pdf', + 'type' => 'application/pdf', + ], + 'attachments' => [ + 'tmp_name' => [ + sys_get_temp_dir() . '/uploadedfile02', + sys_get_temp_dir() . '/uploadedfile03', + ], + 'size' => [ + 3724, + 263, + ], + 'error' => [ + UPLOAD_ERR_OK, + UPLOAD_ERR_OK, + ], + 'name' => [ + 'copy-id.pdf', + 'proof-of-residence.pdf', + ], + 'type' => [ + 'image/jpg', + 'application/pdf', + ], + ], + 'forms' => [ + 'tmp_name' => [ + 'g201' => sys_get_temp_dir() . '/uploadedfile04', + 'g202' => [ + 'a' => sys_get_temp_dir() . '/uploadedfile05', + 'c' => sys_get_temp_dir() . '/uploadedfile06', + ], + 'g301' => [ + sys_get_temp_dir() . '/uploadedfile07', + ], + ], + 'size' => [ + 'g201' => 942, + 'g202' => [ + 'a' => 2391, + 'c' => 485, + ], + 'g301' => [ + 4732, + 124901432, + ], + ], + 'error' => [ + 'g201' => UPLOAD_ERR_OK, + 'g202' => [ + 'a' => UPLOAD_ERR_OK, + 'b' => UPLOAD_ERR_NO_FILE, + 'c' => UPLOAD_ERR_PARTIAL, + ], + 'g301' => [ + UPLOAD_ERR_OK, + UPLOAD_ERR_FORM_SIZE, + ], + ], + 'name' => [ + ], + 'type' => [ + ], + ], + ] +); + +var_dump($request->getUploadedFiles()); + +?> +--EXPECTF-- +array(3) { + ["document"]=> + object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + string(%d) "%suploadedfile01" + ["size":"HttpMessage\UploadedFile":private]=> + int(2822) + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + string(12) "document.pdf" + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + string(15) "application/pdf" + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) + } + ["attachments"]=> + array(2) { + [0]=> + object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + string(%d) "%suploadedfile02" + ["size":"HttpMessage\UploadedFile":private]=> + int(3724) + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + string(11) "copy-id.pdf" + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + string(9) "image/jpg" + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) + } + [1]=> + object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + string(%d) "%suploadedfile03" + ["size":"HttpMessage\UploadedFile":private]=> + int(263) + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + string(22) "proof-of-residence.pdf" + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + string(15) "application/pdf" + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) + } + } + ["forms"]=> + array(3) { + ["g201"]=> + object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + string(%d) "%suploadedfile04" + ["size":"HttpMessage\UploadedFile":private]=> + int(942) + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) + } + ["g202"]=> + array(3) { + ["a"]=> + object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + string(%d) "%suploadedfile05" + ["size":"HttpMessage\UploadedFile":private]=> + int(2391) + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) + } + ["b"]=> + object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + NULL + ["size":"HttpMessage\UploadedFile":private]=> + NULL + ["error":"HttpMessage\UploadedFile":private]=> + int(4) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) + } + ["c"]=> + object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + NULL + ["size":"HttpMessage\UploadedFile":private]=> + int(485) + ["error":"HttpMessage\UploadedFile":private]=> + int(3) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) + } + } + ["g301"]=> + array(2) { + [0]=> + object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + string(%d) "%suploadedfile07" + ["size":"HttpMessage\UploadedFile":private]=> + int(4732) + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) + } + [1]=> + object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + NULL + ["size":"HttpMessage\UploadedFile":private]=> + int(124901432) + ["error":"HttpMessage\UploadedFile":private]=> + int(2) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) + } + } + } +} \ No newline at end of file diff --git a/tests/ServerRequest/__construct_004.phpt b/tests/ServerRequest/__construct_004.phpt new file mode 100644 index 0000000..cb2fcaf --- /dev/null +++ b/tests/ServerRequest/__construct_004.phpt @@ -0,0 +1,76 @@ +--TEST-- +Create a ServerRequest with server params +--FILE-- + 'HTTP/1.1', + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', + 'CONTENT_LENGTH' => 1324, + 'HTTP_ACCEPT' => 'text/html', + 'HTTP_ACCEPT_CHARSET' => 'utf-8', + 'HTTP_ACCEPT_ENCODING' => 'zip, deflate', + 'PHP_AUTH_USER' => 'john', + 'PHP_AUTH_PASS' => 'secret', + 'SERVER_PORT' => 80, + ] +); + +var_dump($request->getMethod()); +var_dump($request->getUri()); +var_dump($request->getHeaders()); + +?> +--EXPECTF-- +string(0) "" +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(4) "http" + ["userInfo":"HttpMessage\Uri":private]=> + string(11) "john:secret" + ["host":"HttpMessage\Uri":private]=> + string(11) "example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(6) "/foos/" + ["query":"HttpMessage\Uri":private]=> + string(5) "bar=1" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" +} +array(6) { + ["Host"]=> + array(1) { + [0]=> + string(11) "example.com" + } + ["Accept"]=> + array(1) { + [0]=> + string(9) "text/html" + } + ["Accept-Charset"]=> + array(1) { + [0]=> + string(5) "utf-8" + } + ["Accept-Encoding"]=> + array(1) { + [0]=> + string(12) "zip, deflate" + } + ["Content-Type"]=> + array(1) { + [0]=> + string(33) "application/x-www-form-urlencoded" + } + ["Content-Length"]=> + array(1) { + [0]=> + int(1324) + } +} \ No newline at end of file diff --git a/tests/ServerRequest/__construct_005.phpt b/tests/ServerRequest/__construct_005.phpt new file mode 100644 index 0000000..bb1d45e --- /dev/null +++ b/tests/ServerRequest/__construct_005.phpt @@ -0,0 +1,152 @@ +--TEST-- +Create a ServerRequest with server params for uri +--FILE-- + 'HTTP/1.1', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'SERVER_PORT' => 80, + ] +); +echo $request->getUri(), "\n"; + +// HTTP (explicit) +$request = new HttpMessage\ServerRequest( + [ + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTPS' => 'off', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'SERVER_PORT' => 80, + ] +); +echo $request->getUri(), "\n"; + +// HTTPS +$request = new HttpMessage\ServerRequest( + [ + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTPS' => 'on', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'SERVER_PORT' => 443, + ] +); +echo $request->getUri(), "\n"; + +// Custom HTTP port +$request = new HttpMessage\ServerRequest( + [ + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTPS' => 'off', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'SERVER_PORT' => 8080, + ] +); +echo $request->getUri(), "\n"; + +// HTTPS on port 80 +$request = new HttpMessage\ServerRequest( + [ + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTPS' => 'on', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'SERVER_PORT' => 80, + ] +); +echo $request->getUri(), "\n"; + +// No protocol, but default HTTP port +$request = new HttpMessage\ServerRequest( + [ + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'SERVER_PORT' => 80, + ] +); +echo $request->getUri(), "\n"; + +// No protocol, but default HTTPS port +$request = new HttpMessage\ServerRequest( + [ + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'SERVER_PORT' => 80, + ] +); +echo $request->getUri(), "\n"; + +// No protocol, no port +$request = new HttpMessage\ServerRequest( + [ + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + ] +); +echo $request->getUri(), "\n"; + +// User +$request = new HttpMessage\ServerRequest( + [ + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'PHP_AUTH_USER' => 'john' + ] +); +echo $request->getUri(), "\n"; + +// User + pass +$request = new HttpMessage\ServerRequest( + [ + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'PHP_AUTH_USER' => 'john', + 'PHP_AUTH_PASS' => 'secret', + ] +); +echo $request->getUri(), "\n"; + +// Only pass (ignored) +$request = new HttpMessage\ServerRequest( + [ + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'PHP_AUTH_PASS' => 'secret', + ] +); +echo $request->getUri(), "\n"; + +// Query string overwrite +$request = new HttpMessage\ServerRequest( + [ + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foos/?bar=1', + 'QUERY_STRING' => 'bar=99', + ] +); +echo $request->getUri(), "\n"; + + +?> +--EXPECT-- +http://example.com/foos/?bar=1 +http://example.com/foos/?bar=1 +https://example.com/foos/?bar=1 +http://example.com:8080/foos/?bar=1 +https://example.com:80/foos/?bar=1 +http://example.com/foos/?bar=1 +http://example.com/foos/?bar=1 +//example.com/foos/?bar=1 +http://john@example.com/foos/?bar=1 +http://john:secret@example.com/foos/?bar=1 +http://example.com/foos/?bar=1 +http://example.com/foos/?bar=99 \ No newline at end of file diff --git a/tests/ServerRequest/__construct_err01.phpt b/tests/ServerRequest/__construct_err01.phpt new file mode 100644 index 0000000..586d505 --- /dev/null +++ b/tests/ServerRequest/__construct_err01.phpt @@ -0,0 +1,49 @@ +--TEST-- +Create ServerRequest with invalid arguments +--FILE-- +getMessage(), "\n"; +} + +try { + new HttpMessage\ServerRequest([], ''); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + new HttpMessage\ServerRequest([], [], ''); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + new HttpMessage\ServerRequest([], [], ''); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + new HttpMessage\ServerRequest([], [], [], ''); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + new HttpMessage\ServerRequest([], [], [], [], ''); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +%sttpMessage\ServerRequest::__construct()%sarray, string given +%sttpMessage\ServerRequest::__construct()%sarray, string given +%sttpMessage\ServerRequest::__construct()%sarray, string given +%sttpMessage\ServerRequest::__construct()%sarray, string given +%sttpMessage\ServerRequest::__construct()%sarray, string given +%sttpMessage\ServerRequest::__construct()%sarray, string given diff --git a/tests/ServerRequest/attributes_001.phpt b/tests/ServerRequest/attributes_001.phpt new file mode 100644 index 0000000..2ae8c05 --- /dev/null +++ b/tests/ServerRequest/attributes_001.phpt @@ -0,0 +1,52 @@ +--TEST-- +ServerRequest::withAttribute() and ServerRequest::withoutAttribute() +--FILE-- +withAttribute('foo', 'bar') + ->withAttribute('colors', ['red', 'green']) + ->withAttribute('user', ['id' => 22, 'name' => 'John']); + +$newRequest = $request + ->withoutAttribute('foo') + ->withAttribute('user', ['id' => 32, 'name' => 'Jane']); + +var_dump($request->getAttributes()); +var_dump($newRequest->getAttributes()); + +?> +--EXPECT-- +array(3) { + ["foo"]=> + string(3) "bar" + ["colors"]=> + array(2) { + [0]=> + string(3) "red" + [1]=> + string(5) "green" + } + ["user"]=> + array(2) { + ["id"]=> + int(22) + ["name"]=> + string(4) "John" + } +} +array(2) { + ["colors"]=> + array(2) { + [0]=> + string(3) "red" + [1]=> + string(5) "green" + } + ["user"]=> + array(2) { + ["id"]=> + int(32) + ["name"]=> + string(4) "Jane" + } +} \ No newline at end of file diff --git a/tests/ServerRequest/attributes_002.phpt b/tests/ServerRequest/attributes_002.phpt new file mode 100644 index 0000000..a453a0d --- /dev/null +++ b/tests/ServerRequest/attributes_002.phpt @@ -0,0 +1,40 @@ +--TEST-- +ServerRequest::getAttribute() +--FILE-- +withAttribute('foo', 'bar') + ->withAttribute('colors', ['red', 'green']) + ->withAttribute('user', ['id' => 22, 'name' => 'John']); + +var_dump($request->getAttributes()); +var_dump($request->getAttribute('colors', [])); +var_dump($request->getAttribute('qux', 42)); + +?> +--EXPECT-- +array(3) { + ["foo"]=> + string(3) "bar" + ["colors"]=> + array(2) { + [0]=> + string(3) "red" + [1]=> + string(5) "green" + } + ["user"]=> + array(2) { + ["id"]=> + int(22) + ["name"]=> + string(4) "John" + } +} +array(2) { + [0]=> + string(3) "red" + [1]=> + string(5) "green" +} +int(42) \ No newline at end of file diff --git a/tests/ServerRequest/attributes_err01.phpt b/tests/ServerRequest/attributes_err01.phpt new file mode 100644 index 0000000..c66373b --- /dev/null +++ b/tests/ServerRequest/attributes_err01.phpt @@ -0,0 +1,22 @@ +--TEST-- +ServerRequest::withAttribute() with invalid arguments +--FILE-- +withAttribute('foo'); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $request->withAttribute(['foo' => 'bar'], 22); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\ServerRequest::withAttribute() expects exactly 2 %s, 1 given +HttpMessage\ServerRequest::withAttribute()%s string, array given diff --git a/tests/ServerRequest/cookieParams_001.phpt b/tests/ServerRequest/cookieParams_001.phpt new file mode 100644 index 0000000..e559659 --- /dev/null +++ b/tests/ServerRequest/cookieParams_001.phpt @@ -0,0 +1,30 @@ +--TEST-- +ServerRequest::withCookieParams() +--FILE-- +withCookieParams([ + 'foo' => 'bar', + 'color' => 'green', + 'user' => 22, + ]); + +$newRequest = $request->withCookieParams(['user' => 32]); + +var_dump($request->getCookieParams()); +var_dump($newRequest->getCookieParams()); + +?> +--EXPECT-- +array(3) { + ["foo"]=> + string(3) "bar" + ["color"]=> + string(5) "green" + ["user"]=> + int(22) +} +array(1) { + ["user"]=> + int(32) +} \ No newline at end of file diff --git a/tests/ServerRequest/cookieParams_err01.phpt b/tests/ServerRequest/cookieParams_err01.phpt new file mode 100644 index 0000000..1dbe3e6 --- /dev/null +++ b/tests/ServerRequest/cookieParams_err01.phpt @@ -0,0 +1,22 @@ +--TEST-- +ServerRequest::withCookieParams() with invalid arguments +--FILE-- +withCookieParams(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $request->withCookieParams('foo=bar;'); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\ServerRequest::withCookieParams() expects exactly 1 %s, 0 given +%sttpMessage\ServerRequest::withCookieParams()%s array, string given diff --git a/tests/ServerRequest/parsedBody_001.phpt b/tests/ServerRequest/parsedBody_001.phpt new file mode 100644 index 0000000..8b37527 --- /dev/null +++ b/tests/ServerRequest/parsedBody_001.phpt @@ -0,0 +1,40 @@ +--TEST-- +ServerRequest::withParsedBody() +--FILE-- +withParsedBody([ + 'foo' => 'bar', + 'color' => 'green', + 'user' => 22, +]); + +$objRequest = $request->withParsedBody((object)[ + 'color' => 'red', + 'user' => 42, +]); + +$nullRequest = $objRequest->withParsedBody(null); + +var_dump($arrRequest->getParsedBody()); +var_dump($objRequest->getParsedBody()); +var_dump($nullRequest->getParsedBody()); + +?> +--EXPECTF-- +array(3) { + ["foo"]=> + string(3) "bar" + ["color"]=> + string(5) "green" + ["user"]=> + int(22) +} +object(stdClass)#%d (2) { + ["color"]=> + string(3) "red" + ["user"]=> + int(42) +} +NULL \ No newline at end of file diff --git a/tests/ServerRequest/parsedBody_err01.phpt b/tests/ServerRequest/parsedBody_err01.phpt new file mode 100644 index 0000000..9f5b980 --- /dev/null +++ b/tests/ServerRequest/parsedBody_err01.phpt @@ -0,0 +1,22 @@ +--TEST-- +ServerRequest::withParsedBody() with invalid arguments +--FILE-- +withQueryParams(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $request->withParsedBody('foo=bar;'); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\ServerRequest::withQueryParams() expects exactly 1 %s, 0 given +HttpMessage\ServerRequest::withParsedBody()%sarray, string given diff --git a/tests/ServerRequest/queryParams_001.phpt b/tests/ServerRequest/queryParams_001.phpt new file mode 100644 index 0000000..afe06d7 --- /dev/null +++ b/tests/ServerRequest/queryParams_001.phpt @@ -0,0 +1,30 @@ +--TEST-- +ServerRequest::withQueryParams() +--FILE-- +withQueryParams([ + 'foo' => 'bar', + 'color' => 'green', + 'user' => 22, + ]); + +$newRequest = $request->withQueryParams(['user' => 32]); + +var_dump($request->getQueryParams()); +var_dump($newRequest->getQueryParams()); + +?> +--EXPECT-- +array(3) { + ["foo"]=> + string(3) "bar" + ["color"]=> + string(5) "green" + ["user"]=> + int(22) +} +array(1) { + ["user"]=> + int(32) +} \ No newline at end of file diff --git a/tests/ServerRequest/queryParams_err01.phpt b/tests/ServerRequest/queryParams_err01.phpt new file mode 100644 index 0000000..a7dd760 --- /dev/null +++ b/tests/ServerRequest/queryParams_err01.phpt @@ -0,0 +1,22 @@ +--TEST-- +ServerRequest::withQueryParams() with invalid arguments +--FILE-- +withQueryParams(); +} catch (ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $request->withQueryParams('foo=bar;'); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\ServerRequest::withQueryParams() expects exactly 1 %s, 0 given +%sttpMessage\ServerRequest::withQueryParams()%s array, string given diff --git a/tests/Stream/__construct_001.phpt b/tests/Stream/__construct_001.phpt new file mode 100644 index 0000000..4b3cf55 --- /dev/null +++ b/tests/Stream/__construct_001.phpt @@ -0,0 +1,29 @@ +--TEST-- +Create Stream without arguments +--FILE-- +detach(); +var_dump(stream_get_meta_data($resource)); +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +array(6) { + ["wrapper_type"]=> + string(3) "PHP" + ["stream_type"]=> + string(4) "TEMP" + ["mode"]=> + string(3) "w+b" + ["unread_bytes"]=> + int(0) + ["seekable"]=> + bool(true) + ["uri"]=> + string(10) "php://temp" +} \ No newline at end of file diff --git a/tests/Stream/__construct_002.phpt b/tests/Stream/__construct_002.phpt new file mode 100644 index 0000000..581334c --- /dev/null +++ b/tests/Stream/__construct_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Create Stream with resource +--FILE-- +detach() === $resource); +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +bool(true) \ No newline at end of file diff --git a/tests/Stream/__construct_003.phpt b/tests/Stream/__construct_003.phpt new file mode 100644 index 0000000..8fde2aa --- /dev/null +++ b/tests/Stream/__construct_003.phpt @@ -0,0 +1,35 @@ +--TEST-- +Create Stream with file name +--FILE-- +detach(); +var_dump(stream_get_meta_data($resource)); +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +array(9) { + ["timed_out"]=> + bool(false) + ["blocked"]=> + bool(true) + ["eof"]=> + bool(false) + ["wrapper_type"]=> + string(9) "plainfile" + ["stream_type"]=> + string(5) "STDIO" + ["mode"]=> + string(1) "r" + ["unread_bytes"]=> + int(0) + ["seekable"]=> + bool(true) + ["uri"]=> + string(%d) "%s__construct_003.php" +} \ No newline at end of file diff --git a/tests/Stream/__construct_004.phpt b/tests/Stream/__construct_004.phpt new file mode 100644 index 0000000..2634434 --- /dev/null +++ b/tests/Stream/__construct_004.phpt @@ -0,0 +1,35 @@ +--TEST-- +Create Stream with PHP stream name and mode +--FILE-- +detach(); +var_dump(stream_get_meta_data($resource)); +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +array(9) { + ["timed_out"]=> + bool(false) + ["blocked"]=> + bool(true) + ["eof"]=> + bool(false) + ["wrapper_type"]=> + string(3) "PHP" + ["stream_type"]=> + string(6) "MEMORY" + ["mode"]=> + string(3) "w+b" + ["unread_bytes"]=> + int(0) + ["seekable"]=> + bool(true) + ["uri"]=> + string(12) "php://memory" +} \ No newline at end of file diff --git a/tests/Stream/__construct_err01.phpt b/tests/Stream/__construct_err01.phpt new file mode 100644 index 0000000..ddf21d1 --- /dev/null +++ b/tests/Stream/__construct_err01.phpt @@ -0,0 +1,15 @@ +--TEST-- +Create Stream with closed stream +--FILE-- +getMessage(); +} +?> +--EXPECT-- +Resource is not a stream diff --git a/tests/Stream/__construct_err02.phpt b/tests/Stream/__construct_err02.phpt new file mode 100644 index 0000000..e75a21d --- /dev/null +++ b/tests/Stream/__construct_err02.phpt @@ -0,0 +1,12 @@ +--TEST-- +Create Stream with invalid argument +--FILE-- +getMessage(); +} +?> +--EXPECT-- +Expected parameter 1 to be a string or resource, array given diff --git a/tests/Stream/__construct_err03.phpt b/tests/Stream/__construct_err03.phpt new file mode 100644 index 0000000..979d8e8 --- /dev/null +++ b/tests/Stream/__construct_err03.phpt @@ -0,0 +1,12 @@ +--TEST-- +Create Stream with non-existing file +--FILE-- +getMessage(), [DIRECTORY_SEPARATOR => '/']); +} +?> +--EXPECTF-- +Failed to open '%s/Stream/not/exists' stream diff --git a/tests/Stream/__toString_001.phpt b/tests/Stream/__toString_001.phpt new file mode 100644 index 0000000..fb036a9 --- /dev/null +++ b/tests/Stream/__toString_001.phpt @@ -0,0 +1,19 @@ +--TEST-- +Cast Stream to string +--FILE-- + +--EXPECT-- +string(26) "abcdefghijklmnopqrstuvwxyz" +string(26) "abcdefghijklmnopqrstuvwxyz" +string(26) "abcdefghijklmnopqrstuvwxyz" diff --git a/tests/Stream/__toString_002.phpt b/tests/Stream/__toString_002.phpt new file mode 100644 index 0000000..1cbcb2e --- /dev/null +++ b/tests/Stream/__toString_002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cast unreadable Stream to string from non-readable stream +--FILE-- + +--CLEAN-- + +--EXPECT-- +string(0) "" diff --git a/tests/Stream/__toString_003.phpt b/tests/Stream/__toString_003.phpt new file mode 100644 index 0000000..e4fabb9 --- /dev/null +++ b/tests/Stream/__toString_003.phpt @@ -0,0 +1,19 @@ +--TEST-- +Cast unreadable Stream to string from file +--FILE-- + +--CLEAN-- + +--EXPECT-- +string(3) "foo" \ No newline at end of file diff --git a/tests/Stream/close_001.phpt b/tests/Stream/close_001.phpt new file mode 100644 index 0000000..a508ad6 --- /dev/null +++ b/tests/Stream/close_001.phpt @@ -0,0 +1,20 @@ +--TEST-- +Stream::close() +--FILE-- +close(); +var_dump($stream); + +var_dump(get_resource_type($resource)); + +$stream->close(); // No error +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (Unknown) +} +string(7) "Unknown" \ No newline at end of file diff --git a/tests/Stream/close_002.phpt b/tests/Stream/close_002.phpt new file mode 100644 index 0000000..e43fc46 --- /dev/null +++ b/tests/Stream/close_002.phpt @@ -0,0 +1,22 @@ +--TEST-- +Stream::close() on already closed +--FILE-- +close(); +var_dump($stream); +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (Unknown) +} +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (Unknown) +} \ No newline at end of file diff --git a/tests/Stream/close_003.phpt b/tests/Stream/close_003.phpt new file mode 100644 index 0000000..ad97612 --- /dev/null +++ b/tests/Stream/close_003.phpt @@ -0,0 +1,72 @@ +--TEST-- +Stream::close() +--FILE-- +close(); + +try { + $stream->tell(); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->seek(1); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->rewind(); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->read(1); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->getContents(); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->write("abc"); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +var_dump((string)$stream); +var_dump($stream->getSize()); + +var_dump($stream->eof()); +var_dump($stream->isSeekable()); +var_dump($stream->isWritable()); +var_dump($stream->isReadable()); + +var_dump($stream->getMetadata()); +var_dump($stream->getMetadata('uri')); +?> +--EXPECT-- +Stream is closed +Stream is closed +Stream is closed +Stream is closed +Stream is closed +Stream is closed +string(0) "" +NULL +bool(true) +bool(false) +bool(false) +bool(false) +array(0) { +} +NULL diff --git a/tests/Stream/detach_001.phpt b/tests/Stream/detach_001.phpt new file mode 100644 index 0000000..934ce1e --- /dev/null +++ b/tests/Stream/detach_001.phpt @@ -0,0 +1,25 @@ +--TEST-- +Stream::detach() +--FILE-- +detach(); +var_dump($stream); + +var_dump($ret === $resource); +var_dump(get_resource_type($resource)); + +var_dump($stream->detach()); + +$stream->close(); // Calling close() while detached should be ignored +?> +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + NULL +} +bool(true) +string(6) "stream" +NULL \ No newline at end of file diff --git a/tests/Stream/detach_002.phpt b/tests/Stream/detach_002.phpt new file mode 100644 index 0000000..ae25eea --- /dev/null +++ b/tests/Stream/detach_002.phpt @@ -0,0 +1,50 @@ +--TEST-- +Stream::detach() call all methods +--FILE-- +detach(); + +try { + $stream->tell(); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->seek(1); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->rewind(); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +var_dump($stream->getSize()); + +var_dump($stream->eof()); +var_dump($stream->isSeekable()); +var_dump($stream->isWritable()); +var_dump($stream->isReadable()); + +var_dump($stream->getMetadata()); +var_dump($stream->getMetadata('uri')); +?> +--EXPECT-- +Stream is detached +Stream is detached +Stream is detached +NULL +bool(true) +bool(false) +bool(false) +bool(false) +array(0) { +} +NULL diff --git a/tests/Stream/detach_003.phpt b/tests/Stream/detach_003.phpt new file mode 100644 index 0000000..81a50a2 --- /dev/null +++ b/tests/Stream/detach_003.phpt @@ -0,0 +1,73 @@ +--TEST-- +Stream::detach() call all methods +--FILE-- +detach(); + +try { + $stream->tell(); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->seek(1); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->rewind(); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->read(1); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->getContents(); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $stream->write("abc"); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} + +var_dump((string)$stream); +var_dump($stream->getSize()); + +var_dump($stream->eof()); +var_dump($stream->isSeekable()); +var_dump($stream->isWritable()); +var_dump($stream->isReadable()); + +var_dump($stream->getMetadata()); +var_dump($stream->getMetadata('uri')); +?> +--EXPECT-- +Stream is detached +Stream is detached +Stream is detached +Stream is detached +Stream is detached +Stream is detached +string(0) "" +NULL +bool(true) +bool(false) +bool(false) +bool(false) +array(0) { +} +NULL diff --git a/tests/Stream/eof_001.phpt b/tests/Stream/eof_001.phpt new file mode 100644 index 0000000..4638a3e --- /dev/null +++ b/tests/Stream/eof_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +Stream::eof() +--FILE-- +eof()); + +fread($resource, 1); +var_dump($stream->eof()); + +fwrite($resource, "abcdef"); +var_dump($stream->eof()); + +fseek($resource, 0); +var_dump($stream->eof()); + +fread($resource, 3); +var_dump($stream->eof()); + +fread($resource, 4); +var_dump($stream->eof()); +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) +bool(false) +bool(false) +bool(true) diff --git a/tests/Stream/eof_002.phpt b/tests/Stream/eof_002.phpt new file mode 100644 index 0000000..b3e099b --- /dev/null +++ b/tests/Stream/eof_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Stream::eof() call all methods +--FILE-- +eof()); + +fclose($resource); +var_dump($stream->eof()); + +?> +--EXPECT-- +bool(false) +bool(true) diff --git a/tests/Stream/eof_003.phpt b/tests/Stream/eof_003.phpt new file mode 100644 index 0000000..a01a589 --- /dev/null +++ b/tests/Stream/eof_003.phpt @@ -0,0 +1,16 @@ +--TEST-- +Stream::eof() +--FILE-- +eof()); + +$stream->detach(); +var_dump($stream->eof()); + +?> +--EXPECT-- +bool(false) +bool(true) diff --git a/tests/Stream/getContents_001.phpt b/tests/Stream/getContents_001.phpt new file mode 100644 index 0000000..37e497d --- /dev/null +++ b/tests/Stream/getContents_001.phpt @@ -0,0 +1,16 @@ +--TEST-- +Stream::getContents() +--FILE-- +getContents()); +var_dump($stream->getContents()); +?> +--EXPECT-- +string(26) "abcdefghijklmnopqrstuvwxyz" +string(0) "" diff --git a/tests/Stream/getContents_002.phpt b/tests/Stream/getContents_002.phpt new file mode 100644 index 0000000..c2f5a13 --- /dev/null +++ b/tests/Stream/getContents_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Stream::getContents() +--FILE-- +getContents()); +var_dump($stream->getContents()); +?> +--EXPECT-- +string(16) "klmnopqrstuvwxyz" +string(0) "" diff --git a/tests/Stream/getContents_err01.phpt b/tests/Stream/getContents_err01.phpt new file mode 100644 index 0000000..50df873 --- /dev/null +++ b/tests/Stream/getContents_err01.phpt @@ -0,0 +1,19 @@ +--TEST-- +Stream::getContents() with non-readable stream +--FILE-- +getContents(); +} catch (RuntimeException $e) { + echo $e->getMessage(); +} +?> +--CLEAN-- + +--EXPECT-- +Stream not readable diff --git a/tests/Stream/getMetadata_001.phpt b/tests/Stream/getMetadata_001.phpt new file mode 100644 index 0000000..b3bc0bf --- /dev/null +++ b/tests/Stream/getMetadata_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +Stream::getMetadata() +--FILE-- +getMetadata()); + +?> +--EXPECT-- +array(9) { + ["timed_out"]=> + bool(false) + ["blocked"]=> + bool(true) + ["eof"]=> + bool(false) + ["wrapper_type"]=> + string(3) "PHP" + ["stream_type"]=> + string(6) "MEMORY" + ["mode"]=> + string(3) "w+b" + ["unread_bytes"]=> + int(0) + ["seekable"]=> + bool(true) + ["uri"]=> + string(12) "php://memory" +} diff --git a/tests/Stream/getMetadata_002.phpt b/tests/Stream/getMetadata_002.phpt new file mode 100644 index 0000000..18f5e2d --- /dev/null +++ b/tests/Stream/getMetadata_002.phpt @@ -0,0 +1,17 @@ +--TEST-- +Stream::getMetadata() with key +--FILE-- +getMetadata('uri')); +var_dump($stream->getMetadata('blocked')); + +var_dump($stream->getMetadata('not_exist')); + +?> +--EXPECT-- +string(12) "php://memory" +bool(true) +NULL diff --git a/tests/Stream/getMetadata_003.phpt b/tests/Stream/getMetadata_003.phpt new file mode 100644 index 0000000..2a33d28 --- /dev/null +++ b/tests/Stream/getMetadata_003.phpt @@ -0,0 +1,30 @@ +--TEST-- +Stream::getMetadata() +--FILE-- +getMetadata()); + +?> +--EXPECTF-- +array(9) { + ["timed_out"]=> + bool(false) + ["blocked"]=> + bool(true) + ["eof"]=> + bool(false) + ["wrapper_type"]=> + string(9) "plainfile" + ["stream_type"]=> + string(5) "STDIO" + ["mode"]=> + string(1) "r" + ["unread_bytes"]=> + int(0) + ["seekable"]=> + bool(true) + ["uri"]=> + string(%d) "%sgetMetadata_003.php" +} diff --git a/tests/Stream/getSize_001.phpt b/tests/Stream/getSize_001.phpt new file mode 100644 index 0000000..9f9e7eb --- /dev/null +++ b/tests/Stream/getSize_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +Stream::getSize() +--FILE-- +getSize()); + +fwrite($resource, "abcdef"); +var_dump($stream->getSize()); + +fwrite($resource, "ghi"); +var_dump($stream->getSize()); + +fseek($resource, 0); +var_dump($stream->getSize()); + +fwrite($resource, "1234567890abcdef"); +var_dump($stream->getSize()); + +fclose($resource); +var_dump($stream->getSize()); +?> +--EXPECT-- +int(0) +int(6) +int(9) +int(9) +int(16) +NULL diff --git a/tests/Stream/isReadable_001.phpt b/tests/Stream/isReadable_001.phpt new file mode 100644 index 0000000..8208aa9 --- /dev/null +++ b/tests/Stream/isReadable_001.phpt @@ -0,0 +1,9 @@ +--TEST-- +Stream::isReadable() with readable stream +--FILE-- +isReadable()); +?> +--EXPECT-- +bool(true) diff --git a/tests/Stream/isReadable_002.phpt b/tests/Stream/isReadable_002.phpt new file mode 100644 index 0000000..62f21fe --- /dev/null +++ b/tests/Stream/isReadable_002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Stream::isReadable() with non-readable stream +--FILE-- +isReadable()); +?> +--CLEAN-- + +--EXPECT-- +bool(false) diff --git a/tests/Stream/isSeekable_001.phpt b/tests/Stream/isSeekable_001.phpt new file mode 100644 index 0000000..30eb9ac --- /dev/null +++ b/tests/Stream/isSeekable_001.phpt @@ -0,0 +1,9 @@ +--TEST-- +Stream::isSeekable() with seekable stream +--FILE-- +isSeekable()); +?> +--EXPECT-- +bool(true) diff --git a/tests/Stream/isSeekable_002.phpt b/tests/Stream/isSeekable_002.phpt new file mode 100644 index 0000000..9b477fa --- /dev/null +++ b/tests/Stream/isSeekable_002.phpt @@ -0,0 +1,9 @@ +--TEST-- +Stream::isSeekable() with unseekable stream +--FILE-- +isSeekable()); +?> +--EXPECT-- +bool(false) diff --git a/tests/Stream/isWritable_001.phpt b/tests/Stream/isWritable_001.phpt new file mode 100644 index 0000000..4fdb095 --- /dev/null +++ b/tests/Stream/isWritable_001.phpt @@ -0,0 +1,11 @@ +--TEST-- +Stream::isWritable() with writable stream +--FILE-- +isWritable()); +?> +--EXPECT-- +bool(true) diff --git a/tests/Stream/isWritable_002.phpt b/tests/Stream/isWritable_002.phpt new file mode 100644 index 0000000..a6b38c0 --- /dev/null +++ b/tests/Stream/isWritable_002.phpt @@ -0,0 +1,9 @@ +--TEST-- +Stream::isWritable() with non-writable stream +--FILE-- +isWritable()); +?> +--EXPECT-- +bool(false) diff --git a/tests/Stream/read_001.phpt b/tests/Stream/read_001.phpt new file mode 100644 index 0000000..368fff4 --- /dev/null +++ b/tests/Stream/read_001.phpt @@ -0,0 +1,20 @@ +--TEST-- +Stream::read() +--FILE-- +read(10)); +var_dump($stream->read(10)); +var_dump($stream->read(10)); +var_dump($stream->read(10)); +?> +--EXPECT-- +string(10) "abcdefghij" +string(10) "klmnopqrst" +string(6) "uvwxyz" +string(0) "" diff --git a/tests/Stream/read_err01.phpt b/tests/Stream/read_err01.phpt new file mode 100644 index 0000000..d2076ff --- /dev/null +++ b/tests/Stream/read_err01.phpt @@ -0,0 +1,15 @@ +--TEST-- +Stream::read() error: no arguments +--FILE-- +read(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Stream::read() expects exactly 1 %s, 0 given diff --git a/tests/Stream/read_err02.phpt b/tests/Stream/read_err02.phpt new file mode 100644 index 0000000..3a32dee --- /dev/null +++ b/tests/Stream/read_err02.phpt @@ -0,0 +1,22 @@ +--TEST-- +Stream::read() err: invalid arguments +--FILE-- +read('hello'); +} catch (Error $e) { + echo strtr($e->getMessage(), ['integer' => 'int']), "\n"; +} + +try { + $stream->read(-1); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Stream::read()%s int, string given +Length parameter must be equal or greater than 0 diff --git a/tests/Stream/read_err03.phpt b/tests/Stream/read_err03.phpt new file mode 100644 index 0000000..4d75943 --- /dev/null +++ b/tests/Stream/read_err03.phpt @@ -0,0 +1,19 @@ +--TEST-- +Stream::read() with non-readable stream +--FILE-- +read(1); +} catch (RuntimeException $e) { + echo $e->getMessage(); +} +?> +--CLEAN-- + +--EXPECT-- +Stream not readable diff --git a/tests/Stream/rewind_001.phpt b/tests/Stream/rewind_001.phpt new file mode 100644 index 0000000..66e9a87 --- /dev/null +++ b/tests/Stream/rewind_001.phpt @@ -0,0 +1,17 @@ +--TEST-- +Stream::rewind() +--FILE-- +rewind(); +var_dump(fread($resource, 4)); +?> +--EXPECT-- +string(0) "" +string(3) "abc" diff --git a/tests/Stream/seek_001.phpt b/tests/Stream/seek_001.phpt new file mode 100644 index 0000000..1785f3a --- /dev/null +++ b/tests/Stream/seek_001.phpt @@ -0,0 +1,34 @@ +--TEST-- +Stream::seek() +--FILE-- +seek(10); +var_dump(fread($resource, 64)); + +$stream->seek(3, SEEK_SET); +var_dump(fread($resource, 6)); + +$stream->seek(4, SEEK_CUR); +var_dump(fread($resource, 6)); + +$stream->seek(-3, SEEK_CUR); +var_dump(fread($resource, 6)); + +$stream->seek(-12, SEEK_END); +var_dump(fread($resource, 6)); + +$stream->seek(100); +var_dump(fread($resource, 6)); +?> +--EXPECT-- +string(16) "klmnopqrstuvwxyz" +string(6) "defghi" +string(6) "nopqrs" +string(6) "qrstuv" +string(6) "opqrst" +string(0) "" diff --git a/tests/Stream/seek_err01.phpt b/tests/Stream/seek_err01.phpt new file mode 100644 index 0000000..724eac5 --- /dev/null +++ b/tests/Stream/seek_err01.phpt @@ -0,0 +1,15 @@ +--TEST-- +Stream::seek() error: no arguments +--FILE-- +seek(); +} catch (Error $e) { + echo $e->getMessage(); +} +?> +--EXPECTF-- +HttpMessage\Stream::seek() expects at least 1 %s, 0 given diff --git a/tests/Stream/seek_err02.phpt b/tests/Stream/seek_err02.phpt new file mode 100644 index 0000000..8ab5755 --- /dev/null +++ b/tests/Stream/seek_err02.phpt @@ -0,0 +1,22 @@ +--TEST-- +Stream::seek() error: invalid arguments +--FILE-- +seek('hello'); +} catch (Error $e) { + echo strtr($e->getMessage(), ['integer' => 'int']), "\n"; +} + +try { + $stream->seek(1, 'hello'); +} catch (Error $e) { + echo strtr($e->getMessage(), ['integer' => 'int']), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Stream::seek()%s int, string given +HttpMessage\Stream::seek()%s int, string given diff --git a/tests/Stream/seek_err03.phpt b/tests/Stream/seek_err03.phpt new file mode 100644 index 0000000..11aeb91 --- /dev/null +++ b/tests/Stream/seek_err03.phpt @@ -0,0 +1,15 @@ +--TEST-- +Stream::seek() error: invalid whence +--FILE-- +seek(1, 999); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Invalid value for whence \ No newline at end of file diff --git a/tests/Stream/seek_err04.phpt b/tests/Stream/seek_err04.phpt new file mode 100644 index 0000000..8502952 --- /dev/null +++ b/tests/Stream/seek_err04.phpt @@ -0,0 +1,14 @@ +--TEST-- +Stream::seek() error: unseekable stream +--FILE-- +seek(0); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Stream is not seekable \ No newline at end of file diff --git a/tests/Stream/tell_001.phpt b/tests/Stream/tell_001.phpt new file mode 100644 index 0000000..c0413b6 --- /dev/null +++ b/tests/Stream/tell_001.phpt @@ -0,0 +1,27 @@ +--TEST-- +Stream::tell() +--FILE-- +tell()); + +fwrite($resource, "abcdef"); +var_dump($stream->tell()); + +fwrite($resource, "ghi"); +var_dump($stream->tell()); + +fseek($resource, 0); +var_dump($stream->tell()); + +fwrite($resource, "1234567890abcdef"); +var_dump($stream->tell()); +?> +--EXPECT-- +int(0) +int(6) +int(9) +int(0) +int(16) diff --git a/tests/Stream/write_001.phpt b/tests/Stream/write_001.phpt new file mode 100644 index 0000000..ec46aab --- /dev/null +++ b/tests/Stream/write_001.phpt @@ -0,0 +1,16 @@ +--TEST-- +Stream::write() +--FILE-- +write('hello'); +$stream->write(' world'); + +fseek($resource, 0); +var_dump(fread($resource, 100)); +?> +--EXPECT-- +string(11) "hello world" diff --git a/tests/Stream/write_err01.phpt b/tests/Stream/write_err01.phpt new file mode 100644 index 0000000..4a3a775 --- /dev/null +++ b/tests/Stream/write_err01.phpt @@ -0,0 +1,15 @@ +--TEST-- +Stream::write() error: no arguments +--FILE-- +write(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Stream::write() expects exactly 1 %s, 0 given diff --git a/tests/Stream/write_err02.phpt b/tests/Stream/write_err02.phpt new file mode 100644 index 0000000..933b338 --- /dev/null +++ b/tests/Stream/write_err02.phpt @@ -0,0 +1,15 @@ +--TEST-- +Stream::read() err: invalid arguments +--FILE-- +write(['foo' => 'bar']); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Stream::write()%s string, array given diff --git a/tests/Stream/write_err03.phpt b/tests/Stream/write_err03.phpt new file mode 100644 index 0000000..78f70ee --- /dev/null +++ b/tests/Stream/write_err03.phpt @@ -0,0 +1,14 @@ +--TEST-- +Stream::read() with non-readable stream +--FILE-- +write('hello'); +} catch (RuntimeException $e) { + echo $e->getMessage(); +} +?> +--EXPECT-- +Stream not writable diff --git a/tests/UploadedFile/__construct_001.phpt b/tests/UploadedFile/__construct_001.phpt new file mode 100644 index 0000000..10bbb10 --- /dev/null +++ b/tests/UploadedFile/__construct_001.phpt @@ -0,0 +1,43 @@ +--TEST-- +Create UploadedFile +--FILE-- +getSize()); +var_dump($upload->getError()); +var_dump($upload->getClientFilename()); +var_dump($upload->getClientMediaType()); + +?> +--CLEAN-- + +--EXPECTF-- +object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + string(%d) "%s/uploadedfile" + ["size":"HttpMessage\UploadedFile":private]=> + int(99) + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + string(10) "myfile.txt" + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + string(10) "text/plain" + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) +} +int(99) +int(0) +string(10) "myfile.txt" +string(10) "text/plain" \ No newline at end of file diff --git a/tests/UploadedFile/__construct_002.phpt b/tests/UploadedFile/__construct_002.phpt new file mode 100644 index 0000000..8b61383 --- /dev/null +++ b/tests/UploadedFile/__construct_002.phpt @@ -0,0 +1,43 @@ +--TEST-- +Create UploadedFile with only a path +--FILE-- +getSize()); +var_dump($upload->getError()); +var_dump($upload->getClientFilename()); +var_dump($upload->getClientMediaType()); + +?> +--CLEAN-- + +--EXPECTF-- +object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + string(%d) "%s/uploadedfile" + ["size":"HttpMessage\UploadedFile":private]=> + NULL + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) +} +NULL +int(0) +NULL +NULL \ No newline at end of file diff --git a/tests/UploadedFile/__construct_003.phpt b/tests/UploadedFile/__construct_003.phpt new file mode 100644 index 0000000..0c28021 --- /dev/null +++ b/tests/UploadedFile/__construct_003.phpt @@ -0,0 +1,43 @@ +--TEST-- +Create UploadedFile with path and error +--FILE-- +getSize()); +var_dump($upload->getError()); +var_dump($upload->getClientFilename()); +var_dump($upload->getClientMediaType()); + +?> +--CLEAN-- + +--EXPECTF-- +object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + NULL + ["size":"HttpMessage\UploadedFile":private]=> + NULL + ["error":"HttpMessage\UploadedFile":private]=> + int(1) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) +} +NULL +int(1) +NULL +NULL \ No newline at end of file diff --git a/tests/UploadedFile/__construct_004.phpt b/tests/UploadedFile/__construct_004.phpt new file mode 100644 index 0000000..c995015 --- /dev/null +++ b/tests/UploadedFile/__construct_004.phpt @@ -0,0 +1,36 @@ +--TEST-- +Create UploadedFile with only an error +--FILE-- +getSize()); +var_dump($upload->getError()); +var_dump($upload->getClientFilename()); +var_dump($upload->getClientMediaType()); + +?> +--EXPECTF-- +object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + NULL + ["file":"HttpMessage\UploadedFile":private]=> + NULL + ["size":"HttpMessage\UploadedFile":private]=> + NULL + ["error":"HttpMessage\UploadedFile":private]=> + int(4) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) +} +NULL +int(4) +NULL +NULL \ No newline at end of file diff --git a/tests/UploadedFile/__construct_005.phpt b/tests/UploadedFile/__construct_005.phpt new file mode 100644 index 0000000..af79d5a --- /dev/null +++ b/tests/UploadedFile/__construct_005.phpt @@ -0,0 +1,48 @@ +--TEST-- +Create UploadedFile with a stream +--FILE-- +getSize()); +var_dump($upload->getError()); +var_dump($upload->getClientFilename()); +var_dump($upload->getClientMediaType()); + +var_dump($upload->getStream() === $stream); +var_dump($upload->getStream()); + +?> +--EXPECTF-- +object(HttpMessage\UploadedFile)#%d (8) { + ["stream":"HttpMessage\UploadedFile":private]=> + object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) + } + ["file":"HttpMessage\UploadedFile":private]=> + NULL + ["size":"HttpMessage\UploadedFile":private]=> + NULL + ["error":"HttpMessage\UploadedFile":private]=> + int(0) + ["clientFilename":"HttpMessage\UploadedFile":private]=> + NULL + ["clientMediaType":"HttpMessage\UploadedFile":private]=> + NULL + ["moved":"HttpMessage\UploadedFile":private]=> + bool(false) + ["checkUploaded":"HttpMessage\UploadedFile":private]=> + bool(false) +} +NULL +int(0) +NULL +NULL +bool(true) +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} diff --git a/tests/UploadedFile/__construct_err01.phpt b/tests/UploadedFile/__construct_err01.phpt new file mode 100644 index 0000000..51cf776 --- /dev/null +++ b/tests/UploadedFile/__construct_err01.phpt @@ -0,0 +1,50 @@ +--TEST-- +Create UploadedFile with invalid arguments +--FILE-- +getMessage(), "\n"; +} + +try { + new HttpMessage\UploadedFile((object)[]); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + new HttpMessage\UploadedFile($stream, [], UPLOAD_ERR_OK, 'myfile.txt', 'text/plain'); +} catch (TypeError $e) { + echo strtr($e->getMessage(), ['integer' => 'int']), "\n"; +} + +try { + new HttpMessage\UploadedFile($stream, 99, [], 'myfile.txt', 'text/plain'); +} catch (TypeError $e) { + echo strtr($e->getMessage(), ['integer' => 'int']), "\n"; +} + +try { + new HttpMessage\UploadedFile($stream, 99, UPLOAD_ERR_OK, [], 'text/plain'); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + new HttpMessage\UploadedFile($stream, 99, UPLOAD_ERR_OK, 'myfile.txt', []); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +HttpMessage\UploadedFile::__construct() expects parameter 1 to be a string or object that implements Psr\Http\Message\StreamInterface, array given +HttpMessage\UploadedFile::__construct() expects parameter 1 to be a string or object that implements Psr\Http\Message\StreamInterface, %s given +%sttpMessage\UploadedFile::__construct()%s, array given +%sttpMessage\UploadedFile::__construct()%s, array given +%sttpMessage\UploadedFile::__construct()%s, array given +%sttpMessage\UploadedFile::__construct()%s, array given diff --git a/tests/UploadedFile/__construct_err02.phpt b/tests/UploadedFile/__construct_err02.phpt new file mode 100644 index 0000000..94233fc --- /dev/null +++ b/tests/UploadedFile/__construct_err02.phpt @@ -0,0 +1,15 @@ +--TEST-- +Create UploadedFile with a non-readable stream +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +Stream provided for uploaded file is not readable \ No newline at end of file diff --git a/tests/UploadedFile/getStream_001.phpt b/tests/UploadedFile/getStream_001.phpt new file mode 100644 index 0000000..19dbbe6 --- /dev/null +++ b/tests/UploadedFile/getStream_001.phpt @@ -0,0 +1,29 @@ +--TEST-- +UploadedFile::getStream() +--FILE-- +getStream(); + +var_dump($stream); +var_dump($stream->getMetadata('uri')); +var_dump((string)$stream); +var_dump($stream == $upload->getStream()); + +?> +--CLEAN-- + +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +string(%d) "%s/uploadedfile" +string(3) "foo" +bool(true) \ No newline at end of file diff --git a/tests/UploadedFile/getStream_002.phpt b/tests/UploadedFile/getStream_002.phpt new file mode 100644 index 0000000..15d2abc --- /dev/null +++ b/tests/UploadedFile/getStream_002.phpt @@ -0,0 +1,37 @@ +--TEST-- +UploadedFile::getStream() after failed move +--FILE-- +moveTo(sys_get_temp_dir() . '/nosuchdir/movedfile'); +} catch (RuntimeException $e) { + // expected +} + +$stream = $upload->getStream(); + +var_dump($stream); +var_dump($stream->getMetadata('uri')); +var_dump((string)$stream); +var_dump($stream == $upload->getStream()); + +?> +--CLEAN-- + +--EXPECTF-- +object(HttpMessage\Stream)#%d (1) { + ["stream":"HttpMessage\Stream":private]=> + resource(%d) of type (stream) +} +string(%d) "%s/uploadedfile" +string(3) "foo" +bool(true) \ No newline at end of file diff --git a/tests/UploadedFile/getStream_err01.phpt b/tests/UploadedFile/getStream_err01.phpt new file mode 100644 index 0000000..5364c3e --- /dev/null +++ b/tests/UploadedFile/getStream_err01.phpt @@ -0,0 +1,14 @@ +--TEST-- +UploadedFile::getStream() without a file +--FILE-- +getStream(); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +No file was uploaded or uploaded file not available \ No newline at end of file diff --git a/tests/UploadedFile/getStream_err02.phpt b/tests/UploadedFile/getStream_err02.phpt new file mode 100644 index 0000000..963655c --- /dev/null +++ b/tests/UploadedFile/getStream_err02.phpt @@ -0,0 +1,27 @@ +--TEST-- +UploadedFile::getStream() after move +--FILE-- +moveTo(sys_get_temp_dir() . '/movedfile'); + +try { + $upload->getStream(); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} +?> +--CLEAN-- + +--EXPECTF-- +Uploaded file '%s/uploadedfile' has already been moved diff --git a/tests/UploadedFile/getStream_err03.phpt b/tests/UploadedFile/getStream_err03.phpt new file mode 100644 index 0000000..1658cd1 --- /dev/null +++ b/tests/UploadedFile/getStream_err03.phpt @@ -0,0 +1,14 @@ +--TEST-- +UploadedFile::getStream() with non-existing file +--FILE-- +getStream(); +} catch (RuntimeException $e) { + echo strtr($e->getMessage(), [DIRECTORY_SEPARATOR => '/']), "\n"; +} +?> +--EXPECTF-- +Failed to open '%s/UploadedFile/not/exist' stream \ No newline at end of file diff --git a/tests/UploadedFile/moveTo_001.phpt b/tests/UploadedFile/moveTo_001.phpt new file mode 100644 index 0000000..1d62c16 --- /dev/null +++ b/tests/UploadedFile/moveTo_001.phpt @@ -0,0 +1,22 @@ +--TEST-- +UploadedFile::getStream() after move +--FILE-- +moveTo($target); + +var_dump(file_get_contents($target)); + +?> +--CLEAN-- + +--EXPECT-- +string(3) "foo" \ No newline at end of file diff --git a/tests/UploadedFile/moveTo_002.phpt b/tests/UploadedFile/moveTo_002.phpt new file mode 100644 index 0000000..7413256 --- /dev/null +++ b/tests/UploadedFile/moveTo_002.phpt @@ -0,0 +1,28 @@ +--TEST-- +UploadedFile::moveTo() after failed move +--FILE-- +moveTo(sys_get_temp_dir() . '/nosuchdir/movedfile'); +} catch (RuntimeException $e) { + // expected +} + +$target = sys_get_temp_dir() . '/movedfile'; +$upload->moveTo($target); + +var_dump(file_get_contents($target)); + +?> +--CLEAN-- + +--EXPECT-- +string(3) "foo" \ No newline at end of file diff --git a/tests/UploadedFile/moveTo_003.phpt b/tests/UploadedFile/moveTo_003.phpt new file mode 100644 index 0000000..b2022e7 --- /dev/null +++ b/tests/UploadedFile/moveTo_003.phpt @@ -0,0 +1,22 @@ +--TEST-- +UploadedFile::moveTo() with stream +--FILE-- +moveTo($target); + +var_dump(file_get_contents($target)); + +?> +--CLEAN-- + +--EXPECT-- +string(3) "foo" \ No newline at end of file diff --git a/tests/UploadedFile/moveTo_err01.phpt b/tests/UploadedFile/moveTo_err01.phpt new file mode 100644 index 0000000..955da15 --- /dev/null +++ b/tests/UploadedFile/moveTo_err01.phpt @@ -0,0 +1,14 @@ +--TEST-- +UploadedFile::moveTo() without a file +--FILE-- +moveTo(sys_get_temp_dir() . '/movedfile'); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +No file was uploaded or uploaded file not available \ No newline at end of file diff --git a/tests/UploadedFile/moveTo_err02.phpt b/tests/UploadedFile/moveTo_err02.phpt new file mode 100644 index 0000000..a88bc92 --- /dev/null +++ b/tests/UploadedFile/moveTo_err02.phpt @@ -0,0 +1,30 @@ +--TEST-- +UploadedFile::moveTo() after move +--FILE-- +moveTo(sys_get_temp_dir() . '/movedfile'); + +try { + $upload->moveTo(sys_get_temp_dir() . '/other'); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} +?> +--CLEAN-- + +--EXPECTF-- +Uploaded file '%s/uploadedfile' has already been moved diff --git a/tests/UploadedFile/moveTo_err03.phpt b/tests/UploadedFile/moveTo_err03.phpt new file mode 100644 index 0000000..46d1ab7 --- /dev/null +++ b/tests/UploadedFile/moveTo_err03.phpt @@ -0,0 +1,24 @@ +--TEST-- +UploadedFile::moveTo() invalid target +--FILE-- +moveTo(sys_get_temp_dir() . '/nosuchdir/movedfile'); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} +?> +--CLEAN-- + +--EXPECTF-- +Warning: HttpMessage\UploadedFile::moveTo(%s/nosuchdir/movedfile): %sailed to open stream: No such file or directory in %smoveTo_err03.php on line %d +Failed to move uploaded file '%s/uploadedfile' to '%s/nosuchdir/movedfile' diff --git a/tests/UploadedFile/moveTo_err04.phpt b/tests/UploadedFile/moveTo_err04.phpt new file mode 100644 index 0000000..e625dc0 --- /dev/null +++ b/tests/UploadedFile/moveTo_err04.phpt @@ -0,0 +1,26 @@ +--TEST-- +UploadedFile::moveTo() with is_uploaded check +--FILE-- +moveTo(sys_get_temp_dir() . '/movedfile'); +} catch (RuntimeException $e) { + echo $e->getMessage(), "\n"; +} +?> +--CLEAN-- + +--EXPECTF-- +Won't move '%s/uploadedfile'; not an uploaded file \ No newline at end of file diff --git a/tests/UploadedFile/moveTo_err05.phpt b/tests/UploadedFile/moveTo_err05.phpt new file mode 100644 index 0000000..f4d5807 --- /dev/null +++ b/tests/UploadedFile/moveTo_err05.phpt @@ -0,0 +1,22 @@ +--TEST-- +UploadedFile::moveTo() with non-existing file +--FILE-- +moveTo(sys_get_temp_dir() . '/movedfile'); +} catch (RuntimeException $e) { + echo strtr($e->getMessage(), [DIRECTORY_SEPARATOR => '/']), "\n"; +} + +?> +--CLEAN-- + +--EXPECTF-- +Warning: HttpMessage\UploadedFile::moveTo(%s): %sailed to open stream: No such file or directory in %s on line %d +Failed to move uploaded file '%s/UploadedFile/not/exist' to '%s/movedfile' diff --git a/tests/Uri/__construct_001.phpt b/tests/Uri/__construct_001.phpt new file mode 100644 index 0000000..3095ccb --- /dev/null +++ b/tests/Uri/__construct_001.phpt @@ -0,0 +1,23 @@ +--TEST-- +Create Uri without arguments +--FILE-- + +--EXPECTF-- +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(0) "" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(0) "" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" +} \ No newline at end of file diff --git a/tests/Uri/__construct_002.phpt b/tests/Uri/__construct_002.phpt new file mode 100644 index 0000000..b01db3d --- /dev/null +++ b/tests/Uri/__construct_002.phpt @@ -0,0 +1,23 @@ +--TEST-- +Create Uri with a basic url +--FILE-- + +--EXPECTF-- +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(5) "https" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(15) "www.example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" +} \ No newline at end of file diff --git a/tests/Uri/__construct_003.phpt b/tests/Uri/__construct_003.phpt new file mode 100644 index 0000000..ebfa8b8 --- /dev/null +++ b/tests/Uri/__construct_003.phpt @@ -0,0 +1,23 @@ +--TEST-- +Create Uri with a full url +--FILE-- + +--EXPECTF-- +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(4) "http" + ["userInfo":"HttpMessage\Uri":private]=> + string(4) "acme" + ["host":"HttpMessage\Uri":private]=> + string(15) "www.example.com" + ["port":"HttpMessage\Uri":private]=> + int(8000) + ["path":"HttpMessage\Uri":private]=> + string(4) "/foo" + ["query":"HttpMessage\Uri":private]=> + string(9) "answer=42" + ["fragment":"HttpMessage\Uri":private]=> + string(8) "question" +} \ No newline at end of file diff --git a/tests/Uri/__construct_004.phpt b/tests/Uri/__construct_004.phpt new file mode 100644 index 0000000..b472627 --- /dev/null +++ b/tests/Uri/__construct_004.phpt @@ -0,0 +1,23 @@ +--TEST-- +Create Uri with username and password +--FILE-- + +--EXPECTF-- +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(5) "https" + ["userInfo":"HttpMessage\Uri":private]=> + string(11) "acme:secure" + ["host":"HttpMessage\Uri":private]=> + string(15) "www.example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" +} \ No newline at end of file diff --git a/tests/Uri/__construct_005.phpt b/tests/Uri/__construct_005.phpt new file mode 100644 index 0000000..3b5af9f --- /dev/null +++ b/tests/Uri/__construct_005.phpt @@ -0,0 +1,23 @@ +--TEST-- +Create Uri with a basic url without a path +--FILE-- + +--EXPECTF-- +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(5) "https" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(15) "www.example.com" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(0) "" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" +} \ No newline at end of file diff --git a/tests/Uri/__construct_006.phpt b/tests/Uri/__construct_006.phpt new file mode 100644 index 0000000..17e209b --- /dev/null +++ b/tests/Uri/__construct_006.phpt @@ -0,0 +1,23 @@ +--TEST-- +Create Uri with a domain name +--FILE-- + +--EXPECTF-- +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(0) "" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(0) "" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(15) "www.example.com" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" +} \ No newline at end of file diff --git a/tests/Uri/__construct_007.phpt b/tests/Uri/__construct_007.phpt new file mode 100644 index 0000000..7f965c1 --- /dev/null +++ b/tests/Uri/__construct_007.phpt @@ -0,0 +1,23 @@ +--TEST-- +Create Uri with an absolute path +--FILE-- + +--EXPECTF-- +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(0) "" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(0) "" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(4) "/foo" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" +} \ No newline at end of file diff --git a/tests/Uri/__construct_008.phpt b/tests/Uri/__construct_008.phpt new file mode 100644 index 0000000..92905a0 --- /dev/null +++ b/tests/Uri/__construct_008.phpt @@ -0,0 +1,23 @@ +--TEST-- +Create Uri with a relative path +--FILE-- + +--EXPECTF-- +object(HttpMessage\Uri)#%d (7) { + ["scheme":"HttpMessage\Uri":private]=> + string(0) "" + ["userInfo":"HttpMessage\Uri":private]=> + string(0) "" + ["host":"HttpMessage\Uri":private]=> + string(0) "" + ["port":"HttpMessage\Uri":private]=> + NULL + ["path":"HttpMessage\Uri":private]=> + string(3) "foo" + ["query":"HttpMessage\Uri":private]=> + string(0) "" + ["fragment":"HttpMessage\Uri":private]=> + string(0) "" +} \ No newline at end of file diff --git a/tests/Uri/__construct_err01.phpt b/tests/Uri/__construct_err01.phpt new file mode 100644 index 0000000..0980baa --- /dev/null +++ b/tests/Uri/__construct_err01.phpt @@ -0,0 +1,12 @@ +--TEST-- +Create Uri error: invalid argument +--FILE-- + 'https']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +%sttpMessage\Uri::__construct()%s string, array given diff --git a/tests/Uri/__construct_err02.phpt b/tests/Uri/__construct_err02.phpt new file mode 100644 index 0000000..46115ea --- /dev/null +++ b/tests/Uri/__construct_err02.phpt @@ -0,0 +1,12 @@ +--TEST-- +Create Uri error: Invalid uri +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +Invalid uri diff --git a/tests/Uri/__toString_001.phpt b/tests/Uri/__toString_001.phpt new file mode 100644 index 0000000..88b4d22 --- /dev/null +++ b/tests/Uri/__toString_001.phpt @@ -0,0 +1,9 @@ +--TEST-- +Cast Uri to string with full url +--FILE-- + +--EXPECT-- +string(55) "http://acme:secure@www.example.com:8000/foo?q=42#answer" diff --git a/tests/Uri/__toString_002.phpt b/tests/Uri/__toString_002.phpt new file mode 100644 index 0000000..daee820 --- /dev/null +++ b/tests/Uri/__toString_002.phpt @@ -0,0 +1,9 @@ +--TEST-- +Cast Uri to string without url +--FILE-- + +--EXPECT-- +string(0) "" diff --git a/tests/Uri/__toString_003.phpt b/tests/Uri/__toString_003.phpt new file mode 100644 index 0000000..0703810 --- /dev/null +++ b/tests/Uri/__toString_003.phpt @@ -0,0 +1,9 @@ +--TEST-- +Cast Uri to string with basic url +--FILE-- + +--EXPECT-- +string(24) "https://www.example.com/" diff --git a/tests/Uri/__toString_004.phpt b/tests/Uri/__toString_004.phpt new file mode 100644 index 0000000..f58a217 --- /dev/null +++ b/tests/Uri/__toString_004.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cast Uri to string with only user info and port +--FILE-- +withUserInfo('foo:bar') + ->withPort(8000); + +var_dump((string)$uri); +?> +--EXPECT-- +string(0) "" diff --git a/tests/Uri/__toString_005.phpt b/tests/Uri/__toString_005.phpt new file mode 100644 index 0000000..fe06f1d --- /dev/null +++ b/tests/Uri/__toString_005.phpt @@ -0,0 +1,10 @@ +--TEST-- +Cast Uri to string with paths +--FILE-- + +--EXPECT-- +string(3) "foo" +string(4) "/foo" diff --git a/tests/Uri/__toString_006.phpt b/tests/Uri/__toString_006.phpt new file mode 100644 index 0000000..2e2b0ff --- /dev/null +++ b/tests/Uri/__toString_006.phpt @@ -0,0 +1,14 @@ +--TEST-- +Cast Uri to string with query or fragment +--FILE-- + +--EXPECT-- +string(5) "?q=42" +string(7) "#answer" +string(12) "?q=42#answer" +string(16) "/foo?q=42#answer" diff --git a/tests/Uri/__toString_007.phpt b/tests/Uri/__toString_007.phpt new file mode 100644 index 0000000..107e538 --- /dev/null +++ b/tests/Uri/__toString_007.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cast Uri to string with authority but without schema +--FILE-- +withHost('www.example.com'); + +var_dump((string)$uri); +var_dump((string)($uri->withUserInfo('user:pass'))); +?> +--EXPECT-- +string(17) "//www.example.com" +string(27) "//user:pass@www.example.com" diff --git a/tests/Uri/__toString_008.phpt b/tests/Uri/__toString_008.phpt new file mode 100644 index 0000000..d626603 --- /dev/null +++ b/tests/Uri/__toString_008.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cast Uri to string with authority but without schema +--FILE-- +withPath('/foo'))); +var_dump((string)((new HttpMessage\Uri)->withPath('//foo'))); +var_dump((string)((new HttpMessage\Uri)->withPath('/////foo'))); +?> +--EXPECT-- +string(4) "/foo" +string(4) "/foo" +string(4) "/foo" diff --git a/tests/Uri/__toString_009.phpt b/tests/Uri/__toString_009.phpt new file mode 100644 index 0000000..8d9299b --- /dev/null +++ b/tests/Uri/__toString_009.phpt @@ -0,0 +1,11 @@ +--TEST-- +Cast Uri to string with authority but without schema +--FILE-- +withPath('foo'))); +var_dump((string)($uri->withPath('//foo'))); +?> +--EXPECT-- +string(26) "http://www.example.com/foo" +string(27) "http://www.example.com//foo" diff --git a/tests/Uri/authority_001.phpt b/tests/Uri/authority_001.phpt new file mode 100644 index 0000000..59bc9b7 --- /dev/null +++ b/tests/Uri/authority_001.phpt @@ -0,0 +1,9 @@ +--TEST-- +Uri::getAuthority() +--FILE-- +getAuthority()); +?> +--EXPECT-- +string(32) "acme:secure@www.example.com:8000" diff --git a/tests/Uri/authority_002.phpt b/tests/Uri/authority_002.phpt new file mode 100644 index 0000000..ec9c51e --- /dev/null +++ b/tests/Uri/authority_002.phpt @@ -0,0 +1,9 @@ +--TEST-- +Uri::getAuthority() +--FILE-- +getAuthority()); +?> +--EXPECT-- +string(0) "" diff --git a/tests/Uri/authority_003.phpt b/tests/Uri/authority_003.phpt new file mode 100644 index 0000000..05ead0e --- /dev/null +++ b/tests/Uri/authority_003.phpt @@ -0,0 +1,10 @@ +--TEST-- +Uri::getAuthority() +--FILE-- +getAuthority()); +?> +--EXPECT-- +string(15) "www.example.com" + diff --git a/tests/Uri/fragment_001.phpt b/tests/Uri/fragment_001.phpt new file mode 100644 index 0000000..11c0071 --- /dev/null +++ b/tests/Uri/fragment_001.phpt @@ -0,0 +1,14 @@ +--TEST-- +Uri::getFragment() +--FILE-- +getFragment()); +var_dump((new HttpMessage\Uri('http://example.com/#foo'))->getFragment()); +var_dump((new HttpMessage\Uri('#foo'))->getFragment()); +var_dump((new HttpMessage\Uri('?a=1#foo'))->getFragment()); +?> +--EXPECT-- +string(0) "" +string(3) "foo" +string(3) "foo" +string(3) "foo" diff --git a/tests/Uri/fragment_002.phpt b/tests/Uri/fragment_002.phpt new file mode 100644 index 0000000..892e1b4 --- /dev/null +++ b/tests/Uri/fragment_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Uri::withFragment() +--FILE-- +withFragment("foo"); +$bar = $foo->withFragment("!bar"); + +var_dump($blank->getFragment()); +var_dump($foo->getFragment()); +var_dump($bar->getFragment()); +?> +--EXPECT-- +string(0) "" +string(3) "foo" +string(4) "!bar" diff --git a/tests/Uri/fragment_err01.phpt b/tests/Uri/fragment_err01.phpt new file mode 100644 index 0000000..a088cac --- /dev/null +++ b/tests/Uri/fragment_err01.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withFragment() error: invalid argument +--FILE-- +withFragment(['fragment' => 'foo']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withFragment()%s string, array given diff --git a/tests/Uri/fragment_err02.phpt b/tests/Uri/fragment_err02.phpt new file mode 100644 index 0000000..b706a6a --- /dev/null +++ b/tests/Uri/fragment_err02.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withFragment() error: missing argument +--FILE-- +withFragment(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withFragment() expects exactly 1 %s, 0 given diff --git a/tests/Uri/host_001.phpt b/tests/Uri/host_001.phpt new file mode 100644 index 0000000..32fd98e --- /dev/null +++ b/tests/Uri/host_001.phpt @@ -0,0 +1,10 @@ +--TEST-- +Uri::getHost() +--FILE-- +getHost()); +var_dump((new HttpMessage\Uri('http://www.example.com/'))->getHost()); +?> +--EXPECT-- +string(0) "" +string(15) "www.example.com" diff --git a/tests/Uri/host_002.phpt b/tests/Uri/host_002.phpt new file mode 100644 index 0000000..af9bae5 --- /dev/null +++ b/tests/Uri/host_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Uri::withHost() +--FILE-- +withHost("www.example.com"); +$dotNet = $dotCom->withHost("www.example.net"); + +var_dump($blank->getHost()); +var_dump($dotCom->getHost()); +var_dump($dotNet->getHost()); +?> +--EXPECT-- +string(0) "" +string(15) "www.example.com" +string(15) "www.example.net" diff --git a/tests/Uri/host_error.phpt b/tests/Uri/host_error.phpt new file mode 100644 index 0000000..5f00ee0 --- /dev/null +++ b/tests/Uri/host_error.phpt @@ -0,0 +1,19 @@ +--TEST-- +Uri::withHost() errors +--FILE-- +withHost(['host' => 'www.example.com']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + (new HttpMessage\Uri)->withHost(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withHost()%s, array given +HttpMessage\Uri::withHost() expects exactly 1 %s, 0 given diff --git a/tests/Uri/path_001.phpt b/tests/Uri/path_001.phpt new file mode 100644 index 0000000..683b6db --- /dev/null +++ b/tests/Uri/path_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +Uri::getPath() +--FILE-- +getPath()); +var_dump((new HttpMessage\Uri('http://example.com/'))->getPath()); +var_dump((new HttpMessage\Uri('http://example.com/'))->getPath()); +var_dump((new HttpMessage\Uri('http://example.com/foo'))->getPath()); +var_dump((new HttpMessage\Uri('/'))->getPath()); +var_dump((new HttpMessage\Uri('/foo'))->getPath()); +?> +--EXPECT-- +string(0) "" +string(0) "" +string(1) "/" +string(4) "/foo" +string(1) "/" +string(4) "/foo" diff --git a/tests/Uri/path_002.phpt b/tests/Uri/path_002.phpt new file mode 100644 index 0000000..dc24c83 --- /dev/null +++ b/tests/Uri/path_002.phpt @@ -0,0 +1,22 @@ +--TEST-- +Uri::withPath() +--FILE-- +withPath("/"); +$absolute = $root->withPath("/foo"); +$relative = $absolute->withPath("foo"); +$double = $absolute->withPath("//foo"); + +var_dump($blank->getPath()); +var_dump($root->getPath()); +var_dump($absolute->getPath()); +var_dump($relative->getPath()); +var_dump($double->getPath()); +?> +--EXPECT-- +string(0) "" +string(1) "/" +string(4) "/foo" +string(3) "foo" +string(5) "//foo" diff --git a/tests/Uri/path_err01.phpt b/tests/Uri/path_err01.phpt new file mode 100644 index 0000000..b44e75a --- /dev/null +++ b/tests/Uri/path_err01.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withPath() error: invalid argument +--FILE-- +withPath(['path' => '/foo']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withPath()%s string, array given diff --git a/tests/Uri/path_err02.phpt b/tests/Uri/path_err02.phpt new file mode 100644 index 0000000..35d6fdc --- /dev/null +++ b/tests/Uri/path_err02.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withPath() error: missing argument +--FILE-- +withPath(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withPath() expects exactly 1 %s, 0 given diff --git a/tests/Uri/port_001.phpt b/tests/Uri/port_001.phpt new file mode 100644 index 0000000..3ac26ab --- /dev/null +++ b/tests/Uri/port_001.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::getPort() +--FILE-- +getPort()); +var_dump((new HttpMessage\Uri('http://www.example.com/'))->getPort()); +var_dump((new HttpMessage\Uri('http://www.example.com/'))->getPort()); +?> +--EXPECT-- +NULL +NULL +int(80) diff --git a/tests/Uri/port_002.phpt b/tests/Uri/port_002.phpt new file mode 100644 index 0000000..b4e40ff --- /dev/null +++ b/tests/Uri/port_002.phpt @@ -0,0 +1,24 @@ +--TEST-- +Uri::withPort() +--FILE-- +withPort(80); +$port8000 = $port80->withPort(8000); +$noPort = $port80->withPort(null); + +var_dump($blank->getPort()); +var_dump($port80->getPort()); +var_dump($port8000->getPort()); +var_dump($noPort->getPort()); + +var_dump((string)$port80); +var_dump((string)$noPort); +?> +--EXPECT-- +NULL +int(80) +int(8000) +NULL +string(21) "http://example.com/" +string(18) "http://example.com/" \ No newline at end of file diff --git a/tests/Uri/port_err01.phpt b/tests/Uri/port_err01.phpt new file mode 100644 index 0000000..1dd9dc0 --- /dev/null +++ b/tests/Uri/port_err01.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withPort() error: invalid argument +--FILE-- +withPort(['port' => 80]); +} catch (TypeError $e) { + echo strtr($e->getMessage(), ['integer' => 'int']), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withPort()%sint, array given diff --git a/tests/Uri/port_err02.phpt b/tests/Uri/port_err02.phpt new file mode 100644 index 0000000..a24b02c --- /dev/null +++ b/tests/Uri/port_err02.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withPort() error: missing argument +--FILE-- +withPort(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withPort() expects exactly 1 %s, 0 given diff --git a/tests/Uri/query_001.phpt b/tests/Uri/query_001.phpt new file mode 100644 index 0000000..88bfacb --- /dev/null +++ b/tests/Uri/query_001.phpt @@ -0,0 +1,14 @@ +--TEST-- +Uri::getQuery() +--FILE-- +getQuery()); +var_dump((new HttpMessage\Uri('http://example.com/?foo=1&bar=2'))->getQuery()); +var_dump((new HttpMessage\Uri('?foo=1&bar=2'))->getQuery()); +var_dump((new HttpMessage\Uri('https://www.example.com/'))->getQuery()); +?> +--EXPECT-- +string(0) "" +string(11) "foo=1&bar=2" +string(11) "foo=1&bar=2" +string(0) "" diff --git a/tests/Uri/query_002.phpt b/tests/Uri/query_002.phpt new file mode 100644 index 0000000..2f92b8d --- /dev/null +++ b/tests/Uri/query_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Uri::withQuery() +--FILE-- +withQuery("foo=1"); +$fooBar = $foo->withQuery("foo=1&bar=2"); + +var_dump($blank->getQuery()); +var_dump($foo->getQuery()); +var_dump($fooBar->getQuery()); +?> +--EXPECT-- +string(0) "" +string(5) "foo=1" +string(11) "foo=1&bar=2" diff --git a/tests/Uri/query_err01.phpt b/tests/Uri/query_err01.phpt new file mode 100644 index 0000000..dc6051e --- /dev/null +++ b/tests/Uri/query_err01.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withQuery() error: invalid argument +--FILE-- +withQuery(['foo' => 1]); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withQuery()%s string, array given diff --git a/tests/Uri/query_err02.phpt b/tests/Uri/query_err02.phpt new file mode 100644 index 0000000..220c2bc --- /dev/null +++ b/tests/Uri/query_err02.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withQuery() error: missing argument +--FILE-- +withQuery(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withQuery() expects exactly 1 %s, 0 given diff --git a/tests/Uri/scheme_001.phpt b/tests/Uri/scheme_001.phpt new file mode 100644 index 0000000..12485b3 --- /dev/null +++ b/tests/Uri/scheme_001.phpt @@ -0,0 +1,16 @@ +--TEST-- +Uri::getScheme() +--FILE-- +getScheme()); +var_dump((new HttpMessage\Uri('http:'))->getScheme()); +var_dump((new HttpMessage\Uri('mailto:john@example.com'))->getScheme()); +var_dump((new HttpMessage\Uri('https://www.example.com/'))->getScheme()); +var_dump((new HttpMessage\Uri('ftp://www.example.com'))->getScheme()); +?> +--EXPECT-- +string(0) "" +string(4) "http" +string(6) "mailto" +string(5) "https" +string(3) "ftp" diff --git a/tests/Uri/scheme_002.phpt b/tests/Uri/scheme_002.phpt new file mode 100644 index 0000000..dddb322 --- /dev/null +++ b/tests/Uri/scheme_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Uri::withScheme() +--FILE-- +withScheme("https"); +$mailto = $https->withScheme("mailto"); + +var_dump($blank->getScheme()); +var_dump($https->getScheme()); +var_dump($mailto->getScheme()); +?> +--EXPECT-- +string(0) "" +string(5) "https" +string(6) "mailto" diff --git a/tests/Uri/scheme_err01.phpt b/tests/Uri/scheme_err01.phpt new file mode 100644 index 0000000..2df465d --- /dev/null +++ b/tests/Uri/scheme_err01.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withHost() error: invalid argument +--FILE-- +withScheme(['scheme' => 'http']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withScheme()%s string, array given diff --git a/tests/Uri/scheme_err02.phpt b/tests/Uri/scheme_err02.phpt new file mode 100644 index 0000000..82ccc21 --- /dev/null +++ b/tests/Uri/scheme_err02.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withHost() error: missing argument +--FILE-- +withScheme(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withScheme() expects exactly 1 %s, 0 given diff --git a/tests/Uri/userInfo_001.phpt b/tests/Uri/userInfo_001.phpt new file mode 100644 index 0000000..e361643 --- /dev/null +++ b/tests/Uri/userInfo_001.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::getUserInfo() +--FILE-- +getUserInfo()); +var_dump((new HttpMessage\Uri('http://user@www.example.com/'))->getUserInfo()); +var_dump((new HttpMessage\Uri('http://user:pass@www.example.com/'))->getUserInfo()); +?> +--EXPECT-- +string(0) "" +string(4) "user" +string(9) "user:pass" diff --git a/tests/Uri/userInfo_002.phpt b/tests/Uri/userInfo_002.phpt new file mode 100644 index 0000000..691ba6c --- /dev/null +++ b/tests/Uri/userInfo_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Uri::withScheme() +--FILE-- +withUserInfo("user"); +$userPass = $user->withUserInfo("user:pass"); + +var_dump($blank->getUserInfo()); +var_dump($user->getUserInfo()); +var_dump($userPass->getUserInfo()); +?> +--EXPECT-- +string(0) "" +string(4) "user" +string(9) "user:pass" diff --git a/tests/Uri/userInfo_err01.phpt b/tests/Uri/userInfo_err01.phpt new file mode 100644 index 0000000..6be1486 --- /dev/null +++ b/tests/Uri/userInfo_err01.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withUserInfo() error: invalid argument +--FILE-- +withUserInfo(['userInfo' => 'user']); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withUserInfo()%s string, array given diff --git a/tests/Uri/userInfo_err02.phpt b/tests/Uri/userInfo_err02.phpt new file mode 100644 index 0000000..c637e0e --- /dev/null +++ b/tests/Uri/userInfo_err02.phpt @@ -0,0 +1,12 @@ +--TEST-- +Uri::withUserInfo() error: missing argument +--FILE-- +withUserInfo(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +HttpMessage\Uri::withUserInfo() expects exactly 1 %s, 0 given diff --git a/tests/skeleton_nop.phpt b/tests/skeleton_nop.phpt deleted file mode 100644 index 0732b1b..0000000 --- a/tests/skeleton_nop.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -TODO: Add tests for your functions ---SKIPIF-- - ---FILE-- - ---EXPECT-- -string(11) "Hello World" - diff --git a/tests/smoke.php b/tests/smoke.php new file mode 100644 index 0000000..76b0a61 --- /dev/null +++ b/tests/smoke.php @@ -0,0 +1,16 @@ + | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "php.h" +#include "php_http_message.h" +#include "macros.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "SAPI.h" +#include "ext/standard/file.h" +#include "ext/psr/psr_http_message.h" +#include "ext/spl/spl_exceptions.h" + +#if HAVE_HTTP_MESSAGE + +zend_class_entry *HttpMessage_UploadedFile_ce = NULL; + +int assert_file_available(zval *file, zval *stream, zval *moved) +{ + zval stream_file, arg1; + char *filename; + + if ((file == NULL || ZVAL_IS_NULL(file)) && (stream == NULL || ZVAL_IS_NULL(stream))) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "No file was uploaded or uploaded file not available"); + return FAILURE; + } + + if (Z_TYPE_P(moved) == IS_TRUE) { + if (!ZVAL_IS_NULL(file)) { + filename = Z_STRVAL_P(file); + STR_CLOSE(filename, Z_STRLEN_P(file)); + } else { + // Can be any StreamInterface object, doesn't need to be Stream from this lib. + ZVAL_STRINGL(&arg1, "uri", 3); + zend_call_method_with_1_params(PROPERTY_ARG(stream), NULL, NULL, "getMetadata", &stream_file, &arg1); + filename = Z_STRVAL(stream_file); + STR_CLOSE(filename, Z_STRLEN(stream_file)); + } + + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Uploaded file '%s' has already been moved", filename); + return FAILURE; + } + + return SUCCESS; +} + +zend_bool assert_uploaded_file(char *path, size_t path_len) +{ + if (SG(rfc1867_uploaded_files) == NULL || !zend_hash_str_exists(SG(rfc1867_uploaded_files), path, path_len)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Won't move '%s'; not an uploaded file", path); + return FAILURE; + } + + return SUCCESS; +} + +void uploaded_file_chmod(char *new_path) +{ + int oldmask; int ret; + + oldmask = umask(077); + umask(oldmask); + + ret = VCWD_CHMOD(new_path, 0666 & ~oldmask); + + if (ret == -1) { + php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); + } +} + +int move_uploaded_file(char *path, size_t path_len, char *new_path, size_t new_path_len) +{ + HashTable *uploaded_files; + zend_bool successful = 0; + + STR_CLOSE(new_path, new_path_len); + + if (php_check_open_basedir_ex(new_path, 1)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to move uploaded file '%s' to '%s'; open_basedir restriction in effect", path, new_path); + return FAILURE; + } + + if (VCWD_RENAME(path, new_path) == 0) { + successful = 1; +#ifndef PHP_WIN32 + uploaded_file_chmod(new_path); +#endif + } else if (php_copy_file_ex(path, new_path, STREAM_DISABLE_OPEN_BASEDIR) == SUCCESS) { + VCWD_UNLINK(path); + successful = 1; + } + + if (!successful) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Failed to move uploaded file '%s' to '%s'", + path, new_path); + return FAILURE; + } + + uploaded_files = SG(rfc1867_uploaded_files); + if (uploaded_files != NULL) { + zend_hash_str_del(uploaded_files, path, path_len); + } + + return SUCCESS; +} + +int move_uploaded_stream(zval *stream, char *new_path, size_t new_path_len) +{ + zval resource; + php_stream *source, *target; + size_t len; + int ret; + + // Can be any StreamInterface object, doesn't need to be Stream from this lib. + zend_call_method_with_0_params(PROPERTY_ARG(stream), NULL, NULL, "detach", &resource); + source = (php_stream*)zend_fetch_resource2_ex(&resource, "stream", php_file_le_stream(), php_file_le_pstream()); + + STR_CLOSE(new_path, new_path_len); + target = php_stream_open_wrapper(new_path, "w", 0, NULL); + + if (EXPECTED(source != NULL && target != NULL)) { + if (source->ops->seek && (source->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { + php_stream_seek(source, 0, SEEK_SET); + }; + + ret = php_stream_copy_to_stream_ex(source, target, PHP_STREAM_COPY_ALL, &len); + } else { + ret = FAILURE; + } + + if (ret == FAILURE) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Failed to stream uploaded file to '%s'", new_path); + } + + return ret; +} + +void construct_uploaded_file( + zval* object, + zval *stream, + zend_string *file, + zend_long size, + zend_long error, + zend_string *clientFilename, + zend_string *clientMediaType, + char checkUploaded +) { + zval zreadable; + + if (error == 0 && stream != NULL) { + // Must be verified it's a StreamInterface object before passing it to this function. + // Can be any StreamInterface object, doesn't need to be Stream from this lib. + zend_call_method_with_0_params(PROPERTY_ARG(stream), NULL, NULL, "isReadable", &zreadable); + + if (UNEXPECTED(Z_TYPE(zreadable) != IS_TRUE)) { + zend_throw_exception( + spl_ce_InvalidArgumentException, "Stream provided for uploaded file is not readable", 0 + ); + } + zend_update_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(object), ZEND_STRL("stream"), stream); + } else if (error == 0 && file != NULL) { + zend_update_property_str(HttpMessage_UploadedFile_ce, PROPERTY_ARG(object), ZEND_STRL("file"), file); + } + + if (clientFilename != NULL) { + zend_update_property_str(HttpMessage_UploadedFile_ce, PROPERTY_ARG(object), ZEND_STRL("clientFilename"), clientFilename); + } + if (clientMediaType != NULL) { + zend_update_property_str(HttpMessage_UploadedFile_ce, PROPERTY_ARG(object), ZEND_STRL("clientMediaType"), clientMediaType); + } + + if (size > 0) { + zend_update_property_long(HttpMessage_UploadedFile_ce, PROPERTY_ARG(object), ZEND_STRL("size"), size); + } + if (error < 0 || error > 8) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "Invalid error code %ld", error); + } + zend_update_property_long(HttpMessage_UploadedFile_ce, PROPERTY_ARG(object), ZEND_STRL("error"), error); + + if (checkUploaded < 0) { + checkUploaded = SG(rfc1867_uploaded_files) != NULL; + } + zend_update_property_bool(HttpMessage_UploadedFile_ce, PROPERTY_ARG(object), ZEND_STRL("checkUploaded"), checkUploaded); +} + +void create_uploaded_file(zval *uploaded_file, zval *tmp_name, zval *size, zval *error, zval *name, zval *type) +{ + zend_object *new_object; + + new_object = zend_objects_new(HttpMessage_UploadedFile_ce); + object_properties_init(new_object, HttpMessage_UploadedFile_ce); + + ZVAL_OBJ(uploaded_file, new_object); + + construct_uploaded_file( + uploaded_file, + NULL, + tmp_name != NULL ? Z_STR_P(tmp_name) : NULL, + size != NULL ? Z_LVAL_P(size) : -1, + Z_LVAL_P(error), + name != NULL ? Z_STR_P(name) : NULL, + type != NULL ? Z_STR_P(type) : NULL, + -1 // auto-detect check is_uploaded + ); +} + +void restructure_uploaded_files( + zval *objects, + HashTable *names, + HashTable *types, + HashTable *tmp_names, + HashTable *errors, + HashTable *sizes +) { + zval *name, *type, *tmp_name, *error, *size, *element; + zend_ulong index; + zend_string *key; + + ZEND_HASH_FOREACH_KEY_VAL(errors, index, key, error) { + element = ARRAY_ADD(Z_ARR_P(objects), index, key); + + name = ARRAY_GET(names, index, key); + type = ARRAY_GET(types, index, key); + tmp_name = ARRAY_GET(tmp_names, index, key); + size = ARRAY_GET(sizes, index, key); + + if (Z_TYPE_P(error) == IS_LONG) { + create_uploaded_file(element, tmp_name, size, error, name, type); + } else if (EXPECTED(Z_TYPE_P(error) == IS_ARRAY)) { + array_init(element); + restructure_uploaded_files(element, Z_ARR_P_NULL(name), Z_ARR_P_NULL(type), Z_ARR_P_NULL(tmp_name), + Z_ARR_P_NULL(error), Z_ARR_P_NULL(size)); // recursion + } + } ZEND_HASH_FOREACH_END(); +} + +void create_uploaded_files(zval *objects, HashTable *files) +{ + zval *zentry, *name, *type, *tmp_name, *error, *size, *element; + HashTable *entry; + zend_ulong index; + zend_string *key; + + array_init(objects); + + ZEND_HASH_FOREACH_KEY_VAL(files, index, key, zentry) { + if (UNEXPECTED(Z_TYPE_P(zentry) != IS_ARRAY)) continue; + + entry = Z_ARR_P(zentry); + error = zend_hash_str_find(entry, ZEND_STRL("error")); + + if (UNEXPECTED(error == NULL)) continue; + + name = zend_hash_str_find(entry, ZEND_STRL("name")); + type = zend_hash_str_find(entry, ZEND_STRL("type")); + tmp_name = zend_hash_str_find(entry, ZEND_STRL("tmp_name")); + size = zend_hash_str_find(entry, ZEND_STRL("size")); + + element = ARRAY_ADD(Z_ARR_P(objects), index, key); + + if (Z_TYPE_P(error) == IS_LONG) { + create_uploaded_file(element, tmp_name, size, error, name, type); + } else if (EXPECTED(Z_TYPE_P(error) == IS_ARRAY)) { + array_init(element); + restructure_uploaded_files(element, Z_ARR_P(name), Z_ARR_P(type), Z_ARR_P(tmp_name), Z_ARR_P(error), + Z_ARR_P(size)); + } + } ZEND_HASH_FOREACH_END(); +} + + +/* __construct */ + +ZEND_BEGIN_ARG_INFO_EX(arginfo_HttpMessageUploadedFile_construct, 0, 0, 1) + ZEND_ARG_INFO(0, fileOrStream) + ZEND_ARG_TYPE_INFO(0, size, IS_LONG, 1) + ZEND_ARG_TYPE_INFO(0, error, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, clientFilename, IS_STRING, 1) + ZEND_ARG_TYPE_INFO(0, clientMediaType, IS_STRING, 1) + ZEND_ARG_INFO(0, checkUploaded) +ZEND_END_ARG_INFO() + +PHP_METHOD(UploadedFile, __construct) +{ + zval *fileOrStream = NULL, *stream = NULL; + zend_string *file = NULL, *clientFilename = NULL, *clientMediaType = NULL; + zend_long size = -1, error = 0; + zend_bool size_is_null = 1, checkUploaded = 0, checkUploaded_is_null = 1; + zend_class_entry *stream_interface; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 6) + Z_PARAM_ZVAL(fileOrStream) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_EX(size, size_is_null, 1, 0) + Z_PARAM_LONG(error) + Z_PARAM_STR_EX(clientFilename, 1, 0) + Z_PARAM_STR_EX(clientMediaType, 1, 0) + Z_PARAM_BOOL_EX(checkUploaded, checkUploaded_is_null, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + if (Z_TYPE_P(fileOrStream) == IS_STRING) { + file = Z_STR_P(fileOrStream); + } else if (Z_TYPE_P(fileOrStream) != IS_NULL) { + stream_interface = HTTP_MESSAGE_PSR_INTERFACE("stream"); + + if (UNEXPECTED(stream_interface == NULL)) { + zend_throw_error(NULL, "Psr\\Http\\Message\\StreamInterface not found"); + return; + } + + if (UNEXPECTED( + Z_TYPE_P(fileOrStream) != IS_OBJECT || + !instanceof_function(Z_OBJCE_P(fileOrStream), stream_interface) + )) { + custom_parameter_type_error(1, + "a string or object that implements Psr\\Http\\Message\\StreamInterface", fileOrStream); + return; + } + + stream = fileOrStream; + } + + construct_uploaded_file(getThis(), stream, file, size_is_null ? -1 : size, error, clientFilename, clientMediaType, + checkUploaded_is_null ? -1 : checkUploaded); +} + +PHP_METHOD(UploadedFile, getStream) +{ + zval rv, *stream, *file, *moved, mode; + + file = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("file"), 0, &rv); + stream = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + moved = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("moved"), 0, &rv); + + if (assert_file_available(file, stream, moved) == FAILURE) { + return; + } + + // Stream isn't set: create new stream for file + if (ZVAL_IS_NULL(stream)) { + ZVAL_STRINGL(&mode, "r", 1); + NEW_OBJECT_CONSTRUCT(stream, HttpMessage_Stream_ce, 2, file, &mode); + } + + RETURN_ZVAL(stream, 1, 0); +} + +PHP_METHOD(UploadedFile, moveTo) +{ + zval rv, *file, *moved, *stream, *checkUploaded; + char *new_path = NULL; + size_t new_path_len = 0; + int move_ret; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_PATH(new_path, new_path_len) + ZEND_PARSE_PARAMETERS_END(); + + file = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("file"), 0, &rv); + stream = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("stream"), 0, &rv); + moved = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("moved"), 0, &rv); + checkUploaded = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("checkUploaded"), 0, &rv); + + if ( + assert_file_available(file, stream, moved) == FAILURE || + (Z_TYPE_P(checkUploaded) == IS_TRUE && assert_uploaded_file(Z_STRVAL_P(file), Z_STRLEN_P(file) == FAILURE)) + ) { + return; + } + + if (!ZVAL_IS_NULL(file)) { + move_ret = move_uploaded_file(Z_STRVAL_P(file), Z_STRLEN_P(file), new_path, new_path_len); + } else { + move_ret = move_uploaded_stream(stream, new_path, new_path_len); + } + + ZVAL_BOOL(moved, move_ret == SUCCESS); +} + +PHP_METHOD(UploadedFile, getSize) +{ + zval rv, *value; + + value = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("size"), 0, &rv); + + RETURN_ZVAL(value, 1, 0); +} + +PHP_METHOD(UploadedFile, getError) +{ + zval rv, *value; + + value = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("error"), 0, &rv); + + RETURN_ZVAL(value, 1, 0); +} + +PHP_METHOD(UploadedFile, getClientFilename) +{ + zval rv, *value; + + value = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("clientFilename"), 0, &rv); + + RETURN_ZVAL(value, 1, 0); +} + +PHP_METHOD(UploadedFile, getClientMediaType) +{ + zval rv, *value; + + value = zend_read_property(HttpMessage_UploadedFile_ce, PROPERTY_ARG(getThis()), ZEND_STRL("clientMediaType"), 0, &rv); + + RETURN_ZVAL(value, 1, 0); +} + +/* Define HttpMessage\UploadedFile class */ + +static const zend_function_entry methods[] = { + PHP_ME(UploadedFile, __construct, arginfo_HttpMessageUploadedFile_construct, ZEND_ACC_PUBLIC) + HTTP_MESSAGE_ME(UploadedFile, getStream) + HTTP_MESSAGE_ME(UploadedFile, moveTo) + HTTP_MESSAGE_ME(UploadedFile, getSize) + HTTP_MESSAGE_ME(UploadedFile, getError) + HTTP_MESSAGE_ME(UploadedFile, getClientFilename) + HTTP_MESSAGE_ME(UploadedFile, getClientMediaType) + PHP_FE_END +}; + +PHP_MINIT_FUNCTION(http_message_uploadedfile) +{ + zend_class_entry ce; + zend_class_entry *interface = HTTP_MESSAGE_PSR_INTERFACE("uploadedfile"); + + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND(interface, "UploadedFile"); + + INIT_NS_CLASS_ENTRY(ce, "HttpMessage", "UploadedFile", methods); + + HttpMessage_UploadedFile_ce = zend_register_internal_class(&ce); + zend_class_implements(HttpMessage_UploadedFile_ce, 1, interface); + + /* Properties */ + zend_declare_property_null(HttpMessage_UploadedFile_ce, ZEND_STRL("stream"), ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_UploadedFile_ce, ZEND_STRL("file"), ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_UploadedFile_ce, ZEND_STRL("size"), ZEND_ACC_PRIVATE); + zend_declare_property_long(HttpMessage_UploadedFile_ce, ZEND_STRL("error"), 0, ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_UploadedFile_ce, ZEND_STRL("clientFilename"), ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_UploadedFile_ce, ZEND_STRL("clientMediaType"), ZEND_ACC_PRIVATE); + + zend_declare_property_bool(HttpMessage_UploadedFile_ce, ZEND_STRL("moved"), 0, ZEND_ACC_PRIVATE); + zend_declare_property_bool(HttpMessage_UploadedFile_ce, ZEND_STRL("checkUploaded"), 0, ZEND_ACC_PRIVATE); + + return SUCCESS; +} + +#endif diff --git a/uploaded_file.h b/uploaded_file.h new file mode 100644 index 0000000..5d76f38 --- /dev/null +++ b/uploaded_file.h @@ -0,0 +1,47 @@ +/* + +----------------------------------------------------------------------+ + | HTTP Message PHP extension | + +----------------------------------------------------------------------+ + | Copyright (c) 2019 Arnold Daniels | + +----------------------------------------------------------------------+ + | Permission is hereby granted, free of charge, to any person | + | obtaining a copy of this software and associated documentation files | + | (the "Software"), to deal in the Software without restriction, | + | including without limitation the rights to use, copy, modify, merge, | + | publish, distribute, sublicense, and/or sell copies of the Software, | + | and to permit persons to whom the Software is furnished to do so, | + | subject to the following conditions: | + | | + | The above copyright notice and this permission notice shall be | + | included in all copies or substantial portions of the Software. | + | | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | + | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | + | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | + | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | + | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | + | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | + | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | + | SOFTWARE. | + +----------------------------------------------------------------------+ + | Author: Arnold Daniels | + +----------------------------------------------------------------------+ +*/ + +#ifndef HTTP_MESSAGE_UPLOADED_FILE_H +#define HTTP_MESSAGE_UPLOADED_FILE_H + +void construct_uploaded_file( + zval* object, + zval *stream, + zend_string *file, + zend_long size, + zend_long error, + zend_string *clientFilename, + zend_string *clientMediaType, + char checkUploaded +); +void create_uploaded_files(zval *objects, HashTable *files); +void uri_set_userinfo(zval *uri, char *user, size_t user_len, char *pass, size_t pass_len); + +#endif //HTTP_MESSAGE_UPLOADED_FILE_H diff --git a/uri.c b/uri.c index 026f217..1f5b4c6 100644 --- a/uri.c +++ b/uri.c @@ -33,18 +33,40 @@ #endif #include "php.h" -#include "php_ini.h" -#include "php_http_message.h" #include "macros.h" #include "zend_exceptions.h" -#include "ext/standard/info.h" +#include "zend_smart_str.h" #include "ext/standard/url.h" #include "ext/psr/psr_http_message.h" +#include "ext/spl/spl_exceptions.h" #if HAVE_HTTP_MESSAGE -zend_class_entry *HttpMessage_Uri_ce; +zend_class_entry *HttpMessage_Uri_ce = NULL; +void uri_set_userinfo(zval *uri, char *user, size_t user_len, char *pass, size_t pass_len) +{ + char *userinfo; + + if (user_len == 0 && pass_len == 0) { + return; + } + + if (pass_len == 0) { + zend_update_property_stringl(HttpMessage_Uri_ce, PROPERTY_ARG(uri), ZEND_STRL("userInfo"), user, user_len); + } else { + userinfo = emalloc(user_len + pass_len + 2); + if (UNEXPECTED(userinfo == NULL)) return; // Memory issue + + user[user_len] = '\0'; + pass[pass_len] = '\0'; + sprintf(userinfo, "%s:%s", user, pass); + + zend_update_property_stringl(HttpMessage_Uri_ce, PROPERTY_ARG(uri), ZEND_STRL("userInfo"), userinfo, user_len + pass_len + 1); + + efree(userinfo); + } +} /* __construct */ @@ -55,39 +77,114 @@ ZEND_END_ARG_INFO() PHP_METHOD(Uri, __construct) { php_url *info; - char *value; + char *value = NULL; size_t value_len = 0; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_STRING(value, value_len) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(value, value_len) ZEND_PARSE_PARAMETERS_END(); if (value_len > 0) { info = php_url_parse_ex(value, value_len); - SET_STRING_PROPERTY(HttpMessage_Uri_ce, "scheme", info->scheme); - SET_STRING_PROPERTY(HttpMessage_Uri_ce, "host", info->host); - SET_STRING_PROPERTY(HttpMessage_Uri_ce, "userInfo", info->user); - SET_STRING_PROPERTY(HttpMessage_Uri_ce, "path", info->path); - SET_STRING_PROPERTY(HttpMessage_Uri_ce, "query", info->query); - SET_STRING_PROPERTY(HttpMessage_Uri_ce, "fragment", info->fragment); + if (info == NULL) { + zend_throw_exception(spl_ce_UnexpectedValueException, "Invalid uri", 0); + return; + } + + SET_URI_PROPERTY(HttpMessage_Uri_ce, "scheme", info->scheme); + SET_URI_PROPERTY(HttpMessage_Uri_ce, "host", info->host); + SET_URI_PROPERTY(HttpMessage_Uri_ce, "path", info->path); + SET_URI_PROPERTY(HttpMessage_Uri_ce, "query", info->query); + SET_URI_PROPERTY(HttpMessage_Uri_ce, "fragment", info->fragment); if (info->port > 0) { - zend_update_property_long(HttpMessage_Uri_ce, getThis(), ZEND_STRL("port"), info->port); + zend_update_property_long(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("port"), info->port); } + +#if PHP_VERSION_ID < 70300 + uri_set_userinfo(getThis(), info->user, STRLEN_NULL(info->user), info->pass, STRLEN_NULL(info->pass)); +#else + uri_set_userinfo(getThis(), ZSTR_VAL_LEN(info->user), ZSTR_VAL_LEN(info->pass)); +#endif } } /* __toString */ - ZEND_BEGIN_ARG_INFO_EX(arginfo_HttpMessageUri_toString, 0, 0, 0) ZEND_END_ARG_INFO() PHP_METHOD(Uri, __toString) { + zval rv, *scheme, *userinfo, *host, *port, *path, *query, *fragment; + smart_str buf = {0}; + char *path_ptr; + size_t path_len = 0; + + scheme = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("scheme"), 0, &rv); + userinfo = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("userInfo"), 0, &rv); + host = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("host"), 0, &rv); + port = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("port"), 0, &rv); + path = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("path"), 0, &rv); + query = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("query"), 0, &rv); + fragment = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("fragment"), 0, &rv); + + smart_str_alloc(&buf, 0, 0); + + if (Z_STRLEN(*scheme) > 0) { + smart_str_appendl(&buf, Z_STRVAL(*scheme), Z_STRLEN(*scheme)); + smart_str_appends(&buf, ":"); + } + + if (Z_STRLEN(*host) > 0) { + smart_str_appends(&buf, "//"); + + if (Z_STRLEN(*userinfo) > 0) { + smart_str_appendl(&buf, Z_STRVAL(*userinfo), Z_STRLEN(*userinfo)); + smart_str_appends(&buf, "@"); + } + + smart_str_appendl(&buf, Z_STRVAL(*host), Z_STRLEN(*host)); + + if (Z_TYPE(*port) == IS_LONG) { + smart_str_appends(&buf, ":"); + smart_str_append_long(&buf, Z_LVAL(*port)); + } + } + + if (Z_STRLEN(*path) > 0) { + if (Z_STRLEN(*host) > 0 && *Z_STRVAL(*path) != '/') { + smart_str_appends(&buf, "/"); + } + + path_ptr = Z_STRVAL(*path); + path_len = Z_STRLEN(*path); + + if (Z_STRLEN(*host) == 0) { + while (path_len > 1 && *path_ptr == '/' && *(path_ptr + 1) == '/') { + path_ptr++; + path_len--; + } + } + + smart_str_appendl(&buf, path_ptr, path_len); + } + + if (Z_STRLEN(*query) > 0) { + smart_str_appends(&buf, "?"); + smart_str_appendl(&buf, Z_STRVAL(*query), Z_STRLEN(*query)); + } + + if (Z_STRLEN(*fragment) > 0) { + smart_str_appends(&buf, "#"); + smart_str_appendl(&buf, Z_STRVAL(*fragment), Z_STRLEN(*fragment)); + } + + RETVAL_STR_COPY(buf.s); + zend_string_release(buf.s); } @@ -97,23 +194,22 @@ PHP_METHOD(Uri, getScheme) { zval rv, *value; - value = zend_read_property(HttpMessage_Uri_ce, getThis(), ZEND_STRL("schema"), 0, &rv); + value = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("scheme"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Uri, withScheme) { - char *value; - size_t value_len; + zend_string *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(value, value_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(value) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property_stringl(HttpMessage_Uri_ce, return_value, ZEND_STRL("schema"), value, value_len); + zend_update_property_str(HttpMessage_Uri_ce, PROPERTY_ARG(return_value), ZEND_STRL("scheme"), value); } @@ -121,11 +217,31 @@ PHP_METHOD(Uri, withScheme) PHP_METHOD(Uri, getAuthority) { - zval rv, *value; + zval rv, *userinfo, *host, *port; + smart_str buf = {0}; - value = zend_read_property(HttpMessage_Uri_ce, getThis(), ZEND_STRL("userInfo"), 0, &rv); + userinfo = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("userInfo"), 0, &rv); + host = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("host"), 0, &rv); + port = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("port"), 0, &rv); - RETURN_ZVAL(value, 1, 0); + if (Z_STRLEN_P(host) == 0) { + RETURN_EMPTY_STRING(); + } + + if (Z_STRLEN_P(userinfo) > 0) { + smart_str_appendl(&buf, Z_STRVAL_P(userinfo), Z_STRLEN_P(userinfo)); + smart_str_appends(&buf, "@"); + } + + smart_str_appendl(&buf, Z_STRVAL_P(host), Z_STRLEN_P(host)); + + if (Z_TYPE_P(port) == IS_LONG) { + smart_str_appends(&buf, ":"); + smart_str_append_long(&buf, Z_LVAL_P(port)); + } + + RETVAL_STR_COPY(buf.s); + zend_string_release(buf.s); } @@ -135,23 +251,22 @@ PHP_METHOD(Uri, getUserInfo) { zval rv, *value; - value = zend_read_property(HttpMessage_Uri_ce, getThis(), ZEND_STRL("userInfo"), 0, &rv); + value = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("userInfo"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Uri, withUserInfo) { - char *value; - size_t value_len; + zend_string *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(value, value_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(value) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property_stringl(HttpMessage_Uri_ce, return_value, ZEND_STRL("userInfo"), value, value_len); + zend_update_property_str(HttpMessage_Uri_ce, PROPERTY_ARG(return_value), ZEND_STRL("userInfo"), value); } @@ -161,23 +276,22 @@ PHP_METHOD(Uri, getHost) { zval rv, *value; - value = zend_read_property(HttpMessage_Uri_ce, getThis(), ZEND_STRL("host"), 0, &rv); + value = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("host"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Uri, withHost) { - char *value; - size_t value_len; + zend_string *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(value, value_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(value) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property_stringl(HttpMessage_Uri_ce, return_value, ZEND_STRL("host"), value, value_len); + zend_update_property_str(HttpMessage_Uri_ce, PROPERTY_ARG(return_value), ZEND_STRL("host"), value); } @@ -187,22 +301,27 @@ PHP_METHOD(Uri, getPort) { zval rv, *value; - value = zend_read_property(HttpMessage_Uri_ce, getThis(), ZEND_STRL("port"), 0, &rv); + value = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("port"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Uri, withPort) { - long value; + zend_long value = 0; + zend_bool value_is_null = 1; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_LONG(value) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_LONG_EX(value, value_is_null,1, 0) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property_long(HttpMessage_Uri_ce, return_value, ZEND_STRL("port"), value); + if (!value_is_null) { + zend_update_property_long(HttpMessage_Uri_ce, PROPERTY_ARG(return_value), ZEND_STRL("port"), value); + } else { + zend_update_property_null(HttpMessage_Uri_ce, PROPERTY_ARG(return_value), ZEND_STRL("port")); + } } @@ -212,23 +331,22 @@ PHP_METHOD(Uri, getPath) { zval rv, *value; - value = zend_read_property(HttpMessage_Uri_ce, getThis(), ZEND_STRL("path"), 0, &rv); + value = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("path"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Uri, withPath) { - char *value; - size_t value_len; + zend_string *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(value, value_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(value) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property_stringl(HttpMessage_Uri_ce, return_value, ZEND_STRL("path"), value, value_len); + zend_update_property_str(HttpMessage_Uri_ce, PROPERTY_ARG(return_value), ZEND_STRL("path"), value); } @@ -238,49 +356,47 @@ PHP_METHOD(Uri, getQuery) { zval rv, *value; - value = zend_read_property(HttpMessage_Uri_ce, getThis(), ZEND_STRL("query"), 0, &rv); + value = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("query"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Uri, withQuery) { - char *value; - size_t value_len; + zend_string *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(value, value_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(value) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property_stringl(HttpMessage_Uri_ce, return_value, ZEND_STRL("query"), value, value_len); + zend_update_property_str(HttpMessage_Uri_ce, PROPERTY_ARG(return_value), ZEND_STRL("query"), value); } -/* query */ +/* fragment */ PHP_METHOD(Uri, getFragment) { zval rv, *value; - value = zend_read_property(HttpMessage_Uri_ce, getThis(), ZEND_STRL("fragment"), 0, &rv); + value = zend_read_property(HttpMessage_Uri_ce, PROPERTY_ARG(getThis()), ZEND_STRL("fragment"), 0, &rv); RETURN_ZVAL(value, 1, 0); } PHP_METHOD(Uri, withFragment) { - char *value; - size_t value_len; + zend_string *value = NULL; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) - Z_PARAM_STRING(value, value_len) - ZEND_PARSE_PARAMETERS_END_EX(); + Z_PARAM_STR(value) + ZEND_PARSE_PARAMETERS_END(); - ZVAL_OBJ(return_value, zend_objects_clone_obj(getThis())); + ZVAL_OBJ(return_value, zend_objects_clone_obj(PROPERTY_ARG(getThis()))); - zend_update_property_stringl(HttpMessage_Uri_ce, return_value, ZEND_STRL("fragment"), value, value_len); + zend_update_property_str(HttpMessage_Uri_ce, PROPERTY_ARG(return_value), ZEND_STRL("fragment"), value); } @@ -310,19 +426,23 @@ static const zend_function_entry uri_functions[] = { PHP_MINIT_FUNCTION(http_message_uri) { zend_class_entry ce; + zend_class_entry *interface = HTTP_MESSAGE_PSR_INTERFACE("uri"); + + ASSERT_HTTP_MESSAGE_INTERFACE_FOUND(interface, "Uri"); + INIT_NS_CLASS_ENTRY(ce, "HttpMessage", "Uri", uri_functions); HttpMessage_Uri_ce = zend_register_internal_class(&ce); - zend_class_implements(HttpMessage_Uri_ce, 1, PsrHttpMessageUriInterface_ce_ptr); + zend_class_implements(HttpMessage_Uri_ce, 1, interface); /* Properties */ - zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("scheme"), "", ZEND_ACC_PROTECTED); - zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("userInfo"), "", ZEND_ACC_PROTECTED); - zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("host"), "", ZEND_ACC_PROTECTED); - zend_declare_property_null(HttpMessage_Uri_ce, ZEND_STRL("port"), ZEND_ACC_PROTECTED); - zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("path"), "", ZEND_ACC_PROTECTED); - zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("query"), "", ZEND_ACC_PROTECTED); - zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("fragment"), "", ZEND_ACC_PROTECTED); + zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("scheme"), "", ZEND_ACC_PRIVATE); + zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("userInfo"), "", ZEND_ACC_PRIVATE); + zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("host"), "", ZEND_ACC_PRIVATE); + zend_declare_property_null(HttpMessage_Uri_ce, ZEND_STRL("port"), ZEND_ACC_PRIVATE); + zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("path"), "", ZEND_ACC_PRIVATE); + zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("query"), "", ZEND_ACC_PRIVATE); + zend_declare_property_string(HttpMessage_Uri_ce, ZEND_STRL("fragment"), "", ZEND_ACC_PRIVATE); return SUCCESS; }