Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/hexdocs/bucket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ defmodule Hexdocs.Bucket do
for version <- versions do
map = %{
version: "v#{version}",
url: Hexdocs.Utils.hexdocs_url(repository, "/#{package}/#{version}")
url: Hexdocs.Utils.hexdocs_url(repository, package, "/#{version}")
}

map = if latest_version == version, do: Map.put(map, :latest, true), else: map
Expand Down
2 changes: 1 addition & 1 deletion lib/hexdocs/package_sitemap.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule Hexdocs.PackageSitemap do
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<%= for page <- pages do %>
<url>
<loc><%= Hexdocs.Utils.hexdocs_url("hexpm", "/#{package_name}/#{page}") %></loc>
<loc><%= Hexdocs.Utils.hexdocs_apex_url("/#{package_name}/#{page}") %></loc>
<lastmod><%= format_datetime updated_at %></lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
Expand Down
19 changes: 0 additions & 19 deletions lib/hexdocs/plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ defmodule Hexdocs.Plug do
:error ->
send_resp(conn, 400, "")

{:redirect, subdomain} ->
redirect_to_private_host(conn, subdomain)

{:ok, subdomain} ->
cond do
# OAuth callback - exchange code for tokens
Expand All @@ -90,20 +87,6 @@ defmodule Hexdocs.Plug do
end
end

defp redirect_to_private_host(conn, subdomain) do
scheme = Application.get_env(:hexdocs, :scheme)
host = Application.get_env(:hexdocs, :private_host)
url = "#{scheme}://#{subdomain}.#{host}#{conn.request_path}"

html = Plug.HTML.html_escape(url)
body = "<html><body>You are being <a href=\"#{html}\">redirected</a>.</body></html>"

conn
|> put_resp_header("location", url)
|> put_resp_header("content-type", "text/html")
|> send_resp(301, body)
end

defp redirect_oauth(conn, organization) do
code_verifier = Hexdocs.OAuth.generate_code_verifier()
code_challenge = Hexdocs.OAuth.generate_code_challenge(code_verifier)
Expand Down Expand Up @@ -285,12 +268,10 @@ defmodule Hexdocs.Plug do
end

defp subdomain(host) do
public_host = Application.get_env(:hexdocs, :host)
private_host = Application.get_env(:hexdocs, :private_host)

case String.split(host, ".", parts: 2) do
[subdomain, ^private_host] -> {:ok, subdomain}
[subdomain, ^public_host] -> {:redirect, subdomain}
_ -> :error
end
end
Expand Down
20 changes: 17 additions & 3 deletions lib/hexdocs/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,34 @@ defmodule Hexdocs.Utils do

@special_package_names Map.keys(Application.compile_env!(:hexdocs, :special_packages))

def hexdocs_url(repository, path) do
def hexdocs_url(repository, package, path) do
"/" <> _ = path

if repository == "hexpm" do
host = Application.get_env(:hexdocs, :host)
scheme = if host == "hexdocs.pm", do: "https", else: "http"
URI.encode("#{scheme}://#{host}#{path}")
URI.encode("#{scheme}://#{package_to_subdomain(package)}.#{host}#{path}")
else
host = Application.get_env(:hexdocs, :private_host)
scheme = if host in ["hexdocs.pm", "hexorgs.pm"], do: "https", else: "http"
URI.encode("#{scheme}://#{repository}.#{host}#{path}")
URI.encode("#{scheme}://#{repository}.#{host}/#{package}#{path}")
end
end

# Hex package names allow underscores (`^[a-z][a-z0-9_]*$`), but RFC 1123
# hostname labels and RFC 6125 wildcard SAN matching don't, and Fastly
# enforces strict SAN matching at the HTTP edge. Map `_` -> `-` for the
# public hexdocs.pm subdomain. The mapping is reversed in the Fastly
# Compute subdomain handler before the GCS bucket key is built.
def package_to_subdomain(name), do: String.replace(name, "_", "-")

def hexdocs_apex_url(path) do
"/" <> _ = path
host = Application.get_env(:hexdocs, :host)
scheme = if host == "hexdocs.pm", do: "https", else: "http"
URI.encode("#{scheme}://#{host}#{path}")
end

def latest_version(versions) do
Enum.find(versions, &(&1.pre == [])) || List.first(versions)
end
Expand Down
21 changes: 10 additions & 11 deletions test/hexdocs/plug_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -309,26 +309,16 @@ defmodule Hexdocs.PlugTest do
end
end

describe "redirect from public host to private host" do
describe "host handling" do
setup do
original_host = Application.get_env(:hexdocs, :host)
original_private_host = Application.get_env(:hexdocs, :private_host)
Application.put_env(:hexdocs, :host, "hexdocs.test")
Application.put_env(:hexdocs, :private_host, "hexorgs.test")

on_exit(fn ->
Application.put_env(:hexdocs, :host, original_host)
Application.put_env(:hexdocs, :private_host, original_private_host)
end)
end

test "301 redirects from *.hexdocs.test to *.hexorgs.test" do
conn = conn(:get, "http://myorg.hexdocs.test:5002/my_package/index.html") |> call()
assert conn.status == 301
[location] = get_resp_header(conn, "location")
assert location == "http://myorg.hexorgs.test/my_package/index.html"
end

test "serves docs on private host" do
conn = conn(:get, "http://myorg.hexorgs.test:5002/foo") |> call()
assert conn.status == 302
Expand All @@ -341,6 +331,15 @@ defmodule Hexdocs.PlugTest do
conn = conn(:get, "http://other.example.com:5002/foo") |> call()
assert conn.status == 400
end

test "returns 400 for *.hexdocs.pm hosts (handled by Fastly)" do
original_host = Application.get_env(:hexdocs, :host)
Application.put_env(:hexdocs, :host, "hexdocs.test")
on_exit(fn -> Application.put_env(:hexdocs, :host, original_host) end)

conn = conn(:get, "http://phoenix.hexdocs.test:5002/index.html") |> call()
assert conn.status == 400
end
end

test "sets security headers" do
Expand Down
6 changes: 4 additions & 2 deletions test/hexdocs/queue_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,16 @@ defmodule Hexdocs.QueueTest do
["var versionNodes = " <> versions_json, "var searchNodes = " <> search_json] =
String.split(docs_config, [";", "\n"], trim: true)

subdomain = URI.encode(String.replace(Atom.to_string(test), "_", "-"))

assert JSON.decode!(versions_json) == [
%{
"url" => "http://localhost/#{URI.encode(Atom.to_string(test))}/3.0.0",
"url" => "http://#{subdomain}.localhost/3.0.0",
"version" => "v3.0.0",
"latest" => true
},
%{
"url" => "http://localhost/#{URI.encode(Atom.to_string(test))}/1.0.0",
"url" => "http://#{subdomain}.localhost/1.0.0",
"version" => "v1.0.0",
"retired" => true
}
Expand Down