;; 
;;=====================================================================================----- 
;; 
;;FUNCTION       Url() 
;; 
;;ACTION         Perform various URL-based operations 
;; 
;;AUTHOR         Glenn Barnas 
;; 
;;VERSION        1.4  - 2020/04/24 
;; 
;;HISTORY        1.0  - 2018/05/25 - Initial Release 
;;               1.1  - 2019/03/07 - Update to perform download/save option 
;;               1.2  - 2019/03/23 - Disable caching while running 
;;               1.3  - 2019/11/01 - Add default User-Agent id header 
;;               1.4  - 2020/04/24 - Enhance error returns with synthetic data 
;; 
;;SYNTAX         Url(url[, type][, header][, body][, user, password][, force] [, target]) 
;; 
;;PARAMETERS     url      - REQUIRED - the url string 
;;                                     MUST contain "://" to be valid. 
;; 
;;               type     - OPTIONAL - The method - PUT, GET, POST, PATCH, or DELETE 
;;                                     Defaults to GET if blank or invalid. 
;; 
;;               header   - OPTIONAL - an array of header pair arrayss (name value, name value...) 
;; 
;;               body     - OPTIONAL - Additional body text to send in POST 
;; 
;;               user     - OPTIONAL - credential User ID (BASIC Auth) 
;; 
;;               password - OPTIONAL - credential password, REQUIRED if User is supplied 
;; 
;;               force    - OPTIONAL - Force a Successful Return (for API calls that return nothing) 
;; 
;;               target   - OPTIONAL - Save path for file downloads 
;; 
;;REMARKS        General UDF for all http(s) operations. Used for file downloads, API calls, etc. 
;; 
;;RETURNS        3-element array, response text, state, status code, and status text 
;;               ===== WARNING ===== 
;;               Outputs diagnostic information to the console 
;;               if the global $DEBUG is set to "2" 
;;               ===== WARNING ===== 
;; 
;;DEPENDENCIES   Msxml2.XMLHTTP 
;; 
;;TESTED WITH    W2K8, W2K12, W2K16 
;; 
;;EXAMPLES        
; 
Function URL($_Url, Optional $_Type, Optional $_aHeader, Optional $_Body, Optional $_User, optional $_Pass, Optional $_Force, Optional $_Target)
 
  Dim $_Rc										; Return Code catcher 
  Dim $_IECom										; IE COM object 
  Dim $_I										; Header index pointer 
  Dim $_aTmp										; Temp array 
  Dim $_oStream										; Object Data Stream 
  Dim $_Err, $_SErr									; error data 
  Dim $_Cache, $_IECKey									; Cache Setting, Cache Keypath 
  Dim $_UAgent, $_UAName                                                                ; User Agent flag, Name 
  Dim $_FVer                                                                            ; Function Version 
 
  Dim $_aResp[4]
  
  $_UAgent = 1                                                                          ; Enable a custom User Agent 
  $_UAName = 'Custom Browser'                                                           ; specify the U-A identity 
  $_FVer   = '1.4'                                                                      ; UDF Version for U-A header 
 
  $_IECKey = 'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
 
  ; Check the URL 
  If Not InStr($_Url, '://')
    $Url = '', '', 999, 'Error: Invalid URL - ' + $_Url
    Exit 87
  EndIf
 
  ; Default to a GET operation if not defined or invalid 
  If VarType($_Type) < 2 Or Not InStr('~PUT~GET~POST~DELETE~PATCH~', '~' + $_Type + '~')
    $_Type = 'GET'
  EndIf
 
  ; Insure that body is empty unless type is POST PUT, or PATCH 
  If Not InStr('_POST_PUT_PATCH_', $_Type)
    $_Body = ''
  EndIf
 
  $_Cache = ReadValue($_IECKey, 'SyncMode5')
 
  ; update the cache value if necessary 
  If $_Cache <> 3
    $_Rc = WriteValue($_IECKey, 'SyncMode5', 3, 'REG_DWORD')
  EndIf
 
  ; Instantiate the connection 
  $_IECom = CreateObject("Msxml2.XMLHTTP")
  If @ERROR
    $_Err  = @ERROR
    $_SErr = @SERROR
    $_Rc = WriteValue($_IECKey, 'SyncMode5', $_Cache, 'REG_DWORD')
    $Url = '', '', 999, 'Error: COM Error - ' + $_Err + ' : ' + $_SErr
    Exit $_Err
  EndIf
 
  ; Initialize the request 
  If $_User and $_Pass
    $_IECom.open($_Type, $_Url, not 1, $_User, $_Pass)
  Else
    $_IECom.open($_Type, $_Url, not 1)
  EndIf
  If @ERROR
    $_Err  = @ERROR
    $_SErr = @SERROR
    $_Rc = WriteValue($_IECKey, 'SyncMode5', $_Cache, 'REG_DWORD')
    $Url = '', '', 999, 'Error: COM OPEN - ' + $_Err + ' : ' + $_SErr
    Exit 3
  EndIf
 
  If $DEBUG = 2
    '<<< URL Function >>>' @CRLF
    '        URL: ' $_Url @CRLF
    '       Type: ' $_Type @CRLF
    'Header Data:' @CRLF
  EndIf
 
  ; Optionally, set the header 
  If UBound($_aHeader) >= 0
    For $_I = 0 to UBound($_aHeader)
      $_aTmp = $_aHeader[$_I]
      If UBound($_aTmp) <> 1			                                        ; bad header structure 
        $Url = '', '', 999, 'Error: Bad Header Definition - Index=' + $_I
        If $DEBUG = 2 
          'Url() - Bad Header!' @CRLF
        EndIf
        Exit 6
      EndIf
      If $_aTmp[0] = 'User-Agent'                                                       ; User Agent was set 
        $_UAgent = 0
      EndIf
      $_IECom.setRequestHeader($_aTmp[0], $_aTmp[1])
      If $DEBUG = 2 ' - ' $_aTmp[0] ', ' $_aTmp[1] @CRLF EndIf
    Next
  EndIf
  
  If $_UAgent
    $_IECom.setRequestHeader('User-Agent', 'Mozilla/5.0 (' + $_UAName + ';' + $_FVer + ')')         ; add our user agent marker 
    If $DEBUG = 2
      ' - User-Agent, Mozilla/5.0 (' + $_UAName + ';' + $_FVer + ')' @CRLF
    EndIf
  EndIf
 
  If $DEBUG = 2
    '       Body:' @CRLF
    '============================================================' @CRLF
    $_Body @CRLF
    '============================================================' @CRLF
  EndIf
 
  ; Send the request 
  $_IECom.send($_Body)
 
  If $_Force And InStr(@SERROR, '80020009')	; force a success return 
    $_aResp[0] = '{' + @CRLF + '  "Result": true,' + @CRLF + '  "ResponseCode": 0,' + @CRLF + '  "Status": OK,' + @CRLF + '  "Error": Force-None' + @CRLF + '}'
    $_aResp[1] = '4'
    $_aResp[2] = '200'
    $_aResp[3] = 'OK'
    $Url = $_aResp
    If $DEBUG = 2
      'Url() - FORCE SUCCESS!' @CRLF
    EndIf
    $_Rc = WriteValue($_IECKey, 'SyncMode5', $_Cache, 'REG_DWORD')
    Exit 0
  EndIf
  If @ERROR
    $_Err  = @ERROR
    $_SErr = @SERROR
    If $DEBUG = 2
      'Url() - SEND FAILURE! '  + $_Err + ' : ' + $_SErr  @CRLF
    EndIf
    $_Rc = WriteValue($_IECKey, 'SyncMode5', $_Cache, 'REG_DWORD')
    $Url = '', '', 999, 'Error: Send Failure! ' + $_Err + ' : ' + $_SErr 
    Exit 3
  EndIf
 
  ; get the response 
  $_aResp[1] = $_IECom.readyState
  $_aResp[2] = $_IECom.status
  $_aResp[3] = $_IECom.statusText
 
  If Trim($_aResp[0]) = ''
    $_aResp[0] = '{' + @CRLF + '  "Result": true,' + @CRLF + '  "ResponseCode": 0,' + @CRLF + '  "Status": OK,' + @CRLF + '  "Error": Force-None' + @CRLF + '}'
  EndIf
 
  ; If Target is specified, save the response body 
  If $_Target
    If $_aResp[2] = '200'
      $_oStream = CreateObject('ADODB.Stream')
      If @ERROR
        $_Err  = @ERROR
        $_SErr = @SERROR
        $_Rc = WriteValue($_IECKey, 'SyncMode5', $_Cache, 'REG_DWORD')
        $Url = '', '', 999, 'Error: Output Create Error! ' + $_Err + ' : ' + $_SErr 
        Exit 2
      EndIf
 
      $_oStream.Type = 1
      $_oStream.Mode = 3
      $_oStream.Open
      $_oStream.Write($_IECom.responseBody)
      If @ERROR
        $_Err  = @ERROR
        $_SErr = @SERROR
        $_Rc = WriteValue($_IECKey, 'SyncMode5', $_Cache, 'REG_DWORD')
        $Url = '', '', 999, 'Error: Save Error! ' + $_Err + ' : ' + $_SErr 
        Exit 4
      EndIf
 
      ; Write the returned data to the file 
      $_oStream.SaveToFile($_Target, 2)
      If $_Err
        $_Err  = @ERROR
        $_SErr = @SERROR
        $_oStream.Close
        $_Rc = WriteValue($_IECKey, 'SyncMode5', $_Cache, 'REG_DWORD')
        $Url = '', '', 999, 'Error: Write Error! ' + $_Err + ' : ' + $_SErr 
        Exit $_Err
      EndIf
      $_oStream.Close
      $_aResp[0] = 'Saved to ' + $_Target
    Else
      $_aResp[0] = 'Not Saved.'
    EndIf
  Else
    $_aResp[0] = $_IECom.responseText
  EndIf
 
  $Url = $_aResp
 
  If $DEBUG = 2
    '  Resp Code: ' $_aResp[1] @CRLF
    '      Resp : ' $_aResp[2] @CRLF
    '  Resp Stat: ' $_aResp[3] @CRLF
    '       Data: ' @CRLF
    '============================================================' @CRLF
    $_aResp[0] @CRLF
    '============================================================' @CRLF
  EndIf
 
  $_IECom.close
 
  $_Rc = WriteValue($_IECKey, 'SyncMode5', $_Cache, 'REG_DWORD')
 
  Exit 0
 
EndFunction