From aee8e62e216fea49ed345d4a64317fafdac4e1c4 Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Thu, 14 May 2020 14:17:19 -0400 Subject: [PATCH] FEATURE: Add endpoint for individual SVG icons (#9765) --- app/controllers/svg_sprite_controller.rb | 29 +++++++++++++++++++-- config/routes.rb | 1 + spec/requests/svg_sprite_controller_spec.rb | 28 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/app/controllers/svg_sprite_controller.rb b/app/controllers/svg_sprite_controller.rb index c4cd07e23e2..1a7aad9f06c 100644 --- a/app/controllers/svg_sprite_controller.rb +++ b/app/controllers/svg_sprite_controller.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true class SvgSpriteController < ApplicationController - skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :search] + skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :search, :svg_icon] - requires_login except: [:show] + requires_login except: [:show, :svg_icon] def show @@ -49,4 +49,29 @@ class SvgSpriteController < ApplicationController render json: icons.take(200), root: false end end + + def svg_icon + no_cookies + + RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do + params.permit(:color) + name = params.require(:name) + icon = SvgSprite.search(name) + + if icon.blank? + render body: nil, status: 404 + else + doc = Nokogiri.XML(icon) + doc.at_xpath("symbol").name = "svg" + doc.at_xpath("svg")['xmlns'] = "http://www.w3.org/2000/svg" + doc.at_xpath("svg")['fill'] = "##{params[:color]}" if params[:color] + + response.headers["Last-Modified"] = 1.years.ago.httpdate + response.headers["Content-Length"] = doc.to_s.bytesize.to_s + immutable_for 1.day + + render plain: doc, disposition: nil, content_type: 'image/svg+xml' + end + end + end end diff --git a/config/routes.rb b/config/routes.rb index 10199d1dff8..353af09a49e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -503,6 +503,7 @@ Discourse::Application.routes.draw do get "svg-sprite/:hostname/svg-:theme_ids-:version.js" => "svg_sprite#show", constraints: { hostname: /[\w\.-]+/, version: /\h{40}/, theme_ids: /([0-9]+(,[0-9]+)*)?/, format: :js } get "svg-sprite/search/:keyword" => "svg_sprite#search", format: false, constraints: { keyword: /[-a-z0-9\s\%]+/ } get "svg-sprite/picker-search" => "svg_sprite#icon_picker_search", defaults: { format: :json } + get "svg-sprite/:hostname/icon(/:color)/:name.svg" => "svg_sprite#svg_icon", constraints: { hostname: /[\w\.-]+/, name: /[-a-z0-9\s\%]+/, color: /(\h{6})/, format: :svg } get "highlight-js/:hostname/:version.js" => "highlight_js#show", constraints: { hostname: /[\w\.-]+/, format: :js } diff --git a/spec/requests/svg_sprite_controller_spec.rb b/spec/requests/svg_sprite_controller_spec.rb index cdfdab766a6..540235552a2 100644 --- a/spec/requests/svg_sprite_controller_spec.rb +++ b/spec/requests/svg_sprite_controller_spec.rb @@ -93,4 +93,32 @@ describe SvgSpriteController do expect(data[0]["id"]).to eq("fab-500px") end end + + context 'svg_icon' do + it "requires .svg extension" do + get "/svg-sprite/#{Discourse.current_hostname}/icon/bolt" + expect(response.status).to eq(404) + end + + it "returns SVG given an icon name" do + get "/svg-sprite/#{Discourse.current_hostname}/icon/bolt.svg" + expect(response.status).to eq(200) + expect(response.body).to include('bolt') + end + + it "returns SVG given an icon name and a color" do + get "/svg-sprite/#{Discourse.current_hostname}/icon/CC0000/fab-github.svg" + expect(response.status).to eq(200) + + expect(response.body).to include('fab-github') + expect(response.body).to include('fill="#CC0000"') + expect(response.headers["Cache-Control"]).to eq("max-age=86400, public, immutable") + end + + it "ignores non-HEX colors" do + get "/svg-sprite/#{Discourse.current_hostname}/icon/orange/fab-github.svg" + expect(response.status).to eq(404) + end + + end end