diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js index e12c8243d10..4e2c45d639e 100644 --- a/app/assets/javascripts/discourse.js +++ b/app/assets/javascripts/discourse.js @@ -57,7 +57,11 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, { faviconChanged: function() { if(Discourse.User.currentProp('dynamic_favicon')) { - new Favcount(Discourse.SiteSettings.favicon_url).set( + var url = Discourse.SiteSettings.favicon_url; + if (/^http/.test(url)) { + url = Discourse.getURL("/favicon/proxied?" + encodeURIComponent(url)); + } + new Favcount(url).set( this.get('notifyCount') ); } diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb index b65f52b0854..507c22f6f96 100644 --- a/app/controllers/static_controller.rb +++ b/app/controllers/static_controller.rb @@ -1,7 +1,10 @@ +require_dependency 'distributed_memoizer' +require_dependency 'file_helper' + class StaticController < ApplicationController skip_before_filter :check_xhr, :redirect_to_login_if_required - skip_before_filter :verify_authenticity_token, only: [:enter] + skip_before_filter :verify_authenticity_token, only: [:cdn_asset, :enter, :favicon] def show return redirect_to(path '/') if current_user && params[:id] == 'login' @@ -82,7 +85,28 @@ class StaticController < ApplicationController redirect_to destination end - skip_before_filter :verify_authenticity_token, only: [:cdn_asset] + # We need to be able to draw our favicon on a canvas + # and pull it off the canvas into a data uri + # This can work by ensuring people set all the right CORS + # settings in the CDN asset, BUT its annoying and error prone + # instead we cache the favicon in redis and serve it out real quick with + # a huge expiry, we also cache these assets in nginx so it bypassed if needed + def favicon + + data = DistributedMemoizer.memoize('favicon' + SiteSetting.favicon_url, 60*60*24) do + file = FileHelper.download(SiteSetting.favicon_url, 50.kilobytes, "favicon.png") + data = file.read + file.unlink + data + end + + expires_in 1.year, public: true + response.headers["Expires"] = 1.year.from_now.httpdate + response.headers["Content-Length"] = data.bytesize.to_s + response.headers["Last-Modified"] = Time.new('2000-01-01').httpdate + render text: data, content_type: "image/png" + end + def cdn_asset path = File.expand_path(Rails.root + "public/assets/" + params[:path]) diff --git a/config/initializers/06-mini_profiler.rb b/config/initializers/06-mini_profiler.rb index 2b0eedb68b9..3726cb173b9 100644 --- a/config/initializers/06-mini_profiler.rb +++ b/config/initializers/06-mini_profiler.rb @@ -37,7 +37,8 @@ if defined?(Rack::MiniProfiler) /^\/uploads/, /^\/javascripts\//, /^\/images\//, - /^\/stylesheets\// + /^\/stylesheets\//, + /^\/favicon\/proxied/ ] # For our app, let's just show mini profiler always, polling is chatty so nuke that diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 31391bf511f..57f4a7ef4f3 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -807,7 +807,7 @@ en: logo_url: "The logo image at the top left of your site, should be a wide rectangle shape. If left blank site title text will be shown." digest_logo_url: "The alternate logo image used at the top of your site's email digest. Should be a wide rectangle shape. If left blank `logo_url` will be used." logo_small_url: "The small logo image at the top left of your site, should be a square shape, seen when scrolling down. If left blank a home glyph will be shown." - favicon_url: "A favicon for your site, see http://en.wikipedia.org/wiki/Favicon" + favicon_url: "A favicon for your site, see http://en.wikipedia.org/wiki/Favicon, to work correctly over a CDN it must be a png" mobile_logo_url: "The fixed position logo image used at the top left of your mobile site. Should be a square shape. If left blank, `logo_url` will be used. eg: http://example.com/uploads/default/logo.png" apple_touch_icon_url: "Icon used for Apple touch devices. Recommended size is 144px by 144px." diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index 021e6ea3dae..cd103e493ce 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -173,7 +173,7 @@ server { # This big block is needed so we can selectively enable # acceleration for backups and avatars # see note about repetition above - location ~ ^/(letter_avatar|user_avatar|highlight-js|stylesheets) { + location ~ ^/(letter_avatar|user_avatar|highlight-js|stylesheets|favicon/proxied) { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/config/routes.rb b/config/routes.rb index c68223269f3..67bfa0f5514 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -520,6 +520,8 @@ Discourse::Application.routes.draw do get "cdn_asset/:site/*path" => "static#cdn_asset", format: false + get "favicon/proxied" => "static#favicon", format: false + get "robots.txt" => "robots_txt#index" Discourse.filters.each do |filter|