# Conflicts: # lib/pleroma/application.ex # lib/pleroma/plugs/oauth_plug.exstable
commit
4944498133
@ -0,0 +1,97 @@
|
||||
defmodule Mix.Tasks.MigrateLocalUploads do
|
||||
use Mix.Task
|
||||
import Mix.Ecto
|
||||
alias Pleroma.{Upload, Uploaders.Local, Uploaders.S3}
|
||||
require Logger
|
||||
|
||||
@log_every 50
|
||||
@shortdoc "Migrate uploads from local to remote storage"
|
||||
|
||||
def run([target_uploader | args]) do
|
||||
delete? = Enum.member?(args, "--delete")
|
||||
Application.ensure_all_started(:pleroma)
|
||||
|
||||
local_path = Pleroma.Config.get!([Local, :uploads])
|
||||
uploader = Module.concat(Pleroma.Uploaders, target_uploader)
|
||||
|
||||
unless Code.ensure_loaded?(uploader) do
|
||||
raise("The uploader #{inspect(uploader)} is not an existing/loaded module.")
|
||||
end
|
||||
|
||||
target_enabled? = Pleroma.Config.get([Upload, :uploader]) == uploader
|
||||
|
||||
unless target_enabled? do
|
||||
Pleroma.Config.put([Upload, :uploader], uploader)
|
||||
end
|
||||
|
||||
Logger.info("Migrating files from local #{local_path} to #{to_string(uploader)}")
|
||||
|
||||
if delete? do
|
||||
Logger.warn(
|
||||
"Attention: uploaded files will be deleted, hope you have backups! (--delete ; cancel with ^C)"
|
||||
)
|
||||
|
||||
:timer.sleep(:timer.seconds(5))
|
||||
end
|
||||
|
||||
uploads =
|
||||
File.ls!(local_path)
|
||||
|> Enum.map(fn id ->
|
||||
root_path = Path.join(local_path, id)
|
||||
|
||||
cond do
|
||||
File.dir?(root_path) ->
|
||||
files = for file <- File.ls!(root_path), do: {id, file, Path.join([root_path, file])}
|
||||
|
||||
case List.first(files) do
|
||||
{id, file, path} ->
|
||||
{%Pleroma.Upload{id: id, name: file, path: id <> "/" <> file, tempfile: path},
|
||||
root_path}
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
||||
File.exists?(root_path) ->
|
||||
file = Path.basename(id)
|
||||
[hash, ext] = String.split(id, ".")
|
||||
{%Pleroma.Upload{id: hash, name: file, path: file, tempfile: root_path}, root_path}
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
total_count = length(uploads)
|
||||
Logger.info("Found #{total_count} uploads")
|
||||
|
||||
uploads
|
||||
|> Task.async_stream(
|
||||
fn {upload, root_path} ->
|
||||
case Upload.store(upload, uploader: uploader, filters: [], size_limit: nil) do
|
||||
{:ok, _} ->
|
||||
if delete?, do: File.rm_rf!(root_path)
|
||||
Logger.debug("uploaded: #{inspect(upload.path)} #{inspect(upload)}")
|
||||
:ok
|
||||
|
||||
error ->
|
||||
Logger.error("failed to upload #{inspect(upload.path)}: #{inspect(error)}")
|
||||
end
|
||||
end,
|
||||
timeout: 150_000
|
||||
)
|
||||
|> Stream.chunk_every(@log_every)
|
||||
|> Enum.reduce(0, fn done, count ->
|
||||
count = count + length(done)
|
||||
Logger.info("Uploaded #{count}/#{total_count} files")
|
||||
count
|
||||
end)
|
||||
|
||||
Logger.info("Done!")
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
Logger.error("Usage: migrate_local_uploads S3|Swift [--delete]")
|
||||
end
|
||||
end
|
@ -0,0 +1,27 @@
|
||||
defmodule Pleroma.HTTP.Connection do
|
||||
@moduledoc """
|
||||
Connection for http-requests.
|
||||
"""
|
||||
|
||||
@hackney_options [pool: :default]
|
||||
@adapter Application.get_env(:tesla, :adapter)
|
||||
|
||||
@doc """
|
||||
Configure a client connection
|
||||
|
||||
# Returns
|
||||
|
||||
Tesla.Env.client
|
||||
"""
|
||||
@spec new(Keyword.t()) :: Tesla.Env.client()
|
||||
def new(opts \\ []) do
|
||||
Tesla.client([], {@adapter, hackney_options(opts)})
|
||||
end
|
||||
|
||||
# fetch Hackney options
|
||||
#
|
||||
defp hackney_options(opts \\ []) do
|
||||
options = Keyword.get(opts, :adapter, [])
|
||||
@hackney_options ++ options
|
||||
end
|
||||
end
|
@ -0,0 +1,126 @@
|
||||
defmodule Pleroma.HTTP.RequestBuilder do
|
||||
@moduledoc """
|
||||
Helper functions for building Tesla requests
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Specify the request method when building a request
|
||||
|
||||
## Parameters
|
||||
|
||||
- request (Map) - Collected request options
|
||||
- m (atom) - Request method
|
||||
|
||||
## Returns
|
||||
|
||||
Map
|
||||
"""
|
||||
@spec method(map(), atom) :: map()
|
||||
def method(request, m) do
|
||||
Map.put_new(request, :method, m)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Specify the request method when building a request
|
||||
|
||||
## Parameters
|
||||
|
||||
- request (Map) - Collected request options
|
||||
- u (String) - Request URL
|
||||
|
||||
## Returns
|
||||
|
||||
Map
|
||||
"""
|
||||
@spec url(map(), String.t()) :: map()
|
||||
def url(request, u) do
|
||||
Map.put_new(request, :url, u)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Add headers to the request
|
||||
"""
|
||||
@spec headers(map(), list(tuple)) :: map()
|
||||
def headers(request, h) do
|
||||
Map.put_new(request, :headers, h)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Add custom, per-request middleware or adapter options to the request
|
||||
"""
|
||||
@spec opts(map(), Keyword.t()) :: map()
|
||||
def opts(request, options) do
|
||||
Map.put_new(request, :opts, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Add optional parameters to the request
|
||||
|
||||
## Parameters
|
||||
|
||||
- request (Map) - Collected request options
|
||||
- definitions (Map) - Map of parameter name to parameter location.
|
||||
- options (KeywordList) - The provided optional parameters
|
||||
|
||||
## Returns
|
||||
|
||||
Map
|
||||
"""
|
||||
@spec add_optional_params(map(), %{optional(atom) => atom}, keyword()) :: map()
|
||||
def add_optional_params(request, _, []), do: request
|
||||
|
||||
def add_optional_params(request, definitions, [{key, value} | tail]) do
|
||||
case definitions do
|
||||
%{^key => location} ->
|
||||
request
|
||||
|> add_param(location, key, value)
|
||||
|> add_optional_params(definitions, tail)
|
||||
|
||||
_ ->
|
||||
add_optional_params(request, definitions, tail)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Add optional parameters to the request
|
||||
|
||||
## Parameters
|
||||
|
||||
- request (Map) - Collected request options
|
||||
- location (atom) - Where to put the parameter
|
||||
- key (atom) - The name of the parameter
|
||||
- value (any) - The value of the parameter
|
||||
|
||||
## Returns
|
||||
|
||||
Map
|
||||
"""
|
||||
@spec add_param(map(), atom, atom, any()) :: map()
|
||||
def add_param(request, :body, :body, value), do: Map.put(request, :body, value)
|
||||
|
||||
def add_param(request, :body, key, value) do
|
||||
request
|
||||
|> Map.put_new_lazy(:body, &Tesla.Multipart.new/0)
|
||||
|> Map.update!(
|
||||
:body,
|
||||
&Tesla.Multipart.add_field(&1, key, Poison.encode!(value),
|
||||
headers: [{:"Content-Type", "application/json"}]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def add_param(request, :file, name, path) do
|
||||
request
|
||||
|> Map.put_new_lazy(:body, &Tesla.Multipart.new/0)
|
||||
|> Map.update!(:body, &Tesla.Multipart.add_file(&1, path, name: name))
|
||||
end
|
||||
|
||||
def add_param(request, :form, name, value) do
|
||||
request
|
||||
|> Map.update(:body, %{name => value}, &Map.put(&1, name, value))
|
||||
end
|
||||
|
||||
def add_param(request, location, key, value) do
|
||||
Map.update(request, location, [{key, value}], &(&1 ++ [{key, value}]))
|
||||
end
|
||||
end
|
@ -0,0 +1,108 @@
|
||||
defmodule Pleroma.MIME do
|
||||
@moduledoc """
|
||||
Returns the mime-type of a binary and optionally a normalized file-name.
|
||||
"""
|
||||
@default "application/octet-stream"
|
||||
@read_bytes 31
|
||||
|
||||
@spec file_mime_type(String.t()) ::
|
||||
{:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error
|
||||
def file_mime_type(path, filename) do
|
||||
with {:ok, content_type} <- file_mime_type(path),
|
||||
filename <- fix_extension(filename, content_type) do
|
||||
{:ok, content_type, filename}
|
||||
end
|
||||
end
|
||||
|
||||
@spec file_mime_type(String.t()) :: {:ok, String.t()} | {:error, any()} | :error
|
||||
def file_mime_type(filename) do
|
||||
File.open(filename, [:read], fn f ->
|
||||
check_mime_type(IO.binread(f, @read_bytes))
|
||||
end)
|
||||
end
|
||||
|
||||
def bin_mime_type(binary, filename) do
|
||||
with {:ok, content_type} <- bin_mime_type(binary),
|
||||
filename <- fix_extension(filename, content_type) do
|
||||
{:ok, content_type, filename}
|
||||
end
|
||||
end
|
||||
|
||||
@spec bin_mime_type(binary()) :: {:ok, String.t()} | :error
|
||||
def bin_mime_type(<<head::binary-size(@read_bytes), _::binary>>) do
|
||||
{:ok, check_mime_type(head)}
|
||||
end
|
||||
|
||||
def mime_type(<<_::binary>>), do: {:ok, @default}
|
||||
|
||||
def bin_mime_type(_), do: :error
|
||||
|
||||
defp fix_extension(filename, content_type) do
|
||||
parts = String.split(filename, ".")
|
||||
|
||||
new_filename =
|
||||
if length(parts) > 1 do
|
||||
Enum.drop(parts, -1) |> Enum.join(".")
|
||||
else
|
||||
Enum.join(parts)
|
||||
end
|
||||
|
||||
cond do
|
||||
content_type == "application/octet-stream" ->
|
||||
filename
|
||||
|
||||
ext = List.first(MIME.extensions(content_type)) ->
|
||||
new_filename <> "." <> ext
|
||||
|
||||
true ->
|
||||
Enum.join([new_filename, String.split(content_type, "/") |> List.last()], ".")
|
||||
end
|
||||
end
|
||||
|
||||
defp check_mime_type(<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, _::binary>>) do
|
||||
"image/png"
|
||||
end
|
||||
|
||||
defp check_mime_type(<<0x47, 0x49, 0x46, 0x38, _, 0x61, _::binary>>) do
|
||||
"image/gif"
|
||||
end
|
||||
|
||||
defp check_mime_type(<<0xFF, 0xD8, 0xFF, _::binary>>) do
|
||||
"image/jpeg"
|
||||
end
|
||||
|
||||
defp check_mime_type(<<0x1A, 0x45, 0xDF, 0xA3, _::binary>>) do
|
||||
"video/webm"
|
||||
end
|
||||
|
||||
defp check_mime_type(<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::binary>>) do
|
||||
"video/mp4"
|
||||
end
|
||||
|
||||
defp check_mime_type(<<0x49, 0x44, 0x33, _::binary>>) do
|
||||
"audio/mpeg"
|
||||
end
|
||||
|
||||
defp check_mime_type(<<255, 251, _, 68, 0, 0, 0, 0, _::binary>>) do
|
||||
"audio/mpeg"
|
||||
end
|
||||
|
||||
defp check_mime_type(
|
||||
<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, _::size(160), 0x80, 0x74, 0x68, 0x65,
|
||||
0x6F, 0x72, 0x61, _::binary>>
|
||||
) do
|
||||
"video/ogg"
|
||||
end
|
||||
|
||||
defp check_mime_type(<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, _::binary>>) do
|
||||
"audio/ogg"
|
||||
end
|
||||
|
||||
defp check_mime_type(<<0x52, 0x49, 0x46, 0x46, _::binary>>) do
|
||||
"audio/wav"
|
||||
end
|
||||
|
||||
defp check_mime_type(_) do
|
||||
@default
|
||||
end
|
||||
end
|
@ -0,0 +1,78 @@
|
||||
defmodule Pleroma.Plugs.UploadedMedia do
|
||||
@moduledoc """
|
||||
"""
|
||||
|
||||
import Plug.Conn
|
||||
require Logger
|
||||
|
||||
@behaviour Plug
|
||||
# no slashes
|
||||
@path "media"
|
||||
@cache_control %{
|
||||
default: "public, max-age=1209600",
|
||||
error: "public, must-revalidate, max-age=160"
|
||||
}
|
||||
|
||||
def init(_opts) do
|
||||
static_plug_opts =
|
||||
[]
|
||||
|> Keyword.put(:from, "__unconfigured_media_plug")
|
||||
|> Keyword.put(:at, "/__unconfigured_media_plug")
|
||||
|> Plug.Static.init()
|
||||
|
||||
%{static_plug_opts: static_plug_opts}
|
||||
end
|
||||
|
||||
def call(conn = %{request_path: <<"/", @path, "/", file::binary>>}, opts) do
|
||||
config = Pleroma.Config.get([Pleroma.Upload])
|
||||
|
||||
with uploader <- Keyword.fetch!(config, :uploader),
|
||||
proxy_remote = Keyword.get(config, :proxy_remote, false),
|
||||
{:ok, get_method} <- uploader.get_file(file) do
|
||||
get_media(conn, get_method, proxy_remote, opts)
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> send_resp(500, "Failed")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
def call(conn, _opts), do: conn
|
||||
|
||||
defp get_media(conn, {:static_dir, directory}, _, opts) do
|
||||
static_opts =
|
||||
Map.get(opts, :static_plug_opts)
|
||||
|> Map.put(:at, [@path])
|
||||
|> Map.put(:from, directory)
|
||||
|
||||
conn = Plug.Static.call(conn, static_opts)
|
||||
|
||||
if conn.halted do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> send_resp(404, "Not found")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
defp get_media(conn, {:url, url}, true, _) do
|
||||
conn
|
||||
|> Pleroma.ReverseProxy.call(url, Pleroma.Config.get([Pleroma.Upload, :proxy_opts], []))
|
||||
end
|
||||
|
||||
defp get_media(conn, {:url, url}, _, _) do
|
||||
conn
|
||||
|> Phoenix.Controller.redirect(external: url)
|
||||
|> halt()
|
||||
end
|
||||
|
||||
defp get_media(conn, unknown, _, _) do
|
||||
Logger.error("#{__MODULE__}: Unknown get startegy: #{inspect(unknown)}")
|
||||
|
||||
conn
|
||||
|> send_resp(500, "Internal Error")
|
||||
|> halt()
|
||||
end
|
||||
end
|
@ -0,0 +1,343 @@
|
||||
defmodule Pleroma.ReverseProxy do
|
||||
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since if-unmodified-since if-none-match if-range range)
|
||||
@resp_cache_headers ~w(etag date last-modified cache-control)
|
||||
@keep_resp_headers @resp_cache_headers ++
|
||||
~w(content-type content-disposition content-encoding content-range accept-ranges vary)
|
||||
@default_cache_control_header "public, max-age=1209600"
|
||||
@valid_resp_codes [200, 206, 304]
|
||||
@max_read_duration :timer.seconds(30)
|
||||
@max_body_length :infinity
|
||||
@methods ~w(GET HEAD)
|
||||
|
||||
@moduledoc """
|
||||
A reverse proxy.
|
||||
|
||||
Pleroma.ReverseProxy.call(conn, url, options)
|
||||
|
||||
It is not meant to be added into a plug pipeline, but to be called from another plug or controller.
|
||||
|
||||
Supports `#{inspect(@methods)}` HTTP methods, and only allows `#{inspect(@valid_resp_codes)}` status codes.
|
||||
|
||||
Responses are chunked to the client while downloading from the upstream.
|
||||
|
||||
Some request / responses headers are preserved:
|
||||
|
||||
* request: `#{inspect(@keep_req_headers)}`
|
||||
* response: `#{inspect(@keep_resp_headers)}`
|
||||
|
||||
If no caching headers (`#{inspect(@resp_cache_headers)}`) are returned by upstream, `cache-control` will be
|
||||
set to `#{inspect(@default_cache_control_header)}`.
|
||||
|
||||
Options:
|
||||
|
||||
* `redirect_on_failure` (default `false`). Redirects the client to the real remote URL if there's any HTTP
|
||||
errors. Any error during body processing will not be redirected as the response is chunked. This may expose
|
||||
remote URL, clients IPs, ….
|
||||
|
||||
* `max_body_length` (default `#{inspect(@max_body_length)}`): limits the content length to be approximately the
|
||||
specified length. It is validated with the `content-length` header and also verified when proxying.
|
||||
|
||||
* `max_read_duration` (default `#{inspect(@max_read_duration)}` ms): the total time the connection is allowed to
|
||||
read from the remote upstream.
|
||||
|
||||
* `inline_content_types`:
|
||||
* `true` will not alter `content-disposition` (up to the upstream),
|
||||
* `false` will add `content-disposition: attachment` to any request,
|
||||
* a list of whitelisted content types
|
||||
|
||||
* `keep_user_agent` will forward the client's user-agent to the upstream. This may be useful if the upstream is
|
||||
doing content transformation (encoding, …) depending on the request.
|
||||
|
||||
* `req_headers`, `resp_headers` additional headers.
|
||||
|
||||
* `http`: options for [hackney](https://github.com/benoitc/hackney).
|
||||
|
||||
"""
|
||||
@hackney Application.get_env(:pleroma, :hackney, :hackney)
|
||||
@httpoison Application.get_env(:pleroma, :httpoison, HTTPoison)
|
||||
|
||||
@default_hackney_options [{:follow_redirect, true}]
|
||||
|
||||
@inline_content_types [
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"audio/mpeg",
|
||||
"audio/mp3",
|
||||
"video/webm",
|
||||
"video/mp4",
|
||||
"video/quicktime"
|
||||
]
|
||||
|
||||
require Logger
|
||||
import Plug.Conn
|
||||
|
||||
@type option() ::
|
||||
{:keep_user_agent, boolean}
|
||||
| {:max_read_duration, :timer.time() | :infinity}
|
||||
| {:max_body_length, non_neg_integer() | :infinity}
|
||||
| {:http, []}
|
||||
| {:req_headers, [{String.t(), String.t()}]}
|
||||
| {:resp_headers, [{String.t(), String.t()}]}
|
||||
| {:inline_content_types, boolean() | [String.t()]}
|
||||
| {:redirect_on_failure, boolean()}
|
||||
|
||||
@spec call(Plug.Conn.t(), url :: String.t(), [option()]) :: Plug.Conn.t()
|
||||
def call(conn = %{method: method}, url, opts \\ []) when method in @methods do
|
||||
hackney_opts =
|
||||
@default_hackney_options
|
||||
|> Keyword.merge(Keyword.get(opts, :http, []))
|
||||
|> @httpoison.process_request_options()
|
||||
|
||||
req_headers = build_req_headers(conn.req_headers, opts)
|
||||
|
||||
opts =
|
||||
if filename = Pleroma.Web.MediaProxy.filename(url) do
|
||||
Keyword.put_new(opts, :attachment_name, filename)
|
||||
else
|
||||
opts
|
||||
end
|
||||
|
||||
with {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
|
||||
:ok <- header_length_constraint(headers, Keyword.get(opts, :max_body_length)) do
|
||||
response(conn, client, url, code, headers, opts)
|
||||
else
|
||||
{:ok, code, headers} ->
|
||||
head_response(conn, url, code, headers, opts)
|
||||
|> halt()
|
||||
|
||||
{:error, {:invalid_http_response, code}} ->
|
||||
Logger.error("#{__MODULE__}: request to #{inspect(url)} failed with HTTP status #{code}")
|
||||
|
||||
conn
|
||||
|> error_or_redirect(
|
||||
url,
|
||||
code,
|
||||
"Request failed: " <> Plug.Conn.Status.reason_phrase(code),
|
||||
opts
|
||||
)
|
||||
|> halt()
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__}: request to #{inspect(url)} failed: #{inspect(error)}")
|
||||
|
||||
conn
|
||||
|> error_or_redirect(url, 500, "Request failed", opts)
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
def call(conn, _, _) do
|
||||
conn
|
||||
|> send_resp(400, Plug.Conn.Status.reason_phrase(400))
|
||||
|> halt()
|
||||
end
|
||||
|
||||
defp request(method, url, headers, hackney_opts) do
|
||||
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
|
||||
method = method |> String.downcase() |> String.to_existing_atom()
|
||||
|
||||
case @hackney.request(method, url, headers, "", hackney_opts) do
|
||||
{:ok, code, headers, client} when code in @valid_resp_codes ->
|
||||
{:ok, code, downcase_headers(headers), client}
|
||||
|
||||
{:ok, code, headers} when code in @valid_resp_codes ->
|
||||
{:ok, code, downcase_headers(headers)}
|
||||
|
||||
{:ok, code, _, _} ->
|
||||
{:error, {:invalid_http_response, code}}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp response(conn, client, url, status, headers, opts) do
|
||||
result =
|
||||
conn
|
||||
|> put_resp_headers(build_resp_headers(headers, opts))
|
||||
|> send_chunked(status)
|
||||
|> chunk_reply(client, opts)
|
||||
|
||||
case result do
|
||||
{:ok, conn} ->
|
||||
halt(conn)
|
||||
|
||||
{:error, :closed, conn} ->
|
||||
:hackney.close(client)
|
||||
halt(conn)
|
||||
|
||||
{:error, error, conn} ->
|
||||
Logger.warn(
|
||||
"#{__MODULE__} request to #{url} failed while reading/chunking: #{inspect(error)}"
|
||||
)
|
||||
|
||||
:hackney.close(client)
|
||||
halt(conn)
|
||||
end
|
||||
end
|
||||
|
||||
defp chunk_reply(conn, client, opts) do
|
||||
chunk_reply(conn, client, opts, 0, 0)
|
||||
end
|
||||
|
||||
defp chunk_reply(conn, client, opts, sent_so_far, duration) do
|
||||
with {:ok, duration} <-
|
||||
check_read_duration(
|
||||
duration,
|
||||
Keyword.get(opts, :max_read_duration, @max_read_duration)
|
||||
),
|
||||
{:ok, data} <- @hackney.stream_body(client),
|
||||
{:ok, duration} <- increase_read_duration(duration),
|
||||
sent_so_far = sent_so_far + byte_size(data),
|
||||
:ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)),
|
||||
{:ok, conn} <- chunk(conn, data) do
|
||||
chunk_reply(conn, client, opts, sent_so_far, duration)
|
||||
else
|
||||
:done -> {:ok, conn}
|
||||
{:error, error} -> {:error, error, conn}
|
||||
end
|
||||
end
|
||||
|
||||
defp head_response(conn, _url, code, headers, opts) do
|
||||
conn
|
||||
|> put_resp_headers(build_resp_headers(headers, opts))
|
||||
|> send_resp(code, "")
|
||||
end
|
||||
|
||||
defp error_or_redirect(conn, url, code, body, opts) do
|
||||
if Keyword.get(opts, :redirect_on_failure, false) do
|
||||
conn
|
||||
|> Phoenix.Controller.redirect(external: url)
|
||||
|> halt()
|
||||
else
|
||||
conn
|
||||
|> send_resp(code, body)
|
||||
|> halt
|
||||
end
|
||||
end
|
||||
|
||||
defp downcase_headers(headers) do
|
||||
Enum.map(headers, fn {k, v} ->
|
||||
{String.downcase(k), v}
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_content_type(headers) do
|
||||
{_, content_type} =
|
||||
List.keyfind(headers, "content-type", 0, {"content-type", "application/octet-stream"})
|
||||
|
||||
[content_type | _] = String.split(content_type, ";")
|
||||
content_type
|
||||
end
|
||||
|
||||
defp put_resp_headers(conn, headers) do
|
||||
Enum.reduce(headers, conn, fn {k, v}, conn ->
|
||||
put_resp_header(conn, k, v)
|
||||
end)
|
||||
end
|
||||
|
||||
defp build_req_headers(headers, opts) do
|
||||
headers =
|
||||
headers
|
||||
|> downcase_headers()
|
||||
|> Enum.filter(fn {k, _} -> k in @keep_req_headers end)
|
||||
|> (fn headers ->
|
||||
headers = headers ++ Keyword.get(opts, :req_headers, [])
|
||||
|
||||
if Keyword.get(opts, :keep_user_agent, false) do
|
||||
List.keystore(
|
||||
headers,
|
||||
"user-agent",
|
||||
0,
|
||||
{"user-agent", Pleroma.Application.user_agent()}
|
||||
)
|
||||
else
|
||||
headers
|
||||
end
|
||||
end).()
|
||||
end
|
||||
|
||||
defp build_resp_headers(headers, opts) do
|
||||
headers
|
||||
|> Enum.filter(fn {k, _} -> k in @keep_resp_headers end)
|
||||
|> build_resp_cache_headers(opts)
|
||||
|> build_resp_content_disposition_header(opts)
|
||||
|> (fn headers -> headers ++ Keyword.get(opts, :resp_headers, []) end).()
|
||||
end
|
||||
|
||||
defp build_resp_cache_headers(headers, opts) do
|
||||
has_cache? = Enum.any?(headers, fn {k, _} -> k in @resp_cache_headers end)
|
||||
|
||||
if has_cache? do
|
||||
headers
|
||||
else
|
||||
List.keystore(headers, "cache-control", 0, {"cache-control", @default_cache_control_header})
|
||||
end
|
||||
end
|
||||
|
||||
defp build_resp_content_disposition_header(headers, opts) do
|
||||
opt = Keyword.get(opts, :inline_content_types, @inline_content_types)
|
||||
|
||||
content_type = get_content_type(headers)
|
||||
|
||||
attachment? =
|
||||
cond do
|
||||
is_list(opt) && !Enum.member?(opt, content_type) -> true
|
||||
opt == false -> true
|
||||
true -> false
|
||||
end
|
||||
|
||||
if attachment? do
|
||||
disposition = "attachment; filename=" <> Keyword.get(opts, :attachment_name, "attachment")
|
||||
List.keystore(headers, "content-disposition", 0, {"content-disposition", disposition})
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
defp header_length_constraint(headers, limit) when is_integer(limit) and limit > 0 do
|
||||
with {_, size} <- List.keyfind(headers, "content-length", 0),
|
||||
{size, _} <- Integer.parse(size),
|
||||
true <- size <= limit do
|
||||
:ok
|
||||
else
|
||||
false ->
|
||||
{:error, :body_too_large}
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp header_length_constraint(_, _), do: :ok
|
||||
|
||||
defp body_size_constraint(size, limit) when is_integer(limit) and limit > 0 and size >= limit do
|
||||
{:error, :body_too_large}
|
||||
end
|
||||
|
||||
defp body_size_constraint(_, _), do: :ok
|
||||
|
||||
defp check_read_duration(duration, max)
|
||||
when is_integer(duration) and is_integer(max) and max > 0 do
|
||||
if duration > max do
|
||||
{:error, :read_duration_exceeded}
|
||||
else
|
||||
{:ok, {duration, :erlang.system_time(:millisecond)}}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_read_duration(_, _), do: {:ok, :no_duration_limit, :no_duration_limit}
|
||||
|
||||
defp increase_read_duration({previous_duration, started})
|
||||
when is_integer(previous_duration) and is_integer(started) do
|
||||
duration = :erlang.system_time(:millisecond) - started
|
||||
{:ok, previous_duration + duration}
|
||||
end
|
||||
|
||||
defp increase_read_duration(_) do
|
||||
{:ok, :no_duration_limit, :no_duration_limit}
|
||||
end
|
||||
end
|
@ -1,190 +1,222 @@
|
||||
defmodule Pleroma.Upload do
|
||||
alias Ecto.UUID
|
||||
@moduledoc """
|
||||
# Upload
|
||||
|
||||
def check_file_size(path, nil), do: true
|
||||
Options:
|
||||
* `:type`: presets for activity type (defaults to Document) and size limits from app configuration
|
||||
* `:description`: upload alternative text
|
||||
* `:base_url`: override base url
|
||||
* `:uploader`: override uploader
|
||||
* `:filters`: override filters
|
||||
* `:size_limit`: override size limit
|
||||
* `:activity_type`: override activity type
|
||||
|
||||
def check_file_size(path, size_limit) do
|
||||
{:ok, %{size: size}} = File.stat(path)
|
||||
size <= size_limit
|
||||
end
|
||||
The `%Pleroma.Upload{}` struct: all documented fields are meant to be overwritten in filters:
|
||||
|
||||
* `:id` - the upload id.
|
||||
* `:name` - the upload file name.
|
||||
* `:path` - the upload path: set at first to `id/name` but can be changed. Keep in mind that the path
|
||||
is once created permanent and changing it (especially in uploaders) is probably a bad idea!
|
||||
* `:tempfile` - path to the temporary file. Prefer in-place changes on the file rather than changing the
|
||||
path as the temporary file is also tracked by `Plug.Upload{}` and automatically deleted once the request is over.
|
||||
|
||||
Related behaviors:
|
||||
|
||||
def store(file, should_dedupe, size_limit \\ nil)
|
||||
|
||||
def store(%Plug.Upload{} = file, should_dedupe, size_limit) do
|
||||
content_type = get_content_type(file.path)
|
||||
|
||||
with uuid <- get_uuid(file, should_dedupe),
|
||||
name <- get_name(file, uuid, content_type, should_dedupe),
|
||||
true <- check_file_size(file.path, size_limit) do
|
||||
strip_exif_data(content_type, file.path)
|
||||
|
||||
{:ok, url_path} = uploader().put_file(name, uuid, file.path, content_type, should_dedupe)
|
||||
|
||||
%{
|
||||
"type" => "Document",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => name
|
||||
}
|
||||
* `Pleroma.Uploaders.Uploader`
|
||||
* `Pleroma.Upload.Filter`
|
||||
|
||||
"""
|
||||
alias Ecto.UUID
|
||||
require Logger
|
||||
|
||||
@type source ::
|
||||
Plug.Upload.t() | data_uri_string ::
|
||||
String.t() | {:from_local, name :: String.t(), id :: String.t(), path :: String.t()}
|
||||
|
||||
@type option ::
|
||||
{:type, :avatar | :banner | :background}
|
||||
| {:description, String.t()}
|
||||
| {:activity_type, String.t()}
|
||||
| {:size_limit, nil | non_neg_integer()}
|
||||
| {:uploader, module()}
|
||||
| {:filters, [module()]}
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: String.t(),
|
||||
name: String.t(),
|
||||
tempfile: String.t(),
|
||||
content_type: String.t(),
|
||||
path: String.t()
|
||||
}
|
||||
defstruct [:id, :name, :tempfile, :content_type, :path]
|
||||
|
||||
@spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()}
|
||||
def store(upload, opts \\ []) do
|
||||
opts = get_opts(opts)
|
||||
|
||||
with {:ok, upload} <- prepare_upload(upload, opts),
|
||||
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
|
||||
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
|
||||
{:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do
|
||||
{:ok,
|
||||
%{
|
||||
"type" => opts.activity_type,
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => upload.content_type,
|
||||
"href" => url_from_spec(opts.base_url, url_spec)
|
||||
}
|
||||
],
|
||||
"name" => Map.get(opts, :description) || upload.name
|
||||
}}
|
||||
else
|
||||
_e -> nil
|
||||
{:error, error} ->
|
||||
Logger.error(
|
||||
"#{__MODULE__} store (using #{inspect(opts.uploader)}) failed: #{inspect(error)}"
|
||||
)
|
||||
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def store(%{"img" => "data:image/" <> image_data}, should_dedupe, size_limit) do
|
||||
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
|
||||
data = Base.decode64!(parsed["data"], ignore: :whitespace)
|
||||
defp get_opts(opts) do
|
||||
{size_limit, activity_type} =
|
||||
case Keyword.get(opts, :type) do
|
||||
:banner ->
|
||||
{Pleroma.Config.get!([:instance, :banner_upload_limit]), "Image"}
|
||||
|
||||
:avatar ->
|
||||
{Pleroma.Config.get!([:instance, :avatar_upload_limit]), "Image"}
|
||||
|
||||
:background ->
|
||||
{Pleroma.Config.get!([:instance, :background_upload_limit]), "Image"}
|
||||
|
||||
with tmp_path <- tempfile_for_image(data),
|
||||
uuid <- UUID.generate(),
|
||||
true <- check_file_size(tmp_path, size_limit) do
|
||||
content_type = get_content_type(tmp_path)
|
||||
strip_exif_data(content_type, tmp_path)
|
||||
|
||||
name =
|
||||
create_name(
|
||||
String.downcase(Base.encode16(:crypto.hash(:sha256, data))),
|
||||
parsed["filetype"],
|
||||
content_type
|
||||
_ ->
|
||||
{Pleroma.Config.get!([:instance, :upload_limit]), "Document"}
|
||||
end
|
||||
|
||||
opts = %{
|
||||
activity_type: Keyword.get(opts, :activity_type, activity_type),
|
||||
size_limit: Keyword.get(opts, :size_limit, size_limit),
|
||||
uploader: Keyword.get(opts, :uploader, Pleroma.Config.get([__MODULE__, :uploader])),
|
||||
filters: Keyword.get(opts, :filters, Pleroma.Config.get([__MODULE__, :filters])),
|
||||
description: Keyword.get(opts, :description),
|
||||
base_url:
|
||||
Keyword.get(
|
||||
opts,
|
||||
:base_url,
|
||||
Pleroma.Config.get([__MODULE__, :base_url], Pleroma.Web.base_url())
|
||||
)
|
||||
}
|
||||
|
||||
{:ok, url_path} = uploader().put_file(name, uuid, tmp_path, content_type, should_dedupe)
|
||||
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => name
|
||||
}
|
||||
else
|
||||
_e -> nil
|
||||
end
|
||||
end
|
||||
# TODO: 1.0+ : remove old config compatibility
|
||||
opts =
|
||||
if Pleroma.Config.get([__MODULE__, :strip_exif]) == true &&
|
||||
!Enum.member?(opts.filters, Pleroma.Upload.Filter.Mogrify) do
|
||||
Logger.warn("""
|
||||
Pleroma: configuration `:instance, :strip_exif` is deprecated, please instead set:
|
||||
|
||||
@doc """
|
||||
Creates a tempfile using the Plug.Upload Genserver which cleans them up
|
||||
automatically.
|
||||
"""
|
||||
def tempfile_for_image(data) do
|
||||
{:ok, tmp_path} = Plug.Upload.random_file("profile_pics")
|
||||
{:ok, tmp_file} = File.open(tmp_path, [:write, :raw, :binary])
|
||||
IO.binwrite(tmp_file, data)
|
||||
:pleroma, Pleroma.Upload, [filters: [Pleroma.Upload.Filter.Mogrify]]
|
||||
|
||||
tmp_path
|
||||
end
|
||||
:pleroma, Pleroma.Upload.Filter.Mogrify, args: "strip"
|
||||
""")
|
||||
|
||||
def strip_exif_data(content_type, file) do
|
||||
settings = Application.get_env(:pleroma, Pleroma.Upload)
|
||||
do_strip = Keyword.fetch!(settings, :strip_exif)
|
||||
[filetype, _ext] = String.split(content_type, "/")
|
||||
Pleroma.Config.put([Pleroma.Upload.Filter.Mogrify], args: "strip")
|
||||
Map.put(opts, :filters, opts.filters ++ [Pleroma.Upload.Filter.Mogrify])
|
||||
else
|
||||
opts
|
||||
end
|
||||
|
||||
if filetype == "image" and do_strip == true do
|
||||
Mogrify.open(file) |> Mogrify.custom("strip") |> Mogrify.save(in_place: true)
|
||||
end
|
||||
end
|
||||
opts =
|
||||
if Pleroma.Config.get([:instance, :dedupe_media]) == true &&
|
||||
!Enum.member?(opts.filters, Pleroma.Upload.Filter.Dedupe) do
|
||||
Logger.warn("""
|
||||
Pleroma: configuration `:instance, :dedupe_media` is deprecated, please instead set:
|
||||
|
||||
defp create_name(uuid, ext, type) do
|
||||
case type do
|
||||
"application/octet-stream" ->
|
||||
String.downcase(Enum.join([uuid, ext], "."))
|
||||
:pleroma, Pleroma.Upload, [filters: [Pleroma.Upload.Filter.Dedupe]]
|
||||
""")
|
||||
|
||||
"audio/mpeg" ->
|
||||
String.downcase(Enum.join([uuid, "mp3"], "."))
|
||||
Map.put(opts, :filters, opts.filters ++ [Pleroma.Upload.Filter.Dedupe])
|
||||
else
|
||||
opts
|
||||
end
|
||||
end
|
||||
|
||||
_ ->
|
||||
String.downcase(Enum.join([uuid, List.last(String.split(type, "/"))], "."))
|
||||
defp prepare_upload(%Plug.Upload{} = file, opts) do
|
||||
with :ok <- check_file_size(file.path, opts.size_limit),
|
||||
{:ok, content_type, name} <- Pleroma.MIME.file_mime_type(file.path, file.filename) do
|
||||
{:ok,
|
||||
%__MODULE__{
|
||||
id: UUID.generate(),
|
||||
name: name,
|
||||
tempfile: file.path,
|
||||
content_type: content_type
|
||||
}}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_uuid(file, should_dedupe) do
|
||||
if should_dedupe do
|
||||
Base.encode16(:crypto.hash(:sha256, File.read!(file.path)))
|
||||
else
|
||||
UUID.generate()
|
||||
defp prepare_upload(%{"img" => "data:image/" <> image_data}, opts) do
|
||||
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
|
||||
data = Base.decode64!(parsed["data"], ignore: :whitespace)
|
||||
hash = String.downcase(Base.encode16(:crypto.hash(:sha256, data)))
|
||||
|
||||
with :ok <- check_binary_size(data, opts.size_limit),
|
||||
tmp_path <- tempfile_for_image(data),
|
||||
{:ok, content_type, name} <-
|
||||
Pleroma.MIME.bin_mime_type(data, hash <> "." <> parsed["filetype"]) do
|
||||
{:ok,
|
||||
%__MODULE__{
|
||||
id: UUID.generate(),
|
||||
name: name,
|
||||
tempfile: tmp_path,
|
||||
content_type: content_type
|
||||
}}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_name(file, uuid, type, should_dedupe) do
|
||||
if should_dedupe do
|
||||
create_name(uuid, List.last(String.split(file.filename, ".")), type)
|
||||
else
|
||||
parts = String.split(file.filename, ".")
|
||||
|
||||
new_filename =
|
||||
if length(parts) > 1 do
|
||||
Enum.drop(parts, -1) |> Enum.join(".")
|
||||
else
|
||||
Enum.join(parts)
|
||||
end
|
||||
|
||||
case type do
|
||||
"application/octet-stream" -> file.filename
|
||||
"audio/mpeg" -> new_filename <> ".mp3"
|
||||
"image/jpeg" -> new_filename <> ".jpg"
|
||||
_ -> Enum.join([new_filename, String.split(type, "/") |> List.last()], ".")
|
||||
end
|
||||
# For Mix.Tasks.MigrateLocalUploads
|
||||
defp prepare_upload(upload = %__MODULE__{tempfile: path}, _opts) do
|
||||
with {:ok, content_type} <- Pleroma.MIME.file_mime_type(path) do
|
||||
{:ok, %__MODULE__{upload | content_type: content_type}}
|
||||
end
|
||||
end
|
||||
|
||||
def get_content_type(file) do
|
||||
match =
|
||||
File.open(file, [:read], fn f ->
|
||||
case IO.binread(f, 8) do
|
||||
<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A>> ->
|
||||
"image/png"
|
||||
|
||||
<<0x47, 0x49, 0x46, 0x38, _, 0x61, _, _>> ->
|
||||
"image/gif"
|
||||
|
||||
<<0xFF, 0xD8, 0xFF, _, _, _, _, _>> ->
|
||||
"image/jpeg"
|
||||
|
||||
<<0x1A, 0x45, 0xDF, 0xA3, _, _, _, _>> ->
|
||||
"video/webm"
|
||||
|
||||
<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70>> ->
|
||||
"video/mp4"
|
||||
|
||||
<<0x49, 0x44, 0x33, _, _, _, _, _>> ->
|
||||
"audio/mpeg"
|
||||
defp check_binary_size(binary, size_limit)
|
||||
when is_integer(size_limit) and size_limit > 0 and byte_size(binary) >= size_limit do
|
||||
{:error, :file_too_large}
|
||||
end
|
||||
|
||||
<<255, 251, _, 68, 0, 0, 0, 0>> ->
|
||||
"audio/mpeg"
|
||||
defp check_binary_size(_, _), do: :ok
|
||||
|
||||
<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
|
||||
case IO.binread(f, 27) do
|
||||
<<_::size(160), 0x80, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61>> ->
|
||||
"video/ogg"
|
||||
defp check_file_size(path, size_limit) when is_integer(size_limit) and size_limit > 0 do
|
||||
with {:ok, %{size: size}} <- File.stat(path),
|
||||
true <- size <= size_limit do
|
||||
:ok
|
||||
else
|
||||
false -> {:error, :file_too_large}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
_ ->
|
||||
"audio/ogg"
|
||||
end
|
||||
defp check_file_size(_, _), do: :ok
|
||||
|
||||
<<0x52, 0x49, 0x46, 0x46, _, _, _, _>> ->
|
||||
"audio/wav"
|
||||
# Creates a tempfile using the Plug.Upload Genserver which cleans them up
|
||||
# automatically.
|
||||
defp tempfile_for_image(data) do
|
||||
{:ok, tmp_path} = Plug.Upload.random_file("profile_pics")
|
||||
{:ok, tmp_file} = File.open(tmp_path, [:write, :raw, :binary])
|
||||
IO.binwrite(tmp_file, data)
|
||||
|
||||
_ ->
|
||||
"application/octet-stream"
|
||||
end
|
||||
end)
|
||||
tmp_path
|
||||
end
|
||||
|
||||
case match do
|
||||
{:ok, type} -> type
|
||||
_e -> "application/octet-stream"
|
||||
end
|
||||
defp url_from_spec(base_url, {:file, path}) do
|
||||
[base_url, "media", path]
|
||||
|> Path.join()
|
||||
end
|
||||
|
||||
defp uploader() do
|
||||
Pleroma.Config.get!([Pleroma.Upload, :uploader])
|
||||
defp url_from_spec({:url, url}) do
|
||||
url
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,35 @@
|
||||
defmodule Pleroma.Upload.Filter do
|
||||
@moduledoc """
|
||||
Upload Filter behaviour
|
||||
|
||||
This behaviour allows to run filtering actions just before a file is uploaded. This allows to:
|
||||
|
||||
* morph in place the temporary file
|
||||
* change any field of a `Pleroma.Upload` struct
|
||||
* cancel/stop the upload
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
@callback filter(Pleroma.Upload.t()) :: :ok | {:ok, Pleroma.Upload.t()} | {:error, any()}
|
||||
|
||||
@spec filter([module()], Pleroma.Upload.t()) :: {:ok, Pleroma.Upload.t()} | {:error, any()}
|
||||
|
||||
def filter([], upload) do
|
||||
{:ok, upload}
|
||||
end
|
||||
|
||||
def filter([filter | rest], upload) do
|
||||
case filter.filter(upload) do
|
||||
:ok ->
|
||||
filter(rest, upload)
|
||||
|
||||
{:ok, upload} ->
|
||||
filter(rest, upload)
|
||||
|
||||
error ->
|
||||
Logger.error("#{__MODULE__}: Filter #{filter} failed: #{inspect(error)}")
|
||||
error
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,10 @@
|
||||
defmodule Pleroma.Upload.Filter.AnonymizeFilename do
|
||||
@moduledoc "Replaces the original filename with a randomly generated string."
|
||||
@behaviour Pleroma.Upload.Filter
|
||||
|
||||
def filter(upload) do
|
||||
extension = List.last(String.split(upload.name, "."))
|
||||
string = Base.url_encode64(:crypto.strong_rand_bytes(10), padding: false)
|
||||
{:ok, %Pleroma.Upload{upload | name: string <> "." <> extension}}
|
||||
end
|
||||
end
|
@ -0,0 +1,10 @@
|
||||
defmodule Pleroma.Upload.Filter.Dedupe do
|
||||
@behaviour Pleroma.Upload.Filter
|
||||
|
||||
def filter(upload = %Pleroma.Upload{name: name, tempfile: path}) do
|
||||
extension = String.split(name, ".") |> List.last()
|
||||
shasum = :crypto.hash(:sha256, File.read!(upload.tempfile)) |> Base.encode16(case: :lower)
|
||||
filename = shasum <> "." <> extension
|
||||
{:ok, %Pleroma.Upload{upload | id: shasum, path: filename}}
|
||||
end
|
||||
end
|
@ -0,0 +1,60 @@
|
||||
defmodule Pleroma.Upload.Filter.Mogrifun do
|
||||
@behaviour Pleroma.Upload.Filter
|
||||
|
||||
@filters [
|
||||
{"implode", "1"},
|
||||
{"-raise", "20"},
|
||||
{"+raise", "20"},
|
||||
[{"-interpolate", "nearest"}, {"-virtual-pixel", "mirror"}, {"-spread", "5"}],
|
||||
"+polaroid",
|
||||
{"-statistic", "Mode 10"},
|
||||
{"-emboss", "0x1.1"},
|
||||
{"-emboss", "0x2"},
|
||||
{"-colorspace", "Gray"},
|
||||
"-negate",
|
||||
[{"-channel", "green"}, "-negate"],
|
||||
[{"-channel", "red"}, "-negate"],
|
||||
[{"-channel", "blue"}, "-negate"],
|
||||
{"+level-colors", "green,gold"},
|
||||
{"+level-colors", ",DodgerBlue"},
|
||||
{"+level-colors", ",Gold"},
|
||||
{"+level-colors", ",Lime"},
|
||||
{"+level-colors", ",Red"},
|
||||
{"+level-colors", ",DarkGreen"},
|
||||
{"+level-colors", "firebrick,yellow"},
|
||||
{"+level-colors", "'rgb(102,75,25)',lemonchiffon"},
|
||||
[{"fill", "red"}, {"tint", "40"}],
|
||||
[{"fill", "green"}, {"tint", "40"}],
|
||||
[{"fill", "blue"}, {"tint", "40"}],
|
||||
[{"fill", "yellow"}, {"tint", "40"}]
|
||||
]
|
||||
|
||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||
filter = Enum.random(@filters)
|
||||
|
||||
file
|
||||
|> Mogrify.open()
|
||||
|> mogrify_filter(filter)
|
||||
|> Mogrify.save(in_place: true)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def filter(_), do: :ok
|
||||
|
||||
defp mogrify_filter(mogrify, [filter | rest]) do
|
||||
mogrify
|
||||
|> mogrify_filter(filter)
|
||||
|> mogrify_filter(rest)
|
||||
end
|
||||
|
||||
defp mogrify_filter(mogrify, []), do: mogrify
|
||||
|
||||
defp mogrify_filter(mogrify, {action, options}) do
|
||||
Mogrify.custom(mogrify, action, options)
|
||||
end
|
||||
|
||||
defp mogrify_filter(mogrify, string) when is_binary(string) do
|
||||
Mogrify.custom(mogrify, string)
|
||||
end
|
||||
end
|
@ -0,0 +1,37 @@
|
||||
defmodule Pleroma.Upload.Filter.Mogrify do
|
||||
@behaviour Pleroma.Uploader.Filter
|
||||
|
||||
@type conversion :: action :: String.t() | {action :: String.t(), opts :: String.t()}
|
||||
@type conversions :: conversion() | [conversion()]
|
||||
|
||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||
filters = Pleroma.Config.get!([__MODULE__, :args])
|
||||
|
||||
file
|
||||
|> Mogrify.open()
|
||||
|> mogrify_filter(filters)
|
||||
|> Mogrify.save(in_place: true)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def filter(_), do: :ok
|
||||
|
||||
defp mogrify_filter(mogrify, nil), do: mogrify
|
||||
|
||||
defp mogrify_filter(mogrify, [filter | rest]) do
|
||||
mogrify
|
||||
|> mogrify_filter(filter)
|
||||
|> mogrify_filter(rest)
|
||||
end
|
||||
|
||||
defp mogrify_filter(mogrify, []), do: mogrify
|
||||
|
||||
defp mogrify_filter(mogrify, {action, options}) do
|
||||
Mogrify.custom(mogrify, action, options)
|
||||
end
|
||||
|
||||
defp mogrify_filter(mogrify, action) when is_binary(action) do
|
||||
Mogrify.custom(mogrify, action)
|
||||
end
|
||||
end
|
@ -1,40 +1,46 @@
|
||||
defmodule Pleroma.Uploaders.S3 do
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
@behaviour Pleroma.Uploaders.Uploader
|
||||
require Logger
|
||||
|
||||
# The file name is re-encoded with S3's constraints here to comply with previous links with less strict filenames
|
||||
def get_file(file) do
|
||||
config = Pleroma.Config.get([__MODULE__])
|
||||
|
||||
{:ok,
|
||||
{:url,
|
||||
Path.join([
|
||||
Keyword.fetch!(config, :public_endpoint),
|
||||
Keyword.fetch!(config, :bucket),
|
||||
strict_encode(URI.decode(file))
|
||||
])}}
|
||||
end
|
||||
|
||||
def put_file(name, uuid, path, content_type, _should_dedupe) do
|
||||
settings = Application.get_env(:pleroma, Pleroma.Uploaders.S3)
|
||||
bucket = Keyword.fetch!(settings, :bucket)
|
||||
public_endpoint = Keyword.fetch!(settings, :public_endpoint)
|
||||
force_media_proxy = Keyword.fetch!(settings, :force_media_proxy)
|
||||
|
||||
{:ok, file_data} = File.read(path)
|
||||
def put_file(upload = %Pleroma.Upload{}) do
|
||||
config = Pleroma.Config.get([__MODULE__])
|
||||
bucket = Keyword.get(config, :bucket)
|
||||
|
||||
File.rm!(path)
|
||||
{:ok, file_data} = File.read(upload.tempfile)
|
||||
|
||||
s3_name = "#{uuid}/#{encode(name)}"
|
||||
s3_name = strict_encode(upload.path)
|
||||
|
||||
{:ok, _} =
|
||||
op =
|
||||
ExAws.S3.put_object(bucket, s3_name, file_data, [
|
||||
{:acl, :public_read},
|
||||
{:content_type, content_type}
|
||||
{:content_type, upload.content_type}
|
||||
])
|
||||
|> ExAws.request()
|
||||
|
||||
url_base = "#{public_endpoint}/#{bucket}/#{s3_name}"
|
||||
|
||||
public_url =
|
||||
if force_media_proxy do
|
||||
MediaProxy.url(url_base)
|
||||
else
|
||||
url_base
|
||||
end
|
||||
case ExAws.request(op) do
|
||||
{:ok, _} ->
|
||||
{:ok, {:file, s3_name}}
|
||||
|
||||
{:ok, public_url}
|
||||
error ->
|
||||
Logger.error("#{__MODULE__}: #{inspect(error)}")
|
||||
{:error, "S3 Upload failed"}
|
||||
end
|
||||
end
|
||||
|
||||
defp encode(name) do
|
||||
String.replace(name, ~r/[^0-9a-zA-Z!.*'()_-]/, "-")
|
||||
@regex Regex.compile!("[^0-9a-zA-Z!.*/'()_-]")
|
||||
def strict_encode(name) do
|
||||
String.replace(name, @regex, "-")
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +1,15 @@
|
||||
defmodule Pleroma.Uploaders.Swift do
|
||||
@behaviour Pleroma.Uploaders.Uploader
|
||||
|
||||
def put_file(name, uuid, tmp_path, content_type, _should_dedupe) do
|
||||
{:ok, file_data} = File.read(tmp_path)
|
||||
remote_name = "#{uuid}/#{name}"
|
||||
def get_file(name) do
|
||||
{:ok, {:url, Path.join([Pleroma.Config.get!([__MODULE__, :object_url]), name])}}
|
||||
end
|
||||
|
||||
Pleroma.Uploaders.Swift.Client.upload_file(remote_name, file_data, content_type)
|
||||
def put_file(upload) do
|
||||
Pleroma.Uploaders.Swift.Client.upload_file(
|
||||
upload.path,
|
||||
File.read!(upload.tmpfile),
|
||||
upload.content_type
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -1,20 +1,40 @@
|
||||
defmodule Pleroma.Uploaders.Uploader do
|
||||
@moduledoc """
|
||||
Defines the contract to put an uploaded file to any backend.
|
||||
Defines the contract to put and get an uploaded file to any backend.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Instructs how to get the file from the backend.
|
||||
|
||||
Used by `Pleroma.Plugs.UploadedMedia`.
|
||||
"""
|
||||
@type get_method :: {:static_dir, directory :: String.t()} | {:url, url :: String.t()}
|
||||
@callback get_file(file :: String.t()) :: {:ok, get_method()}
|
||||
|
||||
@doc """
|
||||
Put a file to the backend.
|
||||
|
||||
Returns `{:ok, String.t } | {:error, String.t} containing the path of the
|
||||
uploaded file, or error information if the file failed to be saved to the
|
||||
respective backend.
|
||||
Returns:
|
||||
|
||||
* `:ok` which assumes `{:ok, upload.path}`
|
||||
* `{:ok, spec}` where spec is:
|
||||
* `{:file, filename :: String.t}` to handle reads with `get_file/1` (recommended)
|
||||
|
||||
This allows to correctly proxy or redirect requests to the backend, while allowing to migrate backends without breaking any URL.
|
||||
* `{url, url :: String.t}` to bypass `get_file/2` and use the `url` directly in the activity.
|
||||
* `{:error, String.t}` error information if the file failed to be saved to the backend.
|
||||
|
||||
|
||||
"""
|
||||
@callback put_file(
|
||||
name :: String.t(),
|
||||
uuid :: String.t(),
|
||||
file :: File.t(),
|
||||
content_type :: String.t(),
|
||||
should_dedupe :: Boolean.t()
|
||||
) :: {:ok, String.t()} | {:error, String.t()}
|
||||
@callback put_file(Pleroma.Upload.t()) ::
|
||||
:ok | {:ok, {:file | :url, String.t()}} | {:error, String.t()}
|
||||
|
||||
@spec put_file(module(), Pleroma.Upload.t()) ::
|
||||
{:ok, {:file | :url, String.t()}} | {:error, String.t()}
|
||||
def put_file(uploader, upload) do
|
||||
case uploader.put_file(upload) do
|
||||
:ok -> {:ok, {:file, upload.path}}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,167 @@
|
||||
defmodule Pleroma.User.Info do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
embedded_schema do
|
||||
field(:banner, :map, default: %{})
|
||||
field(:background, :map, default: %{})
|
||||
field(:source_data, :map, default: %{})
|
||||
field(:note_count, :integer, default: 0)
|
||||
field(:follower_count, :integer, default: 0)
|
||||
field(:locked, :boolean, default: false)
|
||||
field(:default_scope, :string, default: "public")
|
||||
field(:blocks, {:array, :string}, default: [])
|
||||
field(:domain_blocks, {:array, :string}, default: [])
|
||||
field(:deactivated, :boolean, default: false)
|
||||
field(:no_rich_text, :boolean, default: false)
|
||||
field(:ap_enabled, :boolean, default: false)
|
||||
field(:is_moderator, :boolean, default: false)
|
||||
field(:is_admin, :boolean, default: false)
|
||||
field(:keys, :string, default: nil)
|
||||
field(:settings, :map, default: nil)
|
||||
field(:magic_key, :string, default: nil)
|
||||
field(:uri, :string, default: nil)
|
||||
field(:topic, :string, default: nil)
|
||||
field(:hub, :string, default: nil)
|
||||
field(:salmon, :string, default: nil)
|
||||
field(:hide_network, :boolean, default: false)
|
||||
|
||||
# Found in the wild
|
||||
# ap_id -> Where is this used?
|
||||
# bio -> Where is this used?
|
||||
# avatar -> Where is this used?
|
||||
# fqn -> Where is this used?
|
||||
# host -> Where is this used?
|
||||
# subject _> Where is this used?
|
||||
end
|
||||
|
||||
def set_activation_status(info, deactivated) do
|
||||
params = %{deactivated: deactivated}
|
||||
|
||||
info
|
||||
|> cast(params, [:deactivated])
|
||||
|> validate_required([:deactivated])
|
||||
end
|
||||
|
||||
def add_to_note_count(info, number) do
|
||||
set_note_count(info, info.note_count + number)
|
||||
end
|
||||
|
||||
def set_note_count(info, number) do
|
||||
params = %{note_count: Enum.max([0, number])}
|
||||
|
||||
info
|
||||
|> cast(params, [:note_count])
|
||||
|> validate_required([:note_count])
|
||||
end
|
||||
|
||||
def set_follower_count(info, number) do
|
||||
params = %{follower_count: Enum.max([0, number])}
|
||||
|
||||
info
|
||||
|> cast(params, [:follower_count])
|
||||
|> validate_required([:follower_count])
|
||||
end
|
||||
|
||||
def set_blocks(info, blocks) do
|
||||
params = %{blocks: blocks}
|
||||
|
||||
info
|
||||
|> cast(params, [:blocks])
|
||||
|> validate_required([:blocks])
|
||||
end
|
||||
|
||||
def add_to_block(info, blocked) do
|
||||
set_blocks(info, Enum.uniq([blocked | info.blocks]))
|
||||
end
|
||||
|
||||
def remove_from_block(info, blocked) do
|
||||
set_blocks(info, List.delete(info.blocks, blocked))
|
||||
end
|
||||
|
||||
def set_domain_blocks(info, domain_blocks) do
|
||||
params = %{domain_blocks: domain_blocks}
|
||||
|
||||
info
|
||||
|> cast(params, [:domain_blocks])
|
||||
|> validate_required([:domain_blocks])
|
||||
end
|
||||
|
||||
def add_to_domain_block(info, domain_blocked) do
|
||||
set_domain_blocks(info, Enum.uniq([domain_blocked | info.domain_blocks]))
|
||||
end
|
||||
|
||||
def remove_from_domain_block(info, domain_blocked) do
|
||||
set_domain_blocks(info, List.delete(info.domain_blocks, domain_blocked))
|
||||
end
|
||||
|
||||
def set_keys(info, keys) do
|
||||
params = %{keys: keys}
|
||||
|
||||
info
|
||||
|> cast(params, [:keys])
|
||||
|> validate_required([:keys])
|
||||
end
|
||||
|
||||
def remote_user_creation(info, params) do
|
||||
info
|
||||
|> cast(params, [
|
||||
:ap_enabled,
|
||||
:source_data,
|
||||
:banner,
|
||||
:locked,
|
||||
:magic_key,
|
||||
:uri,
|
||||
:hub,
|
||||
:topic,
|
||||
:salmon
|
||||
])
|
||||
end
|
||||
|
||||
def user_upgrade(info, params) do
|
||||
info
|
||||
|> cast(params, [
|
||||
:ap_enabled,
|
||||
:source_data,
|
||||
:banner,
|
||||
:locked,
|
||||
:magic_key
|
||||
])
|
||||
end
|
||||
|
||||
def profile_update(info, params) do
|
||||
info
|
||||
|> cast(params, [
|
||||
:locked,
|
||||
:no_rich_text,
|
||||
:default_scope,
|
||||
:banner,
|
||||
:hide_network,
|
||||
:background
|
||||
])
|
||||
end
|
||||
|
||||
def mastodon_profile_update(info, params) do
|
||||
info
|
||||
|> cast(params, [
|
||||
:locked,
|
||||
:banner
|
||||
])
|
||||
end
|
||||
|
||||
def set_source_data(info, source_data) do
|
||||
params = %{source_data: source_data}
|
||||
|
||||
info
|
||||
|> cast(params, [:source_data])
|
||||
|> validate_required([:source_data])
|
||||
end
|
||||
|
||||
def admin_api_update(info, params) do
|
||||
info
|
||||
|> cast(params, [
|
||||
:is_moderator,
|
||||
:is_admin
|
||||
])
|
||||
end
|
||||
end
|
@ -1,135 +1,34 @@
|
||||
defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
||||
use Pleroma.Web, :controller
|
||||
require Logger
|
||||
alias Pleroma.{Web.MediaProxy, ReverseProxy}
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
@default_proxy_opts [max_body_length: 25 * 1_048_576]
|
||||
|
||||
@max_body_length 25 * 1_048_576
|
||||
|
||||
@cache_control %{
|
||||
default: "public, max-age=1209600",
|
||||
error: "public, must-revalidate, max-age=160"
|
||||
}
|
||||
|
||||
# Content-types that will not be returned as content-disposition attachments
|
||||
# Override with :media_proxy, :safe_content_types in the configuration
|
||||
@safe_content_types [
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"audio/mpeg",
|
||||
"audio/mp3",
|
||||
"video/webm",
|
||||
"video/mp4"
|
||||
]
|
||||
|
||||
def remote(conn, params = %{"sig" => sig, "url" => url}) do
|
||||
config = Application.get_env(:pleroma, :media_proxy, [])
|
||||
|
||||
with true <- Keyword.get(config, :enabled, false),
|
||||
{:ok, url} <- Pleroma.Web.MediaProxy.decode_url(sig, url),
|
||||
def remote(conn, params = %{"sig" => sig64, "url" => url64}) do
|
||||
with config <- Pleroma.Config.get([:media_proxy], []),
|
||||
true <- Keyword.get(config, :enabled, false),
|
||||
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
|
||||
filename <- Path.basename(URI.parse(url).path),
|
||||
true <-
|
||||
if(Map.get(params, "filename"),
|
||||
do: filename == Path.basename(conn.request_path),
|
||||
else: true
|
||||
),
|
||||
{:ok, content_type, body} <- proxy_request(url),
|
||||
safe_content_type <-
|
||||
Enum.member?(
|
||||
Keyword.get(config, :safe_content_types, @safe_content_types),
|
||||
content_type
|
||||
) do
|
||||
conn
|
||||
|> put_resp_content_type(content_type)
|
||||
|> set_cache_header(:default)
|
||||
|> put_resp_header(
|
||||
"content-security-policy",
|
||||
"default-src 'none'; style-src 'unsafe-inline'; media-src data:; img-src 'self' data:"
|
||||
)
|
||||
|> put_resp_header("x-xss-protection", "1; mode=block")
|
||||
|> put_resp_header("x-content-type-options", "nosniff")
|
||||
|> put_attachement_header(safe_content_type, filename)
|
||||
|> send_resp(200, body)
|
||||
:ok <- filename_matches(Map.has_key?(params, "filename"), conn.request_path, url) do
|
||||
ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
|
||||
else
|
||||
false ->
|
||||
send_error(conn, 404)
|
||||
send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404))
|
||||
|
||||
{:error, :invalid_signature} ->
|
||||
send_error(conn, 403)
|
||||
send_resp(conn, 403, Plug.Conn.Status.reason_phrase(403))
|
||||
|
||||
{:error, {:http, _, url}} ->
|
||||
redirect_or_error(conn, url, Keyword.get(config, :redirect_on_failure, true))
|
||||
{:wrong_filename, filename} ->
|
||||
redirect(conn, external: MediaProxy.build_url(sig64, url64, filename))
|
||||
end
|
||||
end
|
||||
|
||||
defp proxy_request(link) do
|
||||
headers = [
|
||||
{"user-agent",
|
||||
"Pleroma/MediaProxy; #{Pleroma.Web.base_url()} <#{
|
||||
Application.get_env(:pleroma, :instance)[:email]
|
||||
}>"}
|
||||
]
|
||||
|
||||
options =
|
||||
@httpoison.process_request_options([:insecure, {:follow_redirect, true}]) ++
|
||||
[{:pool, :default}]
|
||||
|
||||
with {:ok, 200, headers, client} <- :hackney.request(:get, link, headers, "", options),
|
||||
headers = Enum.into(headers, Map.new()),
|
||||
{:ok, body} <- proxy_request_body(client),
|
||||
content_type <- proxy_request_content_type(headers, body) do
|
||||
{:ok, content_type, body}
|
||||
else
|
||||
{:ok, status, _, _} ->
|
||||
Logger.warn("MediaProxy: request failed, status #{status}, link: #{link}")
|
||||
{:error, {:http, :bad_status, link}}
|
||||
def filename_matches(has_filename, path, url) do
|
||||
filename = MediaProxy.filename(url)
|
||||
|
||||
{:error, error} ->
|
||||
Logger.warn("MediaProxy: request failed, error #{inspect(error)}, link: #{link}")
|
||||
{:error, {:http, error, link}}
|
||||
cond do
|
||||
has_filename && filename && Path.basename(path) != filename -> {:wrong_filename, filename}
|
||||
true -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
defp set_cache_header(conn, key) do
|
||||
Plug.Conn.put_resp_header(conn, "cache-control", @cache_control[key])
|
||||
end
|
||||
|
||||
defp redirect_or_error(conn, url, true), do: redirect(conn, external: url)
|
||||
defp redirect_or_error(conn, url, _), do: send_error(conn, 502, "Media proxy error: " <> url)
|
||||
|
||||
defp send_error(conn, code, body \\ "") do
|
||||
conn
|
||||
|> set_cache_header(:error)
|
||||
|> send_resp(code, body)
|
||||
end
|
||||
|
||||
defp proxy_request_body(client), do: proxy_request_body(client, <<>>)
|
||||
|
||||
defp proxy_request_body(client, body) when byte_size(body) < @max_body_length do
|
||||
case :hackney.stream_body(client) do
|
||||
{:ok, data} -> proxy_request_body(client, <<body::binary, data::binary>>)
|
||||
:done -> {:ok, body}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp proxy_request_body(client, _) do
|
||||
:hackney.close(client)
|
||||
{:error, :body_too_large}
|
||||
end
|
||||
|
||||
# TODO: the body is passed here as well because some hosts do not provide a content-type.
|
||||
# At some point we may want to use magic numbers to discover the content-type and reply a proper one.
|
||||
defp proxy_request_content_type(headers, _body) do
|
||||
headers["Content-Type"] || headers["content-type"] || "application/octet-stream"
|
||||
end
|
||||
|
||||
defp put_attachement_header(conn, true, _), do: conn
|
||||
|
||||
defp put_attachement_header(conn, false, filename) do
|
||||
put_resp_header(conn, "content-disposition", "attachment; filename='#{filename}'")
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddUUIDExtension do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
execute("create extension if not exists \"uuid-ossp\"")
|
||||
end
|
||||
end
|
@ -0,0 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddUUIDsToUserInfo do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
execute("update users set info = jsonb_set(info, '{\"id\"}', to_jsonb(uuid_generate_v4()))")
|
||||
end
|
||||
end
|
@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.0808aeafc6252b3050ea95b17dcaff1a.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.34667c2817916147413f.js></script><script type=text/javascript src=/static/js/vendor.32c621c7157f34c20923.js></script><script type=text/javascript src=/static/js/app.065638d22ade92dea420.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.0808aeafc6252b3050ea95b17dcaff1a.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.18df0da570d88ba76ec5.js></script><script type=text/javascript src=/static/js/vendor.0e895ca116d5ba12f2b6.js></script><script type=text/javascript src=/static/js/app.3f7c9aaedc6b87fa9653.js></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,2 @@
|
||||
!function(e){function t(a){if(r[a])return r[a].exports;var n=r[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,i=[];s<c.length;s++)l=c[s],n[l]&&i.push.apply(i,n[l]),n[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(a&&a(c,o);i.length;)i.shift().call(null,t);if(o[0])return r[0]=0,t(0)};var r={},n={0:0};t.e=function(e,a){if(0===n[e])return a.call(null,t);if(void 0!==n[e])n[e].push(a);else{n[e]=[a];var r=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.src=t.p+"static/js/"+e+"."+{1:"0e895ca116d5ba12f2b6",2:"3f7c9aaedc6b87fa9653"}[e]+".js",r.appendChild(c)}},t.m=e,t.c=r,t.p="/"}([]);
|
||||
//# sourceMappingURL=manifest.18df0da570d88ba76ec5.js.map
|
File diff suppressed because one or more lines are too long
@ -1,2 +0,0 @@
|
||||
!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var r=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,i=[];s<c.length;s++)l=c[s],a[l]&&i.push.apply(i,a[l]),a[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(r&&r(c,o);i.length;)i.shift().call(null,t);if(o[0])return n[0]=0,t(0)};var n={},a={0:0};t.e=function(e,r){if(0===a[e])return r.call(null,t);if(void 0!==a[e])a[e].push(r);else{a[e]=[r];var n=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.src=t.p+"static/js/"+e+"."+{1:"32c621c7157f34c20923",2:"065638d22ade92dea420"}[e]+".js",n.appendChild(c)}},t.m=e,t.c=n,t.p="/"}([]);
|
||||
//# sourceMappingURL=manifest.34667c2817916147413f.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><hm:Host xmlns:hm="http://host-meta.net/xrd/1.0">framatube.org</hm:Host><Link rel="lrdd" template="http://framatube.org/main/xrd?uri={uri}"><Title>Resource Descriptor</Title></Link></XRD>
|
@ -0,0 +1,10 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?><XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'
|
||||
xmlns:hm='http://host-meta.net/xrd/1.0'>
|
||||
|
||||
<hm:Host>gerzilla.de</hm:Host>
|
||||
|
||||
<Link rel='lrdd' type="application/xrd+xml" template='https://gerzilla.de/xrd/?uri={uri}' />
|
||||
<Link rel="http://oexchange.org/spec/0.8/rel/resident-target" type="application/xrd+xml"
|
||||
href="https://gerzilla.de/oexchange/xrd" />
|
||||
|
||||
</XRD>
|
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><hm:Host xmlns:hm="http://host-meta.net/xrd/1.0">gnusocial.de</hm:Host><Link rel="lrdd" template="http://gnusocial.de/main/xrd?uri={uri}"><Title>Resource Descriptor</Title></Link></XRD>
|
@ -0,0 +1,55 @@
|
||||
defmodule Pleroma.HTTPTest do
|
||||
use Pleroma.DataCase
|
||||
import Tesla.Mock
|
||||
|
||||
setup do
|
||||
mock(fn
|
||||
%{
|
||||
method: :get,
|
||||
url: "http://example.com/hello",
|
||||
headers: [{"content-type", "application/json"}]
|
||||
} ->
|
||||
json(%{"my" => "data"})
|
||||
|
||||
%{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"}
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "get/1" do
|
||||
test "returns successfully result" do
|
||||
assert Pleroma.HTTP.get("http://example.com/hello") == {
|
||||
:ok,
|
||||
%Tesla.Env{status: 200, body: "hello"}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "get/2 (with headers)" do
|
||||
test "returns successfully result for json content-type" do
|
||||
assert Pleroma.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 Pleroma.HTTP.post("http://example.com/world", "") == {
|
||||
:ok,
|
||||
%Tesla.Env{status: 200, body: "world"}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,56 @@
|
||||
defmodule Pleroma.Plugs.OAuthPlugTest do
|
||||
use Pleroma.Web.ConnCase, async: true
|
||||
|
||||
alias Pleroma.Plugs.OAuthPlug
|
||||
import Pleroma.Factory
|
||||
|
||||
@session_opts [
|
||||
store: :cookie,
|
||||
key: "_test",
|
||||
signing_salt: "cooldude"
|
||||
]
|
||||
|
||||
setup %{conn: conn} do
|
||||
user = insert(:user)
|
||||
{:ok, %{token: token}} = Pleroma.Web.OAuth.Token.create_token(insert(:oauth_app), user)
|
||||
%{user: user, token: token, conn: conn}
|
||||
end
|
||||
|
||||
test "with valid token(uppercase), it assigns the user", %{conn: conn} = opts do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("authorization", "BEARER #{opts[:token]}")
|
||||
|> OAuthPlug.call(%{})
|
||||
|
||||
assert conn.assigns[:user] == opts[:user]
|
||||
end
|
||||
|
||||
test "with valid token(downcase), it assigns the user", %{conn: conn} = opts do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("authorization", "bearer #{opts[:token]}")
|
||||
|> OAuthPlug.call(%{})
|
||||
|
||||
assert conn.assigns[:user] == opts[:user]
|
||||
end
|
||||
|
||||
test "with invalid token, it not assigns the user", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("authorization", "bearer TTTTT")
|
||||
|> OAuthPlug.call(%{})
|
||||
|
||||
refute conn.assigns[:user]
|
||||
end
|
||||
|
||||
test "when token is missed but token in session, it assigns the user", %{conn: conn} = opts do
|
||||
conn =
|
||||
conn
|
||||
|> Plug.Session.call(Plug.Session.init(@session_opts))
|
||||
|> fetch_session()
|
||||
|> put_session(:oauth_token, opts[:token])
|
||||
|> OAuthPlug.call(%{})
|
||||
|
||||
assert conn.assigns[:user] == opts[:user]
|
||||
end
|
||||
end
|
@ -0,0 +1,675 @@
|
||||
defmodule HttpRequestMock do
|
||||
require Logger
|
||||
|
||||
def request(
|
||||
%Tesla.Env{
|
||||
url: url,
|
||||
method: method,
|
||||
headers: headers,
|
||||
query: query,
|
||||
body: body
|
||||
} = _env
|
||||
) do
|
||||
with {:ok, res} <- apply(__MODULE__, method, [url, query, body, headers]) do
|
||||
res
|
||||
else
|
||||
{_, r} = error ->
|
||||
# Logger.warn(r)
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
# GET Requests
|
||||
#
|
||||
def get(url, query \\ [], body \\ [], headers \\ [])
|
||||
|
||||
def get("https://osada.macgirvin.com/channel/mike", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!("test/fixtures/httpoison_mock/https___osada.macgirvin.com_channel_mike.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mike@osada.macgirvin.com.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/29191",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://pawoo.net/users/pekorino.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://pawoo.net/.well-known/webfinger?resource=acct:https://pawoo.net/users/pekorino",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.stopwatchingus-heidelberg.de/api/statuses/user_timeline/18330.atom",
|
||||
_,
|
||||
_,
|
||||
_
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/atarifrosch_feed.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.stopwatchingus-heidelberg.de/.well-known/webfinger?resource=acct:https://social.stopwatchingus-heidelberg.de/user/18330",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/atarifrosch_webfinger.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mamot.fr/users/Skruyb.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___mamot.fr_users_Skruyb.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://mamot.fr/.well-known/webfinger?resource=acct:https://mamot.fr/users/Skruyb",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/skruyb@mamot.fr.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger?resource=nonexistant@social.heldscal.la",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/nonexistant@social.heldscal.la.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://squeet.me/xrd/?uri=lain@squeet.me", _, _,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mst3k.interlinked.me/users/luciferMysticus", _, _,
|
||||
Accept: "application/activity+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/lucifermysticus.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://prismo.news/@mxb", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___prismo.news__mxb.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://hubzilla.example.org/channel/kaniini", _, _,
|
||||
Accept: "application/activity+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/kaniini@hubzilla.example.org.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://niu.moe/users/rye", _, _, Accept: "application/activity+json") do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/rye.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://mastodon.example.org/users/admin/statuses/100787282858396771", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/http___mastodon.example.org_users_admin_status_1234.json"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://puckipedia.com/", _, _, Accept: "application/activity+json") do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/puckipedia.com.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://peertube.moe/accounts/7even", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/7even.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/peertube.moe-vid.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://baptiste.gelez.xyz/@/BaptisteGelez", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://mastodon.example.org/users/admin", _, _, Accept: "application/activity+json") do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/admin@mastdon.example.org.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://mastodon.example.org/@admin/99541947525187367", _, _,
|
||||
Accept: "application/activity+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/mastodon-note-object.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/notice/7369654", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/7369654.html")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mstdn.io/users/mayuutann", _, _, Accept: "application/activity+json") do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mayumayu.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mstdn.io/users/mayuutann/statuses/99568293732299394", _, _,
|
||||
Accept: "application/activity+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mayumayupost.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://pleroma.soykaf.com/users/lain/feed.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get(url, _, _, Accept: "application/xrd+xml,application/jrd+json")
|
||||
when url in [
|
||||
"https://pleroma.soykaf.com/.well-known/webfinger?resource=acct:https://pleroma.soykaf.com/users/lain",
|
||||
"https://pleroma.soykaf.com/.well-known/webfinger?resource=https://pleroma.soykaf.com/users/lain"
|
||||
] do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/api/statuses/user_timeline/1.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/1",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/notice/2827873", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!("test/fixtures/httpoison_mock/https___shitposter.club_notice_2827873.html")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/api/statuses/show/2827873.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://testing.pleroma.lol/objects/b319022a-4946-44c5-9de9-34801f95507b", _, _, _) do
|
||||
{:ok, %Tesla.Env{status: 200}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/api/statuses/user_timeline/5381.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/spc_5381.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/5381",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/spc_5381_xrd.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://shitposter.club/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/shitposter.club_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/api/statuses/show/7369654.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/7369654.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/notice/4027863", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/7369654.html")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://social.sakamoto.gq/users/eal/feed.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/sakamoto_eal_feed.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://social.sakamoto.gq/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/social.sakamoto.gq_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.sakamoto.gq/.well-known/webfinger?resource=https://social.sakamoto.gq/users/eal",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/eal_sakamoto.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056", _, _,
|
||||
Accept: "application/atom+xml"
|
||||
) do
|
||||
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/sakamoto.atom")}}
|
||||
end
|
||||
|
||||
def get("http://mastodon.social/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mastodon.social_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/lambadalambda",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://gs.example.org/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/gs.example.org_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"http://gs.example.org/.well-known/webfinger?resource=http://gs.example.org:4040/index.php/user/1",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://gs.example.org/index.php/api/statuses/user_timeline/1.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://social.heldscal.la/api/statuses/user_timeline/29191.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://squeet.me/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/squeet.me_host_meta")}}
|
||||
end
|
||||
|
||||
def get("https://squeet.me/xrd?uri=lain@squeet.me", _, _,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger?resource=shp@social.heldscal.la",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/shp@social.heldscal.la.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://framatube.org/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/framatube.org_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://framatube.org/main/xrd?uri=framasoft@framatube.org", _, _,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
headers: [{"content-type", "application/json"}],
|
||||
body: File.read!("test/fixtures/httpoison_mock/framasoft@framatube.org.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://gnusocial.de/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/gnusocial.de_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://gnusocial.de/main/xrd?uri=winterdienst@gnusocial.de", _, _,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/winterdienst_webfinger.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://status.alpicola.com/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/status.alpicola.com_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://macgirvin.com/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/macgirvin.com_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://gerzilla.de/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/gerzilla.de_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://gerzilla.de/xrd/?uri=kaniini@gerzilla.de", _, _,
|
||||
Accept: "application/xrd+xml,application/jrd+json"
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
headers: [{"content-type", "application/json"}],
|
||||
body: File.read!("test/fixtures/httpoison_mock/kaniini@gerzilla.de.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://social.heldscal.la/api/statuses/user_timeline/23211.atom", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/23211",
|
||||
_,
|
||||
_,
|
||||
_
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://social.heldscal.la/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/social.heldscal.la_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://social.heldscal.la/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/social.heldscal.la_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mastodon.social/users/lambadalambda.atom", _, _, _) do
|
||||
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.atom")}}
|
||||
end
|
||||
|
||||
def get("https://social.heldscal.la/user/23211", _, _, Accept: "application/activity+json") do
|
||||
{:ok, Tesla.Mock.json(%{"id" => "https://social.heldscal.la/user/23211"}, status: 200)}
|
||||
end
|
||||
|
||||
def get(url, query, body, headers) do
|
||||
{:error,
|
||||
"Not implemented the mock response for get #{inspect(url)}, #{query}, #{inspect(body)}, #{
|
||||
inspect(headers)
|
||||
}"}
|
||||
end
|
||||
|
||||
# POST Requests
|
||||
#
|
||||
|
||||
def post(url, query \\ [], body \\ [], headers \\ [])
|
||||
|
||||
def post("http://example.org/needs_refresh", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: ""
|
||||
}}
|
||||
end
|
||||
|
||||
def post(url, _query, _body, _headers) do
|
||||
{:error, "Not implemented the mock response for post #{inspect(url)}"}
|
||||
end
|
||||
end
|
@ -1,881 +0,0 @@
|
||||
defmodule HTTPoisonMock do
|
||||
alias HTTPoison.Response
|
||||
|
||||
def get(url, body \\ [], headers \\ [])
|
||||
|
||||
def get("https://prismo.news/@mxb", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___prismo.news__mxb.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://osada.macgirvin.com/channel/mike", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!("test/fixtures/httpoison_mock/https___osada.macgirvin.com_channel_mike.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
|
||||
_,
|
||||
_
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mike@osada.macgirvin.com.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://info.pleroma.site/activity.json", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://info.pleroma.site/activity2.json", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity2.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://info.pleroma.site/activity3.json", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity3.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://info.pleroma.site/activity4.json", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity4.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://info.pleroma.site/actor.json", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___info.pleroma.site_actor.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://puckipedia.com/", [Accept: "application/activity+json"], _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/puckipedia.com.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://gerzilla.de/.well-known/webfinger?resource=acct:kaniini@gerzilla.de",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/kaniini@gerzilla.de.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://framatube.org/.well-known/webfinger?resource=acct:framasoft@framatube.org",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/framasoft@framatube.org.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://gnusocial.de/.well-known/webfinger?resource=acct:winterdienst@gnusocial.de",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/winterdienst_webfinger.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "nonexistant@social.heldscal.la"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 500,
|
||||
body: File.read!("test/fixtures/httpoison_mock/nonexistant@social.heldscal.la.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger?resource=shp@social.heldscal.la",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/shp@social.heldscal.la.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "shp@social.heldscal.la"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/shp@social.heldscal.la.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "https://social.heldscal.la/user/23211"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/23211",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "https://social.heldscal.la/user/29191"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/29191",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://mastodon.social/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "https://mastodon.social/users/lambadalambda"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/lambadalambda",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://shitposter.club/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "https://shitposter.club/user/1"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/1",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/5381",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/spc_5381_xrd.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"http://gs.example.org/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "http://gs.example.org:4040/index.php/user/1"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"http://gs.example.org/.well-known/webfinger?resource=http://gs.example.org:4040/index.php/user/1",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.stopwatchingus-heidelberg.de/.well-known/webfinger?resource=https://social.stopwatchingus-heidelberg.de/user/18330",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/atarifrosch_webfinger.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://pleroma.soykaf.com/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "https://pleroma.soykaf.com/users/lain"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://pleroma.soykaf.com/.well-known/webfinger?resource=https://pleroma.soykaf.com/users/lain",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://social.heldscal.la/api/statuses/user_timeline/29191.atom", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/api/statuses/user_timeline/5381.atom", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/spc_5381.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://social.heldscal.la/api/statuses/user_timeline/23211.atom", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mastodon.social/users/lambadalambda.atom", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.atom"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.stopwatchingus-heidelberg.de/api/statuses/user_timeline/18330.atom",
|
||||
_body,
|
||||
_headers
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/atarifrosch_feed.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://pleroma.soykaf.com/users/lain/feed.atom", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://social.sakamoto.gq/users/eal/feed.atom", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/sakamoto_eal_feed.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://gs.example.org/index.php/api/statuses/user_timeline/1.atom", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/notice/2827873", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!("test/fixtures/httpoison_mock/https___shitposter.club_notice_2827873.html")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/api/statuses/show/2827873.atom", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/api/statuses/user_timeline/1.atom", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def post(
|
||||
"https://social.heldscal.la/main/push/hub",
|
||||
{:form, _data},
|
||||
"Content-type": "application/x-www-form-urlencoded"
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 202
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://mastodon.example.org/users/admin/statuses/100787282858396771", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!(
|
||||
"test/fixtures/httpoison_mock/http___mastodon.example.org_users_admin_status_1234.json"
|
||||
)
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://pawoo.net/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "https://pawoo.net/users/pekorino"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://pawoo.net/.well-known/webfinger?resource=https://pawoo.net/users/pekorino",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://pawoo.net/users/pekorino.atom", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://mamot.fr/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "https://mamot.fr/users/Skruyb"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/skruyb@mamot.fr.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://mamot.fr/.well-known/webfinger?resource=https://mamot.fr/users/Skruyb",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/skruyb@mamot.fr.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.sakamoto.gq/.well-known/webfinger",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
params: [resource: "https://social.sakamoto.gq/users/eal"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/eal_sakamoto.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.sakamoto.gq/.well-known/webfinger?resource=https://social.sakamoto.gq/users/eal",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/eal_sakamoto.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://pleroma.soykaf.com/.well-known/webfinger?resource=https://pleroma.soykaf.com/users/shp",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/shp@pleroma.soykaf.com.webfigner")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://squeet.me/xrd/?uri=lain@squeet.me",
|
||||
[Accept: "application/xrd+xml,application/jrd+json"],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mamot.fr/users/Skruyb.atom", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___mamot.fr_users_Skruyb.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056",
|
||||
[Accept: "application/atom+xml"],
|
||||
_
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/sakamoto.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://pleroma.soykaf.com/users/shp/feed.atom", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/shp@pleroma.soykaf.com.feed")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://social.heldscal.la/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/social.heldscal.la_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://status.alpicola.com/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/status.alpicola.com_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://macgirvin.com/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/macgirvin.com_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://mastodon.social/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mastodon.social_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://shitposter.club/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/shitposter.club_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://pleroma.soykaf.com/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/pleroma.soykaf.com_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://social.sakamoto.gq/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/social.sakamoto.gq_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://gs.example.org/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/gs.example.org_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://pawoo.net/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/pawoo.net_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://mamot.fr/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mamot.fr_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://mastodon.xyz/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mastodon.xyz_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://social.wxcafe.net/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/social.wxcafe.net_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://squeet.me/.well-known/host-meta", [], follow_redirect: true) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/squeet.me_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"http://social.stopwatchingus-heidelberg.de/.well-known/host-meta",
|
||||
[],
|
||||
follow_redirect: true
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body:
|
||||
File.read!("test/fixtures/httpoison_mock/social.stopwatchingus-heidelberg.de_host_meta")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("http://mastodon.example.org/users/admin", [Accept: "application/activity+json"], _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/admin@mastdon.example.org.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://hubzilla.example.org/channel/kaniini",
|
||||
[Accept: "application/activity+json"],
|
||||
_
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/kaniini@hubzilla.example.org.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://masto.quad.moe/users/_HellPie", [Accept: "application/activity+json"], _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/hellpie.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://niu.moe/users/rye", [Accept: "application/activity+json"], _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/rye.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://n1u.moe/users/rye", [Accept: "application/activity+json"], _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/rye.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://mst3k.interlinked.me/users/luciferMysticus",
|
||||
[Accept: "application/activity+json"],
|
||||
_
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/lucifermysticus.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mstdn.io/users/mayuutann", [Accept: "application/activity+json"], _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mayumayu.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"http://mastodon.example.org/@admin/99541947525187367",
|
||||
[Accept: "application/activity+json"],
|
||||
_
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/mastodon-note-object.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://mstdn.io/users/mayuutann/statuses/99568293732299394",
|
||||
[Accept: "application/activity+json"],
|
||||
_
|
||||
) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/mayumayupost.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/notice/7369654", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/7369654.html")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://shitposter.club/api/statuses/show/7369654.atom", _body, _headers) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/7369654.atom")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://baptiste.gelez.xyz/@/BaptisteGelez", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/peertube.moe-vid.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://peertube.moe/accounts/7even", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/7even.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(url, body, headers) do
|
||||
{:error,
|
||||
"Not implemented the mock response for get #{inspect(url)}, #{inspect(body)}, #{
|
||||
inspect(headers)
|
||||
}"}
|
||||
end
|
||||
|
||||
def post(url, _body, _headers) do
|
||||
{:error, "Not implemented the mock response for post #{inspect(url)}"}
|
||||
end
|
||||
|
||||
def post(url, _body, _headers, _options) do
|
||||
{:error, "Not implemented the mock response for post #{inspect(url)}"}
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue