Skip to content

Instantly share code, notes, and snippets.

@poa00
Forked from anonymous1184/README.md
Last active December 24, 2023 16:24
Show Gist options
  • Save poa00/e55a5179709c1b98c55b0d56444b023a to your computer and use it in GitHub Desktop.
Save poa00/e55a5179709c1b98c55b0d56444b023a to your computer and use it in GitHub Desktop.
ahk.v1-2.WinHttpRequest

A user yesterday [wanted a specific voice]1 out of text-to-speech, but he wanted one from a web version and not included in the OS (ie, there was the need to scrape the page). Thus...

WinHttpRequest Wrapper ([v2.0]2 / [v1.1]3)

There's no standardized method to make HTTP requests, basically, we have:

  • XMLHTTP.
  • WinHttpRequest.
  • UrlDownloadToFile.
  • Complex DllCall()s.

Download()/UrlDownloadToFile are super-limited, unless you know you need to use it, XMLHTTP should be avoided; and DllCall() is on the advanced spectrum as is basically what you'll do in C++ with wininet.dll/urlmon.dll. That leaves us with WinHttpRequest for which I didn't find a nice wrapper around the object (years ago, maybe now there is) and most importantly, no 7-bit binary encoding support for multipart when dealing with uploads or big PATCH/POST/PUT requests. So, here's my take.

It will help with services and even for scraping (use the APIs if exist). The highlights or main benefits against other methods:

  • Follows redirects.
  • Automatic cookie handling.
  • It has convenience static methods.
  • Can ignore SSL errors, and handles all TLS versions.
  • Returns request headers, JSON, status, and text.
    • The JSON representation is lazily-loaded upon request.
  • The result of the call can be saved into a file (ie download).
  • The MIME type (when uploading) is controlled by the MIME subclass.
    • Extend it if needed (I've never used anything other than what's there, but YMMV).
  • The MIME boundary is 40 chars long, making it compatible with cURL.
    • If you use the appropriate UA length, the request will be the same size as one made by cURL.

Convenience static methods

Equivalent to JavaScript:

    WinHttpRequest.EncodeURI(sUri)
    WinHttpRequest.EncodeURIComponent(sComponent)
    WinHttpRequest.DecodeURI(sUri)
    WinHttpRequest.DecodeURIComponent(sComponent)

AHK key/pair map (object for v1.1) to URL query (key1=val1&key2=val2) and vice versa:

    WinHttpRequest.ObjToQuery(oData)
    WinHttpRequest.QueryToObj(sData)

Calling the object

Creating an instance:

    http := WinHttpRequest(oOptions)

The COM object is exposed via the .whr property:

    MsgBox(http.whr.Option(2), "URL Code Page", 0x40040)
    ; https://learn.microsoft.com/en-us/windows/win32/winhttp/winhttprequestoption

Options:

oOptions := <Map>              ;                Options is a Map (object for v1.1)
oOptions["Proxy"] := false     ;                Default. Use system settings
                               ; "DIRECT"       Direct connection
                               ; "proxy[:port]" Custom-defined proxy, same rules as system proxy
oOptions["Revocation"] := true ;                Default. Check for certificate revocation
                               ; false          Do not check
oOptions["SslError"] := true   ;                Default. Validation of SSL handshake/certificate
                               ; false          Ignore all SSL warnings/errors
oOptions["TLS"] := ""          ;                Defaults to TLS 1.2/1.3
                               ; <Int>          https://support.microsoft.com/en-us/topic/update-to-enable-tls-1-1-and-tls-1-2-as-default-secure-protocols-in-winhttp-in-windows-c4bd73d2-31d7-761e-0178-11268bb10392
oOptions["UA"] := ""           ;                If defined, uses a custom User-Agent string

Returns:

    response := http.VERB(...) ; Object
    response.Headers := <Map>  ; Key/value Map (object for v1.1)
    response.Json := <Json>    ; JSON object
    response.Status := <Int>   ; HTTP status code
    response.Text := ""        ; Plain text response
Methods

HTTP verbs as public methods

    http.DELETE()
    http.GET()
    http.HEAD()
    http.OPTIONS()
    http.PATCH()
    http.POST()
    http.PUT()
    http.TRACE()

All the HTTP verbs use the same parameters:

sUrl     = Required, string.
mBody    = Optional, mixed. String or key/value map (object for v1.1).
oHeaders = Optional, key/value map (object for v1.1). HTTP headers and their values.
oOptions = Optional. key/value map (object for v1.1) as specified below:

oOptions["Encoding"] := ""     ;       Defaults to `UTF-8`.
oOptions["Multipart"] := false ;       Default. Uses `application/x-www-form-urlencoded` for POST.
                               ; true  Force usage of `multipart/form-data` for POST.
oOptions["Save"] := ""         ;       A file path to store the response of the call.
                               ;       (Prepend an asterisk to save even non-200 status codes)
Examples

GET:

    endpoint := "http://httpbin.org/get?key1=val1&key2=val2"
    response := http.GET(endpoint)
    MsgBox(response.Text, "GET", 0x40040)
; or
    endpoint := "http://httpbin.org/get"
    body := "key1=val1&key2=val2"
    response := http.GET(endpoint, body)
    MsgBox(response.Text, "GET", 0x40040)
; or
    endpoint := "http://httpbin.org/get"
    body := Map()
    body["key1"] := "val1"
    body["key2"] := "val2"
    response := http.GET(endpoint, body)
    MsgBox(response.Text, "GET", 0x40040)

POST, regular:

    endpoint := "http://httpbin.org/post"
    body := Map("key1", "val1", "key2", "val2")
    response := http.POST(endpoint, body)
    MsgBox(response.Text, "POST", 0x40040)

POST, force multipart (for big payloads):

    endpoint := "http://httpbin.org/post"
    body := Map()
    body["key1"] := "val1"
    body["key2"] := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
    options := {Multipart:true}
    response := http.POST(endpoint, body, , options)
    MsgBox(response.Text, "POST", 0x40040)

HEAD, retrieve a specific header:

    endpoint := "https://github.com/"
    response := http.HEAD(endpoint)
    MsgBox(response.Headers["X-GitHub-Request-Id"], "HEAD", 0x40040)

Download the response (it handles binary data):

    endpoint := "https://www.google.com/favicon.ico"
    options := Map("Save", A_Temp "\google.ico")
    http.GET(endpoint, , , options)
    RunWait(A_Temp "\google.ico")
    FileDelete(A_Temp "\google.ico")

To upload files, put the paths inside an array:

    ; Image credit: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
    Download("http://probablyprogramming.com/wp-content/uploads/2009/03/handtinyblack.gif", A_Temp "\1x1.gif")

    endpoint := "http://httpbun.org/anything"
    ; Single file
    body := Map("test", 123, "my_image", [A_Temp "\1x1.gif"])
    ; Multiple files (PHP server style)
    ; body := Map("test", 123, "my_image[]", [A_Temp "\1x1.gif", A_Temp "\1x1.gif"])
    headers := Map()
    headers["Accept"] := "application/json"
    response := http.POST(endpoint, body, headers)
    MsgBox(response.Json.files.my_image, "Upload", 0x40040)
Notes

1. Use G33kDude's [cJson.ahk]4 as the JSON library because it has boolean/null support, however others can be used.

2. Even if I said that DllCall() was on the advanced side of things, is better suited to download big files. Regardless if the wrapper supports saving a file, doesn't mean is meant to act as a downloader because the memory usage is considerable (the size of the file needs to be allocated in memory, so a 1 GiB file will need the same amount of memory).

3. Joe Glines did a [talk on the subject]5, if you want a high-level overview about it.

4. You just need to drop it in [a library]6 and start using it.

Footnotes

  1. https://redd.it/mbpwf7

  2. https://gist.github.com/anonymous1184/e6062286ac7f4c35b612d3a53535cc2a/raw/WinHttpRequest.ahk

  3. https://gist.github.com/anonymous1184/e6062286ac7f4c35b612d3a53535cc2a/raw/WinHttpRequest-deprecated.ahk

  4. https://github.com/G33kDude/cJson.ahk

  5. https://youtu.be/fzfJCLeEWfQ

  6. https://www.autohotkey.com/docs/Functions.htm#lib

#Requires AutoHotkey v1.1
; Version: 2023.07.05.2
; https://gist.github.com/e6062286ac7f4c35b612d3a53535cc2a
; Usage and examples: https://redd.it/mcjj4s
; Testing: http://httpbin.org/ | http://httpbun.org/ | http://ptsv2.com/
WinHttpRequest(oOptions := "") {
return new WinHttpRequest(oOptions)
}
class WinHttpRequest extends WinHttpRequest.Functor {
whr := ComObjCreate("WinHttp.WinHttpRequest.5.1")
;#region: Meta
__New(oOptions := "") {
static HTTPREQUEST_PROXYSETTING_DEFAULT := 0, HTTPREQUEST_PROXYSETTING_DIRECT := 1, HTTPREQUEST_PROXYSETTING_PROXY := 2, EnableCertificateRevocationCheck := 18, SslErrorIgnoreFlags := 4, SslErrorFlag_Ignore_All := 13056, SecureProtocols := 9, WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 := 8192, WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 := 2048, UserAgentString := 0
if (!IsObject(oOptions)) {
oOptions := {}
}
if (!oOptions.HasKey("Proxy") || !oOptions.Proxy) {
this.whr.SetProxy(HTTPREQUEST_PROXYSETTING_DEFAULT)
} else if (oOptions.Proxy = "DIRECT") {
this.whr.SetProxy(HTTPREQUEST_PROXYSETTING_DIRECT)
} else {
this.whr.SetProxy(HTTPREQUEST_PROXYSETTING_PROXY, oOptions.Proxy)
}
if (oOptions.HasKey("Revocation")) {
this.whr.Option[EnableCertificateRevocationCheck] := !!oOptions.Revocation
} else {
this.whr.Option[EnableCertificateRevocationCheck] := true
}
if (oOptions.HasKey("SslError")) {
if (oOptions.SslError = false) {
this.whr.Option[SslErrorIgnoreFlags] := SslErrorFlag_Ignore_All
}
}
if (!oOptions.HasKey("TLS")) {
this.whr.Option[SecureProtocols] := WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2
} else {
this.whr.Option[SecureProtocols] := oOptions.TLS
}
if (oOptions.HasKey("UA")) {
this.whr.Option[UserAgentString] := oOptions.UA
}
}
;#endregion
;#region: Static
EncodeUri(sUri) {
return this._EncodeDecode(sUri, true, false)
}
EncodeUriComponent(sComponent) {
return this._EncodeDecode(sComponent, true, true)
}
DecodeUri(sUri) {
return this._EncodeDecode(sUri, false, false)
}
DecodeUriComponent(sComponent) {
return this._EncodeDecode(sComponent, false, true)
}
ObjToQuery(oData) {
if (!IsObject(oData)) {
return oData
}
out := ""
for key, val in oData {
out .= this._EncodeDecode(key, true, true) "="
out .= this._EncodeDecode(val, true, true) "&"
}
return RTrim(out, "&")
}
QueryToObj(sData) {
if (IsObject(sData)) {
return sData
}
sData := LTrim(sData, "?")
obj := {}
for _, part in StrSplit(sData, "&") {
pair := StrSplit(part, "=", "", 2)
key := this._EncodeDecode(pair[1], false, true)
val := this._EncodeDecode(pair[2], false, true)
obj[key] := val
}
return obj
}
;#endregion
;#region: Public
Request(sMethod, sUrl, mBody := "", oHeaders := false, oOptions := false) {
if (this.whr = "") {
throw Exception("Not initialized.", -1)
}
sMethod := Format("{:U}", sMethod) ; CONNECT not supported
if !(sMethod ~= "^(DELETE|GET|HEAD|OPTIONS|PATCH|POST|PUT|TRACE)$") {
throw Exception("Invalid HTTP verb.", -1, sMethod)
}
if !(sUrl := Trim(sUrl)) {
throw Exception("Empty URL.", -1)
}
if (!IsObject(oHeaders)) {
oHeaders := {}
}
if (!IsObject(oOptions)) {
oOptions := {}
}
if (sMethod = "POST") {
multi := oOptions.HasKey("Multipart") ? !!oOptions.Multipart : false
this._Post(mBody, oHeaders, multi)
} else if (sMethod = "GET" && mBody) {
sUrl := RTrim(sUrl, "&")
sUrl .= InStr(sUrl, "?") ? "&" : "?"
sUrl .= WinHttpRequest.ObjToQuery(mBody)
mBody := ""
}
this.whr.Open(sMethod, sUrl, true)
for key, val in oHeaders {
this.whr.SetRequestHeader(key, val)
}
this.whr.Send(mBody)
this.whr.WaitForResponse()
if (oOptions.HasKey("Save")) {
target := RegExReplace(oOptions.Save, "^\h*\*\h*", "", forceSave)
if (this.whr.Status = 200 || forceSave) {
this._Save(target)
}
return this.whr.Status
}
out := new WinHttpRequest._Response()
out.Headers := this._Headers()
out.Status := this.whr.Status
out.Text := this._Text(oOptions.HasKey("Encoding") ? oOptions.Encoding : "")
return out
}
;#endregion
;#region: Private
static _doc := ""
_EncodeDecode(Text, bEncode, bComponent) {
if (this._doc = "") {
this._doc := ComObjCreate("HTMLFile")
this._doc.write("<meta http-equiv='X-UA-Compatible' content='IE=Edge'>")
}
action := (bEncode ? "en" : "de") "codeURI" (bComponent ? "Component" : "")
return ObjBindMethod(this._doc.parentWindow, action).Call(Text)
}
_Headers() {
headers := this.whr.GetAllResponseHeaders()
headers := RTrim(headers, "`r`n")
out := {}
for _, line in StrSplit(headers, "`n", "`r") {
pair := StrSplit(line, ":", " ", 2)
out[pair[1]] := pair[2]
}
return out
}
_Mime(Extension) {
if (WinHttpRequest.MIME.HasKey(Extension)) {
return WinHttpRequest.MIME[Extension]
}
return "application/octet-stream"
}
_MultiPart(ByRef Body) {
static LMEM_ZEROINIT := 64, EOL := "`r`n"
this._memLen := 0
this._memPtr := DllCall("LocalAlloc", "UInt", LMEM_ZEROINIT, "UInt", 1)
boundary := "----------WinHttpRequest-" A_NowUTC A_MSec
for field, value in Body {
this._MultiPartAdd(boundary, EOL, field, value)
}
this._MultipartStr("--" boundary "--" EOL)
Body := ComObjArray(0x11, this._memLen)
pvData := NumGet(ComObjValue(Body) + 8 + A_PtrSize, "Ptr")
DllCall("RtlMoveMemory", "Ptr", pvData, "Ptr", this._memPtr, "UInt", this._memLen)
DllCall("LocalFree", "Ptr", this._memPtr)
return boundary
}
_MultiPartAdd(Boundary, EOL, Field, Value) {
if (!IsObject(Value)) {
str := "--" Boundary
str .= EOL
str .= "Content-Disposition: form-data; name=""" Field """"
str .= EOL
str .= EOL
str .= Value
str .= EOL
this._MultipartStr(str)
return
}
for _, path in Value {
SplitPath path, filename, , ext
str := "--" Boundary
str .= EOL
str .= "Content-Disposition: form-data; name=""" Field """; filename=""" filename """"
str .= EOL
str .= "Content-Type: " this._Mime(ext)
str .= EOL
str .= EOL
this._MultipartStr(str)
this._MultipartFile(path)
this._MultipartStr(EOL)
}
}
_MultipartFile(Path) {
static LHND := 66
try {
oFile := FileOpen(Path, 0x0)
} catch {
throw Exception("Couldn't open file for reading.", -1, Path)
}
this._memLen += oFile.Length
this._memPtr := DllCall("LocalReAlloc", "Ptr", this._memPtr, "UInt", this._memLen, "UInt", LHND)
oFile.RawRead(this._memPtr + this._memLen - oFile.length, oFile.length)
}
_MultipartStr(Text) {
static LHND := 66
size := StrPut(Text, "UTF-8") - 1
this._memLen += size
this._memPtr := DllCall("LocalReAlloc", "Ptr", this._memPtr, "UInt", this._memLen, "UInt", LHND)
StrPut(Text, this._memPtr + this._memLen - size, size, "UTF-8")
}
_Post(ByRef Body, ByRef Headers, bMultipart) {
isMultipart := 0
for _, value in Body {
isMultipart += !!IsObject(value)
}
if (isMultipart || bMultipart) {
Body := WinHttpRequest.QueryToObj(Body)
boundary := this._MultiPart(Body)
Headers["Content-Type"] := "multipart/form-data; boundary=""" boundary """"
} else {
Body := WinHttpRequest.ObjToQuery(Body)
if (!Headers.HasKey("Content-Type")) {
Headers["Content-Type"] := "application/x-www-form-urlencoded"
}
}
}
_Save(Path) {
arr := this.whr.ResponseBody
pData := NumGet(ComObjValue(arr) + 8 + A_PtrSize, "Ptr")
length := arr.MaxIndex() + 1
FileOpen(Path, 0x1).RawWrite(pData + 0, length)
}
_Text(Encoding) {
response := ""
try response := this.whr.ResponseText
if (response = "" || Encoding != "") {
try {
arr := this.whr.ResponseBody
pData := NumGet(ComObjValue(arr) + 8 + A_PtrSize, "Ptr")
length := arr.MaxIndex() + 1
response := StrGet(pData, length, Encoding)
}
}
return response
}
class Functor {
__Call(Method, Parameters*) {
return this.Request(Method, Parameters*)
}
}
class _Response {
Json {
get {
method := Json.HasKey("parse") ? "parse" : "Load"
oJson := ObjBindMethod(Json, method, this.Text).Call()
ObjRawSet(this, "Json", oJson)
return oJson
}
}
}
;#endregion
class MIME {
static 7z := "application/x-7z-compressed"
static gif := "image/gif"
static jpg := "image/jpeg"
static json := "application/json"
static png := "image/png"
static zip := "application/zip"
}
}
#Requires AutoHotkey v2.0
; Version: 2023.07.05.2
; https://gist.github.com/e6062286ac7f4c35b612d3a53535cc2a
; Usage and examples: https://redd.it/mcjj4s
; Testing: http://httpbin.org/ | http://httpbun.org/ | http://ptsv2.com/
class WinHttpRequest extends WinHttpRequest.Functor {
whr := ComObject("WinHttp.WinHttpRequest.5.1")
;#region: Meta
__New(oOptions := "") {
static HTTPREQUEST_PROXYSETTING_DEFAULT := 0, HTTPREQUEST_PROXYSETTING_DIRECT := 1, HTTPREQUEST_PROXYSETTING_PROXY := 2, EnableCertificateRevocationCheck := 18, SslErrorIgnoreFlags := 4, SslErrorFlag_Ignore_All := 13056, SecureProtocols := 9, WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 := 8192, WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 := 2048, UserAgentString := 0
if (!IsObject(oOptions)) {
oOptions := Map()
}
if (!oOptions.Has("Proxy") || !oOptions["Proxy"]) {
this.whr.SetProxy(HTTPREQUEST_PROXYSETTING_DEFAULT)
} else if (oOptions["Proxy"] = "DIRECT") {
this.whr.SetProxy(HTTPREQUEST_PROXYSETTING_DIRECT)
} else {
this.whr.SetProxy(HTTPREQUEST_PROXYSETTING_PROXY, oOptions["Proxy"])
}
if (oOptions.Has("Revocation")) {
this.whr.Option[EnableCertificateRevocationCheck] := !!oOptions["Revocation"]
} else {
this.whr.Option[EnableCertificateRevocationCheck] := true
}
if (oOptions.Has("SslError")) {
if (oOptions["SslError"] = false) {
this.whr.Option[SslErrorIgnoreFlags] := SslErrorFlag_Ignore_All
}
}
if (!oOptions.Has("TLS")) {
this.whr.Option[SecureProtocols] := WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2
} else {
this.whr.Option[SecureProtocols] := oOptions["TLS"]
}
if (oOptions.Has("UA")) {
this.whr.Option[UserAgentString] := oOptions["UA"]
}
}
;#endregion
;#region: Static
static EncodeUri(sUri) {
return this._EncodeDecode(sUri, true, false)
}
static EncodeUriComponent(sComponent) {
return this._EncodeDecode(sComponent, true, true)
}
static DecodeUri(sUri) {
return this._EncodeDecode(sUri, false, false)
}
static DecodeUriComponent(sComponent) {
return this._EncodeDecode(sComponent, false, true)
}
static ObjToQuery(oData) {
if (!IsObject(oData)) {
return oData
}
out := ""
for key, val in oData {
out .= this._EncodeDecode(key, true, true) "="
out .= this._EncodeDecode(val, true, true) "&"
}
return RTrim(out, "&")
}
static QueryToObj(sData) {
if (IsObject(sData)) {
return sData
}
sData := LTrim(sData, "?")
obj := Map()
for _, part in StrSplit(sData, "&") {
pair := StrSplit(part, "=", "", 2)
key := this._EncodeDecode(pair[1], false, true)
val := this._EncodeDecode(pair[2], false, true)
obj[key] := val
}
return obj
}
;#endregion
;#region: Public
Request(sMethod, sUrl, mBody := "", oHeaders := false, oOptions := false) {
if (this.whr = "") {
throw Error("Not initialized.", -1)
}
sMethod := Format("{:U}", sMethod) ; CONNECT not supported
if !(sMethod ~= "^(DELETE|GET|HEAD|OPTIONS|PATCH|POST|PUT|TRACE)$") {
throw Error("Invalid HTTP verb.", -1, sMethod)
}
if !(sUrl := Trim(sUrl)) {
throw Error("Empty URL.", -1)
}
if (!IsObject(oHeaders)) {
oHeaders := Map()
}
if (!IsObject(oOptions)) {
oOptions := Map()
}
if (sMethod = "POST") {
multi := oOptions.Has("Multipart") ? !!oOptions["Multipart"] : false
this._Post(&mBody, &oHeaders, multi)
} else if (sMethod = "GET" && mBody) {
sUrl := RTrim(sUrl, "&")
sUrl .= InStr(sUrl, "?") ? "&" : "?"
sUrl .= WinHttpRequest.ObjToQuery(mBody)
mBody := ""
}
this.whr.Open(sMethod, sUrl, true)
for key, val in oHeaders {
this.whr.SetRequestHeader(key, val)
}
this.whr.Send(mBody)
this.whr.WaitForResponse()
if (oOptions.Has("Save")) {
target := RegExReplace(oOptions["Save"], "^\h*\*\h*", "", &forceSave)
if (this.whr.Status = 200 || forceSave) {
this._Save(target)
}
return this.whr.Status
}
out := WinHttpRequest._Response()
out.Headers := this._Headers()
out.Status := this.whr.Status
out.Text := this._Text(oOptions.Has("Encoding") ? oOptions["Encoding"] : "")
return out
}
;#endregion
;#region: Private
static _doc := ""
static _EncodeDecode(Text, bEncode, bComponent) {
if (this._doc = "") {
this._doc := ComObject("HTMLFile")
this._doc.write("<meta http-equiv='X-UA-Compatible' content='IE=Edge'>")
}
action := (bEncode ? "en" : "de") "codeURI" (bComponent ? "Component" : "")
return ObjBindMethod(this._doc.parentWindow, action).Call(Text)
}
_Headers() {
headers := this.whr.GetAllResponseHeaders()
headers := RTrim(headers, "`r`n")
out := Map()
for _, line in StrSplit(headers, "`n", "`r") {
pair := StrSplit(line, ":", " ", 2)
out.Set(pair*)
}
return out
}
_Mime(Extension) {
if (WinHttpRequest.MIME.HasProp(Extension)) {
return WinHttpRequest.MIME.%Extension%
}
return "application/octet-stream"
}
_MultiPart(&Body) {
static LMEM_ZEROINIT := 64, EOL := "`r`n"
this._memLen := 0
this._memPtr := DllCall("LocalAlloc", "UInt", LMEM_ZEROINIT, "UInt", 1)
boundary := "----------WinHttpRequest-" A_NowUTC A_MSec
for field, value in Body {
this._MultiPartAdd(boundary, EOL, field, value)
}
this._MultipartStr("--" boundary "--" EOL)
Body := ComObjArray(0x11, this._memLen)
pvData := NumGet(ComObjValue(Body) + 8 + A_PtrSize, "Ptr")
DllCall("RtlMoveMemory", "Ptr", pvData, "Ptr", this._memPtr, "UInt", this._memLen)
DllCall("LocalFree", "Ptr", this._memPtr)
return boundary
}
_MultiPartAdd(Boundary, EOL, Field, Value) {
if (!IsObject(Value)) {
str := "--" Boundary
str .= EOL
str .= "Content-Disposition: form-data; name=`"" Field "`""
str .= EOL
str .= EOL
str .= Value
str .= EOL
this._MultipartStr(str)
return
}
for _, path in Value {
SplitPath(path, &filename, , &ext)
str := "--" Boundary
str .= EOL
str .= "Content-Disposition: form-data; name=`"" Field "`"; filename=`"" filename "`""
str .= EOL
str .= "Content-Type: " this._Mime(ext)
str .= EOL
str .= EOL
this._MultipartStr(str)
this._MultipartFile(path)
this._MultipartStr(EOL)
}
}
_MultipartFile(Path) {
static LHND := 66
try {
oFile := FileOpen(Path, 0x0)
} catch {
throw Error("Couldn't open file for reading.", -1, Path)
}
this._memLen += oFile.Length
this._memPtr := DllCall("LocalReAlloc", "Ptr", this._memPtr, "UInt", this._memLen, "UInt", LHND)
oFile.RawRead(this._memPtr + this._memLen - oFile.length, oFile.length)
}
_MultipartStr(Text) {
static LHND := 66
size := StrPut(Text, "UTF-8") - 1
this._memLen += size
this._memPtr := DllCall("LocalReAlloc", "Ptr", this._memPtr, "UInt", this._memLen, "UInt", LHND)
StrPut(Text, this._memPtr + this._memLen - size, size, "UTF-8")
}
_Post(&Body, &Headers, bMultipart) {
isMultipart := 0
for _, value in Body {
isMultipart += !!IsObject(value)
}
if (isMultipart || bMultipart) {
Body := WinHttpRequest.QueryToObj(Body)
boundary := this._MultiPart(&Body)
Headers["Content-Type"] := "multipart/form-data; boundary=`"" boundary "`""
} else {
Body := WinHttpRequest.ObjToQuery(Body)
if (!Headers.Has("Content-Type")) {
Headers["Content-Type"] := "application/x-www-form-urlencoded"
}
}
}
_Save(Path) {
arr := this.whr.ResponseBody
pData := NumGet(ComObjValue(arr) + 8 + A_PtrSize, "Ptr")
length := arr.MaxIndex() + 1
FileOpen(Path, 0x1).RawWrite(pData + 0, length)
}
_Text(Encoding) {
response := ""
try response := this.whr.ResponseText
if (response = "" || Encoding != "") {
try {
arr := this.whr.ResponseBody
pData := NumGet(ComObjValue(arr) + 8 + A_PtrSize, "Ptr")
length := arr.MaxIndex() + 1
response := StrGet(pData, length, Encoding)
}
}
return response
}
class Functor {
/* _H v2.0.2 adds Object.Prototype.Get() breaking
GET verb's dynamic call, this is a workaround. */
GET(Parameters*) {
return this.Request("GET", Parameters*)
}
__Call(Method, Parameters) {
return this.Request(Method, Parameters*)
}
}
class _Response {
Json {
get {
method := HasMethod(JSON, "parse") ? "parse" : "Load"
oJson := ObjBindMethod(JSON, method, this.Text).Call()
this.DefineProp("Json", { Value: oJson })
return oJson
}
}
}
;#endregion
class MIME {
static 7z := "application/x-7z-compressed"
static gif := "image/gif"
static jpg := "image/jpeg"
static json := "application/json"
static png := "image/png"
static zip := "application/zip"
}
}
--- WinHttpRequest.ahk
+++ WinHttpRequest-deprecated.ahk
@@ -1 +1 @@
-#Requires AutoHotkey v2.0
+#Requires AutoHotkey v1.1
@@ -7,0 +8,4 @@
+WinHttpRequest(oOptions := "") {
+ return new WinHttpRequest(oOptions)
+}
+
@@ -10 +14 @@
- whr := ComObject("WinHttp.WinHttpRequest.5.1")
+ whr := ComObjCreate("WinHttp.WinHttpRequest.5.1")
@@ -17 +21 @@
- oOptions := Map()
+ oOptions := {}
@@ -19 +23 @@
- if (!oOptions.Has("Proxy") || !oOptions["Proxy"]) {
+ if (!oOptions.HasKey("Proxy") || !oOptions.Proxy) {
@@ -21 +25 @@
- } else if (oOptions["Proxy"] = "DIRECT") {
+ } else if (oOptions.Proxy = "DIRECT") {
@@ -24 +28 @@
- this.whr.SetProxy(HTTPREQUEST_PROXYSETTING_PROXY, oOptions["Proxy"])
+ this.whr.SetProxy(HTTPREQUEST_PROXYSETTING_PROXY, oOptions.Proxy)
@@ -26,2 +30,2 @@
- if (oOptions.Has("Revocation")) {
- this.whr.Option[EnableCertificateRevocationCheck] := !!oOptions["Revocation"]
+ if (oOptions.HasKey("Revocation")) {
+ this.whr.Option[EnableCertificateRevocationCheck] := !!oOptions.Revocation
@@ -31,2 +35,2 @@
- if (oOptions.Has("SslError")) {
- if (oOptions["SslError"] = false) {
+ if (oOptions.HasKey("SslError")) {
+ if (oOptions.SslError = false) {
@@ -36 +40 @@
- if (!oOptions.Has("TLS")) {
+ if (!oOptions.HasKey("TLS")) {
@@ -39 +43 @@
- this.whr.Option[SecureProtocols] := oOptions["TLS"]
+ this.whr.Option[SecureProtocols] := oOptions.TLS
@@ -41,2 +45,2 @@
- if (oOptions.Has("UA")) {
- this.whr.Option[UserAgentString] := oOptions["UA"]
+ if (oOptions.HasKey("UA")) {
+ this.whr.Option[UserAgentString] := oOptions.UA
@@ -49 +53 @@
- static EncodeUri(sUri) {
+ EncodeUri(sUri) {
@@ -53 +57 @@
- static EncodeUriComponent(sComponent) {
+ EncodeUriComponent(sComponent) {
@@ -57 +61 @@
- static DecodeUri(sUri) {
+ DecodeUri(sUri) {
@@ -61 +65 @@
- static DecodeUriComponent(sComponent) {
+ DecodeUriComponent(sComponent) {
@@ -65 +69 @@
- static ObjToQuery(oData) {
+ ObjToQuery(oData) {
@@ -77 +81 @@
- static QueryToObj(sData) {
+ QueryToObj(sData) {
@@ -82 +86 @@
- obj := Map()
+ obj := {}
@@ -97 +101 @@
- throw Error("Not initialized.", -1)
+ throw Exception("Not initialized.", -1)
@@ -101 +105 @@
- throw Error("Invalid HTTP verb.", -1, sMethod)
+ throw Exception("Invalid HTTP verb.", -1, sMethod)
@@ -104 +108 @@
- throw Error("Empty URL.", -1)
+ throw Exception("Empty URL.", -1)
@@ -107 +111 @@
- oHeaders := Map()
+ oHeaders := {}
@@ -110 +114 @@
- oOptions := Map()
+ oOptions := {}
@@ -113,2 +117,2 @@
- multi := oOptions.Has("Multipart") ? !!oOptions["Multipart"] : false
- this._Post(&mBody, &oHeaders, multi)
+ multi := oOptions.HasKey("Multipart") ? !!oOptions.Multipart : false
+ this._Post(mBody, oHeaders, multi)
@@ -127,2 +131,2 @@
- if (oOptions.Has("Save")) {
- target := RegExReplace(oOptions["Save"], "^\h*\*\h*", "", &forceSave)
+ if (oOptions.HasKey("Save")) {
+ target := RegExReplace(oOptions.Save, "^\h*\*\h*", "", forceSave)
@@ -134 +138 @@
- out := WinHttpRequest._Response()
+ out := new WinHttpRequest._Response()
@@ -137 +141 @@
- out.Text := this._Text(oOptions.Has("Encoding") ? oOptions["Encoding"] : "")
+ out.Text := this._Text(oOptions.HasKey("Encoding") ? oOptions.Encoding : "")
@@ -146 +150 @@
- static _EncodeDecode(Text, bEncode, bComponent) {
+ _EncodeDecode(Text, bEncode, bComponent) {
@@ -148 +152 @@
- this._doc := ComObject("HTMLFile")
+ this._doc := ComObjCreate("HTMLFile")
@@ -158 +162 @@
- out := Map()
+ out := {}
@@ -161 +165 @@
- out.Set(pair*)
+ out[pair[1]] := pair[2]
@@ -167,2 +171,2 @@
- if (WinHttpRequest.MIME.HasProp(Extension)) {
- return WinHttpRequest.MIME.%Extension%
+ if (WinHttpRequest.MIME.HasKey(Extension)) {
+ return WinHttpRequest.MIME[Extension]
@@ -173 +177 @@
- _MultiPart(&Body) {
+ _MultiPart(ByRef Body) {
@@ -193 +197 @@
- str .= "Content-Disposition: form-data; name=`"" Field "`""
+ str .= "Content-Disposition: form-data; name=""" Field """"
@@ -202 +206 @@
- SplitPath(path, &filename, , &ext)
+ SplitPath path, filename, , ext
@@ -205 +209 @@
- str .= "Content-Disposition: form-data; name=`"" Field "`"; filename=`"" filename "`""
+ str .= "Content-Disposition: form-data; name=""" Field """; filename=""" filename """"
@@ -221 +225 @@
- throw Error("Couldn't open file for reading.", -1, Path)
+ throw Exception("Couldn't open file for reading.", -1, Path)
@@ -236 +240 @@
- _Post(&Body, &Headers, bMultipart) {
+ _Post(ByRef Body, ByRef Headers, bMultipart) {
@@ -243,2 +247,2 @@
- boundary := this._MultiPart(&Body)
- Headers["Content-Type"] := "multipart/form-data; boundary=`"" boundary "`""
+ boundary := this._MultiPart(Body)
+ Headers["Content-Type"] := "multipart/form-data; boundary=""" boundary """"
@@ -247 +251 @@
- if (!Headers.Has("Content-Type")) {
+ if (!Headers.HasKey("Content-Type")) {
@@ -276,7 +280 @@
- /* _H v2.0.2 adds Object.Prototype.Get() breaking
- GET verb's dynamic call, this is a workaround. */
- GET(Parameters*) {
- return this.Request("GET", Parameters*)
- }
-
- __Call(Method, Parameters) {
+ __Call(Method, Parameters*) {
@@ -292,3 +290,3 @@
- method := HasMethod(JSON, "parse") ? "parse" : "Load"
- oJson := ObjBindMethod(JSON, method, this.Text).Call()
- this.DefineProp("Json", { Value: oJson })
+ method := Json.HasKey("parse") ? "parse" : "Load"
+ oJson := ObjBindMethod(Json, method, this.Text).Call()
+ ObjRawSet(this, "Json", oJson)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment