Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85831695
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
12 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex
index c230cea46..ca9f5368d 100644
--- a/lib/pleroma/http.ex
+++ b/lib/pleroma/http.ex
@@ -1,138 +1,144 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP do
@moduledoc """
Wrapper for `Tesla.request/2`.
"""
alias Pleroma.HTTP.AdapterHelper
alias Pleroma.HTTP.Request
alias Pleroma.HTTP.RequestBuilder, as: Builder
alias Tesla.Client
alias Tesla.Env
require Logger
@type t :: __MODULE__
@type method() :: :get | :post | :put | :delete | :head
@doc """
Performs GET request.
See `Pleroma.HTTP.request/5`
"""
@spec get(Request.url() | nil, Request.headers(), keyword()) ::
nil | {:ok, Env.t()} | {:error, any()}
def get(url, headers \\ [], options \\ [])
def get(nil, _, _), do: nil
def get(url, headers, options), do: request(:get, url, "", headers, options)
@spec head(Request.url(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()}
def head(url, headers \\ [], options \\ []), do: request(:head, url, "", headers, options)
@doc """
Performs POST request.
See `Pleroma.HTTP.request/5`
"""
@spec post(Request.url(), Tesla.Env.body(), Request.headers(), keyword()) ::
{:ok, Env.t()} | {:error, any()}
def post(url, body, headers \\ [], options \\ []),
do: request(:post, url, body, headers, options)
@doc """
Builds and performs http request.
# Arguments:
`method` - :get, :post, :put, :delete, :head
`url` - full url
`body` - request body
`headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]`
`options` - custom, per-request middleware or adapter options
# Returns:
`{:ok, %Tesla.Env{}}` or `{:error, error}`
"""
@spec request(method(), Request.url(), Tesla.Env.body(), Request.headers(), keyword()) ::
{:ok, Env.t()} | {:error, any()}
def request(method, url, body, headers, options) when is_binary(url) do
uri = URI.parse(url)
adapter_opts = AdapterHelper.options(uri, options || [])
options = put_in(options[:adapter], adapter_opts)
params = options[:params] || []
request = build_request(method, headers, options, url, body, params)
adapter = Application.get_env(:tesla, :adapter)
extra_middleware = options[:tesla_middleware] || []
client = Tesla.client(adapter_middlewares(adapter, extra_middleware), adapter)
maybe_limit(
fn ->
request(client, request)
end,
adapter,
adapter_opts
)
+ |> drop_partial_body()
end
+ # Hackney 4.9 returns the bytes read before the peer closed. No caller wants
+ # them, and callers that cache errors would keep them.
+ defp drop_partial_body({:error, {:closed, body}}) when is_binary(body), do: {:error, :closed}
+ defp drop_partial_body(result), do: result
+
@spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()}
def request(client, request), do: Tesla.request(client, request)
defp build_request(method, headers, options, url, body, params) do
Builder.new()
|> Builder.method(method)
|> Builder.headers(headers)
|> Builder.opts(options)
|> Builder.url(url)
|> Builder.add_param(:body, :body, body)
|> Builder.add_param(:query, :query, params)
|> Builder.convert_to_keyword()
end
@prefix Pleroma.Gun.ConnectionPool
defp maybe_limit(fun, Tesla.Adapter.Gun, opts) do
ConcurrentLimiter.limit(:"#{@prefix}.#{opts[:pool] || :default}", fun)
end
defp maybe_limit(fun, _, _) do
fun.()
end
defp adapter_middlewares(Tesla.Adapter.Gun, extra_middleware) do
default_middleware() ++
[Pleroma.Tesla.Middleware.ConnectionPool] ++
extra_middleware
end
defp adapter_middlewares(_, extra_middleware) do
# A lot of tests are written expecting unencoded URLs
# and the burden of fixing that is high. Also it makes
# them hard to read. Tests will opt-in when we want to validate
# the encoding is being done correctly.
cond do
Pleroma.Config.get(:env) == :test and Pleroma.Config.get(:test_url_encoding) ->
default_middleware()
Pleroma.Config.get(:env) == :test ->
# Emulate redirects in test env, which are handled by adapters in other environments
[Tesla.Middleware.FollowRedirects]
# Hackney and Finch
true ->
default_middleware() ++ extra_middleware
end
end
defp default_middleware,
do: [
Tesla.Middleware.FollowRedirects,
Pleroma.Tesla.Middleware.EncodeUrl,
Pleroma.Tesla.Middleware.MaxBody
]
end
diff --git a/test/pleroma/http_test.exs b/test/pleroma/http_test.exs
index e673e6591..436a22447 100644
--- a/test/pleroma/http_test.exs
+++ b/test/pleroma/http_test.exs
@@ -1,217 +1,227 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTPTest do
use ExUnit.Case, async: false
use Pleroma.Tests.Helpers
import Tesla.Mock
alias Pleroma.HTTP
alias Pleroma.Utils.URIEncoding
setup do
mock(fn
%{
method: :get,
url: "http://example.com/hello",
headers: [{"content-type", "application/json"}]
} ->
json(%{"my" => "data"})
%{method: :head, url: "http://example.com/hello"} ->
%Tesla.Env{status: 200, body: ""}
%{method: :get, url: "http://example.com/hello"} ->
%Tesla.Env{status: 200, body: "hello"}
%{method: :post, url: "http://example.com/world"} ->
%Tesla.Env{status: 200, body: "world"}
+ # Hackney 4.9 hands back the bytes it read before the peer closed.
+ %{method: :get, url: "http://example.com/truncated"} ->
+ {:error, {:closed, "partial body"}}
+
%{method: :get, url: "https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz"} ->
%Tesla.Env{status: 200, body: "emoji data"}
%{
method: :get,
url: "https://example.com/media/foo/bar%20!$&'()*+,;=/:%20@a%20%5Bbaz%5D.mp4"
} ->
%Tesla.Env{status: 200, body: "video data"}
%{method: :get, url: "https://example.com/media/unicode%20%F0%9F%99%82%20.gif"} ->
%Tesla.Env{status: 200, body: "unicode data"}
%{
method: :get,
url:
"https://i.guim.co.uk/img/media/1069ef13c447908272c4de94174cec2b6352cb2f/0_91_2000_1201/master/2000.jpg?width=1200&height=630&quality=85&auto=format&fit=crop&precrop=40:21,offset-x50,offset-y0&overlay-align=bottom%2Cleft&overlay-width=100p&overlay-base64=L2ltZy9zdGF0aWMvb3ZlcmxheXMvdGctb3BpbmlvbnMtYWdlLTIwMTkucG5n&enable=upscale&s=cba21427a73512fdc9863c486c03fdd8"
} ->
%Tesla.Env{status: 200, body: "Guardian image quirk"}
%{
method: :get,
url:
"https://i.guim.co.uk/emoji/Pack%201/koronebless.png?precrop=40:21,overlay-x0,overlay-y0&foo=bar+baz"
} ->
%Tesla.Env{status: 200, body: "Space in query with Guardian quirk"}
%{
method: :get,
url:
"https://examplebucket.s3.amazonaws.com/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=accessKEY%2F20130721%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-Signature=SIGNATURE&X-Amz-SignedHeaders=host"
} ->
%Tesla.Env{status: 200, body: "AWS S3 data"}
end)
:ok
end
describe "head/1" do
test "returns successfully result" do
assert HTTP.head("http://example.com/hello") == {:ok, %Tesla.Env{status: 200, body: ""}}
end
end
describe "get/1" do
test "returns successfully result" do
assert HTTP.get("http://example.com/hello") == {
:ok,
%Tesla.Env{status: 200, body: "hello"}
}
end
end
+ describe "get/1 with a truncated body" do
+ test "drops the partial body from the error" do
+ assert HTTP.get("http://example.com/truncated") == {:error, :closed}
+ end
+ end
+
describe "get/2 (with headers)" do
test "returns successfully result for json content-type" do
assert HTTP.get("http://example.com/hello", [{"content-type", "application/json"}]) ==
{
:ok,
%Tesla.Env{
status: 200,
body: "{\"my\":\"data\"}",
headers: [{"content-type", "application/json"}]
}
}
end
end
describe "post/2" do
test "returns successfully result" do
assert HTTP.post("http://example.com/world", "") == {
:ok,
%Tesla.Env{status: 200, body: "world"}
}
end
end
test "URL encoding properly encodes URLs with spaces" do
clear_config(:test_url_encoding, true)
url_with_space = "https://example.com/emoji/Pack 1/koronebless.png?foo=bar baz"
{:ok, result} = HTTP.get(url_with_space)
assert result.status == 200
properly_encoded_url = "https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz"
{:ok, result} = HTTP.get(properly_encoded_url)
assert result.status == 200
url_with_reserved_chars = "https://example.com/media/foo/bar !$&'()*+,;=/: @a [baz].mp4"
{:ok, result} = HTTP.get(url_with_reserved_chars)
assert result.status == 200
url_with_unicode = "https://example.com/media/unicode 🙂 .gif"
{:ok, result} = HTTP.get(url_with_unicode)
assert result.status == 200
end
test "decodes URL first by default" do
clear_config(:test_url_encoding, true)
normal_url = "https://example.com/media/file%20with%20space.jpg?name=a+space.jpg"
result = URIEncoding.encode_url(normal_url)
assert result == "https://example.com/media/file%20with%20space.jpg?name=a+space.jpg"
end
test "doesn't decode URL first when specified" do
clear_config(:test_url_encoding, true)
normal_url = "https://example.com/media/file%20with%20space.jpg"
result = URIEncoding.encode_url(normal_url, bypass_decode: true)
assert result == "https://example.com/media/file%2520with%2520space.jpg"
end
test "properly applies Guardian image query quirk" do
clear_config(:test_url_encoding, true)
url =
"https://i.guim.co.uk/img/media/1069ef13c447908272c4de94174cec2b6352cb2f/0_91_2000_1201/master/2000.jpg?width=1200&height=630&quality=85&auto=format&fit=crop&precrop=40:21,offset-x50,offset-y0&overlay-align=bottom%2Cleft&overlay-width=100p&overlay-base64=L2ltZy9zdGF0aWMvb3ZlcmxheXMvdGctb3BpbmlvbnMtYWdlLTIwMTkucG5n&enable=upscale&s=cba21427a73512fdc9863c486c03fdd8"
result = URIEncoding.encode_url(url)
assert result == url
{:ok, result_get} = HTTP.get(result)
assert result_get.status == 200
end
test "properly encodes spaces as \"pluses\" in query when using quirks" do
clear_config(:test_url_encoding, true)
url =
"https://i.guim.co.uk/emoji/Pack 1/koronebless.png?precrop=40:21,overlay-x0,overlay-y0&foo=bar baz"
properly_encoded_url =
"https://i.guim.co.uk/emoji/Pack%201/koronebless.png?precrop=40:21,overlay-x0,overlay-y0&foo=bar+baz"
result = URIEncoding.encode_url(url)
assert result == properly_encoded_url
{:ok, result_get} = HTTP.get(result)
assert result_get.status == 200
end
test "properly encode AWS S3 queries" do
clear_config(:test_url_encoding, true)
url =
"https://examplebucket.s3.amazonaws.com/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=accessKEY%2F20130721%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-Signature=SIGNATURE&X-Amz-SignedHeaders=host"
unencoded_url =
"https://examplebucket.s3.amazonaws.com/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=accessKEY/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-Signature=SIGNATURE&X-Amz-SignedHeaders=host"
result = URIEncoding.encode_url(url)
result_unencoded = URIEncoding.encode_url(unencoded_url)
assert result == url
assert result == result_unencoded
{:ok, result_get} = HTTP.get(result)
assert result_get.status == 200
end
test "preserves query key order" do
clear_config(:test_url_encoding, true)
url = "https://example.com/foo?hjkl=qwertz&xyz=abc&bar=baz"
result = URIEncoding.encode_url(url)
assert result == url
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Nov 1, 8:13 PM (13 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1803306
Default Alt Text
(12 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline