12

I'm trying to upload a file to a wiki using Mathematica's URLFetch. Some preliminary work is required - one needs to log in to the wiki, obtain an edit token, and exchange some cookies with the wiki server. URLFetch can handle all of that. But then for the actual file upload, the MediaWiki API (e.g. http://www.mediawiki.org/wiki/API:Upload) specifies that one has to use the POST method with Content-Type=multipart/form-data, but the Mathematica implementation of multipart within URLFetch seems to be incomplete.

The Mathematica documentation of the option "MultipartData" of URLFetch says only:

to upload multipart data, each part must be of the form {name, mimeType, {bytes}}, where {bytes} is a list of bytes

This suggests that in the "Content-Disposition" header of each part of a multipart request, a name can be specified as in:

Content-Disposition: form-data; name="file"

but there seems to be no way to also specify a filename, as in

Content-Disposition: form-data; name="file"; filename="Apple.gif"

Finally, the MediaWiki API requires that a file name be specified; see e.g. the example of a multipart request at the very bottom of http://www.mediawiki.org/wiki/API:Upload (and when I try to upload a file without specifying a filename, the wiki server sends back an error message).

So my question: Is there a more complete implementation of the MultiPart protocol with URLFetch?

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
Dror Bar-Natan
  • 313
  • 1
  • 6
  • 1
    Try to use MultiPartData for the file and then the Parameters option (see the section on options) for the file name. – C. E. Jul 09 '14 at 10:44
  • Tried, failed. At least for MediaWiki, all parameters have to come as parts of of the multipart. – Dror Bar-Natan Jul 09 '14 at 11:11
  • Dror Bar Natan, I have the same problem with URLFetch MultipartData / MultipartElements you had almost a year ago. Did you find a solution? – pvanbijnen Apr 11 '15 at 17:39

1 Answers1

13

This is a hack, but it works.

UploadFile[url_, filePath_, urlParams___] := With[
  {
    bytes = Import[filePath, "Byte"],
    filename = StringJoin[FileBaseName[filePath], ".", FileExtension[filePath]]
  },
  URLExecute[
    url,
    urlParams,
    "Method" -> "POST",
    "MultipartElements" -> {
      {"file\"; filename=\"" <> filename, "application/octet-stream", bytes}
    },
    "Headers" -> {
      "Accept" -> "application/json; charset=UTF-8",
      "Content-Type" -> "multipart/form-data",
      "Expect" -> "" (* See edit 2.5 below *)
    }
  ]
]

Notice that I'm stuffing the filename parameter in the first element of "MultipartElements" along with the field name in order to get it in Content-Disposition for that element.

(* Get a new ephemeral RequestBin from http://requestb.in/, then put URL below: *)
UploadFile["http://requestb.in/1hyjbdl1", "ExampleData/rose.gif"]

result:

Content-Disposition: form-data; name="file"; filename="rose.gif"

Theoretically you could also specify "Content-Disposition" -> "form-data; filename=\"foo\"" in the request header, but the server would have to be expecting exactly one file in the form-data and parse accordingly, whereas the above approach with filenames in each part I think is more commonly expected.


Edit

In version 11, there's a no-hack way to do this:

image = FindFile["ExampleData/Ocelot.jpg"];
req = HTTPRequest[url, <|
  "Body" -> {"image" -> File[image]},
  "Expect" -> "" (* See edit 2.5 below *)
|>];
URLRead[req, "Body"]

Docs (see Scope > "To send files...")

Edit 2, 2.5 Mathematica's HTTP client uses Expect: "100-continue", which is not supported by some common load balancers that may be between you and your server. To disable that, add the header "Expect" -> "".

ZachB
  • 1,200
  • 9
  • 19