bijou 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.txt +4 -0
- data/LICENSE.txt +58 -0
- data/README.txt +48 -0
- data/Rakefile +105 -0
- data/doc/INSTALL.rdoc +260 -0
- data/doc/README.rdoc +314 -0
- data/doc/releases/bijou-0.1.0.rdoc +60 -0
- data/examples/birthday/birthday.rb +34 -0
- data/examples/holiday/holiday.rb +61 -0
- data/examples/holiday/letterhead.txt +4 -0
- data/examples/holiday/signature.txt +9 -0
- data/examples/phishing/letter.txt +29 -0
- data/examples/phishing/letterhead.txt +4 -0
- data/examples/phishing/phishing.rb +21 -0
- data/examples/phishing/signature.txt +9 -0
- data/examples/profile/profile.rb +46 -0
- data/lib/bijou.rb +15 -0
- data/lib/bijou/backend.rb +542 -0
- data/lib/bijou/cgi/adapter.rb +201 -0
- data/lib/bijou/cgi/handler.rb +5 -0
- data/lib/bijou/cgi/request.rb +37 -0
- data/lib/bijou/common.rb +12 -0
- data/lib/bijou/component.rb +108 -0
- data/lib/bijou/config.rb +60 -0
- data/lib/bijou/console/adapter.rb +167 -0
- data/lib/bijou/console/handler.rb +4 -0
- data/lib/bijou/console/request.rb +26 -0
- data/lib/bijou/context.rb +431 -0
- data/lib/bijou/diagnostics.rb +87 -0
- data/lib/bijou/errorformatter.rb +322 -0
- data/lib/bijou/exception.rb +39 -0
- data/lib/bijou/filters.rb +107 -0
- data/lib/bijou/httprequest.rb +108 -0
- data/lib/bijou/httpresponse.rb +268 -0
- data/lib/bijou/lexer.rb +513 -0
- data/lib/bijou/minicgi.rb +159 -0
- data/lib/bijou/parser.rb +1026 -0
- data/lib/bijou/processor.rb +404 -0
- data/lib/bijou/prstringio.rb +400 -0
- data/lib/bijou/webrick/adapter.rb +174 -0
- data/lib/bijou/webrick/handler.rb +32 -0
- data/lib/bijou/webrick/request.rb +45 -0
- data/script/cgi.rb +25 -0
- data/script/console.rb +7 -0
- data/script/server.rb +7 -0
- data/test/t1.cfg +5 -0
- data/test/tc_config.rb +26 -0
- data/test/tc_filter.rb +25 -0
- data/test/tc_lexer.rb +120 -0
- data/test/tc_response.rb +103 -0
- data/test/tc_ruby.rb +62 -0
- data/test/tc_stack.rb +50 -0
- metadata +121 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2007-2008 Todd Lucas. All rights reserved.
|
3
|
+
#
|
4
|
+
# filters.rb - Contains built-in filter definitions
|
5
|
+
#
|
6
|
+
require 'cgi'
|
7
|
+
require 'bijou/common'
|
8
|
+
|
9
|
+
module Bijou
|
10
|
+
#
|
11
|
+
# A filter may be used to modify text from an output tag prior to rendering.
|
12
|
+
# For example, the output tag:
|
13
|
+
#
|
14
|
+
# <%= user.first_name | h %>
|
15
|
+
#
|
16
|
+
# Will pipe the text returned from the first_name method through the +h+
|
17
|
+
# filter. The <tt>h</tt> filter represents Bijou::EncodeHTML and serves to
|
18
|
+
# escape the text for HTML presentation.
|
19
|
+
#
|
20
|
+
# Note that filters may be chained by listing them one after the other
|
21
|
+
# following the pipe separator. For example, the following output tag and
|
22
|
+
# filter sequence:
|
23
|
+
#
|
24
|
+
# <%= user.first_name | th %>
|
25
|
+
#
|
26
|
+
# first invokes the +t+ filter, which trims text using EncodeTrim, and then
|
27
|
+
# pipes the result of that to the +h+ filter, for final HTML presentation.
|
28
|
+
#
|
29
|
+
class Filter
|
30
|
+
# Used during code generation to produce the code that will be used to
|
31
|
+
# call the apply method.
|
32
|
+
def render(input)
|
33
|
+
"Bijou::Filter.apply(#{input})"
|
34
|
+
end
|
35
|
+
|
36
|
+
# The base filter is a no-op, returning the original string, unaltered.
|
37
|
+
def self.apply(input)
|
38
|
+
input # no-op
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# The +h+ filter escapes the input for HTML presentation.
|
43
|
+
class EncodeHTML < Filter
|
44
|
+
def render(input)
|
45
|
+
"Bijou::EncodeHTML.apply(#{input})"
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.apply(input)
|
49
|
+
::CGI::escapeHTML(input)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# The +u+ filter escapes the input, replacing characters that are unsafe for
|
54
|
+
# passing as a URL parameter, with their escaped equivalents.
|
55
|
+
class EncodeURL < Filter
|
56
|
+
def render(input)
|
57
|
+
"Bijou::EncodeURL.apply(#{input})"
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.apply(input)
|
61
|
+
::CGI::escape(input)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# The +a+ filter escapes text intended for HTML tag attribute values.
|
66
|
+
class EncodeAttributeValue < Filter
|
67
|
+
def render(input)
|
68
|
+
"Bijou::EncodeAttribute.apply(#{input})"
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.apply(input)
|
72
|
+
input.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/\'/n, ''').gsub(/</n, '<')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Removes
|
77
|
+
class EncodeTrim < Filter
|
78
|
+
def render(input)
|
79
|
+
"Bijou::EncodeTrim.apply(#{input})"
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.apply(input)
|
83
|
+
#s = input.dup
|
84
|
+
#s.sub!(/^\s*/, "")
|
85
|
+
#s.sub!(/\s*$/, "")
|
86
|
+
#s
|
87
|
+
input.strip
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
#--
|
92
|
+
# TODO: Move this into an installable filter.
|
93
|
+
#++
|
94
|
+
#
|
95
|
+
# This filter doesn't really do anything. It's a basic example of a filter
|
96
|
+
# that might take Wiki-ized text and display as HTML.
|
97
|
+
#
|
98
|
+
class EncodeWiki < Filter
|
99
|
+
def render(input)
|
100
|
+
"Bijou::EncodeWiki.apply(#{input})"
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.apply(input)
|
104
|
+
input.gsub /\n/, "<br/>\n";
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2007-2008 Todd Lucas. All rights reserved.
|
3
|
+
#
|
4
|
+
# httprequest.rb - The base HttpRequest class
|
5
|
+
#
|
6
|
+
require 'bijou/common'
|
7
|
+
|
8
|
+
module Bijou
|
9
|
+
#
|
10
|
+
# This class represents an incoming HTTP request, presenting a common
|
11
|
+
# interface to the Bijou component regardless of the server environment.
|
12
|
+
#
|
13
|
+
class HttpRequest
|
14
|
+
def initialize
|
15
|
+
@params = {}
|
16
|
+
@query_string = {}
|
17
|
+
@form = {}
|
18
|
+
@server_variables = {}
|
19
|
+
@cookies = {}
|
20
|
+
@http_method = ''
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns a hash containing items passed via the HTTP query string.
|
24
|
+
# Synonym for #get.
|
25
|
+
attr_reader :query_string
|
26
|
+
|
27
|
+
# Returns a hash containing form data passed via the HTTP POST action.
|
28
|
+
# Synonym for #post.
|
29
|
+
attr_reader :form
|
30
|
+
|
31
|
+
# Returns a hash containing the combined values of the query string,
|
32
|
+
# post data, and cookies.
|
33
|
+
attr_reader :params
|
34
|
+
|
35
|
+
#
|
36
|
+
# A hash containing standard CGI environment variables.
|
37
|
+
#
|
38
|
+
# AUTH_TYPE HTTP_HOST REMOTE_IDENT
|
39
|
+
# CONTENT_LENGTH HTTP_NEGOTIATE REMOTE_USER
|
40
|
+
# CONTENT_TYPE HTTP_PRAGMA REQUEST_METHOD
|
41
|
+
# GATEWAY_INTERFACE HTTP_REFERER SCRIPT_NAME
|
42
|
+
# HTTP_ACCEPT HTTP_USER_AGENT SERVER_NAME
|
43
|
+
# HTTP_ACCEPT_CHARSET PATH_INFO SERVER_PORT
|
44
|
+
# HTTP_ACCEPT_ENCODING PATH_TRANSLATED SERVER_PROTOCOL
|
45
|
+
# HTTP_ACCEPT_LANGUAGE QUERY_STRING SERVER_SOFTWARE
|
46
|
+
# HTTP_CACHE_CONTROL REMOTE_ADDR
|
47
|
+
# HTTP_FROM REMOTE_HOST
|
48
|
+
#
|
49
|
+
# As well as additions that aren't present in cgi.rb.
|
50
|
+
#
|
51
|
+
# REQUEST_URI
|
52
|
+
#
|
53
|
+
attr_reader :server_variables
|
54
|
+
|
55
|
+
# A hash containing cookies sent by the client.
|
56
|
+
attr_reader :cookies
|
57
|
+
|
58
|
+
# A string representing the HTTP request method (e.g., GET, POST, or HEAD).
|
59
|
+
attr_reader :http_method
|
60
|
+
|
61
|
+
# Returns a hash containing items passed via the HTTP query string.
|
62
|
+
# Synonym for #query_string.
|
63
|
+
def get
|
64
|
+
@query_string
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns a hash containing form data passed via the HTTP POST action.
|
68
|
+
# Synonym for #form.
|
69
|
+
def post
|
70
|
+
@form
|
71
|
+
end
|
72
|
+
|
73
|
+
# TODO: These should be moved to CGI::Request
|
74
|
+
def referrer
|
75
|
+
@server_variables['HTTP_REFERER']
|
76
|
+
end
|
77
|
+
|
78
|
+
def referer
|
79
|
+
@server_variables['HTTP_REFERER']
|
80
|
+
end
|
81
|
+
|
82
|
+
#--
|
83
|
+
# ASP.NET has:
|
84
|
+
# path, path_info, url
|
85
|
+
# which don't directly correspond to Apache CGI variables.
|
86
|
+
# def url
|
87
|
+
# @server_variables['REQUEST_URI']
|
88
|
+
# end
|
89
|
+
#++
|
90
|
+
|
91
|
+
def virtual_path
|
92
|
+
# @server_variables['REQUEST_URI']
|
93
|
+
@server_variables['PATH_INFO']
|
94
|
+
end
|
95
|
+
|
96
|
+
def physical_path
|
97
|
+
@server_variables['PATH_TRANSLATED']
|
98
|
+
end
|
99
|
+
|
100
|
+
def request_method
|
101
|
+
@server_variables['REQUEST_METHOD']
|
102
|
+
end
|
103
|
+
|
104
|
+
def user_agent
|
105
|
+
@server_variables['HTTP_USER_AGENT']
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2007-2008 Todd Lucas. All rights reserved.
|
3
|
+
#
|
4
|
+
# httpresponse.rb - The base HttpResponse class
|
5
|
+
#
|
6
|
+
require 'cgi' # for encoding functions
|
7
|
+
require 'bijou/common'
|
8
|
+
|
9
|
+
module Bijou
|
10
|
+
class HttpResponse
|
11
|
+
ContentType = 'Content-Type'
|
12
|
+
SetCookie = 'Set-Cookie'
|
13
|
+
Location = 'Location'
|
14
|
+
DefaultContentType = 'text/html'
|
15
|
+
DefaultCharset = 'iso-8859-1'
|
16
|
+
|
17
|
+
CR = "\015"
|
18
|
+
LF = "\012"
|
19
|
+
EOL = CR + LF
|
20
|
+
|
21
|
+
attr_accessor :buffer_output
|
22
|
+
|
23
|
+
# Some environments generate their own headers. Headers should be
|
24
|
+
# suppressed in such cases. Note, however that response header attributes,
|
25
|
+
# such as content_type, will have no effect.
|
26
|
+
attr_accessor :suppress_headers
|
27
|
+
|
28
|
+
# A flag indicating whether to suppress content rendering to the client.
|
29
|
+
attr_accessor :suppress_content
|
30
|
+
|
31
|
+
attr_accessor :status
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@buffer_output = true
|
35
|
+
@content_type = DefaultContentType
|
36
|
+
@charset = DefaultCharset
|
37
|
+
@headers = {}
|
38
|
+
@error_headers = {}
|
39
|
+
@cookies = []
|
40
|
+
@output = ''
|
41
|
+
@log = ''
|
42
|
+
@suppress_headers = false
|
43
|
+
@suppress_content = false
|
44
|
+
|
45
|
+
@status = 200
|
46
|
+
update_content_type_header
|
47
|
+
end
|
48
|
+
|
49
|
+
@@status_map = {
|
50
|
+
200 => "200 OK",
|
51
|
+
206 => "206 Partial Content",
|
52
|
+
300 => "300 Multiple Choices",
|
53
|
+
301 => "301 Moved Permanently",
|
54
|
+
302 => "302 Found",
|
55
|
+
304 => "304 Not Modified",
|
56
|
+
400 => "400 Bad Request",
|
57
|
+
401 => "401 Authorization Required",
|
58
|
+
403 => "403 Forbidden",
|
59
|
+
404 => "404 Not Found",
|
60
|
+
405 => "405 Method Not Allowed",
|
61
|
+
406 => "406 Not Acceptable",
|
62
|
+
411 => "411 Length Required",
|
63
|
+
412 => "412 Precondition Failed",
|
64
|
+
500 => "500 Internal Server Error",
|
65
|
+
501 => "501 Method Not Implemented",
|
66
|
+
502 => "502 Bad Gateway",
|
67
|
+
506 => "506 Variant Also Negotiates",
|
68
|
+
}
|
69
|
+
|
70
|
+
def clear_headers
|
71
|
+
@headers = {}
|
72
|
+
@content_type = nil
|
73
|
+
@charset = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def clear_content
|
77
|
+
@output = ''
|
78
|
+
end
|
79
|
+
|
80
|
+
def clear
|
81
|
+
clear_headers
|
82
|
+
clear_content
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sets the HTTP MIME type of the response. The default
|
86
|
+
# is <tt>"text/html"</tt>
|
87
|
+
def content_type=(str)
|
88
|
+
@content_type = str
|
89
|
+
update_content_type_header
|
90
|
+
end
|
91
|
+
|
92
|
+
# Gets the HTTP MIME type of the response. The default
|
93
|
+
# is <tt>"text/html"</tt>
|
94
|
+
def content_type
|
95
|
+
@content_type
|
96
|
+
end
|
97
|
+
|
98
|
+
def charset=(str)
|
99
|
+
@charset = str
|
100
|
+
update_content_type_header
|
101
|
+
end
|
102
|
+
|
103
|
+
def charset
|
104
|
+
@charset
|
105
|
+
end
|
106
|
+
|
107
|
+
def append_cookie(cookie)
|
108
|
+
@cookies.push(cookie)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Sets the value of the specified header. Case is significant so, for
|
112
|
+
# example +Content-type+ is not valid, whereas +Content-Type+ is valid.
|
113
|
+
def set_header(header, value)
|
114
|
+
if header.downcase == ContentType.downcase
|
115
|
+
set_header_content_type(value)
|
116
|
+
elsif header.downcase == Location.downcase
|
117
|
+
@error_headers[header] = value
|
118
|
+
elsif header.downcase == SetCookie.downcase
|
119
|
+
# We include cookies for 302 Found (redirect)
|
120
|
+
@error_headers[header] = value
|
121
|
+
end
|
122
|
+
@headers[header] = value
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns the specified header value. Case is significant so, for
|
126
|
+
# example +Content-type+ is not valid, whereas +Content-Type+ is valid.
|
127
|
+
def get_header(header)
|
128
|
+
@headers[header]
|
129
|
+
end
|
130
|
+
|
131
|
+
# Removes a header having the specified name. Case is significant.
|
132
|
+
def clear_header(header)
|
133
|
+
if header.downcase == ContentType.downcase
|
134
|
+
@content_type = nil
|
135
|
+
end
|
136
|
+
@headers.delete(header)
|
137
|
+
end
|
138
|
+
|
139
|
+
def render_headers
|
140
|
+
result = '';
|
141
|
+
|
142
|
+
if @@status_map.has_key? @status
|
143
|
+
status = @@status_map[@status]
|
144
|
+
else
|
145
|
+
status = @@status_map[200]
|
146
|
+
end
|
147
|
+
|
148
|
+
# status line, e.g.: "Status: 200 OK\r\n"
|
149
|
+
result = "Status: " + status + EOL
|
150
|
+
|
151
|
+
if @status < 300
|
152
|
+
headers = @headers
|
153
|
+
else
|
154
|
+
headers = @error_headers
|
155
|
+
end
|
156
|
+
|
157
|
+
headers.each { |key,value|
|
158
|
+
if value && !value.empty?
|
159
|
+
#result << "#{key}: #{value}" + EOL
|
160
|
+
result << "#{key}: #{value}\n"
|
161
|
+
end
|
162
|
+
}
|
163
|
+
if @cookies
|
164
|
+
@cookies.each {|cookie|
|
165
|
+
result << "Set-Cookie: " + cookie.to_s + EOL
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
# BUGBUG: The output contains \n and after print stdio translates to
|
170
|
+
# \r\n, which increases length. We can't use content-length until fixed.
|
171
|
+
#result << "Content-Length: #{@output.length}\n"
|
172
|
+
|
173
|
+
result << EOL
|
174
|
+
|
175
|
+
#print "Content-type: text/plain; charset=iso-8859-1\n\n"
|
176
|
+
result
|
177
|
+
end
|
178
|
+
|
179
|
+
# Called to terminate a request before the render cycle is complete.
|
180
|
+
# This is useful for redirecting from init or render handlers, for example.
|
181
|
+
def end_request
|
182
|
+
raise EndRequest
|
183
|
+
end
|
184
|
+
|
185
|
+
def location(value)
|
186
|
+
set_header("Location", value)
|
187
|
+
end
|
188
|
+
|
189
|
+
def redirect(value, status = 302)
|
190
|
+
@status = status
|
191
|
+
@suppress_content = true
|
192
|
+
location(value)
|
193
|
+
end_request
|
194
|
+
end
|
195
|
+
|
196
|
+
def render
|
197
|
+
result = ''
|
198
|
+
result << render_headers unless @suppress_headers
|
199
|
+
result << @output
|
200
|
+
result
|
201
|
+
end
|
202
|
+
|
203
|
+
def url_encode(string)
|
204
|
+
::CGI::escape(string)
|
205
|
+
end
|
206
|
+
|
207
|
+
def html_encode(string)
|
208
|
+
::CGI::escapeHTML(string)
|
209
|
+
end
|
210
|
+
|
211
|
+
def write(str)
|
212
|
+
@output << str
|
213
|
+
end
|
214
|
+
|
215
|
+
def writeline(str)
|
216
|
+
@output << str + "\n"
|
217
|
+
end
|
218
|
+
|
219
|
+
def log(text)
|
220
|
+
@log << text
|
221
|
+
end
|
222
|
+
|
223
|
+
def get_log
|
224
|
+
@log
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
def update_content_type_header
|
230
|
+
if @content_type && !@content_type.empty?
|
231
|
+
if @charset && !@charset.empty?
|
232
|
+
@headers[ContentType] = "#{@content_type}; charset=#{@charset}"
|
233
|
+
else
|
234
|
+
@headers[ContentType] = @content_type
|
235
|
+
end
|
236
|
+
else
|
237
|
+
@headers.delete(ContentType)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# If the caller manually sets the content type via the headers hash, then
|
242
|
+
# we make an effort to set the two special content type attributes from it.
|
243
|
+
def set_header_content_type(value)
|
244
|
+
@content_type = nil
|
245
|
+
@charset = nil
|
246
|
+
|
247
|
+
if value && !value.empty?
|
248
|
+
ct = value.split(/\s*;\s*/)
|
249
|
+
if ct.length > 0
|
250
|
+
# Look for Content-Type
|
251
|
+
if ct[0] =~ /^\s*([^\s]+)\s*$/
|
252
|
+
@content_type = $1
|
253
|
+
end
|
254
|
+
|
255
|
+
ct.shift
|
256
|
+
|
257
|
+
# Look for charset
|
258
|
+
ct.each { |param|
|
259
|
+
if param =~ /^\s*charset\s*=\s*(.+?)\s*$/
|
260
|
+
@charset = $1
|
261
|
+
end
|
262
|
+
}
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
end
|