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