1/*
2 * LegacyClonk
3 * Copyright (c) 2013-2016, The OpenClonk Team and contributors
4 * Copyright (c) 2023, The LegacyClonk Team and contributors
5 *
6 * Distributed under the terms of the ISC license; see accompanying file
7 * "COPYING" for details.
8 *
9 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10 * See accompanying file "TRADEMARK" for details.
11 *
12 * To redistribute this file separately, substitute the full license texts
13 * for the above references.
14 */
15
16#pragma once
17
18#include "C4Coroutine.h"
19#include "C4CurlSystem.h"
20#include "C4Network2Address.h"
21#include "StdBuf.h"
22#include "StdSync.h"
23
24#include <atomic>
25#include <cstddef>
26#include <cstdint>
27#include <functional>
28#include <memory>
29#include <mutex>
30#include <shared_mutex>
31#include <span>
32#include <stdexcept>
33#include <string>
34#include <unordered_map>
35
36#if CURL_AT_LEAST_VERSION(8, 11, 0)
37using CURLS = void;
38#else
39using CURLS = struct Curl_share;
40#endif
41
42class C4HTTPClient
43{
44private:
45 struct CURLSDeleter
46 {
47 void operator()(CURLS *share);
48 };
49
50public:
51 using Headers = std::unordered_map<std::string_view, std::string_view>;
52 using ProgressCallback = std::function<bool(std::int64_t, std::int64_t)>;
53
54 class Exception : public C4CurlSystem::Exception
55 {
56 using C4CurlSystem::Exception::Exception;
57 };
58
59 class Uri
60 {
61 public:
62 enum class Part
63 {
64 Uri,
65 Scheme,
66 User,
67 Password,
68 Options,
69 Host,
70 Port,
71 Path,
72 Query,
73 Fragment,
74 ZoneId
75 };
76
77 class String
78 {
79 public:
80 String() = default;
81 ~String();
82
83 String(const String &) = delete;
84 String &operator=(const String &) = delete;
85 String(String &&other) noexcept;
86 String &operator=(String &&other) noexcept;
87
88 public:
89 char **operator &() noexcept { return &ptr; }
90 operator std::string() && { return ptr; }
91 operator StdStrBuf() && { return StdStrBuf{ptr}; }
92
93 private:
94 char *ptr{nullptr};
95 };
96
97 private:
98 struct CURLUDeleter
99 {
100 void operator()(CURLU *const uri);
101 };
102
103 public:
104 Uri(const std::string &serverAddress, std::uint16_t port = 0);
105
106 private:
107 Uri(std::unique_ptr<CURLU, CURLUDeleter> uri, std::uint16_t port = 0);
108
109 public:
110 [[nodiscard]] String GetPart(Part part) const;
111
112 auto get() const { return uri.get(); }
113
114 public:
115 [[nodiscard]] static Uri ParseOldStyle(const std::string &serverAddress, std::uint16_t port = 0);
116
117 private:
118 void SetPort(std::uint16_t port);
119
120 private:
121 std::unique_ptr<CURLU, CURLUDeleter> uri;
122 };
123
124 struct Request
125 {
126 C4HTTPClient::Uri Uri;
127 bool Binary{false};
128 std::span<const std::byte> Data{};
129 };
130
131 struct Result
132 {
133 StdBuf Buffer;
134 C4NetIO::addr_t ServerAddress;
135 };
136
137public:
138 C4HTTPClient();
139
140public:
141 C4Task::Hot<Result> GetAsync(Request request, ProgressCallback &&progressCallback = {}, Headers headers = {});
142 C4Task::Hot<Result> PostAsync(Request request, ProgressCallback &&progressCallback = {}, Headers headers = {});
143
144private:
145 C4Task::Hot<Result> RequestAsync(Request request, ProgressCallback progressCallback, Headers headers, bool post);
146 C4CurlSystem::EasyHandle PrepareRequest(const Request &request, Headers headers, bool post);
147
148 static std::size_t WriteFunction(char *ptr, std::size_t, std::size_t nmemb, void *userData);
149 static int XferInfoFunction(void *userData, std::int64_t downloadTotal, std::int64_t downloadNow, std::int64_t, std::int64_t);
150 static void LockFunction(CURL *, int data, int access, void *userData);
151 static void UnlockFunction(CURL *, int data, void *userData);
152
153private:
154 std::array<std::pair<std::shared_mutex, bool>, 9> shareMutexes{};
155 std::unique_ptr<CURLS, CURLSDeleter> shareHandle;
156};
157