/
payload.rb
175 lines (141 loc) · 4.08 KB
/
payload.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# stolen and modified from rest-client
require 'stringio'
require 'tempfile'
require 'rest-builder/error'
require 'rest-builder/middleware'
begin
require 'mime/types/columnar'
rescue LoadError
require 'mime/types'
end
module RestBuilder
class Payload
Unspecified = Class.new(Hash)
def self.generate_with_headers payload, headers
h = if p = generate(payload)
p.headers.merge(headers)
else
headers
end
[p, h]
end
def self.generate payload
if payload.respond_to?(:read)
Streamed.new(payload)
elsif payload.kind_of?(String)
StreamedString.new(payload)
elsif payload.kind_of?(Hash)
if payload.empty?
nil
elsif Middleware.contain_binary?(payload)
Multipart.new(payload)
else
UrlEncoded.new(payload)
end
else
raise Error.new("Payload should be either String, Hash, or" \
" responding to `read', but: #{payload.inspect}")
end
end
# Payload API
attr_reader :io
alias_method :to_io, :io
def initialize payload; @io = payload ; end
def read bytes=nil; io.read(bytes) ; end
def close ; io.close unless closed?; end
def closed? ; io.closed? ; end
def headers ; {} ; end
def size
if io.respond_to?(:size)
io.size
elsif io.respond_to?(:stat)
io.stat.size
else
0
end
end
class Streamed < Payload
def headers
{'Content-Length' => size.to_s}
end
end
class StreamedString < Streamed
def initialize payload
super(StringIO.new(payload))
end
end
class UrlEncoded < StreamedString
def initialize payload
super(Middleware.percent_encode(payload))
end
def headers
super.merge('Content-Type' => 'application/x-www-form-urlencoded')
end
end
class Multipart < Streamed
EOL = "\r\n"
def initialize payload
super(Tempfile.new("rest-core.payload.#{boundary}"))
io.binmode
payload.each_with_index do |(k, v), i|
if v.kind_of?(Array)
v.each{ |vv| part(k, vv) }
else
part(k, v)
end
end
io.write("--#{boundary}--#{EOL}")
io.rewind
end
def part k, v
io.write("--#{boundary}#{EOL}Content-Disposition: form-data")
io.write("; name=\"#{k}\"") if k
if v.respond_to?(:read)
part_binary(k, v)
else
part_plantext(k, v)
end
end
def part_plantext k, v
io.write("#{EOL}#{EOL}#{v}#{EOL}")
end
def part_binary k, v
if v.respond_to?(:original_filename) # Rails
io.write("; filename=\"#{v.original_filename}\"#{EOL}")
elsif v.respond_to?(:path) # files
io.write("; filename=\"#{File.basename(v.path)}\"#{EOL}")
else # io
io.write("; filename=\"#{k}\"#{EOL}")
end
# supply your own content type for regular files, will you?
if v.respond_to?(:content_type) # Rails
io.write("Content-Type: #{v.content_type}#{EOL}#{EOL}")
elsif v.respond_to?(:path) && type = mime_type(v.path) # files
io.write("Content-Type: #{type}#{EOL}#{EOL}")
else
io.write(EOL)
end
while data = v.read(8192)
io.write(data)
end
io.write(EOL)
ensure
v.close if v.respond_to?(:close)
end
def mime_type path
mime = MIME::Types.type_for(path)
mime.first && mime.first.content_type
end
def boundary
@boundary ||= rand(1_000_000).to_s
end
def headers
super.merge('Content-Type' =>
"multipart/form-data; boundary=#{boundary}")
end
def close
io.close! unless io.closed?
end
end
end
end