File uploads, form encodings, and Xojo
In order to upload a file using an HTTP form, you must encode the form as multipart/form-data. Unfortunately, Xojo/REALstudio's built-in HTTPSocket.SetFormData method only generates application/x-www-form-urlencoded strings. In order to generate the correct form encoding, you have to do it yourself. Having spent more time than I care to admit in figuring out the multipart format, I felt I should document my solution for future developers.
Sub SetFormData(sock As HTTPSocket, FormData As Dictionary, Boundary As String) If Boundary.Trim = "" Then Boundary = "--" + Right(EncodeHex(MD5(Str(Microseconds))), 24) + "-bOuNdArY" End If Static CRLF As String = EndOfLine.Windows Dim data As New MemoryBlock(0) Dim out As New BinaryStream(data) For Each key As String In FormData.Keys out.Write("--" + Boundary + CRLF) If VarType(FormData.Value(Key)) = Variant.TypeString Then out.Write("Content-Disposition: form-data; name=""" + key + """" + CRLF + CRLF) out.Write(FormData.Value(key) + CRLF) ElseIf FormData.Value(Key) IsA FolderItem Then Dim file As FolderItem = FormData.Value(key) out.Write("Content-Disposition: form-data; name=""" + key + """; filename=""" + File.Name + """" + CRLF) out.Write("Content-Type: application/octet-stream" + CRLF + CRLF) ' replace with actual MIME Type Dim bs As BinaryStream = BinaryStream.Open(File) out.Write(bs.Read(bs.Length) + CRLF) bs.Close End If Next out.Write("--" + Boundary + "--" + CRLF) out.Close #If RBVersion > 2012 Then sock.SetRequestContent(data, "multipart/form-data; boundary=" + Boundary) #else sock.SetPostContent(data, "multipart/form-data; boundary=" + Boundary) #endif End Sub
Pass the HTTPSocket, a Dictionary containg "Name":"StringValue" or "Name":FolderItem pairs to be encoded, along with the boundary string to be used. To have a boundary generated for you, pass the empty string.
Dim HTMLForm As New Dictionary HTMLForm.Value("UserName") = "Bob Smith" HTMLForm.Value("Upload") = SpecialFolder.Desktop.Child("Somefile.zip") SetFormData(MyHTTPSocket, HTMLForm, "") ' pass an empty boundary to generate a new one MyHTTPSocket.Post("www.example.com/uploads.php")
This code snippet is derived from a larger project of mine. You can see my original and more feature-rich MultipartForm class here. The class can also reverse the process, creating a dictionary from a multipart/form-data string.You might also be interested in RB-libcURL, a Realbasic and Xojo binding for libcURL.