utopia 0.9.17
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/ext/utopia/xnode/fast_scanner/extconf.rb +6 -0
- data/ext/utopia/xnode/fast_scanner/parser.c +289 -0
- data/lib/utopia.rb +2 -0
- data/lib/utopia/etanni.rb +96 -0
- data/lib/utopia/extensions.rb +87 -0
- data/lib/utopia/link.rb +243 -0
- data/lib/utopia/middleware.rb +33 -0
- data/lib/utopia/middleware/all.rb +24 -0
- data/lib/utopia/middleware/benchmark.rb +47 -0
- data/lib/utopia/middleware/content.rb +139 -0
- data/lib/utopia/middleware/content/node.rb +363 -0
- data/lib/utopia/middleware/controller.rb +198 -0
- data/lib/utopia/middleware/directory_index.rb +54 -0
- data/lib/utopia/middleware/localization.rb +94 -0
- data/lib/utopia/middleware/localization/name.rb +64 -0
- data/lib/utopia/middleware/logger.rb +68 -0
- data/lib/utopia/middleware/redirector.rb +171 -0
- data/lib/utopia/middleware/requester.rb +116 -0
- data/lib/utopia/middleware/static.rb +186 -0
- data/lib/utopia/path.rb +193 -0
- data/lib/utopia/response_helper.rb +22 -0
- data/lib/utopia/session/encrypted_cookie.rb +115 -0
- data/lib/utopia/tag.rb +84 -0
- data/lib/utopia/tags.rb +32 -0
- data/lib/utopia/tags/all.rb +25 -0
- data/lib/utopia/tags/env.rb +24 -0
- data/lib/utopia/tags/fortune.rb +20 -0
- data/lib/utopia/tags/gallery.rb +175 -0
- data/lib/utopia/tags/google_analytics.rb +37 -0
- data/lib/utopia/tags/node.rb +24 -0
- data/lib/utopia/tags/override.rb +28 -0
- data/lib/utopia/time_store.rb +102 -0
- data/lib/utopia/version.rb +24 -0
- data/lib/utopia/xnode.rb +17 -0
- data/lib/utopia/xnode/processor.rb +97 -0
- data/lib/utopia/xnode/scanner.rb +153 -0
- metadata +168 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
# Copyright (c) 2010 Samuel Williams. Released under the GNU GPLv3.
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
require 'utopia/middleware'
|
17
|
+
require 'utopia/path'
|
18
|
+
|
19
|
+
module Utopia
|
20
|
+
module Middleware
|
21
|
+
|
22
|
+
class DirectoryIndex
|
23
|
+
def initialize(app, options = {})
|
24
|
+
@app = app
|
25
|
+
@root = options[:root] || Utopia::Middleware::default_root
|
26
|
+
|
27
|
+
@files = ["index.html"]
|
28
|
+
|
29
|
+
@default = "index"
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(env)
|
33
|
+
path = Path.create(env["PATH_INFO"])
|
34
|
+
|
35
|
+
if path.directory?
|
36
|
+
# Check to see if one of the files exists in the requested directory
|
37
|
+
@files.each do |file|
|
38
|
+
if File.exist?(File.join(@root, path.components, file))
|
39
|
+
env["PATH_INFO"] = (path + file).to_s
|
40
|
+
return @app.call(env)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Use the default path
|
45
|
+
env["PATH_INFO"] = (path + @default).to_s
|
46
|
+
return @app.call(env)
|
47
|
+
else
|
48
|
+
return @app.call(env)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Copyright (c) 2010 Samuel Williams. Released under the GNU GPLv3.
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
require 'utopia/middleware'
|
17
|
+
require 'utopia/response_helper'
|
18
|
+
require 'utopia/middleware/localization/name'
|
19
|
+
|
20
|
+
module Rack
|
21
|
+
class Request
|
22
|
+
def current_locale
|
23
|
+
env["utopia.current_locale"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def all_locales
|
27
|
+
localization.all_locales
|
28
|
+
end
|
29
|
+
|
30
|
+
def localization
|
31
|
+
env["utopia.localization"]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Utopia
|
37
|
+
module Middleware
|
38
|
+
|
39
|
+
class Localization
|
40
|
+
def initialize(app, options = {})
|
41
|
+
@app = app
|
42
|
+
|
43
|
+
@default_locale = options[:default] || "en"
|
44
|
+
@all_locales = options[:all] || ["en"]
|
45
|
+
end
|
46
|
+
|
47
|
+
def named_locale(resource_name)
|
48
|
+
Name.extract_locale(resource_name, @all_locales)
|
49
|
+
end
|
50
|
+
|
51
|
+
def current_locale(env, resource_name)
|
52
|
+
Rack::Request.new(env).GET["locale"] || named_locale(resource_name) || @default_locale
|
53
|
+
end
|
54
|
+
|
55
|
+
attr :all_locales
|
56
|
+
attr :default_locale
|
57
|
+
|
58
|
+
def call(env)
|
59
|
+
path = Path.create(env["PATH_INFO"])
|
60
|
+
|
61
|
+
request_locale = current_locale(env, path.basename)
|
62
|
+
resource_name = Name.nonlocalized(path.basename, @all_locales).join(".")
|
63
|
+
|
64
|
+
env["utopia.current_locale"] = request_locale
|
65
|
+
env["utopia.localization"] = self
|
66
|
+
|
67
|
+
localized_name = Name.localized(resource_name, request_locale, @all_locales).join(".")
|
68
|
+
|
69
|
+
localization_probe = env.dup
|
70
|
+
localization_probe["REQUEST_METHOD"] = "HEAD"
|
71
|
+
localization_probe["PATH_INFO"] = (path.dirname + localized_name).to_s
|
72
|
+
|
73
|
+
response = @app.call(localization_probe)
|
74
|
+
|
75
|
+
if response[0] < 300
|
76
|
+
if path.basename == localized_name
|
77
|
+
return @app.call(env)
|
78
|
+
else
|
79
|
+
return [307, {"Location" => localization_probe["PATH_INFO"]}, []]
|
80
|
+
end
|
81
|
+
elsif response[0] < 400
|
82
|
+
return response
|
83
|
+
else
|
84
|
+
if path.basename == resource_name
|
85
|
+
return @app.call(env)
|
86
|
+
else
|
87
|
+
return [307, {"Location" => (path.dirname + resource_name).to_s}, []]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright (c) 2010 Samuel Williams. Released under the GNU GPLv3.
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Utopia
|
17
|
+
module Middleware
|
18
|
+
class Localization
|
19
|
+
|
20
|
+
module Name
|
21
|
+
def self.nonlocalized(resource_name, all_locales)
|
22
|
+
resource_name = resource_name.split(".") unless resource_name.kind_of? Array
|
23
|
+
|
24
|
+
# We either have a file extension or an existing locale
|
25
|
+
if all_locales.include?(resource_name[-1])
|
26
|
+
resource_name.delete_at(-1)
|
27
|
+
elsif all_locales.include?(resource_name[-2])
|
28
|
+
resource_name.delete_at(-2)
|
29
|
+
end
|
30
|
+
|
31
|
+
resource_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.extract_locale(resource_name, all_locales)
|
35
|
+
resource_name = resource_name.split(".") unless resource_name.kind_of? Array
|
36
|
+
|
37
|
+
# We either have a file extension or an existing locale
|
38
|
+
if all_locales.include?(resource_name[-1])
|
39
|
+
return resource_name[-1]
|
40
|
+
elsif all_locales.include?(resource_name[-2])
|
41
|
+
return resource_name[-2]
|
42
|
+
end
|
43
|
+
|
44
|
+
return nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.localized(resource_name, locale, all_locales)
|
48
|
+
nonlocalized_name = nonlocalized(resource_name, all_locales)
|
49
|
+
|
50
|
+
if locale == nil
|
51
|
+
return nonlocalized_name
|
52
|
+
end
|
53
|
+
|
54
|
+
if nonlocalized_name.size == 1
|
55
|
+
return nonlocalized_name.push(locale)
|
56
|
+
else
|
57
|
+
return nonlocalized_name.insert(-2, locale)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Copyright (c) 2010 Samuel Williams. Released under the GNU GPLv3.
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
require 'utopia/middleware'
|
17
|
+
require 'utopia/time_store'
|
18
|
+
|
19
|
+
module Utopia
|
20
|
+
module Middleware
|
21
|
+
|
22
|
+
class Logger
|
23
|
+
ACCESS_LOG = "access_log"
|
24
|
+
HEADER = [:ip, :agent, :method, :url, :status, :location, :length]
|
25
|
+
|
26
|
+
def write_log(env, response)
|
27
|
+
request = Rack::Request.new(env)
|
28
|
+
|
29
|
+
record = {
|
30
|
+
:ip => request.ip,
|
31
|
+
:host => request.host,
|
32
|
+
:url => request.url,
|
33
|
+
:agent => env['HTTP_USER_AGENT'],
|
34
|
+
:status => response[0],
|
35
|
+
:method => request.request_method,
|
36
|
+
:user => env['REMOTE_USER'],
|
37
|
+
:version => env['HTTP_VERSION']
|
38
|
+
}
|
39
|
+
|
40
|
+
if response[1].key? "Location"
|
41
|
+
record[:location] = response[1]["Location"]
|
42
|
+
end
|
43
|
+
|
44
|
+
if response[1].key? "Content-Length"
|
45
|
+
record[:length] = response[1]["Content-Length"]
|
46
|
+
end
|
47
|
+
|
48
|
+
@log << record
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(app, options = {})
|
52
|
+
@app = app
|
53
|
+
|
54
|
+
@log = options[:log] || TimeStore.new(options[:path] || ACCESS_LOG, options[:header] || HEADER)
|
55
|
+
end
|
56
|
+
|
57
|
+
def call(env)
|
58
|
+
response = @app.call(env)
|
59
|
+
|
60
|
+
Thread.new do
|
61
|
+
write_log(env, response)
|
62
|
+
end
|
63
|
+
|
64
|
+
return response
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# Copyright (c) 2010 Samuel Williams. Released under the GNU GPLv3.
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
require 'utopia/middleware'
|
17
|
+
|
18
|
+
module Utopia
|
19
|
+
module Middleware
|
20
|
+
|
21
|
+
class FailedRequestError < StandardError
|
22
|
+
def initialize(resource_path, resource_status, error_path, error_status)
|
23
|
+
@resource_path = resource_path
|
24
|
+
@resource_status = resource_status
|
25
|
+
|
26
|
+
@error_path = error_path
|
27
|
+
@error_status = error_status
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"Requested resource #{@resource_path} resulted in a #{@resource_status} error. Requested error handler #{@error_path} resulted in a #{@error_status} error."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ExceptionHandler
|
36
|
+
def initialize(app, location)
|
37
|
+
@app = app
|
38
|
+
|
39
|
+
@location = location
|
40
|
+
end
|
41
|
+
|
42
|
+
def fatal_error(env, ex)
|
43
|
+
body = StringIO.new
|
44
|
+
|
45
|
+
body.puts "<!DOCTYPE html><html><head><title>Fatal Error</title></head><body>"
|
46
|
+
|
47
|
+
body.puts "<h1>Fatal Error</h1>"
|
48
|
+
|
49
|
+
body.puts "<p>While requesting resource #{env['PATH_INFO'].to_html}, a fatal error occurred.</p>"
|
50
|
+
|
51
|
+
body.puts "<blockquote><strong>#{ex.class.name.to_html}</strong>: #{ex.to_s.to_html}</blockquote>"
|
52
|
+
|
53
|
+
body.puts "<p>There is nothing more we can do to fix the problem at this point.</p>"
|
54
|
+
|
55
|
+
body.puts "<p>We apologize for the inconvenience.</p>"
|
56
|
+
|
57
|
+
body.puts "</body></html>"
|
58
|
+
body.rewind
|
59
|
+
|
60
|
+
return [400, {"Content-Type" => "text/html"}, body]
|
61
|
+
end
|
62
|
+
|
63
|
+
def redirect(env, ex)
|
64
|
+
return @app.call(env.merge('PATH_INFO' => @location, 'REQUEST_METHOD' => 'GET'))
|
65
|
+
end
|
66
|
+
|
67
|
+
def call(env)
|
68
|
+
begin
|
69
|
+
return @app.call(env)
|
70
|
+
rescue Exception => ex
|
71
|
+
if env['PATH_INFO'] == @location
|
72
|
+
return fatal_error(env, ex)
|
73
|
+
else
|
74
|
+
|
75
|
+
# If redirection fails
|
76
|
+
begin
|
77
|
+
return redirect(env, ex)
|
78
|
+
rescue
|
79
|
+
return fatal_error(env, ex)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Legacy Support
|
88
|
+
ExceptionRedirector = ExceptionHandler
|
89
|
+
|
90
|
+
class Redirector
|
91
|
+
private
|
92
|
+
def normalize_keys(redirects)
|
93
|
+
redirects.each do |key, value|
|
94
|
+
result = nil
|
95
|
+
|
96
|
+
case value
|
97
|
+
when String
|
98
|
+
result = @strings
|
99
|
+
when Regexp
|
100
|
+
result = @patterns
|
101
|
+
else
|
102
|
+
$stderr.puts "Warning, could not process redirect #{key.inspect} to #{value.inspect}!"
|
103
|
+
next
|
104
|
+
end
|
105
|
+
|
106
|
+
if key.kind_of? Array
|
107
|
+
key.each do |subkey|
|
108
|
+
result[subkey] = value
|
109
|
+
end
|
110
|
+
else
|
111
|
+
result[key] = value
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
public
|
117
|
+
def initialize(app, options = {})
|
118
|
+
@app = app
|
119
|
+
|
120
|
+
@strings = {}
|
121
|
+
@patterns = {}
|
122
|
+
|
123
|
+
normalize_keys(options[:redirects]) if options[:redirects]
|
124
|
+
|
125
|
+
@errors = options[:errors]
|
126
|
+
|
127
|
+
LOG.info "#{self.class.name}: Running with #{@strings.size + @patterns.size} rules"
|
128
|
+
end
|
129
|
+
|
130
|
+
def redirect(uri, match_data)
|
131
|
+
if uri.respond_to? :call
|
132
|
+
return uri.call(match_data)
|
133
|
+
else
|
134
|
+
return [301, {"Location" => uri.to_s}, []]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def call(env)
|
139
|
+
base_path = env['PATH_INFO']
|
140
|
+
|
141
|
+
if uri = @strings[base_path]
|
142
|
+
return redirect(@strings[base_path], base_path)
|
143
|
+
end
|
144
|
+
|
145
|
+
@patterns.each do |pattern, uri|
|
146
|
+
if match_data = base_path.match(pattern)
|
147
|
+
return redirect(uri, match_data)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
response = @app.call(env)
|
152
|
+
|
153
|
+
if @errors && response[0] >= 400 && uri = @errors[response[0]]
|
154
|
+
error_request = env.merge("PATH_INFO" => uri, "REQUEST_METHOD" => "GET")
|
155
|
+
error_response = @app.call(error_request)
|
156
|
+
|
157
|
+
if error_response[0] >= 400
|
158
|
+
raise FailedRequestError.new(env['PATH_INFO'], response[0], uri, error_response[0])
|
159
|
+
else
|
160
|
+
# Feed the error code back with the error document
|
161
|
+
error_response[0] = response[0]
|
162
|
+
return error_response
|
163
|
+
end
|
164
|
+
else
|
165
|
+
return response
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|