Page MenuHomePhorge

No OneTemporary

Size
16 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 3c77b0cdb..f489ba1f2 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -1,312 +1,315 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Object do
use Ecto.Schema
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Object.Fetcher
alias Pleroma.ObjectTombstone
alias Pleroma.Repo
alias Pleroma.User
import Ecto.Query
import Ecto.Changeset
require Logger
@type t() :: %__MODULE__{}
schema "objects" do
field(:data, :map)
timestamps()
end
def create(data) do
Object.change(%Object{}, %{data: data})
|> Repo.insert()
end
def change(struct, params \\ %{}) do
struct
|> cast(params, [:data])
|> validate_required([:data])
|> unique_constraint(:ap_id, name: :objects_unique_apid_index)
end
def get_by_id(nil), do: nil
def get_by_id(id), do: Repo.get(Object, id)
def get_by_id_and_maybe_refetch(id, opts \\ []) do
%{updated_at: updated_at} = object = get_by_id(id)
if opts[:interval] &&
NaiveDateTime.diff(NaiveDateTime.utc_now(), updated_at) > opts[:interval] do
case Fetcher.refetch_object(object) do
{:ok, %Object{} = object} ->
object
e ->
Logger.error("Couldn't refresh #{object.data["id"]}:\n#{inspect(e)}")
object
end
else
object
end
end
def get_by_ap_id(nil), do: nil
def get_by_ap_id(ap_id) do
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
end
@doc """
Get a single attachment by it's name and href
"""
@spec get_attachment_by_name_and_href(String.t(), String.t()) :: Object.t() | nil
def get_attachment_by_name_and_href(name, href) do
query =
from(o in Object,
where: fragment("(?)->>'name' = ?", o.data, ^name),
where: fragment("(?)->>'href' = ?", o.data, ^href)
)
Repo.one(query)
end
defp warn_on_no_object_preloaded(ap_id) do
"Object.normalize() called without preloaded object (#{inspect(ap_id)}). Consider preloading the object"
|> Logger.debug()
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
end
def normalize(_, fetch_remote \\ true, options \\ [])
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
# Use this whenever possible, especially when walking graphs in an O(N) loop!
def normalize(%Object{} = object, _, _), do: object
def normalize(%Activity{object: %Object{} = object}, _, _), do: object
# A hack for fake activities
def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _, _) do
%Object{id: "pleroma:fake_object_id", data: data}
end
# No preloaded object
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote, _) do
warn_on_no_object_preloaded(ap_id)
normalize(ap_id, fetch_remote)
end
# No preloaded object
def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote, _) do
warn_on_no_object_preloaded(ap_id)
normalize(ap_id, fetch_remote)
end
# Old way, try fetching the object through cache.
def normalize(%{"id" => ap_id}, fetch_remote, _), do: normalize(ap_id, fetch_remote)
def normalize(ap_id, false, _) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
def normalize(ap_id, true, options) when is_binary(ap_id) do
Fetcher.fetch_object_from_id!(ap_id, options)
end
def normalize(_, _, _), do: nil
# Owned objects can only be mutated by their owner
def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}),
do: actor == ap_id
# Legacy objects can be mutated by anybody
def authorize_mutation(%Object{}, %User{}), do: true
def get_cached_by_ap_id(ap_id) do
key = "object:#{ap_id}"
Cachex.fetch!(:object_cache, key, fn _ ->
object = get_by_ap_id(ap_id)
if object do
{:commit, object}
else
{:ignore, object}
end
end)
end
def context_mapping(context) do
Object.change(%Object{}, %{data: %{"id" => context}})
end
def make_tombstone(%Object{data: %{"id" => id, "type" => type}}, deleted \\ DateTime.utc_now()) do
%ObjectTombstone{
id: id,
formerType: type,
deleted: deleted
}
|> Map.from_struct()
end
def swap_object_with_tombstone(object) do
tombstone = make_tombstone(object)
object
|> Object.change(%{data: tombstone})
|> Repo.update()
end
def delete(%Object{data: %{"id" => id}} = object) do
with {:ok, _obj} = swap_object_with_tombstone(object),
:ok <- delete_attachments(object),
deleted_activity = Activity.delete_by_ap_id(id),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
{:ok, object, deleted_activity}
end
end
defp delete_attachments(%{data: %{"attachment" => [_ | _] = attachments}}) do
hrefs =
Enum.flat_map(attachments, fn attachment ->
Enum.map(attachment["url"], & &1["href"])
end)
+ uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
+
Enum.each(hrefs, fn href ->
href
|> Path.basename()
+ |> uploader.delete_file()
|> Pleroma.Uploaders.Local.delete_file()
end)
names = Enum.map(attachments, & &1["name"])
query =
from(o in Object,
where: fragment("(?)->'name' \\?| (?)", o.data, ^names),
where:
fragment(
"to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href'))::jsonb \\?| (?)",
o.data,
^hrefs
)
)
- Repo.delete_all(query) |> IO.inspect(label: :DELETEDOBJECTS)
+ Repo.delete_all(query)
:ok
end
defp delete_attachments(%{data: _data}) do
:ok
end
def prune(%Object{data: %{"id" => id}} = object) do
with {:ok, object} <- Repo.delete(object),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
{:ok, object}
end
end
def set_cache(%Object{data: %{"id" => ap_id}} = object) do
Cachex.put(:object_cache, "object:#{ap_id}", object)
{:ok, object}
end
def update_and_set_cache(changeset) do
with {:ok, object} <- Repo.update(changeset) do
set_cache(object)
end
end
def increase_replies_count(ap_id) do
Object
|> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id)))
|> update([o],
set: [
data:
fragment(
"""
safe_jsonb_set(?, '{repliesCount}',
(coalesce((?->>'repliesCount')::int, 0) + 1)::varchar::jsonb, true)
""",
o.data,
o.data
)
]
)
|> Repo.update_all([])
|> case do
{1, [object]} -> set_cache(object)
_ -> {:error, "Not found"}
end
end
def decrease_replies_count(ap_id) do
Object
|> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id)))
|> update([o],
set: [
data:
fragment(
"""
safe_jsonb_set(?, '{repliesCount}',
(greatest(0, (?->>'repliesCount')::int - 1))::varchar::jsonb, true)
""",
o.data,
o.data
)
]
)
|> Repo.update_all([])
|> case do
{1, [object]} -> set_cache(object)
_ -> {:error, "Not found"}
end
end
def increase_vote_count(ap_id, name) do
with %Object{} = object <- Object.normalize(ap_id),
"Question" <- object.data["type"] do
multiple = Map.has_key?(object.data, "anyOf")
options =
(object.data["anyOf"] || object.data["oneOf"] || [])
|> Enum.map(fn
%{"name" => ^name} = option ->
Kernel.update_in(option["replies"]["totalItems"], &(&1 + 1))
option ->
option
end)
data =
if multiple do
Map.put(object.data, "anyOf", options)
else
Map.put(object.data, "oneOf", options)
end
object
|> Object.change(%{data: data})
|> update_and_set_cache()
else
_ -> :noop
end
end
@doc "Updates data field of an object"
def update_data(%Object{data: data} = object, attrs \\ %{}) do
object
|> Object.change(%{data: Map.merge(data || %{}, attrs)})
|> Repo.update()
end
def local?(%Object{data: %{"id" => id}}) do
String.starts_with?(id, Pleroma.Web.base_url() <> "/")
end
end
diff --git a/lib/pleroma/uploaders/local.ex b/lib/pleroma/uploaders/local.ex
index 0a795a192..488360684 100644
--- a/lib/pleroma/uploaders/local.ex
+++ b/lib/pleroma/uploaders/local.ex
@@ -1,43 +1,49 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.Local do
@behaviour Pleroma.Uploaders.Uploader
+ @impl true
def get_file(_) do
{:ok, {:static_dir, upload_path()}}
end
+ @impl true
def put_file(upload) do
{local_path, file} =
case Enum.reverse(Path.split(upload.path)) do
[file] ->
{upload_path(), file}
[file | folders] ->
path = Path.join([upload_path()] ++ Enum.reverse(folders))
File.mkdir_p!(path)
{path, file}
end
result_file = Path.join(local_path, file)
if not File.exists?(result_file) do
File.cp!(upload.tempfile, result_file)
end
:ok
end
def upload_path do
Pleroma.Config.get!([__MODULE__, :uploads])
end
- @spec delete_file(String.t()) :: :ok | {:error, File.posix()}
+ @impl true
def delete_file(name) do
upload_path()
|> Path.join(name)
|> File.rm()
+ |> case do
+ :ok -> :ok
+ {:error, posix_error} -> {:error, to_string(posix_error)}
+ end
end
end
diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex
index c36f3d61d..ae970afc9 100644
--- a/lib/pleroma/uploaders/mdii.ex
+++ b/lib/pleroma/uploaders/mdii.ex
@@ -1,37 +1,44 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.MDII do
@moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure"
alias Pleroma.Config
alias Pleroma.HTTP
@behaviour Pleroma.Uploaders.Uploader
# MDII-hosted images are never passed through the MediaPlug; only local media.
# Delegate to Pleroma.Uploaders.Local
+ @impl true
def get_file(file) do
Pleroma.Uploaders.Local.get_file(file)
end
+ @impl true
def put_file(upload) do
cgi = Config.get([Pleroma.Uploaders.MDII, :cgi])
files = Config.get([Pleroma.Uploaders.MDII, :files])
{:ok, file_data} = File.read(upload.tempfile)
extension = String.split(upload.name, ".") |> List.last()
query = "#{cgi}?#{extension}"
with {:ok, %{status: 200, body: body}} <-
HTTP.post(query, file_data, [], adapter: [pool: :default]) do
remote_file_name = String.split(body) |> List.first()
public_url = "#{files}/#{remote_file_name}.#{extension}"
{:ok, {:url, public_url}}
else
_ -> Pleroma.Uploaders.Local.put_file(upload)
end
end
+
+ @impl true
+ def delete_file(_file) do
+ :ok
+ end
end
diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex
index 9876b6398..378bcc77c 100644
--- a/lib/pleroma/uploaders/s3.ex
+++ b/lib/pleroma/uploaders/s3.ex
@@ -1,76 +1,83 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.S3 do
@behaviour Pleroma.Uploaders.Uploader
require Logger
alias Pleroma.Config
# The file name is re-encoded with S3's constraints here to comply with previous
# links with less strict filenames
+ @impl true
def get_file(file) do
config = Config.get([__MODULE__])
bucket = Keyword.fetch!(config, :bucket)
bucket_with_namespace =
cond do
truncated_namespace = Keyword.get(config, :truncated_namespace) ->
truncated_namespace
namespace = Keyword.get(config, :bucket_namespace) ->
namespace <> ":" <> bucket
true ->
bucket
end
{:ok,
{:url,
Path.join([
Keyword.fetch!(config, :public_endpoint),
bucket_with_namespace,
strict_encode(URI.decode(file))
])}}
end
+ @impl true
def put_file(%Pleroma.Upload{} = upload) do
config = Config.get([__MODULE__])
bucket = Keyword.get(config, :bucket)
streaming = Keyword.get(config, :streaming_enabled)
s3_name = strict_encode(upload.path)
op =
if streaming do
upload.tempfile
|> ExAws.S3.Upload.stream_file()
|> ExAws.S3.upload(bucket, s3_name, [
{:acl, :public_read},
{:content_type, upload.content_type}
])
else
{:ok, file_data} = File.read(upload.tempfile)
ExAws.S3.put_object(bucket, s3_name, file_data, [
{:acl, :public_read},
{:content_type, upload.content_type}
])
end
case ExAws.request(op) do
{:ok, _} ->
{:ok, {:file, s3_name}}
error ->
Logger.error("#{__MODULE__}: #{inspect(error)}")
{:error, "S3 Upload failed"}
end
end
+ @impl true
+ def delete_file(_file) do
+ :ok
+ end
+
@regex Regex.compile!("[^0-9a-zA-Z!.*/'()_-]")
def strict_encode(name) do
String.replace(name, @regex, "-")
end
end
diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex
index c0b22c28a..d71e213d2 100644
--- a/lib/pleroma/uploaders/uploader.ex
+++ b/lib/pleroma/uploaders/uploader.ex
@@ -1,81 +1,82 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.Uploader do
import Pleroma.Web.Gettext
@moduledoc """
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` 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.
* `:wait_callback` will wait for an http post request at `/api/pleroma/upload_callback/:upload_path` and call the uploader's `http_callback/3` method.
"""
@type file_spec :: {:file | :url, String.t()}
@callback put_file(Pleroma.Upload.t()) ::
:ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
+ @callback delete_file(file :: String.t()) :: :ok | {:error, String.t()}
+
@callback http_callback(Plug.Conn.t(), Map.t()) ::
{:ok, Plug.Conn.t()}
| {:ok, Plug.Conn.t(), file_spec()}
| {:error, Plug.Conn.t(), String.t()}
@optional_callbacks http_callback: 2
@spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
-
def put_file(uploader, upload) do
case uploader.put_file(upload) do
:ok -> {:ok, {:file, upload.path}}
:wait_callback -> handle_callback(uploader, upload)
{:ok, _} = ok -> ok
{:error, _} = error -> error
end
end
defp handle_callback(uploader, upload) do
:global.register_name({__MODULE__, upload.path}, self())
receive do
{__MODULE__, pid, conn, params} ->
case uploader.http_callback(conn, params) do
{:ok, conn, ok} ->
send(pid, {__MODULE__, conn})
{:ok, ok}
{:error, conn, error} ->
send(pid, {__MODULE__, conn})
{:error, error}
end
after
callback_timeout() -> {:error, dgettext("errors", "Uploader callback timeout")}
end
end
defp callback_timeout do
case Mix.env() do
:test -> 1_000
_ -> 30_000
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Aug 28, 5:29 PM (16 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1736552
Default Alt Text
(16 KB)

Event Timeline