Commit 98328affa2f83378a51e61d8b9826d35b896644f

Authored by Leandro Santos
2 parents 13e6bd4a c8eaed18
Exists in staging and in 1 other branch production

Merge branch 'master' into staging

.travis.yml
... ... @@ -51,21 +51,36 @@ before_script:
51 51 - bundle exec rake db:migrate &>/dev/null
52 52  
53 53 env:
54   - - TASK=test:api
55   - - TASK=test:units
56   - - TASK=test:functionals
57   - - TASK=test:integration
58   - - SLICE=1/2 TASK=cucumber LANG=en
59   - - SLICE=2/2 TASK=cucumber LANG=en
60   - - SLICE=1/4 TASK=selenium
61   - - SLICE=2/4 TASK=selenium
62   - - SLICE=3/4 TASK=selenium
63   - - SLICE=4/4 TASK=selenium
64   - - SLICE=1/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
65   - - SLICE=2/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
66   - - SLICE=3/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
67   - - SLICE=4/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
68   - - SLICE=5/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  54 + global:
  55 + - LANG=en
  56 + matrix:
  57 + - TASK=test:api
  58 + - TASK=test:units
  59 + - TASK=test:functionals
  60 + - TASK=test:integration
  61 + - SLICE=1/2 TASK=cucumber
  62 + - SLICE=2/2 TASK=cucumber
  63 + - SLICE=1/4 TASK=selenium
  64 + - SLICE=2/4 TASK=selenium
  65 + - SLICE=3/4 TASK=selenium
  66 + - SLICE=4/4 TASK=selenium
  67 + - SLICE=1/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  68 + - SLICE=2/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  69 + - SLICE=3/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  70 + - SLICE=4/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  71 + - SLICE=5/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  72 + # chrome hanging on travis
  73 + #- SLICE=1/4 TASK=selenium SELENIUM_DRIVER=chrome
  74 + #- SLICE=2/4 TASK=selenium SELENIUM_DRIVER=chrome
  75 + #- SLICE=3/4 TASK=selenium SELENIUM_DRIVER=chrome
  76 + #- SLICE=4/4 TASK=selenium SELENIUM_DRIVER=chrome
  77 +
  78 +matrix:
  79 + allow_failures:
  80 + - env: SLICE=1/4 TASK=selenium SELENIUM_DRIVER=chrome
  81 + - env: SLICE=2/4 TASK=selenium SELENIUM_DRIVER=chrome
  82 + - env: SLICE=3/4 TASK=selenium SELENIUM_DRIVER=chrome
  83 + - env: SLICE=4/4 TASK=selenium SELENIUM_DRIVER=chrome
69 84  
70 85 script:
71 86 - ./script/ci
... ...
Gemfile
... ... @@ -85,6 +85,7 @@ group :cucumber do
85 85 gem 'cucumber-rails', '~> 1.4.2', :require => false
86 86 gem 'database_cleaner', '~> 1.3'
87 87 gem 'selenium-webdriver', '>= 2.50'
  88 + gem 'chromedriver-helper' if ENV['SELENIUM_DRIVER'] == 'chrome'
88 89 end
89 90  
90 91 # Requires custom dependencies
... ...
app/helpers/application_helper.rb
... ... @@ -1057,13 +1057,11 @@ module ApplicationHelper
1057 1057 end
1058 1058  
1059 1059 def delete_article_message(article)
1060   - CGI.escapeHTML(
1061   - if article.folder?
1062   - _("Are you sure that you want to remove the folder \"%s\"? Note that all the items inside it will also be removed!") % article.name
1063   - else
1064   - _("Are you sure that you want to remove the item \"%s\"?") % article.name
1065   - end
1066   - )
  1060 + if article.folder?
  1061 + _("Are you sure that you want to remove the folder \"%s\"? Note that all the items inside it will also be removed!") % article.name
  1062 + else
  1063 + _("Are you sure that you want to remove the item \"%s\"?") % article.name
  1064 + end
1067 1065 end
1068 1066  
1069 1067 def expirable_link_to(expired, content, url, options = {})
... ...
app/helpers/article_helper.rb
... ... @@ -70,14 +70,14 @@ module ArticleHelper
70 70 content_tag('div',
71 71 content_tag('div',
72 72 radio_button(:article, :published, true) +
73   - content_tag('span', ' ', :class => 'access-public-icon') +
  73 + content_tag('span', ' '.html_safe, :class => 'access-public-icon') +
74 74 content_tag('label', _('Public'), :for => 'article_published_true') +
75 75 content_tag('span', _('Visible to other people'), :class => 'access-note'),
76 76 :class => 'access-item'
77 77 ) +
78 78 content_tag('div',
79 79 radio_button(:article, :published, false) +
80   - content_tag('span', ' ', :class => 'access-private-icon') +
  80 + content_tag('span', ' '.html_safe, :class => 'access-private-icon') +
81 81 content_tag('label', _('Private'), :for => 'article_published_false', :id => "label_private") +
82 82 content_tag('span', _('Limit visibility of this article'), :class => 'access-note'),
83 83 :class => 'access-item'
... ...
app/helpers/boxes_helper.rb
... ... @@ -34,7 +34,7 @@ module BoxesHelper
34 34  
35 35 def display_boxes_editor(holder)
36 36 with_box_decorator self do
37   - content_tag('div', display_boxes(holder, '<' + _('Main content') + '>'), :id => 'box-organizer')
  37 + content_tag('div', display_boxes(holder, '<' + _('Main content') + '>'), :id => 'box-organizer')
38 38 end
39 39 end
40 40  
... ... @@ -52,7 +52,7 @@ module BoxesHelper
52 52  
53 53 def maybe_display_custom_element(holder, element, options = {})
54 54 if holder.respond_to?(element)
55   - content_tag('div', holder.send(element), options)
  55 + content_tag('div', holder.send(element).to_s.html_safe, options)
56 56 else
57 57 ''.html_safe
58 58 end
... ... @@ -64,7 +64,7 @@ module BoxesHelper
64 64  
65 65 def display_updated_box(box)
66 66 with_box_decorator self do
67   - display_box_content(box, '&lt;' + _('Main content') + '&gt;')
  67 + display_box_content(box, '<' + _('Main content') + '>')
68 68 end
69 69 end
70 70  
... ...
app/helpers/categories_helper.rb
... ... @@ -20,7 +20,7 @@ module CategoriesHelper
20 20 def selected_category_link(cat)
21 21 js_remove = "jQuery('#selected-category-#{cat.id}').remove();"
22 22 content_tag('div', button_to_function_without_text(:remove, _('Remove'), js_remove) +
23   - link_to_function(cat.full_name(' &rarr; '), js_remove, :id => "remove-selected-category-#{cat.id}-button", :class => 'select-subcategory-link'),
  23 + link_to_function(cat.full_name(' &rarr; ').html_safe, js_remove, :id => "remove-selected-category-#{cat.id}-button", :class => 'select-subcategory-link'),
24 24 :class => 'selected-category'
25 25 )
26 26 end
... ...
app/helpers/profile_helper.rb
... ... @@ -84,7 +84,7 @@ module ProfileHelper
84 84 entries.map do |entry|
85 85 content = self.send("treat_#{field}", entry)
86 86 unless content.blank?
87   - content_tag('tr', content_tag('td', title(field, entry), :class => 'field-name') + content_tag('td', content))
  87 + content_tag('tr', content_tag('td', title(field, entry), :class => 'field-name') + content_tag('td', content.to_s.html_safe))
88 88 end
89 89 end.join("\n")
90 90 end
... ...
app/views/blocks/raw_html.html.erb
1 1 <%= block_title(block.title, block.subtitle) %>
2 2  
3   -<%=h block.html %>
  3 +<%= block.html.html_safe %>
... ...
app/views/friends/_profile_list.html.erb
1 1 <ul class="profile-list">
2 2 <% profiles.each do |profile| %>
3 3 <li>
4   - <%= link_to_profile profile_image(profile) + '<br/>' + profile.short_name,
  4 + <%= link_to_profile profile_image(profile) + tag('br') + profile.short_name,
5 5 profile.identifier, :class => 'profile-link' %>
6 6 <div class="controll">
7 7 <%= button_without_text :remove, content_tag('span',_('remove')),
... ...
app/views/shared/_select_categories.html.erb
... ... @@ -9,7 +9,7 @@
9 9 categories = [@current_category]
10 10 categories.push(@current_category) while @current_category = @current_category.parent
11 11 %>
12   - <%= categories.compact.reverse.map{|i| update_categories_link(nil,i.name, i.id)}.join %>
  12 + <%= categories.compact.reverse.map{|i| update_categories_link(nil,i.name, i.id)}.join.html_safe %>
13 13  
14 14 <script>
15 15 function add_category() {
... ...
config/initializers/html_safe.rb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +##
  2 +# Object based copy of http://apidock.com/rails/ActionView/Helpers/OutputSafetyHelper/safe_join
  3 +# array.safe_join instead of safe_join(array)
  4 +#
  5 +class Array
  6 + def safe_join sep=nil
  7 + sep = ERB::Util.unwrapped_html_escape sep
  8 +
  9 + self.flatten.map!{ |i| ERB::Util.unwrapped_html_escape i }.join(sep).html_safe
  10 + end
  11 +end
  12 +
  13 +##
  14 +# Just use .to_json instead of .to_json.html_safe
  15 +# as escape_html_entities_in_json is default on rails.
  16 +# http://stackoverflow.com/a/31774454/670229
  17 +#
  18 +ActiveSupport::JSON::Encoding.escape_html_entities_in_json = true
  19 +ActiveSupport::JSON.class_eval do
  20 + module EncodeWithHtmlSafe
  21 + def encode *args
  22 + super.html_safe
  23 + end
  24 + end
  25 + singleton_class.prepend EncodeWithHtmlSafe
  26 +end
... ...
features/support/selenium.rb
... ... @@ -2,9 +2,14 @@ require &#39;selenium/webdriver&#39;
2 2  
3 3 Capybara.default_driver = :selenium
4 4 Capybara.register_driver :selenium do |app|
5   - profile = Selenium::WebDriver::Firefox::Profile.new
6   - profile.native_events = true
7   - Capybara::Selenium::Driver.new app, browser: :firefox, profile: profile
  5 + case ENV['SELENIUM_DRIVER']
  6 + when 'chrome'
  7 + Capybara::Selenium::Driver.new app, browser: :chrome
  8 + else
  9 + profile = Selenium::WebDriver::Firefox::Profile.new
  10 + profile.native_events = true
  11 + Capybara::Selenium::Driver.new app, browser: :firefox, profile: profile
  12 + end
8 13 end
9 14  
10 15 Before('@ignore-hidden-elements') do
... ...
plugins/delivery/lib/delivery_plugin/display_helper.rb
... ... @@ -15,7 +15,7 @@ module DeliveryPlugin::DisplayHelper
15 15 content_tag :option, text, value: method.id,
16 16 data: {label: method.name, type: method.delivery_type, instructions: CGI::escapeHTML(method.description.to_s)},
17 17 selected: if method.id == selected then 'selected' else nil end
18   - end.join
  18 + end.safe_join
19 19 end
20 20  
21 21 def consumer_delivery_field_value order, field
... ...
plugins/people_block/lib/people_block_helper.rb
1 1 module PeopleBlockHelper
2 2 def profiles_images_list(profiles)
3   - profiles.map { |profile| profile_image_link(profile, :minor) }.join("\n")
  3 + profiles.map { |profile| profile_image_link(profile, :minor) }.join("\n").html_safe
4 4 end
5 5  
6 6 def set_address_protocol(address)
... ...
plugins/shopping_cart/features/delivery_client.feature
... ... @@ -64,10 +64,15 @@ Feature: delivery client
64 64 Scenario: gets free delivery due to free over price
65 65 Given I follow "Add to basket"
66 66 And I follow "Add to basket"
  67 + And I wait 0.2 seconds to finish the request
67 68 And I follow "Add to basket"
  69 + And I wait 0.2 seconds to finish the request
68 70 And I follow "Add to basket"
  71 + And I wait 0.2 seconds to finish the request
69 72 And I follow "Add to basket"
  73 + And I wait 0.2 seconds to finish the request
70 74 And I follow "Add to basket"
  75 + And I wait 0.2 seconds to finish the request
71 76 And I should see "Show basket"
72 77 And I follow "Show basket"
73 78 And I wait 1 second for animations
... ...
test/api/people_test.rb
... ... @@ -68,7 +68,7 @@ class PeopleTest &lt; ActiveSupport::TestCase
68 68 end
69 69  
70 70 should 'people endpoint filter by fields parameter with hierarchy' do
71   - fields = URI.encode({only: [:name, {user: [:login]}]}.to_json)
  71 + fields = URI.encode({only: [:name, {user: [:login]}]}.to_json.to_str)
72 72 get "/api/v1/people?#{params.to_query}&fields=#{fields}"
73 73 json = JSON.parse(last_response.body)
74 74 expected = {'people' => [{'name' => person.name, 'user' => {'login' => 'testapi'}}]}
... ...
test/integration/safe_strings_test.rb 0 → 100644
... ... @@ -0,0 +1,87 @@
  1 +require_relative "../test_helper"
  2 +
  3 +class SafeStringsTest < ActionDispatch::IntegrationTest
  4 +
  5 + should 'not escape link to admins on profile page' do
  6 + person = fast_create Person
  7 + community = fast_create Community
  8 + community.add_admin(person)
  9 + get "/profile/#{community.identifier}"
  10 + assert_tag :tag => 'td', :content => 'Admins', :sibling => {
  11 + :tag => 'td', :child => { :tag => 'a', :content => person.name }
  12 + }
  13 + end
  14 +
  15 + should 'not escape people names on members block' do
  16 + person = fast_create Person
  17 + community = fast_create Community
  18 + community.add_member(person)
  19 + community.boxes << Box.new
  20 + community.boxes.first.blocks << MembersBlock.new
  21 + get "/profile/#{community.identifier}"
  22 + assert_tag :tag => 'div', :attributes => { :id => "block-#{community.blocks.first.id}" }, :descendant => {
  23 + :tag => 'li', :attributes => { :class => 'vcard' }, :content => person.name
  24 + }
  25 + end
  26 +
  27 + should 'not escape RawHTMLBlock content' do
  28 + community = fast_create Community
  29 + community.boxes << Box.new
  30 + community.boxes.first.blocks << RawHTMLBlock.new(:html => '<b>bold</b>')
  31 + get "/profile/#{community.identifier}"
  32 + assert_tag :tag => 'div', :attributes => { :id => "block-#{community.blocks.first.id}" }, :descendant => {
  33 + :tag => 'b', :content => 'bold'
  34 + }
  35 + end
  36 +
  37 + should 'not escape profile header or footer' do
  38 + community = fast_create Community
  39 + community.update_header_and_footer('<b>header</b>', '<b>footer</b>')
  40 + get "/profile/#{community.identifier}"
  41 + assert_tag :tag => 'div', :attributes => { :id => 'profile-header' }, :child => { :tag => 'b', :content => 'header' }
  42 + assert_tag :tag => 'div', :attributes => { :id => 'profile-footer' }, :child => { :tag => 'b', :content => 'footer' }
  43 + end
  44 +
  45 + should 'not escape &rarr; symbol from categories' do
  46 + create_user('marley', :password => 'test', :password_confirmation => 'test').activate
  47 + category = fast_create Category
  48 + subcategory = fast_create(Category, :parent_id => category.id)
  49 + Person['marley'].categories << subcategory
  50 + login 'marley', 'test'
  51 + get "/myprofile/marley/profile_editor/edit"
  52 + assert_tag :tag => 'a', :attributes => { :id => "remove-selected-category-#{subcategory.id}-button" },
  53 + :content => "#{category.name} &rarr; #{subcategory.name}"
  54 + end
  55 +
  56 + should 'not escape MainBlock on profile design' do
  57 + create_user('jimi', :password => 'test', :password_confirmation => 'test').activate
  58 + jimi = Person['jimi']
  59 + jimi.boxes << Box.new
  60 + jimi.boxes.first.blocks << MainBlock.new
  61 + login 'jimi', 'test'
  62 + get "/myprofile/jimi/profile_design"
  63 + assert_tag :tag => 'div', :attributes => { :class => 'main-content' }, :content => '&lt;Main content&gt;'
  64 + end
  65 +
  66 + should 'not escape confirmation message on deleting folders' do
  67 + create_user('jimi', :password => 'test', :password_confirmation => 'test').activate
  68 + fast_create(Folder, :name => 'Hey Joe', :profile_id => Person['jimi'].id, :updated_at => DateTime.now)
  69 + login 'jimi', 'test'
  70 + get "/myprofile/jimi/cms"
  71 + assert_tag :tag => 'a', :attributes => {
  72 + 'data-confirm' => /Are you sure that you want to remove the folder &quot;Hey Joe&quot;\?/
  73 + }
  74 + end
  75 +
  76 + should 'not escape people names on manage friends' do
  77 + create_user('jimi', :password => 'test', :password_confirmation => 'test').activate
  78 + friend = fast_create Person
  79 + Person['jimi'].add_friend(friend)
  80 + login 'jimi', 'test'
  81 + get '/myprofile/jimi/friends'
  82 + assert_tag :tag => 'div', :attributes => { :id => 'manage_friends' }, :descendant => {
  83 + :tag => 'a', :attributes => { :class => 'profile-link' }, :content => friend.name
  84 + }
  85 + end
  86 +
  87 +end
... ...
test/unit/cms_helper_test.rb
... ... @@ -62,7 +62,7 @@ class CmsHelperTest &lt; ActionView::TestCase
62 62 profile = fast_create(Profile)
63 63 name = 'My folder'
64 64 folder = fast_create(Folder, :name => name, :profile_id => profile.id)
65   - confirm_message = CGI.escapeHTML("Are you sure that you want to remove the folder \"#{name}\"? Note that all the items inside it will also be removed!")
  65 + confirm_message = "Are you sure that you want to remove the folder \"#{name}\"? Note that all the items inside it will also be removed!"
66 66 expects(:link_to).with('Delete', {action: 'destroy', id: folder.id}, method: :post, 'data-confirm' => confirm_message, class: 'button with-text icon-delete', title: nil)
67 67  
68 68 result = display_delete_button(folder)
... ... @@ -73,7 +73,7 @@ class CmsHelperTest &lt; ActionView::TestCase
73 73 profile = fast_create(Profile)
74 74 name = 'My article'
75 75 article = fast_create(TinyMceArticle, :name => name, :profile_id => profile.id)
76   - confirm_message = CGI.escapeHTML("Are you sure that you want to remove the item \"#{name}\"?")
  76 + confirm_message = "Are you sure that you want to remove the item \"#{name}\"?"
77 77 expects(:link_to).with('Delete', {action: 'destroy', id: article.id}, method: :post, 'data-confirm' => confirm_message, class: 'button with-text icon-delete', title: nil)
78 78  
79 79 result = display_delete_button(article)
... ...