Class: Doze::Serialization::Entity::MultipartFormData

Inherits:
Doze::Serialization::Entity show all
Includes:
FormDataHelpers
Defined in:
lib/doze/serialization/multipart_form_data.rb

Overview

Also ripped off largely from Merb::Parse.

Small differences in the hash it returns for an uploaded file - it will have string keys, use media_type rather than content_type (for consistency with rest of doze) and adds a temp_path key.

These enable it to be used interchangably with nginx upload module if you use config like eg:

upload_set_form_field $upload_field_name “$upload_file_name”; upload_set_form_field $upload_field_name “$upload_content_type”; upload_set_form_field $upload_field_name “$upload_tmp_path”; upload_aggregate_form_field $upload_field_name “$upload_file_size”;

Constant Summary collapse

NAME_REGEX =
/Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
CONTENT_TYPE_REGEX =
/Content-Type: (.*)\r\n/ni.freeze
FILENAME_REGEX =
/Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
CRLF =
"\r\n".freeze
EOL =
CRLF

Constants inherited from Entity

Entity::DEFAULT_TEXT_ENCODING

Instance Attribute Summary

Attributes inherited from Entity

#binary_data_length, #encoding, #extra_content_headers, #language, #media_type, #media_type_params

Instance Method Summary collapse

Methods inherited from Doze::Serialization::Entity

#binary_data, #deserialize, #initialize, #serialize

Methods inherited from Entity

#binary_data, #binary_data_stream, #etag, #initialize

Constructor Details

This class inherits a constructor from Doze::Serialization::Entity

Instance Method Details

#deserialize_streamObject



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/doze/serialization/multipart_form_data.rb', line 39

def deserialize_stream
  boundary = @media_type_params && @media_type_params['boundary'] or raise  "missing boundary parameter for multipart/form-data"
  boundary = "--#{boundary}"
  paramhsh = {}
  buf      = ""
  input    = @binary_data_stream
  input.binmode if defined? input.binmode
  boundary_size = boundary.size + EOL.size
  bufsize       = 16384
  length  = @binary_data_length or raise "expected Content-Length for multipart/form-data"
  length -= boundary_size
  # status is boundary delimiter line
  status = input.read(boundary_size)
  return {} if status == nil || status.empty?
  raise "bad content body:\n'#{status}' should == '#{boundary + EOL}'"  unless status == boundary + EOL
  # second argument to Regexp.quote is for KCODE
  rx = /(?:#{EOL})?#{Regexp.new(Regexp.escape(boundary), {}, 'n')}(#{EOL}|--)/
  loop {
    head      = nil
    body      = ''
    filename  = content_type = name = nil
    read_size = 0
    until head && buf =~ rx
      i = buf.index("\r\n\r\n")
      if( i == nil && read_size == 0 && length == 0 )
        length = -1
        break
      end
      if !head && i
        head = buf.slice!(0, i+2) # First \r\n
        buf.slice!(0, 2)          # Second \r\n

        # String#[] with 2nd arg here is returning
        # a group from match data
        filename     = head[FILENAME_REGEX, 1]
        content_type = head[CONTENT_TYPE_REGEX, 1]
        name         = head[NAME_REGEX, 1]

        if filename && !filename.empty?
          body = Tempfile.new('Doze')
          body.binmode if defined? body.binmode
        end
        next
      end

      # Save the read body part.
      if head && (boundary_size+4 < buf.size)
        body << buf.slice!(0, buf.size - (boundary_size+4))
      end

      read_size = bufsize < length ? bufsize : length
      if( read_size > 0 )
        c = input.read(read_size)
        raise "bad content body"  if c.nil? || c.empty?
        buf << c
        length -= c.size
      end
    end

    # Save the rest.
    if i = buf.index(rx)
      # correct value of i for some edge cases
      if (i > 2) && (j = buf.index(rx, i-2)) && (j < i)
         i = j
       end
      body << buf.slice!(0, i)
      buf.slice!(0, boundary_size+2)

      length = -1  if $1 == "--"
    end

    if filename && !filename.empty?
      body.rewind
      data = {
        "filename"      => File.basename(filename),
        "media_type"    => content_type,
        "tempfile"      => body,
        "temp_path"     => body.path,
        "size"          => File.size(body.path)
      }
    else
      data = body
    end
    paramhsh = normalize_params(paramhsh,name,data)
    break  if buf.empty? || length == -1
  }
  paramhsh
end

#object_data(try_deserialize = true) ⇒ Object



31
32
33
34
35
36
37
# File 'lib/doze/serialization/multipart_form_data.rb', line 31

def object_data(try_deserialize=true)
  @object_data ||= if @lazy_object_data
    @lazy_object_data.call
  elsif try_deserialize
    @binary_data_stream && deserialize_stream
  end
end

#param_entity(name) ⇒ Object

This is designed to work with either actual file upload fields, or the corresponding fields generated by nginx upload module as described above.

yields or returns a Doze::Entity for the uploaded file, with the correct media_type and binary_data_length. ensures to close and unlinks the underlying tempfile afterwards where used with a block.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/doze/serialization/multipart_form_data.rb', line 133

def param_entity(name)
  meta = object_data[name]; return unless meta.is_a?(Hash)
  media_type = meta["media_type"] and media_type = Doze::MediaType[media_type] or return
  size = meta["size"] && meta["size"].to_i
  if (tempfile = meta["tempfile"])
    temp_path = tempfile.path
  elsif (temp_path = meta["temp_path"])
    tempfile = File.open(meta["temp_path"], "rb")
  end
  return unless tempfile
  entity = media_type.new_entity(:binary_data_stream => tempfile, :binary_data_length => size)

  return entity unless block_given?
  begin
    yield entity
  ensure
    tempfile.close
    begin
      File.unlink(temp_path)
    rescue StandardError
      # we made an effort to clean up - but it is a tempfile, so no biggie
    end
  end
end