-
Notifications
You must be signed in to change notification settings - Fork 546
/
tee_input.rb
259 lines (236 loc) · 6.88 KB
/
tee_input.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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# encoding: binary
#
# This file is taken from Unicorn. The following license applies to this file
# (and this file only, not to the rest of Phusion Passenger):
#
# 1. You may make and give away verbatim copies of the source form of the
# software without restriction, provided that you duplicate all of the
# original copyright notices and associated disclaimers.
#
# 2. You may modify your copy of the software in any way, provided that
# you do at least ONE of the following:
#
# a) place your modifications in the Public Domain or otherwise make them
# Freely Available, such as by posting said modifications to Usenet or an
# equivalent medium, or by allowing the author to include your
# modifications in the software.
#
# b) use the modified software only within your corporation or
# organization.
#
# c) rename any non-standard executables so the names do not conflict with
# standard executables, which must also be provided.
#
# d) make other distribution arrangements with the author.
#
# 3. You may distribute the software in object code or executable
# form, provided that you do at least ONE of the following:
#
# a) distribute the executables and library files of the software,
# together with instructions (in the manual page or equivalent) on where
# to get the original distribution.
#
# b) accompany the distribution with the machine-readable source of the
# software.
#
# c) give non-standard executables non-standard names, with
# instructions on where to get the original software distribution.
#
# d) make other distribution arrangements with the author.
#
# 4. You may modify and include the part of the software into any other
# software (possibly commercial). But some files in the distribution
# are not written by the author, so that they are not under this terms.
#
# 5. The scripts and library files supplied as input to or produced as
# output from the software do not automatically fall under the
# copyright of the software, but belong to whomever generated them,
# and may be sold commercially, and may be aggregated with this
# software.
#
# 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE.
require 'stringio'
PhusionPassenger.require_passenger_lib 'utils/tmpio'
module PhusionPassenger
module Utils
# acts like tee(1) on an input input to provide a input-like stream
# while providing rewindable semantics through a File/StringIO backing
# store. On the first pass, the input is only read on demand so your
# Rack application can use input notification (upload progress and
# like). This should fully conform to the Rack::Lint::InputWrapper
# specification on the public API. This class is intended to be a
# strict interpretation of Rack::Lint::InputWrapper functionality and
# will not support any deviations from it.
#
# When processing uploads, Unicorn exposes a TeeInput object under
# "rack.input" of the Rack environment.
class TeeInput
CONTENT_LENGTH = "CONTENT_LENGTH".freeze
HTTP_TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING".freeze
CHUNKED = "chunked".freeze
# The maximum size (in +bytes+) to buffer in memory before
# resorting to a temporary file. Default is 112 kilobytes.
@@client_body_buffer_size = 112 * 1024
# sets the maximum size of request bodies to buffer in memory,
# amounts larger than this are buffered to the filesystem
def self.client_body_buffer_size=(bytes)
@@client_body_buffer_size = bytes
end
# returns the maximum size of request bodies to buffer in memory,
# amounts larger than this are buffered to the filesystem
def self.client_body_buffer_size
@@client_body_buffer_size
end
# Initializes a new TeeInput object. You normally do not have to call
# this unless you are writing an HTTP server.
def initialize(socket, env)
if @len = env[CONTENT_LENGTH]
@len = @len.to_i
elsif env[HTTP_TRANSFER_ENCODING] != CHUNKED
@len = 0
end
@socket = socket
@bytes_read = 0
if @len && @len <= @@client_body_buffer_size
@tmp = StringIO.new("")
else
@tmp = TmpIO.new("PassengerTeeInput")
end
@tmp.binmode
end
def close
@tmp.close
end
def size
if @len
@len
else
pos = @tmp.pos
consume!
@tmp.pos = pos
@len = @tmp.size
end
end
def read(len = nil, buf = "")
buf ||= ""
if len
if len < 0
raise ArgumentError, "negative length #{len} given"
elsif len == 0
buf.replace('')
buf
else
if socket_drained?
@tmp.read(len, buf)
else
tee(read_exact(len, buf))
end
end
else
if socket_drained?
@tmp.read(nil, buf)
else
tee(read_all(buf))
end
end
end
def gets
if socket_drained?
@tmp.gets
else
if @bytes_read == @len
nil
elsif line = @socket.gets
if @len
max_len = @len - @bytes_read
line.slice!(max_len, line.size - max_len)
end
@bytes_read += line.size
tee(line)
else
nil
end
end
end
def seek(*args)
if !socket_drained?
# seek may be forward, or relative to the end, so we need to consume the socket fully into tmp
pos = @tmp.pos # save/restore tmp.pos, to not break relative seeks
consume!
@tmp.pos = pos
end
@tmp.seek(*args)
end
def rewind
return 0 if 0 == @tmp.size
consume! if !socket_drained?
@tmp.rewind # Rack does not specify what the return value is here
end
def each
while line = gets
yield line
end
self # Rack does not specify what the return value is here
end
# Rack repeatedly introduces bugs that rely on this method existing
# https://github.com/rack/rack/pull/1201
def eof?
socket_drained?
end
private
def socket_drained?
if @socket
if @socket.eof?
@socket = nil
true
else
false
end
else
true
end
end
# consumes the stream of the socket
def consume!
junk = ""
nil while read(16 * 1024, junk)
@socket = nil
end
def tee(buffer)
if buffer && buffer.size > 0
@tmp.write(buffer)
end
buffer
end
def read_exact(len, buf)
if @len
max_len = @len - @bytes_read
len = max_len if len > max_len
return nil if len == 0
end
ret = @socket.read(len, buf)
@bytes_read += ret.size if ret
ret
end
def read_all(buf)
if @len
ret = @socket.read(@len - @bytes_read, buf)
if ret
@bytes_read += ret.size
ret
else
buf.replace("")
buf
end
else
ret = @socket.read(nil, buf)
@bytes_read += ret.size
ret
end
end
end
end # module Utils
end # module PhusionPassenger