Dominykas Mostauskis
Dominykas Mostauskis

Reputation: 8135

How to safely handle raw (file) data in Java?

An image gets corrupted while being retrieved (through HTTP) and then sent (through HTTP) to a database. Image's raw data is handled in String form.

The service sends a GET for an image file, receives response with the raw image data (response's body) and the Content-Type. Then, a PUT request is sent with the aforementioned request's body and Content-Type header. (The PUT request is constructed by providing the body in String) This PUT request is sent to a RESTful database (CouchDB), creating an attachment (for those unfamiliar with CouchDB an attachment acts like a static file).

Now I have the original image, which my service GETs and PUTs to a database, and this 'copy' of the original image, that I can now GET from the database. If I then `curl --head -v "[copy's url]" it has the Content-Type of the original image, but Content-Length has changed, went from 200kb to about 400kb. If I GET the 'copy' image with a browser, it is not rendered, whereas, the original renders fine. It is corrupted.

What might be the cause? My guess is that while handling the raw data as a string, my framework guesses the encoding wrong and corrupts it. I have not been able to confirm or deny this. How could I handle this raw data/request body in a safe manner, or how could I properly handle the encoding (if that proves to be the problem)?

Details: Play2 Framework's HTTP client, Scala. Below a test to reproduce:

"able to copy an image" in {
  def waitFor[T](future:Future[T]):T = { // to bypass futures
    Await.result(future, Duration(10000, "millis"))
  }
  val originalImageUrl = "http://laughingsquid.com/wp-content/uploads/grumpy-cat.jpg"
  val couchdbUrl = "http://admin:admin@localhost:5984/testdb"
  val getOriginal:ws.Response = waitFor(WS.url(originalImageUrl).get)
  getOriginal.status mustEqual 200
  val rawImage:String = getOriginal.body
  val originalContentType = getOriginal.header("Content-Type").get

  // need an empty doc to have something to attach the attachment to
  val emptyDocUrl = couchdbUrl + "/empty_doc"
  val putEmptyDoc:ws.Response = waitFor(WS.url(emptyDocUrl).put("{}"))
  putEmptyDoc.status mustEqual 201
  //uploading an attachment will require the doc's revision
  val emptyDocRev = (putEmptyDoc.json \ "rev").as[String]

  // create actual attachment/static file
  val attachmentUrl = emptyDocUrl + "/0"
  val putAttachment:ws.Response = waitFor(WS.url(attachmentUrl)
    .withHeaders(("If-Match", emptyDocRev), ("Content-Type", originalContentType))
    .put(rawImage))
  putAttachment.status mustEqual 201

  // retrieve attachment
  val getAttachment:ws.Response = waitFor(WS.url(attachmentUrl).get)
  getAttachment.status mustEqual 200
  val attachmentContentType = getAttachment.header("Content-Type").get

  originalContentType mustEqual attachmentContentType
  val originalAndCopyMatch = getOriginal.body == getAttachment.body
  originalAndCopyMatch aka "original matches copy" must beTrue // << false
}

Fails at the last 'must':

[error] x  able to copy an image
[error]    original matches copy is false (ApplicationSpec.scala:112)

Upvotes: 1

Views: 461

Answers (1)

estmatic
estmatic

Reputation: 3449

The conversion to String is definitely going to cause problems. You need to work with the bytes as Daniel mentioned.

Looking at the source it looks like ws.Response is just a wrapper. If you get to the underlying class then there are some methods that may help you. On the Java side, someone made a commit on GitHub to expose more ways of getting the response data other than a String.

I'm not familiar with scala but something like this may work:

getOriginal.getAHCResponse.getResponseBodyAsBytes

// instead of getOriginal.body

WS.scala
https://github.com/playframework/playframework/blob/master/framework/src/play/src/main/scala/play/api/libs/ws/WS.scala

WS.java
Here you can see that Response has some new methods, getBodyAsStream() and asByteArray.
https://github.com/playframework/playframework/blob/master/framework/src/play-java/src/main/java/play/libs/WS.java

Upvotes: 1

Related Questions