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,116 @@
|
|
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
|
+
require 'set'
|
19
|
+
|
20
|
+
module Utopia
|
21
|
+
module Middleware
|
22
|
+
|
23
|
+
class Requester
|
24
|
+
TTL_KEY = "utopia.requestor.ttl"
|
25
|
+
REQUESTOR_KEY = "utopia.requestor"
|
26
|
+
PATH_VARIABLES = Set.new
|
27
|
+
MAXIMUM_DEPTH = 5
|
28
|
+
|
29
|
+
class Response
|
30
|
+
def initialize(status, headers, body)
|
31
|
+
@status = status
|
32
|
+
@headers = headers
|
33
|
+
@body = body
|
34
|
+
end
|
35
|
+
|
36
|
+
attr :status
|
37
|
+
attr :headers
|
38
|
+
|
39
|
+
def body
|
40
|
+
unless @body_string
|
41
|
+
buffer = StringIO.new
|
42
|
+
|
43
|
+
@body.each do |string|
|
44
|
+
buffer.write(string)
|
45
|
+
end
|
46
|
+
|
47
|
+
@body_string = buffer.string
|
48
|
+
end
|
49
|
+
|
50
|
+
return @body_string
|
51
|
+
end
|
52
|
+
|
53
|
+
def [](key)
|
54
|
+
return @headers[key]
|
55
|
+
end
|
56
|
+
|
57
|
+
def okay?
|
58
|
+
@status == 200
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class NoRequesterError < ArgumentError
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.[](env)
|
66
|
+
requestor = env[REQUESTOR_KEY]
|
67
|
+
if requestor
|
68
|
+
return requestor
|
69
|
+
else
|
70
|
+
raise NoRequesterError
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize(app, env = {})
|
75
|
+
@app = app
|
76
|
+
@env = env
|
77
|
+
@env[TTL_KEY] = 0
|
78
|
+
end
|
79
|
+
|
80
|
+
attr :env, true
|
81
|
+
|
82
|
+
def call(env)
|
83
|
+
requestor = dup
|
84
|
+
env[REQUESTOR_KEY] = requestor
|
85
|
+
requestor.env = env.merge(@env)
|
86
|
+
requestor.env.delete("rack.request")
|
87
|
+
|
88
|
+
@app.call(env)
|
89
|
+
end
|
90
|
+
|
91
|
+
class RecursiveRequestError < StandardError
|
92
|
+
end
|
93
|
+
|
94
|
+
def request(env)
|
95
|
+
env = @env.merge(env)
|
96
|
+
env[TTL_KEY] += 1
|
97
|
+
|
98
|
+
if env[TTL_KEY].to_i > MAXIMUM_DEPTH
|
99
|
+
raise RecursiveRequestError, env["PATH_INFO"]
|
100
|
+
end
|
101
|
+
|
102
|
+
return Response.new(*@app.call(env))
|
103
|
+
end
|
104
|
+
|
105
|
+
def [](path, method = "GET", env = {})
|
106
|
+
path_env = {
|
107
|
+
"REQUEST_METHOD" => method,
|
108
|
+
"PATH_INFO" => path.to_s
|
109
|
+
}
|
110
|
+
request(env.merge(path_env))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
@@ -0,0 +1,186 @@
|
|
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
|
+
require 'time'
|
20
|
+
|
21
|
+
require 'mime/types'
|
22
|
+
|
23
|
+
module Utopia
|
24
|
+
module Middleware
|
25
|
+
|
26
|
+
class Static
|
27
|
+
DEFAULT_TYPES = [
|
28
|
+
"html", "css", "js", "txt", "rtf", "xml",
|
29
|
+
"pdf", "zip", "tar", "tgz", "tar.gz", "tar.bz2", "dmg",
|
30
|
+
"mp3", "mp4", "wav", "aiff", ["aac", "audio/x-aac"], "mov", "avi", "wmv",
|
31
|
+
/^image/
|
32
|
+
]
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
class FileReader
|
37
|
+
def initialize(path)
|
38
|
+
@path = path
|
39
|
+
end
|
40
|
+
|
41
|
+
attr :path
|
42
|
+
|
43
|
+
def to_path
|
44
|
+
@path
|
45
|
+
end
|
46
|
+
|
47
|
+
def mtime_date
|
48
|
+
File.mtime(@path).httpdate
|
49
|
+
end
|
50
|
+
|
51
|
+
def size
|
52
|
+
File.size(@path)
|
53
|
+
end
|
54
|
+
|
55
|
+
def each
|
56
|
+
File.open(@path, "rb") do |fp|
|
57
|
+
while part = fp.read(8192)
|
58
|
+
yield part
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_mime_types(types)
|
65
|
+
result = {}
|
66
|
+
|
67
|
+
extract_extensions = lambda do |mime_type|
|
68
|
+
mime_type.extensions.each{|ext| result["." + ext] = mime_type.content_type}
|
69
|
+
end
|
70
|
+
|
71
|
+
types.each do |type|
|
72
|
+
current_count = result.size
|
73
|
+
|
74
|
+
begin
|
75
|
+
case type
|
76
|
+
when :defaults
|
77
|
+
result = load_mime_types(DEFAULT_TYPES).merge(result)
|
78
|
+
when Array
|
79
|
+
result["." + type[0]] = type[1]
|
80
|
+
when String
|
81
|
+
mt = MIME::Types.of(type).select{|mt| !mt.obsolete?}.each do |mt|
|
82
|
+
extract_extensions.call(mt)
|
83
|
+
end
|
84
|
+
when Regexp
|
85
|
+
MIME::Types[type].select{|mt| !mt.obsolete?}.each do |mt|
|
86
|
+
extract_extensions.call(mt)
|
87
|
+
end
|
88
|
+
when MIME::Type
|
89
|
+
extract_extensions.call(type)
|
90
|
+
end
|
91
|
+
rescue
|
92
|
+
LOG.error "#{self.class.name}: Error while processing #{type.inspect}!"
|
93
|
+
raise $!
|
94
|
+
end
|
95
|
+
|
96
|
+
if result.size == current_count
|
97
|
+
LOG.warn "#{self.class.name}: Could not find any mime type for file extension #{type.inspect}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
return result
|
102
|
+
end
|
103
|
+
|
104
|
+
public
|
105
|
+
def initialize(app, options = {})
|
106
|
+
@app = app
|
107
|
+
@root = options[:root] || Utopia::Middleware::default_root
|
108
|
+
|
109
|
+
if options[:types]
|
110
|
+
@extensions = load_mime_types(options[:types])
|
111
|
+
else
|
112
|
+
@extensions = load_mime_types(DEFAULT_TYPES)
|
113
|
+
end
|
114
|
+
|
115
|
+
LOG.info "#{self.class.name}: Running in #{@root} with #{extensions.size} filetypes"
|
116
|
+
end
|
117
|
+
|
118
|
+
def fetch_file(path)
|
119
|
+
file_path = File.join(@root, path.components)
|
120
|
+
if File.exist?(file_path)
|
121
|
+
return FileReader.new(file_path)
|
122
|
+
else
|
123
|
+
return nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def lookup_relative_file(path)
|
128
|
+
file = nil
|
129
|
+
name = path.basename
|
130
|
+
|
131
|
+
if split = path.split("@rel@")
|
132
|
+
path = split[0]
|
133
|
+
name = split[1].components
|
134
|
+
|
135
|
+
# Fix a problem if the browser request has multiple @rel@
|
136
|
+
# This normally indicates a browser bug.. :(
|
137
|
+
name.delete("@rel@")
|
138
|
+
else
|
139
|
+
path = path.dirname
|
140
|
+
|
141
|
+
# Relative lookups are not done unless explicitly required by @rel@
|
142
|
+
# ... but they do work. This is a performance optimization.
|
143
|
+
return nil
|
144
|
+
end
|
145
|
+
|
146
|
+
# LOG.debug("Searching for #{name.inspect} starting in #{path.components}")
|
147
|
+
|
148
|
+
path.ascend do |parent_path|
|
149
|
+
file_path = File.join(@root, parent_path.components, name)
|
150
|
+
# LOG.debug("File path: #{file_path}")
|
151
|
+
if File.exist?(file_path)
|
152
|
+
return (parent_path + name).to_s
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
return nil
|
157
|
+
end
|
158
|
+
|
159
|
+
attr :extensions
|
160
|
+
|
161
|
+
def call(env)
|
162
|
+
request = Rack::Request.new(env)
|
163
|
+
ext = File.extname(request.path_info)
|
164
|
+
if @extensions.key? ext
|
165
|
+
path = Path.create(request.path_info).simplify
|
166
|
+
|
167
|
+
if file = fetch_file(path)
|
168
|
+
response_headers = {
|
169
|
+
"Last-Modified" => file.mtime_date,
|
170
|
+
"Content-Type" => @extensions[ext],
|
171
|
+
"Content-Length" => file.size.to_s
|
172
|
+
}
|
173
|
+
|
174
|
+
return [200, response_headers, file]
|
175
|
+
elsif redirect = lookup_relative_file(path)
|
176
|
+
return [307, {"Location" => redirect}, []]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# else if no file was found:
|
181
|
+
return @app.call(env)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
data/lib/utopia/path.rb
ADDED
@@ -0,0 +1,193 @@
|
|
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 'rack/utils'
|
17
|
+
|
18
|
+
module Utopia
|
19
|
+
|
20
|
+
class Path
|
21
|
+
SEPARATOR = "/"
|
22
|
+
|
23
|
+
def initialize(components)
|
24
|
+
# To ensure we don't do anything stupid we freeze the components
|
25
|
+
@components = components.dup.freeze
|
26
|
+
end
|
27
|
+
|
28
|
+
# Shorthand constructor
|
29
|
+
def self.[](path)
|
30
|
+
create(path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.create(path)
|
34
|
+
case path
|
35
|
+
when Path
|
36
|
+
return path
|
37
|
+
when Array
|
38
|
+
return Path.new(path)
|
39
|
+
when String
|
40
|
+
path = Rack::Utils.unescape(path)
|
41
|
+
# Ends with SEPARATOR
|
42
|
+
if path[-1,1] == SEPARATOR
|
43
|
+
return Path.new(path.split(SEPARATOR) << "")
|
44
|
+
else
|
45
|
+
return Path.new(path.split(SEPARATOR))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
attr :components
|
51
|
+
|
52
|
+
def directory?
|
53
|
+
return @components.last == ""
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_directory
|
57
|
+
if directory?
|
58
|
+
return self
|
59
|
+
else
|
60
|
+
return join([""])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def absolute?
|
65
|
+
return @components.first == ""
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_absolute
|
69
|
+
if absolute?
|
70
|
+
return self
|
71
|
+
else
|
72
|
+
return Path.new([""] + @components)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
if @components == [""]
|
78
|
+
SEPARATOR
|
79
|
+
else
|
80
|
+
@components.join(SEPARATOR)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def join(other)
|
85
|
+
Path.new(@components + other).simplify
|
86
|
+
end
|
87
|
+
|
88
|
+
def +(other)
|
89
|
+
if other.kind_of? Array
|
90
|
+
return join(other)
|
91
|
+
elsif other.kind_of? Path
|
92
|
+
if other.absolute?
|
93
|
+
return other
|
94
|
+
else
|
95
|
+
return join(other.components)
|
96
|
+
end
|
97
|
+
else
|
98
|
+
return join([other.to_s])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def simplify
|
103
|
+
result = absolute? ? [""] : []
|
104
|
+
|
105
|
+
components.each do |bit|
|
106
|
+
if bit == ".."
|
107
|
+
result.pop
|
108
|
+
elsif bit != "." && bit != ""
|
109
|
+
result << bit
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
result << "" if directory?
|
114
|
+
return Path.new(result)
|
115
|
+
end
|
116
|
+
|
117
|
+
def basename(ext = nil)
|
118
|
+
if ext
|
119
|
+
File.basename(components.last, ext)
|
120
|
+
else
|
121
|
+
components.last
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def dirname(count = 1)
|
126
|
+
path = Path.new(components[0...-count])
|
127
|
+
|
128
|
+
return absolute? ? path.to_absolute : path
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_local_path
|
132
|
+
components.join(File::SEPARATOR)
|
133
|
+
end
|
134
|
+
|
135
|
+
def ascend(&block)
|
136
|
+
paths = []
|
137
|
+
|
138
|
+
next_parent = self
|
139
|
+
|
140
|
+
begin
|
141
|
+
parent = next_parent
|
142
|
+
|
143
|
+
yield parent if block_given?
|
144
|
+
paths << parent
|
145
|
+
|
146
|
+
next_parent = parent.dirname
|
147
|
+
end until next_parent.eql?(parent)
|
148
|
+
|
149
|
+
return paths
|
150
|
+
end
|
151
|
+
|
152
|
+
def split(at)
|
153
|
+
if at.kind_of? String
|
154
|
+
at = @components.index(at)
|
155
|
+
end
|
156
|
+
|
157
|
+
if at
|
158
|
+
return [Path.new(@components[0...at]), Path.new(@components[at+1..-1])]
|
159
|
+
else
|
160
|
+
return nil
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def dup
|
165
|
+
return Path.new(components.dup)
|
166
|
+
end
|
167
|
+
|
168
|
+
def <=> other
|
169
|
+
@components <=> other.components
|
170
|
+
end
|
171
|
+
|
172
|
+
def eql? other
|
173
|
+
if self.class == other.class
|
174
|
+
return @components.eql?(other.components)
|
175
|
+
else
|
176
|
+
return false
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def == other
|
181
|
+
other.components.each_with_index do |part, index|
|
182
|
+
return false if @components[index] != part
|
183
|
+
end
|
184
|
+
|
185
|
+
return true
|
186
|
+
end
|
187
|
+
|
188
|
+
def hash
|
189
|
+
@components.hash
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|