WELL, first there is a mea culpa. I was mostly wondering if there was a glaring error in the way I was using the WinHTTP COM object, and it appears that I created a few, in "simplifying" the code so that we only had to look at what I was doing with WinHTTP! I will simply aver that essentially, I do either pass variables or use the ""s in an appropriate manner. And have otherwise tried every version possible of passing the variable or quoting the string or what have you haha in the process of trying to root out my mistake.
So, twitter, rather infuriatingly, lays it out this way in the developer docs:
Here is my whole script, what a beast, closely hewing to the Twitter instructions:
Code: Select all
^+a::
WinHTTP := ComObjCreate("WinHTTP.WinHttpRequest.5.1")
;used by HexTo64 function
StringCaseSense On
Chars = ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
;used to generate oauth_nonce, a random one time identifier, used for OAuth. I generate a string of 32 numbers and convert it to base 64 before stripping non-word characters
;Twitter states "any approach which produces a relatively random alphanumeric string should be OK here."
randomNumber := Return32()
oauth_nonce := Base64(randomNumber)
oauth_nonce := StrReplace(oauth_nonce, "=", "")
oauth_nonce := StrReplace(oauth_nonce, "/", "")
oauth_nonce := StrReplace(oauth_nonce, "+", "")
;generated by twitter in App settings, used to identify my specific account. This is not my real token. Used for OAuth.
;I am just trying to post to my own account. Baby steps.
oauth_token = THISISASECRETUSEDTOIDENTIFYMYTWITTERACCOUNT
;Current date in UNIX seconds. Used for OAuth. The -8*3600 is to correct for my current time zone. Used for OAuth.
oauth_timestamp := 31536000*(A_YYYY-1970) + (A_Yday+Floor((A_YYYY-1972)/4))*86400 + A_Hour*3600 + A_Min*60 + A_Sec - 8*3600
;generated by twitter in App settings. identifies my app. Used for OAuth. It is okay for me to share this!
oauth_consumer_key = 2dR18FCdUY302FeodocTzZqgc
;default. Used for OAuth. Per Twitter.
oauth_signature_method = HMAC-SHA1
;default. Used for OAuth. Per Twitter.
oauth_version = 1.0
;used to encode the request and build oauth_signature
;generated by Twitter in App Settings. Neither are my real secrets.
consumer_secret = THISSECRETAUTHENTICATESMYAPP
oauth_token_secret = THISSECRETAUTHENTICATESMYOWNTWITTERACCOUNT
;the two secrets linked together according to Twitter's formatting.
signing_key = %consumer_secret%&%oauth_token_secret%
;used in the signature base string which is ultimately hashed with the signing_key.
status = Just trying to use an app, folks!
encodedStatus := PercentEncode(status)
;used in signature base string. Built according to Twitter's & OAuth specifications.
;Twitter throws a different error (bad request) if this is malformed, but I have weeded those issues out and now consistently get "unable to auhtenticate"
parameterString = include_entities`=true&oauth_consumer_key`=%oauth_consumer_key%&oauth_nonce`=%oauth_nonce%&oauth_signature_method`=%oauth_signature_method%&oauth_timestamp`=%oauth_timestamp%&oauth_token`=%oauth_token%&oauth_version`=%oauth_version%&status`=%encodedStatus%
parameterString := PercentEncode(parameterString)
;takes the base URL for the request and uses the proper Percent Encoding. Used in signature base string. See comment above on malformed vs. unable to authenticate.
url = https://api.twitter.com/1.1/statuses/update.json
urlEncode := PercentEncode(url)
;combines several above strings to use in hashing. Built according to Twitter's & Oauth specifications.
signature_base_string = POST&%urlEncode%&%parameterString%
;creates oauth_signature
;I have tested both HMAC and HexToBase64 functions using the example strings in Twitter's documentation, and the result is a properly formed signature. I have also verified strings generated by myself using other online "we'll hash this for you" services.
oauth_signature := HMAC(signing_key, signature_base_string, "SHA")
oauth_signature := HexToBase64(oauth_signature)
oauth_signature := PercentEncode(oauth_signature)
;Authorization command. DST is built according to Twitter's & OAuth specifications. My format matches Twitter's to a T, excepting my own values of course.
;One thing that IS infuriating is that Twitter uses a rather idiosyncratic set of excepted characters for percent encoding. And there is at least one part of their documentation where they say "now percent encode this" but then when they show a sample of the correct work it appears they only meant a specific portion of it.
DST = OAuth oauth_consumer_key`="%oauth_consumer_key%", oauth_nonce`="%oauth_nonce%", oauth_signature`="%oauth_signature%", oauth_signature_method`="%oauth_signature_method%", oauth_timestamp`="%oauth_timestamp%", oauth_token`="%oauth_token%", oauth_version`="%oauth_version%"
;To pass with the SEND command
encodedStatus = status`=%encodedStatus%
WinHTTP.Open("POST", "https://api.twitter.com/1.1/statuses/update.json", 0)
WinHTTP.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
WinHTTP.SetRequestHeader("Authorization", DST)
WinHTTP.Send(encodedStatus)
Result := WinHTTP.ResponseText
MsgBox %result%
return
;Graciously lifted from these forums. The core work, is I believe, from Laszlo back all the way to '12
;But this is slightly different and I cannot find where it was originally lifted from.
;See comments above, I have tested this against examples and other services, it works properly.
Base64(string, key:="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") ;
{
StringCaseSense On
Loop, Parse, string
{
index := Mod(A_Index, 3)
if (index = 1)
{
base64Index := ((Asc(A_LoopField) >> 2) & 0x3F)
lastBin := (Asc(A_LoopField) & 0x03 ) << 4
}
else if (index = 2)
{
base64Index := lastBin | ((Asc(A_LoopField) >> 4) & 0x0F)
lastBin := (Asc(A_LoopField) & 0x0F) << 2
}
else
{
base64Index := lastBin | ((Asc(A_LoopField) >> 6) & 0x03)
StringMid, base64Char, key, base64Index + 1, 1
code := code base64Char
base64Index := (Asc(A_LoopField)) & 0x3F
}
StringMid, base64Char, key, base64Index + 1, 1
code := code base64Char
}
if (index = 1)
{
StringMid, base64Char, key, lastBin + 1, 1
return code base64Char "=="
}
else if (index = 2)
{
StringMid, base64Char, key, lastBin + 1, 1
return code base64Char "="
}
else
{
return code "=="
}
}
;used in the generation of OAuth_nonce
Return32()
{
randomNumber =
Loop, 32
{
Random, holder, 0, 9
randomNumber = %randomNumber%%holder%
}
return randomNumber
}
;Basic characters to watch out for in percent encoding. The lines that have been commented out are twitter's excepted characters.
;The % is placed first in the function as there appears to be only one instance, in the final step of the signature_base_string, where the % needs to be itself encoded, and it only affects the "status".
PercentEncode(string)
{
string := StrReplace(string, "%","%25")
string := StrReplace(string, """","%22")
;string := StrReplace(string, "-","%2D")
;string := StrReplace(string, ".","%2E")
string := StrReplace(string, "<","%3C")
string := StrReplace(string, ">","%3E")
string := StrReplace(string, "`\","%5C")
string := StrReplace(string, "^","%5E")
;string := StrReplace(string, "_","%5F")
string := StrReplace(string, "``","%60")
string := StrReplace(string, "`{","%7B")
string := StrReplace(string, "|","%7C")
string := StrReplace(string, "`}","%7D")
;string := StrReplace(string, "`~","%7E")
string := StrReplace(string, "!","%21")
string := StrReplace(string, "#","%23")
string := StrReplace(string, "$","%24")
string := StrReplace(string, "&","%26")
string := StrReplace(string, "'","%27")
string := StrReplace(string, "`(","%28")
string := StrReplace(string, "`)","%29")
string := StrReplace(string, "*","%2A")
string := StrReplace(string, "+","%2B")
string := StrReplace(string, "`,","%2C")
string := StrReplace(string, "`/","%2F")
string := StrReplace(string, ":","%3A")
string := StrReplace(string, "`;","%3B")
string := StrReplace(string, "=","%3D")
string := StrReplace(string, "?","%3F")
string := StrReplace(string, "@","%40")
string := StrReplace(string, "`[","%5B")
string := StrReplace(string, "`]","%2D")
string := StrReplace(string, " ","%20")
return string
}
;Lifted from a script created by jNizM on github. https://github.com/jNizM/HashCalc
;Tested against Twitter's sample signatures and keys, as well as other online services, it works properly.
HMAC(Key, Message, Algo := "MD5")
{
static Algorithms := {MD2: {ID: 0x8001, Size: 64}
, MD4: {ID: 0x8002, Size: 64}
, MD5: {ID: 0x8003, Size: 64}
, SHA: {ID: 0x8004, Size: 64}
, SHA256: {ID: 0x800C, Size: 64}
, SHA384: {ID: 0x800D, Size: 128}
, SHA512: {ID: 0x800E, Size: 128}}
static iconst := 0x36
static oconst := 0x5C
if (!(Algorithms.HasKey(Algo)))
{
return ""
}
Hash := KeyHashLen := InnerHashLen := ""
HashLen := 0
AlgID := Algorithms[Algo].ID
BlockSize := Algorithms[Algo].Size
MsgLen := StrPut(Message, "UTF-8") - 1
KeyLen := StrPut(Key, "UTF-8") - 1
VarSetCapacity(K, KeyLen + 1, 0)
StrPut(Key, &K, KeyLen, "UTF-8")
if (KeyLen > BlockSize)
{
CalcAddrHash(&K, KeyLen, AlgID, KeyHash, KeyHashLen)
}
VarSetCapacity(ipad, BlockSize + MsgLen, iconst)
Addr := KeyLen > BlockSize ? &KeyHash : &K
Length := KeyLen > BlockSize ? KeyHashLen : KeyLen
i := 0
while (i < Length)
{
NumPut(NumGet(Addr + 0, i, "UChar") ^ iconst, ipad, i, "UChar")
i++
}
if (MsgLen)
{
StrPut(Message, &ipad + BlockSize, MsgLen, "UTF-8")
}
CalcAddrHash(&ipad, BlockSize + MsgLen, AlgID, InnerHash, InnerHashLen)
VarSetCapacity(opad, BlockSize + InnerHashLen, oconst)
Addr := KeyLen > BlockSize ? &KeyHash : &K
Length := KeyLen > BlockSize ? KeyHashLen : KeyLen
i := 0
while (i < Length)
{
NumPut(NumGet(Addr + 0, i, "UChar") ^ oconst, opad, i, "UChar")
i++
}
Addr := &opad + BlockSize
i := 0
while (i < InnerHashLen)
{
NumPut(NumGet(InnerHash, i, "UChar"), Addr + i, 0, "UChar")
i++
}
return CalcAddrHash(&opad, BlockSize + InnerHashLen, AlgID)
}
;Used by HMAC
CalcAddrHash(addr, length, algid, byref hash = 0, byref hashlength = 0)
{
static h := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f"]
static b := h.minIndex()
hProv := hHash := o := ""
if (DllCall("advapi32\CryptAcquireContext", "Ptr*", hProv, "Ptr", 0, "Ptr", 0, "UInt", 24, "UInt", 0xf0000000))
{
if (DllCall("advapi32\CryptCreateHash", "Ptr", hProv, "UInt", algid, "UInt", 0, "UInt", 0, "Ptr*", hHash))
{
if (DllCall("advapi32\CryptHashData", "Ptr", hHash, "Ptr", addr, "UInt", length, "UInt", 0))
{
if (DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", 0, "UInt*", hashlength, "UInt", 0))
{
VarSetCapacity(hash, hashlength, 0)
if (DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", &hash, "UInt*", hashlength, "UInt", 0))
{
loop % hashlength
{
v := NumGet(hash, A_Index - 1, "UChar")
o .= h[(v >> 4) + b] h[(v & 0xf) + b]
}
}
}
}
DllCall("advapi32\CryptDestroyHash", "Ptr", hHash)
}
DllCall("advapi32\CryptReleaseContext", "Ptr", hProv, "UInt", 0)
}
return o
}
;also courtesy of these forums, Laszlo, 2012
HextoBase64(hex)
{
Loop Parse, hex
{
m := Mod(A_Index,3)
x = 0x%A_loopfield%
IfEqual m,1, SetEnv z, % x << 8
Else IfEqual m,2, EnvAdd z, % x << 4
Else {
z += x
o := o Code(z>>6) code(z)
}
}
IfEqual m,2, Return o Code(z>>6) Code(z) "=="
IfEqual m,1, Return o Code(z>>6) "="
Return o
}
;used by the Base64 function
Code(i) { ; <== Chars[i & 63], 0-base index
Global Chars
StringMid i, Chars, (i&63)+1, 1
Return i
}