Class: Nyara::Part

Inherits:
ParamHash show all
Defined in:
lib/nyara/part.rb

Overview

A part in multipart
for an easy introduction, http://msdn.microsoft.com/en-us/library/ms526943(v=exchg.10).aspx

  • todo make it possible to store data into /tmp (this requires memory threshold counting)
  • todo nested multipart?

Constant Summary collapse

MECHANISMS =
%w[base64 quoted-printable 7bit 8bit binary].freeze
TOKEN =

rfc2616

token         := 1*<any CHAR except CTLs or separators>
separators    := "(" | ")" | "<" | ">" | "@"
               | "," | ";" | ":" | "\" | <">
               | "/" | "[" | "]" | "?" | "="
               | "{" | "}" | " " | "\t"
CTL           := <any US-ASCII control character
               (octets 0 - 31) and DEL (127)>
/[^\x00-\x1f\x7f()<>@,;:\\"\/\[\]?=\{\}\ \t]+/ni
ATTR_CHAR =

rfc5978

attr-char   := ALPHA / DIGIT ; rfc5234
            / "!" / "#" / "$" / "&" / "+" / "-" / "."
            / "^" / "_" / "`" / "|" / "~"
/[a-z0-9!#$&+\-\.\^_`|~]/ni
EX_PARAM =

rfc5978 (NOTE rfc2231 param continuations is not recommended)

value-chars := pct-encoded / attr-char
pct-encoded := "%" HEXDIG HEXDIG
/\s*;\s*(filename|name)\s*(?:
  = \s* "((?>\\"|[^"])*)"         # quoted string - v1
  | = \s* (#{TOKEN})              # token - v2
  | \*= \s* ([\w\-]+)             # charset - enc
        '[\w\-]+'                 # language
        ((?>%\h\h|#{ATTR_CHAR})+) # value-chars - v3
)/xni

Instance Method Summary collapse

Methods inherited from ParamHash

#[], #[]=, #_aref, #_aset, #key?, #nested_aref, #nested_aset, parse_cookie, parse_param, split_name

Methods inherited from Hash

#to_param

Constructor Details

#initialize(head) ⇒ Part

Analyse given head and build a param hash representing the part

  • head - header
  • mechanism - 7bit, 8bit, binary, base64, or quoted-printable
  • type - mime type
  • data - decoded data (incomplete before Part#final called)
  • filename - basename of uploaded data
  • name - param name, in array form. If it comes like a[b][][c], then it becomes ["a", "b", "", "c"] after parsing.

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
# File 'lib/nyara/part.rb', line 53

def initialize head
  self['head'] = head
  if mechanism = head['Content-Transfer-Encoding']
    self['mechanism'] = mechanism.strip.downcase
  end
  if self['type'] = head['Content-Type']
    self['type'] = self['type'][/.*?(?=;|$)/]
  end
  self['data'] = ''.force_encoding('binary')

  disposition = head['Content-Disposition']
  if disposition
    # todo just use binary when constructing it?
    disposition.force_encoding('binary')
    # skip first token
    ex_params = disposition.sub TOKEN, ''.force_encoding('binary')

    # store values not so specific as values with charset
    tmp_values = {}
    ex_params.scan EX_PARAM do |name, v1, v2, enc, v3|
      name.downcase!
      case name
      when 'name', 'filename'
        if enc
          self[name] = enc_unescape enc, v3
        else
          tmp_values[name] = (v1.force_encoding('utf-8') || (CGI.unescape(v2) rescue nil))
        end
      end
    end

    if filename = (self['filename'] ||= tmp_values['filename'])
      self['filename'] = File.basename filename
    end

    self['name'] ||= tmp_values['name']
  end

  # rfc2111: url-encoded
  self['name'] ||= (head['Content-Id'] ? (CGI.unescape(head['Content-Id']) rescue nil) : nil)
end

Instance Method Details

#finalObject

NOTE close connection on error


155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/nyara/part.rb', line 155

def final
  case self['mechanism']
  when 'base64'
    if tmp = self['tmp']
      self['data'] << tmp.unpack('m').first
      delete 'tmp'
    end

  when 'quoted-printable'
    if tmp = self['tmp']
      self['data'] << tmp.gsub(/=(\h\h)|=\r\n/n) do
        [$1].pack 'H*'
      end
      delete 'tmp'
    end
  end
  self
end

#inspectObject


196
197
198
# File 'lib/nyara/part.rb', line 196

def inspect
  "<Nyara::Part #{to_inspect_h.inspect}>"
end

#merge_into(params) ⇒ Object

Merge self data into params


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/nyara/part.rb', line 96

def merge_into params
  unless name = self['name']
    warn "looks like bad part: #{self['header'].inspect}"
    return
  end

  # NOTE `[` are `]` are escaped in url-encoded, so should not split before decode
  keys = ParamHash.split_name(name)

  if self['filename']
    params.send :nested_aset, keys, self
  elsif self['type']
    warn "looks like bad part: #{self['header'].inspect}"
  else
    params.send :nested_aset, keys, CGI.unescape(self['data'])
  end
end

#pretty_print(q) ⇒ Object


200
201
202
203
204
# File 'lib/nyara/part.rb', line 200

def pretty_print q
  q.text "<Nyara::Part "
  to_inspect_h.pretty_print q
  q.text ">"
end

#to_inspect_hObject


184
185
186
187
188
189
190
191
192
193
194
# File 'lib/nyara/part.rb', line 184

def to_inspect_h
  h = {}
  each do |k, v|
    if k == 'data'
      h[k] = "#{v.bytesize}:#{v[0..5]}..."
    else
      h[k] = v
    end
  end
  h
end

#update(raw) ⇒ Object

Params

  • raw in binary encoding

NOTE close connection on error


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/nyara/part.rb', line 119

def update raw
  case self['mechanism']
  when 'base64'
    # rfc2045#section-6.8
    raw.gsub! /\s+/n, ''
    if self['tmp']
      raw = (self['tmp'] << raw)
    end
    # last part can be at most 4 bytes and 2 '='s
    size = raw.bytesize - 6
    if size >= 4
      size = size / 4 * 4
      self['data'] << raw.slice!(0...size).unpack('m').first
    end
    self['tmp'] = raw

  when 'quoted-printable'
    # http://en.wikipedia.org/wiki/Quoted-printable
    if self['tmp']
      raw = (self['tmp'] << raw)
    end
    if i = raw.rindex("\r\n")
      s = raw.slice! i
      s.gsub!(/=(?:(\h\h)|\r\n)/n) do
        [$1].pack 'H*'
      end
      self['data'] << s
    end
    self['tmp'] = raw

  else # '7bit', '8bit', 'binary', ...
    self['data'] << raw
  end
end