ps/Modules/Cole.PowerShell.Developer/Public/Join-UrlComponents.ps1

277 lines
11 KiB
PowerShell
Raw Normal View History

2023-05-30 22:51:22 -07:00
function Join-UrlComponents {
<#
.SYNOPSIS
Used to join together the parts of a URL based on inputs
.PARAMETER UrlComponents
An object of url components. Typically comprised of one or more of the other parameters on this method
.PARAMETER BaseUrl
A [System.Uri] (or string representation thereof) to start the Url from
.PARAMETER Hostname
A hostname (or IP address) to connect to
.PARAMETER Scheme
The connection scheme. Ex: http, https. Defaults to http.
.PARAMETER Port
The port to be used to connect to. Can be null.
.PARAMETER Path
The path to connect to. Can supply one or more values
.PARAMETER Query
The query string to append to the Url. Can supply one or more values
.PARAMETER Fragment
The fragment to append to the Url. Can supply one or more values
.PARAMETER Username
The username for the connection. This is typically frowned upon. Prefer a secure header.
.PARAMETER SecureString
The password for the connection. This is typically frowned upon. Prefer a secure header.
.PARAMETER Credential
The username and password for the connection. This is typically frowned upon. Prefer a secure header.
#>
[CmdletBinding(DefaultParameterSetName = 'PartsWithUsername')]
[OutputType([string])]
param (
[Parameter(Mandatory = $true, ParameterSetName = 'UrlComponents', ValueFromPipeline = $true)]
[object]$UrlComponents,
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithCredential')]
[string]$BaseUrl,
[Parameter(Mandatory = $true, ParameterSetName = 'PartsWithUsername')]
[Parameter(Mandatory = $true, ParameterSetName = 'PartsWithCredential')]
[Alias("Host")]
[string]$Hostname,
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
[string]$Scheme,
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
[AllowNull()]
[Nullable[uint16]]$Port,
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
[string[]]$Path,
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
[object]$Query,
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
[string]$Fragment,
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
[string]$Username,
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
[Alias("Password")]
[SecureString]$SecureString,
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithCredential')]
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
[PSCredential]$Credential
)
$logLead = (Get-LogLeadName)
$password = $null
$schemeDelimiter = "://"
$isDefaultPort = $false
$defaultPorts = @{
ftp = 21;
ssh = 22;
telnet = 23;
mailto = 25;
http = 80;
ldap = 389;
https = 443;
"net.tcp" = 808;
}
if ($PSCmdlet.ParameterSetName -eq 'UrlComponents') {
if ($null -eq $UrlComponents) {
throw "$logLead : `$null values can not be supplied for the UrlComponent object"
}
# Set the other values to the defaults provided by this object if present
$BaseUrl = $UrlComponents.BaseUrl
$Hostname = $UrlComponents.Hostname
$Scheme = $UrlComponents.Scheme
$Port = $UrlComponents.Port
$pathCoalesce = Coalesce $UrlComponents.Segments $UrlComponents.Path
$Path = @($pathCoalesce)
$Query = @($UrlComponents.Query)
$Fragment = $UrlComponents.Fragment
$Username = $UrlComponents.Username
$password = $UrlComponents.Password
$Credential = $UrlComponents.Credential
}
if ($null -ne $SecureString) {
$password = (ConvertFrom-SecureString $SecureString)
}
if ($null -ne $Credential) {
if ([string]::IsNullOrWhiteSpace($Username)) {
$Username = $Credential.Username
}
if ([string]::IsNullOrWhiteSpace($password)) {
$password = (Get-PasswordFromCredential -Credential $Credential)
}
}
if (![string]::IsNullOrWhiteSpace($BaseUrl)) {
# got a baseurl, see if there are assignable values that weren't also supplied
$uri = [Uri]::new($BaseUrl)
$Hostname = Coalesce $Hostname $uri.Host
$Scheme = Coalesce $Scheme $uri.Scheme 'http'
$Port = Coalesce $Port $uri.Port
$Path = Coalesce $Path $uri.Segments
$Query = Coalesce $Query $uri.Query
$Fragment = Coalesce $Fragment $uri.Fragment
}
if ([string]::IsNullOrWhiteSpace($Hostname)) {
throw "$logLead : Unable to determine any hostname property for this url from provided parameters. Can not continue."
}
if ([string]::IsNullOrWhiteSpace($Scheme)) {
$Scheme = 'http'
}
if (($null -eq $Port) -or ($Port -eq 0)) {
if (![string]::IsNullOrWhiteSpace($Scheme)) {
$Port = $defaultPorts[$Scheme]
}
}
$isDefaultPort = $Port -eq $defaultPorts[$Scheme]
$formattedUsernameAndPassword = ""
if (![string]::IsNullOrWhiteSpace($Username)) {
if (![string]::IsNullOrWhiteSpace($password)) {
$formattedPassword = ":$password"
}
$formattedUsernameAndPassword = "$Username$formattedPassword"
if (![string]::IsNullOrWhiteSpace($formattedUsernameAndPassword)) {
$formattedUsernameAndPassword = "$formattedUsernameAndPassword@"
}
}
$formattedPort = ""
if (!$isDefaultPort) {
$formattedPort = ":$Port"
}
$pathSegments = @()
$addTrailingSlash = $false
foreach ($pathSegment in $Path) {
$pathSegment = $pathSegment.TrimStart("/")
$addTrailingSlash = $pathSegment.EndsWith("/")
$pathSegment = $pathSegment.TrimEnd("/")
# Can't use empty segments.
# While a webserver may be able to interpret http://example.com/////path, we won't do that to the poor webserver. It'll just get http://example.com/path instead
if (![string]::IsNullOrWhiteSpace($pathSegment)) {
$pathSegments += $pathSegment
}
}
$formattedPath = $pathSegments -join '/'
# If the last path segment we saw ended with a slash, put that back on the end of the url.
# Example: REST apis may end with a slash, so Path = @("search/") => "/search/"
# Example: Path = @("search/","page.html") => "/search/page.html"
if (![string]::IsNullOrWhiteSpace($formattedPath) -and $addTrailingSlash) {
$formattedPath = "$formattedPath/"
}
$querySegments = @()
$querySegmentKeys = @("Name","Key","Id")
if ($Query -is [System.Collections.IEnumerable] -and $Query -isnot [string]) {
$keys = $Query.Keys
if (!(Any $keys)) {
$keys = @($Query.PSObject.Properties.Where({$_.MemberType -eq 'NoteProperty'}).Name)
}
if (!(Any $keys)) {
$keys = @($Query.PSObject.Properties.Where({$_.Name -eq 'Keys'}).Value)
}
if (Any $keys) {
# must be a hash object, take the names and values
foreach ($key in $keys) {
$value = $Query.$key
if ($value -is [System.Collections.IEnumerable] -and $value -isnot [string]) {
$value = $value -join ','
}
$querySegments += "$key=$value"
}
} else {
foreach ($querySegment in $Query) {
if ($querySegment -is [string]) {
$querySegment = $querySegment.TrimStart("?")
$splitSegments = $querySegment -split '&'
foreach ($splitSegment in $splitSegments) {
if (![string]::IsNullOrWhiteSpace($splitSegment)) {
$querySegments += $splitSegment
}
}
} else {
$key = ""
foreach ($queryKey in $querySegmentKeys) {
if (![string]::IsNullOrWhiteSpace($querySegment.$queryKey)) {
$key = $querySegment.Key
}
}
if ([string]::IsNullOrWhiteSpace($key)) {
continue
}
$value = $querySegment.Value
if ($null -ne $value) {
if ($value -is [System.Collections.IEnumerable] -and $value -isnot [string]) {
$value = $value -join ','
}
$querySegments += "$key=$value"
} else {
Write-Warning "$logLead : Found querySegment with matching key but no .Value element"
continue
}
}
}
}
} elseif ($Query -is [string]) {
$querySegment = $Query.TrimStart("?")
$splitSegments = $querySegment -split '&'
foreach ($splitSegment in $splitSegments) {
if (![string]::IsNullOrWhiteSpace($splitSegment)) {
$querySegments += $splitSegment
}
}
} else {
Write-Verbose "$logLead : No `$Query value to process, or can't process it"
}
$formattedQuery = ""
if (Any $querySegments) {
$formattedQuery = $querySegments -join '&'
if (![string]::IsNullOrWhiteSpace($formattedQuery)) {
$formattedQuery = "?$formattedQuery"
}
}
$formattedFragment = ""
if (![string]::IsNullOrWhiteSpace($Fragment)) {
$formattedFragment = $formattedFragment.TrimStart('#')
$formattedFragment = "#$formattedFragment"
}
$formattedUrl = "$scheme$schemeDelimiter$formattedUsernameAndPassword$Hostname$formattedPort/$formattedPath$formattedQuery$formattedFragment"
return $formattedUrl
}