summaryrefslogtreecommitdiff
path: root/lib/polyjuice/client/endpoint/get_media_download.ex
blob: 7bd9e358a540df1dd907fd2bbcbd6478e5d5a2be (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# Copyright 2020 Hubert Chathi <hubert@uhoreg.ca>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

defmodule Polyjuice.Client.Endpoint.GetMediaDownload do
  @moduledoc """
  Download from the media repository.

  https://matrix.org/docs/spec/client_server/latest#get-matrix-media-r0-download-servername-mediaid
  https://matrix.org/docs/spec/client_server/latest#get-matrix-media-r0-download-servername-mediaid-filename
  """

  @type t :: %__MODULE__{
          url: String.t() | URI.t() | {String.t(), String.t()},
          allow_remote: boolean,
          filename: String.t() | nil
        }

  @enforce_keys [:url]
  defstruct [
    :url,
    :filename,
    allow_remote: true
  ]

  defimpl Polyjuice.Client.Endpoint.Proto do
    def http_spec(%{
          url: mxc_url,
          allow_remote: allow_remote,
          filename: filename
        }) do
      e = &URI.encode_www_form/1

      {server_name, media_id} =
        case mxc_url do
          _ when is_binary(mxc_url) ->
            %{
              scheme: "mxc",
              host: host,
              path: path
            } = URI.parse(mxc_url)

            {host, String.trim_leading(path, "/")}

          %URI{
            scheme: "mxc",
            host: host,
            path: path
          } ->
            {host, String.trim_leading(path, "/")}

          {_, _} ->
            mxc_url
        end

      filename_part = if is_binary(filename), do: "/" <> e.(filename), else: ""

      Polyjuice.Client.Endpoint.HttpSpec.get(
        :media_r0,
        "download/#{e.(server_name)}/#{e.(media_id)}#{filename_part}",
        query: if(!allow_remote, do: [allow_remote: "false"]),
        headers: [
          {"Accept", "*/*"}
        ],
        stream_response: true
      )
    end

    def transform_http_result(req, status_code, resp_headers, body) do
      if status_code == 200 do
        content_type =
          Polyjuice.Client.Endpoint.get_header(
            resp_headers,
            "content-type",
            "application/octet-stream"
          )

        filename =
          Enum.find_value(
            resp_headers,
            fn {name, value} ->
              if String.downcase(name, :ascii) == "content-disposition" do
                Regex.split(~r";\s*", value)
                |> Enum.find_value(fn directive ->
                  # FIXME: also handle "filename*"
                  with [d_name, quoted_name] <- String.split(directive, "=", parts: 2),
                       "filename" <- String.downcase(d_name) do
                    case Regex.run(~r/"(.*)"/, quoted_name) do
                      [_, name] -> Regex.replace(~r/\\(.)/, name, "\\1")
                      nil -> quoted_name
                    end
                    # do some basic sanitizing
                    |> (&Regex.replace(~r/\0/, &1, "")).()
                    |> Path.basename()
                    |> (&if(&1 == "", do: nil, else: &1)).()
                  else
                    _ ->
                      nil
                  end
                end)
              end
            end
          )

        {:ok, filename, content_type, body}
      else
        Polyjuice.Client.Endpoint.parse_response(req, status_code, resp_headers, Enum.join(body))
      end
    end
  end
end