Tools/Helpers.ps1
# read in the content from a dynamic pode file and invoke its content function ConvertFrom-PodeFile { param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] $Content, [Parameter()] $Data = @{} ) # if we have data, then setup the data param if (!(Test-Empty $Data)) { $Content = "param(`$data)`nreturn `"$($Content -replace '"', '``"')`"" } else { $Content = "return `"$($Content -replace '"', '``"')`"" } # invoke the content as a script to generate the dynamic content return (Invoke-ScriptBlock -ScriptBlock ([scriptblock]::Create($Content)) -Arguments $Data -Return) } function Get-Type { param ( [Parameter()] $Value ) if ($null -eq $Value) { return $null } $type = $Value.GetType() return @{ 'Name' = $type.Name.ToLowerInvariant(); 'BaseName' = $type.BaseType.Name.ToLowerInvariant(); } } function Test-Empty { param ( [Parameter()] $Value ) $type = Get-Type $Value if ($null -eq $type) { return $true } switch ($type.Name) { 'string' { return [string]::IsNullOrWhiteSpace($Value) } 'hashtable' { return ($Value.Count -eq 0) } 'scriptblock' { return ($null -eq $Value -or [string]::IsNullOrWhiteSpace($Value.ToString())) } } switch ($type.BaseName) { 'valuetype' { return $false } 'array' { return ((Get-Count $Value) -eq 0 -or $Value.Count -eq 0) } } return ([string]::IsNullOrWhiteSpace($Value) -or (Get-Count $Value) -eq 0 -or $Value.Count -eq 0) } function Get-PSVersionTable { return $PSVersionTable } function Test-IsUnix { return (Get-PSVersionTable).Platform -ieq 'unix' } function Test-IsWindows { $v = Get-PSVersionTable return ($v.Platform -ilike '*win*' -or ($null -eq $v.Platform -and $v.PSEdition -ieq 'desktop')) } function Test-IsPSCore { return (Get-PSVersionTable).PSEdition -ieq 'core' } function Test-IsAdminUser { # check the current platform, if it's unix then return true if (Test-IsUnix) { return $true } try { $principal = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent()) if ($principal -eq $null) { return $false } return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } catch [exception] { Write-Host 'Error checking user administrator priviledges' -ForegroundColor Red Write-Host $_.Exception.Message -ForegroundColor Red return $false } } function New-PodeSelfSignedCertificate { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $IP, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Port, [Parameter()] [string] $Certificate ) # only bind if windows at the moment if (!(Test-IsWindows)) { Write-Host "Certificates are currently only supported on Windows" -ForegroundColor Yellow return } # check if this ip/port is already bound $sslPortInUse = (netsh http show sslcert) | Where-Object { $_ -ilike "*IP:port*" -and $_ -ilike "*$($IP):$($Port)" } if ($sslPortInUse) { Write-Host "$($IP):$($Port) already has a certificate bound" -ForegroundColor Green return } # ensure a cert has been supplied if (Test-Empty $Certificate) { throw "A certificate is required for ssl connections, either 'self' or '*.example.com' can be supplied to the 'listen' function" } # generate a self-signed cert if (@('self', 'self-signed') -icontains $Certificate) { Write-Host "Generating self-signed certificate for $($IP):$($Port)..." -NoNewline -ForegroundColor Cyan # generate the cert -- has to call "powershell.exe" for ps-core on windows $cert = (PowerShell.exe -NoProfile -Command { $expire = (Get-Date).AddYears(1) $c = New-SelfSignedCertificate -DnsName 'localhost' -CertStoreLocation 'Cert:\LocalMachine\My' -NotAfter $expire ` -KeyAlgorithm RSA -HashAlgorithm SHA256 -KeyLength 4096 -Subject 'CN=localhost'; if ($null -eq $c.Thumbprint) { return $c } return $c.Thumbprint }) if ($LASTEXITCODE -ne 0 -or !$?) { throw "Failed to generate self-signed certificte:`n$($cert)" } } # ensure a given cert exists for binding else { Write-Host "Binding $($Certificate) to $($IP):$($Port)..." -NoNewline -ForegroundColor Cyan # ensure the certificate exists, and get it's thumbprint $cert = (Get-ChildItem 'Cert:\LocalMachine\My' | Where-Object { $_.Subject -imatch [regex]::Escape($Certificate) }) if (Test-Empty $cert) { throw "Failed to find the $($Certificate) certificate at LocalMachine\My" } $cert = ($cert)[0].Thumbprint } # bind the cert to the ip:port $ipport = "$($IP):$($Port)" $result = netsh http add sslcert ipport=$ipport certhash=$cert appid=`{e3ea217c-fc3d-406b-95d5-4304ab06c6af`} if ($LASTEXITCODE -ne 0 -or !$?) { throw "Failed to attach certificate:`n$($result)" } Write-Host " Done" -ForegroundColor Green } function Get-HostIPRegex { param ( [Parameter(Mandatory=$true)] [ValidateSet('Both', 'Hostname', 'IP')] [string] $Type ) $ip_rgx = '\[[a-f0-9\:]+\]|((\d+\.){3}\d+)|\:\:\d+|\*|all' $host_rgx = '([a-z]|\*\.)(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])+' switch ($Type.ToLowerInvariant()) { 'both' { return "(?<host>($($ip_rgx)|$($host_rgx)))" } 'hostname' { return "(?<host>($($host_rgx)))" } 'ip' { return "(?<host>($($ip_rgx)))" } } } function Get-PortRegex { return '(?<port>\d+)' } function Get-PodeEndpointInfo { param ( [Parameter()] [string] $Endpoint, [switch] $AnyPortOnZero ) if ([string]::IsNullOrWhiteSpace($Endpoint)) { return $null } $hostRgx = Get-HostIPRegex -Type Both $portRgx = Get-PortRegex $cmbdRgx = "$($hostRgx)\:$($portRgx)" # validate that we have a valid ip/host:port address if (!(($Endpoint -imatch "^$($cmbdRgx)$") -or ($Endpoint -imatch "^$($hostRgx)[\:]{0,1}") -or ($Endpoint -imatch "[\:]{0,1}$($portRgx)$"))) { throw "Failed to parse '$($Endpoint)' as a valid IP/Host:Port address" } # grab the ip address/hostname $_host = $Matches['host'] if (Test-Empty $_host) { $_host = '*' } # ensure we have a valid ip address/hostname if (!(Test-IPAddress -IP $_host)) { throw "The IP address supplied is invalid: $($_host)" } # grab the port $_port = $Matches['port'] if (Test-Empty $_port) { $_port = 0 } # ensure the port is valid if ($_port -lt 0) { throw "The port cannot be negative: $($_port)" } # return the info return @{ 'Host' = $_host; 'Port' = (iftet ($AnyPortOnZero -and $_port -eq 0) '*' $_port); } } function Test-IPAddress { param ( [Parameter()] [string] $IP ) if ((Test-Empty $IP) -or ($IP -ieq '*') -or ($IP -ieq 'all') -or ($IP -imatch "^$(Get-HostIPRegex -Type Hostname)$")) { return $true } try { [System.Net.IPAddress]::Parse($IP) | Out-Null return $true } catch [exception] { return $false } } function Test-Hostname { param ( [Parameter()] [string] $Hostname ) return ($Hostname -imatch "^$(Get-HostIPRegex -Type Hostname)$") } function ConvertTo-IPAddress { param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] $Endpoint ) return [System.Net.IPAddress]::Parse(([System.Net.IPEndPoint]$Endpoint).Address.ToString()) } function Get-IPAddressesForHostname { param ( [Parameter(Mandatory=$true)] [string] $Hostname, [Parameter(Mandatory=$true)] [ValidateSet('All', 'IPv4', 'IPv6')] [string] $Type ) # get the ip addresses for the hostname $ips = @([System.Net.Dns]::GetHostAddresses($Hostname)) # return ips based on type switch ($Type.ToLowerInvariant()) { 'ipv4' { $ips = @(($ips | Where-Object { $_.AddressFamily -ieq 'InterNetwork' })) } 'ipv6' { $ips = @(($ips | Where-Object { $_.AddressFamily -ieq 'InterNetworkV6' })) } } return @(($ips | Select-Object -ExpandProperty IPAddressToString)) } function Test-IPAddressLocal { param ( [Parameter(Mandatory=$true)] [string] $IP ) return (@('127.0.0.1', '::1', '[::1]', 'localhost') -icontains $IP) } function Test-IPAddressAny { param ( [Parameter(Mandatory=$true)] [string] $IP ) return (@('0.0.0.0', '*', 'all', '::', '[::]') -icontains $IP) } function Test-IPAddressLocalOrAny { param ( [Parameter(Mandatory=$true)] [string] $IP ) return ((Test-IPAddressLocal -IP $IP) -or (Test-IPAddressAny -IP $IP)) } function Get-IPAddress { param ( [Parameter()] [string] $IP ) if ((Test-Empty $IP) -or ($IP -ieq '*') -or ($IP -ieq 'all')) { return [System.Net.IPAddress]::Any } if (($IP -ieq '::') -or ($IP -ieq '[::]')) { return [System.Net.IPAddress]::IPv6Any } if ($IP -imatch "^$(Get-HostIPRegex -Type Hostname)$") { return $IP } return [System.Net.IPAddress]::Parse($IP) } function Test-IPAddressInRange { param ( [Parameter(Mandatory=$true)] $IP, [Parameter(Mandatory=$true)] $LowerIP, [Parameter(Mandatory=$true)] $UpperIP ) if ($IP.Family -ine $LowerIP.Family) { return $false } $valid = $true 0..3 | ForEach-Object { if ($valid -and (($IP.Bytes[$_] -lt $LowerIP.Bytes[$_]) -or ($IP.Bytes[$_] -gt $UpperIP.Bytes[$_]))) { $valid = $false } } return $valid } function Test-IPAddressIsSubnetMask { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $IP ) return (($IP -split '/').Length -gt 1) } function Get-SubnetRange { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $SubnetMask ) # split for ip and number of 1 bits $split = $SubnetMask -split '/' if ($split.Length -le 1) { return $null } $ip_parts = $split[0] -isplit '\.' $bits = [int]$split[1] # generate the netmask $network = @("", "", "", "") $count = 0 foreach ($i in 0..3) { foreach ($b in 1..8) { $count++ if ($count -le $bits) { $network[$i] += "1" } else { $network[$i] += "0" } } } # covert netmask to bytes 0..3 | ForEach-Object { $network[$_] = [Convert]::ToByte($network[$_], 2) } # calculate the bottom range $bottom = @(0..3 | ForEach-Object { [byte]([byte]$network[$_] -band [byte]$ip_parts[$_]) }) # calculate the range $range = @(0..3 | ForEach-Object { 256 + (-bnot [byte]$network[$_]) }) # calculate the top range $top = @(0..3 | ForEach-Object { [byte]([byte]$ip_parts[$_] + [byte]$range[$_]) }) return @{ 'Lower' = ($bottom -join '.'); 'Upper' = ($top -join '.'); 'Range' = ($range -join '.'); 'Netmask' = ($network -join '.'); 'IP' = ($ip_parts -join '.'); } } function Add-PodeRunspace { param ( [Parameter(Mandatory=$true)] [ValidateSet('Main', 'Schedules', 'Gui')] [string] $Type, [Parameter(Mandatory=$true)] [ValidateNotNull()] [scriptblock] $ScriptBlock, [Parameter()] $Parameters, [switch] $Forget ) try { $ps = [powershell]::Create() $ps.RunspacePool = $PodeSession.RunspacePools[$Type] $ps.AddScript({ Add-PodePSDrives }) | Out-Null $ps.AddScript($ScriptBlock) | Out-Null if (!(Test-Empty $Parameters)) { $Parameters.Keys | ForEach-Object { $ps.AddParameter($_, $Parameters[$_]) | Out-Null } } if ($Forget) { $ps.BeginInvoke() | Out-Null } else { $PodeSession.Runspaces += @{ 'Pool' = $Type; 'Runspace' = $ps; 'Status' = $ps.BeginInvoke(); 'Stopped' = $false; } } } catch { $Error[0] | Out-Default throw $_.Exception } } function Close-PodeRunspaces { param ( [switch] $ClosePool ) try { if (!(Test-Empty $PodeSession.Runspaces)) { # sleep for 1s before doing this, to let listeners dispose Start-Sleep -Seconds 1 # now dispose runspaces $PodeSession.Runspaces | Where-Object { !$_.Stopped } | ForEach-Object { dispose $_.Runspace $_.Stopped = $true } $PodeSession.Runspaces = @() } # dispose the runspace pools if ($ClosePool -and $null -ne $PodeSession.RunspacePools) { $PodeSession.RunspacePools.Values | Where-Object { $null -ne $_ -and !$_.IsDisposed } | ForEach-Object { dispose $_ -Close } } } catch { $Error[0] | Out-Default throw $_.Exception } } function Get-ConsoleKey { if ([Console]::IsInputRedirected -or ![Console]::KeyAvailable) { return $null } return [Console]::ReadKey($true) } function Test-TerminationPressed { param ( [Parameter()] $Key = $null ) if ($PodeSession.DisableTermination) { return $false } if ($null -eq $Key) { $Key = Get-ConsoleKey } return ($null -ne $Key -and $Key.Key -ieq 'c' -and $Key.Modifiers -band [ConsoleModifiers]::Control) } function Test-RestartPressed { param ( [Parameter()] $Key = $null ) if ($null -eq $Key) { $Key = Get-ConsoleKey } return ($null -ne $Key -and $Key.Key -ieq 'r' -and $Key.Modifiers -band [ConsoleModifiers]::Control) } function Start-TerminationListener { Add-PodeRunspace -Type 'Main' { # default variables $options = "AllowCtrlC,IncludeKeyUp,NoEcho" $ctrlState = "LeftCtrlPressed" $char = 'c' $cancel = $false # are we on ps-core? $onCore = ($PSVersionTable.PSEdition -ieq 'core') while ($true) { if ($Console.UI.RawUI.KeyAvailable) { $key = $Console.UI.RawUI.ReadKey($options) if ([char]$key.VirtualKeyCode -ieq $char) { if ($onCore) { $cancel = ($key.Character -ine $char) } else { $cancel = (($key.ControlKeyState -band $ctrlState) -ieq $ctrlState) } } if ($cancel) { Write-Host 'Terminating...' -NoNewline $PodeSession.Tokens.Cancellation.Cancel() break } } Start-Sleep -Milliseconds 10 } } } function Close-Pode { param ( [switch] $Exit ) # stpo all current runspaces Close-PodeRunspaces -ClosePool # stop the file monitor if it's running Stop-PodeFileMonitor try { # remove all the cancellation tokens dispose $PodeSession.Tokens.Cancellation dispose $PodeSession.Tokens.Restart } catch { $Error[0] | Out-Default } # remove all of the pode temp drives Remove-PodePSDrives if ($Exit -and ![string]::IsNullOrWhiteSpace($PodeSession.Server.Type)) { Write-Host " Done" -ForegroundColor Green } } function New-PodePSDrive { param ( [Parameter(Mandatory=$true)] [string] $Path, [Parameter()] [string] $Name ) # if no name is passed, used a randomly generated one if ([string]::IsNullOrWhiteSpace($Name)) { $Name = "PodeDir$(Get-NewGuid)" } # if the path supplied doesn't exist, error if (!(Test-Path $Path)) { throw "Path does not exist: $($Path)" } # create the temp drive $drive = (New-PSDrive -Name $Name -PSProvider FileSystem -Root $Path -Scope Global) # store internally, and return the drive's name if (!$PodeSession.Server.Drives.ContainsKey($drive.Name)) { $PodeSession.Server.Drives[$drive.Name] = $Path } return "$($drive.Name):" } function Add-PodePSDrives { $PodeSession.Server.Drives.Keys | ForEach-Object { New-PodePSDrive -Path $PodeSession.Server.Drives[$_] -Name $_ | Out-Null } } function Add-PodePSInbuiltDrives { # create drive for views, if path exists $path = (Join-ServerRoot 'views') if (Test-Path $path) { $PodeSession.Server.InbuiltDrives['views'] = (New-PodePSDrive -Path $path) } # create drive for public content, if path exists $path = (Join-ServerRoot 'public') if (Test-Path $path) { $PodeSession.Server.InbuiltDrives['public'] = (New-PodePSDrive -Path $path) } } function Remove-PodePSDrives { Get-PSDrive PodeDir* | Remove-PSDrive | Out-Null } <# # Sourced and editted from https://davewyatt.wordpress.com/2014/04/06/thread-synchronization-in-powershell/ #> function Lock { param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] [object] $InputObject, [Parameter(Mandatory=$true)] [ValidateNotNull()] [scriptblock] $ScriptBlock ) if ($null -eq $InputObject) { return } if ($InputObject.GetType().IsValueType) { throw 'Cannot lock value types' } $locked = $false try { [System.Threading.Monitor]::Enter($InputObject.SyncRoot) $locked = $true if ($ScriptBlock -ne $null) { Invoke-ScriptBlock -ScriptBlock $ScriptBlock -NoNewClosure } } catch { $Error[0] | Out-Default throw $_.Exception } finally { if ($locked) { [System.Threading.Monitor]::Pulse($InputObject.SyncRoot) [System.Threading.Monitor]::Exit($InputObject.SyncRoot) } } } function Join-ServerRoot { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Folder, [Parameter()] [string] $FilePath, [Parameter()] [string] $Root ) # use the root path of the server if (Test-Empty $Root) { $Root = $PodeSession.Server.Root } # join the folder/file to the root path if ([string]::IsNullOrWhiteSpace($FilePath)) { return (Join-Path $Root $Folder) } else { return (Join-Path $Root (Join-Path $Folder $FilePath)) } } function Invoke-ScriptBlock { param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] [Alias('s')] [scriptblock] $ScriptBlock, [Parameter()] [Alias('a')] $Arguments = $null, [switch] $Scoped, [switch] $Return, [switch] $Splat, [switch] $NoNewClosure ) if (!$NoNewClosure) { $ScriptBlock = ($ScriptBlock).GetNewClosure() } if ($Scoped) { if ($Splat) { $result = (& $ScriptBlock @Arguments) } else { $result = (& $ScriptBlock $Arguments) } } else { if ($Splat) { $result = (. $ScriptBlock @Arguments) } else { $result = (. $ScriptBlock $Arguments) } } if ($Return) { return $result } } <# If-This-Else-That. If Check is true return Value1, else return Value2 #> function Iftet { param ( [Parameter()] [bool] $Check, [Parameter()] $Value1, [Parameter()] $Value2 ) if ($Check) { return $Value1 } return $Value2 } function Coalesce { param ( [Parameter()] $Value1, [Parameter()] $Value2 ) return (iftet (Test-Empty $Value1) $Value2 $Value1) } function Get-FileExtension { param ( [Parameter()] [string] $Path, [switch] $TrimPeriod ) $ext = [System.IO.Path]::GetExtension($Path) if ($TrimPeriod) { $ext = $ext.Trim('.') } return $ext } function Get-FileName { param ( [Parameter()] [string] $Path, [switch] $WithoutExtension ) if ($WithoutExtension) { return [System.IO.Path]::GetFileNameWithoutExtension($Path) } return [System.IO.Path]::GetFileName($Path) } function Stopwatch { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(Mandatory=$true)] [ValidateNotNull()] [scriptblock] $ScriptBlock ) try { $watch = [System.Diagnostics.Stopwatch]::StartNew() . $ScriptBlock } catch { $Error[0] | Out-Default throw $_.Exception } finally { $watch.Stop() Out-Default -InputObject "[Stopwatch]: $($watch.Elapsed) [$($Name)]" } } function Test-ValidNetworkFailure { param ( [Parameter()] $Exception ) $msgs = @( '*network name is no longer available*', '*nonexistent network connection*', '*broken pipe*' ) return (($msgs | Where-Object { $Exception.Message -ilike $_ } | Measure-Object).Count -gt 0) } function ConvertFrom-RequestContent { param ( [Parameter()] $Request ) # get the requests content type and boundary $MetaData = Get-ContentTypeAndBoundary -ContentType $Request.ContentType $Encoding = $Request.ContentEncoding # result object for data/files $Result = @{ 'Data' = @{}; 'Files' = @{}; } # if there is no content-type then do nothing if (Test-Empty $MetaData.ContentType) { return $Result } # if the content-type is not multipart/form-data, get the string data if ($MetaData.ContentType -ine 'multipart/form-data') { $Content = Read-StreamToEnd -Stream $Request.InputStream -Encoding $Encoding # if there is no content then do nothing if (Test-Empty $Content) { return $Result } } # run action for the content type switch ($MetaData.ContentType) { { $_ -ilike '*/json' } { $Result.Data = ($Content | ConvertFrom-Json) } { $_ -ilike '*/xml' } { $Result.Data = [xml]($Content) } { $_ -ilike '*/csv' } { $Result.Data = ($Content | ConvertFrom-Csv) } { $_ -ilike '*/x-www-form-urlencoded' } { $Result.Data = (ConvertFrom-NameValueToHashTable -Collection ([System.Web.HttpUtility]::ParseQueryString($Content))) } { $_ -ieq 'multipart/form-data' } { # convert the stream to bytes $Content = ConvertFrom-StreamToBytes -Stream $Request.InputStream $Lines = Get-ByteLinesFromByteArray -Bytes $Content -Encoding $Encoding -IncludeNewLine # get the indexes for boundary lines (start and end) $boundaryIndexes = @() for ($i = 0; $i -lt $Lines.Length; $i++) { if ((Test-ByteArrayIsBoundary -Bytes $Lines[$i] -Boundary $MetaData.Boundary.Start -Encoding $Encoding) -or (Test-ByteArrayIsBoundary -Bytes $Lines[$i] -Boundary $MetaData.Boundary.End -Encoding $Encoding)) { $boundaryIndexes += $i } } # loop through the boundary indexes (exclude last, as it's the end boundary) for ($i = 0; $i -lt ($boundaryIndexes.Length - 1); $i++) { $bIndex = $boundaryIndexes[$i] # the next line contains the key-value field names (content-disposition) $fields = @{} $disp = ConvertFrom-BytesToString -Bytes $Lines[$bIndex+1] -Encoding $Encoding -RemoveNewLine @($disp -isplit ';') | ForEach-Object { $atoms = @($_ -isplit '=') if ($atoms.Length -eq 2) { $fields.Add($atoms[0].Trim(), $atoms[1].Trim(' "')) } } # use the next line to work out field values if (!$fields.ContainsKey('filename')) { $value = ConvertFrom-BytesToString -Bytes $Lines[$bIndex+3] -Encoding $Encoding -RemoveNewLine $Result.Data.Add($fields.name, $value) } # if we have a file, work out file and content type if ($fields.ContainsKey('filename')) { $Result.Data.Add($fields.name, $fields.filename) if (!(Test-Empty $fields.filename)) { $type = ConvertFrom-BytesToString -Bytes $Lines[$bIndex+2] -Encoding $Encoding -RemoveNewLine $Result.Files.Add($fields.filename, @{ 'ContentType' = (@($type -isplit ':')[1].Trim()); 'Bytes' = $null; }) $bytes = @() $Lines[($bIndex+4)..($boundaryIndexes[$i+1]-1)] | ForEach-Object { $bytes += $_ } $Result.Files[$fields.filename].Bytes = (Remove-NewLineBytesFromArray $bytes $Encoding) } } } } default { $Result.Data = $Content } } return $Result } function Test-ByteArrayIsBoundary { param ( [Parameter()] [byte[]] $Bytes, [Parameter()] [string] $Boundary, [Parameter()] $Encoding = [System.Text.Encoding]::UTF8 ) # if no bytes, return if ($Bytes.Length -eq 0) { return $false } # if length difference >3, return (ie, 2 offset for `r`n) if (($Bytes.Length - $Boundary.Length) -gt 3) { return $false } # check if bytes starts with the boundary return (ConvertFrom-BytesToString $Bytes $Encoding).StartsWith($Boundary) } function Get-ContentTypeAndBoundary { param ( [Parameter()] [string] $ContentType ) $obj = @{ 'ContentType' = [string]::Empty; 'Boundary' = @{ 'Start' = [string]::Empty; 'End' = [string]::Empty; } } if (Test-Empty $ContentType) { return $obj } $split = @($ContentType -isplit ';') $obj.ContentType = $split[0].Trim() if ($split.Length -gt 1) { $obj.Boundary.Start = "--$(($split[1] -isplit '=')[1].Trim())" $obj.Boundary.End = "$($obj.Boundary.Start)--" } return $obj } function ConvertFrom-NameValueToHashTable { param ( [Parameter()] $Collection ) if ($null -eq $Collection) { return $null } $ht = @{} $Collection.Keys | ForEach-Object { $ht[$_] = $Collection[$_] } return $ht } function Get-NewGuid { return ([guid]::NewGuid()).ToString() } function Get-Count { param ( [Parameter()] $Object ) return ($Object | Measure-Object).Count } function Get-ContentAsBytes { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Path ) if (Test-IsPSCore) { return (Get-Content -Path $Path -Raw -AsByteStream) } return (Get-Content -Path $Path -Raw -Encoding byte) } function Test-PathAccess { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Path ) try { Get-Item $Path | Out-Null } catch [System.UnauthorizedAccessException] { return $false } return $true } function Test-PodePath { param ( [Parameter()] $Path, [switch] $NoStatus, [switch] $FailOnDirectory ) # if the file doesnt exist then fail on 404 if ((Test-Empty $Path) -or !(Test-Path $Path)) { if (!$NoStatus) { status 404 } return $false } # if the file isn't accessible then fail 401 if (!(Test-PathAccess $Path)) { if (!$NoStatus) { status 401 } return $false } # if we're failing on a directory then fail on 404 if ($FailOnDirectory -and (Test-PathIsDirectory $Path)) { if (!$NoStatus) { status 404 } return $false } return $true } function Test-PathIsFile { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Path ) return (![string]::IsNullOrWhiteSpace([System.IO.Path]::GetExtension($Path))) } function Test-PathIsDirectory { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Path ) return ([string]::IsNullOrWhiteSpace([System.IO.Path]::GetExtension($Path))) } |