Class: N::CgiUtils

Inherits:
Object show all
Defined in:
lib/nitro/adapters/cgi.rb

Overview

CGI utility methods.

Constant Summary collapse

CR =

HTTP protocol EOL constants

"\x0d"
LF =
"\x0a"
CRLF =
"\x0d\x0a"
EOL =
CRLF
STATUS_OK =

Constants for readable code

200
STATUS_PARTIAL_CONTENT =
206
STATUS_MOVED =
301
STATUS_REDIRECT =
302
STATUS_SEE_OTHER =

gmosx: VERIFY THIS

303
STATUS_SEE_OTHER_307 =

gmosx: VERIFY THIS

307
STATUS_NOT_MODIFIED =
304
STATUS_BAD_REQUEST =
400
STATUS_AUTH_REQUIRED =
401
STATUS_FORBIDDEN =
403
STATUS_NOT_FOUND =
404
STATUS_METHOD_NOT_ALLOWED =
405
STATUS_NOT_ACCEPTABLE =
406
STATUS_LENGTH_REQUIRED =
411
STATUS_PRECONDITION_FAILED =
412
STATUS_SERVER_ERROR =
500
STATUS_NOT_IMPLEMENTED =
501
STATUS_BAD_GATEWAY =
502
STATUS_VARIANT_ALSO_VARIES =
506
STATUS_STRINGS =

Hash to allow id to description maping.

{
	200 => "OK",
	206 => "Partial Content",
	300 => "Multiple Choices",
	301 => "Moved Permanently",
	302 => "Found",
	303 => "See other", # gmosx: VERIFY THIS
	304 => "Not Modified",
	307 => "See other 07", # gmosx: VERIFY THIS
	400 => "Bad Request",
	401 => "Authorization Required",
	403 => "Forbidden",
	404 => "Not Found",
	405 => "Method Not Allowed",
	406 => "Not Acceptable",
	411 => "Length Required",
	412 => "Precondition Failed",
	500 => "Internal Server Error",
	501 => "Method Not Implemented",
	502 => "Bad Gateway",
	506 => "Variant Also Negotiates"
}

Class Method Summary collapse

Class Method Details

.parse_cookies(context) ⇒ Object

Parse the HTTP_COOKIE header and returns the cookies as a key->value hash. For efficiency no cookie objects are created.

context

The context



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/nitro/adapters/cgi.rb', line 120

def self.parse_cookies(context) 
	env = context.env

	# FIXME: dont precreate?
	context.cookies = {}

	if env.has_key?('HTTP_COOKIE') or env.has_key?('COOKIE')    
		(env['HTTP_COOKIE'] or env['COOKIE']).split(/; /).each do |c|
			key, val = c.split(/=/, 2)
			
			key = CGI.unescape(key)
			val = val.split(/&/).collect{|v| CGI::unescape(v)}.join("\0")

			if context.cookies.include?(key)
				context.cookies[key] += "\0" + val
			else
				context.cookies[key] = val
			end
		end
	end
end

.parse_multipart(context, boundary) ⇒ Object

Parse a multipart request.



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
260
# File 'lib/nitro/adapters/cgi.rb', line 188

def self.parse_multipart(context, boundary)
	io = context.in
	env = context.headers

	io.binmode # if defined?(ins.binmode)

	buffer = ''
	bufsize = Cgi.buffer_size

	boundary = "--" + boundary
	boundary_size = boundary.size + EOL.size

	content_length = context.headers['CONTENT_LENGTH'].to_i

	if content_length > Cgi.max_content_length
		raise 'Request content length exceeds limit'
	end

	context.params = {}

	status = io.read(boundary_size)

	if (status.nil?) or ( (boundary + EOL) != status )
		raise 'Bad content body'
	end
	
	content_length -= boundary_size

	head = nil

	loop do 
		until head and /#{boundary}(?:#{EOL}|--)/n.match(buffer)
			if (not head) and /#{EOL}#{EOL}/n.match(buffer)
				buffer = buffer.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
					head = $1.dup
					""
				end
				next
			end

			puts "*** #{head}"
			
			/Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
			filename = $1
			if /Mac/ni.match(env['HTTP_USER_AGENT']) and
					/Mozilla/ni.match(env['HTTP_USER_AGENT']) and
					(not /MSIE/ni.match(env['HTTP_USER_AGENT']))
				filename = CGI::unescape(filename)
			end

			puts "--- f: #{filename}"
		
			if filename 
				/Content-Type: (.*)/ni.match(head)
				content_type = $1 or ''

				puts "--- c: #{content_type}"
			end
			
			chunk = if bufsize < content_length
						io.read(bufsize)
					else
						io.read(content_length)
					end

			raise 'Bad content body' unless chunk

				
			buffer << chunk 
			content_length -= chunk.size
		end
	end
end

.parse_params(context) ⇒ Object

Initialize the request params. Handles multipart forms (in particular, forms that involve file uploads). Reads query parameters in the @params field, and cookies into @cookies.



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/nitro/adapters/cgi.rb', line 165

def self.parse_params(context)
	method = context.method
	if (:post == method) and
			%r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n.match(context.headers['CONTENT_TYPE'])
		boundary = $1.dup
		# context.params = read_multipart(boundary, Integer(context.headers['CONTENT_LENGTH']), context.in, context.headers)
		parse_multipart(context, boundary)
	else 
		case method
		when :get, :head
			context.params = CgiUtils.parse_query_string(context.query_string)	
		when :post
			context.in.binmode #  if defined?(context.in.binmode)
									
			context.params = CgiUtils.parse_query_string(
					context.in.read(
					Integer(context.headers['CONTENT_LENGTH'])) || '')	
		end
	end
end

.parse_query_string(query_string) ⇒ Object

Returns a hash with the pairs from the query string. The implicit hash construction that is done in parse_request_params is not done here.

Parameters in the form xxx[] are converted to arrays.



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
# File 'lib/nitro/adapters/cgi.rb', line 86

def self.parse_query_string(query_string)

	# gmosx, THINK: better return nil here?
	return {} if (query_string.nil? or query_string.empty?)

	params = {}

	query_string.split(/[&;]/).each do |p| 
		key, val = p.split('=')
		
		key = CGI.unescape(key) unless key.nil?
		val = CGI.unescape(val) unless val.nil?

		if key =~ /(.*)\[\]$/
			if params.has_key?($1)
				params[$1] << val
			else
				params[$1] = [val]
			end
		else
			params[key] = val.nil? ? nil : val
		end
	end
	
	return params
end

.read_multipart(boundary, content_length, stdinput, env_table) ⇒ Object

Parse a multipart request. Copied from ruby’s cgi.rb



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/nitro/adapters/cgi.rb', line 265

def self.read_multipart(boundary, content_length, stdinput, env_table)
	params = Hash.new([])
	boundary = "--" + boundary
	buf = ""
	bufsize = 10 * 1024

	# start multipart/form-data
	stdinput.binmode if defined? stdinput.binmode
	
	if nil == status
		raise EOFError, "no content body"
	elsif boundary + EOL != status
		raise EOFError, "bad content body"
	end

	loop do
		head = nil
		if 10240 < content_length
			require "tempfile"
			body = Tempfile.new("CGI")
		else
			begin
				require "stringio"
				body = StringIO.new
			rescue LoadError
				require "tempfile"
				body = Tempfile.new("CGI")
			end
		end
		body.binmode if defined? body.binmode

		until head and /#{boundary}(?:#{EOL}|--)/n.match(buf)

			if (not head) and /#{EOL}#{EOL}/n.match(buf)
				buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
					head = $1.dup
					""
				end
				next
			end

			if head and ( (EOL + boundary + EOL).size < buf.size )
				body.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
				buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
			end

			c = if bufsize < content_length
						stdinput.read(bufsize)
					else
						stdinput.read(content_length)
					end
			if c.nil?
				raise EOFError, "bad content body"
			end
			buf.concat(c)
			content_length -= c.size
		end

		buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{boundary}([\r\n]{1,2}|--)/n) do
			body.print $1
			if "--" == $2
				content_length = -1
			end
			""
		end

		body.rewind

		/Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
filename = ($1 or "")
if /Mac/ni.match(env_table['HTTP_USER_AGENT']) and
	/Mozilla/ni.match(env_table['HTTP_USER_AGENT']) and
	(not /MSIE/ni.match(env_table['HTTP_USER_AGENT']))
filename = CGI::unescape(filename)
end
		
		/Content-Type: (.*)/ni.match(head)
		content_type = ($1 or "")

		(class << body; self; end).class_eval do
			alias local_path path
			define_method(:original_filename) {filename.dup.taint}
			define_method(:content_type) {content_type.dup.taint}
		end

		/Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
		name = $1.dup

		if name =~ /(.*)\[\]$/
			if params.has_name?($1)
				params[$1] << body
			else
				params[$1] = [body]
			end
		else
			params[name] = body.nil? ? nil : body
		end
		
		break if buf.size == 0
		break if content_length === -1
	end

	params
end

.response_headers(context) ⇒ Object

Build the response headers for the context.



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/nitro/adapters/cgi.rb', line 144

def self.response_headers(context)
	reason = STATUS_STRINGS[context.status] 
	buf = "HTTP/1.1 #{context.status} #{reason} #{EOL}"
	
	context.response_headers.each do |key, value|
		tmp = key.gsub(/\bwww|^te$|\b\w/) { |s| s.upcase }
		buf << "#{tmp}: #{value}" << EOL
	end

	context.response_cookies.each do |cookie|
		buf << "Set-Cookie: " << cookie.to_s << EOL
	end if context.response_cookies
		
	buf << EOL
end