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 ]
TRACE_PATTERN =
/^((\w:){0,1}.*?):(\d+)(:?(.*))$/

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)



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/web/cgi.rb', line 64

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
  @cgd.output.binmode if @cgd.output.respond_to? :binmode
  
  @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.



31
32
33
# File 'lib/web/cgi.rb', line 31

def options
  @options
end

#sessionObject (readonly)

Returns the value of attribute session.



31
32
33
# File 'lib/web/cgi.rb', line 31

def session
  @session
end

Class Method Details

.basic_process(options = {}) ⇒ Object

:nodoc:



640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
# File 'lib/web/cgi.rb', line 640

def CGI::basic_process( options={}) #:nodoc:
  unless Thread::current[:in_process]
    Thread::current[:in_process] = true
    Web.set_cgi( CGI.new( options ) )
    
    begin
      case Web["narf_resource"]
      when "narf-logo.gif"
        Web::send_lib_file('resources/narf-logo.gif')
      when "narf-styles.css"
        Web::send_lib_file('resources/narf-styles.css')
      when "highlight_table.js"
        Web::send_lib_file('resources/highlight_table.js')
      else
        yield Web.get_cgi
      end
    rescue Exception => error
      if (options[:testing])
        raise error
      else
        Web::report_error( error )
      end
    end

    Web.get_cgi.close
    Thread::current[:in_process] = false
    Web.get_cgi
  else
    yield Web.get_cgi
    Web.get_cgi
  end
end

.create(options = {}) ⇒ Object



25
26
27
# File 'lib/web/cgi.rb', line 25

def CGI::create(options = {})
  CGI::new(options)
end

.get_cgi(options = {}) ⇒ Object

get the singleton cgi object for Web



17
18
19
20
21
22
23
# File 'lib/web/cgi.rb', line 17

def CGI.get_cgi( options={} )
  unless Thread.current[:cgi]
    CGI.set_cgi( Web::CGI.new(options) )
  end

  Thread.current[:cgi]
end

.process(options = {}, &block) ⇒ Object



681
682
683
684
685
686
687
688
689
690
# File 'lib/web/cgi.rb', line 681

def CGI::process( options={}, &block )
  if CGI::server_sniff == :fcgi
    FCGI::each_request{ |fcgi|
      options[:fcgi] = fcgi
      CGI::basic_process( options, &block )
    }
  else
    CGI::basic_process(options, &block )
  end
end

.server_sniffObject

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



627
628
629
630
631
632
633
634
635
636
637
638
# File 'lib/web/cgi.rb', line 627

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



12
13
14
# File 'lib/web/cgi.rb', line 12

def CGI.set_cgi cgi
  Thread.current[: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 = <<-EOF
<div>
<h1>Request Details</h1><br>
<table cellspacing="0" cellpadding="0" border="1" style="width:100%;border-collapse:collapse;">
<tr>
  <th class="alt" align="Left" colspan=2><h3><b>Request Parameters</b></h3></th></tr>
  <narf:foreach from=parameters item=parameter>
    <tr><th width=150>{$parameter.key}</th><td>{$parameter.value}</td></tr>
  </narf:foreach>
</table>
<br>
<table cellspacing="0" cellpadding="0" border="1" style="width:100%;border-collapse:collapse;">
  <tr><th class="alt" align="Left" colspan=2><h3><b>Cookies</b></h3></th></tr>
  <narf:foreach from=cookies item=cookie>
    <tr><th  width=150>{$cookie.key}</th><td>{$cookie.value}</td></tr>
  </narf:foreach>
</table>
<br>
<table cellspacing="0" cellpadding="0" border="1" style="width:100%;border-collapse:collapse;">
  <tr><th class="alt" align="Left" colspan=2><h3><b>Session</b></h3></th></tr>
  <narf:foreach from=session item=sessionitem>
    <tr><th  width=150>{$sessionitem.key}</th><td>{$sessionitem.value}</td></tr>
 </table>
</div>
EOF
end

Instance Method Details

#[](key) ⇒ Object

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



121
122
123
124
125
126
127
# File 'lib/web/cgi.rb', line 121

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



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

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



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

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.



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

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

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



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

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



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

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)



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

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

#cgdObject



45
46
47
# File 'lib/web/cgi.rb', line 45

def cgd
  @cgd
end

#charsetObject



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

def charset
  split_content_type[1]
end

#charset=(new_charset) ⇒ Object



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

def charset= new_charset
  set_full_content_type( content_type, new_charset )
end

#clearObject

Reset output buffer. Fails if headers have been sent.



181
182
183
184
185
186
187
188
# File 'lib/web/cgi.rb', line 181

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

#closeObject

flushes the output and, if applicable, saves the session



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

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”



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

def content_type
  split_content_type[0]
end

#content_type=(new_content_type) ⇒ Object



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

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



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

def cookies
  @cgd.cookies
end

#cookies_sentObject

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



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

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

#document_rootObject



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

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

#encodingObject



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

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

#encoding=(new_encoding) ⇒ Object



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

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

#envObject

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



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

def env
  @cgd.env
end

#flushObject

send header to the client, and flush any buffered output



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

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.



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

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

#get_contentObject

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



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

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?



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

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

#get_form(name) ⇒ Object

:nodoc:



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

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

#get_form_fields(name) ⇒ Object

:nodoc:



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

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

#get_form_value(formname, name) ⇒ Object

:nodoc:



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

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:



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

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.



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

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

#headerObject



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

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

#header_sent?Boolean

Returns:

  • (Boolean)


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

def header_sent?
  @header_sent
end

#key?(key) ⇒ Boolean

test whether a param was submitted

Returns:

  • (Boolean)


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

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

#keysObject

list the submitted params



153
154
155
# File 'lib/web/cgi.rb', line 153

def keys
  multiple_params.keys
end

#locationObject



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

def location
  get_header( "Location" ).first
end

#location=(location) ⇒ Object

see set redirect



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

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

#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.

Returns:

  • (Boolean)


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

def nph?
  @cgd.nph?
end

#paramsObject Also known as: multiple_params

returns params as a hash of arrays



101
102
103
# File 'lib/web/cgi.rb', line 101

def params
  @cgd.multiple_params
end

#params=(other) ⇒ Object Also known as: multiple_params=

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



108
109
110
# File 'lib/web/cgi.rb', line 108

def params= other
  @cgd.multiple_params = other
end

#path_infoObject



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

def path_info
  @cgd.env['path_info']
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
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
# File 'lib/web/cgi.rb', line 578

def report_error( err )
  # write to error log
  $stderr.binmode
  $stderr.puts err.class.to_s + ": " + err.to_s
  err.backtrace.each do |line|
    $stderr.puts "  " + line.chomp
  end
  # end write to error log
 
  # write to browser
  title = err.class.to_s
  
  msg = "<b style='font-size:20px'>" + err.to_s.gsub(/\n/,"<br>") + "</b><br>"
  msg += Time::now().to_s

  msg += <<-STYLE
<style type="text/css">
.columnHead {
  background-color:CCCDDD;
  text-align:center;
  font-weight:bold;
}
.info {
  text-align:center;
}
.info_row:hover{
  background-color:F8FF80;
}
</style>
STYLE
  msg += "<p><table onMouseover=\"changeto(event, '#F8FF80')\" onMouseout=\"changeback(event, '#eeeeff')\">\n"
  msg += "<tr><td class='columnHead'>File</td><td class='columnHead'>&nbsp;Line&nbsp;</td><td class='columnHead'>Method</td></tr>\n"
       
  err.backtrace.each do |level|
    level =~ TRACE_PATTERN
    level = [ $1, $3, $5 ]
    msg += "<tr class='info_row'>\n"
    level.each{ |column| msg += "<td class='info'>" + (column || '') + "</td>\n" }
    msg += "</tr>\n\n"
  end
  
  msg += "</table>"
  
  Web::print_message( title, msg )
  # end write to browser
end

#reset_headersObject



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

def reset_headers
    @headers_sent = false
end

#script_nameObject



37
38
39
# File 'lib/web/cgi.rb', line 37

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_file(filename) ⇒ Object

This method will replace the contents of the output buffer by reading from the given filename. If the content-type has not already been set, it will try and guess an appropriate mime-type from the extension of the file.

Note: this doesn’t flush the output, you can still keep writing after this method.



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

def send_file( filename )
  clear()
  write( File.open( filename, 'rb' ) { |f| f.read } )
  if self.content_type == "text/html"
    self.content_type = Web::Mime::get_mimetype(filename)
  end
  Web::close()
end

#send_headerObject

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



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

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.



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

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



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

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.



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

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( “,” )



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

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



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

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

#split_paramsObject

:nodoc:



148
149
150
# File 'lib/web/cgi.rb', line 148

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

#statusObject

the default status is 200



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

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

#status=(new_status) ⇒ Object



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

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

#trace_outputObject



88
89
90
91
92
93
94
95
96
97
# File 'lib/web/cgi.rb', line 88

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

Returns:

  • (Boolean)


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

def unbuffered?
  @options[:unbuffered]
end