1 /*
2  * PROJECT:     ReactOS Applications Manager
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Async Internet operation using WinINet
5  * COPYRIGHT:   Copyright 2020 He Yang            (1160386205@qq.com)
6  */
7 
8 #include "rapps.h"
9 #include <wininet.h>
10 #include <atlbase.h>
11 #include "asyncinet.h"
12 
13 
14 BOOL AsyncInetIsCanceled(pASYNCINET AsyncInet);
15 BOOL AsyncInetAcquire(pASYNCINET AsyncInet);
16 VOID AsyncInetRelease(pASYNCINET AsyncInet);
17 int AsyncInetPerformCallback(pASYNCINET AsyncInet,
18     ASYNC_EVENT Event,
19     WPARAM wParam,
20     LPARAM lParam
21     );
22 VOID CALLBACK AsyncInetStatusCallback(
23     HINTERNET hInternet,
24     DWORD_PTR dwContext,
25     DWORD dwInternetStatus,
26     LPVOID lpvStatusInformation,
27     DWORD dwStatusInformationLength
28     );
29 VOID AsyncInetReadFileLoop(pASYNCINET AsyncInet);
30 BOOL AsyncInetCleanUp(pASYNCINET AsyncInet);
31 VOID AsyncInetFree(pASYNCINET AsyncInet);
32 
33 pASYNCINET AsyncInetDownload(LPCWSTR lpszAgent,
34     DWORD   dwAccessType,
35     LPCWSTR lpszProxy,
36     LPCWSTR lpszProxyBypass,
37     LPCWSTR lpszUrl,
38     BOOL bAllowCache,
39     ASYNCINET_CALLBACK Callback,
40     VOID* Extension
41     ) // allocate memory for AsyncInet and start a download task
42 {
43     pASYNCINET AsyncInet = NULL;
44     BOOL bSuccess = FALSE;
45     DWORD InetOpenUrlFlag = 0;
46     INTERNET_STATUS_CALLBACK OldCallbackFunc;
47 
48     AsyncInet = (pASYNCINET)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(ASYNCINET));
49     if (!AsyncInet)
50     {
51         OutputDebugStringA("At File: " __FILE__ " HeapAlloc returned 0\n");
52         return 0;
53     }
54 
55     AsyncInet->bCancelled = FALSE;
56 
57     AsyncInet->hInternet = NULL;
58     AsyncInet->hInetFile = NULL;
59 
60     AsyncInet->Callback = Callback;
61     AsyncInet->Extension = Extension;
62 
63     AsyncInet->hEventHandleCreated = CreateEvent(NULL, FALSE, FALSE, NULL);
64 
65     InitializeCriticalSection(&(AsyncInet->CriticalSection));
66     AsyncInet->ReferenceCnt = 1; // 1 for callee itself
67     AsyncInet->hEventHandleClose = CreateEvent(NULL, FALSE, FALSE, NULL);
68 
69     if (AsyncInet->hEventHandleCreated && AsyncInet->hEventHandleClose)
70     {
71         AsyncInet->hInternet = InternetOpenW(lpszAgent, dwAccessType, lpszProxy, lpszProxyBypass, INTERNET_FLAG_ASYNC);
72 
73         if (AsyncInet->hInternet)
74         {
75             OldCallbackFunc = InternetSetStatusCallbackW(AsyncInet->hInternet, AsyncInetStatusCallback);
76             if (OldCallbackFunc != INTERNET_INVALID_STATUS_CALLBACK)
77             {
78                 InetOpenUrlFlag |= INTERNET_FLAG_PASSIVE | INTERNET_FLAG_RESYNCHRONIZE;
79                 if (!bAllowCache)
80                 {
81                     InetOpenUrlFlag |= INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD;
82                 }
83 
84                 AsyncInet->hInetFile = InternetOpenUrlW(AsyncInet->hInternet, lpszUrl, 0, 0, InetOpenUrlFlag, (DWORD_PTR)AsyncInet);
85 
86                 if (AsyncInet->hInetFile)
87                 {
88                     // operate complete synchronously
89                     bSuccess = TRUE;
90                     AsyncInetReadFileLoop(AsyncInet);
91                 }
92                 else
93                 {
94                     if (GetLastError() == ERROR_IO_PENDING)
95                     {
96                         // everything fine. waiting for handle created
97                         switch (WaitForSingleObject(AsyncInet->hEventHandleCreated, INFINITE))
98                         {
99                         case WAIT_OBJECT_0:
100                             if (AsyncInet->hInetFile)
101                             {
102                                 bSuccess = TRUE;
103                             }
104                         }
105                     }
106                 }
107             }
108         }
109     }
110 
111     if (!bSuccess)
112     {
113         AsyncInetFree(AsyncInet);
114         AsyncInet = NULL;
115     }
116 
117     if (AsyncInet)
118     {
119         // add reference count for caller.
120         // the caller is responsible for call AsyncInetRelease when no longer using it.
121         AsyncInetAcquire(AsyncInet);
122     }
123     return AsyncInet;
124 }
125 
126 BOOL AsyncInetCancel(pASYNCINET AsyncInet) // mark as cancelled (this will send a cancel notificaion at last) and do graceful cleanup.
127 {
128     if (AsyncInet)
129     {
130         HINTERNET hInetFile;
131         EnterCriticalSection(&(AsyncInet->CriticalSection));
132         AsyncInet->bCancelled = TRUE;
133         hInetFile = AsyncInet->hInetFile;
134         AsyncInet->hInetFile = NULL;
135         LeaveCriticalSection(&(AsyncInet->CriticalSection));
136 
137         if (hInetFile)
138         {
139             InternetCloseHandle(hInetFile);
140             return TRUE;
141         }
142     }
143 
144     return FALSE;
145 }
146 
147 BOOL AsyncInetIsCanceled(pASYNCINET AsyncInet) // if returned TRUE, no operation should be exectued further
148 {
149     if (AsyncInet)
150     {
151         EnterCriticalSection(&(AsyncInet->CriticalSection));
152         if (AsyncInet->bCancelled)
153         {
154             LeaveCriticalSection(&(AsyncInet->CriticalSection));
155             return TRUE;
156         }
157         LeaveCriticalSection(&(AsyncInet->CriticalSection));
158     }
159     return FALSE;
160 }
161 
162 BOOL AsyncInetAcquire(pASYNCINET AsyncInet) // try to increase refcnt by 1. if returned FALSE, AsyncInet should not be used anymore
163 {
164     BOOL bResult = FALSE;
165     if (AsyncInet)
166     {
167         EnterCriticalSection(&(AsyncInet->CriticalSection));
168         ATLASSERT(AsyncInet->ReferenceCnt > 0);
169         if (!AsyncInetIsCanceled(AsyncInet))
170         {
171             AsyncInet->ReferenceCnt++;
172             bResult = TRUE;
173         }
174         // otherwise (AsyncInet->bCleanUp == TRUE)
175         // AsyncInetAcquire will return FALSE.
176         // In this case, any thread should no longer use this AsyncInet
177 
178         LeaveCriticalSection(&(AsyncInet->CriticalSection));
179     }
180 
181     return bResult;
182 }
183 
184 VOID AsyncInetRelease(pASYNCINET AsyncInet) // try to decrease refcnt by 1
185 {
186     BOOL bCleanUp = FALSE;
187     if (AsyncInet)
188     {
189         EnterCriticalSection(&(AsyncInet->CriticalSection));
190 
191         ATLASSERT(AsyncInet->ReferenceCnt);
192         AsyncInet->ReferenceCnt--;
193         if (AsyncInet->ReferenceCnt == 0)
194         {
195             bCleanUp = TRUE;
196         }
197 
198         LeaveCriticalSection(&(AsyncInet->CriticalSection));
199 
200         if (bCleanUp)
201         {
202             AsyncInetCleanUp(AsyncInet);
203         }
204     }
205 }
206 
207 int AsyncInetPerformCallback(pASYNCINET AsyncInet,
208     ASYNC_EVENT Event,
209     WPARAM wParam,
210     LPARAM lParam
211     )
212 {
213     if (AsyncInet && AsyncInet->Callback)
214     {
215         return AsyncInet->Callback(AsyncInet, Event, wParam, lParam, AsyncInet->Extension);
216     }
217     return 0;
218 }
219 
220 VOID CALLBACK AsyncInetStatusCallback(
221     HINTERNET hInternet,
222     DWORD_PTR dwContext,
223     DWORD dwInternetStatus,
224     LPVOID lpvStatusInformation,
225     DWORD dwStatusInformationLength
226     )
227 {
228     pASYNCINET AsyncInet = (pASYNCINET)dwContext;
229     switch (dwInternetStatus)
230     {
231     case INTERNET_STATUS_HANDLE_CREATED:
232     {
233         // retrieve handle created by InternetOpenUrlW
234         AsyncInet->hInetFile = *((LPHINTERNET)lpvStatusInformation);
235         SetEvent(AsyncInet->hEventHandleCreated);
236         break;
237     }
238     case INTERNET_STATUS_HANDLE_CLOSING:
239     {
240         if (AsyncInetIsCanceled(AsyncInet))
241         {
242             AsyncInetPerformCallback(AsyncInet, ASYNCINET_CANCELLED, 0, 0);
243         }
244         SetEvent(AsyncInet->hEventHandleClose);
245         break;
246     }
247     case INTERNET_STATUS_REQUEST_COMPLETE:
248     {
249         INTERNET_ASYNC_RESULT* AsyncResult = (INTERNET_ASYNC_RESULT*)lpvStatusInformation;
250 
251         if (!AsyncInet->hInetFile)
252         {
253             if (!AsyncInetIsCanceled(AsyncInet))
254             {
255                 // some error occurs during InternetOpenUrl
256                 // and INTERNET_STATUS_HANDLE_CREATED is skipped
257                 SetEvent(AsyncInet->hEventHandleCreated);
258                 break;
259             }
260         }
261 
262         switch (AsyncResult->dwError)
263         {
264         case ERROR_SUCCESS:
265         {
266             // there are two possibilities:
267             // 1: InternetOpenUrlW (async mode) complete. (this should be only for the first time)
268             // 2: InternetReadFile (async mode) complete.
269             // so I use bIsOpenUrlComplete field in ASYNCINET to indicate this.
270 
271             if (!(AsyncInet->bIsOpenUrlComplete)) // InternetOpenUrlW completed
272             {
273                 AsyncInet->bIsOpenUrlComplete = TRUE;
274             }
275             else // asynchronous InternetReadFile complete
276             {
277                 AsyncInetPerformCallback(AsyncInet, ASYNCINET_DATA, (WPARAM)(AsyncInet->ReadBuffer), (LPARAM)(AsyncInet->BytesRead));
278             }
279 
280             AsyncInetReadFileLoop(AsyncInet);
281         }
282         break;
283         case ERROR_INVALID_HANDLE:
284         case ERROR_INTERNET_OPERATION_CANCELLED:
285         case ERROR_CANCELLED:
286             if (AsyncInetIsCanceled(AsyncInet))
287             {
288                 AsyncInetRelease(AsyncInet);
289                 break;
290             }
291 
292             // fall down
293         default:
294             // something went wrong
295             if (AsyncInetIsCanceled(AsyncInet))
296             {
297                 // sending both ASYNCINET_ERROR and ASYNCINET_CANCELLED may lead to unpredictable behavior
298                 // TODO: log the error
299                 AsyncInetRelease(AsyncInet);
300             }
301             else
302             {
303                 AsyncInetPerformCallback(AsyncInet, ASYNCINET_ERROR, 0, (LPARAM)(AsyncResult->dwError));
304                 AsyncInetRelease(AsyncInet);
305             }
306             break;
307         }
308         break;
309     }
310     }
311     return;
312 }
313 
314 VOID AsyncInetReadFileLoop(pASYNCINET AsyncInet)
315 {
316     if ((!AsyncInet) || (!AsyncInet->hInetFile))
317     {
318         return;
319     }
320 
321     while (1)
322     {
323         if (AsyncInetIsCanceled(AsyncInet))
324         {
325             // abort now.
326             AsyncInetRelease(AsyncInet);
327             break;
328         }
329 
330         BOOL bRet = InternetReadFile(AsyncInet->hInetFile,
331             AsyncInet->ReadBuffer,
332             _countof(AsyncInet->ReadBuffer),
333             &(AsyncInet->BytesRead));
334 
335         if (bRet)
336         {
337             if (AsyncInet->BytesRead == 0)
338             {
339                 // everything read. now complete.
340                 AsyncInetPerformCallback(AsyncInet, ASYNCINET_COMPLETE, 0, 0);
341                 AsyncInetRelease(AsyncInet);
342                 break;
343             }
344             else
345             {
346                 // read completed immediately.
347                 AsyncInetPerformCallback(AsyncInet, ASYNCINET_DATA, (WPARAM)(AsyncInet->ReadBuffer), (LPARAM)(AsyncInet->BytesRead));
348             }
349         }
350         else
351         {
352             DWORD dwError;
353             if ((dwError = GetLastError()) == ERROR_IO_PENDING)
354             {
355                 // performing asynchronous IO, everything OK.
356                 break;
357             }
358             else
359             {
360                 if (dwError == ERROR_INVALID_HANDLE ||
361                     dwError == ERROR_INTERNET_OPERATION_CANCELLED ||
362                     dwError == ERROR_CANCELLED)
363                 {
364                     if (AsyncInetIsCanceled(AsyncInet))
365                     {
366                         // not an error. just normally cancelling
367                         AsyncInetRelease(AsyncInet);
368                         break;
369                     }
370                 }
371 
372                 if (!AsyncInetIsCanceled(AsyncInet)) // can not send both ASYNCINET_ERROR and ASYNCINET_CANCELLED
373                 {
374                     AsyncInetPerformCallback(AsyncInet, ASYNCINET_ERROR, 0, dwError);
375                 }
376                 else
377                 {
378                     // TODO: log the error
379                 }
380                 AsyncInetRelease(AsyncInet);
381                 break;
382             }
383         }
384     }
385     return;
386 }
387 
388 BOOL AsyncInetCleanUp(pASYNCINET AsyncInet) // close all handle and clean up
389 {
390     if (AsyncInet)
391     {
392         ATLASSERT(AsyncInet->ReferenceCnt == 0);
393         // close the handle, waiting for all pending request cancelled.
394 
395         if (AsyncInet->bCancelled) // already closed
396         {
397             AsyncInet->hInetFile = NULL;
398         }
399         else if (AsyncInet->hInetFile)
400         {
401             InternetCloseHandle(AsyncInet->hInetFile);
402             AsyncInet->hInetFile = NULL;
403         }
404 
405         // only cleanup when handle closed notification received
406         switch (WaitForSingleObject(AsyncInet->hEventHandleClose, INFINITE))
407         {
408         case WAIT_OBJECT_0:
409         {
410             AsyncInetFree(AsyncInet); // now safe to free the structure
411             return TRUE;
412         }
413         default:
414             ATLASSERT(FALSE);
415             AsyncInetFree(AsyncInet);
416             return FALSE;
417         }
418     }
419     else
420     {
421         return FALSE;
422     }
423 }
424 
425 VOID AsyncInetFree(pASYNCINET AsyncInet) // close all handles, free the memory occupied by AsyncInet
426 {
427     if (AsyncInet)
428     {
429         DeleteCriticalSection(&(AsyncInet->CriticalSection));
430 
431         if (AsyncInet->hEventHandleCreated)
432         {
433             CloseHandle(AsyncInet->hEventHandleCreated);
434             AsyncInet->hEventHandleCreated = NULL;
435         }
436         if (AsyncInet->hEventHandleClose)
437         {
438             CloseHandle(AsyncInet->hEventHandleClose);
439             AsyncInet->hEventHandleClose = NULL;
440         }
441         if (AsyncInet->hInternet)
442         {
443             InternetCloseHandle(AsyncInet->hInternet);
444             AsyncInet->hInternet = NULL;
445         }
446         if (AsyncInet->hInetFile)
447         {
448             InternetCloseHandle(AsyncInet->hInetFile);
449             AsyncInet->hInetFile = NULL;
450         }
451         HeapFree(GetProcessHeap(), 0, AsyncInet);
452     }
453 }
454