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”}

  1. 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

  1. 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

  1. 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

On 15 Jan 2009, at 03:48, sarah wrote:

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

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


When I use some attachment_fu_hacks that I found here:
http://seventytwo.co.uk/posts/making-swfupload-and-rails-work-together:

def uploaded_data=(file_data)
return nil if file_data.nil? || file_data.size == 0
self.content_type = detect_mimetype(file_data)
self.filename = file_data.original_filename if respond_to?
(:filename)
if file_data.is_a?(StringIO)
file_data.rewind
self.temp_data = file_data.read
else
self.temp_path = file_data.path
end
end

def detect_mimetype(file_data)
if file_data.content_type.strip == “application/octet-stream”
return File.mime_type?(file_data.original_filename)
else
return file_data.content_type
end
end
end

Errors are thrown because file_data – the string passed in through my
params[:image_uploaded_data] from Flash does not respond to
content_type or original_filename


Am I missing something from the point where the I am receiving the
data in the controller and getting it to attachment_fu? It doesn’t
know it is application/octet-stream because it doesn’t respond to
content-type at the controller level.

Thanks,
Sarah

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

On 15 Jan 2009, at 19:55, sarah wrote:

else
Am I missing something from the point where the I am receiving the
data in the controller and getting it to attachment_fu? It doesn’t
know it is application/octet-stream because it doesn’t respond to
content-type at the controller level.

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