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