VBA HTTP GET request - cookies with colons - vba

I am trying to send an HTTP GET request in VBA which includes a cookie containing a colon character, like so:
objReq.Open "GET", "http://my.url.com?foo=bar", False
objReq.setRequestHeader "Cookie", "abcd=cookie:containing:colons"
objReq.Send
Depending on what object type I use for objReq, however the request gets treated differently.
The following object type works:
Dim objReq As MSXML2.ServerXMLHTTP
Set objReq = New MSXML2.ServerXMLHTTP
Unfortunately, I need to use a different object type (as MSXML2.ServerXMLHTTP can't capture sufficient detail about HTTP redirects). From what I've read, I need to use Winhttp.WinHttpRequest, MSXML2.ServerXMLHTTP40, or MSXML2.ServerXMLHTTP60, but using any of those objects results in the following error when including colons in the cookie value.
I have tried replacing the colons with Chr(58), %3A, and double-quoting within the string to no avail. I have also tried adding a 'Content-Type' header with various character encodings, but that doesn't seem to work either.
Anyone know how I can send a cookie value containing colons using the Winhttp.WinHttpRequest, MSXML2.ServerXMLHTTP40, or MSXML2.ServerXMLHTTP60 objects?
PS: Alternatively, if anyone knows how I can get the ending URL of a redirect sequence when using MSXML2.ServerXMLHTTP, that would work as well! Winhttp.WinHttpRequest would allow me to capture a 302 status code, and MSXML2.ServerXMLHTTP40 or MSXML2.ServerXMLHTTP60 would allow me to use GetOption(-1), but MSXML2.ServerXMLHTTP doesn't support either of these methods (from what I can tell).

I did a bit of testing with WinHttpRequest and I came up with the following code:
Dim objReq As WinHttp.WinHttpRequest
Set objReq = New WinHttp.WinHttpRequest
objReq.Option(WinHttpRequestOption_EnableRedirects) = True
objReq.Open "GET", "http://www.example.com", False
objReq.setRequestHeader "Cookie", "abcd=cookie:containing:colons"
objReq.send
I did notice i got the same error that you posted when I forgot to include the "http://" in the url.
I hope this helps!

Related

How to write WebRequest cookies correctly?

I would like to know how to send correctly cookies value of timezone in HTTP request.
Code from response using HTTLiveHeaders in firefox:
Cookie: YSC=FcoGhPJts60; CONSENT=YES+cb.20220329-15-p1.en+FX+439; PREF=tz=Europe.London; GPS=1; VISITOR_INFO1_LIVE=v-FuzDqhJuF
My code:
Dim timeZone As String = "tz=Europe.London"
req.Cookies.Container.Add(New Uri("https://www.website.com"), New Cookie("PREF", timeZone))
because normally after PREF there is also "=" sign, but in HTTP request we don't need to add it, but it have another "=" so I'm not sure if it includes or not.
thanks for help.

Format this GET request in VB.NET code behind?

I am trying a "GET" method to request a count of activities from the Accelo API here:
https://api.accelo.com/docs/?http#count-activities
And although i've used a very similar POST method to successfully get the access token using an authentication method, I cannot for the life of me figure out how to get the count of activities. The API says to use "GET" and past the access token as "bearer..." and I've also tried doing a post and getting the stream first, tried sending in some data and accessing the "list activities" endpoint instead...nothing is working. everything I do returns the error "400. Bad Request."
I've tried passing data in a query string format directly in the URI, and tried passing no data since its a GET function. It looks to me like I'm following the API exactly.
Dim data2 = Nothing ' Encoding.UTF8.GetBytes(jsonstring)
Dim _list = GetListOfActivities(New Uri("https://example.api.accelo.com/api/v0/activities/count.xml"), data2, _AccessToken)
Dim reqa As WebRequest = WebRequest.Create(uri)
' reqa.Method = "GET"
reqa.Headers.Add("GET", "/api/v0/activities/count.xml HTTP/1.1")
reqa.Headers.Add("Authorization", "Bearer " & _AccessToken)
reqa.ContentType = "application/x-www-form-urlencoded"
'reqa.ContentLength = jsonDataBytes.Length
' Dim streama = reqa.GetRequestStream()
' streama.Write(jsonDataBytes, 0, jsonDataBytes.Length)
'streama.Close()
Dim responsea As WebResponse = reqa.GetResponse()
Console.WriteLine((CType(responsea, HttpWebResponse)).StatusDescription)
I must be formatting the request wrong - please help?
My problem turned out to be something stupid, and literally beyond the scope of what I posted here. I had specified a "scope" in my initial request to get the access token that was set to read-only "staff" data (I had copied-and-pasted their example online into my code, for other parameters like grant type, and I brought the scope along with it), and in this scenario here I was trying to access "activities" data and not "staff" data. I would have thought I'd get a permissions-related error, instead of "bad request" which confused me, but anyway it works now.
The above code - actually with this line:
reqa.Method = "GET"
instead of this line:
reqa.Headers.Add("GET", "/api/v0/activities/count.xml HTTP/1.1")
Works just fine since I changed my scope to read(all) in my initial web method getting the access token.

Making a UTF-8 call from VBA

I'm trying to make calls to openstreetmap (specifically to https://nominatim.openstreetmap.org/search?amenity=charging_station&format=json&q=Elly-Beinhorn-Ring+2,12529+Schönefeld
) and from any webbrowser it works fine. When calling via Excel VBA is complains that the string is not UTF-8. I haven't used fiddler yet but to me it is clear that srequest should be in UTF-8 format. I found another solution to convert the url to ascii but I'd prefer to have it in utf-8 here's some sample code which creates the error:
Dim httpObject As Object
Set httpObject = CreateObject("MSXML2.XMLHTTP")
srequest = "https://nominatim.openstreetmap.org/search?amenity=charging_station&format=json&q=Elly-Beinhorn-Ring+2,12529+Schönefeld"
httpObject.Open "GET", srequest, False
httpObject.setRequestHeader "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"
httpObject.send
sgetResult = httpObject.responsetext
Debug.Print sgetResult
If anybody can help how to force MSXML2 to make an utf-8 call, i'd be happy. at the moment my solution is to replace German Umlauts (ä,ü,ö) with (ae,ue,oe) which works but is not what I want. Other solutions like URLEncode() have created more chaos by also converting + and , to values starting with %.
I could solve my problem by replacing this line:
Set httpObject = CreateObject("MSXML2.XMLHTTP")
with this one:
Set httpObject = CreateObject("MSXML2.ServerXMLHTTP")
Forcing MSXML2 to utf-8 is not the right way to go. All browsers convert to ASCII before sending the request. Easiest way to see this is in the Network tab in your browser Dev tools. Chrome translates your URL to
https://nominatim.openstreetmap.org/search?amenity=charging_station&format=json&q=Elly-Beinhorn-Ring+2,12529+Sch%C3%B6nefeld
So the easy way is to encode ö to %C3%B6 (as described here: https://www.fileformat.info/info/unicode/char/00f6/index.htm) and the other characters in a similar way. There are a lot of encoding methods available out there. Have a look

VBA http get request response is gibberish

I tried to search for a quick answer to this but I did not see a response...so I apologize if this is redundant.
I am pretty new to VBA and only use it to pull data from APIs to make my life easier.
My question is about the response im getting from a particular API. HEre is the code im using:
Dim URL As String: URL = "API URL HERE in json format"
Dim Http As New WinHttpRequest
Dim Resp As String
Http.Open "GET", URL, False
Http.Send
Resp = Http.ResponseText
Debug.Print Resp
So the Resp text is complete gibberish...is this a security thing? I pass a security key successfully when I use it in the browser so I assumed it was not that.... I have used this exact method on numerous APIs but this is the first time I have seen this garbled response. FYI the url I am using works just fine in a browser.
The Resp looks like this for example:
gõi:?ñq¢²^2?7AÄ??æºz³Gs=ΠÜ?¬«¤%?$ÖÉ'q¯¼|?¼²ôue¦½Þ"HË!ø5[4]s½?·Þ.OÛÃBh×?4"rÊÊ[r7
Thanks for any help!
Khauna

401 Status Returned on Access Token Errors

While a 401 Unauthorized may seem spiffy for these ("Access token is missing or invalid") it can throw many a client HTTP stack into prompting the user for credentials, something that won't succeed anyway since normal HTTP authentication mechanisms are not in play.
While I can detour that using another client library that I can direct not to attempt auto-auth or user prompting (and have done so) this seems to violate RFC 7235 as far as I can tell.
I suspect that a 403 Forbidden would be more compliant here and less grief for API users. Most of them probably just see any non-2XX status and immediately run to look for a JSON "error" reponse body.
I have a detour so I'm not complaining, but something seems fishy here. Surely I'm missing something? Is it common practice now to use the 401 in this manner for REST-like HTTP APIs?
More detail
This works as long as the proper auth token is used, but causes a GUI prompt for user/pw if a bad token is used:
Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a deep copy of template JSON.
With JsonBag
.Item("title") = txtTitle.Text
.Item("body") = txtBody.Text
End With
With XMLHTTP
.abort 'Clean up previously failed request if any.
.open "POST", PBConfig.Item("CreatePushUrl"), True
.setRequestHeader "Access-Token", PBConfig.Item("AccessToken")
.setRequestHeader "Content-Type", "application/json"
.onreadystatechange = SinkRSChange
.send JsonBag.JSON
End With
If the prompt is canceled by the user then the 401 gets reported to the code.
In light of information below I tried sending the auth token as a user ID value. However this raises a prompt even if the auth token is correct:
Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a deep copy of template JSON.
With JsonBag
.Item("title") = txtTitle.Text
.Item("body") = txtBody.Text
End With
With XMLHTTP
.abort 'Clean up previously failed request if any.
.open "POST", PBConfig.Item("CreatePushUrl"), True, PBConfig.Item("AccessToken")
.setRequestHeader "Content-Type", "application/json"
.onreadystatechange = SinkRSChange
.send JsonBag.JSON
End With
If the user manually enters the valid auth token into the prompt as the user ID the request then succeeds.
Based on new information below
This can be made to work by explictly sending a "." as password:
Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a deep copy of template JSON.
With JsonBag
.Item("title") = txtTitle.Text
.Item("body") = txtBody.Text
End With
With XMLHTTP
.abort 'Clean up previously failed request if any.
.open "POST", PBConfig.Item("CreatePushUrl"), True, PBConfig.Item("AccessToken"), "."
.setRequestHeader "Content-Type", "application/json"
.onreadystatechange = SinkRSChange
.send JsonBag.JSON
End With
Correct token value works, bad token value returns the 401 where it can be handled. No credentials prompt dialogs now.
Normal HTTP authentication mechanisms are technically in play. The api even asks your browser for credentials so you can do requests in your browser (someone actually requested that).
HTTP libraries that have special behavior for 401s do seem to be a problem, but the one time it happened I was been able to disable the magic 401 handling. I have no idea who is in violation of RFC 7235 here. RFC 2616 10.4.2 seems to indicate that the current behavior is "correct". Do you have a list of HTTP clients that prompt the user for credentials?
Maybe a 403 makes more sense here, but Stripe at least seems to use a 401: https://stripe.com/docs/api#errors and they are all about the REST. Switching to a 403 would break all existing clients as well. Most clients actually don't look at the JSON body oddly enough, they just look at the status code.
I think if I make another HTTP API it will have only 200/400/500 status codes with POST of JSON encoded bodies and JSON responses.
Alternative:
If support for downlevel versions of Windows is not required you can use the WinHttp.WinHttpRequest object as a replacement for the MSXML2.XMLHTTP object used in the question examples above.
Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a copy.
JsonBag("title") = txtTitle.Text
JsonBag("body") = txtBody.Text
With WinHttp
.Abort 'Clean up previously failed request if any.
.Open "POST", PBConfig("CreatePushUrl"), True
.SetAutoLogonPolicy AutoLogonPolicy_Never
.SetRequestHeader "Access-Token", PBConfig("AccessToken")
.SetRequestHeader "Content-Type", "application/json"
.Send JsonBag.JSON
End With
The key to this is .SetAutoLogonPolicy AutoLogonPolicy_Never which we don't have available with the older class.
Note that this example makes use of the fact that a JsonBag has .Item() as its default property... just in case you were wondering about that difference in this code snippet compared to previous ones. It has no bearing on the use of WinHttp and could have been written this way in the earlier snippets as well.