diff --git a/src/functions/public/ConvertFrom-Base64Url.ps1 b/src/functions/public/ConvertFrom-Base64Url.ps1 new file mode 100644 index 0000000..39a735a --- /dev/null +++ b/src/functions/public/ConvertFrom-Base64Url.ps1 @@ -0,0 +1,74 @@ +function ConvertFrom-Base64Url { + <# + .SYNOPSIS + Decodes a base64url-encoded string or array of strings into UTF-8 strings. + + .DESCRIPTION + Converts a base64url-encoded string or array of strings into human-readable UTF-8 strings. Base64url encoding + is a URL-safe variant of base64 that uses '-' instead of '+', '_' instead of '/', and omits padding '=' characters. + The function accepts input from the pipeline and validates the input using the `Test-Base64Url` function before decoding. + + .EXAMPLE + "U29tZSBkYXRh" | ConvertFrom-Base64Url + + Output: + ```powershell + Some data + ``` + + Decodes the base64url-encoded string "U29tZSBkYXRh" into its original UTF-8 representation. + + .EXAMPLE + @("SGVsbG8", "V29ybGQ") | ConvertFrom-Base64Url + + Output: + ```powershell + Hello + World + ``` + + Decodes each base64url-encoded string in the array into its original UTF-8 representation. + + .OUTPUTS + System.String + + .NOTES + The decoded UTF-8 string(s). + + .LINK + https://psmodule.io/Base64/Functions/ConvertFrom-Base64Url/ + #> + [Alias('ConvertFrom-Base64UrlString')] + [OutputType([string])] + [CmdletBinding()] + param( + # The base64url-encoded string or array of strings to be decoded. + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName + )] + [ValidateScript({ Test-Base64Url -Base64UrlString $_ }, ErrorMessage = 'Invalid Base64Url string')] + [string[]] $Base64UrlString, + + # The encoding to use when converting the string to bytes. + [Parameter()] + [ValidateSet('UTF8', 'UTF7', 'UTF32', 'ASCII', 'Unicode', 'BigEndianUnicode', 'Latin1')] + [string] $Encoding = 'UTF8' + ) + + process { + foreach ($item in $Base64UrlString) { + # Convert base64url to base64 by replacing '-' with '+', '_' with '/', and adding padding if needed + $base64 = $item.Replace('-', '+').Replace('_', '/') + + # Add padding if needed (base64 length must be multiple of 4) + $padding = $base64.Length % 4 + if ($padding -ne 0) { + $base64 += '=' * (4 - $padding) + } + + [System.Text.Encoding]::$Encoding.GetString([Convert]::FromBase64String($base64)) + } + } +} \ No newline at end of file diff --git a/src/functions/public/ConvertTo-Base64Url.ps1 b/src/functions/public/ConvertTo-Base64Url.ps1 new file mode 100644 index 0000000..8e91d3d --- /dev/null +++ b/src/functions/public/ConvertTo-Base64Url.ps1 @@ -0,0 +1,67 @@ +function ConvertTo-Base64Url { + <# + .SYNOPSIS + Converts a string or array of strings to their base64url encoded representation. + + .DESCRIPTION + This function takes a string or array of strings as input and converts them to base64url encoded strings using UTF-8 encoding. + Base64url encoding is a URL-safe variant of base64 that replaces '+' with '-', '/' with '_', and removes padding '=' characters. + It accepts input from the pipeline and can process string values directly or as an array. + + .EXAMPLE + "Hello World" | ConvertTo-Base64Url + + Output: + ```powershell + SGVsbG8gV29ybGQ + ``` + + Converts the string "Hello World" to its base64url encoded equivalent. + + .EXAMPLE + @("Hello", "World") | ConvertTo-Base64Url + + Output: + ```powershell + SGVsbG8 + V29ybGQ + ``` + + Converts each string in the array to its base64url encoded equivalent. + + .OUTPUTS + System.String + + .NOTES + The base64url encoded representation of the input string(s). + + .LINK + https://psmodule.io/Base64/Functions/ConvertTo-Base64Url/ + #> + [Alias('ConvertTo-Base64UrlString')] + [OutputType([string])] + [CmdletBinding()] + param( + # The input string or array of strings to be converted to base64url encoding. + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName + )] + [string[]] $String, + + # The encoding to use when converting the string to bytes. + [Parameter()] + [ValidateSet('UTF8', 'UTF7', 'UTF32', 'ASCII', 'Unicode', 'BigEndianUnicode', 'Latin1')] + [string] $Encoding = 'UTF8' + ) + + process { + foreach ($item in $String) { + $base64 = [Convert]::ToBase64String([System.Text.Encoding]::$Encoding.GetBytes($item)) + # Convert to base64url by replacing '+' with '-', '/' with '_', and removing padding '=' + $base64url = $base64.Replace('+', '-').Replace('/', '_').TrimEnd('=') + $base64url + } + } +} \ No newline at end of file diff --git a/src/functions/public/Test-Base64Url.ps1 b/src/functions/public/Test-Base64Url.ps1 new file mode 100644 index 0000000..feb5e27 --- /dev/null +++ b/src/functions/public/Test-Base64Url.ps1 @@ -0,0 +1,73 @@ +filter Test-Base64Url { + <# + .SYNOPSIS + Determines whether a given string is a valid base64url-encoded string. + + .DESCRIPTION + This function checks whether the provided string is a valid base64url-encoded string. + Base64url encoding is a URL-safe variant of base64 that uses '-' instead of '+', '_' instead of '/', + and omits padding '=' characters. It attempts to decode the input by converting it to standard base64 + and using `[Convert]::FromBase64String()`. If the decoding succeeds, it returns `$true`; otherwise, it returns `$false`. + + .EXAMPLE + Test-Base64Url -Base64UrlString 'U29tZSBkYXRh' + + Output: + ```powershell + True + ``` + + Returns `$true` as the string is a valid base64url-encoded string. + + .EXAMPLE + 'U29tZSBkYXRh' | Test-Base64Url + + Output: + ```powershell + True + ``` + + Returns `$true` as the string is a valid base64url-encoded string. + + .OUTPUTS + bool + + .NOTES + Returns `$true` if the string is a valid base64url-encoded string, otherwise `$false`. + + .LINK + https://psmodule.io/Test/Functions/Test-Base64Url + #> + [OutputType([bool])] + [CmdletBinding()] + param ( + # The base64url-encoded string to validate. + [Parameter( + Mandatory, + ValueFromPipeline + )] + [string] $Base64UrlString + ) + + try { + # Check for invalid characters (base64url should only contain A-Z, a-z, 0-9, -, _) + if ($Base64UrlString -match '[^A-Za-z0-9\-_]') { + return $false + } + + # Convert base64url to base64 by replacing '-' with '+', '_' with '/' + $base64 = $Base64UrlString.Replace('-', '+').Replace('_', '/') + + # Add padding if needed (base64 length must be multiple of 4) + $padding = $base64.Length % 4 + if ($padding -ne 0) { + $base64 += '=' * (4 - $padding) + } + + # Try to decode + $null = [Convert]::FromBase64String($base64) + $true + } catch { + $false + } +} \ No newline at end of file diff --git a/tests/Base64.Tests.ps1 b/tests/Base64.Tests.ps1 index 9d0002d..a76b14d 100644 --- a/tests/Base64.Tests.ps1 +++ b/tests/Base64.Tests.ps1 @@ -3,6 +3,9 @@ . "$PSScriptRoot/../src/functions/public/Test-Base64.ps1" . "$PSScriptRoot/../src/functions/public/ConvertTo-Base64.ps1" . "$PSScriptRoot/../src/functions/public/ConvertFrom-Base64.ps1" + . "$PSScriptRoot/../src/functions/public/Test-Base64Url.ps1" + . "$PSScriptRoot/../src/functions/public/ConvertTo-Base64Url.ps1" + . "$PSScriptRoot/../src/functions/public/ConvertFrom-Base64Url.ps1" # Create test files $testContentPath = '/tmp/base64_test/test_content.txt' @@ -111,4 +114,124 @@ Describe 'Base64' { $result[2] | Should -Be 'Line 3' } } + Context 'Function: Test-Base64Url' { + It "Test-Base64Url -Base64UrlString 'VGhpc0lzQU5pY2VTdHJpbmc' -> true" { + Test-Base64Url -Base64UrlString 'VGhpc0lzQU5pY2VTdHJpbmc' | Should -Be $true + } + It "'SGVsbG8gV29ybGQ' | Test-Base64Url -> true" { + 'SGVsbG8gV29ybGQ' | Test-Base64Url | Should -Be $true + } + It "Test-Base64Url with invalid characters should return false" { + 'Invalid+String/With=Padding' | Test-Base64Url | Should -Be $false + } + It "Test-Base64Url with URL-safe characters should return true" { + 'VGhpcyB3aWxsIGhhdmUgKyBhbmQgLyBjaGFyYWN0ZXJzIHdoZW4gZW5jb2RlZA' | Test-Base64Url | Should -Be $true + } + } + Context 'Function: ConvertTo-Base64Url' { + It "ConvertTo-Base64Url -String 'ThisIsANiceString' -> VGhpc0lzQU5pY2VTdHJpbmc" { + ConvertTo-Base64Url -String 'ThisIsANiceString' | Should -Be 'VGhpc0lzQU5pY2VTdHJpbmc' + } + + It "'Hello World' | ConvertTo-Base64Url -> SGVsbG8gV29ybGQ" { + 'Hello World' | ConvertTo-Base64Url | Should -Be 'SGVsbG8gV29ybGQ' + } + It "ConvertTo-Base64Url -String @('Hello', 'World') -> @('SGVsbG8', 'V29ybGQ')" { + $result = ConvertTo-Base64Url -String @('Hello', 'World') + $result.Count | Should -Be 2 + $result[0] | Should -Be 'SGVsbG8' + $result[1] | Should -Be 'V29ybGQ' + } + + It "@('Hello', 'World') | ConvertTo-Base64Url -> @('SGVsbG8', 'V29ybGQ')" { + $result = @('Hello', 'World') | ConvertTo-Base64Url + $result.Count | Should -Be 2 + $result[0] | Should -Be 'SGVsbG8' + $result[1] | Should -Be 'V29ybGQ' + } + + It "Variable containing multiple strings piped to ConvertTo-Base64Url" { + $strings = @('Hello', 'World') + $result = $strings | ConvertTo-Base64Url + $result.Count | Should -Be 2 + $result[0] | Should -Be 'SGVsbG8' + $result[1] | Should -Be 'V29ybGQ' + } + + It "File content piped to ConvertTo-Base64Url" { + $testFilePath = '/tmp/base64_test/test_content.txt' + $result = Get-Content -Path $testFilePath | ConvertTo-Base64Url + $result.Count | Should -Be 3 + $result[0] | Should -Be 'TGluZSAx' + $result[1] | Should -Be 'TGluZSAy' + $result[2] | Should -Be 'TGluZSAz' + } + + It "ConvertTo-Base64Url removes padding characters" { + # Test string that would normally have padding + 'sure.' | ConvertTo-Base64Url | Should -Be 'c3VyZS4' + } + + It "ConvertTo-Base64Url replaces + and / with URL-safe characters" { + # Test string that contains characters that would result in + and / in base64 + 'subject?' | ConvertTo-Base64Url | Should -Be 'c3ViamVjdD8' + } + } + Context 'Function: ConvertFrom-Base64Url' { + It "ConvertFrom-Base64Url -Base64UrlString 'VGhpc0lzQU5pY2VTdHJpbmc' -> ThisIsANiceString" { + ConvertFrom-Base64Url -Base64UrlString 'VGhpc0lzQU5pY2VTdHJpbmc' | Should -Be 'ThisIsANiceString' + } + + It "'SGVsbG8gV29ybGQ' | ConvertFrom-Base64Url -> Hello World" { + 'SGVsbG8gV29ybGQ' | ConvertFrom-Base64Url | Should -Be 'Hello World' + } + It "ConvertFrom-Base64Url -Base64UrlString @('SGVsbG8', 'V29ybGQ') -> @('Hello', 'World')" { + $result = ConvertFrom-Base64Url -Base64UrlString @('SGVsbG8', 'V29ybGQ') + $result.Count | Should -Be 2 + $result[0] | Should -Be 'Hello' + $result[1] | Should -Be 'World' + } + + It "@('SGVsbG8', 'V29ybGQ') | ConvertFrom-Base64Url -> @('Hello', 'World')" { + $result = @('SGVsbG8', 'V29ybGQ') | ConvertFrom-Base64Url + $result.Count | Should -Be 2 + $result[0] | Should -Be 'Hello' + $result[1] | Should -Be 'World' + } + + It "Variable containing multiple Base64Url strings piped to ConvertFrom-Base64Url" { + $base64UrlStrings = @('SGVsbG8', 'V29ybGQ') + $result = $base64UrlStrings | ConvertFrom-Base64Url + $result.Count | Should -Be 2 + $result[0] | Should -Be 'Hello' + $result[1] | Should -Be 'World' + } + + It "File content with Base64Url strings piped to ConvertFrom-Base64Url" { + $testBase64UrlFilePath = '/tmp/base64_test/test_base64url_content.txt' + @('TGluZSAx', 'TGluZSAy', 'TGluZSAz') | Out-File -FilePath $testBase64UrlFilePath + $result = Get-Content -Path $testBase64UrlFilePath | ConvertFrom-Base64Url + $result.Count | Should -Be 3 + $result[0] | Should -Be 'Line 1' + $result[1] | Should -Be 'Line 2' + $result[2] | Should -Be 'Line 3' + } + + It "ConvertFrom-Base64Url handles missing padding correctly" { + # Test string without padding + 'c3VyZS4' | ConvertFrom-Base64Url | Should -Be 'sure.' + } + + It "ConvertFrom-Base64Url handles URL-safe characters correctly" { + # Test string with URL-safe characters + 'c3ViamVjdD8' | ConvertFrom-Base64Url | Should -Be 'subject?' + } + + It "Round-trip conversion maintains data integrity" { + $original = 'Test string with special chars: +/?=' + $encoded = $original | ConvertTo-Base64Url + $decoded = $encoded | ConvertFrom-Base64Url + $decoded | Should -Be $original + } + } }