# frozen_string_literal: true describe StaticController do fab!(:upload) { Fabricate(:upload) } context '#favicon' do let(:filename) { 'smallest.png' } let(:file) { file_from_fixtures(filename) } let(:upload) do UploadCreator.new(file, filename).create_for(Discourse.system_user.id) end before_all do DistributedMemoizer.flush! end after do DistributedMemoizer.flush! end describe 'local store' do it 'returns the default favicon if favicon has not been configured' do get '/favicon/proxied' expect(response.status).to eq(200) expect(response.media_type).to eq('image/png') expect(response.body.bytesize).to eq(SiteIconManager.favicon.filesize) end it 'returns the configured favicon' do SiteSetting.favicon = upload get '/favicon/proxied' expect(response.status).to eq(200) expect(response.media_type).to eq('image/png') expect(response.body.bytesize).to eq(upload.filesize) end end describe 'external store' do let(:upload) do Upload.create!( url: '//s3-upload-bucket.s3-us-east-1.amazonaws.com/somewhere/a.png', original_filename: filename, filesize: file.size, user_id: Discourse.system_user.id ) end before do setup_s3 end it 'can proxy a favicon correctly' do SiteSetting.favicon = upload stub_request(:get, "https:/#{upload.url}") .to_return(status: 200, body: file) get '/favicon/proxied' expect(response.status).to eq(200) expect(response.media_type).to eq('image/png') expect(response.body.bytesize).to eq(upload.filesize) end end end context '#brotli_asset' do it 'returns a non brotli encoded 404 if asset is missing' do get "/brotli_asset/missing.js" expect(response.status).to eq(404) expect(response.headers['Content-Encoding']).not_to eq('br') expect(response.headers['Cache-Control']).to match(/max-age=1/) end it 'can handle fallback brotli assets' do begin assets_path = Rails.root.join("tmp/backup_assets") GlobalSetting.stubs(:fallback_assets_path).returns(assets_path.to_s) FileUtils.mkdir_p(assets_path) file_path = assets_path.join("test.js.br") File.write(file_path, 'fake brotli file') get "/brotli_asset/test.js" expect(response.status).to eq(200) expect(response.headers["Cache-Control"]).to match(/public/) ensure File.delete(file_path) end end it 'has correct headers for brotli assets' do begin assets_path = Rails.root.join("public/assets") FileUtils.mkdir_p(assets_path) file_path = assets_path.join("test.js.br") File.write(file_path, 'fake brotli file') get "/brotli_asset/test.js" expect(response.status).to eq(200) expect(response.headers["Cache-Control"]).to match(/public/) ensure File.delete(file_path) end end it 'has correct cors headers for brotli assets' do begin assets_path = Rails.root.join("public/assets") FileUtils.mkdir_p(assets_path) file_path = assets_path.join("test.js.br") File.write(file_path, 'fake brotli file') GlobalSetting.stubs(:cdn_url).returns("https://www.example.com/") get "/brotli_asset/test.js" expect(response.status).to eq(200) expect(response.headers["Access-Control-Allow-Origin"]).to match("*") ensure File.delete(file_path) end end end context '#cdn_asset' do let (:site) { RailsMultisite::ConnectionManagement.current_db } it 'can serve assets' do begin assets_path = Rails.root.join("public/assets") FileUtils.mkdir_p(assets_path) file_path = assets_path.join("test.js.br") File.write(file_path, 'fake brotli file') get "/cdn_asset/#{site}/test.js.br" expect(response.status).to eq(200) expect(response.headers["Cache-Control"]).to match(/public/) ensure File.delete(file_path) end end end context '#show' do before do post = create_post SiteSetting.tos_topic_id = post.topic.id SiteSetting.guidelines_topic_id = post.topic.id SiteSetting.privacy_topic_id = post.topic.id end context "with a static file that's present" do it "should return the right response for /faq" do get "/faq" expect(response.status).to eq(200) expect(response.body).to include(I18n.t('js.faq')) expect(response.body).to include("FAQ - Discourse") end end [ ['tos', :tos_url, I18n.t('js.tos')], ['privacy', :privacy_policy_url, I18n.t('js.privacy')] ].each do |id, setting_name, text| context "#{id}" do context "when #{setting_name} site setting is NOT set" do it "renders the #{id} page" do get "/#{id}" expect(response.status).to eq(200) expect(response.body).to include(text) end end context "when #{setting_name} site setting is set" do before do SiteSetting.set(setting_name, 'http://example.com/page') end it "redirects to the #{setting_name}" do get "/#{id}" expect(response).to redirect_to('http://example.com/page') end end end end context "with a missing file" do it "should respond 404" do get "/static/does-not-exist" expect(response.status).to eq(404) end context "modal pages" do it "should return the right response for /signup" do get "/signup" expect(response.status).to eq(200) end it "should return the right response for /password-reset" do get "/password-reset" expect(response.status).to eq(200) end end end it 'should redirect to / when logged in and path is /login' do sign_in(Fabricate(:user)) get "/login" expect(response).to redirect_to('/') end it "should display the login template when login is required" do SiteSetting.login_required = true get "/login" expect(response.status).to eq(200) expect(response.body).to include(PrettyText.cook(I18n.t( 'login_required.welcome_message', title: SiteSetting.title ))) end context "when login_required is enabled" do before do SiteSetting.login_required = true end ['faq', 'guidelines', 'rules', 'conduct'].each do |page_name| it "#{page_name} page redirects to login page for anon" do get "/#{page_name}" expect(response).to redirect_to '/login' end it "#{page_name} page redirects to login page for anon" do get "/#{page_name}" expect(response).to redirect_to '/login' end it "#{page_name} page loads for logged in user" do sign_in(Fabricate(:user)) get "/#{page_name}" expect(response.status).to eq(200) expect(response.body).to include(I18n.t('js.guidelines')) end end end context "crawler view" do it "should include correct title" do get '/faq', headers: { 'HTTP_USER_AGENT' => 'Googlebot' } expect(response.status).to eq(200) expect(response.body).to include("FAQ - Discourse") end end context "plugin api extensions" do after do Rails.application.reload_routes! StaticController::CUSTOM_PAGES.clear end it "adds new topic-backed pages" do routes = Proc.new do get "contact" => "static#show", id: "contact" end Discourse::Application.routes.send(:eval_block, routes) topic_id = Fabricate(:post, cooked: "contact info").topic_id SiteSetting.setting(:test_contact_topic_id, topic_id) Plugin::Instance.new.add_topic_static_page("contact", topic_id: "test_contact_topic_id") get "/contact" expect(response.status).to eq(200) expect(response.body).to include("contact info") end it "replaces existing topic-backed pages" do topic_id = Fabricate(:post, cooked: "Regular FAQ").topic_id SiteSetting.setting(:test_faq_topic_id, topic_id) polish_topic_id = Fabricate(:post, cooked: "Polish FAQ").topic_id SiteSetting.setting(:test_polish_faq_topic_id, polish_topic_id) Plugin::Instance.new.add_topic_static_page("faq") do current_user&.locale == "pl" ? "test_polish_faq_topic_id" : "test_faq_topic_id" end get "/faq" expect(response.status).to eq(200) expect(response.body).to include("Regular FAQ") sign_in(Fabricate(:user, locale: "pl")) get "/faq" expect(response.status).to eq(200) expect(response.body).to include("Polish FAQ") end end it "does not pollute SiteSetting.title (regression)" do SiteSetting.title = "test" SiteSetting.short_site_description = "something" expect do get "/login" get "/login" end.to_not change { SiteSetting.title } end end describe '#enter' do context 'without a redirect path' do it 'redirects to the root url' do post "/login.json" expect(response).to redirect_to('/') end end context 'with a redirect path' do it 'redirects to the redirect path' do post "/login.json", params: { redirect: '/foo' } expect(response).to redirect_to('/foo') end end context 'with a full url' do it 'redirects to the correct path' do post "/login.json", params: { redirect: "#{Discourse.base_url}/foo" } expect(response).to redirect_to('/foo') end end context 'with a redirect path with query params' do it 'redirects to the redirect path and preserves query params' do post "/login.json", params: { redirect: '/foo?bar=1' } expect(response).to redirect_to('/foo?bar=1') end end context 'with a period to force a new host' do it 'redirects to the root path' do post "/login.json", params: { redirect: ".org/foo" } expect(response).to redirect_to('/') end end context 'with a full url to someone else' do it 'redirects to the root path' do post "/login.json", params: { redirect: "http://eviltrout.com/foo" } expect(response).to redirect_to('/') end end context 'with an invalid URL' do it "redirects to the root" do post "/login.json", params: { redirect: "javascript:alert('trout')" } expect(response).to redirect_to('/') end end context 'with an array' do it "redirects to the root" do post "/login.json", params: { redirect: ["/foo"] } expect(response.status).to eq(400) json = response.parsed_body expect(json["errors"]).to be_present expect(json["errors"]).to include( I18n.t("invalid_params", message: "redirect") ) end end context 'when the redirect path is the login page' do it 'redirects to the root url' do post "/login.json", params: { redirect: login_path } expect(response).to redirect_to('/') end end end describe "#service_worker_asset" do it "works" do get "/service-worker.js" expect(response.status).to eq(200) expect(response.content_type).to start_with("application/javascript") expect(response.body).to include("workbox") end it "replaces sourcemap URL" do Rails.application.assets_manifest.stubs(:find_sources).with("service-worker.js").returns([ <<~JS someFakeServiceWorkerSource(); //# sourceMappingURL=service-worker-abcde.js.map JS ]) get "/service-worker.js" expect(response.status).to eq(200) expect(response.content_type).to start_with("application/javascript") expect(response.body).to include("sourceMappingURL=/assets/service-worker-abcde.js.map") end end end