camaleon_cms 2.7.4 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of camaleon_cms might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +20 -20
- data/app/apps/plugins/attack/config/config.json +2 -2
- data/app/apps/plugins/front_cache/admin_controller.rb +4 -6
- data/app/apps/plugins/front_cache/config/config.json +1 -1
- data/app/apps/plugins/front_cache/config/locales/translation.yml +1 -1
- data/app/apps/plugins/front_cache/front_cache_helper.rb +3 -3
- data/app/apps/plugins/visibility_post/config/config.json +2 -2
- data/app/apps/themes/camaleon_first/assets/js/main.js +1 -1
- data/app/apps/themes/camaleon_first/views/index.html.erb +1 -1
- data/app/apps/themes/default/assets/js/main.js +1 -1
- data/app/apps/themes/new/assets/js/main.js +1 -1
- data/app/apps/themes/new/views/index.html.erb +4 -4
- data/app/apps/themes/new/views/layouts/_footer.html.erb +2 -2
- data/app/assets/javascripts/camaleon_cms/admin/_posttype.js +7 -6
- data/app/assets/javascripts/camaleon_cms/admin/admin-basic-manifest.js +2 -2
- data/app/assets/javascripts/camaleon_cms/admin/admin-manifest.js +1 -1
- data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +20 -29
- data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/de.js +2 -2
- data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/en.js +2 -2
- data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/fr.js +2 -2
- data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/it.js +2 -2
- data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/nl.js +1 -1
- data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/pt-BR.js +2 -2
- data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/ru.js +2 -2
- data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/uk.js +2 -2
- data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/zh-CN.js +1 -1
- data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +2 -2
- data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +15 -10
- data/app/controllers/camaleon_cms/admin/installers_controller.rb +1 -1
- data/app/controllers/camaleon_cms/admin/media_controller.rb +6 -19
- data/app/controllers/camaleon_cms/admin/settings_controller.rb +3 -2
- data/app/controllers/camaleon_cms/camaleon_controller.rb +8 -2
- data/app/decorators/camaleon_cms/application_decorator.rb +8 -0
- data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
- data/app/decorators/camaleon_cms/post_decorator.rb +5 -5
- data/app/decorators/camaleon_cms/post_type_decorator.rb +5 -4
- data/app/decorators/camaleon_cms/site_decorator.rb +2 -1
- data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +1 -1
- data/app/helpers/camaleon_cms/admin/category_helper.rb +1 -1
- data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +12 -11
- data/app/helpers/camaleon_cms/html_helper.rb +6 -6
- data/app/helpers/camaleon_cms/plugins_helper.rb +1 -1
- data/app/helpers/camaleon_cms/session_helper.rb +1 -1
- data/app/helpers/camaleon_cms/short_code_helper.rb +1 -1
- data/app/helpers/camaleon_cms/site_helper.rb +1 -1
- data/app/helpers/camaleon_cms/theme_helper.rb +1 -1
- data/app/helpers/camaleon_cms/uploader_helper.rb +10 -5
- data/app/mailers/camaleon_cms/html_mailer.rb +5 -2
- data/app/models/camaleon_cms/ability.rb +1 -1
- data/app/models/camaleon_cms/custom_field_group.rb +2 -2
- data/app/models/camaleon_cms/nav_menu.rb +1 -1
- data/app/models/camaleon_cms/post.rb +1 -1
- data/app/models/camaleon_cms/post_default.rb +1 -1
- data/app/models/camaleon_cms/post_type.rb +8 -8
- data/app/models/camaleon_cms/site.rb +1 -1
- data/app/models/camaleon_cms/term_taxonomy.rb +13 -0
- data/app/models/concerns/camaleon_cms/custom_fields_read.rb +11 -22
- data/app/models/concerns/camaleon_cms/site_default_settings.rb +1 -1
- data/app/uploaders/camaleon_cms_uploader.rb +5 -0
- data/app/validators/camaleon_cms/post_uniq_validator.rb +15 -15
- data/app/validators/camaleon_cms/uniq_validator.rb +9 -3
- data/app/validators/camaleon_cms/user_url_validator.rb +207 -0
- data/app/views/camaleon_cms/admin/media/index.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +2 -2
- data/app/views/camaleon_cms/default_theme/index.html.erb +4 -4
- data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +3 -3
- data/app/views/layouts/camaleon_cms/admin/_footer.html.erb +1 -1
- data/config/initializers/custom_initializers.rb +14 -0
- data/config/locales/camaleon_cms/admin/en.yml +11 -1
- data/lib/camaleon_cms/engine.rb +1 -1
- data/lib/camaleon_cms/version.rb +1 -1
- data/lib/ext/hash.rb +1 -1
- data/lib/ext/string.rb +2 -2
- data/lib/generators/camaleon_cms/theme_template/assets/js/main.js +1 -1
- metadata +50 -7
@@ -101,7 +101,7 @@ module CamaleonCms
|
|
101
101
|
# add a post for current model
|
102
102
|
# title: title for post, => required
|
103
103
|
# content: html text content, => required
|
104
|
-
# thumb: image url, => default (empty). check
|
104
|
+
# thumb: image url, => default (empty). check https://camaleon.website/api-methods.html#section_fileuploads
|
105
105
|
# categories: [1,3,4,5], => default (empty)
|
106
106
|
# tags: String comma separated, => default (empty)
|
107
107
|
# slug: string key for post, => default (empty)
|
@@ -111,7 +111,7 @@ module CamaleonCms
|
|
111
111
|
# settings: Hash of post settings, sample => settings:
|
112
112
|
# {has_content: false, has_summary: true, default_layout: 'my_layout', default_template: 'my_template' } (optional, see more in post.set_setting(...))
|
113
113
|
# data_metas: {template: "", layout: ""}
|
114
|
-
# sample: my_posttype.add_post(title: "My Title", post_order: 5, content: 'lorem_ipsum', settings: {default_template: "home/counters", has_content: false, has_seo: false, skip_fields: ["sub_tite", 'banner']}, fields: {pattern: true, bg: '
|
114
|
+
# sample: my_posttype.add_post(title: "My Title", post_order: 5, content: 'lorem_ipsum', settings: {default_template: "home/counters", has_content: false, has_seo: false, skip_fields: ["sub_tite", 'banner']}, fields: {pattern: true, bg: 'https://www.reallusion.com/de/images/3dx5/whatsnew/3dx5_features_banner_bg_02.jpg'})
|
115
115
|
# More samples here: https://gist.github.com/owen2345/eba9691585ed78ad6f7b52e9591357bf
|
116
116
|
# return created post if it was created, else return errors
|
117
117
|
def add_post(args)
|
@@ -139,12 +139,12 @@ module CamaleonCms
|
|
139
139
|
# return all available route formats of this post type for content posts
|
140
140
|
def contents_route_formats
|
141
141
|
{
|
142
|
-
'post_of_post_type' => '<code>/group/:post_type_id-:title/:slug</code><br> (Sample:
|
143
|
-
'post_of_category' => '<code>/category/:category_id-:title/:slug</code><br> (Sample:
|
144
|
-
'post_of_category_post_type' => '<code>/:post_type_title/category/:category_id-:title/:slug</code><br> (Sample:
|
145
|
-
'post_of_posttype' => '<code>/:post_type_title/:slug</code><br> (Sample:
|
146
|
-
'post' => '<code>/:slug</code><br> (Sample:
|
147
|
-
'hierarchy_post' => '<code>/:parent1_slug/:parent2_slug/.../:slug</code><br> (Sample:
|
142
|
+
'post_of_post_type' => '<code>/group/:post_type_id-:title/:slug</code><br> (Sample: https://localhost.com/group/17-services/myservice.html)',
|
143
|
+
'post_of_category' => '<code>/category/:category_id-:title/:slug</code><br> (Sample: https://localhost.com/category/17-services/myservice.html)',
|
144
|
+
'post_of_category_post_type' => '<code>/:post_type_title/category/:category_id-:title/:slug</code><br> (Sample: https://localhost.com/services/category/17-services/myservice.html)',
|
145
|
+
'post_of_posttype' => '<code>/:post_type_title/:slug</code><br> (Sample: https://localhost.com/services/myservice.html)',
|
146
|
+
'post' => '<code>/:slug</code><br> (Sample: https://localhost.com/myservice.html)',
|
147
|
+
'hierarchy_post' => '<code>/:parent1_slug/:parent2_slug/.../:slug</code><br> (Sample: https://localhost.com/item-1/item-1-1/item-111.html)'
|
148
148
|
}
|
149
149
|
end
|
150
150
|
|
@@ -91,7 +91,7 @@ module CamaleonCms
|
|
91
91
|
# return theme model with slug theme_slug for this site
|
92
92
|
# theme_slug: (optional) if it is null, this will return current theme for this site
|
93
93
|
def get_theme(theme_slug = nil)
|
94
|
-
themes.where(slug:
|
94
|
+
themes.where(slug: theme_slug || get_theme_slug, status: nil).first_or_create!
|
95
95
|
end
|
96
96
|
|
97
97
|
# return plugin model with slug plugin_slug
|
@@ -16,6 +16,19 @@ module CamaleonCms
|
|
16
16
|
# attr_accessible :data_options
|
17
17
|
# attr_accessible :data_metas
|
18
18
|
|
19
|
+
# TODO: Remove the 1st branch when support will be dropped of Rails < 7.1
|
20
|
+
if ::Rails::VERSION::STRING < '7.1.0'
|
21
|
+
before_validation(on: %i[create update]) do
|
22
|
+
%i[name description].each do |attr|
|
23
|
+
next unless new_record? || attribute_changed?(attr)
|
24
|
+
|
25
|
+
self[attr] = ActionController::Base.helpers.sanitize(__send__(attr))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
else
|
29
|
+
normalizes :name, :description, with: ->(field) { ActionController::Base.helpers.sanitize(field) }
|
30
|
+
end
|
31
|
+
|
19
32
|
# callbacks
|
20
33
|
before_validation :before_validating
|
21
34
|
before_destroy :destroy_dependencies
|
@@ -3,18 +3,6 @@ module CamaleonCms
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
included do
|
5
5
|
before_destroy :_destroy_custom_field_groups
|
6
|
-
# DEPRECATED, INSTEAD USE: custom_fields
|
7
|
-
has_many :fields, lambda { |object|
|
8
|
-
where(object_class: object.class.to_s.gsub('Decorator', '').gsub('CamaleonCms::', ''))
|
9
|
-
}, class_name: 'CamaleonCms::CustomField', foreign_key: :objectid
|
10
|
-
# DEPRECATED, INSTEAD USE: custom_field_values
|
11
|
-
has_many :field_values, lambda { |object|
|
12
|
-
where(object_class: object.class.to_s.gsub('Decorator', '').gsub('CamaleonCms::', ''))
|
13
|
-
}, class_name: 'CamaleonCms::CustomFieldsRelationship', foreign_key: :objectid, dependent: :delete_all
|
14
|
-
# DEPRECATED, INSTEAD USE: custom_field_groups
|
15
|
-
has_many :field_groups, lambda { |object|
|
16
|
-
where(object_class: object.class.to_s.parseCamaClass)
|
17
|
-
}, class_name: 'CamaleonCms::CustomFieldGroup', foreign_key: :objectid
|
18
6
|
end
|
19
7
|
|
20
8
|
# get custom field groups for current object
|
@@ -38,7 +26,7 @@ module CamaleonCms
|
|
38
26
|
{ kind: args,
|
39
27
|
include_parent: false }
|
40
28
|
else
|
41
|
-
{ kind: 'Post', include_parent: false }.merge(args)
|
29
|
+
{ kind: 'Post', include_parent: false }.merge!(args)
|
42
30
|
end
|
43
31
|
class_name = self.class.to_s.parseCamaClass
|
44
32
|
case class_name
|
@@ -73,7 +61,7 @@ module CamaleonCms
|
|
73
61
|
CamaleonCms::CustomFieldGroup.where(object_class: "PostType_#{args[:kind]}", objectid: id)
|
74
62
|
end
|
75
63
|
else # 'Plugin' or other classes
|
76
|
-
|
64
|
+
custom_field_groups
|
77
65
|
end
|
78
66
|
end
|
79
67
|
|
@@ -131,7 +119,7 @@ module CamaleonCms
|
|
131
119
|
# puts res[0]['my_slug1'].first ==> "val 1"
|
132
120
|
def get_fields_grouped(field_keys)
|
133
121
|
res = []
|
134
|
-
custom_field_values.where(custom_field_slug: field_keys).order(group_number: :asc).group_by(&:group_number).
|
122
|
+
custom_field_values.where(custom_field_slug: field_keys).order(group_number: :asc).group_by(&:group_number).each_value do |group_fields|
|
135
123
|
group = {}
|
136
124
|
field_keys.each do |field_key|
|
137
125
|
_tmp = []
|
@@ -196,8 +184,8 @@ module CamaleonCms
|
|
196
184
|
# kind: argument only for PostType model: (Post | Category | PostTag), default => Post. If kind = "" this will add group for all post_types
|
197
185
|
def add_custom_field_group(values, kind = 'Post')
|
198
186
|
values = values.with_indifferent_access
|
199
|
-
group = get_field_groups(kind).
|
200
|
-
unless group
|
187
|
+
group = get_field_groups(kind).find_by(slug: values[:slug])
|
188
|
+
unless group
|
201
189
|
site = _cama_get_field_site
|
202
190
|
values[:parent_id] = site.id if site.present?
|
203
191
|
group = if is_a?(CamaleonCms::Post) # harcoded for post to support custom field groups
|
@@ -206,6 +194,7 @@ module CamaleonCms
|
|
206
194
|
get_field_groups(kind).create!(values)
|
207
195
|
end
|
208
196
|
end
|
197
|
+
|
209
198
|
group
|
210
199
|
end
|
211
200
|
alias add_field_group add_custom_field_group
|
@@ -215,8 +204,8 @@ module CamaleonCms
|
|
215
204
|
# more details in add_manual_field(item, options) from custom field groups
|
216
205
|
# kind: argument only for PostType model: (Post | Category | PostTag), default => Post
|
217
206
|
def add_custom_field_to_default_group(item, options, kind = 'Post')
|
218
|
-
g = get_field_groups(kind).
|
219
|
-
g
|
207
|
+
g = get_field_groups(kind).find_by(slug: '_default')
|
208
|
+
g ||= add_custom_field_group({ name: 'Default Field Group', slug: '_default' }, kind)
|
220
209
|
g.add_manual_field(item, options)
|
221
210
|
end
|
222
211
|
alias add_field add_custom_field_to_default_group
|
@@ -243,7 +232,7 @@ module CamaleonCms
|
|
243
232
|
|
244
233
|
ActiveRecord::Base.transaction do
|
245
234
|
custom_field_values.delete_all
|
246
|
-
datas.
|
235
|
+
datas.each_value do |fields_data|
|
247
236
|
fields_data.each do |field_key, values|
|
248
237
|
next unless values[:values].present?
|
249
238
|
|
@@ -260,7 +249,7 @@ module CamaleonCms
|
|
260
249
|
# update new value for field with slug _key
|
261
250
|
# Sample: my_posy.update_field_value('sub_title', 'Test Sub Title')
|
262
251
|
def update_field_value(_key, value = nil, group_number = 0)
|
263
|
-
custom_field_values.
|
252
|
+
custom_field_values.find_by(custom_field_slug: _key, group_number: group_number)&.update_column('value', value)
|
264
253
|
rescue StandardError
|
265
254
|
nil
|
266
255
|
end
|
@@ -287,7 +276,7 @@ module CamaleonCms
|
|
287
276
|
# sample: my_post.set_field_value('subtitle', 'Sub Title', {group_number: 1})
|
288
277
|
# sample: my_post.set_field_value('subtitle', 'Sub Title', {group_number: 1, group_number: 1}) # add field values for fields in group 1
|
289
278
|
def set_field_value(key, value, args = {})
|
290
|
-
args = { order: 0, group_number: 0, field_id: nil, clear: true }.merge(args)
|
279
|
+
args = { order: 0, group_number: 0, field_id: nil, clear: true }.merge!(args)
|
291
280
|
unless args[:field_id].present?
|
292
281
|
args[:field_id] = begin
|
293
282
|
get_field_object(key).id
|
@@ -25,7 +25,7 @@ module CamaleonCms
|
|
25
25
|
else
|
26
26
|
title = 'Welcome'
|
27
27
|
slug = 'welcome'
|
28
|
-
content = "<p style='text-align: center;'><img width='155' height='155' src='
|
28
|
+
content = "<p style='text-align: center;'><img width='155' height='155' src='https://camaleon.website/media/132/logo2.png' alt='logo' /></p><p><strong>Camaleon CMS</strong> is a free and open-source tool and a fexible content management system (CMS) based on <a href='https://rubyonrails.org'>Ruby on Rails</a>.</p> <p>With Camaleon you can do the following:</p> <ul> <li>Create instantly a lot of sites in the same installation</li> <li>Manage your content information in several languages</li> <li>Extend current functionality by plugins (MVC structure and no more echo or prints anywhere)</li> <li>Create or install different themes for each site</li> <li>Create your own structure without coding anything (adapt Camaleon as you want and not you for Camaleon)</li> <li>Create your store and start to sell your products using our plugins</li> <li>Avoid web attacks</li> <li>Compare the speed and enjoy the speed of your new Camaleon site</li> <li>Customize or create your themes for mobile support</li> <li>Support more visitors at the same time</li> <li>Manage your information with a panel like wordpress </li> <li>All urls are oriented for SEO</li> <li>Multiples roles of users</li> </ul>"
|
29
29
|
end
|
30
30
|
user = users.admin_scope.first
|
31
31
|
unless user.present?
|
@@ -8,23 +8,23 @@ module CamaleonCms
|
|
8
8
|
return unless ptype.present? # only for posts that belongs to a post type model
|
9
9
|
|
10
10
|
posts = ptype.site.posts
|
11
|
-
.where(
|
12
|
-
|
13
|
-
|
11
|
+
.where(
|
12
|
+
"(#{slug_array.map { |s| "#{CamaleonCms::Post.table_name}.slug LIKE '%-->#{s}<!--%'" }
|
13
|
+
.join(' OR ')} ) OR #{CamaleonCms::Post.table_name}.slug = ?", record.slug
|
14
|
+
)
|
14
15
|
.where.not(id: record.id)
|
15
16
|
.where.not(status: %i[draft draft_child trash])
|
16
|
-
|
17
|
-
record.errors[:base] <<
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
17
|
+
unless posts.empty?
|
18
|
+
record.errors[:base] <<
|
19
|
+
if slug_array.size > 1
|
20
|
+
"#{I18n.t('camaleon_cms.admin.post.message.requires_different_slug')}: #{posts.pluck(:slug).map do |slug|
|
21
|
+
record.slug.to_s.translations.map do |lng, r_slug|
|
22
|
+
"#{r_slug} (#{lng})" if slug.translations_array.include?(r_slug)
|
23
|
+
end.join(',')
|
24
|
+
end.join(',').split(',').uniq.clean_empty.join(', ')} "
|
25
|
+
else
|
26
|
+
"#{I18n.t('camaleon_cms.admin.post.message.requires_different_slug')}: #{record.slug} "
|
27
|
+
end
|
28
28
|
end
|
29
29
|
|
30
30
|
# avoid recursive page parent
|
@@ -3,9 +3,15 @@ module CamaleonCms
|
|
3
3
|
def validate(record)
|
4
4
|
return if record.skip_slug_validation?
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
taxonomy_table = CamaleonCms::TermTaxonomy.table_name
|
7
|
+
slug_exists = CamaleonCms::TermTaxonomy.where(slug: record.slug)
|
8
|
+
.where.not(id: record.id)
|
9
|
+
.where("#{taxonomy_table}.taxonomy" => record.taxonomy)
|
10
|
+
.where("#{taxonomy_table}.parent_id" => record.parent_id).exists?
|
11
|
+
|
12
|
+
return unless slug_exists
|
13
|
+
|
14
|
+
record.errors[:base] << I18n.t('camaleon_cms.admin.post.message.requires_different_slug').to_s
|
9
15
|
end
|
10
16
|
end
|
11
17
|
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2011-present GitLab B.V.
|
4
|
+
#
|
5
|
+
# See https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/url_blocker.rb
|
6
|
+
#
|
7
|
+
# Portions of this software are licensed under the "MIT Expat" license as defined below.
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
10
|
+
# of this software and associated documentation files (the "Software"), to deal
|
11
|
+
# in the Software without restriction, including without limitation the rights
|
12
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
13
|
+
# copies of the Software, and to permit persons to whom the Software is
|
14
|
+
# furnished to do so, subject to the following conditions:
|
15
|
+
#
|
16
|
+
# The above copyright notice and this permission notice shall be included in all
|
17
|
+
# copies or substantial portions of the Software.
|
18
|
+
#
|
19
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
20
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
21
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
22
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
23
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
24
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
25
|
+
# SOFTWARE.
|
26
|
+
|
27
|
+
# require 'resolv'
|
28
|
+
require 'ipaddress'
|
29
|
+
require 'addressable/uri'
|
30
|
+
|
31
|
+
module CamaleonCms
|
32
|
+
class UserUrlValidator
|
33
|
+
LOCAL_IPS = %w[0.0.0.0 ::].freeze
|
34
|
+
|
35
|
+
def self.validate(...)
|
36
|
+
new.validate(...)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@errors = []
|
41
|
+
end
|
42
|
+
|
43
|
+
# Validates the given url according to the constraints specified by the received arguments.
|
44
|
+
#
|
45
|
+
# allow_localhost - Registers error if URL resolves to a localhost IP address and argument is false.
|
46
|
+
# allow_local_network - Registers error if URL resolves to a link-local address and argument is false.
|
47
|
+
# enforce_user - Registers error if URL user doesn't start with alphanumeric characters and argument is true.
|
48
|
+
# enforce_sanitizing - Registers error if URL includes any HTML/CSS/JS tags and argument is true.
|
49
|
+
#
|
50
|
+
# Returns an array with [<uri>, <original-hostname>].
|
51
|
+
def validate(url, allow_localhost: false, allow_local_network: false, enforce_user: true, enforce_sanitizing: true)
|
52
|
+
return invalid_url unless url.present?
|
53
|
+
|
54
|
+
# Param url can be a string, URI or Addressable::URI
|
55
|
+
return invalid_url unless (uri = parse_url(url))
|
56
|
+
|
57
|
+
validate_uri(uri: uri, enforce_sanitizing: enforce_sanitizing, enforce_user: enforce_user)
|
58
|
+
return @errors if @errors.any?
|
59
|
+
|
60
|
+
address_info = get_address_info(uri)
|
61
|
+
return @errors if @errors.any?
|
62
|
+
|
63
|
+
validate_local_request(
|
64
|
+
address_info: address_info,
|
65
|
+
allow_localhost: allow_localhost,
|
66
|
+
allow_local_network: allow_local_network
|
67
|
+
)
|
68
|
+
|
69
|
+
@errors.empty? || @errors
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def validate_uri(uri:, enforce_sanitizing:, enforce_user:)
|
75
|
+
validate_html_tags(uri) if enforce_sanitizing
|
76
|
+
|
77
|
+
validate_user(uri.user) if enforce_user
|
78
|
+
validate_hostname(uri.hostname)
|
79
|
+
end
|
80
|
+
|
81
|
+
# @param uri [Addressable::URI]
|
82
|
+
# @return [Array<Addrinfo>] addrinfo object for the URI
|
83
|
+
def get_address_info(uri)
|
84
|
+
Addrinfo.getaddrinfo(uri.hostname, get_port(uri), nil, :STREAM).map do |addr|
|
85
|
+
addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
|
86
|
+
end
|
87
|
+
rescue ArgumentError => e
|
88
|
+
# Addrinfo.getaddrinfo errors if the domain exceeds 1024 characters.
|
89
|
+
@errors << I18n.t('camaleon_cms.admin.validate.hostname_long') if e.message.include?('hostname too long')
|
90
|
+
|
91
|
+
@errors << "#{e.message}: #{I18n.t('camaleon_cms.admin.validate.url')}" if @errors.blank?
|
92
|
+
rescue SocketError
|
93
|
+
@errors << I18n.t('camaleon_cms.admin.validate.host_invalid')
|
94
|
+
end
|
95
|
+
|
96
|
+
def validate_local_request(address_info:, allow_localhost:, allow_local_network:)
|
97
|
+
return if allow_local_network && allow_localhost
|
98
|
+
|
99
|
+
unless allow_localhost
|
100
|
+
validate_localhost(address_info)
|
101
|
+
validate_loopback(address_info)
|
102
|
+
end
|
103
|
+
|
104
|
+
return if allow_local_network
|
105
|
+
|
106
|
+
validate_local_network(address_info)
|
107
|
+
validate_link_local(address_info)
|
108
|
+
validate_shared_address(address_info)
|
109
|
+
validate_limited_broadcast_address(address_info)
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_port(uri)
|
113
|
+
uri.port || uri.default_port
|
114
|
+
end
|
115
|
+
|
116
|
+
def validate_html_tags(uri)
|
117
|
+
uri_str = uri.to_s
|
118
|
+
sanitized_uri = ActionController::Base.helpers.sanitize(uri_str, tags: [])
|
119
|
+
@errors << I18n.t('camaleon_cms.admin.validate.html_tags') unless sanitized_uri == uri_str
|
120
|
+
end
|
121
|
+
|
122
|
+
# @param [String, Addressable::URI, #to_str] url The URL string to parse
|
123
|
+
# @return [Addressable::URI, nil] URI object based on the parsed string, or `nil` if the `url` is invalid
|
124
|
+
def parse_url(url)
|
125
|
+
invalid = nil
|
126
|
+
uri = Addressable::URI.parse(url).tap do |parsed_url|
|
127
|
+
invalid = true if multiline_blocked?(parsed_url)
|
128
|
+
end
|
129
|
+
return if invalid
|
130
|
+
|
131
|
+
uri
|
132
|
+
rescue Addressable::URI::InvalidURIError, URI::InvalidURIError
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
136
|
+
def multiline_blocked?(parsed_url)
|
137
|
+
url = parsed_url.to_s
|
138
|
+
|
139
|
+
return true if url =~ /[\n\r]/
|
140
|
+
# Google Cloud Storage uses a multi-line, encoded Signature query string
|
141
|
+
return false if %w[http https].include?(parsed_url.scheme&.downcase)
|
142
|
+
|
143
|
+
CGI.unescape(url) =~ /[\n\r]/
|
144
|
+
end
|
145
|
+
|
146
|
+
def validate_user(value)
|
147
|
+
return if value.blank?
|
148
|
+
return if value =~ /\A\p{Alnum}/
|
149
|
+
|
150
|
+
@errors << I18n.t('camaleon_cms.admin.validate.username_alphanumeric')
|
151
|
+
end
|
152
|
+
|
153
|
+
def validate_hostname(value)
|
154
|
+
return if value.blank?
|
155
|
+
return if IPAddress.valid?(value)
|
156
|
+
return if value =~ /\A\p{Alnum}/
|
157
|
+
|
158
|
+
@errors << I18n.t('camaleon_cms.admin.validate.host_or_ip_invalid')
|
159
|
+
end
|
160
|
+
|
161
|
+
def validate_localhost(addrs_info)
|
162
|
+
return if (Socket.ip_address_list.map(&:ip_address).concat(LOCAL_IPS) & addrs_info.map(&:ip_address)).empty?
|
163
|
+
|
164
|
+
@errors << I18n.t('camaleon_cms.admin.validate.no_localhost_requests')
|
165
|
+
end
|
166
|
+
|
167
|
+
def validate_loopback(addrs_info)
|
168
|
+
return unless addrs_info.any? { |addr| addr.ipv4_loopback? || addr.ipv6_loopback? }
|
169
|
+
|
170
|
+
@errors << I18n.t('camaleon_cms.admin.validate.no_loopback_requests')
|
171
|
+
end
|
172
|
+
|
173
|
+
def validate_local_network(addrs_info)
|
174
|
+
return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? || addr.ipv6_unique_local? }
|
175
|
+
|
176
|
+
@errors << I18n.t('camaleon_cms.admin.validate.no_local_net_requests')
|
177
|
+
end
|
178
|
+
|
179
|
+
def validate_link_local(addrs_info)
|
180
|
+
netmask = IPAddr.new('169.254.0.0/16')
|
181
|
+
return unless addrs_info.any? { |addr| addr.ipv6_linklocal? || netmask.include?(addr.ip_address) }
|
182
|
+
|
183
|
+
@errors << I18n.t('camaleon_cms.admin.validate.no_link_local_net_requests')
|
184
|
+
end
|
185
|
+
|
186
|
+
def validate_shared_address(addrs_info)
|
187
|
+
netmask = IPAddr.new('100.64.0.0/10')
|
188
|
+
return unless addrs_info.any? { |addr| netmask.include?(addr.ip_address) }
|
189
|
+
|
190
|
+
@errors << I18n.t('camaleon_cms.admin.validate.no_shared_address_requests')
|
191
|
+
end
|
192
|
+
|
193
|
+
# Registers an error if any IP in `addrs_info` is the limited broadcast address.
|
194
|
+
# https://datatracker.ietf.org/doc/html/rfc919#section-7
|
195
|
+
def validate_limited_broadcast_address(addrs_info)
|
196
|
+
blocked_ips = ['255.255.255.255']
|
197
|
+
|
198
|
+
return if (blocked_ips & addrs_info.map(&:ip_address)).empty?
|
199
|
+
|
200
|
+
@errors << I18n.t('camaleon_cms.admin.validate.no_limited_broadcast_address_requests')
|
201
|
+
end
|
202
|
+
|
203
|
+
def invalid_url
|
204
|
+
@errors << I18n.t('camaleon_cms.admin.validate.url')
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -48,7 +48,7 @@
|
|
48
48
|
<fieldset>
|
49
49
|
<legend><%= t("camaleon_cms.admin.media.external", default: 'From URL') %></legend>
|
50
50
|
<div class="form-group">
|
51
|
-
<input type="text" name="remote_file" class="form-control" placeholder="
|
51
|
+
<input type="text" name="remote_file" class="form-control" placeholder="https://..." class="required">
|
52
52
|
</div>
|
53
53
|
<div class="form-group">
|
54
54
|
<button type="submit" class="btn btn-primary"><%= t("camaleon_cms.admin.button.submit") %></button>
|
@@ -10,7 +10,7 @@
|
|
10
10
|
<hr>
|
11
11
|
<div class="alert alert-info">Gmail Need Permissions:
|
12
12
|
<div class="pull-left">
|
13
|
-
<a href="
|
13
|
+
<a href="https://know.mailsbestfriend.com/smtp_error_password_command_failed_5345714-1194946499.shtml" target="_blank">Check
|
14
14
|
here.</a>
|
15
15
|
</div>
|
16
16
|
<div class="pull-right">
|
@@ -55,4 +55,4 @@
|
|
55
55
|
return false;
|
56
56
|
});
|
57
57
|
});
|
58
|
-
</script>
|
58
|
+
</script>
|
@@ -12,19 +12,19 @@
|
|
12
12
|
<!-- Wrapper for slides -->
|
13
13
|
<div class="carousel-inner">
|
14
14
|
<div class="item active">
|
15
|
-
<img src="
|
15
|
+
<img src="https://placehold.it/800x400" alt="...">
|
16
16
|
<div class="carousel-caption">
|
17
17
|
<h2>Heading</h2>
|
18
18
|
</div>
|
19
19
|
</div>
|
20
20
|
<div class="item">
|
21
|
-
<img src="
|
21
|
+
<img src="https://placehold.it/800x400" alt="...">
|
22
22
|
<div class="carousel-caption">
|
23
23
|
<h2>Heading</h2>
|
24
24
|
</div>
|
25
25
|
</div>
|
26
26
|
<div class="item">
|
27
|
-
<img src="
|
27
|
+
<img src="https://placehold.it/800x400" alt="...">
|
28
28
|
<div class="carousel-caption">
|
29
29
|
<h2>Heading</h2>
|
30
30
|
</div>
|
@@ -67,4 +67,4 @@
|
|
67
67
|
</div>
|
68
68
|
|
69
69
|
</div>
|
70
|
-
</section>
|
70
|
+
</section>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
xml.instruct! :xml, version: '1.0'
|
2
|
-
xml.urlset 'xmlns' => '
|
2
|
+
xml.urlset 'xmlns' => 'https://www.sitemaps.org/schemas/sitemap/0.9' do
|
3
3
|
current_site.get_languages.each_with_index do |lang, index|
|
4
|
-
lang = (index
|
4
|
+
lang = (index == 0 ? nil : lang)
|
5
5
|
xml.url do
|
6
6
|
xml.loc current_site.the_url(locale: lang)
|
7
7
|
xml.lastmod current_site.updated_at.to_date
|
@@ -54,7 +54,7 @@ xml.urlset 'xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9' do
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
-
@r[:custom].
|
57
|
+
@r[:custom].each_value do |item|
|
58
58
|
xml.url do
|
59
59
|
xml.loc item[:url]
|
60
60
|
xml.lastmod item[:lastmod] || Date.today.to_s
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<footer class="main-footer" id="main-footer">
|
2
2
|
<div class="row">
|
3
|
-
<div class="col-md-6"><strong>Copyright © 2015 - <%= Time.now.year %> <a href="
|
3
|
+
<div class="col-md-6"><strong>Copyright © 2015 - <%= Time.now.year %> <a href="https://camaleon.website">Camaleon CMS.</a> </strong></div>
|
4
4
|
<div class="col-md-6 hidden-xs">
|
5
5
|
<div class="pull-left">
|
6
6
|
<a id="link_see_intro" href="#" onclick="init_intro(); return false;"><i class="fa fa-tv"></i> <%= I18n.t('camaleon_cms.see_intro') %>.</a>
|
@@ -9,4 +9,18 @@ Rails.application.config.to_prepare do |_config|
|
|
9
9
|
f = File.join(ap['path'], 'config', 'custom_models.rb')
|
10
10
|
eval(File.read(f)) if File.exist?(f)
|
11
11
|
end
|
12
|
+
|
13
|
+
# This block can be overridden in the app initializer to wrap the sleep and delete_file in an async job,
|
14
|
+
# something like this:
|
15
|
+
#
|
16
|
+
# CamaleonDeleteFileJob.set(wait: temporal_time).perform_later(file_key)
|
17
|
+
# # put this in app/jobs/camaleon_delete_file_job.rb:
|
18
|
+
# include CamaleonCms::UploaderHelper
|
19
|
+
# cama_uploader.delete_file(file_key)
|
20
|
+
CamaleonCmsUploader.delete_block do |settings, cama_uploader, file_key|
|
21
|
+
next unless Rails.env.test?
|
22
|
+
|
23
|
+
sleep(settings[:temporal_time])
|
24
|
+
cama_uploader.delete_file(file_key)
|
25
|
+
end
|
12
26
|
end
|
@@ -212,7 +212,6 @@ en:
|
|
212
212
|
reload: 'Reload'
|
213
213
|
clear_cache: 'Clear Cache'
|
214
214
|
name_required: 'File name is required'
|
215
|
-
local_upload_denied: 'Cannot upload from localhost'
|
216
215
|
menus:
|
217
216
|
menus: Menus
|
218
217
|
link_url: 'Link URL'
|
@@ -684,6 +683,16 @@ en:
|
|
684
683
|
required: 'This field is required.'
|
685
684
|
remote: 'Please fix this field.'
|
686
685
|
email: 'Please enter a valid email address.'
|
686
|
+
host_invalid: 'Host cannot be resolved or invalid'
|
687
|
+
host_or_ip_invalid: 'Hostname or IP address invalid'
|
688
|
+
hostname_long: 'Host is too long (maximum is 1024 characters)'
|
689
|
+
html_tags: 'HTML/CSS/JS tags are not allowed'
|
690
|
+
no_localhost_requests: 'Requests to localhost are not allowed'
|
691
|
+
no_loopback_requests: 'Requests to loopback addresses are not allowed'
|
692
|
+
no_local_net_requests: 'Requests to the local network are not allowed'
|
693
|
+
no_link_local_net_requests: 'Requests to the link local network are not allowed'
|
694
|
+
no_shared_address_requests: 'Requests to the shared address space are not allowed'
|
695
|
+
no_limited_broadcast_address_requests: 'Requests to the limited broadcast address are not allowed'
|
687
696
|
url: 'Please enter a valid URL.'
|
688
697
|
date: 'Please enter a valid date.'
|
689
698
|
dateiso: 'Please enter a valid date ( ISO ).'
|
@@ -697,6 +706,7 @@ en:
|
|
697
706
|
range: 'Please enter a value between {0} and {1}.'
|
698
707
|
max: 'Please enter a value less than or equal to {0}.'
|
699
708
|
min: 'Please enter a value greater than or equal to {0}.'
|
709
|
+
username_alphanumeric: 'Username needs to start with an alphanumeric character'
|
700
710
|
widgets:
|
701
711
|
create_widget: 'Create Widget'
|
702
712
|
create_sidebar: 'Create Sidebar'
|
data/lib/camaleon_cms/engine.rb
CHANGED
@@ -59,7 +59,7 @@ module CamaleonCms
|
|
59
59
|
app.config.assets.paths << File.join($camaleon_engine_dir, 'app', 'assets', 'fonts')
|
60
60
|
app.config.encoding = 'utf-8'
|
61
61
|
|
62
|
-
# add prefix url, like:
|
62
|
+
# add prefix url, like: https://localhost.com/blog/
|
63
63
|
# config.action_controller.relative_url_root = PluginRoutes.system_info["relative_url_root"] if PluginRoutes.system_info["relative_url_root"].present?
|
64
64
|
|
65
65
|
# multiple route files
|
data/lib/camaleon_cms/version.rb
CHANGED
data/lib/ext/hash.rb
CHANGED
data/lib/ext/string.rb
CHANGED
@@ -66,11 +66,11 @@ class String
|
|
66
66
|
end
|
67
67
|
|
68
68
|
# parse string into domain
|
69
|
-
#
|
69
|
+
# https://owen.camaleon.website into owen.camaleon.website
|
70
70
|
def parse_domain
|
71
71
|
url = self
|
72
72
|
uri = URI.parse(url)
|
73
|
-
uri = URI.parse("
|
73
|
+
uri = URI.parse("https://#{url}") if uri.scheme.nil?
|
74
74
|
host = (uri.host || self).downcase
|
75
75
|
h = host.start_with?('www.') ? host[4..] : host
|
76
76
|
"#{h}#{":#{uri.port}" unless [80, 443].include?(uri.port)}"
|