Merge pull request #43 from dmilov/topic/no-content-type

fix: Incorrect parsing of Binary Content Mode cloud events
This commit is contained in:
dmilov 2021-08-30 12:05:03 +03:00 committed by GitHub
commit c715c5c97e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 526 additions and 408 deletions

View File

@ -9,7 +9,7 @@
RootModule = 'CloudEvents.Sdk.psm1' RootModule = 'CloudEvents.Sdk.psm1'
# Version number of this module. # Version number of this module.
ModuleVersion = '0.3.0' ModuleVersion = '0.3.1'
# Supported PSEditions # Supported PSEditions
CompatiblePSEditions = @('Core') CompatiblePSEditions = @('Core')

View File

@ -7,7 +7,7 @@ $xmlDataSerilizationLibPath = Join-Path (Join-Path $PSScriptRoot 'dataserializat
. $xmlDataSerilizationLibPath . $xmlDataSerilizationLibPath
function New-CloudEvent { function New-CloudEvent {
<# <#
.SYNOPSIS .SYNOPSIS
This function creates a new cloud event. This function creates a new cloud event.
@ -35,46 +35,46 @@ function New-CloudEvent {
Creates a cloud event with Type, Source, Id, and Time Creates a cloud event with Type, Source, Id, and Time
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
$Type, $Type,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[System.Uri] [System.Uri]
$Source, $Source,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[string] [string]
$Id, $Id,
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[DateTime] [DateTime]
$Time $Time
) )
PROCESS { PROCESS {
$cloudEvent = New-Object ` $cloudEvent = New-Object `
-TypeName 'CloudNative.CloudEvents.CloudEvent' ` -TypeName 'CloudNative.CloudEvents.CloudEvent' `
-ArgumentList @( -ArgumentList @(
$Type, $Type,
$Source, $Source,
$Id, $Id,
$Time, $Time,
@()) @())
Write-Output $cloudEvent Write-Output $cloudEvent
} }
} }
#region Set Data Functions #region Set Data Functions
function Set-CloudEventData { function Set-CloudEventData {
<# <#
.SYNOPSIS .SYNOPSIS
This function sets data in a cloud event. This function sets data in a cloud event.
@ -98,45 +98,45 @@ function Set-CloudEventData {
Sets xml data to the cloud event Sets xml data to the cloud event
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory = $true, [Parameter(Mandatory = $true,
ValueFromPipeline = $true)] ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[CloudNative.CloudEvents.CloudEvent] [CloudNative.CloudEvents.CloudEvent]
$CloudEvent, $CloudEvent,
[Parameter(Mandatory = $true, [Parameter(Mandatory = $true,
ValueFromPipeline = $false)] ValueFromPipeline = $false)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[object] [object]
$Data, $Data,
# CloudEvent 'datacontenttype' attribute. Content type of the 'data' attribute value. # CloudEvent 'datacontenttype' attribute. Content type of the 'data' attribute value.
# This attribute enables the data attribute to carry any type of content, whereby # This attribute enables the data attribute to carry any type of content, whereby
# format and encoding might differ from that of the chosen event format. # format and encoding might differ from that of the chosen event format.
[Parameter(Mandatory = $false, [Parameter(Mandatory = $false,
ValueFromPipeline = $false)] ValueFromPipeline = $false)]
[string] [string]
$DataContentType) $DataContentType)
PROCESS { PROCESS {
# https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype # https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype
$contentType = New-Object ` $contentType = New-Object `
-TypeName 'System.Net.Mime.ContentType' ` -TypeName 'System.Net.Mime.ContentType' `
-ArgumentList ($DataContentType) -ArgumentList ($DataContentType)
$cloudEvent.Data = $Data $cloudEvent.Data = $Data
$cloudEvent.DataContentType = $dataContentType $cloudEvent.DataContentType = $dataContentType
Write-Output $CloudEvent Write-Output $CloudEvent
} }
} }
function Set-CloudEventJsonData { function Set-CloudEventJsonData {
<# <#
.SYNOPSIS .SYNOPSIS
This function sets JSON format data in a cloud event. This function sets JSON format data in a cloud event.
@ -163,43 +163,43 @@ function Set-CloudEventJsonData {
Sets JSON data to the cloud event Sets JSON data to the cloud event
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory = $true, [Parameter(Mandatory = $true,
ValueFromPipeline = $true)] ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[CloudNative.CloudEvents.CloudEvent] [CloudNative.CloudEvents.CloudEvent]
$CloudEvent, $CloudEvent,
[Parameter(Mandatory = $true, [Parameter(Mandatory = $true,
ValueFromPipeline = $false)] ValueFromPipeline = $false)]
[ValidateNotNull()] [ValidateNotNull()]
[Hashtable] [Hashtable]
$Data, $Data,
[Parameter(Mandatory = $false, [Parameter(Mandatory = $false,
ValueFromPipeline = $false)] ValueFromPipeline = $false)]
[int] [int]
$Depth = 3) $Depth = 3)
PROCESS { PROCESS {
# DataContentType is set to 'application/json' # DataContentType is set to 'application/json'
# https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype # https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype
$dataContentType = New-Object ` $dataContentType = New-Object `
-TypeName 'System.Net.Mime.ContentType' ` -TypeName 'System.Net.Mime.ContentType' `
-ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Json) -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Json)
$cloudEvent.DataContentType = $dataContentType $cloudEvent.DataContentType = $dataContentType
$cloudEvent.Data = ConvertTo-Json -InputObject $Data -Depth $Depth $cloudEvent.Data = ConvertTo-Json -InputObject $Data -Depth $Depth
Write-Output $CloudEvent Write-Output $CloudEvent
} }
} }
function Set-CloudEventXmlData { function Set-CloudEventXmlData {
<# <#
.SYNOPSIS .SYNOPSIS
This function sets XML format data in a cloud event. This function sets XML format data in a cloud event.
@ -229,43 +229,43 @@ function Set-CloudEventXmlData {
Sets XML data in the cloud event Sets XML data in the cloud event
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory = $true, [Parameter(Mandatory = $true,
ValueFromPipeline = $true)] ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[CloudNative.CloudEvents.CloudEvent] [CloudNative.CloudEvents.CloudEvent]
$CloudEvent, $CloudEvent,
[Parameter(Mandatory = $true, [Parameter(Mandatory = $true,
ValueFromPipeline = $false)] ValueFromPipeline = $false)]
[ValidateNotNull()] [ValidateNotNull()]
[Hashtable] [Hashtable]
$Data, $Data,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[bool] [bool]
$AttributesKeysInElementAttributes) $AttributesKeysInElementAttributes)
PROCESS { PROCESS {
# DataContentType is set to 'application/xml' # DataContentType is set to 'application/xml'
$dataContentType = New-Object ` $dataContentType = New-Object `
-TypeName 'System.Net.Mime.ContentType' ` -TypeName 'System.Net.Mime.ContentType' `
-ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Xml) -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Xml)
$cloudEvent.DataContentType = $dataContentType $cloudEvent.DataContentType = $dataContentType
$cloudEvent.Data = ConvertTo-CEDataXml -InputObject $Data -AttributesKeysInElementAttributes $AttributesKeysInElementAttributes $cloudEvent.Data = ConvertTo-CEDataXml -InputObject $Data -AttributesKeysInElementAttributes $AttributesKeysInElementAttributes
Write-Output $CloudEvent Write-Output $CloudEvent
} }
} }
#endregion Set Data Functions #endregion Set Data Functions
#region Read Data Functions #region Read Data Functions
function Read-CloudEventData { function Read-CloudEventData {
<# <#
.SYNOPSIS .SYNOPSIS
This function gets the data from a cloud event. This function gets the data from a cloud event.
@ -282,23 +282,23 @@ function Read-CloudEventData {
Reads data from a cloud event received on the http response Reads data from a cloud event received on the http response
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory = $true, [Parameter(Mandatory = $true,
ValueFromPipeline = $true)] ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[CloudNative.CloudEvents.CloudEvent] [CloudNative.CloudEvents.CloudEvent]
$CloudEvent $CloudEvent
) )
PROCESS { PROCESS {
Write-Output $CloudEvent.Data Write-Output $CloudEvent.Data
} }
} }
function Read-CloudEventJsonData { function Read-CloudEventJsonData {
<# <#
.SYNOPSIS .SYNOPSIS
This function gets JSON fromat data from a cloud event as a PowerShell hashtable. This function gets JSON fromat data from a cloud event as a PowerShell hashtable.
@ -320,56 +320,57 @@ function Read-CloudEventJsonData {
#> #>
<# <#
.DESCRIPTION .DESCRIPTION
Returns PowerShell hashtable that represents the CloudEvent Json Data Returns PowerShell hashtable that represents the CloudEvent Json Data
if the data content type is 'application/json', otherwise otherwise non-terminating error and no result if the data content type is 'application/json', otherwise otherwise non-terminating error and no result
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory = $true, [Parameter(Mandatory = $true,
ValueFromPipeline = $true)] ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[CloudNative.CloudEvents.CloudEvent] [CloudNative.CloudEvents.CloudEvent]
$CloudEvent, $CloudEvent,
[Parameter(Mandatory = $false, [Parameter(Mandatory = $false,
ValueFromPipeline = $false)] ValueFromPipeline = $false)]
[int] [int]
$Depth = 3 $Depth = 3
) )
PROCESS { PROCESS {
# DataContentType is expected to be 'application/json' # DataContentType is expected to be 'application/json'
# https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype # https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype
$dataContentType = New-Object ` $dataContentType = New-Object `
-TypeName 'System.Net.Mime.ContentType' ` -TypeName 'System.Net.Mime.ContentType' `
-ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Json) -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Json)
if ($CloudEvent.DataContentType -eq $dataContentType -or ` if ($CloudEvent.DataContentType -eq $dataContentType -or `
($CloudEvent.DataContentType -eq $null -and ` # Datacontent Type is Optional, if it is not specified we assume it is JSON as per https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#datacontenttype ($CloudEvent.DataContentType -eq $null -and # Datacontent Type is Optional, if it is not specified we assume it is JSON as per https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#datacontenttype
$cloudEvent.Data -is [Newtonsoft.Json.Linq.JObject])) { $cloudEvent.Data -is [Newtonsoft.Json.Linq.JObject])) {
$data = $cloudEvent.Data $data = $cloudEvent.Data
if ($cloudEvent.Data -is [byte[]]) { if ($cloudEvent.Data -is [byte[]]) {
$data = [System.Text.Encoding]::UTF8.GetString($data) $data = [System.Text.Encoding]::UTF8.GetString($data)
} }
$result = $data.ToString() | ConvertFrom-Json -AsHashtable -Depth $Depth $result = $data.ToString() | ConvertFrom-Json -AsHashtable -Depth $Depth
Write-Output $result Write-Output $result
} else { }
Write-Error "Cloud Event '$($cloudEvent.Id)' has no json data" else {
} Write-Error "Cloud Event '$($cloudEvent.Id)' has no json data"
} }
}
} }
function Read-CloudEventXmlData { function Read-CloudEventXmlData {
<# <#
.SYNOPSIS .SYNOPSIS
This function gets XML fromat data from a cloud event as a PowerShell hashtable. This function gets XML fromat data from a cloud event as a PowerShell hashtable.
@ -430,55 +431,56 @@ function Read-CloudEventXmlData {
#> #>
<# <#
.DESCRIPTION .DESCRIPTION
Returns PowerShell hashtable that represents the CloudEvent Xml Data Returns PowerShell hashtable that represents the CloudEvent Xml Data
if the data content type is 'application/xml', otherwise non-terminating error and no result if the data content type is 'application/xml', otherwise non-terminating error and no result
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory = $true, [Parameter(Mandatory = $true,
ValueFromPipeline = $true)] ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[CloudNative.CloudEvents.CloudEvent] [CloudNative.CloudEvents.CloudEvent]
$CloudEvent, $CloudEvent,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[ValidateSet("SkipAttributes", "AlwaysAttrValue", "AttrValueWhenAttributes")] [ValidateSet("SkipAttributes", "AlwaysAttrValue", "AttrValueWhenAttributes")]
[string] [string]
$ConvertMode $ConvertMode
) )
PROCESS { PROCESS {
# DataContentType is expected to be 'application/xml' # DataContentType is expected to be 'application/xml'
$dataContentType = New-Object ` $dataContentType = New-Object `
-TypeName 'System.Net.Mime.ContentType' ` -TypeName 'System.Net.Mime.ContentType' `
-ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Xml) -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Xml)
if ($CloudEvent.DataContentType -eq $dataContentType) { if ($CloudEvent.DataContentType -eq $dataContentType) {
$data = $cloudEvent.Data $data = $cloudEvent.Data
if ($cloudEvent.Data -is [byte[]]) { if ($cloudEvent.Data -is [byte[]]) {
$data = [System.Text.Encoding]::UTF8.GetString($data) $data = [System.Text.Encoding]::UTF8.GetString($data)
} }
$result = $data.ToString() | ConvertFrom-CEDataXml -ConvertMode $ConvertMode $result = $data.ToString() | ConvertFrom-CEDataXml -ConvertMode $ConvertMode
Write-Output $result Write-Output $result
} else { }
Write-Error "Cloud Event '$($cloudEvent.Id)' has no xml data" else {
} Write-Error "Cloud Event '$($cloudEvent.Id)' has no xml data"
} }
}
} }
#endregion Read Data Functions #endregion Read Data Functions
#region HTTP Protocol Binding Conversion Functions #region HTTP Protocol Binding Conversion Functions
function ConvertTo-HttpMessage { function ConvertTo-HttpMessage {
<# <#
.SYNOPSIS .SYNOPSIS
This function converts a cloud event object to a Http Message. This function converts a cloud event object to a Http Message.
@ -519,121 +521,121 @@ function ConvertTo-HttpMessage {
Sends a cloud event http requests to a server Sends a cloud event http requests to a server
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter( [Parameter(
Mandatory = $true, Mandatory = $true,
ValueFromPipeline = $true, ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $false)] ValueFromPipelineByPropertyName = $false)]
[ValidateNotNull()] [ValidateNotNull()]
[CloudNative.CloudEvents.CloudEvent] [CloudNative.CloudEvents.CloudEvent]
$CloudEvent, $CloudEvent,
[Parameter( [Parameter(
Mandatory = $true, Mandatory = $true,
ValueFromPipeline = $false, ValueFromPipeline = $false,
ValueFromPipelineByPropertyName = $false)] ValueFromPipelineByPropertyName = $false)]
[CloudNative.CloudEvents.ContentMode] [CloudNative.CloudEvents.ContentMode]
$ContentMode) $ContentMode)
PROCESS { PROCESS {
# Output Object # Output Object
$result = New-Object -TypeName PSCustomObject $result = New-Object -TypeName PSCustomObject
$cloudEventFormatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter' $cloudEventFormatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter'
$HttpHeaderPrefix = "ce-"; $HttpHeaderPrefix = "ce-";
$SpecVersionHttpHeader1 = $HttpHeaderPrefix + "cloudEventsVersion"; $SpecVersionHttpHeader1 = $HttpHeaderPrefix + "cloudEventsVersion";
$SpecVersionHttpHeader2 = $HttpHeaderPrefix + "specversion"; $SpecVersionHttpHeader2 = $HttpHeaderPrefix + "specversion";
$headers = @{} $headers = @{}
# Build HTTP headers # Build HTTP headers
foreach ($attribute in $cloudEvent.GetAttributes()) { foreach ($attribute in $cloudEvent.GetAttributes()) {
if (-not $attribute.Key.Equals([CloudNative.CloudEvents.CloudEventAttributes]::DataAttributeName($cloudEvent.SpecVersion)) -and ` if (-not $attribute.Key.Equals([CloudNative.CloudEvents.CloudEventAttributes]::DataAttributeName($cloudEvent.SpecVersion)) -and `
-not $attribute.Key.Equals([CloudNative.CloudEvents.CloudEventAttributes]::DataContentTypeAttributeName($cloudEvent.SpecVersion))) { -not $attribute.Key.Equals([CloudNative.CloudEvents.CloudEventAttributes]::DataContentTypeAttributeName($cloudEvent.SpecVersion))) {
if ($attribute.Value -is [string]) { if ($attribute.Value -is [string]) {
$headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToString()) $headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToString())
} }
elseif ($attribute.Value -is [DateTime]) { elseif ($attribute.Value -is [DateTime]) {
$headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')) $headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))
} }
elseif ($attribute.Value -is [Uri] -or $attribute.Value -is [int]) { elseif ($attribute.Value -is [Uri] -or $attribute.Value -is [int]) {
$headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToString()) $headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToString())
} }
else else {
{ $headers.Add(($HttpHeaderPrefix + $attribute.Key),
$headers.Add(($HttpHeaderPrefix + $attribute.Key), [System.Text.Encoding]::UTF8.GetString($cloudEventFormatter.EncodeAttribute($cloudEvent.SpecVersion, $attribute.Key,
[System.Text.Encoding]::UTF8.GetString($cloudEventFormatter.EncodeAttribute($cloudEvent.SpecVersion, $attribute.Key, $attribute.Value,
$attribute.Value, $cloudEvent.Extensions.Values)));
$cloudEvent.Extensions.Values))); }
}
}
}
# Add Headers property to the output object
$result | Add-Member -MemberType NoteProperty -Name 'Headers' -Value $headers
# Process Structured Mode
# Structured Mode supports non-batching JSON format only
# https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#14-event-formats
if ($ContentMode -eq [CloudNative.CloudEvents.ContentMode]::Structured) {
# Format Body as byte[]
$contentType = $null
# CloudEventFormatter is instance of 'CloudNative.CloudEvents.JsonEventFormatter' from the
# .NET CloudEvents SDK for the purpose of fomatting structured mode
$buffer = $cloudEventFormatter.EncodeStructuredEvent($cloudEvent, [ref] $contentType)
$result | Add-Member -MemberType NoteProperty -Name 'Body' -Value $buffer
$result.Headers.Add('Content-Type', $contentType)
}
# Process Binary Mode
if ($ContentMode -eq [CloudNative.CloudEvents.ContentMode]::Binary) {
$bodyData = $null
if ($cloudEvent.DataContentType -ne $null) {
$result.Headers.Add('Content-Type', $cloudEvent.DataContentType)
}
if ($cloudEvent.Data -is [byte[]]) {
$bodyData = $cloudEvent.Data
}
elseif ($cloudEvent.Data -is [string]) {
$bodyData = [System.Text.Encoding]::UTF8.GetBytes($cloudEvent.Data.ToString())
}
elseif ($cloudEvent.Data -is [IO.Stream]) {
$buffer = New-Object 'byte[]' -ArgumentList 1024
$ms = New-Object 'IO.MemoryStream'
try {
$read = 0
while (($read = $cloudEvent.Data.Read($buffer, 0, 1024)) -gt 0)
{
$ms.Write($buffer, 0, $read);
} }
$bodyData = $ms.ToArray() }
} finally {
$ms.Dispose()
}
} else { # Add Headers property to the output object
$bodyData = $cloudEventFormatter.EncodeAttribute($cloudEvent.SpecVersion, $result | Add-Member -MemberType NoteProperty -Name 'Headers' -Value $headers
[CloudNative.CloudEvents.CloudEventAttributes]::DataAttributeName($cloudEvent.SpecVersion),
$cloudEvent.Data, $cloudEvent.Extensions.Values)
}
# Add Body property to the output object # Process Structured Mode
$result | Add-Member -MemberType NoteProperty -Name 'Body' -Value $bodyData # Structured Mode supports non-batching JSON format only
} # https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#14-event-formats
if ($ContentMode -eq [CloudNative.CloudEvents.ContentMode]::Structured) {
# Format Body as byte[]
$contentType = $null
Write-Output $result # CloudEventFormatter is instance of 'CloudNative.CloudEvents.JsonEventFormatter' from the
} # .NET CloudEvents SDK for the purpose of fomatting structured mode
$buffer = $cloudEventFormatter.EncodeStructuredEvent($cloudEvent, [ref] $contentType)
$result | Add-Member -MemberType NoteProperty -Name 'Body' -Value $buffer
$result.Headers.Add('Content-Type', $contentType)
}
# Process Binary Mode
if ($ContentMode -eq [CloudNative.CloudEvents.ContentMode]::Binary) {
$bodyData = $null
if ($cloudEvent.DataContentType -ne $null) {
$result.Headers.Add('Content-Type', $cloudEvent.DataContentType)
}
if ($cloudEvent.Data -is [byte[]]) {
$bodyData = $cloudEvent.Data
}
elseif ($cloudEvent.Data -is [string]) {
$bodyData = [System.Text.Encoding]::UTF8.GetBytes($cloudEvent.Data.ToString())
}
elseif ($cloudEvent.Data -is [IO.Stream]) {
$buffer = New-Object 'byte[]' -ArgumentList 1024
$ms = New-Object 'IO.MemoryStream'
try {
$read = 0
while (($read = $cloudEvent.Data.Read($buffer, 0, 1024)) -gt 0) {
$ms.Write($buffer, 0, $read);
}
$bodyData = $ms.ToArray()
}
finally {
$ms.Dispose()
}
}
else {
$bodyData = $cloudEventFormatter.EncodeAttribute($cloudEvent.SpecVersion,
[CloudNative.CloudEvents.CloudEventAttributes]::DataAttributeName($cloudEvent.SpecVersion),
$cloudEvent.Data, $cloudEvent.Extensions.Values)
}
# Add Body property to the output object
$result | Add-Member -MemberType NoteProperty -Name 'Body' -Value $bodyData
}
Write-Output $result
}
} }
function ConvertFrom-HttpMessage { function ConvertFrom-HttpMessage {
<# <#
.SYNOPSIS .SYNOPSIS
This function converts a Http Message to a cloud event object This function converts a Http Message to a cloud event object
@ -655,136 +657,150 @@ function ConvertFrom-HttpMessage {
Converts a http response to a cloud event object Converts a http response to a cloud event object
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter( [Parameter(
Mandatory = $true, Mandatory = $true,
ValueFromPipeline = $false, ValueFromPipeline = $false,
ValueFromPipelineByPropertyName = $false)] ValueFromPipelineByPropertyName = $false)]
[ValidateNotNull()] [ValidateNotNull()]
[hashtable] [hashtable]
$Headers, $Headers,
[Parameter( [Parameter(
Mandatory = $false, Mandatory = $false,
ValueFromPipeline = $false, ValueFromPipeline = $false,
ValueFromPipelineByPropertyName = $false)] ValueFromPipelineByPropertyName = $false)]
[ValidateNotNull()] [ValidateNotNull()]
$Body) $Body)
PROCESS { PROCESS {
$HttpHeaderPrefix = "ce-"; $HttpHeaderPrefix = "ce-";
$SpecVersionHttpHeader1 = $HttpHeaderPrefix + "cloudEventsVersion"; $SpecVersionHttpHeader = $HttpHeaderPrefix + "specversion";
$SpecVersionHttpHeader2 = $HttpHeaderPrefix + "specversion";
$result = $null $result = $null
# Always Convert Body to byte[] # Always Convert Body to byte[]
# Conversion works with byte[] while # Conversion works with byte[] while
# body can be string in HTTP responses # body can be string in HTTP responses
# for text content type # for text content type
if ($Body -is [string]) { if ($Body -is [string]) {
$Body = [System.Text.Encoding]::UTF8.GetBytes($Body) $Body = [System.Text.Encoding]::UTF8.GetBytes($Body)
} }
if ($Headers['Content-Type'] -ne $null) { if ($null -ne $Headers['Content-Type']) {
$ContentType = $Headers['Content-Type'] $ContentType = $Headers['Content-Type']
if ($ContentType -is [array]) { if ($ContentType -is [array]) {
# Get the first content-type value # Get the first content-type value
$ContentType = $ContentType[0] $ContentType = $ContentType[0]
}
if ($ContentType.StartsWith([CloudNative.CloudEvents.CloudEvent]::MediaType,
[StringComparison]::InvariantCultureIgnoreCase)) {
# Handle Structured Mode
$ctParts = $ContentType.Split(';')
if ($ctParts[0].Trim().StartsWith(([CloudNative.CloudEvents.CloudEvent]::MediaType) + ([CloudNative.CloudEvents.JsonEventFormatter]::MediaTypeSuffix),
[StringComparison]::InvariantCultureIgnoreCase)) {
# Structured Mode supports non-batching JSON format only
# https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#14-event-formats
# .NET SDK 'CloudNative.CloudEvents.JsonEventFormatter' type is used
# to decode the Structured Mode CloudEvents
$json = [System.Text.Encoding]::UTF8.GetString($Body)
$jObject = [Newtonsoft.Json.Linq.JObject]::Parse($json)
$formatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter'
$result = $formatter.DecodeJObject($jObject, $null)
$result.Data = $result.Data
} else {
# Throw error for unsupported encoding
throw "Unsupported CloudEvents encoding"
}
} else {
# Handle Binary Mode
$version = [CloudNative.CloudEvents.CloudEventsSpecVersion]::Default
if ($Headers.Contains($SpecVersionHttpHeader1)) {
$version = [CloudNative.CloudEvents.CloudEventsSpecVersion]::V0_1
}
if ($Headers.Contains($SpecVersionHttpHeader2)) {
if ($Headers[$SpecVersionHttpHeader2][0] -eq "0.2") {
$version = [CloudNative.CloudEvents.CloudEventsSpecVersion]::V0_2
} elseif ($Headers[$SpecVersionHttpHeader2][0] -eq "0.3") {
$version = [CloudNative.CloudEvents.CloudEventsSpecVersion]::V0_3
} }
}
$cloudEvent = New-Object ` if ($ContentType.StartsWith([CloudNative.CloudEvents.CloudEvent]::MediaType,
-TypeName 'CloudNative.CloudEvents.CloudEvent' ` [StringComparison]::InvariantCultureIgnoreCase)) {
-ArgumentList @($version, $null);
$attributes = $cloudEvent.GetAttributes(); # Handle Structured Mode
$ctParts = $ContentType.Split(';')
if ($ctParts[0].Trim().StartsWith(([CloudNative.CloudEvents.CloudEvent]::MediaType) + ([CloudNative.CloudEvents.JsonEventFormatter]::MediaTypeSuffix),
[StringComparison]::InvariantCultureIgnoreCase)) {
# Get attributes from HTTP Headers # Structured Mode supports non-batching JSON format only
foreach ($httpHeader in $Headers.GetEnumerator()) { # https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#14-event-formats
if ($httpHeader.Key.Equals($SpecVersionHttpHeader1, [StringComparison]::InvariantCultureIgnoreCase) -or `
$httpHeader.Key.Equals($SpecVersionHttpHeader2, [StringComparison]::InvariantCultureIgnoreCase)) {
continue
}
if ($httpHeader.Key.StartsWith($HttpHeaderPrefix, [StringComparison]::InvariantCultureIgnoreCase)) { # .NET SDK 'CloudNative.CloudEvents.JsonEventFormatter' type is used
$headerValue = $httpHeader.Value # to decode the Structured Mode CloudEvents
if ($headerValue -is [array]) {
# Get the first object
$headerValue = $headerValue[0]
}
$name = $httpHeader.Key.Substring(3);
# Abolished structures in headers in 1.0 $json = [System.Text.Encoding]::UTF8.GetString($Body)
if ($version -ne [CloudNative.CloudEvents.CloudEventsSpecVersion]::V0_1 -and ` $jObject = [Newtonsoft.Json.Linq.JObject]::Parse($json)
$headerValue -ne $null -and ` $formatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter'
$headerValue.StartsWith('"') -and ` $result = $formatter.DecodeJObject($jObject, $null)
$headerValue.EndsWith('"') -or `
$headerValue.StartsWith("'") -and $headerValue.EndsWith("'") -or `
$headerValue.StartsWith("{") -and $headerValue.EndsWith("}") -or `
$headerValue.StartsWith("[") -and $headerValue.EndsWith("]")) {
$jsonFormatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter' $result.Data = $result.Data
}
else {
# Throw error for unsupported encoding
throw "Unsupported CloudEvents encoding"
}
}
else {
# Handle Binary Mode
$version = $null
if ($Headers.Contains($SpecVersionHttpHeader) -and `
$null -ne $Headers[$SpecVersionHttpHeader] -and `
($Headers[$SpecVersionHttpHeader] | Select-Object -First 1).StartsWith('1.0')) {
# We do support the 1.0 cloud event version
$version = [CloudNative.CloudEvents.CloudEventsSpecVersion]::V1_0
}
$attributes[$name] = $jsonFormatter.DecodeAttribute($version, $name, if ($null -ne $version) {
[System.Text.Encoding]::UTF8.GetBytes($headerValue), $null); # SpecVersion is REQUIRED attribute, it it is not specified this is not a CloudEvent
} else { # https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#specversion
$attributes[$name] = $headerValue $cloudEvent = New-Object `
} -TypeName 'CloudNative.CloudEvents.CloudEvent' `
} -ArgumentList @($version, $null);
}
if ($Headers['Content-Type'] -ne $null -and $Headers['Content-Type'][0] -is [string]) { $attributes = $cloudEvent.GetAttributes();
$cloudEvent.DataContentType = New-Object 'System.Net.Mime.ContentType' -ArgumentList @($Headers['Content-Type'][0])
}
# Get Data from HTTP Body # Get attributes from HTTP Headers
$cloudEvent.Data = $Body foreach ($httpHeader in $Headers.GetEnumerator()) {
if ($httpHeader.Key.Equals($SpecVersionHttpHeader, [StringComparison]::InvariantCultureIgnoreCase)) {
continue
}
$result = $cloudEvent if ($httpHeader.Key.StartsWith($HttpHeaderPrefix, [StringComparison]::InvariantCultureIgnoreCase)) {
} $headerValue = $httpHeader.Value
} if ($headerValue -is [array]) {
# Get the first object
$headerValue = $headerValue[0]
}
$name = $httpHeader.Key.Substring(3);
Write-Output $result # Abolished structures in headers in 1.0
} if ( $null -ne $headerValue -and `
$headerValue.StartsWith('"') -and `
$headerValue.EndsWith('"') -or `
$headerValue.StartsWith("'") -and $headerValue.EndsWith("'") -or `
$headerValue.StartsWith("{") -and $headerValue.EndsWith("}") -or `
$headerValue.StartsWith("[") -and $headerValue.EndsWith("]")) {
$jsonFormatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter'
$attributes[$name] = $jsonFormatter.DecodeAttribute($version, $name,
[System.Text.Encoding]::UTF8.GetBytes($headerValue), $null);
}
else {
$attributes[$name] = $headerValue
}
}
}
# Verify parsed attributes from HTTP Headers
if ($null -ne $attributes['datacontenttype']) {
# ce-datatype is prohibitted by the protocol -> throw error
# https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#311-http-content-type
throw "'ce-datacontenttype' HTTP header is prohibited for Binary ContentMode CloudEvent"
}
if ($Headers['Content-Type'] -is [string]) {
$cloudEvent.DataContentType = New-Object 'System.Net.Mime.ContentType' -ArgumentList @($Headers['Content-Type'])
} elseif ($Headers['Content-Type'][0] -is [string]) {
$cloudEvent.DataContentType = New-Object 'System.Net.Mime.ContentType' -ArgumentList @($Headers['Content-Type'][0])
}
# Id, Type, and Source are reuiqred to be non-empty strings otherwise consider this is not a CloudEvent
# https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#required-attributes
if ( -not [string]::IsNullOrEmpty($cloudEvent.Id) -and `
-not [string]::IsNullOrEmpty($cloudEvent.Source) -and `
-not [string]::IsNullOrEmpty($cloudEvent.Type)) {
# Get Data from HTTP Body
$cloudEvent.Data = $Body
$result = $cloudEvent
}
}
}
}
Write-Output $result
}
} }
#endregion HTTP Protocol Binding Conversion Functions #endregion HTTP Protocol Binding Conversion Functions

View File

@ -4,9 +4,9 @@
# ************************************************************************** # **************************************************************************
Describe "ConvertFrom-HttpMessage Function Tests" { Describe "ConvertFrom-HttpMessage Function Tests" {
BeforeAll { BeforeEach {
$expectedSpecVersion = '1.0' $script:expectedSpecVersion = '1.0'
$expectedStructuredContentType = 'application/cloudevents+json' $script:expectedStructuredContentType = 'application/cloudevents+json'
} }
Context "Converts CloudEvent in Binary Content Mode" { Context "Converts CloudEvent in Binary Content Mode" {
@ -77,6 +77,7 @@ Describe "ConvertFrom-HttpMessage Function Tests" {
$expectedType = 'test' $expectedType = 'test'
$expectedSource = 'urn:test' $expectedSource = 'urn:test'
$expectedDataContentType = 'application/xml' $expectedDataContentType = 'application/xml'
$expectedId = 'test-id-2'
$expectedData = [Text.Encoding]::UTF8.GetBytes('<much wow="xml"/>') $expectedData = [Text.Encoding]::UTF8.GetBytes('<much wow="xml"/>')
$headers = @{ $headers = @{
@ -84,6 +85,7 @@ Describe "ConvertFrom-HttpMessage Function Tests" {
'ce-specversion' = $expectedSpecVersion 'ce-specversion' = $expectedSpecVersion
'ce-type' = $expectedType 'ce-type' = $expectedType
'ce-source' = $expectedSource 'ce-source' = $expectedSource
'ce-id' = $expectedId
} }
$body = $expectedData $body = $expectedData
@ -95,6 +97,7 @@ Describe "ConvertFrom-HttpMessage Function Tests" {
# Assert # Assert
$actual | Should -Not -Be $null $actual | Should -Not -Be $null
$actual.Id | Should -Be $expectedId
$actual.Type | Should -Be $expectedType $actual.Type | Should -Be $expectedType
$actual.Source | Should -Be $expectedSource $actual.Source | Should -Be $expectedSource
$actual.DataContentType | Should -Be $expectedDataContentType $actual.DataContentType | Should -Be $expectedDataContentType
@ -105,6 +108,81 @@ Describe "ConvertFrom-HttpMessage Function Tests" {
$actualData | Should -Be $expectedData $actualData | Should -Be $expectedData
} }
It 'Returns null when ce-id is not specified' {
# Arrange
$expectedType = 'test'
$expectedSource = 'urn:test'
$expectedDataContentType = 'application/xml'
$expectedData = [Text.Encoding]::UTF8.GetBytes('<much wow="xml"/>')
$headers = @{
'Content-Type' = @($expectedDataContentType, 'charset=utf-8')
'ce-specversion' = $expectedSpecVersion
'ce-type' = $expectedType
'ce-source' = $expectedSource
}
$body = $expectedData
# Act
$actual = ConvertFrom-HttpMessage `
-Headers $headers `
-Body $body
# Assert
$actual | Should -Be $null
}
It 'Returns null when ce-source is not specified' {
# Arrange
$expectedType = 'test'
$expectedDataContentType = 'application/xml'
$expectedId = 'test-id-3'
$expectedData = [Text.Encoding]::UTF8.GetBytes('<much wow="xml"/>')
$headers = @{
'Content-Type' = @($expectedDataContentType, 'charset=utf-8')
'ce-specversion' = $expectedSpecVersion
'ce-type' = $expectedType
'ce-id' = $expectedId
}
$body = $expectedData
# Act
$actual = ConvertFrom-HttpMessage `
-Headers $headers `
-Body $body
# Assert
$actual | Should -Be $null
}
It 'Returns null when ce-type is not specified' {
# Arrange
$expectedSource = 'urn:test'
$expectedDataContentType = 'application/xml'
$expectedId = 'test-id-4'
$expectedData = [Text.Encoding]::UTF8.GetBytes('<much wow="xml"/>')
$headers = @{
'Content-Type' = @($expectedDataContentType, 'charset=utf-8')
'ce-specversion' = $expectedSpecVersion
'ce-source' = $expectedSource
'ce-id' = $expectedId
}
$body = $expectedData
# Act
$actual = ConvertFrom-HttpMessage `
-Headers $headers `
-Body $body
# Assert
$actual | Should -Be $null
}
} }
Context "Converts CloudEvent in Structured Content Mode" { Context "Converts CloudEvent in Structured Content Mode" {
@ -245,5 +323,29 @@ Describe "ConvertFrom-HttpMessage Function Tests" {
-Body $body } | ` -Body $body } | `
Should -Throw "*Unsupported CloudEvents encoding*" Should -Throw "*Unsupported CloudEvents encoding*"
} }
It 'Returns null when no Content-Type header' {
# Arrange
$expectedType = 'test'
$expectedSource = 'urn:test'
$expectedDataContentType = 'application/xml'
$expectedData = [Text.Encoding]::UTF8.GetBytes('<much wow="xml"/>')
$structuredJsonBody = @{
'specversion' = $expectedSpecVersion
'type' = $expectedType
'source' = $expectedSource
'datacontenttype' = $expectedDataContentType
'data' = $expectedData
}
$body = [Text.Encoding]::UTF8.GetBytes(($structuredJsonBody | ConvertTo-Json))
# Act & Assert
$ce = ConvertFrom-HttpMessage `
-Headers @{} `
-Body $body
$ce | Should -Be $null
}
} }
} }