embargoed 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +19 -0
- data/README.md +27 -0
- data/lib/embargoed/configuration.rb +60 -0
- data/lib/embargoed/engine.rb +16 -0
- data/lib/embargoed/i18n/accept_language_parser.rb +109 -0
- data/lib/embargoed/i18n/internationalization.rb +141 -0
- data/lib/embargoed/ip_locator.rb +29 -0
- data/lib/embargoed/maintenance_file.rb +119 -0
- data/lib/embargoed/maintenance_page/base.rb +79 -0
- data/lib/embargoed/maintenance_page/erb.rb +22 -0
- data/lib/embargoed/maintenance_page/html.rb +21 -0
- data/lib/embargoed/maintenance_page/json.rb +26 -0
- data/lib/embargoed/maintenance_page.rb +24 -0
- data/lib/embargoed/ordered_options.rb +98 -0
- data/lib/embargoed/rake_tasks.rb +3 -0
- data/lib/embargoed/request.rb +39 -0
- data/lib/embargoed/version.rb +3 -0
- data/lib/embargoed.rb +16 -0
- data/lib/rack/embargoed.rb +21 -0
- data/lib/tasks/maintenance.rake +48 -0
- data/public/Geoacumen-Country.mmdb +0 -0
- data/public/embargoed-message.jpg +0 -0
- data/public/ip_geo_db_license.md +206 -0
- data/public/maintenance.html +62 -0
- data/public/maintenance.html.erb +69 -0
- data/public/maintenance.json +1 -0
- metadata +239 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c4f9b6c54838896e47c825725907170322148c1bf40351d0e4607b5375037e98
|
4
|
+
data.tar.gz: 63e544b02169e811de9547fd1bf95a4008df0faaaa5e5fdf51efbdfb253b5d18
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2a3a1062c73c4014abb13f1077587831b98a17e77675e806b567f165f6bf352538b929ab2a01c15a0958004f90c7716d7e74cf63b232da3e646aaa805cae7b6d
|
7
|
+
data.tar.gz: 4183a31830ffee4d535f8a35b49681318462d8afe416d330bf3a5d8cf01466cd60c6679c69a3689779584efe048335cf9e09fd32e1819a5a123e3e1e314b2e8d
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 by Biola University
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Embargoed
|
2
|
+
=======
|
3
|
+
Embargoed is [Rack](http://rack.rubyforge.org/) middleware with a [Ruby on Rails](http://rubyonrails.org) engine that blocks all requests from Russia and displays a pro-Ukraine message instead.
|
4
|
+
|
5
|
+
Embargoed is a fork from [Turnout](https://github.com/biola/turnout).
|
6
|
+
|
7
|
+
![Embargoed message displayed to Russian visitors](https://github.com/rameerez/embargoed/blob/main/public/embargoed-message.jpg?raw=true)
|
8
|
+
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this to your `Gemfile`:
|
13
|
+
|
14
|
+
gem 'embargoed'
|
15
|
+
|
16
|
+
then run
|
17
|
+
|
18
|
+
bundle install
|
19
|
+
|
20
|
+
_Note that you'll need to restart your Rails server before it works_
|
21
|
+
|
22
|
+
That's it! Just by having `embargoed` on your Gemfile your Rails app will display this message to all requests from a Russian IP.
|
23
|
+
|
24
|
+
|
25
|
+
## Collaborate
|
26
|
+
|
27
|
+
Please feel free to contact me [@rameerez](https://twitter.com/rameerez) or fork this to port it to other platforms, or make PRs to this repo to collaborate.
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative './ordered_options'
|
2
|
+
module Embargoed
|
3
|
+
class Configuration
|
4
|
+
SETTINGS = [
|
5
|
+
:app_root,
|
6
|
+
:named_maintenance_file_paths,
|
7
|
+
:maintenance_pages_path,
|
8
|
+
:default_maintenance_page,
|
9
|
+
:default_reason,
|
10
|
+
:default_allowed_ips,
|
11
|
+
:skip_middleware,
|
12
|
+
:default_allowed_paths,
|
13
|
+
:default_response_code,
|
14
|
+
:default_retry_after,
|
15
|
+
:i18n
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
SETTINGS.each do |setting|
|
19
|
+
attr_accessor setting
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@skip_middleware = false
|
24
|
+
@app_root = '.'
|
25
|
+
@named_maintenance_file_paths = {default: app_root.join('tmp', 'maintenance.yml').to_s}
|
26
|
+
@maintenance_pages_path = app_root.join('public').to_s
|
27
|
+
@default_maintenance_page = Embargoed::MaintenancePage::HTML
|
28
|
+
@default_reason = "The site is temporarily down for maintenance.\nPlease check back soon."
|
29
|
+
@default_allowed_paths = []
|
30
|
+
@default_allowed_ips = []
|
31
|
+
@default_response_code = 503
|
32
|
+
@default_retry_after = 7200 # 2 hours by default
|
33
|
+
@i18n = Embargoed::OrderedOptions.new
|
34
|
+
@i18n.railties_load_path = []
|
35
|
+
@i18n.load_path = []
|
36
|
+
@i18n.fallbacks = Embargoed::OrderedOptions.new
|
37
|
+
@i18n.enabled = false
|
38
|
+
@i18n.use_language_header = false
|
39
|
+
end
|
40
|
+
|
41
|
+
def app_root
|
42
|
+
Pathname.new(@app_root.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
def named_maintenance_file_paths=(named_paths)
|
46
|
+
# Force keys to symbols
|
47
|
+
@named_maintenance_file_paths = Hash[named_paths.map { |k, v| [k.to_sym, v] }]
|
48
|
+
end
|
49
|
+
|
50
|
+
def update(settings_hash)
|
51
|
+
settings_hash.each do |setting, value|
|
52
|
+
unless SETTINGS.include? setting.to_sym
|
53
|
+
raise ArgumentError, "invalid setting: #{setting}"
|
54
|
+
end
|
55
|
+
|
56
|
+
self.public_send "#{setting}=", value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'embargoed'
|
2
|
+
require 'rack/embargoed'
|
3
|
+
require 'rails'
|
4
|
+
|
5
|
+
# For Rails 3
|
6
|
+
if defined? Rails::Engine
|
7
|
+
module Embargoed
|
8
|
+
class Engine < Rails::Engine
|
9
|
+
initializer 'embargoed.add_to_middleware_stack' do |app|
|
10
|
+
unless Embargoed.config.skip_middleware
|
11
|
+
app.config.middleware.use(Rack::Embargoed)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Embargoed
|
2
|
+
class AcceptLanguageParser
|
3
|
+
attr_accessor :header
|
4
|
+
|
5
|
+
def initialize(header)
|
6
|
+
@header = header
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns a sorted array based on user preference in HTTP_ACCEPT_LANGUAGE.
|
10
|
+
# Browsers send this HTTP header, so don't think this is holy.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# request.user_preferred_languages
|
15
|
+
# # => [ 'nl-NL', 'nl-BE', 'nl', 'en-US', 'en' ]
|
16
|
+
#
|
17
|
+
def user_preferred_languages
|
18
|
+
return [] if header.to_s.strip.empty?
|
19
|
+
@user_preferred_languages ||= begin
|
20
|
+
header.to_s.gsub(/\s+/, '').split(',').map do |language|
|
21
|
+
locale, quality = language.split(';q=')
|
22
|
+
raise ArgumentError, 'Not correctly formatted' unless locale =~ /^[a-z\-0-9]+|\*$/i
|
23
|
+
|
24
|
+
locale = locale.downcase.gsub(/-[a-z0-9]+$/i, &:upcase) # Uppercase territory
|
25
|
+
locale = nil if locale == '*' # Ignore wildcards
|
26
|
+
|
27
|
+
quality = quality ? quality.to_f : 1.0
|
28
|
+
|
29
|
+
[locale, quality]
|
30
|
+
end.sort do |(_, left), (_, right)|
|
31
|
+
right <=> left
|
32
|
+
end.map(&:first).compact
|
33
|
+
rescue ArgumentError # Just rescue anything if the browser messed up badly.
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Sets the user languages preference, overriding the browser
|
39
|
+
#
|
40
|
+
def user_preferred_languages=(languages)
|
41
|
+
@user_preferred_languages = languages
|
42
|
+
end
|
43
|
+
|
44
|
+
# Finds the locale specifically requested by the browser.
|
45
|
+
#
|
46
|
+
# Example:
|
47
|
+
#
|
48
|
+
# request.preferred_language_from I18n.available_locales
|
49
|
+
# # => 'nl'
|
50
|
+
#
|
51
|
+
def preferred_language_from(array)
|
52
|
+
(user_preferred_languages & array.map(&:to_s)).first
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the first of the user_preferred_languages that is compatible
|
56
|
+
# with the available locales. Ignores region.
|
57
|
+
#
|
58
|
+
# Example:
|
59
|
+
#
|
60
|
+
# request.compatible_language_from I18n.available_locales
|
61
|
+
#
|
62
|
+
def compatible_language_from(available_languages)
|
63
|
+
user_preferred_languages.map do |preferred| #en-US
|
64
|
+
preferred = preferred.downcase
|
65
|
+
preferred_language = preferred.split('-', 2).first
|
66
|
+
|
67
|
+
available_languages.find do |available| # en
|
68
|
+
available = available.to_s.downcase
|
69
|
+
preferred == available || preferred_language == available.split('-', 2).first
|
70
|
+
end
|
71
|
+
end.compact.first
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns a supplied list of available locals without any extra application info
|
75
|
+
# that may be attached to the locale for storage in the application.
|
76
|
+
#
|
77
|
+
# Example:
|
78
|
+
# [ja_JP-x1, en-US-x4, en_UK-x5, fr-FR-x3] => [ja-JP, en-US, en-UK, fr-FR]
|
79
|
+
#
|
80
|
+
def sanitize_available_locales(available_languages)
|
81
|
+
available_languages.map do |available|
|
82
|
+
available.to_s.split(/[_-]/).reject { |part| part.start_with?("x") }.join("-")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the first of the user preferred languages that is
|
87
|
+
# also found in available languages. Finds best fit by matching on
|
88
|
+
# primary language first and secondarily on region. If no matching region is
|
89
|
+
# found, return the first language in the group matching that primary language.
|
90
|
+
#
|
91
|
+
# Example:
|
92
|
+
#
|
93
|
+
# request.language_region_compatible(available_languages)
|
94
|
+
#
|
95
|
+
def language_region_compatible_from(available_languages)
|
96
|
+
available_languages = sanitize_available_locales(available_languages)
|
97
|
+
user_preferred_languages.map do |preferred| #en-US
|
98
|
+
preferred = preferred.downcase
|
99
|
+
preferred_language = preferred.split('-', 2).first
|
100
|
+
|
101
|
+
lang_group = available_languages.select do |available| # en
|
102
|
+
preferred_language == available.downcase.split('-', 2).first
|
103
|
+
end
|
104
|
+
|
105
|
+
lang_group.find { |lang| lang.downcase == preferred } || lang_group.first #en-US, en-UK
|
106
|
+
end.compact.first
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'i18n/backend/fallbacks'
|
3
|
+
require_relative './accept_language_parser'
|
4
|
+
require_relative '../ordered_options'
|
5
|
+
|
6
|
+
module Embargoed
|
7
|
+
class Internationalization
|
8
|
+
class << self
|
9
|
+
attr_reader :env
|
10
|
+
attr_writer :env
|
11
|
+
|
12
|
+
def initialize_i18n(env)
|
13
|
+
@env = env
|
14
|
+
setup_i18n_config
|
15
|
+
end
|
16
|
+
|
17
|
+
def i18n_config
|
18
|
+
@i18n_config = Embargoed.config.i18n
|
19
|
+
@i18n_config = @i18n_config.is_a?(Embargoed::OrderedOptions) ? @i18n_config : Embargoed::InheritableOptions.new(@i18n_config)
|
20
|
+
end
|
21
|
+
|
22
|
+
def embargoed_page
|
23
|
+
@embargoed_page ||= Embargoed.config.default_maintenance_page
|
24
|
+
end
|
25
|
+
|
26
|
+
def http_accept_language
|
27
|
+
language = (env.nil? || env.empty?) ? nil : env["HTTP_ACCEPT_LANGUAGE"]
|
28
|
+
@http_accept_language ||= Embargoed::AcceptLanguageParser.new(language)
|
29
|
+
end
|
30
|
+
|
31
|
+
def setup_additional_helpers
|
32
|
+
i18n_additional_helpers = i18n_config.delete(:additional_helpers)
|
33
|
+
i18n_additional_helpers = i18n_additional_helpers.is_a?(Array) ? i18n_additional_helpers : []
|
34
|
+
|
35
|
+
i18n_additional_helpers.each do |helper|
|
36
|
+
embargoed_page.send(:include, helper) if helper.is_a?(Module)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def expanded(path)
|
41
|
+
result = []
|
42
|
+
if File.directory?(path)
|
43
|
+
result.concat(Dir.glob(File.join(path, '**', '**')).map { |file| file }.sort)
|
44
|
+
else
|
45
|
+
result << path
|
46
|
+
end
|
47
|
+
result.uniq!
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns all expanded paths but only if they exist in the filesystem.
|
52
|
+
def existent(path)
|
53
|
+
expanded(path).select { |f| File.exist?(f) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Setup i18n configuration.
|
57
|
+
def setup_i18n_config
|
58
|
+
return unless i18n_config.enabled
|
59
|
+
setup_additional_helpers
|
60
|
+
fallbacks = i18n_config.delete(:fallbacks)
|
61
|
+
|
62
|
+
|
63
|
+
# Avoid issues with setting the default_locale by disabling available locales
|
64
|
+
# check while configuring.
|
65
|
+
enforce_available_locales = i18n_config.delete(:enforce_available_locales)
|
66
|
+
enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
|
67
|
+
I18n.enforce_available_locales = false
|
68
|
+
|
69
|
+
i18n_config.except(:enabled, :use_language_header).each do |setting, value|
|
70
|
+
case setting
|
71
|
+
when :railties_load_path
|
72
|
+
I18n.load_path.unshift(*value.map { |file| existent(file) }.flatten)
|
73
|
+
when :load_path
|
74
|
+
I18n.load_path += value
|
75
|
+
else
|
76
|
+
I18n.send("#{setting}=", value)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
|
82
|
+
I18n.backend.load_translations
|
83
|
+
|
84
|
+
# Restore available locales check so it will take place from now on.
|
85
|
+
I18n.enforce_available_locales = enforce_available_locales
|
86
|
+
|
87
|
+
begin
|
88
|
+
if i18n_config.use_language_header
|
89
|
+
I18n.locale = http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale
|
90
|
+
else
|
91
|
+
I18n.locale = I18n.default_locale
|
92
|
+
end
|
93
|
+
rescue
|
94
|
+
#nothing
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
def array_wrap(object)
|
100
|
+
if object.nil?
|
101
|
+
[]
|
102
|
+
elsif object.respond_to?(:to_ary)
|
103
|
+
object.to_ary || [object]
|
104
|
+
else
|
105
|
+
[object]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def include_fallbacks_module
|
110
|
+
I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
|
111
|
+
end
|
112
|
+
|
113
|
+
def init_fallbacks(fallbacks)
|
114
|
+
include_fallbacks_module
|
115
|
+
|
116
|
+
args = case fallbacks
|
117
|
+
when Embargoed::OrderedOptions
|
118
|
+
[*(fallbacks[:defaults] || []) << fallbacks[:map]].compact
|
119
|
+
when Hash, Array
|
120
|
+
array_wrap(fallbacks)
|
121
|
+
else # TrueClass
|
122
|
+
[]
|
123
|
+
end
|
124
|
+
|
125
|
+
I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
|
126
|
+
end
|
127
|
+
|
128
|
+
def validate_fallbacks(fallbacks)
|
129
|
+
case fallbacks
|
130
|
+
when Embargoed::OrderedOptions
|
131
|
+
!fallbacks.empty?
|
132
|
+
when TrueClass, Array, Hash
|
133
|
+
true
|
134
|
+
else
|
135
|
+
raise "Unexpected fallback type #{fallbacks.inspect}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'maxmind/db'
|
2
|
+
|
3
|
+
module Embargoed
|
4
|
+
class IPLocator
|
5
|
+
|
6
|
+
def self.get_country_code(ip)
|
7
|
+
country_code = "none"
|
8
|
+
|
9
|
+
# Using an Open-Source IP db released under an Apache2 license: https://github.com/geoacumen/geoacumen-country
|
10
|
+
db = File.expand_path("../../../public/Geoacumen-Country.mmdb", __FILE__)
|
11
|
+
reader = MaxMind::DB.new(db, mode: MaxMind::DB::MODE_MEMORY)
|
12
|
+
|
13
|
+
begin
|
14
|
+
record = reader.get(ip)
|
15
|
+
rescue => error
|
16
|
+
puts error.inspect
|
17
|
+
end
|
18
|
+
|
19
|
+
if !record.nil?
|
20
|
+
country_code = record['country']['iso_code']
|
21
|
+
end
|
22
|
+
|
23
|
+
reader.close
|
24
|
+
|
25
|
+
return country_code
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Embargoed
|
5
|
+
class MaintenanceFile
|
6
|
+
attr_reader :path
|
7
|
+
|
8
|
+
SETTINGS = [:reason, :allowed_paths, :allowed_ips, :response_code, :retry_after]
|
9
|
+
attr_reader(*SETTINGS)
|
10
|
+
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
@reason = Embargoed.config.default_reason
|
14
|
+
@allowed_paths = Embargoed.config.default_allowed_paths
|
15
|
+
@allowed_ips = Embargoed.config.default_allowed_ips
|
16
|
+
@response_code = Embargoed.config.default_response_code
|
17
|
+
@retry_after = Embargoed.config.default_retry_after
|
18
|
+
|
19
|
+
import_yaml if exists?
|
20
|
+
end
|
21
|
+
|
22
|
+
def exists?
|
23
|
+
File.exist? path
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_h
|
27
|
+
SETTINGS.each_with_object({}) do |att, hash|
|
28
|
+
hash[att] = send(att)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_yaml(key_mapper = :to_s)
|
33
|
+
to_h.each_with_object({}) { |(key, val), hash|
|
34
|
+
hash[key.send(key_mapper)] = val
|
35
|
+
}.to_yaml
|
36
|
+
end
|
37
|
+
|
38
|
+
def write
|
39
|
+
FileUtils.mkdir_p(dir_path) unless Dir.exist? dir_path
|
40
|
+
|
41
|
+
File.open(path, 'w') do |file|
|
42
|
+
file.write to_yaml
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete
|
47
|
+
File.delete(path) if exists?
|
48
|
+
end
|
49
|
+
|
50
|
+
def import(hash)
|
51
|
+
SETTINGS.map(&:to_s).each do |att|
|
52
|
+
self.send(:"#{att}=", hash[att]) unless hash[att].nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
true
|
56
|
+
end
|
57
|
+
alias :import_env_vars :import
|
58
|
+
|
59
|
+
# Find the first MaintenanceFile that exists
|
60
|
+
def self.find
|
61
|
+
path = named_paths.values.find { |p| File.exist? p }
|
62
|
+
self.new(path) if path
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.named(name)
|
66
|
+
path = named_paths[name.to_sym]
|
67
|
+
self.new(path) unless path.nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.default
|
71
|
+
self.new(named_paths.values.first)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def retry_after=(value)
|
77
|
+
@retry_after = value
|
78
|
+
end
|
79
|
+
|
80
|
+
def reason=(reason)
|
81
|
+
@reason = reason.to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
# Splits strings on commas for easier importing of environment variables
|
85
|
+
def allowed_paths=(paths)
|
86
|
+
if paths.is_a? String
|
87
|
+
# Grab everything between commas that aren't escaped with a backslash
|
88
|
+
paths = paths.to_s.split(/(?<!\\),\ ?/).map do |path|
|
89
|
+
path.strip.gsub('\,', ',') # remove the escape characters
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
@allowed_paths = paths
|
94
|
+
end
|
95
|
+
|
96
|
+
# Splits strings on commas for easier importing of environment variables
|
97
|
+
def allowed_ips=(ips)
|
98
|
+
ips = ips.to_s.split(',') if ips.is_a? String
|
99
|
+
|
100
|
+
@allowed_ips = ips
|
101
|
+
end
|
102
|
+
|
103
|
+
def response_code=(code)
|
104
|
+
@response_code = code.to_i
|
105
|
+
end
|
106
|
+
|
107
|
+
def dir_path
|
108
|
+
File.dirname(path)
|
109
|
+
end
|
110
|
+
|
111
|
+
def import_yaml
|
112
|
+
import YAML::load(File.open(path)) || {}
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.named_paths
|
116
|
+
Embargoed.config.named_maintenance_file_paths
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Embargoed
|
2
|
+
module MaintenancePage
|
3
|
+
class Base
|
4
|
+
attr_reader :reason
|
5
|
+
|
6
|
+
def initialize(reason = nil, options = {})
|
7
|
+
@options = options.is_a?(Hash) ? options : {}
|
8
|
+
@reason = reason
|
9
|
+
end
|
10
|
+
|
11
|
+
def rack_response(code = nil, retry_after = nil)
|
12
|
+
code ||= Embargoed.config.default_response_code
|
13
|
+
[code, headers(retry_after), body]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Override with an array of media type strings. i.e. text/html
|
17
|
+
def self.media_types
|
18
|
+
raise NotImplementedError, '.media_types must be overridden in subclasses'
|
19
|
+
end
|
20
|
+
def media_types() self.class.media_types end
|
21
|
+
|
22
|
+
# Override with a file extension value like 'html' or 'json'
|
23
|
+
def self.extension
|
24
|
+
raise NotImplementedError, '.extension must be overridden in subclasses'
|
25
|
+
end
|
26
|
+
def extension() self.class.extension end
|
27
|
+
|
28
|
+
def custom_path
|
29
|
+
Pathname.new(Embargoed.config.maintenance_pages_path).join(filename)
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def self.inherited(subclass)
|
35
|
+
MaintenancePage.all << subclass
|
36
|
+
end
|
37
|
+
|
38
|
+
def headers(retry_after = nil)
|
39
|
+
headers = {'Content-Type' => media_types.first, 'Content-Length' => length}
|
40
|
+
# Include the Retry-After header unless it wasn't specified
|
41
|
+
headers['Retry-After'] = retry_after.to_s unless retry_after.nil?
|
42
|
+
headers
|
43
|
+
end
|
44
|
+
|
45
|
+
def length
|
46
|
+
content.bytesize.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def body
|
50
|
+
[content]
|
51
|
+
end
|
52
|
+
|
53
|
+
def content
|
54
|
+
file_content.gsub(/{{\s?reason\s?}}/, reason)
|
55
|
+
end
|
56
|
+
|
57
|
+
def file_content
|
58
|
+
File.read(path)
|
59
|
+
end
|
60
|
+
|
61
|
+
def path
|
62
|
+
if File.exist? custom_path
|
63
|
+
custom_path
|
64
|
+
else
|
65
|
+
default_path
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def default_path
|
70
|
+
File.expand_path("../../../../public/#{filename}", __FILE__)
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def filename
|
75
|
+
"maintenance.#{extension}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'tilt'
|
3
|
+
require 'tilt/erb'
|
4
|
+
require_relative './html'
|
5
|
+
require_relative '../i18n/internationalization'
|
6
|
+
|
7
|
+
module Embargoed
|
8
|
+
module MaintenancePage
|
9
|
+
class Erb < Embargoed::MaintenancePage::HTML
|
10
|
+
|
11
|
+
def content
|
12
|
+
Embargoed::Internationalization.initialize_i18n(@options[:env])
|
13
|
+
Tilt.new(File.expand_path(path)).render(self, {reason: reason}.merge(@options))
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.extension
|
17
|
+
'html.erb'
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
module Embargoed
|
3
|
+
module MaintenancePage
|
4
|
+
class HTML < Base
|
5
|
+
def reason
|
6
|
+
super.to_s.split("\n").map{|txt| "<p>#{txt}</p>" }.join("\n")
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.media_types
|
10
|
+
%w{
|
11
|
+
text/html
|
12
|
+
application/xhtml+xml
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.extension
|
17
|
+
'html'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Embargoed
|
4
|
+
module MaintenancePage
|
5
|
+
class JSON < Base
|
6
|
+
def reason
|
7
|
+
super.to_s.to_json
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.media_types
|
11
|
+
%w{
|
12
|
+
application/json
|
13
|
+
text/json
|
14
|
+
application/x-javascript
|
15
|
+
text/javascript
|
16
|
+
text/x-javascript
|
17
|
+
text/x-json
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.extension
|
22
|
+
'json'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|