rubysl-cgi 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+