Class: Web::CGI

Inherits:
Object show all
Includes:
Test::Unit::Assertions, TemplatePrinter
Defined in:
lib/web/cgi.rb,
lib/web/cgi.rb,
lib/web/forms.rb,
lib/web/template.rb,
lib/web/traceoutput.rb

Overview

Purpose

Web::CGI is the core of Narf. You will find documentation for many useful methods here. I recommend calling these methods through the Web module, which will manage the current CGI object and delegate to it.

Constant Summary collapse

MULTIPLE_KEY =
/\[\]\z/
ENV_KEYS =
[ :path_info, :document_root, :script_name ]
@@__web__cgi =
nil
@@cgi_instances =
{}
@@in_process =
false

Instance Attribute Summary collapse

Attributes included from TemplatePrinter

#template_name, #template_vars

Class Method Summary collapse

Instance Method Summary collapse

Methods included from TemplatePrinter

#assert_template_not_used, #assert_template_used, #assert_vars_equals, #assert_vars_includes, #print_template, #template_include_path

Methods included from Test::Unit::Assertions

#assert_includes

Constructor Details

#initialize(options = {}) ⇒ CGI

Construct cgi with the given options. Defaults are in parenthesis. These options are alpha and due for change as narf supports different backends.

:session

set the session to the given hash (Web::Session).

:cgd

pass in a CGD driver

:env

pass in the ENV variables

:in

set the input stream to the given IO ($stdin).

:out

set the output stream to the given IO ($stdout).

:unbuffered

flag whether output should(n’t) be buffered. (false)

:path_info

path_info is the part of the query that is after the script, i.e. the Narf.html in /wiki.rb/Narf.html (Request.path_info)

:document_root

Document root of website. (Request.document_root)

:script_name

Script name, ie /wiki.rb in /wiki.rb/Narf.html (Request.script_name)



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/web/cgi.rb', line 80

def initialize(options={})
  @options = options
  @cgd = options[:cgd] || CGD.create( options )

  ENV_KEYS.each { |symbol|
    env[symbol.to_s.downcase] = options[symbol] if options[symbol]
  }

  # set output buffer
  @content = StringIO.new
  if (unbuffered?)
    @output = @cgd.output
  else
    @output = StringIO.new
  end
  
  @session = if (options.has_key? :session)
               options[:session]
             else
               Session.new( self, options )
             end
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



41
42
43
# File 'lib/web/cgi.rb', line 41

def options
  @options
end

#sessionObject (readonly)

Returns the value of attribute session.



41
42
43
# File 'lib/web/cgi.rb', line 41

def session
  @session
end

Class Method Details

.basic_process(options = {}) ⇒ Object

:nodoc:



607
608
609
610
611
612
613
614
615
616
617
618
619
# File 'lib/web/cgi.rb', line 607

def CGI::basic_process( options={}) #:nodoc:
  unless @@in_process
    @@in_process = true
    Web.set_cgi( CGI.new( options ) )
    yield Web.get_cgi
    Web.get_cgi.close
    @@in_process = false
    Web.get_cgi
  else
    yield Web.get_cgi
    Web.get_cgi
  end
end

.create(options = {}) ⇒ Object



60
61
62
63
64
# File 'lib/web/cgi.rb', line 60

def CGI::create(options={}) 
  
  options[:out] ||= $stdout
  @@cgi_instances[ options[:out].id ] ||= CGI.new(options)
end

.get_cgi(options = {}) ⇒ Object

get the singleton cgi object for Web



19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/web/cgi.rb', line 19

def CGI.get_cgi( options={} )
  unless @@__web__cgi
    @@__web__cgi = Web::CGI.new(options)
    
    at_exit {
      @@__web__cgi.close if @@__web__cgi
    }
  end

  @@__web__cgi

end

.process(options = {}) {|cgi| ... } ⇒ Object

:nodoc:

Yields:

  • (cgi)


32
33
34
35
36
37
# File 'lib/web/cgi.rb', line 32

def CGI.process( options={} )   #:nodoc:
  cgi = Web::CGI.get_cgi( options )
  yield cgi
  cgi.close
  cgi
end

.server_sniffObject

returns one of these values: :cgi, :fastcgi, :mod_ruby, :webrick



593
594
595
596
597
598
599
600
601
602
603
604
# File 'lib/web/cgi.rb', line 593

def CGI::server_sniff
  if( Object.const_defined?( "Apache" ) \
      && Apache.const_defined?( "Request" ) \
      && $stdin.kind_of?( Apache::Request ) )
    :mod_ruby
  elsif ( Object.const_defined?( "FCGI" ) \
          && ! FCGI.is_cgi? )
    :fcgi
  else
    :cgi
  end
end

.set_cgi(cgi) ⇒ Object

set the singleton cgi object for Web



14
15
16
# File 'lib/web/cgi.rb', line 14

def CGI.set_cgi cgi
  @@__web__cgi = cgi
end

.trace_output_templateObject



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
# File 'lib/web/traceoutput.rb', line 3

def CGI.trace_output_template
      template = "<div>\n<h1>Request Details</h1><br>\n<table cellspacing=\"0\" cellpadding=\"0\" border=\"1\" style=\"width:100%;border-collapse:collapse;\">\n<tr>\n  <th class=\"alt\" align=\"Left\" colspan=2><h3><b>Request Parameters</b></h3></th></tr>\n  <narf:foreach from=parameters item=parameter>\n    <tr><th width=150>{$parameter.key}</th><td>{$parameter.value}</td></tr>\n  </narf:foreach>\n</table>\n<br>\n<table cellspacing=\"0\" cellpadding=\"0\" border=\"1\" style=\"width:100%;border-collapse:collapse;\">\n  <tr><th class=\"alt\" align=\"Left\" colspan=2><h3><b>Cookies</b></h3></th></tr>\n  <narf:foreach from=cookies item=cookie>\n    <tr><th  width=150>{$cookie.key}</th><td>{$cookie.value}</td></tr>\n  </narf:foreach>\n</table>\n<br>\n<table cellspacing=\"0\" cellpadding=\"0\" border=\"1\" style=\"width:100%;border-collapse:collapse;\">\n  <tr><th class=\"alt\" align=\"Left\" colspan=2><h3><b>Session</b></h3></th></tr>\n  <narf:foreach from=session item=sessionitem>\n    <tr><th  width=150>{$sessionitem.key}</th><td>{$sessionitem.value}</td></tr>\n </table>\n</div>\n"
end

Instance Method Details

#<<(*args) ⇒ Object

Append output to client



184
# File 'lib/web/cgi.rb', line 184

def << (*args) end

#[](key) ⇒ Object

access parameters. If the key is array-style, aka param[], return the array. Otherwise, return the joined string.



131
132
133
134
135
136
137
# File 'lib/web/cgi.rb', line 131

def [] (key)
  if (MULTIPLE_KEY =~ key)
    multiple_params[key]
  else
    single_param(key)
  end
end

#[]=(key, value) ⇒ Object

set param at the given key. This is useful to change state in your app without asking the browser to redirect



141
142
143
144
145
146
# File 'lib/web/cgi.rb', line 141

def []= (key, value)
  unless value.kind_of? Array
    value = [value]
  end
  multiple_params[key] = value
end

#add_header(name, value) ⇒ Object

There’s a bit of special casing in here, for the follow reasons:

  • some headers should only be sent once. If you add Content-Encoding, Location, or Status, it will overwrite the old headers instead of adding a second header

  • content-type is a strange header. It is a combination of the content-type and charset attributes. setting content-type will cause the content-type attribute to be set; setting charset will cause the charset attribute to be set.

    I don’t know if this is the correct behavior. Should this method assume that the content-type set here is the full content type, and try to split the header into it’s two parts?

  • If the headers have been sent, this will throw a Web::Error



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/web/cgi.rb', line 287

def add_header(name , value )
  unless header_sent?
    if (/content-encoding/i =~ name  )
      header['Content-Encoding'] = [value]
    elsif( /location/i =~ name )
      header['Location'] = [value]
    elsif( /status/i =~ name )
      if /^\d*$/ =~ value.to_s
        self.status = value
      else
        header['Status'] = [value]
      end
    elsif(/content-type/i =~ name)
      header['Content-Type'] = [value]
    elsif(/charset/i =~ name)
      self.charset = value
    else
      header[name] ||= []
      header[name].push value
    end
  else
    raise Web::Error.new( "Can't add_header after header have been sent to client" )
  end
end

#assert_content(expected, message = "") ⇒ Object

for testing: raises Test::Unit exception if content is not set to the provided string.



231
232
233
# File 'lib/web/cgi.rb', line 231

def assert_content expected, message=""
  assert_equal( expected, get_content, message );
end

Throws Test::Unit::AssertionFailedException if cookie values are not present.



476
477
478
# File 'lib/web/cgi.rb', line 476

def assert_cookie( name, values, message="" )
  assert_equal( [values].flatten, get_cookie(name), message )
end

#assert_form_includes(formname, vars) ⇒ Object

assert output content contains a form that includes the given hash of values. See Web::Testing



259
260
261
# File 'lib/web/cgi.rb', line 259

def assert_form_includes formname, vars
  assert_includes vars, get_form_fields(formname)
end

#assert_header(name, values, message = "") ⇒ Object

Raises Test::Unit::AssertionFailedError if the header has not been set to the provided value(s)



342
343
344
# File 'lib/web/cgi.rb', line 342

def assert_header( name, values, message="" )
  assert_equal( [values].flatten, get_header(name), message )
end

#cgdObject



55
56
57
# File 'lib/web/cgi.rb', line 55

def cgd
  @cgd
end

#charsetObject



406
407
408
# File 'lib/web/cgi.rb', line 406

def charset
  split_content_type[1]
end

#charset=(new_charset) ⇒ Object



410
411
412
# File 'lib/web/cgi.rb', line 410

def charset= new_charset
  set_full_content_type( content_type, new_charset )
end

#clearObject

Reset output buffer. Fails if headers have been sent.



202
203
204
205
206
207
208
# File 'lib/web/cgi.rb', line 202

def clear()
  if header_sent?
    raise( Exception.new( "Can't call Web::clear()" ) )
  else
    @output = StringIO.new
  end
end

#closeObject

flushes the output and, if applicable, saves the session



264
265
266
267
268
269
270
271
# File 'lib/web/cgi.rb', line 264

def close
  flush
  if $DISPLAY_TRACE
    trace_output
  end
  @session.save if (@session.respond_to? :save)
  @cgd.close
end

#content_typeObject

the default content-type is “text/html”



394
395
396
# File 'lib/web/cgi.rb', line 394

def content_type
  split_content_type[0]
end

#content_type=(new_content_type) ⇒ Object



398
399
400
401
402
403
404
# File 'lib/web/cgi.rb', line 398

def content_type= new_content_type
  content1, charset1 = split_content_type
  content2, charset2 = split_content_type( new_content_type )
  
  set_full_content_type( content2 || content1,
                         charset2 || charset1 )
end

#cookiesObject

array of cookies sent by the client



173
174
175
# File 'lib/web/cgi.rb', line 173

def cookies
  @cgd.cookies
end

#cookies_sentObject

returns a hash of all the cookie n/v pairs that have been set on the cgi



465
466
467
468
469
470
471
472
# File 'lib/web/cgi.rb', line 465

def cookies_sent
  cookies = {}
  get_header("Set-Cookie").each do |cookie|
    /\A(.*?)=([^;]*)/ =~ cookie
    cookies[$1] = $2
  end
  cookies
end

#document_rootObject



43
44
45
# File 'lib/web/cgi.rb', line 43

def document_root
  @cgd.env['document_root']
end

#encodingObject



371
372
373
# File 'lib/web/cgi.rb', line 371

def encoding
  get_header( "Content-Encoding" ).first
end

#encoding=(new_encoding) ⇒ Object



375
376
377
# File 'lib/web/cgi.rb', line 375

def encoding=( new_encoding )
  add_header( "Content-Encoding", new_encoding )
end

#envObject

ENV variables, scoped to the cgi (for in-process servers)



488
489
490
# File 'lib/web/cgi.rb', line 488

def env
  @cgd.env
end

#flushObject

send header to the client, and flush any buffered output



215
216
217
218
219
220
221
# File 'lib/web/cgi.rb', line 215

def flush
  unless unbuffered?
    send_header
    @cgd.output << @output.string
    @output = StringIO.new
  end
end

#full_content_typeObject

The content type header is a combination of the content_type and the charset. This method returns that combination.



385
386
387
# File 'lib/web/cgi.rb', line 385

def full_content_type
  get_header( "Content-Type" ).first
end

#get_contentObject

returns the body content of the response (sans headers).



225
226
227
# File 'lib/web/cgi.rb', line 225

def get_content
  @content.string
end

returns an array of cookie values that have been set. path / expires / etc. info is currently not returned, should be added in?



456
457
458
459
460
461
# File 'lib/web/cgi.rb', line 456

def get_cookie key
  get_header("Set-Cookie").collect{ |cookie|
    /\A(#{ key })=([^;]*)/ =~ cookie
    $2
  }.compact
end

#get_form(name) ⇒ Object

:nodoc:



239
240
241
# File 'lib/web/cgi.rb', line 239

def get_form( name ) #:nodoc:
  get_formreader[name]
end

#get_form_fields(name) ⇒ Object

:nodoc:



243
244
245
# File 'lib/web/cgi.rb', line 243

def get_form_fields(name) #:nodoc:
  get_formreader.get_fields(name)
end

#get_form_value(formname, name) ⇒ Object

:nodoc:



247
248
249
250
251
252
253
254
255
# File 'lib/web/cgi.rb', line 247

def get_form_value formname, name #:nodoc:
  form = get_form_fields(formname)
  if form == nil
    raise Web::Error.new("Form '#{formname}' does not exist") 
  end
  
  value = form[name]
  value
end

#get_formreaderObject

:nodoc:



235
236
237
# File 'lib/web/cgi.rb', line 235

def get_formreader #:nodoc:
  return @form_fields_cache ||= FormReader.new( get_content )
end

#get_header(name) ⇒ Object

returns an array of header values set with the given name.



313
314
315
316
317
318
319
# File 'lib/web/cgi.rb', line 313

def get_header name
  header.keys.find_all { |key|
    key.downcase == name.downcase
  }.collect{ |key|
    header[key].dup
  }.flatten
end

#headerObject



334
335
336
337
338
# File 'lib/web/cgi.rb', line 334

def header 
  @header ||= {"Content-Type" => ["text/html"],
               "Status" => ["200 OK"] }
  @header
end

#header_sent?Boolean



330
331
332
# File 'lib/web/cgi.rb', line 330

def header_sent?
  @header_sent
end

#key?(key) ⇒ Boolean

test whether a param was submitted



168
169
170
# File 'lib/web/cgi.rb', line 168

def key? (key)
  multiple_params.has_key? key
end

#keysObject

list the submitted params



163
164
165
# File 'lib/web/cgi.rb', line 163

def keys
  multiple_params.keys
end

#locationObject



356
357
358
# File 'lib/web/cgi.rb', line 356

def location
  get_header( "Location" ).first
end

#location=(location) ⇒ Object

see set redirect



361
362
363
# File 'lib/web/cgi.rb', line 361

def location= location
  add_header( "Location", location )
end

#make_query_string(params) ⇒ Object



72
73
74
75
76
# File 'lib/web/forms.rb', line 72

def make_query_string params
    params.collect do |a,b|
  "#{a}=#{Web.escape(b)}"
    end.join("&")
end

#multiple_paramsObject

returns params as a hash of arrays



116
117
118
# File 'lib/web/cgi.rb', line 116

def multiple_params
  @cgd.multiple_params
end

#multiple_params=(other) ⇒ Object

set multiple params, in case you want to change the state of your app without a round trip to the client.



122
123
124
# File 'lib/web/cgi.rb', line 122

def multiple_params= other
  @cgd.multiple_params = other
end

#nph?Boolean

Aside from when :nph is set in the options, scripts running in IIS always use nph mode. This code will probably be affected as cgi is re-organized to support multiple backends.



483
484
485
# File 'lib/web/cgi.rb', line 483

def nph?
  @cgd.nph?
end

#path_infoObject



51
52
53
# File 'lib/web/cgi.rb', line 51

def path_info
  @cgd.env['path_info']
end

Append output to client



190
# File 'lib/web/cgi.rb', line 190

def print(*args) end

#puts(*args) ⇒ Object

Append output to client with line endings



186
# File 'lib/web/cgi.rb', line 186

def puts(*args) end

#queryObject



78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/web/forms.rb', line 78

def query
    if Web["__submitted"] != ""
  aquery = {}
  typed_params.each do |k,v|
      if k =~ /^__q\.(.+)/
    aquery[$1] = v
      end
  end
  aquery
    else
  typed_params
    end
end

#report_error(err) ⇒ Object



578
579
580
581
582
583
584
585
586
587
588
589
# File 'lib/web/cgi.rb', line 578

def report_error( err )
  self.puts "<h3>ruby error:</h3>"
  self.puts Dir.pwd
  self.puts err.to_s.gsub(/\n/,"<br><br>")
  self.puts "<blockquote>"
  self.puts( if (err.backtrace.size > 3)
               err.backtrace[0...-2]
             else
               err.backtrace
             end).collect{ |string| string + "<br>" }
  self.puts "</blockquote>"
end

#reset_headersObject



92
93
94
# File 'lib/web/forms.rb', line 92

def reset_headers
    @headers_sent = false
end

#script_nameObject



47
48
49
# File 'lib/web/cgi.rb', line 47

def script_name
  @cgd.env['script_name']
end

#script_pathObject



96
97
98
99
100
101
102
# File 'lib/web/forms.rb', line 96

def script_path
    if m = /(.*)\//.match(Web.get_cgi.script_name)
  $1
    else
  ""
    end
end

#send_headerObject

Send header to the client. No more header values can be sent after this method is called!



323
324
325
326
327
328
# File 'lib/web/cgi.rb', line 323

def send_header
  unless header_sent?
    @cgd.send_header( self.header )
    @header_sent = true
  end
end

Cookies require a name and a value. You can also use these optional keyword arguments:

:path => <i>string<i>

path (need better description)

:domain => string

domain (need better description)

:expires => date

date this cookie should expire

:secure => true || false

whether this cookie should be tagged as secure.



421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/web/cgi.rb', line 421

def set_cookie( name, value, options={} )
  value = Array(value).collect{ |field|
    Web::escape(field)
  }.join("&")
  
  cookie = "#{ name }=#{ value }"
  
  
  path = if (options[:path])
           options[:path]
         else
           %r|^(.*/)|.match(env["script_name"])
           ($1 or "")
         end
  
  cookie += "; path=#{ path }"
  
  if (options[:domain])
    cookie += "; domain=#{ options[:domain] }"
  end
  
  if (options[:expires])
    cookie += "; expires=#{ Web::rfc1123_date( options[:expires] ) }"
  end
  
  if (options[:secure])
    cookie += "; secure"
  end
  
  add_header( "Set-Cookie", cookie )
end

#set_full_content_type(new_content, new_charset) ⇒ Object



389
390
391
# File 'lib/web/cgi.rb', line 389

def set_full_content_type( new_content, new_charset )
  add_header( "Content-type", [ new_content, new_charset ].compact.join('; charset=') )
end

#set_redirect(new_location) ⇒ Object

Sets the status and the location appropriately.



366
367
368
369
# File 'lib/web/cgi.rb', line 366

def set_redirect( new_location )
  self.status = "302"
  self.location = new_location
end

#single_param(key) ⇒ Object

If params[0] is a Web::Upload, returns that value. Otherwise it returns params.join( “,” )



150
151
152
153
154
155
156
# File 'lib/web/cgi.rb', line 150

def single_param(key)
  if (multiple_params[key].first.kind_of? Web::Upload)
    multiple_params[key].first
  else
    multiple_params[key].join( "," )
  end
end

#split_content_type(target = full_content_type) ⇒ Object



379
380
381
# File 'lib/web/cgi.rb', line 379

def split_content_type( target = full_content_type )
  target.split( Regexp.new('; charset=') )
end

#split_paramsObject

:nodoc:



158
159
160
# File 'lib/web/cgi.rb', line 158

def split_params #:nodoc:
  Web::Testing::MultiHashTree.new(multiple_params).fields
end

#statusObject

the default status is 200



347
348
349
350
# File 'lib/web/cgi.rb', line 347

def status
  get_header( "Status" ).first =~ /^(\d+)( .*)?$/ 
  $1
end

#status=(new_status) ⇒ Object



352
353
354
# File 'lib/web/cgi.rb', line 352

def status= new_status
  add_header( "Status", "#{ new_status } #{ Web::HTTP_STATUS[new_status.to_s] }" )
end

#trace_outputObject



103
104
105
106
107
108
109
110
111
112
# File 'lib/web/cgi.rb', line 103

def trace_output
  templater = Narflates.new(CGI.trace_output_template,{})
  templater.parse(self,{ "parameters" => 
                        multiple_params.collect { |key,value| { "key" => key, "value" => value } },
                        "cookies" =>
                        cookies.collect { |key,value| { "key" => key, "value" => value } },
                        "session" =>
                        session.collect { |key,value| { "key" => key, "value" => value } } })
  flush
end

#typed_paramsObject



68
69
70
# File 'lib/web/forms.rb', line 68

def typed_params
    Request.typed_params multiple_params
end

#unbuffered?Boolean



210
211
212
# File 'lib/web/cgi.rb', line 210

def unbuffered?
  @options[:unbuffered]
end

#write(*args) ⇒ Object

Append output to client



188
# File 'lib/web/cgi.rb', line 188

def write(*args) end