application/octet-stream from flash: binary data, not swfupload

Hello,

I'm working on a rails app with a flash application in it, and the flash application posts binary data to the rails app, to create and upload an image. The image arrives from Flash with a Content-Type of "application/octet-stream", but by the time the image data hits the controller action, it is a string.

I have googled and found numerous solutions for similar cases: getting the content_type of data uploaded from flash (generally via swfobject) and adjusting for when the content-type is application/octet-stream. But, these don't work in my case -- I'm guessing that all those cases are for actually uploading files rather than transporting binary data.

I do have mimetype_fu installed and attachment_fu hacks and the mime_type gem but none of those solutions work because the data it's working on in the controller is a string, and doesn't have a content_type method. All of these workarounds involve querying the data for its content_type and adjusting from there.

Initially we thought "oh the form doesn't have multipart set", but when I track the data in request.rb (the parse_multipart_form_parameters method), the data seems to be properly encoded with the correct form type:

The flash developer and I have done a lot of searching and it may be that the data needs to be sent differently from Flash (encoded in a different format perhaps, as a few blog posts suggest). But on the Rails end, does anyone have any insight on why the data is being thrown out by the time it gets to the controller? And, more importantly, where I might intercept it so by the time it hits the controller it is still application/octet-stream?

Below are some debug outputs if you want to see more details:

1. LOGGING from parse_multipart_form_parameters in request.rb. This is the incoming request from Flash.

-- Wed Jan 14 19:22:03 -0600 2009: here in parse_multipart_form_parameters: body: #<StringIO:0x3f03ab8> ; boundary: iuxcwgdgqyluhwtavtkiwakxwlyxfkxs ; body_size: 65822 ; env: {"SERVER_NAME"=>"localhost", "HTTP_ENCTYPE"=>"multipart/form-data", "PATH_INFO"=>"/projects/13/elements/21/assets/130/visual_notes", "CONTENT_LENGTH"=>"65822", "HTTP_CONTENT_TYPE"=>"multipart/form-data; boundary=iuxcwgdgqyluhwtavtkiwakxwlyxfkxs", "HTTP_ACCEPT_ENCODING"=>"gzip,deflate", "HTTP_USER_AGENT"=>"Mozilla/ 5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.5) Gecko/ 2008120121 Firefox/3.0.5", "SCRIPT_NAME"=>"/", "SERVER_PROTOCOL"=>"HTTP/1.1", "HTTP_CACHE_CONTROL"=>"no-cache", "HTTP_ACCEPT_LANGUAGE"=>"en-us,en;q=0.5", "HTTP_HOST"=>"localhost: 3000", "REMOTE_ADDR"=>"127.0.0.1", "SERVER_SOFTWARE"=>"Mongrel 1.1.5", "HTTP_CONTENT_LENGTH"=>"65822", "HTTP_KEEP_ALIVE"=>"300", "REQUEST_PATH"=>"/projects/13/elements/21/assets/130/visual_notes", "CONTENT_TYPE"=>"multipart/form-data; boundary=iuxcwgdgqyluhwtavtkiwakxwlyxfkxs", "HTTP_REFERER"=>"http:// localhost:3000/movies/image_creator.swf", "HTTP_COOKIE"=>"last-visited- project=canvasband; _canvasband_session=BAh7CToMdXNlcl9pZGkMOgxjc3JmX2lkIiUyOTI3ZjJiMGJlMDhmNzMxMDZh %0AY2M4NjUyZGZmYWI5ZDoOcmV0dXJuX3RvMCIKZmxhc2hJQzonQWN0aW9uQ29u %0AdHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D-- dcc1e413220b4e43db3ab08a37cf67ff73f8ec6e; auth_token=; mingle_2_1_session_id=4292f420b98099b491c469a9bffab2ac", "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.7", "HTTP_VERSION"=>"HTTP/1.1", "REQUEST_URI"=>"/projects/13/elements/21/ assets/130/visual_notes", "SERVER_PORT"=>"3000", "GATEWAY_INTERFACE"=>"CGI/1.2", "HTTP_ACCEPT"=>"text/html,application/ xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "HTTP_CONNECTION"=>"keep- alive", "REQUEST_METHOD"=>"POST"}

2. A little later in read_multipart, I can see that the data has arrived in the application/octet-stream format:

Content-Disposition: form-data; name="image_uploaded_data"; filename="1141922993.jpg" Content-Type: application/octet-stream

3. My controller action params

Parameters: `"element_id"=>"21", "project_id"=>"13", "action"=>"create", "authenticity_token"=>"d72daa6709e28c9ed1b55566419e1e8bd714da11", "image_uploaded_data"=>"1141951886.jpg", "Upload"=>"Submit Query", "controller"=>"projects/elements/assets/visual_notes", "visual_note_created_by_id"=>"1", "asset_id"=>"130

4. Output when running a before_filter to before_filter do |controller|     logger.info("CLASS: #{controller.params [:image_uploaded_data].class}") end CLASS: String

Thanks,

Sarah

I’m working on a rails app with a flash application in it, and the flash application posts binary data to the rails app, to create and upload an image. The image arrives from Flash with a Content-Type of “application/octet-stream”, but by the time the image data hits the controller action, it is a string.

That’s merely because of the filesize of the data you are sending. Everything below 16KB will be seen as StringIO, above it will be a TempFile. Attachment_fu has everything in place to handle it.

Look at line 303 at http://github.com/technoweenie/attachment_fu/blob/743a95be0f01696530f558e7f4934cc7e57d3ab8/lib/technoweenie/attachment_fu.rb

I have googled and found numerous solutions for similar cases: getting the content_type of data uploaded from flash (generally via swfobject) and adjusting for when the content-type is application/octet-stream. But, these don’t work in my case – I’m guessing that all those cases are for actually uploading files rather than transporting binary data.

Attachment_fu combined with mimetype_fu work perfectly for me with any data sent from Flash. Flash has no way to pass in the appropriate content type (simply said: it’s always application/octet-stream), but I can see in your controller that you have a filename available (xxxx.jpg). This should be enough for mimetype_fu to determine the content-type as “image/jpeg”.

Best regards

Peter De Berdt

Thanks for the reply, Peter. Unfortunately, this is not what is happening in my application. Are you uploading images or passing binary data created in Flash to Rails?

What is happening for us is that I am able to get the content-type as "image/jpeg" using mimetype_fu, but that is as far as things go. If I then try to save that data in the controller (using attachment_fu), like so:

image_params = {:uploaded_data => params['image_uploaded_data']} image = @visual_note.build_image(image_params.merge(:created_by => current_user)) if image.valid? #do stuff else #errors end

I get validates_as_attachment errors: Content type can't be blank; Size can't be blank; Size is not included in the list; Filename can't be blank

How about:

image_params = {:temp_data => params[‘image_uploaded_data’],

            :filename => "whateveruwant",

            :content_type => "image/jpeg"

           }

image = @visual_note.build_image(image_params.merge(:created_by =>

current_user))

if image.valid?

#do stuff

else

#errors

end

Means the data that comes from Flash will be processed and you manually set the other fields.

Best regards

Peter De Berdt

Interesting. This is definitely a step in a productive direction and I am very thankful for the suggestions that you have given. It's not *quite* doing the trick, but it's the closest I've been so far. Here's the scoop:

First, to do this, I had to make :content_type and :filename :attr_accessible and make a setter for temp_data in my asset class, since it's not a setter attribute. This method calls set_temp_data in attachment_fu, like so:

def temp_data= set_temp_data(data) end

I then followed your suggestion in the controller, like so, but getting the mime_type from mimetype_fu, in case it's not always "image/ jpeg"

image_params = {:temp_data => params ['image_uploaded_data'], :filename=> params ['image_uploaded_data'], :content_type => File.mime_type?(params ['image_uploaded_data'])}

Note that each of values is the same field: params ['image_uploaded_data'] is the string of the filename that comes in.

At this point, I ran the code and for the first time the process worked: the image validated, and it was stored on s3. However, the image itself was not the image exported from Flash: it was a jpeg *of* the string of filename. You can see it here: http://s3.amazonaws.com/canvasband_development/assets/255/v1/1152215550.jpg

Weird. So I looked more closely at attachment_fu, and saw that in the uploaded_data= method, the seemingly relevant lines here pass the data from the file to set_temp_data, like so:

(line 334) if file_data.is_a?(StringIO)     file_data.rewind     set_temp_data file_data.read else   self.temp_paths.unshift file_data end

So I switched my temp data setter to mimic that: def temp_data=(data)     data.rewind     set_temp_data(data.read)   end

and tried again, at which point I received the error: NoMethodError (undefined method `rewind' for "1152233660.jpg":String)

So I then tried to convert the string to a stringIO object,

def temp_data=(data)     datastr = StringIO.new(data)     datastr.rewind     set_temp_data(datastr.read)   end

Which then worked again -- ie, saved and uploaded the image -- except the "image" it saved was still a jpeg of the filename, again visible on s3 http://s3.amazonaws.com/canvasband_development/assets/263/v1/1152241736_thumb.jpg, but not what Flash sent in.

Which seems to bring me back to the original problem, namely: despite the fact that the binary data is coming into the application, by the time I'm in the controller I can't figure out how to access that data, and am only managing to get this pseudo-image.

Is there anything else you can think of here?

Thanks again for your input thus far, Sarah