rubysl-cgi 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ca1e8dbe8f76a7d86da0d860b5a3c41b53056b5b
4
- data.tar.gz: f661b2e445100662dba142d2e30e383985c6b892
3
+ metadata.gz: a954e484bc04491a8e3a1b0c3cf1ae3215524f57
4
+ data.tar.gz: a6e590cf123f141d7d01118e29ae3e9ea41d59f2
5
5
  SHA512:
6
- metadata.gz: c81bd82dfec68d8f91b3957375fb0484e3dce0da709d168f839c85d5277f8df6bd1aebb6f373f1f35a1f1b24b687de64a703392c3a6471d0aff8de1b122e0fcf
7
- data.tar.gz: 760e91d5984724defc82b925025fcc9a0f205a3e32c48db01f4da306d47bb34c73591ce12b685257c7b5c09ced959cbe4e667106882fffe86e2f90229ebcaeb6
6
+ metadata.gz: 550528ca4b546b3d0a8e2c0e035df813d75c01ccab75f6499cfc27603f7f53774fc610c12223f0454682de7edbe9515a91e464f0a78e6d454af92dacc10d28dd
7
+ data.tar.gz: c05811504f35f8ac07efb920d6f5a4065ad7675772ce294f718ecd91bc8efee849c4e650ebc363cee2a68434766fda77c857f2b51b5109da8dfbf2cc9f45b3a3
data/.gitignore CHANGED
@@ -15,4 +15,3 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
- .rbx
@@ -1,8 +1,7 @@
1
1
  language: ruby
2
- before_install:
3
- - gem update --system
4
- - gem --version
5
- - gem install rubysl-bundler
6
- script: bundle exec mspec spec
2
+ env:
3
+ - RUBYLIB=lib
4
+ script: bundle exec mspec
7
5
  rvm:
8
- - rbx-nightly-18mode
6
+ - 1.9.3
7
+ - rbx-nightly-19mode
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Brian Shirai
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # RubySL::Cgi
1
+ # Rubysl::Cgi
2
2
 
3
3
  TODO: Write a gem description
4
4
 
@@ -24,6 +24,6 @@ TODO: Write usage instructions here
24
24
 
25
25
  1. Fork it
26
26
  2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Added some feature'`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
28
  4. Push to the branch (`git push origin my-new-feature`)
29
29
  5. Create new Pull Request
data/Rakefile CHANGED
@@ -1,2 +1 @@
1
- #!/usr/bin/env rake
2
1
  require "bundler/gem_tasks"
@@ -0,0 +1,165 @@
1
+ require 'cgi/util'
2
+ class CGI
3
+ @@accept_charset="UTF-8" unless defined?(@@accept_charset)
4
+ # Class representing an HTTP cookie.
5
+ #
6
+ # In addition to its specific fields and methods, a Cookie instance
7
+ # is a delegator to the array of its values.
8
+ #
9
+ # See RFC 2965.
10
+ #
11
+ # == Examples of use
12
+ # cookie1 = CGI::Cookie::new("name", "value1", "value2", ...)
13
+ # cookie1 = CGI::Cookie::new("name" => "name", "value" => "value")
14
+ # cookie1 = CGI::Cookie::new('name' => 'name',
15
+ # 'value' => ['value1', 'value2', ...],
16
+ # 'path' => 'path', # optional
17
+ # 'domain' => 'domain', # optional
18
+ # 'expires' => Time.now, # optional
19
+ # 'secure' => true # optional
20
+ # )
21
+ #
22
+ # cgi.out("cookie" => [cookie1, cookie2]) { "string" }
23
+ #
24
+ # name = cookie1.name
25
+ # values = cookie1.value
26
+ # path = cookie1.path
27
+ # domain = cookie1.domain
28
+ # expires = cookie1.expires
29
+ # secure = cookie1.secure
30
+ #
31
+ # cookie1.name = 'name'
32
+ # cookie1.value = ['value1', 'value2', ...]
33
+ # cookie1.path = 'path'
34
+ # cookie1.domain = 'domain'
35
+ # cookie1.expires = Time.now + 30
36
+ # cookie1.secure = true
37
+ class Cookie < Array
38
+
39
+ # Create a new CGI::Cookie object.
40
+ #
41
+ # :call-seq:
42
+ # Cookie.new(name_string,*value)
43
+ # Cookie.new(options_hash)
44
+ #
45
+ # +name_string+::
46
+ # The name of the cookie; in this form, there is no #domain or
47
+ # #expiration. The #path is gleaned from the +SCRIPT_NAME+ environment
48
+ # variable, and #secure is false.
49
+ # <tt>*value</tt>::
50
+ # value or list of values of the cookie
51
+ # +options_hash+::
52
+ # A Hash of options to initialize this Cookie. Possible options are:
53
+ #
54
+ # name:: the name of the cookie. Required.
55
+ # value:: the cookie's value or list of values.
56
+ # path:: the path for which this cookie applies. Defaults to the
57
+ # the value of the +SCRIPT_NAME+ environment variable.
58
+ # domain:: the domain for which this cookie applies.
59
+ # expires:: the time at which this cookie expires, as a +Time+ object.
60
+ # secure:: whether this cookie is a secure cookie or not (default to
61
+ # false). Secure cookies are only transmitted to HTTPS
62
+ # servers.
63
+ #
64
+ # These keywords correspond to attributes of the cookie object.
65
+ def initialize(name = "", *value)
66
+ @domain = nil
67
+ @expires = nil
68
+ if name.kind_of?(String)
69
+ @name = name
70
+ %r|^(.*/)|.match(ENV["SCRIPT_NAME"])
71
+ @path = ($1 or "")
72
+ @secure = false
73
+ return super(value)
74
+ end
75
+
76
+ options = name
77
+ unless options.has_key?("name")
78
+ raise ArgumentError, "`name' required"
79
+ end
80
+
81
+ @name = options["name"]
82
+ value = Array(options["value"])
83
+ # simple support for IE
84
+ if options["path"]
85
+ @path = options["path"]
86
+ else
87
+ %r|^(.*/)|.match(ENV["SCRIPT_NAME"])
88
+ @path = ($1 or "")
89
+ end
90
+ @domain = options["domain"]
91
+ @expires = options["expires"]
92
+ @secure = options["secure"] == true ? true : false
93
+
94
+ super(value)
95
+ end
96
+
97
+ # Name of this cookie, as a +String+
98
+ attr_accessor :name
99
+ # Path for which this cookie applies, as a +String+
100
+ attr_accessor :path
101
+ # Domain for which this cookie applies, as a +String+
102
+ attr_accessor :domain
103
+ # Time at which this cookie expires, as a +Time+
104
+ attr_accessor :expires
105
+ # True if this cookie is secure; false otherwise
106
+ attr_reader("secure")
107
+
108
+ # Returns the value or list of values for this cookie.
109
+ def value
110
+ self
111
+ end
112
+
113
+ # Replaces the value of this cookie with a new value or list of values.
114
+ def value=(val)
115
+ replace(Array(val))
116
+ end
117
+
118
+ # Set whether the Cookie is a secure cookie or not.
119
+ #
120
+ # +val+ must be a boolean.
121
+ def secure=(val)
122
+ @secure = val if val == true or val == false
123
+ @secure
124
+ end
125
+
126
+ # Convert the Cookie to its string representation.
127
+ def to_s
128
+ val = collect{|v| CGI::escape(v) }.join("&")
129
+ buf = "#{@name}=#{val}"
130
+ buf << "; domain=#{@domain}" if @domain
131
+ buf << "; path=#{@path}" if @path
132
+ buf << "; expires=#{CGI::rfc1123_date(@expires)}" if @expires
133
+ buf << "; secure" if @secure == true
134
+ buf
135
+ end
136
+
137
+ end # class Cookie
138
+
139
+ # Parse a raw cookie string into a hash of cookie-name=>Cookie
140
+ # pairs.
141
+ #
142
+ # cookies = CGI::Cookie::parse("raw_cookie_string")
143
+ # # { "name1" => cookie1, "name2" => cookie2, ... }
144
+ #
145
+ def Cookie::parse(raw_cookie)
146
+ cookies = Hash.new([])
147
+ return cookies unless raw_cookie
148
+
149
+ raw_cookie.split(/[;,]\s?/).each do |pairs|
150
+ name, values = pairs.split('=',2)
151
+ next unless name and values
152
+ name = CGI::unescape(name)
153
+ values ||= ""
154
+ values = values.split('&').collect{|v| CGI::unescape(v,@@accept_charset) }
155
+ if cookies.has_key?(name)
156
+ values = cookies[name].value + values
157
+ end
158
+ cookies[name] = Cookie::new(name, *values)
159
+ end
160
+
161
+ cookies
162
+ end
163
+ end
164
+
165
+
@@ -0,0 +1,864 @@
1
+ #--
2
+ # Methods for generating HTML, parsing CGI-related parameters, and
3
+ # generating HTTP responses.
4
+ #++
5
+ class CGI
6
+
7
+ $CGI_ENV = ENV # for FCGI support
8
+
9
+ # String for carriage return
10
+ CR = "\015"
11
+
12
+ # String for linefeed
13
+ LF = "\012"
14
+
15
+ # Standard internet newline sequence
16
+ EOL = CR + LF
17
+
18
+ REVISION = '$Id$' #:nodoc:
19
+
20
+ # Whether processing will be required in binary vs text
21
+ NEEDS_BINMODE = File::BINARY != 0
22
+
23
+ # Path separators in different environments.
24
+ PATH_SEPARATOR = {'UNIX'=>'/', 'WINDOWS'=>'\\', 'MACINTOSH'=>':'}
25
+
26
+ # HTTP status codes.
27
+ HTTP_STATUS = {
28
+ "OK" => "200 OK",
29
+ "PARTIAL_CONTENT" => "206 Partial Content",
30
+ "MULTIPLE_CHOICES" => "300 Multiple Choices",
31
+ "MOVED" => "301 Moved Permanently",
32
+ "REDIRECT" => "302 Found",
33
+ "NOT_MODIFIED" => "304 Not Modified",
34
+ "BAD_REQUEST" => "400 Bad Request",
35
+ "AUTH_REQUIRED" => "401 Authorization Required",
36
+ "FORBIDDEN" => "403 Forbidden",
37
+ "NOT_FOUND" => "404 Not Found",
38
+ "METHOD_NOT_ALLOWED" => "405 Method Not Allowed",
39
+ "NOT_ACCEPTABLE" => "406 Not Acceptable",
40
+ "LENGTH_REQUIRED" => "411 Length Required",
41
+ "PRECONDITION_FAILED" => "412 Precondition Failed",
42
+ "SERVER_ERROR" => "500 Internal Server Error",
43
+ "NOT_IMPLEMENTED" => "501 Method Not Implemented",
44
+ "BAD_GATEWAY" => "502 Bad Gateway",
45
+ "VARIANT_ALSO_VARIES" => "506 Variant Also Negotiates"
46
+ }
47
+
48
+ # :startdoc:
49
+
50
+ # Synonym for ENV.
51
+ def env_table
52
+ ENV
53
+ end
54
+
55
+ # Synonym for $stdin.
56
+ def stdinput
57
+ $stdin
58
+ end
59
+
60
+ # Synonym for $stdout.
61
+ def stdoutput
62
+ $stdout
63
+ end
64
+
65
+ private :env_table, :stdinput, :stdoutput
66
+
67
+ # Create an HTTP header block as a string.
68
+ #
69
+ # :call-seq:
70
+ # http_header(content_type_string="text/html")
71
+ # http_header(headers_hash)
72
+ #
73
+ # Includes the empty line that ends the header block.
74
+ #
75
+ # +content_type_string+::
76
+ # If this form is used, this string is the <tt>Content-Type</tt>
77
+ # +headers_hash+::
78
+ # A Hash of header values. The following header keys are recognized:
79
+ #
80
+ # type:: The Content-Type header. Defaults to "text/html"
81
+ # charset:: The charset of the body, appended to the Content-Type header.
82
+ # nph:: A boolean value. If true, prepend protocol string and status
83
+ # code, and date; and sets default values for "server" and
84
+ # "connection" if not explicitly set.
85
+ # status::
86
+ # The HTTP status code as a String, returned as the Status header. The
87
+ # values are:
88
+ #
89
+ # OK:: 200 OK
90
+ # PARTIAL_CONTENT:: 206 Partial Content
91
+ # MULTIPLE_CHOICES:: 300 Multiple Choices
92
+ # MOVED:: 301 Moved Permanently
93
+ # REDIRECT:: 302 Found
94
+ # NOT_MODIFIED:: 304 Not Modified
95
+ # BAD_REQUEST:: 400 Bad Request
96
+ # AUTH_REQUIRED:: 401 Authorization Required
97
+ # FORBIDDEN:: 403 Forbidden
98
+ # NOT_FOUND:: 404 Not Found
99
+ # METHOD_NOT_ALLOWED:: 405 Method Not Allowed
100
+ # NOT_ACCEPTABLE:: 406 Not Acceptable
101
+ # LENGTH_REQUIRED:: 411 Length Required
102
+ # PRECONDITION_FAILED:: 412 Precondition Failed
103
+ # SERVER_ERROR:: 500 Internal Server Error
104
+ # NOT_IMPLEMENTED:: 501 Method Not Implemented
105
+ # BAD_GATEWAY:: 502 Bad Gateway
106
+ # VARIANT_ALSO_VARIES:: 506 Variant Also Negotiates
107
+ #
108
+ # server:: The server software, returned as the Server header.
109
+ # connection:: The connection type, returned as the Connection header (for
110
+ # instance, "close".
111
+ # length:: The length of the content that will be sent, returned as the
112
+ # Content-Length header.
113
+ # language:: The language of the content, returned as the Content-Language
114
+ # header.
115
+ # expires:: The time on which the current content expires, as a +Time+
116
+ # object, returned as the Expires header.
117
+ # cookie::
118
+ # A cookie or cookies, returned as one or more Set-Cookie headers. The
119
+ # value can be the literal string of the cookie; a CGI::Cookie object;
120
+ # an Array of literal cookie strings or Cookie objects; or a hash all of
121
+ # whose values are literal cookie strings or Cookie objects.
122
+ #
123
+ # These cookies are in addition to the cookies held in the
124
+ # @output_cookies field.
125
+ #
126
+ # Other headers can also be set; they are appended as key: value.
127
+ #
128
+ # Examples:
129
+ #
130
+ # http_header
131
+ # # Content-Type: text/html
132
+ #
133
+ # http_header("text/plain")
134
+ # # Content-Type: text/plain
135
+ #
136
+ # http_header("nph" => true,
137
+ # "status" => "OK", # == "200 OK"
138
+ # # "status" => "200 GOOD",
139
+ # "server" => ENV['SERVER_SOFTWARE'],
140
+ # "connection" => "close",
141
+ # "type" => "text/html",
142
+ # "charset" => "iso-2022-jp",
143
+ # # Content-Type: text/html; charset=iso-2022-jp
144
+ # "length" => 103,
145
+ # "language" => "ja",
146
+ # "expires" => Time.now + 30,
147
+ # "cookie" => [cookie1, cookie2],
148
+ # "my_header1" => "my_value"
149
+ # "my_header2" => "my_value")
150
+ #
151
+ # This method does not perform charset conversion.
152
+ def http_header(options='text/html')
153
+ if options.is_a?(String)
154
+ content_type = options
155
+ buf = _header_for_string(content_type)
156
+ elsif options.is_a?(Hash)
157
+ if options.size == 1 && options.has_key?('type')
158
+ content_type = options['type']
159
+ buf = _header_for_string(content_type)
160
+ else
161
+ buf = _header_for_hash(options.dup)
162
+ end
163
+ else
164
+ raise ArgumentError.new("expected String or Hash but got #{options.class}")
165
+ end
166
+ if defined?(MOD_RUBY)
167
+ _header_for_modruby(buf)
168
+ return ''
169
+ else
170
+ buf << EOL # empty line of separator
171
+ return buf
172
+ end
173
+ end # http_header()
174
+
175
+ # This method is an alias for #http_header, when HTML5 tag maker is inactive.
176
+ #
177
+ # NOTE: use #http_header to create HTTP header blocks, this alias is only
178
+ # provided for backwards compatibility.
179
+ #
180
+ # Using #header with the HTML5 tag maker will create a <header> element.
181
+ alias :header :http_header
182
+
183
+ def _header_for_string(content_type) #:nodoc:
184
+ buf = ''
185
+ if nph?()
186
+ buf << "#{$CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'} 200 OK#{EOL}"
187
+ buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
188
+ buf << "Server: #{$CGI_ENV['SERVER_SOFTWARE']}#{EOL}"
189
+ buf << "Connection: close#{EOL}"
190
+ end
191
+ buf << "Content-Type: #{content_type}#{EOL}"
192
+ if @output_cookies
193
+ @output_cookies.each {|cookie| buf << "Set-Cookie: #{cookie}#{EOL}" }
194
+ end
195
+ return buf
196
+ end # _header_for_string
197
+ private :_header_for_string
198
+
199
+ def _header_for_hash(options) #:nodoc:
200
+ buf = ''
201
+ ## add charset to option['type']
202
+ options['type'] ||= 'text/html'
203
+ charset = options.delete('charset')
204
+ options['type'] += "; charset=#{charset}" if charset
205
+ ## NPH
206
+ options.delete('nph') if defined?(MOD_RUBY)
207
+ if options.delete('nph') || nph?()
208
+ protocol = $CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'
209
+ status = options.delete('status')
210
+ status = HTTP_STATUS[status] || status || '200 OK'
211
+ buf << "#{protocol} #{status}#{EOL}"
212
+ buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
213
+ options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || ''
214
+ options['connection'] ||= 'close'
215
+ end
216
+ ## common headers
217
+ status = options.delete('status')
218
+ buf << "Status: #{HTTP_STATUS[status] || status}#{EOL}" if status
219
+ server = options.delete('server')
220
+ buf << "Server: #{server}#{EOL}" if server
221
+ connection = options.delete('connection')
222
+ buf << "Connection: #{connection}#{EOL}" if connection
223
+ type = options.delete('type')
224
+ buf << "Content-Type: #{type}#{EOL}" #if type
225
+ length = options.delete('length')
226
+ buf << "Content-Length: #{length}#{EOL}" if length
227
+ language = options.delete('language')
228
+ buf << "Content-Language: #{language}#{EOL}" if language
229
+ expires = options.delete('expires')
230
+ buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires
231
+ ## cookie
232
+ if cookie = options.delete('cookie')
233
+ case cookie
234
+ when String, Cookie
235
+ buf << "Set-Cookie: #{cookie}#{EOL}"
236
+ when Array
237
+ arr = cookie
238
+ arr.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
239
+ when Hash
240
+ hash = cookie
241
+ hash.each {|name, c| buf << "Set-Cookie: #{c}#{EOL}" }
242
+ end
243
+ end
244
+ if @output_cookies
245
+ @output_cookies.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
246
+ end
247
+ ## other headers
248
+ options.each do |key, value|
249
+ buf << "#{key}: #{value}#{EOL}"
250
+ end
251
+ return buf
252
+ end # _header_for_hash
253
+ private :_header_for_hash
254
+
255
+ def nph? #:nodoc:
256
+ return /IIS\/(\d+)/.match($CGI_ENV['SERVER_SOFTWARE']) && $1.to_i < 5
257
+ end
258
+
259
+ def _header_for_modruby(buf) #:nodoc:
260
+ request = Apache::request
261
+ buf.scan(/([^:]+): (.+)#{EOL}/o) do |name, value|
262
+ warn sprintf("name:%s value:%s\n", name, value) if $DEBUG
263
+ case name
264
+ when 'Set-Cookie'
265
+ request.headers_out.add(name, value)
266
+ when /^status$/i
267
+ request.status_line = value
268
+ request.status = value.to_i
269
+ when /^content-type$/i
270
+ request.content_type = value
271
+ when /^content-encoding$/i
272
+ request.content_encoding = value
273
+ when /^location$/i
274
+ request.status = 302 if request.status == 200
275
+ request.headers_out[name] = value
276
+ else
277
+ request.headers_out[name] = value
278
+ end
279
+ end
280
+ request.send_http_header
281
+ return ''
282
+ end
283
+ private :_header_for_modruby
284
+
285
+ # Print an HTTP header and body to $DEFAULT_OUTPUT ($>)
286
+ #
287
+ # :call-seq:
288
+ # cgi.out(content_type_string='text/html')
289
+ # cgi.out(headers_hash)
290
+ #
291
+ # +content_type_string+::
292
+ # If a string is passed, it is assumed to be the content type.
293
+ # +headers_hash+::
294
+ # This is a Hash of headers, similar to that used by #http_header.
295
+ # +block+::
296
+ # A block is required and should evaluate to the body of the response.
297
+ #
298
+ # <tt>Content-Length</tt> is automatically calculated from the size of
299
+ # the String returned by the content block.
300
+ #
301
+ # If <tt>ENV['REQUEST_METHOD'] == "HEAD"</tt>, then only the header
302
+ # is output (the content block is still required, but it is ignored).
303
+ #
304
+ # If the charset is "iso-2022-jp" or "euc-jp" or "shift_jis" then the
305
+ # content is converted to this charset, and the language is set to "ja".
306
+ #
307
+ # Example:
308
+ #
309
+ # cgi = CGI.new
310
+ # cgi.out{ "string" }
311
+ # # Content-Type: text/html
312
+ # # Content-Length: 6
313
+ # #
314
+ # # string
315
+ #
316
+ # cgi.out("text/plain") { "string" }
317
+ # # Content-Type: text/plain
318
+ # # Content-Length: 6
319
+ # #
320
+ # # string
321
+ #
322
+ # cgi.out("nph" => true,
323
+ # "status" => "OK", # == "200 OK"
324
+ # "server" => ENV['SERVER_SOFTWARE'],
325
+ # "connection" => "close",
326
+ # "type" => "text/html",
327
+ # "charset" => "iso-2022-jp",
328
+ # # Content-Type: text/html; charset=iso-2022-jp
329
+ # "language" => "ja",
330
+ # "expires" => Time.now + (3600 * 24 * 30),
331
+ # "cookie" => [cookie1, cookie2],
332
+ # "my_header1" => "my_value",
333
+ # "my_header2" => "my_value") { "string" }
334
+ # # HTTP/1.1 200 OK
335
+ # # Date: Sun, 15 May 2011 17:35:54 GMT
336
+ # # Server: Apache 2.2.0
337
+ # # Connection: close
338
+ # # Content-Type: text/html; charset=iso-2022-jp
339
+ # # Content-Length: 6
340
+ # # Content-Language: ja
341
+ # # Expires: Tue, 14 Jun 2011 17:35:54 GMT
342
+ # # Set-Cookie: foo
343
+ # # Set-Cookie: bar
344
+ # # my_header1: my_value
345
+ # # my_header2: my_value
346
+ # #
347
+ # # string
348
+ def out(options = "text/html") # :yield:
349
+
350
+ options = { "type" => options } if options.kind_of?(String)
351
+ content = yield
352
+ options["length"] = content.bytesize.to_s
353
+ output = stdoutput
354
+ output.binmode if defined? output.binmode
355
+ output.print http_header(options)
356
+ output.print content unless "HEAD" == env_table['REQUEST_METHOD']
357
+ end
358
+
359
+
360
+ # Print an argument or list of arguments to the default output stream
361
+ #
362
+ # cgi = CGI.new
363
+ # cgi.print # default: cgi.print == $DEFAULT_OUTPUT.print
364
+ def print(*options)
365
+ stdoutput.print(*options)
366
+ end
367
+
368
+ # Parse an HTTP query string into a hash of key=>value pairs.
369
+ #
370
+ # params = CGI::parse("query_string")
371
+ # # {"name1" => ["value1", "value2", ...],
372
+ # # "name2" => ["value1", "value2", ...], ... }
373
+ #
374
+ def CGI::parse(query)
375
+ params = {}
376
+ query.split(/[&;]/).each do |pairs|
377
+ key, value = pairs.split('=',2).collect{|v| CGI::unescape(v) }
378
+
379
+ next unless key
380
+
381
+ params[key] ||= []
382
+ params[key].push(value) if value
383
+ end
384
+
385
+ params.default=[].freeze
386
+ params
387
+ end
388
+
389
+ # Maximum content length of post data
390
+ ##MAX_CONTENT_LENGTH = 2 * 1024 * 1024
391
+
392
+ # Maximum content length of multipart data
393
+ MAX_MULTIPART_LENGTH = 128 * 1024 * 1024
394
+
395
+ # Maximum number of request parameters when multipart
396
+ MAX_MULTIPART_COUNT = 128
397
+
398
+ # Mixin module that provides the following:
399
+ #
400
+ # 1. Access to the CGI environment variables as methods. See
401
+ # documentation to the CGI class for a list of these variables. The
402
+ # methods are exposed by removing the leading +HTTP_+ (if it exists) and
403
+ # downcasing the name. For example, +auth_type+ will return the
404
+ # environment variable +AUTH_TYPE+, and +accept+ will return the value
405
+ # for +HTTP_ACCEPT+.
406
+ #
407
+ # 2. Access to cookies, including the cookies attribute.
408
+ #
409
+ # 3. Access to parameters, including the params attribute, and overloading
410
+ # #[] to perform parameter value lookup by key.
411
+ #
412
+ # 4. The initialize_query method, for initializing the above
413
+ # mechanisms, handling multipart forms, and allowing the
414
+ # class to be used in "offline" mode.
415
+ #
416
+ module QueryExtension
417
+
418
+ %w[ CONTENT_LENGTH SERVER_PORT ].each do |env|
419
+ define_method(env.sub(/^HTTP_/, '').downcase) do
420
+ (val = env_table[env]) && Integer(val)
421
+ end
422
+ end
423
+
424
+ %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
425
+ PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_HOST
426
+ REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME
427
+ SERVER_NAME SERVER_PROTOCOL SERVER_SOFTWARE
428
+
429
+ HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
430
+ HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
431
+ HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
432
+ define_method(env.sub(/^HTTP_/, '').downcase) do
433
+ env_table[env]
434
+ end
435
+ end
436
+
437
+ # Get the raw cookies as a string.
438
+ def raw_cookie
439
+ env_table["HTTP_COOKIE"]
440
+ end
441
+
442
+ # Get the raw RFC2965 cookies as a string.
443
+ def raw_cookie2
444
+ env_table["HTTP_COOKIE2"]
445
+ end
446
+
447
+ # Get the cookies as a hash of cookie-name=>Cookie pairs.
448
+ attr_accessor :cookies
449
+
450
+ # Get the parameters as a hash of name=>values pairs, where
451
+ # values is an Array.
452
+ attr_reader :params
453
+
454
+ # Get the uploaded files as a hash of name=>values pairs
455
+ attr_reader :files
456
+
457
+ # Set all the parameters.
458
+ def params=(hash)
459
+ @params.clear
460
+ @params.update(hash)
461
+ end
462
+
463
+ ##
464
+ # Parses multipart form elements according to
465
+ # http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
466
+ #
467
+ # Returns a hash of multipart form parameters with bodies of type StringIO or
468
+ # Tempfile depending on whether the multipart form element exceeds 10 KB
469
+ #
470
+ # params[name => body]
471
+ #
472
+ def read_multipart(boundary, content_length)
473
+ ## read first boundary
474
+ stdin = stdinput
475
+ first_line = "--#{boundary}#{EOL}"
476
+ content_length -= first_line.bytesize
477
+ status = stdin.read(first_line.bytesize)
478
+ raise EOFError.new("no content body") unless status
479
+ raise EOFError.new("bad content body") unless first_line == status
480
+ ## parse and set params
481
+ params = {}
482
+ @files = {}
483
+ boundary_rexp = /--#{Regexp.quote(boundary)}(#{EOL}|--)/
484
+ boundary_size = "#{EOL}--#{boundary}#{EOL}".bytesize
485
+ boundary_end = nil
486
+ buf = ''
487
+ bufsize = 10 * 1024
488
+ max_count = MAX_MULTIPART_COUNT
489
+ n = 0
490
+ tempfiles = []
491
+ while true
492
+ (n += 1) < max_count or raise StandardError.new("too many parameters.")
493
+ ## create body (StringIO or Tempfile)
494
+ body = create_body(bufsize < content_length)
495
+ tempfiles << body if defined?(Tempfile) && body.kind_of?(Tempfile)
496
+ class << body
497
+ if method_defined?(:path)
498
+ alias local_path path
499
+ else
500
+ def local_path
501
+ nil
502
+ end
503
+ end
504
+ attr_reader :original_filename, :content_type
505
+ end
506
+ ## find head and boundary
507
+ head = nil
508
+ separator = EOL * 2
509
+ until head && matched = boundary_rexp.match(buf)
510
+ if !head && pos = buf.index(separator)
511
+ len = pos + EOL.bytesize
512
+ head = buf[0, len]
513
+ buf = buf[(pos+separator.bytesize)..-1]
514
+ else
515
+ if head && buf.size > boundary_size
516
+ len = buf.size - boundary_size
517
+ body.print(buf[0, len])
518
+ buf[0, len] = ''
519
+ end
520
+ c = stdin.read(bufsize < content_length ? bufsize : content_length)
521
+ raise EOFError.new("bad content body") if c.nil? || c.empty?
522
+ buf << c
523
+ content_length -= c.bytesize
524
+ end
525
+ end
526
+ ## read to end of boundary
527
+ m = matched
528
+ len = m.begin(0)
529
+ s = buf[0, len]
530
+ if s =~ /(\r?\n)\z/
531
+ s = buf[0, len - $1.bytesize]
532
+ end
533
+ body.print(s)
534
+ buf = buf[m.end(0)..-1]
535
+ boundary_end = m[1]
536
+ content_length = -1 if boundary_end == '--'
537
+ ## reset file cursor position
538
+ body.rewind
539
+ ## original filename
540
+ /Content-Disposition:.* filename=(?:"(.*?)"|([^;\r\n]*))/i.match(head)
541
+ filename = $1 || $2 || ''
542
+ filename = CGI.unescape(filename) if unescape_filename?()
543
+ body.instance_variable_set(:@original_filename, filename.taint)
544
+ ## content type
545
+ /Content-Type: (.*)/i.match(head)
546
+ (content_type = $1 || '').chomp!
547
+ body.instance_variable_set(:@content_type, content_type.taint)
548
+ ## query parameter name
549
+ /Content-Disposition:.* name=(?:"(.*?)"|([^;\r\n]*))/i.match(head)
550
+ name = $1 || $2 || ''
551
+ if body.original_filename.empty?
552
+ value=body.read.dup.force_encoding(@accept_charset)
553
+ body.unlink if defined?(Tempfile) && body.kind_of?(Tempfile)
554
+ (params[name] ||= []) << value
555
+ unless value.valid_encoding?
556
+ if @accept_charset_error_block
557
+ @accept_charset_error_block.call(name,value)
558
+ else
559
+ raise InvalidEncoding,"Accept-Charset encoding error"
560
+ end
561
+ end
562
+ class << params[name].last;self;end.class_eval do
563
+ define_method(:read){self}
564
+ define_method(:original_filename){""}
565
+ define_method(:content_type){""}
566
+ end
567
+ else
568
+ (params[name] ||= []) << body
569
+ @files[name]=body
570
+ end
571
+ ## break loop
572
+ break if content_length == -1
573
+ end
574
+ raise EOFError, "bad boundary end of body part" unless boundary_end =~ /--/
575
+ params.default = []
576
+ params
577
+ ensure
578
+ if $! && tempfiles
579
+ tempfiles.each {|t|
580
+ if t.path
581
+ t.unlink
582
+ end
583
+ }
584
+ end
585
+ end # read_multipart
586
+ private :read_multipart
587
+ def create_body(is_large) #:nodoc:
588
+ if is_large
589
+ require 'tempfile'
590
+ body = Tempfile.new('CGI', encoding: "ascii-8bit")
591
+ else
592
+ begin
593
+ require 'stringio'
594
+ body = StringIO.new("".force_encoding("ascii-8bit"))
595
+ rescue LoadError
596
+ require 'tempfile'
597
+ body = Tempfile.new('CGI', encoding: "ascii-8bit")
598
+ end
599
+ end
600
+ body.binmode if defined? body.binmode
601
+ return body
602
+ end
603
+ def unescape_filename? #:nodoc:
604
+ user_agent = $CGI_ENV['HTTP_USER_AGENT']
605
+ return /Mac/i.match(user_agent) && /Mozilla/i.match(user_agent) && !/MSIE/i.match(user_agent)
606
+ end
607
+
608
+ # offline mode. read name=value pairs on standard input.
609
+ def read_from_cmdline
610
+ require "shellwords"
611
+
612
+ string = unless ARGV.empty?
613
+ ARGV.join(' ')
614
+ else
615
+ if STDIN.tty?
616
+ STDERR.print(
617
+ %|(offline mode: enter name=value pairs on standard input)\n|
618
+ )
619
+ end
620
+ array = readlines rescue nil
621
+ if not array.nil?
622
+ array.join(' ').gsub(/\n/n, '')
623
+ else
624
+ ""
625
+ end
626
+ end.gsub(/\\=/n, '%3D').gsub(/\\&/n, '%26')
627
+
628
+ words = Shellwords.shellwords(string)
629
+
630
+ if words.find{|x| /=/n.match(x) }
631
+ words.join('&')
632
+ else
633
+ words.join('+')
634
+ end
635
+ end
636
+ private :read_from_cmdline
637
+
638
+ # A wrapper class to use a StringIO object as the body and switch
639
+ # to a TempFile when the passed threshold is passed.
640
+ # Initialize the data from the query.
641
+ #
642
+ # Handles multipart forms (in particular, forms that involve file uploads).
643
+ # Reads query parameters in the @params field, and cookies into @cookies.
644
+ def initialize_query()
645
+ if ("POST" == env_table['REQUEST_METHOD']) and
646
+ %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|.match(env_table['CONTENT_TYPE'])
647
+ raise StandardError.new("too large multipart data.") if env_table['CONTENT_LENGTH'].to_i > MAX_MULTIPART_LENGTH
648
+ boundary = $1.dup
649
+ @multipart = true
650
+ @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
651
+ else
652
+ @multipart = false
653
+ @params = CGI::parse(
654
+ case env_table['REQUEST_METHOD']
655
+ when "GET", "HEAD"
656
+ if defined?(MOD_RUBY)
657
+ Apache::request.args or ""
658
+ else
659
+ env_table['QUERY_STRING'] or ""
660
+ end
661
+ when "POST"
662
+ stdinput.binmode if defined? stdinput.binmode
663
+ stdinput.read(Integer(env_table['CONTENT_LENGTH'])) or ''
664
+ else
665
+ read_from_cmdline
666
+ end.dup.force_encoding(@accept_charset)
667
+ )
668
+ unless Encoding.find(@accept_charset) == Encoding::ASCII_8BIT
669
+ @params.each do |key,values|
670
+ values.each do |value|
671
+ unless value.valid_encoding?
672
+ if @accept_charset_error_block
673
+ @accept_charset_error_block.call(key,value)
674
+ else
675
+ raise InvalidEncoding,"Accept-Charset encoding error"
676
+ end
677
+ end
678
+ end
679
+ end
680
+ end
681
+ end
682
+
683
+ @cookies = CGI::Cookie::parse((env_table['HTTP_COOKIE'] or env_table['COOKIE']))
684
+ end
685
+ private :initialize_query
686
+
687
+ # Returns whether the form contained multipart/form-data
688
+ def multipart?
689
+ @multipart
690
+ end
691
+
692
+ # Get the value for the parameter with a given key.
693
+ #
694
+ # If the parameter has multiple values, only the first will be
695
+ # retrieved; use #params to get the array of values.
696
+ def [](key)
697
+ params = @params[key]
698
+ return '' unless params
699
+ value = params[0]
700
+ if @multipart
701
+ if value
702
+ return value
703
+ elsif defined? StringIO
704
+ StringIO.new("".force_encoding("ascii-8bit"))
705
+ else
706
+ Tempfile.new("CGI",encoding:"ascii-8bit")
707
+ end
708
+ else
709
+ str = if value then value.dup else "" end
710
+ str
711
+ end
712
+ end
713
+
714
+ # Return all query parameter names as an array of String.
715
+ def keys(*args)
716
+ @params.keys(*args)
717
+ end
718
+
719
+ # Returns true if a given query string parameter exists.
720
+ def has_key?(*args)
721
+ @params.has_key?(*args)
722
+ end
723
+ alias key? has_key?
724
+ alias include? has_key?
725
+
726
+ end # QueryExtension
727
+
728
+ # Exception raised when there is an invalid encoding detected
729
+ class InvalidEncoding < Exception; end
730
+
731
+ # @@accept_charset is default accept character set.
732
+ # This default value default is "UTF-8"
733
+ # If you want to change the default accept character set
734
+ # when create a new CGI instance, set this:
735
+ #
736
+ # CGI.accept_charset = "EUC-JP"
737
+ #
738
+ @@accept_charset="UTF-8"
739
+
740
+ # Return the accept character set for all new CGI instances.
741
+ def self.accept_charset
742
+ @@accept_charset
743
+ end
744
+
745
+ # Set the accept character set for all new CGI instances.
746
+ def self.accept_charset=(accept_charset)
747
+ @@accept_charset=accept_charset
748
+ end
749
+
750
+ # Return the accept character set for this CGI instance.
751
+ attr_reader :accept_charset
752
+
753
+ # Create a new CGI instance.
754
+ #
755
+ # :call-seq:
756
+ # CGI.new(tag_maker) { block }
757
+ # CGI.new(options_hash = {}) { block }
758
+ #
759
+ #
760
+ # <tt>tag_maker</tt>::
761
+ # This is the same as using the +options_hash+ form with the value <tt>{
762
+ # :tag_maker => tag_maker }</tt> Note that it is recommended to use the
763
+ # +options_hash+ form, since it also allows you specify the charset you
764
+ # will accept.
765
+ # <tt>options_hash</tt>::
766
+ # A Hash that recognizes two options:
767
+ #
768
+ # <tt>:accept_charset</tt>::
769
+ # specifies encoding of received query string. If omitted,
770
+ # <tt>@@accept_charset</tt> is used. If the encoding is not valid, a
771
+ # CGI::InvalidEncoding will be raised.
772
+ #
773
+ # Example. Suppose <tt>@@accept_charset</tt> is "UTF-8"
774
+ #
775
+ # when not specified:
776
+ #
777
+ # cgi=CGI.new # @accept_charset # => "UTF-8"
778
+ #
779
+ # when specified as "EUC-JP":
780
+ #
781
+ # cgi=CGI.new(:accept_charset => "EUC-JP") # => "EUC-JP"
782
+ #
783
+ # <tt>:tag_maker</tt>::
784
+ # String that specifies which version of the HTML generation methods to
785
+ # use. If not specified, no HTML generation methods will be loaded.
786
+ #
787
+ # The following values are supported:
788
+ #
789
+ # "html3":: HTML 3.x
790
+ # "html4":: HTML 4.0
791
+ # "html4Tr":: HTML 4.0 Transitional
792
+ # "html4Fr":: HTML 4.0 with Framesets
793
+ # "html5":: HTML 5
794
+ #
795
+ # <tt>block</tt>::
796
+ # If provided, the block is called when an invalid encoding is
797
+ # encountered. For example:
798
+ #
799
+ # encoding_errors={}
800
+ # cgi=CGI.new(:accept_charset=>"EUC-JP") do |name,value|
801
+ # encoding_errors[name] = value
802
+ # end
803
+ #
804
+ # Finally, if the CGI object is not created in a standard CGI call
805
+ # environment (that is, it can't locate REQUEST_METHOD in its environment),
806
+ # then it will run in "offline" mode. In this mode, it reads its parameters
807
+ # from the command line or (failing that) from standard input. Otherwise,
808
+ # cookies and other parameters are parsed automatically from the standard
809
+ # CGI locations, which varies according to the REQUEST_METHOD.
810
+ def initialize(options = {}, &block) # :yields: name, value
811
+ @accept_charset_error_block = block_given? ? block : nil
812
+ @options={:accept_charset=>@@accept_charset}
813
+ case options
814
+ when Hash
815
+ @options.merge!(options)
816
+ when String
817
+ @options[:tag_maker]=options
818
+ end
819
+ @accept_charset=@options[:accept_charset]
820
+ if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE")
821
+ Apache.request.setup_cgi_env
822
+ end
823
+
824
+ extend QueryExtension
825
+ @multipart = false
826
+
827
+ initialize_query() # set @params, @cookies
828
+ @output_cookies = nil
829
+ @output_hidden = nil
830
+
831
+ case @options[:tag_maker]
832
+ when "html3"
833
+ require 'cgi/html'
834
+ extend Html3
835
+ element_init()
836
+ extend HtmlExtension
837
+ when "html4"
838
+ require 'cgi/html'
839
+ extend Html4
840
+ element_init()
841
+ extend HtmlExtension
842
+ when "html4Tr"
843
+ require 'cgi/html'
844
+ extend Html4Tr
845
+ element_init()
846
+ extend HtmlExtension
847
+ when "html4Fr"
848
+ require 'cgi/html'
849
+ extend Html4Tr
850
+ element_init()
851
+ extend Html4Fr
852
+ element_init()
853
+ extend HtmlExtension
854
+ when "html5"
855
+ require 'cgi/html'
856
+ extend Html5
857
+ element_init()
858
+ extend HtmlExtension
859
+ end
860
+ end
861
+
862
+ end # class CGI
863
+
864
+