diff --git a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js index e44465f8a28..d6fe3c93c28 100644 --- a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js +++ b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js @@ -8,6 +8,8 @@ const path = require("path"); const { promises: fs } = require("fs"); const { JSDOM } = require("jsdom"); const { shouldLoadPluginTestJs } = require("discourse/lib/plugin-js"); +const { Buffer } = require("node:buffer"); +const { cwd, env } = require("node:process"); // via https://stackoverflow.com/a/6248722/165668 function generateUID() { @@ -202,7 +204,7 @@ async function applyBootstrap(bootstrap, template, response, baseURL, preload) { async function buildFromBootstrap(proxy, baseURL, req, response, preload) { try { const template = await fs.readFile( - path.join(process.cwd(), "dist", "index.html"), + path.join(cwd(), "dist", "index.html"), "utf8" ); @@ -221,8 +223,23 @@ async function buildFromBootstrap(proxy, baseURL, req, response, preload) { } async function handleRequest(proxy, baseURL, req, res) { - const originalHost = req.headers["x-forwarded-host"] || req.headers.host; - req.headers.host = new URL(proxy).host; + // x-forwarded-host is used in e.g. GitHub CodeSpaces + let originalHost = req.headers["x-forwarded-host"] || req.headers.host; + + if (env["FORWARD_HOST"] === "true") { + if (/^localhost(\:|$)/.test(originalHost)) { + // Can't access default site in multisite via "localhost", redirect to 127.0.0.1 + res.redirect( + 307, + `http://${originalHost.replace("localhost", "127.0.0.1")}${req.path}` + ); + return; + } else { + req.headers.host = originalHost; + } + } else { + req.headers.host = new URL(proxy).host; + } if (req.headers["Origin"]) { req.headers["Origin"] = req.headers["Origin"] @@ -270,34 +287,39 @@ async function handleRequest(proxy, baseURL, req, res) { `http://${originalHost}/assets/`, `http://${originalHost}/ember-cli-live-reload.js`, `http://${originalHost}/_lr/`, - ]; + ].join(" "); + const newCSP = csp - .replace(new RegExp(proxy, "g"), `http://${originalHost}`) - .replace( - new RegExp("script-src ", "g"), - `script-src ${emberCliAdditions.join(" ")} ` - ); + .replaceAll(proxy, `http://${originalHost}`) + .replaceAll("script-src ", `script-src ${emberCliAdditions}`); + res.set("content-security-policy", newCSP); } const contentType = response.headers.get("content-type"); - const isHTML = contentType && contentType.startsWith("text/html"); - const responseText = await response.text(); - const preloadJson = isHTML ? extractPreloadJson(responseText) : null; + const isHTML = contentType?.startsWith("text/html"); - if (preloadJson) { - const html = await buildFromBootstrap( - proxy, - baseURL, - req, - response, - extractPreloadJson(responseText) - ); - res.set("content-type", "text/html"); - res.send(html); + res.status(response.status); + + if (isHTML) { + const responseText = await response.text(); + const preloadJson = isHTML ? extractPreloadJson(responseText) : null; + + if (preloadJson) { + const html = await buildFromBootstrap( + proxy, + baseURL, + req, + response, + extractPreloadJson(responseText) + ); + res.set("content-type", "text/html"); + res.send(html); + } else { + res.send(responseText); + } } else { - res.status(response.status); - res.send(responseText); + res.send(Buffer.from(await response.arrayBuffer())); } } @@ -308,7 +330,7 @@ module.exports = { return true; }, - contentFor: function (type, config) { + contentFor(type, config) { if (shouldLoadPluginTestJs() && type === "test-plugin-js") { return ` @@ -339,8 +361,11 @@ to serve API requests. For example: app.use(rawMiddleware, async (req, res, next) => { try { - if (this.shouldHandleRequest(req)) { + if (this.shouldForwardRequest(req)) { await handleRequest(proxy, baseURL, req, res); + } else { + // Fixes issues when using e.g. "localhost" instead of loopback IP address + req.headers.host = "127.0.0.1"; } } catch (error) { res.send(` @@ -357,28 +382,19 @@ to serve API requests. For example: }); }, - shouldHandleRequest(request) { - if (request.path === "/tests/index.html") { - return false; - } - - if (request.get("Accept") && request.get("Accept").includes("text/html")) { - return true; - } - - const contentType = request.get("Content-Type"); - if (!contentType) { - return false; - } - + shouldForwardRequest(request) { if ( - contentType.includes("application/x-www-form-urlencoded") || - contentType.includes("multipart/form-data") || - contentType.includes("application/json") + ["/tests/index.html", "/ember-cli-live-reload.js", "/testem.js"].includes( + request.path + ) ) { - return true; + return false; } - return false; + if (request.path.startsWith("/_lr/")) { + return false; + } + + return true; }, }; diff --git a/bin/ember-cli b/bin/ember-cli index 64d4d66561e..55f771aa281 100755 --- a/bin/ember-cli +++ b/bin/ember-cli @@ -6,8 +6,8 @@ require 'pathname' RAILS_ROOT = File.expand_path("../../", Pathname.new(__FILE__).realpath) PORT = ENV["UNICORN_PORT"] ||= "3000" HOSTNAME = ENV["DISCOURSE_HOSTNAME"] ||= "127.0.0.1" -yarn_dir = File.join(RAILS_ROOT, "app/assets/javascripts/discourse") - +YARN_DIR = File.join(RAILS_ROOT, "app/assets/javascripts/discourse") +CUSTOM_ARGS = ["--try", "--test", "--unicorn", "-u", "--forward-host"] PROXY = if ARGV.include?("--try") "https://try.discourse.org" @@ -38,19 +38,22 @@ if ARGV.include?("-h") || ARGV.include?("--help") puts "#{"--test".cyan} To run the test suite" puts "#{"--unicorn, -u".cyan} To run a unicorn server as well" puts "The rest of the arguments are passed to ember server per:", "" - exec "yarn -s --cwd #{yarn_dir} run ember #{command} --help" + exec "yarn -s --cwd #{YARN_DIR} run ember #{command} --help" end -args = ["-s", "--cwd", yarn_dir, "run", "ember", command] + ARGV.reject do |a| - ["--try", "--test", "--unicorn", "-u"].include?(a) -end +args = ["-s", "--cwd", YARN_DIR, "run", "ember", command] + (ARGV - CUSTOM_ARGS) if !args.include?("test") && !args.include?("--proxy") args << "--proxy" args << PROXY end -exit 1 if !system "yarn -s install --cwd #{yarn_dir}" +exit 1 if !system "yarn -s install --cwd #{YARN_DIR}" + +yarn_env = {} +if ARGV.include?("--forward-host") + yarn_env["FORWARD_HOST"] = "true" +end if ARGV.include?("-u") || ARGV.include?("--unicorn") unicorn_env = { "DISCOURSE_PORT" => ENV["DISCOURSE_PORT"] || "4200" } @@ -58,7 +61,7 @@ if ARGV.include?("-u") || ARGV.include?("--unicorn") Thread.new do require 'open3' - Open3.popen2e("yarn", *args.to_a.flatten) do |i, oe, t| + Open3.popen2e(yarn_env, "yarn", *args.to_a.flatten) do |i, oe, t| puts "Ember CLI running on PID: #{t.pid}" oe.each do |line| if line.include?("\e[32m200\e") || line.include?("\e[36m304\e[0m") || line.include?("POST /message-bus") @@ -77,5 +80,5 @@ if ARGV.include?("-u") || ARGV.include?("--unicorn") Process.wait(unicorn_pid) else - exec "yarn", *args.to_a.flatten + exec(yarn_env, "yarn", *args.to_a.flatten) end