1#ifndef ENGINE_HTTP_H
2#define ENGINE_HTTP_H
3
4#include "kernel.h"
5
6#include <base/hash_ctxt.h>
7#include <base/types.h>
8
9#include <atomic>
10#include <chrono>
11#include <condition_variable>
12#include <cstdint>
13#include <memory>
14#include <mutex>
15#include <optional>
16
17typedef struct _json_value json_value;
18class IStorage;
19
20enum class EHttpState
21{
22 ERROR = -1,
23 QUEUED,
24 RUNNING,
25 DONE,
26 ABORTED,
27};
28
29enum class HTTPLOG
30{
31 NONE,
32 FAILURE,
33 ALL,
34};
35
36enum class IPRESOLVE
37{
38 WHATEVER,
39 V4,
40 V6,
41};
42
43class CTimeout
44{
45public:
46 long m_ConnectTimeoutMs;
47 long m_TimeoutMs;
48 long m_LowSpeedLimit;
49 long m_LowSpeedTime;
50};
51
52class IHttpRequest
53{
54 friend class IHttp;
55
56public:
57 IHttpRequest(const char *pUrl);
58 virtual ~IHttpRequest();
59
60 void Timeout(CTimeout Timeout) { m_Timeout = Timeout; }
61 // Skip the download if the local file is newer or as new as the remote file.
62 void MaxResponseSize(int64_t MaxResponseSize) { m_MaxResponseSize = MaxResponseSize; }
63 void LogProgress(HTTPLOG LogProgress) { m_LogProgress = LogProgress; }
64 void SkipByFileTime(bool SkipByFileTime) { m_SkipByFileTime = SkipByFileTime; }
65 void IpResolve(IPRESOLVE IpResolve) { m_IpResolve = IpResolve; }
66 void FailOnErrorStatus(bool FailOnErrorStatus) { m_FailOnErrorStatus = FailOnErrorStatus; }
67 // Download to memory only. Get the result via `Result*`.
68 void WriteToMemory();
69 // Download to filesystem and memory.
70 void WriteToFileAndMemory(IStorage *pStorage, const char *pDest, int StorageType);
71 // Download to the filesystem only.
72 void WriteToFile(IStorage *pStorage, const char *pDest, int StorageType);
73 // Don't place the file in the specified location until
74 // `OnValidation(true)` has been called.
75 void ValidateBeforeOverwrite(bool ValidateBeforeOverwrite) { m_ValidateBeforeOverwrite = ValidateBeforeOverwrite; }
76 void ExpectSha256(const SHA256_DIGEST &Sha256) { m_ExpectedSha256 = Sha256; }
77
78 void Head();
79 void Post(const unsigned char *pData, size_t DataLength);
80 void PostJson(const char *pJson);
81
82 virtual void Header(const char *pNameColonValue) = 0;
83 void HeaderString(const char *pName, const char *pValue);
84 void HeaderInt(const char *pName, int Value);
85
86 const char *Dest() const;
87 double Current() const { return m_Current.load(m: std::memory_order_relaxed); }
88 double Size() const { return m_Size.load(m: std::memory_order_relaxed); }
89 int Progress() const { return m_Progress.load(m: std::memory_order_relaxed); }
90 EHttpState State() const { return m_State; }
91 bool Done() const
92 {
93 EHttpState CurrentState = State();
94 return CurrentState != EHttpState::QUEUED && CurrentState != EHttpState::RUNNING;
95 }
96 virtual void Abort() { m_Abort = true; }
97 bool IsAbortRequested() const { return m_Abort; }
98 void Wait();
99
100 /**
101 * Callback functions for handling progress of an HTTP request.
102 *
103 * @remark These callbacks may be called from a separate HTTP thread.
104 * Make sure the implementation is thread-safe and **do not stall the thread!**
105 */
106 class IProgressCallback
107 {
108 public:
109 virtual ~IProgressCallback() = default;
110 virtual void OnProgress() = 0;
111 virtual void OnCompletion(EHttpState State) = 0;
112 };
113 void SetProgressCallback(IProgressCallback *pCallback) { m_pProgressCallback = pCallback; }
114
115 // If `ValidateBeforeOverwrite` is set, this needs to be called after
116 // validating that the downloaded file has the correct format.
117 //
118 // If called with `true`, it'll place the downloaded file at the final
119 // destination, if called with `false`, it'll instead delete the
120 // temporary downloaded file.
121 void OnValidation(bool Success);
122
123 void Result(unsigned char **ppResult, size_t *pResultLength) const;
124 json_value *ResultJson() const;
125 const SHA256_DIGEST &ResultSha256() const;
126
127 int StatusCode() const;
128 std::optional<int64_t> ResultAgeSeconds() const;
129 std::optional<int64_t> ResultLastModified() const;
130
131protected:
132 static const char *const USER_AGENT_STRING;
133 enum class REQUEST
134 {
135 GET,
136 HEAD,
137 POST,
138 POST_JSON,
139 };
140 static const char *GetRequestType(REQUEST Type);
141
142 // Request
143 char m_aUrl[256] = "";
144 REQUEST m_Type = REQUEST::GET;
145 unsigned char *m_pBody = nullptr;
146 size_t m_BodyLength = 0;
147
148 // Settings
149 CTimeout m_Timeout = CTimeout{.m_ConnectTimeoutMs: 0, .m_TimeoutMs: 0, .m_LowSpeedLimit: 0, .m_LowSpeedTime: 0};
150 int64_t m_MaxResponseSize = -1;
151 HTTPLOG m_LogProgress = HTTPLOG::ALL;
152 bool m_SkipByFileTime = true;
153 IPRESOLVE m_IpResolve = IPRESOLVE::WHATEVER;
154 bool m_FailOnErrorStatus = true;
155 bool m_ValidateBeforeOverwrite = false;
156 std::optional<SHA256_DIGEST> m_ExpectedSha256 = std::nullopt;
157 int64_t m_IfModifiedSince = -1;
158
159 // Result
160 std::optional<SHA256_DIGEST> m_ActualSha256 = std::nullopt;
161 SHA256_CTX m_ActualSha256Ctx;
162 uint64_t m_ResponseLength = 0;
163 int m_StatusCode = 0;
164 std::optional<int64_t> m_ResultDate = std::nullopt;
165 std::optional<int64_t> m_ResultLastModified = std::nullopt;
166
167 bool m_WriteToMemory = true;
168 bool m_WriteToFile = false;
169
170 // If `m_WriteToMemory` is true.
171 size_t m_BufferSize = 0;
172 unsigned char *m_pBuffer = nullptr;
173
174 // If `m_WriteToFile` is true.
175 IOHANDLE m_File = nullptr;
176 char m_aDestAbsoluteTmp[IO_MAX_PATH_LENGTH] = "";
177 char m_aDestAbsolute[IO_MAX_PATH_LENGTH] = "";
178 char m_aDest[IO_MAX_PATH_LENGTH] = "";
179
180 // Progress
181 std::atomic<double> m_Size = 0.0;
182 std::atomic<double> m_Current = 0.0;
183 std::atomic<int> m_Progress = 0;
184 std::atomic<EHttpState> m_State = EHttpState::QUEUED;
185 std::atomic<bool> m_Abort = false;
186 IProgressCallback *m_pProgressCallback = nullptr;
187
188 std::mutex m_WaitMutex;
189 std::condition_variable m_WaitCondition;
190
191 bool ShouldSkipRequest();
192 // Abort the request with an error if `BeforeInit()` returns false.
193 bool BeforeInit();
194
195 // Abort the request if `OnData()` returns something other than
196 // `DataSize`.
197 size_t OnData(const char *pData, size_t DataSize);
198 void OnCompletionInternal(EHttpState State);
199};
200
201std::unique_ptr<IHttpRequest> CreateHttpRequest(const char *pUrl);
202
203inline std::unique_ptr<IHttpRequest> HttpHead(const char *pUrl)
204{
205 std::unique_ptr<IHttpRequest> pResult = CreateHttpRequest(pUrl);
206 pResult->Head();
207 return pResult;
208}
209
210inline std::unique_ptr<IHttpRequest> HttpGet(const char *pUrl)
211{
212 return CreateHttpRequest(pUrl);
213}
214
215inline std::unique_ptr<IHttpRequest> HttpGetFile(const char *pUrl, IStorage *pStorage, const char *pOutputFile, int StorageType)
216{
217 std::unique_ptr<IHttpRequest> pResult = HttpGet(pUrl);
218 pResult->WriteToFile(pStorage, pDest: pOutputFile, StorageType);
219 pResult->Timeout(Timeout: CTimeout{.m_ConnectTimeoutMs: 4000, .m_TimeoutMs: 0, .m_LowSpeedLimit: 500, .m_LowSpeedTime: 5});
220 return pResult;
221}
222
223inline std::unique_ptr<IHttpRequest> HttpGetBoth(const char *pUrl, IStorage *pStorage, const char *pOutputFile, int StorageType)
224{
225 std::unique_ptr<IHttpRequest> pResult = HttpGet(pUrl);
226 pResult->WriteToFileAndMemory(pStorage, pDest: pOutputFile, StorageType);
227 pResult->Timeout(Timeout: CTimeout{.m_ConnectTimeoutMs: 4000, .m_TimeoutMs: 0, .m_LowSpeedLimit: 500, .m_LowSpeedTime: 5});
228 return pResult;
229}
230
231inline std::unique_ptr<IHttpRequest> HttpPost(const char *pUrl, const unsigned char *pData, size_t DataLength)
232{
233 std::unique_ptr<IHttpRequest> pResult = CreateHttpRequest(pUrl);
234 pResult->Post(pData, DataLength);
235 pResult->Timeout(Timeout: CTimeout{.m_ConnectTimeoutMs: 4000, .m_TimeoutMs: 15000, .m_LowSpeedLimit: 500, .m_LowSpeedTime: 5});
236 return pResult;
237}
238
239inline std::unique_ptr<IHttpRequest> HttpPostJson(const char *pUrl, const char *pJson)
240{
241 std::unique_ptr<IHttpRequest> pResult = CreateHttpRequest(pUrl);
242 pResult->PostJson(pJson);
243 pResult->Timeout(Timeout: CTimeout{.m_ConnectTimeoutMs: 4000, .m_TimeoutMs: 15000, .m_LowSpeedLimit: 500, .m_LowSpeedTime: 5});
244 return pResult;
245}
246
247void EscapeUrl(char *pBuf, size_t Size, const char *pStr);
248
249template<size_t Size>
250void EscapeUrl(char (&aBuf)[Size], const char *pStr)
251{
252 EscapeUrl(aBuf, Size, pStr);
253}
254
255class IHttp : public IInterface
256{
257 MACRO_INTERFACE("http")
258
259public:
260 virtual void Run(std::shared_ptr<IHttpRequest> pRequest) = 0;
261
262 virtual bool HasIpresolveBug() const = 0;
263};
264
265class IEngineHttp : public IHttp
266{
267 MACRO_INTERFACE("enginehttp")
268
269public:
270 virtual bool Init(std::chrono::milliseconds ShutdownDelay) = 0;
271 void Shutdown() override = 0;
272};
273
274IEngineHttp *CreateEngineHttp();
275
276#endif
277