[KERNEL32]
[reactos.git] / base / applications / network / dwnl / dwnl.c
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 }