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