Malicious Office (OOXML) — malware analysis report

Static analysis result for SHA-256 6590bd1de0480c70…

MALICIOUS

Office (OOXML)

1.34 MB Created: 2011-08-25 14:29:57 UTC Authoring application: Microsoft Excel 16.0300 First seen: 2021-11-20
MD5: f3f3bf82bae367cf9d1f71c71336e7a7 SHA-1: bef1391e660597d511a0c890fb73ee8614877e15 SHA-256: 6590bd1de0480c700a2f5249e43a923e1e184c74e55ebfb459d1ec1f0679e255
278 Risk Score

Malware Insights

MITRE ATT&CK
T1059.005 Visual Basic T1566.001 Spearphishing Attachment

The critical heuristic 'OLE_VBA_OBFUSCATED_AUTOEXEC_LOADER' indicates that the sample contains obfuscated auto-executing VBA code designed to run upon opening the document. The 'OLE_VBA_SHELL' and 'OLE_VBA_CREATEOBJ' heuristics suggest that this code likely attempts to execute external commands or download additional payloads. The presence of the 'Workbook_Open' macro further supports this, as it's a common entry point for malicious VBA execution. The embedded URL 'https://github.com/VBA-tools/VBA-Web' is noted, though its reputation is unknown.

Heuristics 9

  • VBA project inside OOXML medium 6 related findings OOXML_VBA
    Document contains a VBA project — VBA macros present
  • Potential Shell call in VBA critical OLE_VBA_SHELL
    Potential Shell call in VBA
    Matched line in script
    ''
    ' Prepare text for shell
    ' - Wrap in "..."
  • Obfuscated auto-exec VBA loader critical OLE_VBA_OBFUSCATED_AUTOEXEC_LOADER
    Auto-exec VBA reconstructs strings with a heavy custom decoder (numeric char-array, repeated hex-string decode, or junk-token Replace removal) and feeds them to a COM-instantiation or execution sink. This obfuscated-loader shape keeps CreateObject/Shell/URL indicators out of the macro source.
    Matched line in script
        Set web_Http = CreateObject("WinHttp.WinHttpRequest.5.1")
  • CreateObject call high OLE_VBA_CREATEOBJ
    CreateObject call
    Matched line in script
        Set web_Http = CreateObject("WinHttp.WinHttpRequest.5.1")
  • CallByName call high OLE_VBA_CALLBYNAME
    CallByName call
    Matched line in script
                        If web_Converter("ParseType") = "Binary" Then
                            Set ParseByFormat = VBA.CallByName(web_Instance, web_Callback, VBA.vbMethod, Bytes)
                        Else
  • VBA p-code auto-exec with execution tokens high OLE_VBA_PCODE_AUTOEXEC_EXEC
    Compiled VBA/cache stream contains an auto-execution token together with shell/download/object-execution tokens. This catches p-code-only or source-extraction-failure macro documents where visible source is unavailable.
  • Workbook_Open macro low OLE_VBA_WBOPEN
    Workbook_Open macro
    Matched line in script
    Private Sub Workbook_Open()
        On Error Resume Next
  • Hidden worksheet (hidden) low OOXML_HIDDEN_SHEET
    Excel workbook contains 1 hidden sheet(s) — hidden sheets are commonly used to conceal macro code, staging data, or intermediate payload construction
  • Embedded URL info EMBEDDED_URL
    One or more URLs were extracted from the document. The URL itself is not a detection — see the per-URL labels for which channel (macro, JS, link annotation, document body, ...) reached each URL.
    URL https://github.com/VBA-tools/VBA-Web In document text (OOXML body / shared strings)
    • https://api.example/com/v1/messages/123In document text (OOXML body / shared strings)
    • https://github.com/VBA-tools/VBA-Web/wiki/XML-Support-in-4.0In document text (OOXML body / shared strings)
    • https://github.com/VBA-tools/VBA-Web/wiki/Url-EncodingIn document text (OOXML body / shared strings)
    • http://www.di-mgt.com.au/src/basMD5.bas.htmlIn document text (OOXML body / shared strings)
    • https://github.com/VBA-tools/VBA-JSONIn document text (OOXML body / shared strings)
    • http://www.vbaccelerator.com/home/VB/Code/Techniques/RunTime_Debug_Tracing/VB6_Tracer_Utility_zip_cStringBuilder_cls.aspIn document text (OOXML body / shared strings)
    • https://github.com/VBA-tools/VBA-JSON/pull/82In document text (OOXML body / shared strings)
    • https://github.com/VBA-tools/VBA-UtcConverterIn document text (OOXML body / shared strings)
    • https://github.com/timhall/VBA-Dictionary\r\nAuthorIn document text (OOXML body / shared strings)
    • https://github.com/timhall/VBA-DictionaryIn document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/tables/tablenext/3d22137acec09aa007f7718fdcd7c0b03e32bdf2.js?date=In document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/tables/tablenext/In document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/nextgame.htmlIn document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.comIn document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/tables/tablelast/3d22137acec09aa007f7718fdcd7c0b03e32bdf2.js?date=In document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/tables/tablelast/In document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/lastgame.htmlIn document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/tables/livegame/3d22137acec09aa007f7718fdcd7c0b03e32bdf2.js?date=In document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/tables/livegame/In document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/livescore.htmlIn document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/it/livescore.htmlIn document text (OOXML body / shared strings)
    • https://github.com/VBA-tools/VBA-Web/VBA-W�In document text (OOXML body / shared strings)
    • https://www.asianbetsoccer.com/it/livescore.html�In document text (OOXML body / shared strings)
    • http://www.opensource.org/licenses/mit-license.phpIn document text (OOXML body / shared strings)
    • https://api.example.com/In document text (OOXML body / shared strings)
    • https://api.example.com/messagesIn document text (OOXML body / shared strings)
    • https://api.example.com/messages/123?a=1&b=2In document text (OOXML body / shared strings)
    • https://tools.ietf.org/html/rfc6265In document text (OOXML body / shared strings)
    • https://www.example.com/api/In document text (OOXML body / shared strings)
    • https://api.example.com/v1/messagesIn document text (OOXML body / shared strings)
    • https://api.example.com/v1/users/idIn document text (OOXML body / shared strings)
    • https://api.example.com/v1/In document text (OOXML body / shared strings)
    • https://msdn.microsoft.com/en-us/library/aa383770(VS.85).aspxIn document text (OOXML body / shared strings)
    • http://curl.haxx.se/libcurl/c/libcurl-errors.htmlIn document text (OOXML body / shared strings)
    • https://api.example.com/v1/messages/1In document text (OOXML body / shared strings)
    • http://msdn.microsoft.com/en-us/library/windows/desktop/aa384059(v=vs.85).aspxIn document text (OOXML body / shared strings)
    • http://msdn.microsoft.com/en-us/library/windows/desktop/ms724421.aspxIn document text (OOXML body / shared strings)
    • http://msdn.microsoft.com/en-us/library/windows/desktop/ms724949.aspxIn document text (OOXML body / shared strings)
    • http://msdn.microsoft.com/en-us/library/windows/desktop/ms725485.aspxIn document text (OOXML body / shared strings)
    • http://support.microsoft.com/kb/269370In document text (OOXML body / shared strings)
    • https://api.example.com/v1/peeple/{idIn document text (OOXML body / shared strings)
    • https://tools.ietf.org/html/rfc3986In document text (OOXML body / shared strings)
    • https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithmIn document text (OOXML body / shared strings)
    • https://www.w3.org/International/URLUTF8Encoder.javaIn document text (OOXML body / shared strings)
    • https://www.google.com/a/b/c.html?a=1&b=2#hashIn document text (OOXML body / shared strings)
    • http://stackoverflow.com/questions/8246340/does-vba-have-a-hash-hmacIn document text (OOXML body / shared strings)
    • http://code.google.com/p/vba-json/In document text (OOXML body / shared strings)
    • http://www.ietf.org/rfc/rfc4627.txtIn document text (OOXML body / shared strings)
    • https://support.microsoft.com/en-us/kb/272138In document text (OOXML body / shared strings)
    +8 more URL(s)

Extracted artifacts 2

Files carved from inside the sample during analysis.

FilenameKindSourceSize
macros.bas vba-macro oletools.olevba.extract_macros (decoded VBA source from OOXML) 310170 bytes
SHA-256: f1b93e5387800224167db40496a7caa83eb47c21ab556752a6df0236d1ae2c49
Preview script
First 1,000 lines of the extracted script
Attribute VB_Name = "ThisWorkbook"
Attribute VB_Base = "0{00020819-0000-0000-C000-000000000046}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True
Private Sub Workbook_BeforeClose(Cancel As Boolean)

    If Evaluate("ISREF('Dbg'!C1)") Then    'Restituisce Vero se il foglio Dbg esiste
        Application.DisplayAlerts = False
        Sheets("Dbg").Visible = True
        Application.DecimalSeparator = Sheets("Dbg").Range("A1").Value
        Sheets("Dbg").Delete
        Application.DisplayAlerts = True
    End If

End Sub

Private Sub Workbook_Open()
    On Error Resume Next
    If Evaluate("ISREF('Dbg'!C1)") Then    'Restituisce Vero se il foglio Dbg esiste
        Application.DisplayAlerts = False
        Sheets("Dbg").Visible = True
        Sheets("Dbg").Delete
        Application.DisplayAlerts = True
    End If
    Dbg_O ("Init")
    Sheets("Dbg").Range("A1") = Application.DecimalSeparator
    Application.DecimalSeparator = "."


End Sub

Attribute VB_Name = "SCANNER"
Attribute VB_Base = "0{00020820-0000-0000-C000-000000000046}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True

Attribute VB_Name = "IWebAuthenticator"
Attribute VB_Base = "0{FCFB3D2A-A0FA-1068-A738-08002B3371B5}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = False
''
' IWebAuthenticator v4.1.6
' (c) Tim Hall - https://github.com/VBA-tools/VBA-Web
'
' Interface for creating authenticators for rest client
'
' @class IWebAuthenticator
' @author tim.hall.engr@gmail.com
' @license MIT (http://www.opensource.org/licenses/mit-license.php)
'' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
Option Explicit

' ============================================= '
' Public Methods
' ============================================= '

''
' Hook for taking action before a request is executed
'
' @method BeforeExecute
' @param {WebClient} Client The client that is about to execute the request
' @param in|out {WebRequest} Request The request about to be executed
''
Public Sub BeforeExecute(ByVal Client As WebClient, ByRef Request As WebRequest)
' e.g Add headers, cookies, etc.
End Sub

''
' Hook for taking action after request has been executed
'
' @method AfterExecute
' @param {WebClient} Client The client that executed request
' @param {WebRequest} Request The request that was just executed
' @param in|out {WebResponse} Response to request
''
Public Sub AfterExecute(ByVal Client As WebClient, ByVal Request As WebRequest, ByRef Response As WebResponse)
' e.g. Handle 401 Unauthorized or other issues
End Sub

''
' Hook for updating http before send
'
' @method PrepareHttp
' @param {WebClient} Client
' @param {WebRequest} Request
' @param in|out {WinHttpRequest} Http
''
Public Sub PrepareHttp(ByVal Client As WebClient, ByVal Request As WebRequest, ByRef Http As Object)
' e.g. Update option, headers, etc.
End Sub

''
' Hook for updating cURL before send
'
' @method PrepareCurl
' @param {WebClient} Client
' @param {WebRequest} Request
' @param in|out {String} Curl
''
Public Sub PrepareCurl(ByVal Client As WebClient, ByVal Request As WebRequest, ByRef Curl As String)
' e.g. Add flags to cURL
End Sub

Attribute VB_Name = "WebRequest"
Attribute VB_Base = "0{FCFB3D2A-A0FA-1068-A738-08002B3371B5}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = False
''
' WebRequest v4.1.6
' (c) Tim Hall - https://github.com/VBA-tools/VBA-Web
'
' `WebRequest` is used to create detailed requests
' (including formatting, querystrings, headers, cookies, and much more).
'
' Usage:
' ```VB.net
' Dim Request As New WebRequest
' Request.Resource = "users/{Id}"
'
' Request.Method = WebMethod.HttpPut
' Request.RequestFormat = WebFormat.UrlEncoded
' Request.ResponseFormat = WebFormat.Json
'
' Dim Body As New Dictionary
' Body.Add "name", "Tim"
' Body.Add "project", "VBA-Web"
' Set Request.Body = Body
'
' Request.AddUrlSegment "Id", 123
' Request.AddQuerystringParam "api_key", "abcd"
' Request.AddHeader "Authorization", "Token ..."
'
' ' -> PUT (Client.BaseUrl)users/123?api_key=abcd
' '    Authorization: Token ...
' '
' '    name=Tim&project=VBA-Web
' ```
'
' Errors:
' 11020 / 80042b0c / -2147210484 - Cannot add body parameter to non-Dictionary
'
' @class WebRequest
' @author tim.hall.engr@gmail.com
' @license MIT (http://www.opensource.org/licenses/mit-license.php)
'' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
Option Explicit

' --------------------------------------------- '
' Constants and Private Variables
' --------------------------------------------- '

Private web_pRequestFormat As WebFormat
Private web_pResponseFormat As WebFormat
Private web_pCustomRequestFormat As String
Private web_pCustomResponseFormat As String
Private web_pBody As Variant
Private web_pConvertedBody As Variant
Private web_pContentType As String
Private web_pAccept As String
Private web_pContentLength As Long
Private web_pId As String

' --------------------------------------------- '
' Properties
' --------------------------------------------- '

''
' Set the request's portion of the url to be appended to the client's BaseUrl.
' Can include Url Segments for dynamic values
' and Querystring parameters are smart enough to be appended to existing querystring
' (or added to resource if there isn't an existing querystring).
'
' @example
' ```VB.net
' Dim Client As New WebClient
' Client.BaseUrl = "https://api.example.com/"
'
' Dim Request As New WebRequest
' Request.Resource = "messages"
'
' ' -> Url: https://api.example.com/messages
'
' Request.Resource = "messages/{id}?a=1"
' Request.AddUrlSegment "id", 123
' Request.AddQuerystringParam "b", 2
'
' ' -> Url: https://api.example.com/messages/123?a=1&b=2
' ```
'
' @property Resource
' @type String
''
Public Resource As String

''
' Set the HTTP method to be used for the request:
' GET, POST, PUT, PATCH, DELETE
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.Method = WebMethod.HttpGet
' Request.Method = WebMethod.HttpPost
' ' or HttpPut / HttpPatch / HttpDelete
' ```
'
' @property Method
' @type WebMethod
''
Public Method As WebMethod

''
' _Note_ To add headers, use [`AddHeader`](#/WebRequest/AddHeader).
'
' `Collection` of Headers to include with request,
' stored as `KeyValue` (`Dictionary: {Key: "...", Value: "..."}`).
'
' @property Headers
' @type Collection
''
Public Headers As Collection

''
' _Note_ To add querystring parameters, use [`AddQuerystringParam`](#/WebRequest/AddQuerystringParam).
'
' `Collection` of querystring parameters to include with request,
' stored as `KeyValue` (`Dictionary: {Key: "...", Value: "..."}`).
'
' @property QuerystringParams
' @type Collection
''
Public QuerystringParams As Collection

''
' _Note_ To add Url Segments, use [`AddUrlSegment`](#/WebRequest/AddUrlSegment)
'
' Url Segments are used to easily add dynamic values to `Resource`.
' Create a Url Segement in `Resource` with curly brackets and then
' replace with dynamic value with [`AddUrlSegment`](#AddUrlSegment).
'
' @example
' ```VB.net
' Dim Request As New WebRequest
'
' Dim User As String
' Dim Id As Long
' User = "Tim"
' Id = 123
'
' ' OK: Use string concatenation for dynamic values
' Request.Resource = User & "/messages/" & Id
'
' ' BETTER: Use Url Segments for dynamic values
' Request.Resource = "{User}/messages/{Id}"
' Request.AddUrlSegment "User", User
' Request.AddUrlSegment "Id", Id
'
' Request.FormattedResource ' = "Tim/messages/123"
' ```
'
' @property UrlSegments
' @type Dictionary
''
Public UrlSegments As Dictionary

''
' _Note_ To add cookies, use [`AddCookie`](#/WebRequest/AddCookie).
'
' `Collection` of cookies to include with request,
' stored as `KeyValue` (`Dictionary: {Key: "...", Value: "..."}`).
'
' @property Cookies
' @type Collection
''
Public Cookies As Collection

''
' User agent to use with request
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.UserAgent = "Mozilla/5.0"
'
' ' -> (Header) User-Agent: Mozilla/5.0
' ```
'
' @property UserAgent
' @type String
' @default "VBA-Web v#.#.# (https://github.com/VBA-tools/VBA-Web)"
''
Public UserAgent As String

''
' Set `RequestFormat`, `ResponseFormat`, and `Content-Type` and `Accept`
' headers for the `WebRequest`
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.Format = WebFormat.Json
' ' -> Request.RequestFormat = WebFormat.Json
' '    Request.ResponseFormat = WebFormat.Json
' '    (Header) Content-Type: application/json
' '    (Header) Accept: application/json
' ```
'
' @property Format
' @type WebFormat
''
Public Property Get Format() As WebFormat
    Format = RequestFormat
End Property
Public Property Let Format(Value As WebFormat)
    Me.RequestFormat = Value
    Me.ResponseFormat = Value
End Property

''
' Set the format to use for converting the response `Body` to string and for the `Content-Type` header
'
' _Note_ If `WebFormat.Custom` is used, the [`CustomRequestFormat`](#/WebRequest/CustomRequestFormat) must be set.
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.Body = Array("A", "B", "C")
'
' Request.RequestFormat = WebFormat.Json
'
' ' -> (Header) Content-Type: application/json
' ' -> Convert Body to JSON string
' Request.Body ' = "["A","B","C"]"
' ```
'
' @property RequestFormat
' @type WebFormat
' @default WebFormat.Json
''
Public Property Get RequestFormat() As WebFormat
    RequestFormat = web_pRequestFormat
End Property
Public Property Let RequestFormat(Value As WebFormat)
    If Value <> web_pRequestFormat Then
        web_pRequestFormat = Value

        ' Clear cached converted body
        web_pConvertedBody = Empty
    End If
End Property

''
' Set the format to use for converting the response `Content` to `Data` and for the `Accept` header
'
' _Note_ If `WebFormat.Custom` is used, the [`CustomResponseFormat`](#/WebRequest/CustomResponseFormat) must be set.
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.ResponseFormat = WebFormat.Json
'
' ' -> (Header) Accept: application/json
'
' Dim Response As WebResponse
' ' ... from Execute
' Response.Content = "{""message"":""Howdy!""}"
'
' ' -> Parse Content to JSON Dictionary
' Debug.Print Response.Data("message") ' -> "Howdy!"
' ```
'
' @property ResponseFormat
' @type WebFormat
' @default WebFormat.Json
''
Public Property Get ResponseFormat() As WebFormat
    ResponseFormat = web_pResponseFormat
End Property
Public Property Let ResponseFormat(Value As WebFormat)
    If Value <> web_pResponseFormat Then
        web_pResponseFormat = Value

        ' Clear cached converted body
        web_pConvertedBody = Empty
    End If
End Property

''
' Use converter registered with [`WebHelpers.RegisterConverter`](#/WebHelpers/RegisterConverter)
' to convert `Body` to string and set `Content-Type` header.
'
' (Automatically sets `RequestFormat` to `WebFormat.Custom`)
'
' @example
' ```VB.net
' WebHelpers.RegisterConverter "csv", "text/csv", "Module.ConvertToCsv", "Module.ParseCsv"
'
' Dim Request As New WebRequest
' Request.CustomRequestFormat = "csv"
'
' ' -> (Header) Content-Type: text/csv
' ' -> Body converted to string with Module.ConvertToCsv
' ```
'
' @property CustomRequestFormat
' @type String
''
Public Property Get CustomRequestFormat() As String
    CustomRequestFormat = web_pCustomRequestFormat
End Property
Public Property Let CustomRequestFormat(Value As String)
    If Value <> web_pCustomRequestFormat Then
        web_pCustomRequestFormat = Value

        ' Clear cached converted body
        web_pConvertedBody = Empty

        If Value <> "" Then
            web_pRequestFormat = WebFormat.Custom
        End If
    End If
End Property

''
' Use converter registered with [`WebHelpers.RegisterConverter`](#/WebHelpers/RegisterConverter)
' to convert the response `Content` to `Data` and set `Accept` header.
'
' (Automatically sets `ResponseFormat` to `WebFormat.Custom`)
'
' @example
' ```VB.net
' WebHelpers.RegisterConverter "csv", "text/csv", "Module.ConvertToCsv", "Module.ParseCsv"
'
' Dim Request As New WebRequest
' Request.CustomResponseFormat = "csv"
'
' ' -> (Header) Accept: text/csv
' ' -> WebResponse Content converted Data with Module.ParseCsv
' ```
'
' @property CustomResponseFormat
' @type String
''
Public Property Get CustomResponseFormat() As String
    CustomResponseFormat = web_pCustomResponseFormat
End Property
Public Property Let CustomResponseFormat(Value As String)
    If Value <> web_pCustomResponseFormat Then
        web_pCustomResponseFormat = Value

        ' Clear cached converted body
        web_pConvertedBody = Empty

        If Value <> "" Then
            ResponseFormat = WebFormat.Custom
        End If
    End If
End Property

''
' Set automatically from `RequestFormat` or `CustomRequestFormat`,
' but can be overriden to set `Content-Type` header for request.
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.ContentType = "text/csv"
'
' ' -> (Header) Content-Type: text/csv
' ```
'
' @property ContentType
' @type String
' @default Media-type of request format
''
Public Property Get ContentType() As String
    If web_pContentType <> "" Then
        ContentType = web_pContentType
    Else
        ContentType = WebHelpers.FormatToMediaType(Me.RequestFormat, Me.CustomRequestFormat)
    End If
End Property
Public Property Let ContentType(Value As String)
    web_pContentType = Value
End Property

''
' Set automatically from `ResponseFormat` or `CustomResponseFormat`,
' but can be overriden to set `Accept` header for request.
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.Accept = "text/csv"
'
' ' -> (Header) Accept: text/csv
' ```
'
' @property Accept
' @type String
' @default Media-type of response format
''
Public Property Get accept() As String
    If web_pAccept <> "" Then
        accept = web_pAccept
    Else
        accept = WebHelpers.FormatToMediaType(Me.ResponseFormat, Me.CustomResponseFormat)
    End If
End Property
Public Property Let accept(Value As String)
    web_pAccept = Value
End Property

''
' Set automatically by length of `Body`,
' but can be overriden to set `Content-Length` header for request.
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.ContentLength = 200
'
' ' -> (Header) Content-Length: 200
' ```
'
' @property ContentLength
' @type Long
' @default Length of `Body`
''
Public Property Get ContentLength() As Long
    If web_pContentLength >= 0 Then
        ContentLength = web_pContentLength
    Else
        ContentLength = Len(Me.Body)
    End If
End Property
Public Property Let ContentLength(Value As Long)
    web_pContentLength = Value
End Property

''
' - Get: Body value converted to string using `RequestFormat` or `CustomRequestFormat`
' - Let: Use `String` or `Array` for Body
' - Set: Use `Collection`, `Dictionary`, or `Object` for Body
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.RequestFormat = WebFormat.Json
'
' ' Let: String|Array
' Request.Body = "text"
' Debug.Print Request.Body ' -> "text"
'
' Request.Body = Array("A", "B", "C")
' Debug.Print Request.Body ' -> "["A","B","C"]"
'
' ' Set: Collection|Dictionary|Object
' Dim Body As Object
' Set Body = New Collection
' Body.Add "Howdy!"
' Set Request.Body = Body
' Debug.Print Request.Body ' -> "["Howdy!"]"
'
' Set Body = New Dictionary
' Body.Add "a", 123
' Body.Add "b", 456
' Set Request.Body = Body
' Debug.Print Request.Body ' -> "{"a":123,"b":456}"
' ```
'
' @property Body
' @type String|Array|Collection|Dictionary|Variant
''
Public Property Get Body() As Variant
    If Not VBA.IsEmpty(web_pBody) Then
        If VBA.VarType(web_pBody) = vbString Then
            Body = web_pBody
        ElseIf IsEmpty(web_pConvertedBody) Then
            ' Convert body and cache
            Body = WebHelpers.ConvertToFormat(web_pBody, Me.RequestFormat, Me.CustomRequestFormat)
            web_pConvertedBody = Body
        Else
            Body = web_pConvertedBody
        End If
    End If
End Property
Public Property Let Body(Value As Variant)
    web_pConvertedBody = Empty
    web_pBody = Value
End Property
Public Property Set Body(Value As Variant)
    web_pConvertedBody = Empty
    Set web_pBody = Value
End Property

''
' Get `Resource` with Url Segments replaced and Querystring added.
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.Resource = "examples/{Id}"
' Request.AddUrlSegment "Id", 123
' Request.AddQuerystringParam "message", "Hello"
'
' Debug.Print Request.FormattedResource ' -> "examples/123?message=Hello"
' ```
'
' @property FormattedResource
' @type String
''
Public Property Get FormattedResource() As String
    Dim web_Segment As Variant
    Dim web_Encoding As UrlEncodingMode

    FormattedResource = Me.Resource

    ' Replace url segments
    For Each web_Segment In Me.UrlSegments.Keys
        FormattedResource = VBA.Replace(FormattedResource, "{" & web_Segment & "}", WebHelpers.UrlEncode(Me.UrlSegments(web_Segment)))
    Next web_Segment

    ' Add querystring
    If Me.QuerystringParams.Count > 0 Then
        If VBA.InStr(FormattedResource, "?") <= 0 Then
            FormattedResource = FormattedResource & "?"
        Else
            FormattedResource = FormattedResource & "&"
        End If

        ' For querystrings, W3C defines form-urlencoded as the required encoding,
        ' but the treatment of space -> "+" (rather than "%20") can cause issues
        '
        ' If the request format is explicitly form-urlencoded, use FormUrlEncoding (space -> "+")
        ' otherwise, use subset of RFC 3986 and form-urlencoded that should work for both cases (space -> "%20")
        If Me.RequestFormat = WebFormat.FormUrlEncoded Then
            web_Encoding = UrlEncodingMode.FormUrlEncoding
        Else
            web_Encoding = UrlEncodingMode.QueryUrlEncoding
        End If
        FormattedResource = FormattedResource & WebHelpers.ConvertToUrlEncoded(Me.QuerystringParams, EncodingMode:=web_Encoding)
    End If
End Property

''
' @internal
' @property Id
' @type String
''
Public Property Get id() As String
    If web_pId = "" Then: web_pId = WebHelpers.CreateNonce
    id = web_pId
End Property

' ============================================= '
' Public Methods
' ============================================= '

''
' Add header to be sent with request.
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.AddHeader "Authentication", "Bearer ..."
'
' ' -> (Header) Authorization: Bearer ...
' ```
'
' @method AddHeader
' @param {String} Key
' @param {Variant} Value
''
Public Sub AddHeader(Key As String, Value As Variant)
    Me.Headers.Add WebHelpers.CreateKeyValue(Key, Value)
End Sub

''
' Add/replace header to be sent with request.
' `SetHeader` should be used for headers that can only be included once with a request
' (e.g. Authorization, Content-Type, etc.).
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.AddHeader "Authorization", "A..."
' Request.AddHeader "Authorization", "B..."
'
' ' -> Headers:
' '    Authorization: A...
' '    Authorization: B...
'
' Request.SetHeader "Authorization", "C..."
'
' ' -> Headers:
' '    Authorization: C...
' ```
'
' @method SetHeader
' @param {String} Key
' @param {Variant} Value
''
Public Sub SetHeader(Key As String, Value As Variant)
    WebHelpers.AddOrReplaceInKeyValues Me.Headers, Key, Value
End Sub

''
' Url Segments are used to easily add dynamic values to `Resource`.
' Create a Url Segement in `Resource` with curly brackets and then
' replace with dynamic value with `AddUrlSegment`.
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Dim User As String
' Dim Id As Long
'
' User = "Tim"
' Id = 123
'
' ' OK: Use string concatenation for dynamic values
' Request.Resource = User & "/messages/" & Id
'
' ' BETTER: Use Url Segments for dynamic values
' Request.Resource = "{User}/messages/{Id}"
' Request.AddUrlSegment "User", User
' Request.AddUrlSegment "Id", Id
'
' Debug.Print Request.FormattedResource ' > "Tim/messages/123"
' ```
'
' @method AddUrlSegment
' @param {String} Key
' @param {String} Value
''
Public Sub AddUrlSegment(Segment As String, Value As Variant)
    Me.UrlSegments.Item(Segment) = Value
End Sub

''
' Add querysting parameter to be used in `FormattedResource` for request.
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.Resource = "messages"
' Request.AddQuerystringParam "from", "Tim"
'
' Request.FormattedResource ' = "messages?from=Tim"
' ```
'
' @method AddQuerystringParam
' @param {String} Key
' @param {Variant} Value
''
Public Sub AddQuerystringParam(Key As String, Value As Variant)
    Me.QuerystringParams.Add WebHelpers.CreateKeyValue(Key, Value)
End Sub

''
' Add cookie to be sent with request.
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.AddCookie "a", "abc"
' Request.AddCookie "b", 123
'
' ' -> (Header) Cookie: a=abc; b=123;
' ```
'
' @method AddCookie
' @param {String} Key
' @param {Variant} Value
''
Public Sub AddCookie(Key As String, Value As Variant)
    Me.Cookies.Add WebHelpers.CreateKeyValue( _
                   web_EncodeCookieName(Key), _
                   WebHelpers.UrlEncode(Value, EncodingMode:=UrlEncodingMode.CookieUrlEncoding) _
                   )
End Sub

''
' Add `Key-Value` to `Body`.
' `Body` must be a `Dictionary` (if it's an `Array` or `Collection` an error is thrown)
'
' @example
' ```VB.net
' Dim Request As New WebRequest
' Request.Format = WebFormat.Json
'
' Request.AddBodyParameter "a", 123
' Debug.Print Request.Body ' -> "{"a":123}"
'
' ' Can add parameters to existing Dictionary
' Dim Body As New Dictionary
' Body.Add "a", 123
'
' Set Request.Body = Body
' Request.AddBodyParameter "b", 456
'
' Debug.Print Request.Body ' -> "{"a":123,"b":456}"
' ```
'
' @method AddBodyParameter
' @param {Variant} Key
' @param {Variant} Value
' @throws 11020 / 80042b0c / -2147210484 - Cannot add body parameter to non-Dictionary
''
Public Sub AddBodyParameter(Key As Variant, Value As Variant)
    If VBA.IsEmpty(web_pBody) Then
        Set web_pBody = New Dictionary
    ElseIf Not TypeOf web_pBody Is Dictionary Then
        Dim web_ErrorDescription As String
        web_ErrorDescription = "Cannot add body parameter to non-Dictionary Body (existing Body must be of type Dictionary)"

        WebHelpers.LogError web_ErrorDescription, "WebRequest.AddBodyParameter", 11020 + vbObjectError
        Err.Raise 11020 + vbObjectError, "WebRequest.AddBodyParameter", web_ErrorDescription
    End If

    If VBA.IsObject(Value) Then
        Set web_pBody(Key) = Value
    Else
        web_pBody(Key) = Value
    End If

    ' Clear cached converted body
    web_pConvertedBody = Empty
End Sub

''
' Prepare request for execution
'
' @internal
' @method Prepare
''
Public Sub Prepare()
' Add/replace general headers for request
    SetHeader "User-Agent", Me.UserAgent
    SetHeader "Accept", Me.accept
    If Me.Method <> WebMethod.HttpGet Then
        SetHeader "Content-Type", Me.ContentType
        SetHeader "Content-Length", VBA.CStr(Me.ContentLength)
    End If
End Sub

''
' Clone request
'
' @internal
' @method Clone
' @return {WebRequest}
''
Public Function Clone() As WebRequest
    Set Clone = New WebRequest

    ' Note: Clone underlying for properties with default values
    Clone.Resource = Me.Resource
    Clone.Method = Me.Method
    Clone.UserAgent = Me.UserAgent
    Clone.accept = web_pAccept
    Clone.ContentType = web_pContentType
    Clone.ContentLength = web_pContentLength
    Clone.RequestFormat = Me.RequestFormat
    Clone.ResponseFormat = Me.ResponseFormat
    Clone.CustomRequestFormat = Me.CustomRequestFormat
    Clone.CustomResponseFormat = Me.CustomResponseFormat

    Set Clone.Headers = WebHelpers.CloneCollection(Me.Headers)
    Set Clone.QuerystringParams = WebHelpers.CloneCollection(Me.QuerystringParams)
    Set Clone.UrlSegments = WebHelpers.CloneDictionary(Me.UrlSegments)
    Set Clone.Cookies = WebHelpers.CloneCollection(Me.Cookies)

    If VBA.IsObject(web_pBody) Then
        Set Clone.Body = web_pBody
    Else
        Clone.Body = web_pBody
    End If
End Function

''
' Create WebRequest from options
'
' @method CreateFromOptions
' @param {Dictionary} Options
' @param {Collection} [Options.Headers] Collection of `KeyValue`
' @param {Collection} [Options.Cookies] Collection of `KeyValue`
' @param {Collection} [Options.QuerystringParams] Collection of `KeyValue`
' @param {Dictionary} [Options.UrlSegments]
''
Public Sub CreateFromOptions(Options As Dictionary)
    If Not Options Is Nothing Then
        If Options.Exists("Headers") Then
            Set Me.Headers = Options("Headers")
        End If
        If Options.Exists("Cookies") Then
            Set Me.Cookies = Options("Cookies")
        End If
        If Options.Exists("QuerystringParams") Then
            Set Me.QuerystringParams = Options("QuerystringParams")
        End If
        If Options.Exists("UrlSegments") Then
            Set Me.UrlSegments = Options("UrlSegments")
        End If
    End If
End Sub

' ============================================= '
' Private Functions
' ============================================= '

' Encode cookie name
'
' References:
' - RFC 6265 https://tools.ietf.org/html/rfc6265
Private Function web_EncodeCookieName(web_CookieName As Variant) As String
    Dim web_CookieVal As String
    Dim web_StringLen As Long

    web_CookieVal = VBA.CStr(web_CookieName)
    web_StringLen = VBA.Len(web_CookieVal)

    If web_StringLen > 0 Then
        Dim web_Result() As String
        Dim web_i As Long
        Dim web_CharCode As Integer
        Dim web_Char As String
        ReDim web_Result(web_StringLen)

        ' ALPHA / DIGIT / "!" / "#" / "$" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
        ' Note: "%" is allowed in spec, but is currently excluded due to parsing issues

        ' Loop through string characters
        For web_i = 1 To web_StringLen
            ' Get character and ascii code
            web_Char = VBA.Mid$(web_CookieVal, web_i, 1)
            web_CharCode = VBA.Asc(web_Char)

            Select Case web_CharCode
                Case 65 To 90, 97 To 122
                    ' ALPHA
                    web_Result(web_i) = web_Char
                Case 48 To 57
                    ' DIGIT
                    web_Result(web_i) = web_Char
                Case 33, 35, 36, 38, 39, 42, 43, 45, 46, 94, 95, 96, 124, 126
                    ' "!" / "#" / "$" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
                    web_Result(web_i) = web_Char

                Case 0 To 15
                    web_Result(web_i) = "%0" & VBA.Hex(web_CharCode)
                Case Else
                    web_Result(web_i) = "%" & VBA.Hex(web_CharCode)
            End Select
        Next web_i

        web_EncodeCookieName = VBA.Join$(web_Result, "")
    End If
End Function

Private Sub Class_Initialize()
' Set default values
    Me.RequestFormat = WebFormat.Json
    Me.ResponseFormat = WebFormat.Json
    Me.UserAgent = WebUserAgent

    Set Me.Headers = New Collection
    Set Me.QuerystringParams = New Collection
    Set Me.UrlSegments = New Dictionary
    Set Me.Cookies = New Collection
    Me.ContentLength = -1
End Sub

Attribute VB_Name = "WebClient"
Attribute VB_Base = "0{FCFB3D2A-A0FA-1068-A738-08002B3371B5}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = False
''
' WebClient v4.1.6
…
vbaProject_00.bin vba-project OOXML VBA project: xl/vbaProject.bin 778240 bytes
SHA-256: bc8a5d9c8f97c8205446439d920c0ab0b641221e49213fc8a9e253207947568f