1 #define COBJMACROS 2 #include <urlmon.h> 3 #include <wininet.h> 4 #include <wchar.h> 5 #include <strsafe.h> 6 #include <conutils.h> 7 8 #include "resource.h" 9 10 /* FIXME: add correct definitions to urlmon.idl */ 11 #ifdef UNICODE 12 #define URLDownloadToFile URLDownloadToFileW 13 #else 14 #define URLDownloadToFile URLDownloadToFileA 15 #endif 16 17 #define DWNL_E_LASTERROR 0 18 #define DWNL_E_NEEDTARGETFILENAME -1 19 #define DWNL_E_UNSUPPORTEDSCHEME -2 20 21 typedef struct 22 { 23 const IBindStatusCallbackVtbl* lpIBindStatusCallbackVtbl; 24 LONG ref; 25 WCHAR szHostName[INTERNET_MAX_HOST_NAME_LENGTH + 1]; 26 WCHAR szMimeType[128]; 27 UINT64 Size; 28 UINT64 Progress; 29 UINT bResolving : 1; 30 UINT bConnecting : 1; 31 UINT bSendingReq : 1; 32 UINT bBeginTransfer : 1; 33 } CBindStatusCallback; 34 35 #define impl_to_interface(impl,iface) (struct iface *)(&(impl)->lp##iface##Vtbl) 36 #define interface_to_impl(instance,iface) ((CBindStatusCallback*)((ULONG_PTR)instance - FIELD_OFFSET(CBindStatusCallback,lp##iface##Vtbl))) 37 38 static void 39 CBindStatusCallback_Destroy(CBindStatusCallback *This) 40 { 41 return; 42 } 43 44 static void 45 write_status(LPCWSTR lpFmt, ...) 46 { 47 va_list args; 48 WCHAR szTxt[128]; 49 CONSOLE_SCREEN_BUFFER_INFO csbi; 50 51 va_start(args, lpFmt); 52 StringCbVPrintf(szTxt, sizeof(szTxt), lpFmt, args); 53 va_end(args); 54 55 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) 56 { 57 ConPrintf(StdOut, L"\r%*.*s", -(csbi.dwSize.X - 1), csbi.dwSize.X - 1, szTxt); 58 } 59 else 60 { 61 ConPuts(StdOut, szTxt); 62 } 63 } 64 65 static void 66 CBindStatusCallback_UpdateProgress(CBindStatusCallback *This) 67 { 68 WCHAR szMessage[MAX_PATH]; 69 70 /* FIXME: better output */ 71 if (This->Size != 0) 72 { 73 UINT Percentage; 74 75 Percentage = (UINT)((This->Progress * 100) / This->Size); 76 if (Percentage > 99) 77 Percentage = 99; 78 79 LoadStringW(NULL, IDS_BYTES_DOWNLOADED_FULL, szMessage, ARRAYSIZE(szMessage)); 80 81 write_status(szMessage, Percentage, This->Progress); 82 } 83 else 84 { 85 LoadStringW(NULL, IDS_BYTES_DOWNLOADED, szMessage, ARRAYSIZE(szMessage)); 86 87 /* Unknown size */ 88 write_status(szMessage, This->Progress); 89 } 90 } 91 92 static ULONG STDMETHODCALLTYPE 93 CBindStatusCallback_AddRef(IBindStatusCallback *iface) 94 { 95 CBindStatusCallback *This = interface_to_impl(iface, IBindStatusCallback); 96 ULONG ret; 97 98 ret = InterlockedIncrement((PLONG)&This->ref); 99 return ret; 100 } 101 102 static ULONG STDMETHODCALLTYPE 103 CBindStatusCallback_Release(IBindStatusCallback *iface) 104 { 105 CBindStatusCallback *This = interface_to_impl(iface, IBindStatusCallback); 106 ULONG ret; 107 108 ret = InterlockedDecrement((PLONG)&This->ref); 109 if (ret == 0) 110 { 111 CBindStatusCallback_Destroy(This); 112 113 HeapFree(GetProcessHeap(), 114 0, 115 This); 116 } 117 118 return ret; 119 } 120 121 static HRESULT STDMETHODCALLTYPE 122 CBindStatusCallback_QueryInterface(IBindStatusCallback *iface, 123 REFIID iid, 124 PVOID *pvObject) 125 { 126 CBindStatusCallback *This = interface_to_impl(iface, IBindStatusCallback); 127 128 *pvObject = NULL; 129 130 if (IsEqualIID(iid, 131 &IID_IBindStatusCallback) || 132 IsEqualIID(iid, 133 &IID_IUnknown)) 134 { 135 *pvObject = impl_to_interface(This, IBindStatusCallback); 136 } 137 else 138 return E_NOINTERFACE; 139 140 CBindStatusCallback_AddRef(iface); 141 return S_OK; 142 } 143 144 static HRESULT STDMETHODCALLTYPE 145 CBindStatusCallback_OnStartBinding(IBindStatusCallback *iface, 146 DWORD dwReserved, 147 IBinding* pib) 148 { 149 return E_NOTIMPL; 150 } 151 152 static HRESULT STDMETHODCALLTYPE 153 CBindStatusCallback_GetPriority(IBindStatusCallback *iface, 154 LONG* pnPriority) 155 { 156 return E_NOTIMPL; 157 } 158 159 static HRESULT STDMETHODCALLTYPE 160 CBindStatusCallback_OnLowResource(IBindStatusCallback *iface, 161 DWORD reserved) 162 { 163 return E_NOTIMPL; 164 } 165 166 static HRESULT STDMETHODCALLTYPE 167 CBindStatusCallback_OnProgress(IBindStatusCallback *iface, 168 ULONG ulProgress, 169 ULONG ulProgressMax, 170 ULONG ulStatusCode, 171 LPCWSTR szStatusText) 172 { 173 CBindStatusCallback *This = interface_to_impl(iface, IBindStatusCallback); 174 175 switch (ulStatusCode) 176 { 177 case BINDSTATUS_FINDINGRESOURCE: 178 if (!This->bResolving) 179 { 180 wcscpy(This->szHostName, szStatusText); 181 This->bResolving = TRUE; 182 183 ConResPrintf(StdOut, IDS_RESOLVING, This->szHostName); 184 } 185 break; 186 187 case BINDSTATUS_CONNECTING: 188 This->bConnecting = TRUE; 189 This->bSendingReq = FALSE; 190 This->bBeginTransfer = FALSE; 191 This->szMimeType[0] = L'\0'; 192 if (This->bResolving) 193 { 194 ConResPrintf(StdOut, IDS_DONE); 195 ConResPrintf(StdOut, IDS_CONNECTING_TO_FULL, This->szHostName, szStatusText); 196 } 197 else 198 { 199 ConResPrintf(StdOut, IDS_CONNECTING_TO, szStatusText); 200 } 201 break; 202 203 case BINDSTATUS_REDIRECTING: 204 This->bResolving = FALSE; 205 This->bConnecting = FALSE; 206 This->bSendingReq = FALSE; 207 This->bBeginTransfer = FALSE; 208 This->szMimeType[0] = L'\0'; 209 ConResPrintf(StdOut, IDS_REDIRECTING_TO, szStatusText); 210 break; 211 212 case BINDSTATUS_SENDINGREQUEST: 213 This->bBeginTransfer = FALSE; 214 This->szMimeType[0] = L'\0'; 215 if (This->bResolving || This->bConnecting) 216 ConResPrintf(StdOut, IDS_DONE); 217 218 if (!This->bSendingReq) 219 ConResPrintf(StdOut, IDS_SEND_REQUEST); 220 221 This->bSendingReq = TRUE; 222 break; 223 224 case BINDSTATUS_MIMETYPEAVAILABLE: 225 wcscpy(This->szMimeType, szStatusText); 226 break; 227 228 case BINDSTATUS_BEGINDOWNLOADDATA: 229 This->Progress = (UINT64)ulProgress; 230 This->Size = (UINT64)ulProgressMax; 231 232 if (This->bSendingReq) 233 ConResPrintf(StdOut, IDS_DONE); 234 235 if (!This->bBeginTransfer && This->Size != 0) 236 { 237 if (This->szMimeType[0] != L'\0') 238 ConResPrintf(StdOut, IDS_LENGTH_FULL, This->Size, This->szMimeType); 239 else 240 ConResPrintf(StdOut, IDS_LENGTH, This->Size); 241 } 242 243 ConPuts(StdOut, L"\n"); 244 245 This->bBeginTransfer = TRUE; 246 break; 247 248 case BINDSTATUS_ENDDOWNLOADDATA: 249 ConResPrintf(StdOut, IDS_FILE_SAVED); 250 break; 251 252 case BINDSTATUS_DOWNLOADINGDATA: 253 This->Progress = (UINT64)ulProgress; 254 This->Size = (UINT64)ulProgressMax; 255 256 CBindStatusCallback_UpdateProgress(This); 257 break; 258 } 259 260 return S_OK; 261 } 262 263 static HRESULT STDMETHODCALLTYPE 264 CBindStatusCallback_OnStopBinding(IBindStatusCallback *iface, 265 HRESULT hresult, 266 LPCWSTR szError) 267 { 268 return E_NOTIMPL; 269 } 270 271 static HRESULT STDMETHODCALLTYPE 272 CBindStatusCallback_GetBindInfo(IBindStatusCallback *iface, 273 DWORD* grfBINDF, 274 BINDINFO* pbindinfo) 275 { 276 return E_NOTIMPL; 277 } 278 279 static HRESULT STDMETHODCALLTYPE 280 CBindStatusCallback_OnDataAvailable(IBindStatusCallback *iface, 281 DWORD grfBSCF, 282 DWORD dwSize, 283 FORMATETC* pformatetc, 284 STGMEDIUM* pstgmed) 285 { 286 return E_NOTIMPL; 287 } 288 289 static HRESULT STDMETHODCALLTYPE 290 CBindStatusCallback_OnObjectAvailable(IBindStatusCallback *iface, 291 REFIID riid, 292 IUnknown* punk) 293 { 294 return E_NOTIMPL; 295 } 296 297 static const struct IBindStatusCallbackVtbl vtblIBindStatusCallback = 298 { 299 CBindStatusCallback_QueryInterface, 300 CBindStatusCallback_AddRef, 301 CBindStatusCallback_Release, 302 CBindStatusCallback_OnStartBinding, 303 CBindStatusCallback_GetPriority, 304 CBindStatusCallback_OnLowResource, 305 CBindStatusCallback_OnProgress, 306 CBindStatusCallback_OnStopBinding, 307 CBindStatusCallback_GetBindInfo, 308 CBindStatusCallback_OnDataAvailable, 309 CBindStatusCallback_OnObjectAvailable, 310 }; 311 312 static IBindStatusCallback * 313 CreateBindStatusCallback(void) 314 { 315 CBindStatusCallback *This; 316 317 This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); 318 if (This == NULL) 319 return NULL; 320 321 This->lpIBindStatusCallbackVtbl = &vtblIBindStatusCallback; 322 This->ref = 1; 323 324 return impl_to_interface(This, IBindStatusCallback); 325 } 326 327 328 // ToDo: Show status, get file name from webserver, better error reporting 329 330 static int 331 get_display_url(IN LPURL_COMPONENTS purl, 332 OUT LPWSTR szBuffer, 333 IN PDWORD pdwBufferSize) 334 { 335 URL_COMPONENTS urlc; 336 337 /* Hide the password */ 338 urlc = *purl; 339 urlc.lpszPassword = NULL; 340 urlc.dwPasswordLength = 0; 341 342 if (!InternetCreateUrl(&urlc, ICU_ESCAPE, szBuffer, pdwBufferSize)) 343 return DWNL_E_LASTERROR; 344 345 return 1; 346 } 347 348 static int 349 download_file(IN LPCWSTR pszUrl, 350 IN LPCWSTR pszFile OPTIONAL) 351 { 352 WCHAR szScheme[INTERNET_MAX_SCHEME_LENGTH + 1]; 353 WCHAR szHostName[INTERNET_MAX_HOST_NAME_LENGTH + 1]; 354 WCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH + 1]; 355 WCHAR szPassWord[INTERNET_MAX_PASSWORD_LENGTH + 1]; 356 WCHAR szUrlPath[INTERNET_MAX_PATH_LENGTH + 1]; 357 WCHAR szExtraInfo[INTERNET_MAX_PATH_LENGTH + 1]; 358 WCHAR szUrl[INTERNET_MAX_URL_LENGTH + 1]; 359 DWORD dwUrlLen; 360 LPTSTR pszFilePart; 361 URL_COMPONENTS urlc; 362 IBindStatusCallback *pbsc; 363 int iRet; 364 SYSTEMTIME sysTime; 365 366 if (pszFile != NULL && pszFile[0] == L'\0') 367 pszFile = NULL; 368 369 urlc.dwStructSize = sizeof(urlc); 370 urlc.lpszScheme = szScheme; 371 urlc.dwSchemeLength = sizeof(szScheme) / sizeof(szScheme[0]); 372 urlc.lpszHostName = szHostName; 373 urlc.dwHostNameLength = sizeof(szHostName) / sizeof(szHostName[0]); 374 urlc.lpszUserName = szUserName; 375 urlc.dwUserNameLength = sizeof(szUserName) / sizeof(szUserName[0]); 376 urlc.lpszPassword = szPassWord; 377 urlc.dwPasswordLength = sizeof(szPassWord) / sizeof(szPassWord[0]); 378 urlc.lpszUrlPath = szUrlPath; 379 urlc.dwUrlPathLength = sizeof(szUrlPath) / sizeof(szUrlPath[0]); 380 urlc.lpszExtraInfo = szExtraInfo; 381 urlc.dwExtraInfoLength = sizeof(szExtraInfo) / sizeof(szExtraInfo[0]); 382 if (!InternetCrackUrl(pszUrl, wcslen(pszUrl), ICU_ESCAPE, &urlc)) 383 return DWNL_E_LASTERROR; 384 385 if (urlc.nScheme != INTERNET_SCHEME_FTP && 386 urlc.nScheme != INTERNET_SCHEME_GOPHER && 387 urlc.nScheme != INTERNET_SCHEME_HTTP && 388 urlc.nScheme != INTERNET_SCHEME_HTTPS) 389 { 390 return DWNL_E_UNSUPPORTEDSCHEME; 391 } 392 393 if (urlc.nScheme == INTERNET_SCHEME_FTP && urlc.dwUserNameLength == 0 && urlc.dwPasswordLength == 0) 394 { 395 wcscpy(szUserName, L"anonymous"); 396 urlc.dwUserNameLength = wcslen(szUserName); 397 } 398 399 /* FIXME: Get file name from server */ 400 if (urlc.dwUrlPathLength == 0 && pszFile == NULL) 401 return DWNL_E_NEEDTARGETFILENAME; 402 403 pszFilePart = wcsrchr(szUrlPath, L'/'); 404 if (pszFilePart != NULL) 405 pszFilePart++; 406 407 if (pszFilePart == NULL && pszFile == NULL) 408 return DWNL_E_NEEDTARGETFILENAME; 409 410 if (pszFile == NULL) 411 pszFile = pszFilePart; 412 413 if (urlc.dwUserNameLength == 0) 414 urlc.lpszUserName = NULL; 415 416 if (urlc.dwPasswordLength == 0) 417 urlc.lpszPassword = NULL; 418 419 /* Generate the URL to be displayed (without a password) */ 420 dwUrlLen = sizeof(szUrl) / sizeof(szUrl[0]); 421 iRet = get_display_url(&urlc, szUrl, &dwUrlLen); 422 if (iRet <= 0) 423 return iRet; 424 425 GetLocalTime(&sysTime); 426 427 ConPrintf(StdOut, L"--%d-%02d-%02d %02d:%02d:%02d-- %s\n\t=> %s\n", 428 sysTime.wYear, sysTime.wMonth, sysTime.wDay, 429 sysTime.wHour, sysTime.wMinute, sysTime.wSecond, 430 szUrl, pszFile); 431 432 /* Generate the URL to download */ 433 dwUrlLen = sizeof(szUrl) / sizeof(szUrl[0]); 434 if (!InternetCreateUrl(&urlc, ICU_ESCAPE, szUrl, &dwUrlLen)) 435 return DWNL_E_LASTERROR; 436 437 pbsc = CreateBindStatusCallback(); 438 if (pbsc == NULL) 439 return DWNL_E_LASTERROR; 440 441 if(!SUCCEEDED(URLDownloadToFile(NULL, szUrl, pszFile, 0, pbsc))) 442 { 443 IBindStatusCallback_Release(pbsc); 444 return DWNL_E_LASTERROR; /* FIXME */ 445 } 446 447 IBindStatusCallback_Release(pbsc); 448 return 1; 449 } 450 451 static int 452 print_err(int iErr) 453 { 454 write_status(L""); 455 456 if (iErr == DWNL_E_LASTERROR) 457 { 458 if (GetLastError() == ERROR_SUCCESS) 459 { 460 /* File not found */ 461 ConResPrintf(StdErr, IDS_ERROR_DOWNLOAD); 462 } 463 else 464 { 465 /* Display last error code */ 466 ConResPrintf(StdErr, IDS_ERROR_CODE, GetLastError()); 467 } 468 } 469 else 470 { 471 switch (iErr) 472 { 473 case DWNL_E_NEEDTARGETFILENAME: 474 ConResPrintf(StdErr, IDS_ERROR_FILENAME); 475 break; 476 477 case DWNL_E_UNSUPPORTEDSCHEME: 478 ConResPrintf(StdErr, IDS_ERROR_PROTOCOL); 479 break; 480 } 481 } 482 483 return 1; 484 } 485 486 int wmain(int argc, WCHAR **argv) 487 { 488 int iErr, iRet = 0; 489 490 /* Initialize the Console Standard Streams */ 491 ConInitStdStreams(); 492 493 if(argc != 2 && argc != 3) 494 { 495 ConResPrintf(StdOut, IDS_USAGE); 496 return 2; 497 } 498 499 iErr = download_file(argv[1], argc == 3 ? argv[2] : NULL); 500 if (iErr <= 0) 501 iRet = print_err(iErr); 502 503 return iRet; 504 } 505