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
What the...?If you're thinking that using a BinaryStream.Write here is strange, please read this.

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.

Comments (0)





Allowed tags: <b><i><br>Add a new comment: