aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-core/fetch.lua
blob: c1f01dc2b1f825c688084027d928a2afbfe03828 (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local a = require "mason-core.async"
local async_uv = require "mason-core.async.uv"
local log = require "mason-core.log"
local platform = require "mason-core.platform"
local powershell = require "mason-core.installer.managers.powershell"
local spawn = require "mason-core.spawn"
local version = require "mason.version"

local USER_AGENT = ("mason.nvim %s (+https://github.com/mason-org/mason.nvim)"):format(version.VERSION)

local TIMEOUT_SECONDS = 30

---@alias FetchMethod
---| '"GET"'
---| '"POST"'
---| '"PUT"'
---| '"PATCH"'
---| '"DELETE"'

---@alias FetchOpts {out_file: string?, method: FetchMethod?, headers: table<string, string>?, data: string?}

---@async
---@param url string The url to fetch.
---@param opts FetchOpts?
---@return Result # Result<string>
local function fetch(url, opts)
    opts = opts or {}
    if not opts.headers then
        opts.headers = {}
    end
    if not opts.method then
        opts.method = "GET"
    end
    opts.headers["User-Agent"] = USER_AGENT
    log.fmt_debug("Fetching URL %s", url)

    local platform_specific = Result.failure

    if platform.is.win then
        local header_entries = _.join(
            "; ",
            _.map(function(pair)
                return ("%q=%q"):format(pair[1], pair[2])
            end, _.to_pairs(opts.headers))
        )
        local headers = ("-Headers @{%s}"):format(header_entries)
        if opts.out_file then
            platform_specific = function()
                return powershell.command(
                    ([[iwr %s -TimeoutSec %s -UseBasicParsing -Method %q -Uri %q %s -OutFile %q;]]):format(
                        headers,
                        TIMEOUT_SECONDS,
                        opts.method,
                        url,
                        opts.data and ("-Body %s"):format(opts.data) or "",
                        opts.out_file
                    )
                )
            end
        else
            platform_specific = function()
                return powershell.command(
                    ([[Write-Output (iwr %s -TimeoutSec %s -Method %q -UseBasicParsing %s -Uri %q).Content;]]):format(
                        headers,
                        TIMEOUT_SECONDS,
                        opts.method,
                        opts.data and ("-Body %s"):format(opts.data) or "",
                        url
                    )
                )
            end
        end
    end

    local function wget()
        local headers =
            _.sort_by(_.identity, _.map(_.compose(_.format "--header=%s", _.join ": "), _.to_pairs(opts.headers)))
        return spawn.wget {
            headers,
            "-nv",
            "-o",
            "/dev/null",
            "-O",
            opts.out_file or "-",
            ("--timeout=%s"):format(TIMEOUT_SECONDS),
            ("--method=%s"):format(opts.method),
            opts.data and {
                ("--body-data=%s"):format(opts.data) or vim.NIL,
            } or vim.NIL,
            url,
        }
    end

    local function curl()
        local headers = _.sort_by(
            _.nth(2),
            _.map(
                _.compose(function(header)
                    return { "-H", header }
                end, _.join ": "),
                _.to_pairs(opts.headers)
            )
        )
        return spawn.curl {
            headers,
            "-fsSL",
            {
                "-X",
                opts.method,
            },
            opts.data and { "-d", "@-" } or vim.NIL,
            opts.out_file and { "-o", opts.out_file } or vim.NIL,
            { "--connect-timeout", TIMEOUT_SECONDS },
            url,
            on_spawn = a.scope(function(_, stdio)
                local stdin = stdio[1]
                if opts.data then
                    log.trace("Writing stdin to curl", opts.data)
                    async_uv.write(stdin, opts.data)
                end
                async_uv.shutdown(stdin)
                async_uv.close(stdin)
            end),
        }
    end

    return curl():or_else(wget):or_else(platform_specific):map(function(result)
        if opts.out_file then
            return result
        else
            return result.stdout
        end
    end)
end

return fetch