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.
Files changed (53) hide show
  1. data/ChangeLog.txt +4 -0
  2. data/LICENSE.txt +58 -0
  3. data/README.txt +48 -0
  4. data/Rakefile +105 -0
  5. data/doc/INSTALL.rdoc +260 -0
  6. data/doc/README.rdoc +314 -0
  7. data/doc/releases/bijou-0.1.0.rdoc +60 -0
  8. data/examples/birthday/birthday.rb +34 -0
  9. data/examples/holiday/holiday.rb +61 -0
  10. data/examples/holiday/letterhead.txt +4 -0
  11. data/examples/holiday/signature.txt +9 -0
  12. data/examples/phishing/letter.txt +29 -0
  13. data/examples/phishing/letterhead.txt +4 -0
  14. data/examples/phishing/phishing.rb +21 -0
  15. data/examples/phishing/signature.txt +9 -0
  16. data/examples/profile/profile.rb +46 -0
  17. data/lib/bijou.rb +15 -0
  18. data/lib/bijou/backend.rb +542 -0
  19. data/lib/bijou/cgi/adapter.rb +201 -0
  20. data/lib/bijou/cgi/handler.rb +5 -0
  21. data/lib/bijou/cgi/request.rb +37 -0
  22. data/lib/bijou/common.rb +12 -0
  23. data/lib/bijou/component.rb +108 -0
  24. data/lib/bijou/config.rb +60 -0
  25. data/lib/bijou/console/adapter.rb +167 -0
  26. data/lib/bijou/console/handler.rb +4 -0
  27. data/lib/bijou/console/request.rb +26 -0
  28. data/lib/bijou/context.rb +431 -0
  29. data/lib/bijou/diagnostics.rb +87 -0
  30. data/lib/bijou/errorformatter.rb +322 -0
  31. data/lib/bijou/exception.rb +39 -0
  32. data/lib/bijou/filters.rb +107 -0
  33. data/lib/bijou/httprequest.rb +108 -0
  34. data/lib/bijou/httpresponse.rb +268 -0
  35. data/lib/bijou/lexer.rb +513 -0
  36. data/lib/bijou/minicgi.rb +159 -0
  37. data/lib/bijou/parser.rb +1026 -0
  38. data/lib/bijou/processor.rb +404 -0
  39. data/lib/bijou/prstringio.rb +400 -0
  40. data/lib/bijou/webrick/adapter.rb +174 -0
  41. data/lib/bijou/webrick/handler.rb +32 -0
  42. data/lib/bijou/webrick/request.rb +45 -0
  43. data/script/cgi.rb +25 -0
  44. data/script/console.rb +7 -0
  45. data/script/server.rb +7 -0
  46. data/test/t1.cfg +5 -0
  47. data/test/tc_config.rb +26 -0
  48. data/test/tc_filter.rb +25 -0
  49. data/test/tc_lexer.rb +120 -0
  50. data/test/tc_response.rb +103 -0
  51. data/test/tc_ruby.rb +62 -0
  52. data/test/tc_stack.rb +50 -0
  53. 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, '&amp;').gsub(/\"/n, '&quot;').gsub(/\'/n, '&apos;').gsub(/</n, '&lt;')
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