Here are a couple of VB6 functions that can be used to upload a zip file through an http post request.
Using ready-made Microsoft.XMLHTTP or WinHttp.WinHttpRequest.5.1
First the easy one, using XMLHTTP to do the actual work
Private Function pvPostFile(sUrl As String, sFileName As String, Optional ByVal bAsync As Boolean) As String Const STR_BOUNDARY As String = "3fbd04f5-b1ed-4060-99b9-fca7ff59c113" Dim nFile As Integer Dim baBuffer() As Byte Dim sPostData As String '--- read file nFile = FreeFile Open sFileName For Binary Access Read As nFile If LOF(nFile) > 0 Then ReDim baBuffer(0 To LOF(nFile) - 1) As Byte Get nFile, , baBuffer sPostData = StrConv(baBuffer, vbUnicode) End If Close nFile '--- prepare body sPostData = "--" & STR_BOUNDARY & vbCrLf & _ "Content-Disposition: form-data; name=""uploadfile""; filename=""" & Mid$(sFileName, InStrRev(sFileName, "\") + 1) & """" & vbCrLf & _ "Content-Type: application/octet-stream" & vbCrLf & vbCrLf & _ sPostData & vbCrLf & _ "--" & STR_BOUNDARY & "--" '--- post With CreateObject("Microsoft.XMLHTTP") .Open "POST", sUrl, bAsync .SetRequestHeader "Content-Type", "multipart/form-data; boundary=" & STR_BOUNDARY .Send pvToByteArray(sPostData) If Not bAsync Then pvPostFile = .ResponseText End If End With End Function Private Function pvToByteArray(sText As String) As Byte() pvToByteArray = StrConv(sText, vbFromUnicode) End Function
The biggest benefit of using XMLHTTP is the async option. MSXML spawns a worker thread that sends the request even if the last reference to the object is set to nothing.
Instead of Microsoft.XMLHTTP one can use WinHttp.WinHttpRequest.5.1 but setting the last object reference to nothing cancels the async request. Edit: Not true! It turned out Microsoft.XMLHTTP seems to get corrupted more often in the wild due to bad installers. We are now using WinHttp.WinHttpRequest.5.1 exclusively for our uploads.
Another caveat is the pvToByteArray
function. It turns out send method can not handle “byref” byte arrays, so for instance passing baBuffer
will fail, as VB6 sets up VT_BYREF
bit of the type of the variant parameter.
Using wininet.dll API
Here is the hard-core API version. Biggest drawback is that it’s syncronous by nature.
Private Const INTERNET_AUTODIAL_FORCE_ONLINE As Long = 1 Private Const INTERNET_OPEN_TYPE_PRECONFIG As Long = 0 Private Const INTERNET_DEFAULT_HTTP_PORT As Long = 80 Private Const INTERNET_SERVICE_HTTP As Long = 3 Private Const INTERNET_FLAG_RELOAD As Long = &H80000000 Private Const HTTP_ADDREQ_FLAG_REPLACE As Long = &H80000000 Private Const HTTP_ADDREQ_FLAG_ADD As Long = &H20000000 Private Declare Function InternetAutodial Lib "wininet.dll" (ByVal dwFlags As Long, ByVal dwReserved As Long) As Long Private Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" (ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, ByVal sProxyBypass As String, ByVal lFlags As Long) As Long Private Declare Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" (ByVal hInternetSession As Long, ByVal sServerName As String, ByVal nServerPort As Integer, ByVal sUsername As String, ByVal sPassword As String, ByVal lService As Long, ByVal lFlags As Long, ByVal lContext As Long) As Long Private Declare Function HttpOpenRequest Lib "wininet.dll" Alias "HttpOpenRequestA" (ByVal hHttpSession As Long, ByVal sVerb As String, ByVal sObjectName As String, ByVal sVersion As String, ByVal sReferer As String, ByVal something As Long, ByVal lFlags As Long, ByVal lContext As Long) As Long Private Declare Function HttpAddRequestHeaders Lib "wininet.dll" Alias "HttpAddRequestHeadersA" (ByVal hHttpRequest As Long, ByVal sHeaders As String, ByVal lHeadersLength As Long, ByVal lModifiers As Long) As Long Private Declare Function HttpSendRequest Lib "wininet.dll" Alias "HttpSendRequestA" (ByVal hHttpRequest As Long, ByVal sHeaders As String, ByVal lHeadersLength As Long, ByVal sOptional As String, ByVal lOptionalLength As Long) As Long Private Declare Function InternetCloseHandle Lib "wininet.dll" (ByVal hInet As Long) As Long Private Function pvPostFile(sUrl As String, sFileName As String) As Boolean Const STR_APP_NAME As String = "Uploader" Dim hOpen As Long Dim hConnection As Long Dim hRequest As Long Dim sHeader As String Dim sBoundary As String Dim nFile As Integer Dim baData() As Byte Dim sPostData As String Dim sHttpServer As String Dim lHttpPort As Long Dim sUploadPage As String '--- read file nFile = FreeFile Open sFileName For Binary Access Read As nFile If LOF(nFile) > 0 Then ReDim baData(0 To LOF(nFile) - 1) As Byte Get nFile, , baData sPostData = StrConv(baData, vbUnicode) End If Close nFile '--- parse url sHttpServer = sUrl If InStr(sHttpServer, "://") > 0 Then sHttpServer = Mid$(sHttpServer, InStr(sHttpServer, "://") + 3) End If If InStr(sHttpServer, "/") > 0 Then sUploadPage = Mid$(sHttpServer, InStr(sHttpServer, "/")) sHttpServer = Left$(sHttpServer, InStr(sHttpServer, "/") - 1) End If If InStr(sHttpServer, ":") > 0 Then On Error Resume Next lHttpPort = CLng(Mid$(sHttpServer, InStr(sHttpServer, ":") + 1)) On Error GoTo 0 sHttpServer = Left$(sHttpServer, InStr(sHttpServer, ":") - 1) End If '--- prepare request If InternetAutodial(INTERNET_AUTODIAL_FORCE_ONLINE, 0) = 0 Then GoTo QH End If hOpen = InternetOpen(STR_APP_NAME, INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0) If hOpen = 0 Then GoTo QH End If hConnection = InternetConnect(hOpen, sHttpServer, IIf(lHttpPort <> 0, lHttpPort, INTERNET_DEFAULT_HTTP_PORT), vbNullString, vbNullString, INTERNET_SERVICE_HTTP, 0, 0) If hConnection = 0 Then GoTo QH End If hRequest = HttpOpenRequest(hConnection, "POST", sUploadPage, "HTTP/1.0", vbNullString, 0, INTERNET_FLAG_RELOAD, 0) If hRequest = 0 Then GoTo QH End If '--- prepare headers sBoundary = "3fbd04f5-b1ed-4060-99b9-fca7ff59c113" sHeader = "Content-Type: multipart/form-data; boundary=" & sBoundary & vbCrLf If HttpAddRequestHeaders(hRequest, sHeader, Len(sHeader), HTTP_ADDREQ_FLAG_REPLACE Or HTTP_ADDREQ_FLAG_ADD) = 0 Then GoTo QH End If '--- post data sPostData = "--" & sBoundary & vbCrLf & _ "Content-Disposition: multipart/form-data; name=""uploadfile""; filename=""" & Mid$(sFileName, InStrRev(sFileName, "\") + 1) & """" & vbCrLf & _ "Content-Type: application/octet-stream" & vbCrLf & vbCrLf & _ sPostData & vbCrLf & _ "--" & sBoundary & "--" If HttpSendRequest(hRequest, vbNullString, 0, sPostData, Len(sPostData)) = 0 Then GoTo QH End If '--- success pvPostFile = True QH: If hRequest <> 0 Then Call InternetCloseHandle(hRequest) End If If hConnection <> 0 Then Call InternetCloseHandle(hConnection) End If If hOpen <> 0 Then Call InternetCloseHandle(hOpen) End If End Function
It’s not hard adding authentication to send request but I don’t need it so never implemented it. The binary nature ot the upload is set by Content-Type
being application/octet-stream
.
Note that you cannot use Content-Transfer-Encoding with http, it’s reserved for e-mail only, i.e. no base64 encoding is necessary. Binary file is sent directly in the http stream. Note: feel free to change the boundary (use uuidgen.exe to generate a guid or anything unique).
The server-side script
Here is a simple upload_errors.php
script that can be used as a target of the post request
<?php $base_dir = dirname( __FILE__ ) . '/../ErrorsUpload/' . $_GET["id"]; if(!is_dir($base_dir)) mkdir($base_dir, 0777); move_uploaded_file($_FILES["uploadfile"]["tmp_name"], $base_dir . '/' . $_FILES["uploadfile"]["name"]); ?>
Basicly it expects an id param in the url and an uploadfile param in the body. Id is used to create a sub-directory in an off-site (publicly not visible) directory ErrorsUpload
where the uploaded zip file is stored.
Here as the caller code I’m using that accesses the above upload_errors.php
Private Sub Command1_Click() pvPostFile "http://{{your_server_here}}/upload_errors.php?id={A0AD2346-9849-4EF0-9A93-ACFE17910734}", "C:\TEMP\Errors_2011_07_11.zip" End Sub
I’m preparing a zip file with the errors that are logged at the client site, then I’m using the client id in the url, posting the zip file.
Edit: JScript implementation
Turns out byte-array to string handling is not so straight-forward in JScript. Here is a sample implementation of postFile
function extensively using ADODB.Stream to handle conversions:
function readBinaryFile(fileName) { var stream = WScript.CreateObject("ADODB.Stream"); stream.Type = 1; stream.Open(); stream.LoadFromFile(fileName); return stream.Read(); } function toArray(str) { var stream = WScript.CreateObject("ADODB.Stream"); stream.Type = 2; stream.Charset = "_autodetect"; stream.Open() stream.WriteText(str); stream.Position = 0; stream.Type = 1; return stream.Read(); } function postFile(url, fileName, async) { var STR_BOUNDARY = "3fbd04f5-b1ed-4060-99b9-fca7ff59c113"; // prepare post data var stream = WScript.CreateObject("ADODB.Stream"); stream.Type = 1; stream.Open() stream.Write(toArray("--" + STR_BOUNDARY + "\r\n" + "Content-Disposition: form-data; name=\"uploadfile\"; filename=\"" + fileName.substr(fileName.lastIndexOf("\\") + 1, fileName.length) + "\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n")); stream.Write(readBinaryFile(fileName)); stream.Write(toArray("\r\n--" + STR_BOUNDARY + "--")); stream.Position = 0; // post request var xhr = WScript.CreateObject("Microsoft.XMLHTTP"); xhr.Open("POST", url, async); xhr.SetRequestHeader("Content-Type", "multipart/form-data; boundary=" + STR_BOUNDARY); xhr.Send(stream.Read()); if (async) WScript.Sleep(1); }
Hello, I am trying to use winhttp from jscript and of course i am having trouble with the actual POST data. In this case you opened the file as binary, then converted it to UTF8 and then back to byte array.
In my case since jscript only supports variants i am trying to do the same but without much success, using adodb.stream. What i fail to understand so far is exactly what kind of encoding does the send data needs. Do you know this? Thanks, Mario
@Mario Simoes: Hi, just updated the post to include a sample JScript implementation. You can check it out for more ideas.
Hi,
Just came here to say that you are a fucking wizard works perfectly!
Used the wininet.dll method to upload Excel Sheets from the User to the Server.
Never tough that HTTP upload via VBA was possible.
This is amazing, works perfectly! After many hours of research and toying with the code, the solution I came up with was slowly becoming your solution above, and I used your solution to fill in the missing pieces. In the end, I just copied your solution over, and it is exactly what I need. Thanks!
Very good my friend!
This was very helpful for me and works great for me!
Hi I have a problem i want to replicate a multi part form post in Vba excel where i am sending a form data as http post apart from attachment can you tell me how to use this solution to cater to that problem ?
Thanks you so much for this solution. I have a small question, how do i capture response returned after HTTP POST is completed? Basically i’m looking to see if i can tell the user if the upload is success or failure.
@messed-up: The result is in `ResponseText` property after synchronous `Send`. Just updated the sample `pvPostFile` function to return server response if called synchronously.
Thanks, will do that.
With CreateObject(“Microsoft.XMLHTTP”)
.Open “POST”, sUrl, False
.SetRequestHeader “Content-Type”, “multipart/form-data; boundary=” & STR_BOUNDARY
.Send pvToByteArray(sPostData)
pvPostFile = .ResponseText
End With
So, this is the modified part. But still the ‘pvPostFile’ is null/empty. Any idea?
Must be something server-side. Try posting to google.com and check if there is a response text just to be sure the function is implemented correctly.
I’ve updated the sample code above, note that `ResponseText` is available only upon synchronous execution.
Yes, you were right. The server was not returning anything.
this work in windows8 64 bits?
Sure, my development machine is Win 8.1 x64 so yes, it’s working on WOW64.
If you have troubles with x64 VBA share your experience here.
Hi,
I have managed to implement your code ‘as is’ and it works on my server – thanks!
In addition I’d like to pass binary files to a another site that requires user/password as well as a couple of other fields. How do I submit those parameters? I have tried as part of the URL but that seems to corrupt the password – at least that’s what the error message says, though I’m not 100% sure I can trust that. I’ve also tried as a plaintext part of the multi-part form – again, to no avail.
I may have an embarrassing gap in my learning and that is, in your example code, how are the various uses of ‘upload’ (in server-side PHP, in client VBA) linked? Equally, how does the server know to properly name the uploaded file?
Yours,
Puzzled
I’ve solved my problems.
The main one was the various erroneous tries I’d made to avoid corrupting the binary file I was sending. In actuality the difficulty was a downstream bug; the site owner had allowed a hack for a particular file-type to become general.
The advertised code now works for me in multiple scenarios.
In addition, the ability to fill plaintext POST variables can be handled like this in the XMLHTTP setting:
Yes, the code seems to be ok as username/password should be text/plain encoding — no international characters allowed.
FYI, HTTP protocol for multipart/form-data content type forms is described at end of this reference documentation:
http://www.w3.org/TR/html4/interact/forms.html
Yes, I got there when I thought my problems were solely at my end.
I do confess though that I was unable to make the multi-file example at the bottom of that page work – and also understand the reason for the two boundaries (unless that was exampling the ability to change). I presume that the multipart section can be nested but I’m now getting back to exploiting my upload code rather than creating it.
BTW, I note in the code I posted, double hyphens (without a separating space) have been transformed into an n-dash (?).
Lastly – nice fern!
How to post multiple files along with some form data?
Hi! Nice code it works!!
But in server side I can’t receive the file with that function “$input = print_r($_FILES, true);”
only with that one, because the code converts my file in binary:
$i = strpos($input, “\r\n\r\n”);
if ($i !== false)
$input = substr($input, $i + 4);
file_put_contents(‘resultado.zip’, $input);
I just wanna send zip files, dont wanna convert in binary. any solution?
Just use `move_uploaded_file` function as shown in the code above in section “The server-side script”. Your binary zip file is already stored to a temp location by PHP for your convenience. You just have to move/rename it with `move_uploaded_file` function to `resultado.zip`. No need to fiddle with `$_FILES` contents, IMO.
Thanks for the fast awnser.
Something like that?
move_uploaded_file($_FILES[“resultado.zip”][“resultado.zip”], $base_dir . ‘/’ . $_FILES[“resultado.zip”][“resultado.zip”]);
Did you try the original code as is? It will use the original filename on the client machine which PHP puts in
$_FILES["uploadfile"]["name"]
— this gets to be the filename you passed as argument to VB’s `pvPostFile` function.If you really want to use a different (hard-coded) name for the uploaded file on the web server use something like
move_uploaded_file($_FILES["uploadfile"]["tmp_name"], $base_dir . '/' . 'resultado.zip')
it creates me a ErrorsUpload folder, and nothing in there. Don’t work for me, that function.
Do you know how to implement hash code in your VBA function to make the upload of the file with security?
Thanks
From your VB code try to access the backend URL w/ something in the `id` parameter like this:
pvPostFile "http://{{your_server_here}}/upload_errors.php?id=test", "C:\TEMP\resultado.zip"
This wll create a subdirectory `test` in `ErrorsUpload` and place `resultado.zip` there. This is the behavior I needed for my puposes, `id` in my case is unique per client machine.
For security you can try using ssl/tls on your web server (this involves certificates).
For checksums for upload verification you have to device some protocol on your own, nothing ready-made here.
The first function ends with a “End Sub” is that a mistake right?
Ooops, fixed.
10x,
</wqw>
The code works, but I think the file is not the same after send to the server and before, well is almost the same, nothing is missing, but if we look to the file size they are not the same, for only a few bytes.
If we compare the 2 MD5, they are not the same too.
Don’t know if I need to say, when the MD5s are not the same, the file is not the same too.
What kind of backend do you test with? I don’t have problems with PHP under Apache.
How do I do to send the File Name in the header?
Pingback: Convert XML file to String variable in VBA - QuestionFocus
Hi,
If I use above code I am getting the error as “{“status”:400,”code”:”E_INPUT_MESSAGE_NOT_READABLE”,”message”:”The input message is not readable.”,”details”:”Fail to parse the first part in the multipart request”,”id”:”9b3516b3-c6bc-4b8b-af33-8cf3bd50301d”}”. My request is to send a file and its properties(metadata) to the server. Please help I want to send Json object to the server along with document.
Your service is probably expecting the JSON content to be in the first part and binary file in the second part of a multipart request.
Take a look at this gist for ideas: https://gist.github.com/wqweto/100a0f8c7a90863a06f7b669b1eb6261
Pingback: excel - Come inviare file via HTTP_POST con Excel con VBA?
The code works for me only on VB but on javaScript gives me an error:
xhr.ResponseText
“{\”Message\”:\”An error has occurred.\”}”
There is no JavaScript in this blog post. Can you clarify what you mean or I’ll have to delete your comment soon.
Sorry, i Mean JScript, i realised you code is using “\n” i changed to “\r\n” and it worked, now i’m having other error named “: -1072896658.” i tried to change the charset to utf8,utf-8, UTF8,UTF-8,and many others… but nothing worked
Here i can be more specific:

i runned it on “fiddler software” and the request sent correctly
i can’t understand why “http.responseText” doens’t works
Tks, i din’t realise it, i just changed it and works very well.
I tried your code using the winhttp5.1 (not the xmlhttp) as you suggested. My server is a Java Web App that seems to accept files from JavaScript Ajax requests. But the filename isn’t getting detected somehow on the server-side in the servlet. Once I get that, I think I should be able to save the file on server (code was in place for JS Ajax requests to work). Any idea?
Check out https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition especially `filename` vs `filename*` directives — might be an encoding issue.
Like Melque said, on Jscript code you are using “\r” instead “\r\n” i changed it in my code, and works pretty well.
Thanks for the bug report. Yes, it seems `multipart/form-data` has to use CRLF for row separators and ADODB.Stream does *not* translate \n to \r\n when in binary mode.
Fixed in JScript code above!
I have been struggling for days and your comments about the “send method can not handle “byref” byte arrays, so for instance passing baBuffer will fail, as VB6 sets up VT_BYREF bit of the type of the variant parameter.” was the problem I was facing – thanks, great article.
Hi, im using this code, to upload outlook message files with mime-type = application/vnd.ms-outlook.
upload works fine, but i cant open the uploaded files.
outlook means that i havent the right permission.
could i change any permissions in this script, i cant see it
This sounds totally irrelevant. Just make sure the uploaded files are *binary* equivalent to local original files.
Whatever issues you are facing stem from another source altogether (most probably httpd mime-types [mis]configuration).
i compare both files.
i downloaded the uploaded file and see, that there is no content (0kb)
if ist is a misconfiguration of mime-type, where can i get the right?
i checked the mime type on https://www.htmlstrip.com/mime-file-type-checker
> i downloaded the uploaded file and see, that there is no content (0kb)
Ok, so upload does *not* work fine. Yes, it might be permissions issue, not mime-types one but doubt I can help any further.
ok, thanks for your help
Pingback: วิธีส่งข้อความแจ้งเตือน (Notification) จาก Excel เข้า Line หรือ Email : ภาค4 - เทพเอ็กเซล : Thep Excel
Pingback: How to send files via HTTP_POST with Excel using VBA? - MicroEducate
Pingback: 如何使用VBA使用Excel通过HTTP_POST发送文件? - 开发百科-程序代码问答平台
Pingback: How to send files via HTTP_POST with Excel using VBA? – Make Me Engineer