utopia 2.30.2 → 2.31.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/bake/utopia/server.rb +1 -1
- data/bake/utopia/site.rb +3 -3
- data/context/getting-started.md +93 -0
- data/context/index.yaml +32 -0
- data/context/integrating-with-javascript.md +75 -0
- data/context/middleware.md +157 -0
- data/context/server-setup.md +116 -0
- data/context/updating-utopia.md +69 -0
- data/context/what-is-xnode.md +41 -0
- data/lib/utopia/content/document.rb +39 -37
- data/lib/utopia/content/link.rb +1 -2
- data/lib/utopia/content/links.rb +2 -2
- data/lib/utopia/content/markup.rb +10 -10
- data/lib/utopia/content/middleware.rb +195 -0
- data/lib/utopia/content/namespace.rb +1 -1
- data/lib/utopia/content/node.rb +1 -1
- data/lib/utopia/content/response.rb +1 -1
- data/lib/utopia/content/tags.rb +1 -1
- data/lib/utopia/content.rb +4 -186
- data/lib/utopia/controller/actions.md +8 -8
- data/lib/utopia/controller/actions.rb +1 -1
- data/lib/utopia/controller/base.rb +4 -4
- data/lib/utopia/controller/middleware.rb +133 -0
- data/lib/utopia/controller/respond.rb +2 -46
- data/lib/utopia/controller/responder.rb +103 -0
- data/lib/utopia/controller/rewrite.md +2 -2
- data/lib/utopia/controller/rewrite.rb +1 -1
- data/lib/utopia/controller/variables.rb +11 -5
- data/lib/utopia/controller.rb +4 -126
- data/lib/utopia/exceptions/mailer.rb +4 -4
- data/lib/utopia/extensions/array_split.rb +2 -2
- data/lib/utopia/extensions/date_comparisons.rb +3 -3
- data/lib/utopia/import_map.rb +374 -0
- data/lib/utopia/localization/middleware.rb +173 -0
- data/lib/utopia/localization/wrapper.rb +52 -0
- data/lib/utopia/localization.rb +4 -202
- data/lib/utopia/path.rb +26 -11
- data/lib/utopia/redirection.rb +2 -2
- data/lib/utopia/session/lazy_hash.rb +1 -1
- data/lib/utopia/session/middleware.rb +218 -0
- data/lib/utopia/session/serialization.rb +1 -1
- data/lib/utopia/session.rb +4 -205
- data/lib/utopia/static/local_file.rb +19 -19
- data/lib/utopia/static/middleware.rb +120 -0
- data/lib/utopia/static/mime_types.rb +1 -1
- data/lib/utopia/static.rb +4 -108
- data/lib/utopia/version.rb +1 -1
- data/lib/utopia.rb +1 -0
- data/readme.md +7 -0
- data/releases.md +7 -0
- data/setup/site/config.ru +1 -1
- data.tar.gz.sig +0 -0
- metadata +31 -4
- metadata.gz.sig +0 -0
- data/lib/utopia/locale.rb +0 -29
- data/lib/utopia/responder.rb +0 -59
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2009-2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "wrapper"
|
|
7
|
+
|
|
8
|
+
module Utopia
|
|
9
|
+
module Localization
|
|
10
|
+
class Middleware
|
|
11
|
+
RESOURCE_NOT_FOUND = [400, {}, []].freeze
|
|
12
|
+
|
|
13
|
+
HTTP_ACCEPT_LANGUAGE = "HTTP_ACCEPT_LANGUAGE".freeze
|
|
14
|
+
|
|
15
|
+
# @param locales [Array<String>] An array of all supported locales.
|
|
16
|
+
# @param default_locale [String] The default locale if none is provided.
|
|
17
|
+
# @param default_locales [String] The locales to try in order if none is provided.
|
|
18
|
+
# @param hosts [Hash<Pattern, String>] Specify a mapping of the HTTP_HOST header to a given locale.
|
|
19
|
+
# @param ignore [Array<Pattern>] A list of patterns matched against PATH_INFO which will not be localized.
|
|
20
|
+
def initialize(app, locales:, default_locale: nil, default_locales: nil, hosts: {}, ignore: [])
|
|
21
|
+
@app = app
|
|
22
|
+
|
|
23
|
+
@all_locales = HTTP::Accept::Languages::Locales.new(locales)
|
|
24
|
+
|
|
25
|
+
# Locales here are represented as an array of strings, e.g. ['en', 'ja', 'cn', 'de'] and are used in order if no locale is specified by the user.
|
|
26
|
+
unless @default_locales = default_locales
|
|
27
|
+
if default_locale
|
|
28
|
+
@default_locales = [default_locale, nil]
|
|
29
|
+
else
|
|
30
|
+
# We append nil, i.e. no localization.
|
|
31
|
+
@default_locales = @all_locales.names + [nil]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@default_locale = default_locale || @default_locales.first
|
|
36
|
+
|
|
37
|
+
unless @default_locales.include? @default_locale
|
|
38
|
+
@default_locales.unshift(@default_locale)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Select a localization based on a request host name:
|
|
42
|
+
@hosts = hosts
|
|
43
|
+
|
|
44
|
+
@ignore = ignore || options[:nonlocalized]
|
|
45
|
+
|
|
46
|
+
@methods = methods
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def freeze
|
|
50
|
+
return self if frozen?
|
|
51
|
+
|
|
52
|
+
@all_locales.freeze
|
|
53
|
+
@default_locales.freeze
|
|
54
|
+
@default_locale.freeze
|
|
55
|
+
@hosts.freeze
|
|
56
|
+
@ignore.freeze
|
|
57
|
+
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
attr :all_locales
|
|
62
|
+
attr :default_locale
|
|
63
|
+
|
|
64
|
+
def preferred_locales(env)
|
|
65
|
+
return to_enum(:preferred_locales, env) unless block_given?
|
|
66
|
+
|
|
67
|
+
# Keep track of what locales have been tried:
|
|
68
|
+
locales = Set.new
|
|
69
|
+
|
|
70
|
+
host_preferred_locales(env) do |locale|
|
|
71
|
+
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
request_preferred_locale(env) do |locale, path|
|
|
75
|
+
# We have extracted a locale from the path, so from this point on we should use the updated path:
|
|
76
|
+
env = env.merge(Rack::PATH_INFO => path.to_s)
|
|
77
|
+
|
|
78
|
+
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
browser_preferred_locales(env).each do |locale|
|
|
82
|
+
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
@default_locales.each do |locale|
|
|
86
|
+
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def host_preferred_locales(env)
|
|
91
|
+
http_host = env[Rack::HTTP_HOST]
|
|
92
|
+
|
|
93
|
+
# Yield all hosts which match the incoming http_host:
|
|
94
|
+
@hosts.each do |pattern, locale|
|
|
95
|
+
yield locale if http_host[pattern]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def request_preferred_locale(env)
|
|
100
|
+
path = Path[env[Rack::PATH_INFO]]
|
|
101
|
+
|
|
102
|
+
if request_locale = @all_locales.patterns[path.first]
|
|
103
|
+
# Remove the localization prefix:
|
|
104
|
+
path.delete_at(0)
|
|
105
|
+
|
|
106
|
+
yield request_locale, path
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def browser_preferred_locales(env)
|
|
111
|
+
accept_languages = env[HTTP_ACCEPT_LANGUAGE]
|
|
112
|
+
|
|
113
|
+
# No user prefered languages:
|
|
114
|
+
return [] unless accept_languages
|
|
115
|
+
|
|
116
|
+
# Extract the ordered list of languages:
|
|
117
|
+
languages = HTTP::Accept::Languages.parse(accept_languages)
|
|
118
|
+
|
|
119
|
+
# Returns available languages based on the order languages:
|
|
120
|
+
return @all_locales & languages
|
|
121
|
+
rescue HTTP::Accept::ParseError
|
|
122
|
+
# If we fail to parse the browser Accept-Language header, we ignore it (silently).
|
|
123
|
+
return []
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def localized?(env)
|
|
127
|
+
# Ignore requests which match the ignored paths:
|
|
128
|
+
path_info = env[Rack::PATH_INFO]
|
|
129
|
+
return false if @ignore.any?{|pattern| path_info[pattern] != nil}
|
|
130
|
+
|
|
131
|
+
return true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Set the Vary: header on the response to indicate that this response should include the header in the cache key.
|
|
135
|
+
def vary(env, response)
|
|
136
|
+
headers = response[1].to_a
|
|
137
|
+
|
|
138
|
+
# This response was based on the Accept-Language header:
|
|
139
|
+
headers << ["Vary", "Accept-Language"]
|
|
140
|
+
|
|
141
|
+
# Althought this header is generally not supported, we supply it anyway as it is useful for debugging:
|
|
142
|
+
if locale = env[CURRENT_LOCALE_KEY]
|
|
143
|
+
# Set the Content-Location to point to the localized URI as requested:
|
|
144
|
+
headers["Content-Location"] = "/#{locale}" + env[Rack::PATH_INFO]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
return response
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def call(env)
|
|
151
|
+
# Pass the request through if it shouldn't be localized:
|
|
152
|
+
return @app.call(env) unless localized?(env)
|
|
153
|
+
|
|
154
|
+
env[LOCALIZATION_KEY] = self
|
|
155
|
+
|
|
156
|
+
response = nil
|
|
157
|
+
|
|
158
|
+
# We have a non-localized request, but there might be a localized resource. We return the best localization possible:
|
|
159
|
+
preferred_locales(env) do |localized_env|
|
|
160
|
+
# puts "Trying locale: #{localized_env[CURRENT_LOCALE_KEY]}: #{localized_env[Rack::PATH_INFO]}..."
|
|
161
|
+
|
|
162
|
+
response = @app.call(localized_env)
|
|
163
|
+
|
|
164
|
+
break unless response[0] >= 400
|
|
165
|
+
|
|
166
|
+
response[2].close if response[2].respond_to?(:close)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
return vary(env, response)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2009-2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "middleware"
|
|
7
|
+
|
|
8
|
+
module Utopia
|
|
9
|
+
# A middleware which attempts to find localized content.
|
|
10
|
+
module Localization
|
|
11
|
+
LOCALIZATION_KEY = "utopia.localization".freeze
|
|
12
|
+
CURRENT_LOCALE_KEY = "utopia.localization.current_locale".freeze
|
|
13
|
+
|
|
14
|
+
# A wrapper to provide easy access to locale related data in the request.
|
|
15
|
+
class Wrapper
|
|
16
|
+
def initialize(env)
|
|
17
|
+
@env = env
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def localization
|
|
21
|
+
@env[LOCALIZATION_KEY]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def localized?
|
|
25
|
+
localization != nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the current locale or nil if not localized.
|
|
29
|
+
def current_locale
|
|
30
|
+
@env[CURRENT_LOCALE_KEY]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns the default locale or nil if not localized.
|
|
34
|
+
def default_locale
|
|
35
|
+
localization && localization.default_locale
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns an empty array if not localized.
|
|
39
|
+
def all_locales
|
|
40
|
+
localization && localization.all_locales || []
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def localized_path(path, locale)
|
|
44
|
+
"/#{locale}#{path}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.[] request
|
|
49
|
+
Wrapper.new(request.env)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/utopia/localization.rb
CHANGED
|
@@ -3,210 +3,12 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2009-2025, by Samuel Williams.
|
|
5
5
|
|
|
6
|
-
require_relative "middleware"
|
|
6
|
+
require_relative "localization/middleware"
|
|
7
7
|
|
|
8
8
|
module Utopia
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Wrapper
|
|
13
|
-
def initialize(env)
|
|
14
|
-
@env = env
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def localization
|
|
18
|
-
@env[LOCALIZATION_KEY]
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def localized?
|
|
22
|
-
localization != nil
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Returns the current locale or nil if not localized.
|
|
26
|
-
def current_locale
|
|
27
|
-
@env[CURRENT_LOCALE_KEY]
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Returns the default locale or nil if not localized.
|
|
31
|
-
def default_locale
|
|
32
|
-
localization && localization.default_locale
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Returns an empty array if not localized.
|
|
36
|
-
def all_locales
|
|
37
|
-
localization && localization.all_locales || []
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def localized_path(path, locale)
|
|
41
|
-
"/#{locale}#{path}"
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def self.[] request
|
|
46
|
-
Wrapper.new(request.env)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
RESOURCE_NOT_FOUND = [400, {}, []].freeze
|
|
50
|
-
|
|
51
|
-
HTTP_ACCEPT_LANGUAGE = "HTTP_ACCEPT_LANGUAGE".freeze
|
|
52
|
-
LOCALIZATION_KEY = "utopia.localization".freeze
|
|
53
|
-
CURRENT_LOCALE_KEY = "utopia.localization.current_locale".freeze
|
|
54
|
-
|
|
55
|
-
# @param locales [Array<String>] An array of all supported locales.
|
|
56
|
-
# @param default_locale [String] The default locale if none is provided.
|
|
57
|
-
# @param default_locales [String] The locales to try in order if none is provided.
|
|
58
|
-
# @param hosts [Hash<Pattern, String>] Specify a mapping of the HTTP_HOST header to a given locale.
|
|
59
|
-
# @param ignore [Array<Pattern>] A list of patterns matched against PATH_INFO which will not be localized.
|
|
60
|
-
def initialize(app, locales:, default_locale: nil, default_locales: nil, hosts: {}, ignore: [])
|
|
61
|
-
@app = app
|
|
62
|
-
|
|
63
|
-
@all_locales = HTTP::Accept::Languages::Locales.new(locales)
|
|
64
|
-
|
|
65
|
-
# Locales here are represented as an array of strings, e.g. ['en', 'ja', 'cn', 'de'] and are used in order if no locale is specified by the user.
|
|
66
|
-
unless @default_locales = default_locales
|
|
67
|
-
if default_locale
|
|
68
|
-
@default_locales = [default_locale, nil]
|
|
69
|
-
else
|
|
70
|
-
# We append nil, i.e. no localization.
|
|
71
|
-
@default_locales = @all_locales.names + [nil]
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
@default_locale = default_locale || @default_locales.first
|
|
76
|
-
|
|
77
|
-
unless @default_locales.include? @default_locale
|
|
78
|
-
@default_locales.unshift(@default_locale)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Select a localization based on a request host name:
|
|
82
|
-
@hosts = hosts
|
|
83
|
-
|
|
84
|
-
@ignore = ignore || options[:nonlocalized]
|
|
85
|
-
|
|
86
|
-
@methods = methods
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def freeze
|
|
90
|
-
return self if frozen?
|
|
91
|
-
|
|
92
|
-
@all_locales.freeze
|
|
93
|
-
@default_locales.freeze
|
|
94
|
-
@default_locale.freeze
|
|
95
|
-
@hosts.freeze
|
|
96
|
-
@ignore.freeze
|
|
97
|
-
|
|
98
|
-
super
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
attr :all_locales
|
|
102
|
-
attr :default_locale
|
|
103
|
-
|
|
104
|
-
def preferred_locales(env)
|
|
105
|
-
return to_enum(:preferred_locales, env) unless block_given?
|
|
106
|
-
|
|
107
|
-
# Keep track of what locales have been tried:
|
|
108
|
-
locales = Set.new
|
|
109
|
-
|
|
110
|
-
host_preferred_locales(env) do |locale|
|
|
111
|
-
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
request_preferred_locale(env) do |locale, path|
|
|
115
|
-
# We have extracted a locale from the path, so from this point on we should use the updated path:
|
|
116
|
-
env = env.merge(Rack::PATH_INFO => path.to_s)
|
|
117
|
-
|
|
118
|
-
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
browser_preferred_locales(env).each do |locale|
|
|
122
|
-
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
@default_locales.each do |locale|
|
|
126
|
-
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def host_preferred_locales(env)
|
|
131
|
-
http_host = env[Rack::HTTP_HOST]
|
|
132
|
-
|
|
133
|
-
# Yield all hosts which match the incoming http_host:
|
|
134
|
-
@hosts.each do |pattern, locale|
|
|
135
|
-
yield locale if http_host[pattern]
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def request_preferred_locale(env)
|
|
140
|
-
path = Path[env[Rack::PATH_INFO]]
|
|
141
|
-
|
|
142
|
-
if request_locale = @all_locales.patterns[path.first]
|
|
143
|
-
# Remove the localization prefix:
|
|
144
|
-
path.delete_at(0)
|
|
145
|
-
|
|
146
|
-
yield request_locale, path
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def browser_preferred_locales(env)
|
|
151
|
-
accept_languages = env[HTTP_ACCEPT_LANGUAGE]
|
|
152
|
-
|
|
153
|
-
# No user prefered languages:
|
|
154
|
-
return [] unless accept_languages
|
|
155
|
-
|
|
156
|
-
# Extract the ordered list of languages:
|
|
157
|
-
languages = HTTP::Accept::Languages.parse(accept_languages)
|
|
158
|
-
|
|
159
|
-
# Returns available languages based on the order languages:
|
|
160
|
-
return @all_locales & languages
|
|
161
|
-
rescue HTTP::Accept::ParseError
|
|
162
|
-
# If we fail to parse the browser Accept-Language header, we ignore it (silently).
|
|
163
|
-
return []
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def localized?(env)
|
|
167
|
-
# Ignore requests which match the ignored paths:
|
|
168
|
-
path_info = env[Rack::PATH_INFO]
|
|
169
|
-
return false if @ignore.any? { |pattern| path_info[pattern] != nil }
|
|
170
|
-
|
|
171
|
-
return true
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# Set the Vary: header on the response to indicate that this response should include the header in the cache key.
|
|
175
|
-
def vary(env, response)
|
|
176
|
-
headers = response[1].to_a
|
|
177
|
-
|
|
178
|
-
# This response was based on the Accept-Language header:
|
|
179
|
-
headers << ["Vary", "Accept-Language"]
|
|
180
|
-
|
|
181
|
-
# Althought this header is generally not supported, we supply it anyway as it is useful for debugging:
|
|
182
|
-
if locale = env[CURRENT_LOCALE_KEY]
|
|
183
|
-
# Set the Content-Location to point to the localized URI as requested:
|
|
184
|
-
headers["Content-Location"] = "/#{locale}" + env[Rack::PATH_INFO]
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
return response
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def call(env)
|
|
191
|
-
# Pass the request through if it shouldn't be localized:
|
|
192
|
-
return @app.call(env) unless localized?(env)
|
|
193
|
-
|
|
194
|
-
env[LOCALIZATION_KEY] = self
|
|
195
|
-
|
|
196
|
-
response = nil
|
|
197
|
-
|
|
198
|
-
# We have a non-localized request, but there might be a localized resource. We return the best localization possible:
|
|
199
|
-
preferred_locales(env) do |localized_env|
|
|
200
|
-
# puts "Trying locale: #{localized_env[CURRENT_LOCALE_KEY]}: #{localized_env[Rack::PATH_INFO]}..."
|
|
201
|
-
|
|
202
|
-
response = @app.call(localized_env)
|
|
203
|
-
|
|
204
|
-
break unless response[0] >= 400
|
|
205
|
-
|
|
206
|
-
response[2].close if response[2].respond_to?(:close)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
return vary(env, response)
|
|
9
|
+
module Localization
|
|
10
|
+
def self.new(...)
|
|
11
|
+
Middleware.new(...)
|
|
210
12
|
end
|
|
211
13
|
end
|
|
212
14
|
end
|
data/lib/utopia/path.rb
CHANGED
|
@@ -57,9 +57,9 @@ module Utopia
|
|
|
57
57
|
|
|
58
58
|
# Converts '+' into whitespace and hex encoded characters into their equivalent characters.
|
|
59
59
|
def self.unescape(string)
|
|
60
|
-
string.tr("+", " ").gsub(/((?:%[0-9a-fA-F]{2})+)/n)
|
|
60
|
+
string.tr("+", " ").gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
|
|
61
61
|
[$1.delete("%")].pack("H*")
|
|
62
|
-
|
|
62
|
+
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def self.[] path
|
|
@@ -215,19 +215,34 @@ module Utopia
|
|
|
215
215
|
end
|
|
216
216
|
|
|
217
217
|
def simplify
|
|
218
|
-
|
|
218
|
+
components = []
|
|
219
219
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
end
|
|
220
|
+
index = 0
|
|
221
|
+
|
|
222
|
+
if @components[0] == ""
|
|
223
|
+
components << ""
|
|
224
|
+
index += 1
|
|
226
225
|
end
|
|
227
226
|
|
|
228
|
-
|
|
227
|
+
while index < @components.size
|
|
228
|
+
bit = @components[index]
|
|
229
|
+
if bit == "."
|
|
230
|
+
# No-op (ignore current directory)
|
|
231
|
+
elsif bit == "" && index != @components.size - 1
|
|
232
|
+
# No-op (ignore multiple slashes)
|
|
233
|
+
elsif bit == ".." && components.last && components.last != ".."
|
|
234
|
+
if components.last != ""
|
|
235
|
+
# We can go up one level:
|
|
236
|
+
components.pop
|
|
237
|
+
end
|
|
238
|
+
else
|
|
239
|
+
components << bit
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
index += 1
|
|
243
|
+
end
|
|
229
244
|
|
|
230
|
-
return self.class.new(
|
|
245
|
+
return self.class.new(components)
|
|
231
246
|
end
|
|
232
247
|
|
|
233
248
|
# Returns the first path component.
|
data/lib/utopia/redirection.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Utopia
|
|
|
13
13
|
def initialize(resource_path, resource_status, error_path, error_status)
|
|
14
14
|
@resource_path = resource_path
|
|
15
15
|
@resource_status = resource_status
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
@error_path = error_path
|
|
18
18
|
@error_status = error_status
|
|
19
19
|
|
|
@@ -47,7 +47,7 @@ module Utopia
|
|
|
47
47
|
if unhandled_error?(response) && location = @codes[response[0]]
|
|
48
48
|
error_request = env.merge(Rack::PATH_INFO => location, Rack::REQUEST_METHOD => Rack::GET)
|
|
49
49
|
error_response = @app.call(error_request)
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
if error_response[0] >= 400
|
|
52
52
|
raise RequestFailure.new(env[Rack::PATH_INFO], response[0], location, error_response[0])
|
|
53
53
|
else
|