bijou 0.1.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.
- 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
|