utopia 0.9.17
Sign up to get free protection for your applications and to get access to all the features.
- 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
|