LibQurl (a libcurl wrapper for AHKv2)

Post your working scripts, libraries and tools.
Qriist
Posts: 82
Joined: 11 Sep 2016, 04:02

LibQurl (a libcurl wrapper for AHKv2)

11 Mar 2024, 03:39

Hello all, this is a class wrapping libcurl for AHKv2. Although only a few features are currently fleshed out, this is a full direct binding of every curl function, plus some QOL scaffolding.

Features:
  • this is a full direct binding of libcurl, meaning that you have access* to all functions
  • libcurl's "easy" interface is currently partially wrapped in a user-friendly way
  • transparently compressed transfers are on by default - this saves bandwidth and time
  • numerous simultaneous curl handles are supported, though the "multi" interface isn't yet smoothly implemented
  • the ability to download a file directly into RAM without touching the disk - very useful when working with APIs
*Around 50 dll functions were added in an "untested" state and are clearly marked as such. Curl's docs are a bit of a mess so there are almost certainly some instances of providing the wrong data type in the DllCall. Caveat emptor.

Getting up and running is extraordinarily simple:

Code: Select all

;boilerplate
#requires Autohotkey v2.1-alpha.2
#Include <LibQurl>
curl := LibQurl()
curl.register(A_ScriptDir "\lib\libcurl-x64.dll")	;internally SetOpt()'s several useful defaults

;can be modified as required
url := "https://www.google.com"
curl.SetOpt("URL",url)
curl.WriteToFile(a_scriptdir "\download\google.html")
curl.Perform()
A short walkthrough of some of the flexibility features of LibQurl:

Code: Select all

#requires Autohotkey v2.1-alpha.2
#Include <LibQurl>
curl := LibQurl()

;points the class at the libcurl dll and (optionally) save the handle
handleA := curl.register(A_ScriptDir "\lib\libcurl-x64.dll")

;this line is commented because the class attempts to find the cert with the dll
; curl.SetOpt("CAINFO",A_ScriptDir "\lib\curl-ca-bundle.crt")

;Set some options.
;Note that we don't need to pass the saved handle when we aren't multitasking.
curl.SetOpt("URL","https://httpbin.org/headers")
curl.SetHeaders(Map("tidbit","is a header")) ;pass some outgoing headers
curl.WriteToFile(a_scriptdir "\download\httpbin-body.json")
; curl.HeaderToMem() ;headers are written into memory by default
curl.Perform()
msgbox "=== handleA ===`n"
    .   "[[[   HEADERS   ]]]`n" 
    .   curl.GetLastHeaders() "`n`n"
    .   "[[[   BODY   ]]]`n" 
    .   curl.GetLastBody()


;program dictates we need to work with two sets of independent options now
handleB := curl.Init()

;handleA has *technically* already set URL but I'm setting it again here for clarity
curl.SetOpt("URL","https://httpbin.org/headers",handleA)
curl.SetHeaders(Map("tidbit","is a header","jank","extraJank"),handleA)

;handleA routine determined it wants to write to file
curl.WriteToFile(a_scriptdir "\download\a-body.json",handleA)
curl.HeaderToFile(a_scriptdir "\download\a-head.txt",handleA)

;start working on handleB options
curl.SetOpt("URL","https://www.titsandasses.org",handleB)
curl.WriteToFile(a_scriptdir "\download\b-body.json",handleB)
curl.HeaderToFile(a_scriptdir "\download\b-head.txt",handleB)

;something changed, now we want to save handleA in memory
;note the comma: there is an optional first parameter
curl.WriteToMem(,handleA)   
curl.HeaderToMem(,handleA)

;download on each of our handles
;multiple Perform() operations don't have to be sequential
curl.Perform(handleA)
curl.Perform(handleB)

msgbox "=== handleA (again) ===`n"
    .   "[[[   HEADERS   ]]]`n" 
    .   curl.GetLastHeaders(,handleA) "`n`n"
    .   "[[[   BODY   ]]]`n" 
    .   curl.GetLastBody(,handleA)

msgbox "=== handleB ===`n"
    .   "[[[   HEADERS   ]]]`n" 
    .   curl.GetLastHeaders(,handleB) "`n`n"
    .   "[[[   BODY   ]]]`n" 
    .   curl.GetLastBody(,handleB)

;"object" will return the reference object regardless of type
headersA := curl.GetLastHeaders("object",handleA)
headersB := curl.GetLastHeaders("object",handleB)

msgbox "The Type() of handleA headers is: " Type(headersA) "`n`n"
    .   "The Type() of handleB headers is: " Type(headersB)

MsgBox "This msgbox will show that the object is returned only if the requested subtype matches. "
    .   "Otherwise, it will try to return a string in the correct encoding. "
    .   "Will return UTF-8 as a last resort.`n`n"

    .   "HeaderToMem(,handleA)   =>   GetLastHeaders(<request>,handleA)`n"
    .   "no special request" a_tab a_tab "headersA Type() = " Type(curl.GetLastHeaders(unset,handleA)) "`n"    ;"String"
    .   "Requesting 'Object'" a_tab "headersA Type() = " Type(curl.GetLastHeaders("object",handleA)) "`n"    ;"Buffer"
    .   "Requesting 'File'" a_tab a_tab "headersA Type() = " Type(curl.GetLastHeaders("File",handleA)) "`n"    ;"String"
    .   "Requesting 'Buffer'" a_tab a_tab "headersA Type() = " Type(curl.GetLastHeaders("Buffer",handleA)) "`n`n"     ;"Buffer"

    .   "HeaderToFile(,handleB)   =>   GetLastHeaders(<request>,handleB)`n"
    .   "no special request" a_tab a_tab "headersB Type() = " Type(curl.GetLastHeaders(unset,handleB)) "`n"    ;"String"
    .   "Requesting 'Object'" a_tab "headersB Type() = " Type(curl.GetLastHeaders("object",handleB)) "`n"    ;"File"
    .   "Requesting 'File'" a_tab a_tab "headersB Type() = " Type(curl.GetLastHeaders("File",handleB)) "`n"    ;"File"
    .   "Requesting 'Buffer'" a_tab a_tab "headersB Type() = " Type(curl.GetLastHeaders("Buffer",handleB)) "`n"   ;"String"
aaaand here's the class itself:

Code: Select all

#requires Autohotkey v2.1-alpha.2
class LibQurl {
    __New() {
        this.handleMap := Map()
        static curlDLLhandle := ""
        static curlDLLpath := ""
        this.Opt := Map()   ;option reference matrix
        this.struct := LibQurl._struct()  ;holds the various structs
        this.writeRefs := Map()    ;holds the various write handles
        this.CURL_ERROR_SIZE := 256
    }
    register(dllPath) {
        if !FileExist(dllPath)
            throw ValueError("libcurl DLL not found!", -1, dllPath)
        this.curlDLLpath := dllpath
        this.curlDLLhandle := DllCall("LoadLibrary", "Str", dllPath, "Ptr")   ;load the DLL into resident memory
        this._curl_global_init()
        this._buildOptMap()
        ; msgbox this["CurlVersionInfo"]
        this.VersionInfo := this.GetVersionInfo()
        return this.Init()
    }
    GetVersionInfo(){
        verPtr := this._curl_version_info()
        retObj := this.struct.curl_version_info_data(verPtr)
        return retObj
    }

    ListHandles(){
        ;returns a plaintext listing of all handles
        ret := ""
        for k,v in this.handleMap {
            ret .= k "`n"
        }
        return Trim(ret,"`n")
    }
    Init(){
        handle := this._curl_easy_init()
        this.handleMap[handle] := this.handleMap[0] := Map() ;handleMap[0] is a dynamic reference to the last created handle
        If !this.handleMap[handle]
            throw ValueError("Problem in 'curl_easy_init'! Unable to init easy interface!", -1, this.curlDLLpath)
        this.handleMap[handle]["handle"] := handle
        this.handleMap[handle]["options"] := Map()  ;prepares option storage
        ,this.SetOpt("ACCEPT_ENCODING","",handle)    ;enables compressed transfers without affecting input headers
        ,this.SetOpt("FOLLOWLOCATION",1,handle)    ;allows curl to follow redirects
        ,this.SetOpt("MAXREDIRS",30,handle)    ;limits redirects to 30 (matches recent curl default)
        
        ;try to auto-load curl's cert bundle
        ;can still be set per handle
        SplitPath(this.curlDLLpath,,&dlldir)
        If FileExist(dlldir "\curl-ca-bundle.crt")
            this.SetOpt("CAINFO",dlldir "\curl-ca-bundle.crt",handle)

        this.handleMap[handle]["callbacks"] := Map()  ;prepares write callbacks
        for k,v in ["body","header","read","progress","debug"]{
            this.handleMap[handle]["callbacks"][v] := Map()
            this.handleMap[handle]["callbacks"][v]["CBF"] := ""
        }
        this._setCallbacks(1,1,1,1,,handle) ;don't enable debug by default
        this.HeaderToMem(0,handle)    ;automatically save lastHeader to memory
        return handle
    }
    EasyInit(){ ;just a clarifying alias for Init()
        return this.Init()
    }
    ; DupeInit(handle?){
        ; newHandle := this._curl_easy_duphandle(handle)
        ; this.handleMap[newHandle] := this.handleMap[0] := this.DeepClone(this.handleMap[handle])
        ; If !this.handleMap[newHandle]
        ;     throw ValueError("Problem in 'curl_easy_duphandle'! Unable to init easy interface!", -1, this.curlDLLpath)
        ; this.handleMap[newHandle] := this.handleMap[0] := Map() ;handleMap[0] is a dynamic reference to the last created handle
        ; ,this.handleMap[newHandle]["options"] := Map()  ;prepares option storage
        ; for k,v in this.handleMap[handle]["options"]
        ;     this.SetOpt(k,v,newHandle)
        ; this.handleMap[newHandle]["handle"] := newHandle
        ; return newHandle 
        /*
        if !IsSet(handle)
            handle := this.handleMap[0]["handle"]   ;defaults to the last created handle
        newHandle := this._curl_easy_duphandle(handle)
        If !this.handleMap[handle]
            throw ValueError("Problem in 'curl_easy_init'! Unable to init easy interface!", -1, this.curlDLLpath)
        ; msgbox handle "`n" newHandle "`n`n" this.handleMap[0]["handle"]
        this.handleMap[newHandle] := this.DeepClone(this.handleMap[handle])
        msgbox this.ShowOB(this.handleMap[newHandle])
        this.handleMap[0]["handle"] := this.handleMap[newHandle]["handle"]
        ; msgbox this.handleMap[newHandle]["handle"] "`n" this.handleMap[handle]["handle"] "`n`n" this.handleMap[0]["handle"]
        ; this.handleMap[newHandle] := this.handleMap[0] := Map() ;handleMap[0] is a dynamic reference to the last created handle
        ; ,this.handleMap[newHandle]["options"] := Map()  ;prepares option storage


        ; for k,v in this.handleMap[handle]["options"]
        ;     this.SetOpt(k,v,newHandle)
        return newHandle   
        */     
    ; }
    GetErrorString(errornum){
        return StrGet(this._curl_easy_strerror(errornum),"UTF-8")
    }
    ListOpts(handle?){  ;returns human-readable printout of the given handle's set options
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        ret := "These are the options that have been set for this handle:`n"
        for k,v in this.handleMap[handle]["options"]{
                if (v!="")
                    ret .= k ": " (!IsObject(v)?v:"<OBJECT>") "`n"
                else
                    ret .= k ": " "<NULL>" "`n"
        }
        return ret
    }

    ShowOB(ob, strOB := "") {  ; returns `n list.  pass object, returns list of elements. nice chart format with `n.  strOB for internal use only.
        (Type(Ob) ~= 'Object|Gui') ? Ob := Ob.OwnProps() : 1
        for i, v in ob
        (!isobject(v)) ? (rets .= "`n [" strOB i "] = [" v "]") : (rets .= ShowOB(v, strOB i "."))
        return isSet(rets) ? rets : ""
    }
    SetOpt(option,parameter,handle?){
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        this.handleMap[handle]["options"][option] := parameter
        return this._curl_easy_setopt(handle,option,parameter)
    }
    SetOpts(optionMap,&optErrMap,handle?){  ;for setting multiple options at once
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        optErrMap := Map()
        optErrVal := 0
        ;TODO - add handling for Opts with scaffolding
        for k,v in optionMap {
            Switch k, "OFF" {
                ; case "URL":{}
                Default: optErrVal += optErrMap[k] := this.SetOpt(k,v,handle)
            }
        }
        return optErrVal    ;any non-zero value means you should check the optErrMap
    }

    WriteToFile(filename, handle?) {
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        ;instanstiate Storage.File
        passedHandleMap := this.handleMap
        this.handleMap[handle]["callbacks"]["body"]["storageHandle"] := LibQurl.Storage.File(filename, &passedHandleMap, "body", "w", handle)
        this.SetOpt("WRITEDATA",this.handleMap[handle]["callbacks"]["body"]["storageHandle"],handle)
        this.SetOpt("WRITEFUNCTION",this.handleMap[handle]["callbacks"]["body"]["CBF"],handle) 
        Return
    }

    WriteToMem(maxCapacity := 0, handle?) {
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        passedHandleMap := this.handleMap
        this.handleMap[handle]["callbacks"]["body"]["storageHandle"] := LibQurl.Storage.MemBuffer(dataPtr?, maxCapacity?, dataSize?, &passedHandleMap, "body", handle)
        this.SetOpt("WRITEDATA",this.handleMap[handle]["callbacks"]["body"]["storageHandle"],handle)
        this.SetOpt("WRITEFUNCTION",this.handleMap[handle]["callbacks"]["body"]["CBF"],handle)
		Return
	}
	
	; WriteToNone() {
	; 	Return (this._writeTo := "")
	; }

    ; WriteToMagic(handle?) {
    ;     if !IsSet(handle)
    ;         handle := this.handleMap[0]["handle"]   ;defaults to the last created handle
    ;     ;instanstiate Storage.File
    ;     passedHandleMap := this.handleMap
    ;     this.handleMap[handle]["callbacks"]["body"]["storageHandle"] := class_libcurl.Storage.File(filename, &passedHandleMap, "body", "w", handle)
    ;     this.SetOpt("WRITEDATA",this.handleMap[handle]["callbacks"]["body"]["storageHandle"],handle)
    ;     this.SetOpt("WRITEFUNCTION",this.handleMap[handle]["callbacks"]["body"]["CBF"],handle) 
    ;     Return
    ; }
	
	HeaderToFile(filename, handle?) {
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        passedHandleMap := this.handleMap
        this.handleMap[handle]["callbacks"]["header"]["storageHandle"] := LibQurl.Storage.File(filename, &passedHandleMap, "header", "w", handle)
        this.SetOpt("HEADERDATA",this.handleMap[handle]["callbacks"]["header"]["storageHandle"],handle)
        this.SetOpt("HEADERFUNCTION",this.handleMap[handle]["callbacks"]["header"]["CBF"],handle)
		Return
	}
	
	HeaderToMem(maxCapacity := 0, handle?) {
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        passedHandleMap := this.handleMap
		this.handleMap[handle]["callbacks"]["header"]["storageHandle"] := LibQurl.Storage.MemBuffer(dataPtr?, maxCapacity?, dataSize?, &passedHandleMap, "header", handle)
        this.SetOpt("HEADERDATA",this.handleMap[handle]["callbacks"]["header"]["storageHandle"],handle)
        this.SetOpt("HEADERFUNCTION",this.handleMap[handle]["callbacks"]["header"]["CBF"],handle)
        Return
	}
	
	; HeaderToNone() {
	; 	Return (this._headerTo := "")
	; }


    _setCallbacks(body?,header?,read?,progress?,debug?,handle?){
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle

        if IsSet(body){
            CBF := this.handleMap[handle]["callbacks"]["body"]["CBF"]
            if IsInteger(CBF){  ;checks if this callback already exists
                this.writeRefs[CBF] -= 1    ;decrement the reference tracker
                ,CallbackFree(CBF)
                ,(CBF=0?writeRefs.delete(CBF):"")   ;remove key if done with it
            }

            this.handleMap[handle]["callbacks"]["body"]["CBF"] := CBF := CallbackCreate(
                (dataPtr, size, sizeBytes, userdata) =>
                this._writeCallbackFunction(dataPtr, size, sizeBytes, userdata, handle)
            )

            ;creates the tracking key
            this.writeRefs[CBF] := 1
        }


        if IsSet(header){
            CBF := this.handleMap[handle]["callbacks"]["header"]["CBF"]
            if IsInteger(CBF){  ;checks if this callback already exists
                this.writeRefs[CBF] -= 1    ;decrement the reference tracker
                ,CallbackFree(CBF)
                ,(CBF=0?writeRefs.delete(CBF):"")   ;remove key if done with it
            }
            ; if IsInteger(this.handleMap[handle]["callbacks"]["header"]["CBF"])
            ;     CallbackFree(this.handleMap[handle]["callbacks"]["header"]["CBF"])
            this.handleMap[handle]["callbacks"]["header"]["CBF"] := CBF := CallbackCreate(
                (dataPtr, size, sizeBytes, userdata) =>
                this._headerCallbackFunction(dataPtr, size, sizeBytes, userdata, handle)
            )
            
            ;creates the tracking key
            this.writeRefs[CBF] := 1
            ; msgbox (CBF)
        }
        ; if IsSet(read)
        ; if IsSet(progress)
        ; if IsSet(debug)
        
        
        ;non-lambda rewrite
        ;   actualCallbackFunction(dataPtr, size, sizeBytes, userdata) {
        ;     return this._writeCallbackFunction(dataPtr, size, sizeBytes, userdata, passed_curl_handle)
        ;   }
        ;   this.handleMap[handle]["writeCallbackFunction"] := CallbackCreate(actualCallbackFunction)
        ; Curl._CB_Header   := CallbackCreate(Curl._HeaderCallback)
		; Curl._CB_Read     := CallbackCreate(Curl._ReadCallback)
		; Curl._CB_Progress := CallbackCreate(Curl._ProgressCallback)
		; Curl._CB_Debug    := CallbackCreate(Curl._DebugCallback)
    }
    Perform(handle?){
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        this.handleMap[handle]["callbacks"]["body"]["storageHandle"].Open()
        this.handleMap[handle]["callbacks"]["header"]["storageHandle"].Open()
        retCode := DllCall("libcurl-x64\curl_easy_perform","Ptr",handle)
        ; msgbox "perform code: " retCode
        this.handleMap[handle]["callbacks"]["body"]["storageHandle"].Close()
        this.handleMap[handle]["callbacks"]["header"]["storageHandle"].Close()

        ;accessibly attach body to handle output
        bodyObj := this.handleMap[handle]["callbacks"]["body"]
        lastBody := (bodyObj["writeType"]="memory"?bodyObj["writeTo"]:FileOpen(bodyObj["filename"],"rw"))
        this.handleMap[handle]["lastBody"] := lastBody

        ;accessibly attach headers to handle output
        headerObj := this.handleMap[handle]["callbacks"]["header"]
        lastHeaders := (headerObj["writeType"]="memory"?headerObj["writeTo"]:FileOpen(headerObj["filename"],"rw"))
        this.handleMap[handle]["lastHeaders"] := lastHeaders
        return retCode
    }
    GetLastHeaders(returnAsEncoding := "UTF-8",handle?){
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        lastHeaders := this.handleMap[handle]["lastHeaders"]
        if ((returnAsEncoding = "Object") && IsObject(lastHeaders))
        || ((returnAsEncoding = "File") && (Type(lastHeaders) = "File"))
        || ((returnAsEncoding = "Buffer") && (Type(lastHeaders) = "Buffer"))
            return lastHeaders
        RegexMatch(returnAsEncoding,"i)(?:Object|File|Buffer|(\S+))",&f) ;filter object types
        return (Type(lastHeaders)="File"?(lastHeaders.seek(0,0)=1?"":"") lastHeaders.read()
            :StrGet(lastHeaders,(f[1]=returnAsEncoding?f[1]:"UTF-8")))
    }
    GetLastBody(returnAsEncoding := "UTF-8",handle?){
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        lastBody := this.handleMap[handle]["lastBody"]
        if ((returnAsEncoding = "Object") && IsObject(lastBody))
        || ((returnAsEncoding = "File") && (Type(lastBody) = "File"))
        || ((returnAsEncoding = "Buffer") && (Type(lastBody) = "Buffer"))
            return lastBody
        RegexMatch(returnAsEncoding,"i)(?:Object|File|Buffer|(\S+))",&f) ;filter object types
        return (Type(lastBody)="File"?(lastBody.seek(0,0)=1?"":"") lastBody.read()
            :StrGet(lastBody,(f[1]=returnAsEncoding?f[1]:"UTF-8")))
    }

    Cleanup(handle?){
        handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
        for k,v in this.handleMap[handle]["callbacks"]
            if IsInteger(this.handleMap[handle]["callbacks"][k]["CBF"])
                CallbackFree(this.handleMap[handle]["callbacks"][k]["CBF"])
        this.handleMap.Delete(handle)
        this._curl_easy_cleanup(handle)
    }
    EasyCleanup(handle?){   ;alias for Cleanup
        this.Cleanup(handle?)
    }
	; Callbacks
	; =========
    _writeCallbackFunction(dataPtr, size, sizeBytes, userdata, handle) {
		dataSize := size * sizeBytes
        return this.handleMap[handle]["callbacks"]["body"]["storageHandle"].RawWrite(dataPtr, dataSize)
	}

	_headerCallbackFunction(dataPtr, size, sizeBytes, userdata, handle) {
		dataSize := size * sizeBytes
        ; msgbox type(this.handleMap[handle]["callbacks"]["header"]["storageHandle"])
		Return this.handleMap[handle]["callbacks"]["header"]["storageHandle"].RawWrite(dataPtr, dataSize)
	}
 
    Class Storage {
        ; Wrapper for file. Shouldn't be used directly.
        Class File {
            __New(filename, &handleMap, storageCategory, accessMode := "w", handle?) {
                this.handleMap := handleMap
                handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle

                this.writeObj := this.handleMap[handle]["callbacks"][storageCategory]
                this.writeObj["writeType"] := "file"
                this.writeObj["filename"] := filename
                this.writeObj["accessMode"] := accessMode
                this.writeObj["writeTo"] := ""
                this.writeObj["curlHandle"] := handle
                this.storageCategory := storageCategory
                ; ; User callbacks
                ; this.OnWrite    := ""
                ; this.OnRead     := ""
                ; this.OnHeader   := ""
                ; this.OnProgress := ""
                ; this.OnDebug    := ""
                
                ; ; Input/output
                ; this._writeTo  := ""
                ; this._headerTo := ""
                ; this._readFrom := ""
            }

            Open() {
                If (this.writeObj["accessMode"] == "w") {
                    SplitPath(this.writeObj["filename"], , &fileDirPath)
                    If fileDirPath
                        DirCreate fileDirPath
                    this.writeObj["writeTo"] := FileOpen(this.writeObj["filename"], this.writeObj["accessMode"], "CP0") 
                    
                    ;associates the write object with the curl handle
                    ; this.handleMap["assoc"][this.writeObj["writeTo"].handle] := this.getCurlHandle()
                    ; msgbox this.handleMap["assoc"][this.getHandle()]
                }
            }

            Close() {
            	this.writeObj["writeTo"].Close()
                ; this.handleMap["assoc"].Delete(this.writeObj["writeTo"].handle)
            }

            Write(data) {
                ; If (this._fileObject == "")
                ; 	Return -1
                Return this.writeObj["writeTo"].Write(data)
            }

            RawWrite(srcDataPtr, srcDataSize) {
            	; If (this._fileObject == "")
            	; || (this._accessMode != "w")
            	; 	Return -1
            	Return this.writeObj["writeTo"].RawWrite(srcDataPtr+0, srcDataSize)
            }

            getCurlHandle() {
                return this.writeObj["curlHandle"]
            }

            RawRead(dstDataPtr, dstDataSize) {
            ; 	If (this._fileObject == "")
            ; 	|| (this._accessMode != "r")
            ; 		Return -1

                Return this.writeObj["writeTo"].RawRead(dstDataPtr+0, dstDataSize)
            }

            Seek(offset, origin := 0) {
            	Return !(this.writeObj["writeTo"].Seek(offset, origin))
            }
        }

        Class MemBuffer {
        ; Wrapper for memory buffer, similar to regular FileObject
        	__New(dataPtr := 0, maxCapacity?, dataSize := 0, &handleMap?, storageCategory?, handle?) {
        		; this._data     := ""
        		this._dataPos  := 0
                this.handleMap := handleMap
                handle ??= this.handleMap[0]["handle"]   ;defaults to the last created handle
                ; msgbox handle

                this.handle := handle
                this.storageCategory := storageCategory
                this.writeObj := this.handleMap[handle]["callbacks"][storageCategory]
                this.writeObj["writeType"] := "memory"

                If !IsSet(maxCapacity)
        			maxCapacity := 8*1024*1024  ; 8 Mb
                this.writeObj["maxCapacity"] := maxCapacity
                this.writeObj["writeTo"] := Buffer(maxCapacity := Max(maxCapacity, dataSize))
                ; this.writeObj["writeTo"] := Buffer(maxCapacity)
                this.writeObj["curlHandle"] := handle
                this.writeObj["interimPtr"] := 0
        		



        		If (dataPtr != 0) {
        			this._dataMax  := maxCapacity
        			this._dataSize := dataSize
        			this._dataPtr  := dataPtr
        		} Else
        		; No argument, store inside class.
        		{
        			this._dataSize := 0
        			this._dataMax  := ObjSetCapacity(this.writeObj["writeTo"], maxCapacity)
        			this._dataPtr  := "" ;ObjGetAddress(this._data)
        		}
        	}

        	Open() {
        		; Do nothing
        	}

        	Close() {
        		; this.handleMap[this.handle]["lastHeaders"] := this.writeObj["writeTo"]
                ; msgbox strget(this.writeObj["writeBuffer"],"UTF-8")
        	}

        	; Write(data) {
        	; 	srcDataSize := StrPut(srcText, "CP0")

        	; 	If ((this._dataPos + srcDataSize) > this._dataMax)
        	; 		Return -1

        	; 	StrPut(data, this._dataPtr + this._dataPos, "CP0")

        	; 	this._dataPos  += srcDataSize
        	; 	this._dataSize := Max(this._dataSize, this._dataPos)

        	; 	Return srcDataSize
        	; }

            RawWrite(srcDataPtr, srcDataSize) {
                Offset := this.writeObj["writeTo"].Size
                this.writeObj["writeTo"].Size += srcDataSize
                DllCall("ntdll\memcpy"
                    , "Ptr" , this.writeObj["writeTo"].Ptr + Offset
                    , "Ptr" , srcDataPtr+0
                    , "Int" , srcDataSize)
                Return srcDataSize
            }

        	; GetAsText(encoding := "UTF-8") {
        	; 	isEncodingWide := ((encoding = "UTF-16") || (encoding = "CP1200"))
        	; 	textMaxLength  := this._dataSize / (isEncodingWide ? 2 : 1)
        	; 	Return StrGet(this._dataPtr, textMaxLength, encoding)
        	; }

        	; RawRead(dstDataPtr, dstDataSize) {
        	; 	dataLeft := this._dataSize - this._dataPos
        	; 	dstDataSize := Min(dstDataSize, dataLeft)

        	; 	DllCall("ntdll\memcpy"
        	; 	, "Ptr" , dstDataPtr
        	; 	, "Ptr" , this._dataPtr + this._dataPos
        	; 	, "Int" , dstDataSize)

        	; 	Return dstDataSize
        	; }

        	; Seek(offset, origin := 0) {
        	; 	newDataPos := offset
        	; 	+ ( (origin == 0) ? 0               ; SEEK_SET
        	; 	  : (origin == 1) ? this._dataPos   ; SEEK_CUR
        	; 	  : (origin == 2) ? this._dataSize  ; SEEK_END
        	; 	  : 0 )                             ; Unknown 'origin', use SEEK_SET

        	; 	If (newDataPos > this._dataSize)
        	; 	|| (newDataPos < 0)
        	; 		Return 1  ; CURL_SEEKFUNC_FAIL

        	; 	this._dataPos := newDataPos
        	; 	Return 0  ; CURL_SEEKFUNC_OK
        	; }

        	; Tell() {
        	; 	Return this._dataPos
        	; }

        	Length() {
        		Return this._dataSize
        	}
        }
    }

    
    ErrorHandler(callingMethod,invokedCurlFunction,curlErrorCodeType,incomingValue?){
        If (curlErrorCodeType = "Curlcode") {

        } else if (curlErrorCodeType = "Curlmcode") {

        } else if (curlErrorCodeType = "Curlshcode") {

        } else if (curlErrorCodeType = "Curlucode") {

        } else if (curlErrorCodeType = "Curlhcode") {

        }
    }
    DeepClone(obj) {    ;https://github.com/thqby/ahk2_lib/blob/master/deepclone.ahk
        ;fully copies an object without any shared references.
        objs := Map(), objs.Default := ''
        return clone(obj)
    
        clone(obj) {
            switch Type(obj) {
                case 'Array', 'Map':
                    o := obj.Clone()
                    for k, v in o
                        if IsObject(v)
                            o[k] := objs[p := ObjPtr(v)] || (objs[p] := clone(v))
                    return o
                case 'Object':
                    o := obj.Clone()
                    for k, v in o.OwnProps()
                        if IsObject(v)
                            o.%k% := objs[p := ObjPtr(v)] || (objs[p] := clone(v))
                    return o
                default:
                    return obj
            }
        }
    }
    ; Sets custom HTTP headers for request.
	; Pass an array of "Header: value" strings OR a Map of the same.
	; Use empty value ("Header: ") to disable internally used header.
	; Use semicolon ("Header;") to add the header with no value.
	SetHeaders(headersArrayOrMap,handle?) {
        if (Type(headersArrayOrMap)="Map"){
            headersArray := []
            for k,v in headersArrayOrMap{
                switch v {
                    case "":    ;diabled
                        headersArray.Push(k ": ")
                    case ";":   ;empty
                        headersArray.Push(k ";")
                    default:
                        headersArray.Push(k ": " v)
                }
            }
        } else {
            headersArray := headersArrayOrMap
        }
        headersPtr := this._ArrayToSList(headersArray)
		Return this.SetOpt("HTTPHEADER", headersPtr,handle?)
	}
    	; Linked-list
	; ===========
	
	; Converts an array of strings to linked-list.
	; Returns pointer to linked-list, or 0 if something went wrong.
	
	_ArrayToSList(strArray) {
		ptrSList := 0
		ptrTemp  := 0
		
		Loop strArray.Length {
			ptrTemp := this._curl_slist_append(ptrSList,strArray[A_Index])
            
    		If (ptrTemp == 0) {
				Curl._FreeSList(ptrSList)
				Return 0
			}
			ptrSList := ptrTemp
		}
		
		Return ptrSList
	}
	
	
	; Converts linked-list to an array of strings.
	
	_SListToArray(ptrSList) {
		result  := []
		ptrNext := ptrSList
		
		Loop {
			If (ptrNext == 0)
				Break
			
			ptrData := NumGet(ptrNext, 0, "Ptr")
			ptrNext := NumGet(ptrNext, A_PtrSize, "Ptr")
			
			result.Push(StrGet(ptrData, "CP0"))
		}
		
		Return result
	}
	
	
	_FreeSList(ptrSList?) {
		If (!IsSet(ptrSList) || (ptrSList == 0))
			Return
		this._curl_slist_free_all(ptrSList)
	}


    
    ;internal libcurl functions called by this class
    _curl_easy_cleanup(handle) {    ;untested https://curl.se/libcurl/c/curl_easy_cleanup.html
        DllCall(this.curlDLLpath "\curl_easy_cleanup"
            ,   "Ptr", handle)
    }
    _curl_easy_duphandle(handle) {  ;untested   https://curl.se/libcurl/c/curl_easy_duphandle.html
        ret := DllCall(this.curlDLLpath "\curl_easy_duphandle"
            , "Int", handle)
        return ret
    }
    _curl_easy_escape(handle, url) {
        ;doesn't like unicode, should I use the native windows function for this?
        ;char *curl_easy_escape(CURL *curl, const char *string, int length);
        esc := DllCall(this.curlDLLpath "\curl_easy_escape"
            , "Ptr", handle
            , "AStr", url
            , "Int", 0
            , "Ptr")
        return StrGet(esc, "UTF-8")

    }
    _curl_easy_getinfo(handle,info,&retCode) {  ;untested   https://curl.se/libcurl/c/curl_easy_getinfo.html
        return DllCall(this.curlDLLpath "\curl_easy_getinfo"
            ,   "Ptr", handle
            ,   "UInt", info
            ,   "Int", retCode)
    }
    _curl_easy_header(handle,name,index,origin,request) {   ;untested https://curl.se/libcurl/c/curl_easy_header.html
        return DllCall(this.curlDLLpath "\curl_easy_header"
            ,   "Ptr", name
            ,   "Int", index
            ,   "Int", origin
            ,   "Int", request
            ,   "Ptr")
    }
    _curl_easy_init() {
        return DllCall(this.curlDLLpath "\curl_easy_init")
    }
    _curl_easy_nextheader(handle,origin,request,prev) { ;untested https://curl.se/libcurl/c/curl_easy_nextheader.html
        return DllCall(this.curlDLLpath "\curl_easy_nextheader"
            ,   "Int", origin
            ,   "Int", request
            ,   "Ptr", prev
            ,   "Ptr")
    }
    _curl_easy_option_by_id(id) {
        ;returns from the pre-built array
        If this.optMap.Has(id)
            return this.optMap[id]
        return 0
    }
    _curl_easy_option_by_name(name) {
        ;returns from the pre-built array
        If this.optMap.Has(name)
            return this.optMap[name]
        return 0

        ; retCode := DllCall(this.curlDLLpath "\curl_easy_option_by_name"
        ;     ,"AStr",name
        ;     ,"Ptr")
        ; return retCode
    }
    _curl_easy_option_next(optPtr) {    ;https://curl.se/libcurl/c/curl_easy_option_next.html
        return DllCall("libcurl-x64\curl_easy_option_next"
            ,   "UInt", optPtr
            ,   "Ptr")
    }
    _curl_easy_pause(handle,bitmask) {  ;untested   https://curl.se/libcurl/c/curl_easy_pause.html
        return DllCall(this.curlDLLpath "\curl_easy_pause"
            ,   "Int", handle
            ,   "UInt", bitmask)
    }
    _curl_easy_perform(handle?) {
        if !IsSet(handle)
            handle := this.handleMap[0]["handle"]   ;defaults to the last created handle
        retCode := DllCall(this.curlDLLpath "\curl_easy_perform"
            , "Ptr", handle)
        return retCode
    }
    _curl_easy_recv(handle,buffer,buflen,&bytes) { ;untested   https://curl.se/libcurl/c/curl_easy_recv.html
        return DllCall(this.curlDLLpath "\curl_easy_recv"
            ,   "Ptr", handle
            ,   "Ptr", buffer
            ,   "Int", buflen
            ,   "Int", &bytes)
    }
    _curl_easy_reset(handle) {  ;https://curl.se/libcurl/c/curl_easy_reset.html
        DllCall(this.curlDLLpath "\curl_easy_reset"
            , "Ptr", handle)
    }
    _curl_easy_send(handle,buffer,buflen,&bytes) { ;untested   https://curl.se/libcurl/c/curl_easy_send.html
        return DllCall(this.curlDLLpath "\curl_easy_send"
            ,   "Ptr", handle
            ,   "Ptr", buffer
            ,   "Int", buflen
            ,   "Int", &bytes)
    }
    _curl_easy_setopt(handle, option, parameter, debug?) {
        if IsSet(debug)
            msgbox this.showob(this.opt[option]) "`n`n`n"
        ; .   "passed handle: " handle "`n"
        ; .   "passed id:" this.opt[option].id "`n"
        ; .   "passed type: " argTypes[this.opt[option].type]
        retCode := DllCall(this.curlDLLpath "\curl_easy_setopt"
            , "Ptr", handle
            , "Int", this.opt[option]["id"]
            , this.opt[option]["type"], parameter)
        return retCode
    }
    _curl_easy_strerror(errornum) {
        return DllCall(this.curlDLLpath "\curl_easy_strerror"
            , "Int", errornum
            ,"Ptr")
    }
    _curl_easy_unescape(handle,input,inlength,outlength) { ;untested   https://curl.se/libcurl/c/curl_easy_unescape.html
        return DllCall(this.curlDLLpath "\curl_easy_unescape"
            ,   "Ptr", handle
            ,   "AStr", input
            ,   "Int", inlength
            ,   "Int", outlength)
    }
    _curl_easy_upkeep(handle) { ;untested https://curl.se/libcurl/c/curl_easy_upkeep.html
        return DllCall(this.curlDLLpath "\curl_easy_upkeep"
            , "Ptr", handle)
    }
    _curl_free(pStr) {  ;untested   ;https://curl.se/libcurl/c/curl_free.html
        DllCall("libcurl\curl_free"
            ,   "Ptr", pStr)
    }
    _curl_getdate(datestring) {   ;untested   https://curl.se/libcurl/c/curl_getdate.html
        return DllCall(this.curlDLLpath "\curl_global_getdate"
            ,   "AStr", datestring
            ,   "UInt", "") ;not used, pass a NULL
    }
    _curl_global_cleanup(handle) {  ;untested   https://curl.se/libcurl/c/curl_global_cleanup.html
        DllCall(this.curlDLLpath "\curl_global_cleanup")
    }
    _curl_global_init() {   ;https://curl.se/libcurl/c/curl_global_init.html
        ;can't find the various flag values so it's locked to the default "everything" mode for now - prolly okay
        if DllCall(this.curlDLLpath "\curl_global_init", "Int", 0x03, "CDecl")  ;returns 0 on success
            throw ValueError("Problem in 'curl_global_init'! Unable to init DLL!", -1, this.curlDLLpath)
        else
            return
    }
    ; _curl_global_init_mem(flags,curl_malloc_callback,curl_free_callback,curl_realloc_callback,curl_strdup_callback,curl_calloc_callback) {   ;untested   https://curl.se/libcurl/c/curl_global_init_mem.html

    ; }
    _curl_global_sslset(id,name,&avail?) {  ;untested   https://curl.se/libcurl/c/curl_global_sslset.html
        return DllCall(this.curlDLLpath "\curl_global_sslset"
            ,   "Int", id
            ,   "AStr", name
            ,   "Ptr", &avail?)
    }
    _curl_global_trace(config){   ;untested   https://curl.se/libcurl/c/curl_global_trace.html
        return DllCall(this.curlDLLpath "\curl_global_trace"
            ,   "AStr", config)
    }
    _curl_mime_addpart(mime_handle) { ;untested   https://curl.se/libcurl/c/curl_mime_addpart.html
        return DllCall(this.curlDLLpath "\curl_mime_addpart"
            ,   "Int", mime_handle)
    }
    _curl_mime_data(mime_handle,data,datasize) { ;untested   https://curl.se/libcurl/c/curl_mime_data.html
        return DllCall(this.curlDLLpath "\curl_mime_data"
            ,   "Int", mime_handle
            ,   "Ptr", data
            ,   "Int", datasize)
    }
    _curl_mime_data_cb(mime_handle,datasize,readfunc,seekfunc,freefunc,arg) {  ;untested   https://curl.se/libcurl/c/curl_mime_data_cb.html
        return DllCall(this.curlDLLpath "\curl_mime_data_cb"
            ,   "Int", mime_handle
            ,   "Int", datasize
            ,   "Ptr", readfunc
            ,   "Ptr", seekfunc
            ,   "Ptr", freefunc
            ,   "Ptr", arg)
    }
    _curl_mime_encoder(mime_handle,encoding) {  ;untested   https://curl.se/libcurl/c/curl_mime_encoder.html
        return DllCall(this.curlDLLpath "\curl_mime_encoder"
            ,   "Int", mime_handle
            ,   "AStr", encoding)
    }
    _curl_mime_filedata(mime_handle,filename) {    ;untested   https://curl.se/libcurl/c/curl_mime_filedata.html
        return DllCall(this.curlDLLpath "\curl_mime_filedata"
            ,   "Int", mime_handle
            ,   "AStr", filename)
    }
    _curl_mime_filename(mime_handle,filename) { ;untested   https://curl.se/libcurl/c/curl_mime_filename.html
        return DllCall(this.curlDLLpath "\curl_mime_filename"
            ,   "Int", mime_handle
            ,   "AStr", filename)
    }
    _curl_mime_free(mime_handle) {  ;untested   https://curl.se/libcurl/c/curl_mime_free.html
        return DllCall(this.curlDLLpath "curl_mime_free"
            ,   "Int", mime_handle)
    }
    _curl_mime_headers(mime_handle,headers,take_ownership) {    ;untested   https://curl.se/libcurl/c/curl_mime_headers.html
        return DllCall(this.curlDLLpath "curl_mime_headers"
            ,   "Int", mime_handle
            ,   "Int", headers
            ,   "Int", take_ownership
    }
    _curl_mime_init(easy_handle) {  ;untested   https://curl.se/libcurl/c/curl_mime_init.html
        /*  use the mime interface in place of the following depreciated functions:
            curl_formadd
            curl_formfree
            curl_formget
        */
        return DllCall(this.curlDLLpath "\curl_mime_init"
            ,   "Int", easy_handle)
    }
    _curl_mime_name(mime_handle,name) { ;untested   https://curl.se/libcurl/c/curl_mime_name.html
        return DllCall(this.curlDLLpath "\curl_mime_name"
            ,   "Int", mime_handle
            ,   "AStr", name)
    }
    _curl_mime_subparts(mime_handle,mime_part) {  ;untested   https://curl.se/libcurl/c/curl_mime_subparts.html
        return DllCall(this.curlDLLpath "\curl_mime_subparts"
            ,   "Int", mime_handle
            ,   "Int", mime_part)
    }
    _curl_mime_type(mime_part,mimetype) {   ;untested   https://curl.se/libcurl/c/curl_mime_type.html
        return DllCall(this.curlDLLpath "\curl_mime_type"
            ,   "Int", mime_part
            ,   "AStr", mimetype)
    }
    _curl_multi_add_handle(multi_handle, easy_handle) { ;untested   https://curl.se/libcurl/c/curl_multi_add_handle.html
        return DllCall(this.curlDLLpath "\curl_multi_add_handle"
            ,   "Ptr", multi_handle
            ,   "Ptr", easy_handle)
    }
    _curl_multi_assign(multi_handle,sockfd,sockptr) {   ;untested   https://curl.se/libcurl/c/curl_multi_assign.html
        return DllCall(this.curlDLLpath "\curl_multi_assign"
            ,   "Int", multi_handle
            ,   "Int", sockfd
            ,   "Ptr". sockptr)
    }
    _curl_multi_cleanup(multi_handle) { ;untested   https://curl.se/libcurl/c/curl_multi_cleanup.html
        return DllCall(this.curlDLLpath "\curl_multi_cleanup"
            ,   "Int", multi_handle)
    }
    _curl_multi_fdset(multi_handle,read_fd_set,write_fd_set,exc_fd_set,max_fd) {    ;untested   https://curl.se/libcurl/c/curl_multi_fdset.html
        return DllCall(this.curlDLLpath "\curl_multi_fdset"
            ,   "Ptr", read_fd_set
            ,   "Ptr", write_fd_set
            ,   "Ptr", exc_fd_set
            ,   "Int", max_fd)
    }
    _curl_multi_get_handles(multi_handle) { ;untested   https://curl.se/libcurl/c/curl_multi_get_handles.html
        return DllCall(this.curlDLLpath "curl_multi_get_handles"
            ,   "Int", multi_handle
            ,   "Ptr")
    }
    _curl_multi_info_read(multi_handle, msgs_in_queue) {    ;untested   https://curl.se/libcurl/c/curl_multi_info_read.html
        return DllCall(this.curlDLLpath "curl_multi_info_read"
            ,   "Int", multi_handle
            ,   "Int", msgs_in_queue
            ,   "Ptr")
    }
    _curl_multi_init() {    ;untested   https://curl.se/libcurl/c/curl_multi_init.html
        return DllCall(this.curlDLLpath "curl_multi_init"
            ,   "Ptr")
    }
    _curl_multi_perform(multi_handle, running_handles) {    ;untested   https://curl.se/libcurl/c/curl_multi_perform.html
        return DllCall(this.curlDLLpath "\curl_multi_add_handle"
            ,   "Int", multi_handle
            ,   "Ptr", running_handles)
    }
    _curl_multi_remove_handle(multi_handle, easy_handle) {   ;untested   https://curl.se/libcurl/c/curl_multi_remove_handle.html
        return DllCall(this.curlDLLpath "\curl_multi_remove_handle"
            ,   "Int", multi_handle
            ,   "Int", easy_handle)
    }
    _curl_multi_setopt(multi_handle, option, parameter) {  ;untested   https://curl.se/libcurl/c/curl_multi_setopt.html
        return DllCall(this.curlDLLpath "_curl_multi_setopt"
            ,   "Int", multi_handle
            ,   "Int", option
            ,   paramType, parameter)   ;TODO - build multi opt map
    }
    _curl_multi_socket_action(multi_handle,sockfd,ev_bitmask,running_handles) {   ;untested   https://curl.se/libcurl/c/curl_multi_socket_action.html
        return DllCall(this.curlDLLpath "\curl_multi_socket_action"
            ,   "Int", multi_handle
            ,   "Int", sockfd
            ,   "Int", ev_bitmask
            ,   "Int", running_handles)
    }
    _curl_multi_strerror(errornum) {    ;untested   https://curl.se/libcurl/c/curl_multi_strerror.html
        return DllCall(this.curlDLLpath "\curl_multi_strerror"
            ,   "Int", errornum
            ,   "Ptr")
    }
    _curl_multi_timeout(multi_handle,timeout) { ;untested   https://curl.se/libcurl/c/curl_multi_timeout.html
        return DllCall(this.curlDLLpath "curl_multi_timeout"
            ,   "Int", multi_handle
            ,   "Int", timeout)
    }
    _curl_multi_poll(multi_handle,extra_fds,extra_nfds,timeout_ms,&numfds) {    ;untested   https://curl.se/libcurl/c/curl_multi_poll.html
        return DllCall(this.curlDLLpath "curl_multi_poll"
            ,   "Ptr", multi_handle
            ,   "Ptr", extra_fds
            ,   "UInt", extra_nfds
            ,   "Int", timeout_ms
            ,   "Ptr", &numfds)
    }
    _curl_multi_wait(multi_handle, extra_fds, extra_nfds, timeout_ms, numfds) {    ;untested   https://curl.se/libcurl/c/curl_multi_wait.html
        return DllCall(this.curlDLLpath "\curl_multi_wait"
            ,   "Ptr", multi_handle
            ,   "Ptr", extra_fds
            ,   "UInt", extra_nfds
            ,   "Int", timeout_ms
            ,   "Ptr", numfds)
    }
    _curl_multi_wakeup(multi_handle) {  ;untested   https://curl.se/libcurl/c/curl_multi_wakeup.html
        return DllCall(this.curlDLLpath "\curl_multi_wakeup"
            ,   "Int", multi_handle)
    }
    _curl_pushheader_byname(headerStruct, name) { ;untested   https://curl.se/libcurl/c/curl_pushheader_byname.html
        return DllCall(this.curlDLLpath "\curl_pushheader_byname"
            ,   "Ptr", headerStruct
            ,   "AStr", name
            ,   "Ptr")
    }
    _curl_pushheader_bynum(headerStruct, num) { ;untested   https://curl.se/libcurl/c/curl_pushheader_bynum.html
        return DllCall(this.curlDLLpath "\curl_pushheader_bynum"
            ,   "Ptr", headerStruct
            ,   "Int", num
            ,   "Ptr")
    }
    _curl_share_cleanup(share_handle) { ;untested   https://curl.se/libcurl/c/curl_share_cleanup.html
        return DllCall(this.curlDLLpath "\curl_share_cleanup"
            ,   "Int", share_handle)
    }
    _curl_share_init() {    ;untested   https://curl.se/libcurl/c/curl_share_init.html
        return DllCall(this.curlDLLpath "\curl_share_init")
    }
    _curl_share_setopt(share_handle,option,parameter) { ;untested   https://curl.se/libcurl/c/curl_share_setopt.html
        return DllCall(this.curlDLLpath "\curl_share_setopt"
            ,   "Int", share_handle
            ,   "Int", option
            ,   paramType, parameter)   ;TODO - build share opt map
    }
    _curl_share_strerror(errornum) {    ;untested   https://curl.se/libcurl/c/curl_share_strerror.html
        return DllCall(this.curlDLLpath "\curl_share_strerror"
            ,   "Int", errornum
            ,   "Ptr")
    }
    _curl_slist_append(ptrSList,strArrayItem) { ;https://curl.se/libcurl/c/curl_slist_append.html
        return DllCall(this.curlDLLpath "\curl_slist_append"
            , "Ptr" , ptrSList
            , "AStr", strArrayItem
            , "Ptr")
    }
    _curl_slist_free_all(ptrSList) {    ;untested   https://curl.se/libcurl/c/curl_slist_free_all.html
        return DllCall(Curl.curlDLLpath "\curl_slist_free_all"
            , "Ptr", ptrSList)
    }
    _curl_url() {   ;untested   https://curl.se/libcurl/c/curl_url.html
        return DllCall(this.curlDLLpath "\curl_url")
    }
    _curl_url_cleanup(url_handle) {   ;untested   https://curl.se/libcurl/c/curl_url_cleanup.html
        return DllCall(this.curlDLLpath "\curl_url_cleanup"
            ,   "Int", url_handle)
    }
    _curl_url_dup(url_handle) { ;untested   https://curl.se/libcurl/c/curl_url_dup.html
        return DllCall(this.curlDLLpath "\curl_url_dup"
            ,   "Int", url_handle)
    }
    _curl_url_get(url_handle,part,content,flags) { ;untested   https://curl.se/libcurl/c/curl_url_get.html
        return DllCall(this.curlDLLpath "\curl_url_get"
            ,   "Int", url_handle
            ,   "Int", part
            ,   "AStr", content
            ,   "UInt", flags)
    }
    _curl_url_set(url_handle,part,content,flags) {   ;untested   https://curl.se/libcurl/c/curl_url_set.html
        return DllCall(this.curlDLLpath "\curl_url_set"
            ,   "Int", url_handle
            ,   "Int", part
            ,   "AStr", content
            ,   "UInt", flags)
    }
    _curl_url_strerror(errornum) {  ;untested   https://curl.se/libcurl/c/curl_url_strerror.html
        return DllCall(this.curlDLLpath "\curl_url_strerror"
            ,   "Int", errornum)
    }
    _curl_version() {   ;https://curl.se/libcurl/c/curl_version.html
        return StrGet(DllCall(this.curlDLLpath "\curl_version"
            ,   "char", 0
            ,   "Ptr")  ;return a ptr from DllCall
            ,   "UTF-8")
    }
    _curl_version_info() {  ;https://curl.se/libcurl/c/curl_version_info.html
        ;returns run-time libcurl version info
        return DllCall(this.curlDLLpath "\curl_version_info"
            ,   "Int", 0xA
            ,   "Ptr")
    }
    _curl_ws_recv(easy_handle,buffer,buflen,&recv,&meta) {   ;untested   https://curl.se/libcurl/c/curl_ws_recv.html
        return DllCall(this.curlDLLpath "\curl_ws_recv"
            ,   "Int", easy_handle
            ,   "Ptr", buffer
            ,   "Int", buflen
            ,   "Int", &recv
            ,   "Ptr", meta)
    }
    _curl_ws_send(easy_handle,buffer,buflen,&sent,fragsize,flags) { ;untested   https://curl.se/libcurl/c/curl_ws_send.html
        return DllCall(this.curlDLLpath "\curl_ws_send"
            ,   "Int", easy_handle
            ,   "Ptr", buffer
            ,   "Int", buflen
            ,   "Int", &sent
            ,   "Int", fragsize
            ,   "UInt", flags)
    }
    _curl_ws_meta(easy_handle) {    ;untested   https://curl.se/libcurl/c/curl_ws_meta.html
        return DllCall(this.curlDLLpath "\curl_version_info"
            , "Int", easy_handle
            , "Ptr")
    }


    StringToBase64(String, Encoding := "UTF-8")
    {
        static CRYPT_STRING_BASE64 := 0x00000001
        static CRYPT_STRING_NOCRLF := 0x40000000

        Binary := Buffer(StrPut(String, Encoding))
        StrPut(String, Binary, Encoding)
        if !(DllCall("crypt32\CryptBinaryToStringW", "Ptr", Binary, "UInt", Binary.Size - 1, "UInt", (CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF), "Ptr", 0, "UInt*", &Size := 0))
            throw OSError()

        Base64 := Buffer(Size << 1, 0)
        if !(DllCall("crypt32\CryptBinaryToStringW", "Ptr", Binary, "UInt", Binary.Size - 1, "UInt", (CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF), "Ptr", Base64, "UInt*", Size))
            throw OSError()

        return StrGet(Base64)
    }
    class _struct {
        walkPtrArray(inPtr) {
            retObj := []
            loop {
                pFeature := NumGet(inPtr + ((A_Index - 1) * A_PtrSize), "Ptr")
                if (pFeature = 0) {
                    break
                }
                retObj.push(StrGet(pFeature, "UTF-8"))
            }
            return retObj
        }
        curl_easyoption(ptr) {
            return Map("name",StrGet(numget(ptr, "Ptr"), "CP0")
                ,   "id", numget(ptr, 8, "UInt")
                ,   "rawCurlType", numget(ptr, 12, "UInt")
                ,   "flags", numget(ptr, 16, "UInt"))
        }
        curl_version_info_data(ptr){
            ;build initial struct map
            retObj := Map()
            retObj["age"] := NumGet(ptr,(Offset := 0),"Int") + 1 ;intentionally +1
            retObj["version"] := StrGet(NumGet(ptr,8,"Ptr"),"UTF-8")
            retObj["version_num"] := NumGet(ptr,16,"UInt")
            retObj["host"] := StrGet(NumGet(ptr,24,"Ptr"),"UTF-8")
            retObj["features"] := NumGet(ptr,32,"UInt")
            retObj["ssl_version"] := StrGet(NumGet(ptr,40,"Ptr"),"UTF-8")
            retObj["ssl_version_num"] := NumGet(ptr,48,"UInt")
            retObj["libz_version"] := StrGet(NumGet(ptr,56,"Ptr"),"UTF-8")
            retObj["protocols"] := this.walkPtrArray(NumGet(ptr,64,"Ptr"))

            ;walk through optional struct members
            If (retObj["age"] >= 2) {
                retObj["ares"] := str(ptr,72)
                retObj["ares"] := NumGet(ptr,80,"Int")
            }
            If (retObj["age"] >= 3) {
                retObj["libidn"] := str(ptr,88)
            }
            If (retObj["age"] >= 4) {
                retObj["iconv_ver_num"] := NumGet(ptr, 96, "Int")
                retObj["libssh_version"] := str(ptr,104)
            }
            If (retObj["age"] >= 5) {
                retObj["brotli_ver_num"] := NumGet(ptr, 112, "Int")
                retObj["brotli_version"] := str(ptr, 120)
            }
            If (retObj["age"] >= 6) {
                retObj["nghttp2_version"] := NumGet(ptr, 128, "UInt")
                retObj["nghttp2"] := str(ptr,136)
                retObj["quic_version"] := str(ptr,144)
            }
            If (retObj["age"] >= 7) {
                retObj["cainfo"] := str(ptr,152)
                retObj["capath"] := str(ptr,160)   
            }
            If (retObj["age"] >= 8) {
                retObj["zstd_ver_num"] := NumGet(ptr,168,"Int")
                retObj["zstd_version"] := str(ptr,176)
            }
            If (retObj["age"] >= 9) {
                retObj["hyper_version"] := str(ptr,184)
            }
            If (retObj["age"] >= 10) {
                retObj["gsasl_version"] := str(ptr,192)
            }
            If (retObj["age"] >= 11) {
                retObj["feature_names"] := this.walkPtrArray(NumGet(ptr,200,"Ptr"))
            }
            return retObj
            str(ptr,offset,encoding := "UTF-8"){
                return (NumGet(ptr,offset,"Ptr")=0?0:StrGet(NumGet(ptr,offset,"Ptr"),encoding))
            }
        }

    }
    _buildOptMap() {    ;creates a reference matrix of all known SETCURLOPTs
        this.Opt.CaseSense := "Off"
        optPtr := 0
        argTypes := Map(0, Map("type", "Int", "easyType", "CURLOT_LONG")
                    ,   1, Map("type", "Int", "easyType", "CURLOT_VALUES")
                    ,   2, Map("type", "Int64", "easyType", "CURLOT_OFF_T")
                    ,   3, Map("type", "Ptr", "easyType", "CURLOT_OBJECT")
                    ,   4, Map("type", "Astr", "easyType", "CURLOT_STRING")
                    ,   5, Map("type", "Ptr", "easyType", "CURLOT_SLIST")
                    ,   6, Map("type", "Ptr", "easyType", "CURLOT_CBPTR")
                    ,   7, Map("type", "Ptr", "easyType", "CURLOT_BLOB")
                    ,   8, Map("type", "Ptr", "easyType", "CURLOT_FUNCTION"))
        
        Loop {
            optPtr := this._curl_easy_option_next(optPtr)
            if (optPtr = 0)
                break
            o := this.struct.curl_easyoption(optPtr)
            /*
                ;types defined in v1 class  *rearranged to follow typedef enum*
                LONG :=     0 + AHK_ARG * 1  ; Long
                BITS := LONG                 ; Long argument with a set of values/bitmask
                OFFT := 30000 + AHK_ARG * 6  ; Curl_off_t (Int64)
                OBJP := 10000 + AHK_ARG * 2  ; Object pointer
                STRP := 10000 + AHK_ARG * 3  ; String pointer
                SLIP := 10000 + AHK_ARG * 4  ; Linked-list pointer
                CBPT := OBJP                 ; Argument pointer passed to callback
                BLOB := 40000 + AHK_ARG * 7  ; Blob struct pointer
                FUNP := 20000 + AHK_ARG * 5  ; Function pointer
            
                {LONG:"Int"
                ,   OBJECTPOINT:"Ptr"
                ,   STRINGPOINT:"Astr"
                ,   FUNCTIONPOINT:"Ptr"
                ,   OFF_T:"Int64"
                ,   BLOB:"Ptr"}
            */

            ; o.type := argTypes[o.rawCurlType].type
            ;     , o.easyType := argTypes[o.rawCurlType].easyType
            ; this.Opt["CURLOPT_" o.name] := this.Opt[o.name] := this.Opt[o.id] := o
            ; static argTypes := { 0: { type: "Int", easyType: "CURLOT_LONG" }
            ;     , 1: { type: "Int", easyType: "CURLOT_VALUES" }
            ;     , 2: { type: "Int64", easyType: "CURLOT_OFF_T" }
            ;     , 3: { type: "Ptr", easyType: "CURLOT_OBJECT" }
            ;     , 4: { type: "Astr", easyType: "CURLOT_STRING" }
            ;     , 5: { type: "Ptr", easyType: "CURLOT_SLIST" }
            ;     , 6: { type: "Ptr", easyType: "CURLOT_CBPTR" }
            ;     , 7: { type: "Ptr", easyType: "CURLOT_BLOB" }
            ;     , 8: { type: "Ptr", easyType: "CURLOT_FUNCTION" } }
            ; o.type := argTypes[o.rawCurlType].type
            ;     , o.easyType := argTypes[o.rawCurlType].easyType
            ; this.Opt["CURLOPT_" o.name] := this.Opt[o.name] := this.Opt[o.id] := o
            o["type"] := argTypes[o["rawCurlType"]]["type"]
                , o["easyType"] := argTypes[o["rawCurlType"]]["easyType"]
            this.Opt["CURLOPT_" o["name"]] := this.Opt[o["name"]] := this.Opt[o["id"]] := o
        }
        ; msgbox this.ShowOB(this.opt)
    }
}
I leaned heavily on @mcl's v1 libcurl wrapping to understand what the DLL was asking of me. A lot of the class layout mirrors his efforts, but I also departed in many important ways to make way for the multi interface.

I've done a fair bit of programming in AHKv1 so I'm not new to the logic of running a library, but I am certainly new to both AHKv2 and DLL bindings. There was a marked learning curve in getting as far as I have. I am open to any and all feedback, particularly with regards to streamlining or otherwise improving class layout. (I am aware there' s still a lot of commented/orphaned code, that is slowly being cleaned up as new features are finalized.)

A massive thanks to the following for their help in the AHK Discord: @geek @Bkid @MrDoge @CloakerSmoker @tidbit
I would not have been able to muddle through this without their significant guidance.

GitHub repo: https://github.com/Qriist/LibQurl
LibCurl download: https://curl.se/download.html
SSL DLLs: https://www.openssl.org/source/
Last edited by Qriist on 25 Mar 2024, 17:22, edited 5 times in total.
cumulonimbus
Posts: 4
Joined: 19 Aug 2023, 13:37

Re: LibQurl (a libcurl wrapper for AHKv2)

11 Mar 2024, 06:15

You can refer to this library:
https://github.com/tmplinshi/libcurl-test


Your calling method is too complicated. My calling method is:
curl({url:"test.com/x.zip", head: {"Accept-Encoding": "br"}, opt: {"user": "xxx"}, file: "buf"})
Qriist
Posts: 82
Joined: 11 Sep 2016, 04:02

Re: LibQurl (a libcurl wrapper for AHKv2)

11 Mar 2024, 07:17

cumulonimbus wrote:
11 Mar 2024, 06:15
You can refer to this library:
https://github.com/tmplinshi/libcurl-test


Your calling method is too complicated. My calling method is:
curl({url:"test.com/x.zip", head: {"Accept-Encoding": "br"}, opt: {"user": "xxx"}, file: "buf"})
Thank you for your feedback.

The library you referred me to follows the same basic steps as I did above:
Spoiler

Your method is perfectly suited for simple tasks. I've used similar methods many times over, myself.

However, LibQurl is intended to be a complete libcurl binding with some QOL that abstracts away a lot of the headache of getting things set up. A lot of the scaffolding you see is done is support of upcoming features that I personally want for implementation in other projects.

Although, I would not be opposed to implementing a "one and done" method for those that prefer it.
Qriist
Posts: 82
Joined: 11 Sep 2016, 04:02

Re: LibQurl (a libcurl wrapper for AHKv2)

11 Mar 2024, 08:30

Added a super basic version of SetOpts() to batch process options.
Qriist
Posts: 82
Joined: 11 Sep 2016, 04:02

Re: LibQurl (a libcurl wrapper for AHKv2)

25 Mar 2024, 17:31

Added ~50 untested DllCalls wrapping every non-depreciated libcurl option.

As far as I am aware, this is the only full libcurl wrap for AHKv2 in existence.

Each untested method is clearly marked as such. Expect these to slowly be stabilized over the next few weeks, with sensible QOL scaffolding being added as required.

I would happily welcome anybody who wants to knock out testing of whatever functions they feel like. (Please show your test code and any changes!)
DocTrinsOGrace
Posts: 7
Joined: 19 Mar 2024, 16:23

Re: LibQurl (a libcurl wrapper for AHKv2)

25 Mar 2024, 20:07

This is cool. I just came across your post. Oddly I had been thinking of using the unix curl, parse what I need with awk, then return the results for further processing by AutoHotKey. However, it didn't get past the "thought bubble stage."

Return to “Scripts and Functions (v2)”

Who is online

Users browsing this forum: fiendhunter, Marium0505 and 90 guests