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,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
|