[QMGR]
[reactos.git] / reactos / dll / win32 / qmgr / file.c
1 /*
2 * Queue Manager (BITS) File
3 *
4 * Copyright 2007, 2008 Google (Roy Shea, Dan Hipschman)
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #include "qmgr.h"
22
23 #include <urlmon.h>
24 #include <winhttp.h>
25
26 static inline BackgroundCopyFileImpl *impl_from_IBackgroundCopyFile2(
27 IBackgroundCopyFile2 *iface)
28 {
29 return CONTAINING_RECORD(iface, BackgroundCopyFileImpl, IBackgroundCopyFile2_iface);
30 }
31
32 static HRESULT WINAPI BackgroundCopyFile_QueryInterface(
33 IBackgroundCopyFile2 *iface,
34 REFIID riid,
35 void **obj)
36 {
37 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
38
39 TRACE("(%p)->(%s %p)\n", file, debugstr_guid(riid), obj);
40
41 if (IsEqualGUID(riid, &IID_IUnknown) ||
42 IsEqualGUID(riid, &IID_IBackgroundCopyFile) ||
43 IsEqualGUID(riid, &IID_IBackgroundCopyFile2))
44 {
45 *obj = iface;
46 }
47 else
48 {
49 *obj = NULL;
50 return E_NOINTERFACE;
51 }
52
53 IBackgroundCopyFile2_AddRef(iface);
54 return S_OK;
55 }
56
57 static ULONG WINAPI BackgroundCopyFile_AddRef(
58 IBackgroundCopyFile2 *iface)
59 {
60 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
61 ULONG ref = InterlockedIncrement(&file->ref);
62 TRACE("(%p)->(%d)\n", file, ref);
63 return ref;
64 }
65
66 static ULONG WINAPI BackgroundCopyFile_Release(
67 IBackgroundCopyFile2 *iface)
68 {
69 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
70 ULONG ref = InterlockedDecrement(&file->ref);
71
72 TRACE("(%p)->(%d)\n", file, ref);
73
74 if (ref == 0)
75 {
76 IBackgroundCopyJob3_Release(&file->owner->IBackgroundCopyJob3_iface);
77 HeapFree(GetProcessHeap(), 0, file->info.LocalName);
78 HeapFree(GetProcessHeap(), 0, file->info.RemoteName);
79 HeapFree(GetProcessHeap(), 0, file);
80 }
81
82 return ref;
83 }
84
85 /* Get the remote name of a background copy file */
86 static HRESULT WINAPI BackgroundCopyFile_GetRemoteName(
87 IBackgroundCopyFile2 *iface,
88 LPWSTR *pVal)
89 {
90 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
91
92 TRACE("(%p)->(%p)\n", file, pVal);
93
94 return return_strval(file->info.RemoteName, pVal);
95 }
96
97 static HRESULT WINAPI BackgroundCopyFile_GetLocalName(
98 IBackgroundCopyFile2 *iface,
99 LPWSTR *pVal)
100 {
101 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
102
103 TRACE("(%p)->(%p)\n", file, pVal);
104
105 return return_strval(file->info.LocalName, pVal);
106 }
107
108 static HRESULT WINAPI BackgroundCopyFile_GetProgress(
109 IBackgroundCopyFile2 *iface,
110 BG_FILE_PROGRESS *pVal)
111 {
112 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
113
114 TRACE("(%p)->(%p)\n", file, pVal);
115
116 EnterCriticalSection(&file->owner->cs);
117 *pVal = file->fileProgress;
118 LeaveCriticalSection(&file->owner->cs);
119
120 return S_OK;
121 }
122
123 static HRESULT WINAPI BackgroundCopyFile_GetFileRanges(
124 IBackgroundCopyFile2 *iface,
125 DWORD *RangeCount,
126 BG_FILE_RANGE **Ranges)
127 {
128 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
129 FIXME("(%p)->(%p %p)\n", file, RangeCount, Ranges);
130 return E_NOTIMPL;
131 }
132
133 static HRESULT WINAPI BackgroundCopyFile_SetRemoteName(
134 IBackgroundCopyFile2 *iface,
135 LPCWSTR Val)
136 {
137 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
138 FIXME("(%p)->(%s)\n", file, debugstr_w(Val));
139 return E_NOTIMPL;
140 }
141
142 static const IBackgroundCopyFile2Vtbl BackgroundCopyFile2Vtbl =
143 {
144 BackgroundCopyFile_QueryInterface,
145 BackgroundCopyFile_AddRef,
146 BackgroundCopyFile_Release,
147 BackgroundCopyFile_GetRemoteName,
148 BackgroundCopyFile_GetLocalName,
149 BackgroundCopyFile_GetProgress,
150 BackgroundCopyFile_GetFileRanges,
151 BackgroundCopyFile_SetRemoteName
152 };
153
154 HRESULT BackgroundCopyFileConstructor(BackgroundCopyJobImpl *owner,
155 LPCWSTR remoteName, LPCWSTR localName,
156 BackgroundCopyFileImpl **file)
157 {
158 BackgroundCopyFileImpl *This;
159
160 TRACE("(%s, %s, %p)\n", debugstr_w(remoteName), debugstr_w(localName), file);
161
162 This = HeapAlloc(GetProcessHeap(), 0, sizeof *This);
163 if (!This)
164 return E_OUTOFMEMORY;
165
166 This->info.RemoteName = strdupW(remoteName);
167 if (!This->info.RemoteName)
168 {
169 HeapFree(GetProcessHeap(), 0, This);
170 return E_OUTOFMEMORY;
171 }
172
173 This->info.LocalName = strdupW(localName);
174 if (!This->info.LocalName)
175 {
176 HeapFree(GetProcessHeap(), 0, This->info.RemoteName);
177 HeapFree(GetProcessHeap(), 0, This);
178 return E_OUTOFMEMORY;
179 }
180
181 This->IBackgroundCopyFile2_iface.lpVtbl = &BackgroundCopyFile2Vtbl;
182 This->ref = 1;
183
184 This->fileProgress.BytesTotal = BG_SIZE_UNKNOWN;
185 This->fileProgress.BytesTransferred = 0;
186 This->fileProgress.Completed = FALSE;
187 This->owner = owner;
188 This->read_size = 0;
189 This->tempFileName[0] = 0;
190 IBackgroundCopyJob3_AddRef(&owner->IBackgroundCopyJob3_iface);
191
192 *file = This;
193 return S_OK;
194 }
195
196 static HRESULT error_from_http_response(DWORD code)
197 {
198 switch (code)
199 {
200 case 200: return S_OK;
201 case 400: return BG_E_HTTP_ERROR_400;
202 case 401: return BG_E_HTTP_ERROR_401;
203 case 404: return BG_E_HTTP_ERROR_404;
204 case 407: return BG_E_HTTP_ERROR_407;
205 case 414: return BG_E_HTTP_ERROR_414;
206 case 501: return BG_E_HTTP_ERROR_501;
207 case 503: return BG_E_HTTP_ERROR_503;
208 case 504: return BG_E_HTTP_ERROR_504;
209 case 505: return BG_E_HTTP_ERROR_505;
210 default:
211 FIXME("unhandled response code %u\n", code);
212 return S_OK;
213 }
214 }
215
216 static void CALLBACK progress_callback_http(HINTERNET handle, DWORD_PTR context, DWORD status,
217 LPVOID buf, DWORD buflen)
218 {
219 BackgroundCopyFileImpl *file = (BackgroundCopyFileImpl *)context;
220 BackgroundCopyJobImpl *job = file->owner;
221
222 TRACE("%p, %p, %x, %p, %u\n", handle, file, status, buf, buflen);
223
224 switch (status)
225 {
226 case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
227 {
228 DWORD code, len, size;
229
230 size = sizeof(code);
231 if (WinHttpQueryHeaders(handle, WINHTTP_QUERY_STATUS_CODE|WINHTTP_QUERY_FLAG_NUMBER,
232 NULL, &code, &size, NULL))
233 {
234 if ((job->error.code = error_from_http_response(code)))
235 {
236 EnterCriticalSection(&job->cs);
237
238 job->error.context = BG_ERROR_CONTEXT_REMOTE_FILE;
239 if (job->error.file) IBackgroundCopyFile2_Release(job->error.file);
240 job->error.file = &file->IBackgroundCopyFile2_iface;
241 IBackgroundCopyFile2_AddRef(job->error.file);
242
243 LeaveCriticalSection(&job->cs);
244 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
245 }
246 else
247 {
248 EnterCriticalSection(&job->cs);
249
250 job->error.context = 0;
251 if (job->error.file)
252 {
253 IBackgroundCopyFile2_Release(job->error.file);
254 job->error.file = NULL;
255 }
256
257 LeaveCriticalSection(&job->cs);
258 }
259 }
260 size = sizeof(len);
261 if (WinHttpQueryHeaders(handle, WINHTTP_QUERY_CONTENT_LENGTH|WINHTTP_QUERY_FLAG_NUMBER,
262 NULL, &len, &size, NULL))
263 {
264 file->fileProgress.BytesTotal = len;
265 }
266 break;
267 }
268 case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
269 {
270 file->read_size = buflen;
271 break;
272 }
273 case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
274 {
275 WINHTTP_ASYNC_RESULT *result = (WINHTTP_ASYNC_RESULT *)buf;
276 job->error.code = result->dwError;
277 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
278 break;
279 }
280 default: break;
281 }
282
283 SetEvent(job->wait);
284 }
285
286 static DWORD wait_for_completion(BackgroundCopyJobImpl *job)
287 {
288 HANDLE handles[2] = {job->wait, job->cancel};
289 DWORD error = ERROR_SUCCESS;
290
291 switch (WaitForMultipleObjects(2, handles, FALSE, INFINITE))
292 {
293 case WAIT_OBJECT_0:
294 break;
295
296 case WAIT_OBJECT_0 + 1:
297 error = ERROR_CANCELLED;
298 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_CANCELLED);
299 break;
300
301 default:
302 error = GetLastError();
303 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
304 break;
305 }
306
307 return error;
308 }
309
310 static UINT target_from_index(UINT index)
311 {
312 switch (index)
313 {
314 case 0: return WINHTTP_AUTH_TARGET_SERVER;
315 case 1: return WINHTTP_AUTH_TARGET_PROXY;
316 default:
317 ERR("unhandled index %u\n", index);
318 break;
319 }
320 return 0;
321 }
322
323 static UINT scheme_from_index(UINT index)
324 {
325 switch (index)
326 {
327 case 0: return WINHTTP_AUTH_SCHEME_BASIC;
328 case 1: return WINHTTP_AUTH_SCHEME_NTLM;
329 case 2: return WINHTTP_AUTH_SCHEME_PASSPORT;
330 case 3: return WINHTTP_AUTH_SCHEME_DIGEST;
331 case 4: return WINHTTP_AUTH_SCHEME_NEGOTIATE;
332 default:
333 ERR("unhandled index %u\n", index);
334 break;
335 }
336 return 0;
337 }
338
339 static BOOL set_request_credentials(HINTERNET req, BackgroundCopyJobImpl *job)
340 {
341 UINT i, j;
342
343 for (i = 0; i < BG_AUTH_TARGET_PROXY; i++)
344 {
345 UINT target = target_from_index(i);
346 for (j = 0; j < BG_AUTH_SCHEME_PASSPORT; j++)
347 {
348 UINT scheme = scheme_from_index(j);
349 const WCHAR *username = job->http_options.creds[i][j].Credentials.Basic.UserName;
350 const WCHAR *password = job->http_options.creds[i][j].Credentials.Basic.Password;
351
352 if (!username) continue;
353 if (!WinHttpSetCredentials(req, target, scheme, username, password, NULL)) return FALSE;
354 }
355 }
356 return TRUE;
357 }
358
359 static BOOL transfer_file_http(BackgroundCopyFileImpl *file, URL_COMPONENTSW *uc,
360 const WCHAR *tmpfile)
361 {
362 BackgroundCopyJobImpl *job = file->owner;
363 HANDLE handle;
364 HINTERNET ses, con = NULL, req = NULL;
365 DWORD flags = (uc->nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0;
366 char buf[4096];
367 BOOL ret = FALSE;
368 DWORD written;
369
370 transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_CONNECTING);
371
372 if (!(ses = WinHttpOpen(NULL, 0, NULL, NULL, WINHTTP_FLAG_ASYNC))) return FALSE;
373 WinHttpSetStatusCallback(ses, progress_callback_http, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, 0);
374 if (!WinHttpSetOption(ses, WINHTTP_OPTION_CONTEXT_VALUE, &file, sizeof(file))) goto done;
375
376 if (!(con = WinHttpConnect(ses, uc->lpszHostName, uc->nPort, 0))) goto done;
377 if (!(req = WinHttpOpenRequest(con, NULL, uc->lpszUrlPath, NULL, NULL, NULL, flags))) goto done;
378 if (!set_request_credentials(req, job)) goto done;
379
380 if (!(WinHttpSendRequest(req, job->http_options.headers, ~0u, NULL, 0, 0, (DWORD_PTR)file))) goto done;
381 if (wait_for_completion(job) || job->error.code) goto done;
382
383 if (!(WinHttpReceiveResponse(req, NULL))) goto done;
384 if (wait_for_completion(job) || job->error.code) goto done;
385
386 transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_TRANSFERRING);
387
388 handle = CreateFileW(tmpfile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
389 if (handle == INVALID_HANDLE_VALUE) goto done;
390
391 for (;;)
392 {
393 file->read_size = 0;
394 if (!(ret = WinHttpReadData(req, buf, sizeof(buf), NULL))) break;
395 if (wait_for_completion(job) || job->error.code)
396 {
397 ret = FALSE;
398 break;
399 }
400 if (!file->read_size) break;
401 if (!(ret = WriteFile(handle, buf, file->read_size, &written, NULL))) break;
402
403 EnterCriticalSection(&job->cs);
404 file->fileProgress.BytesTransferred += file->read_size;
405 job->jobProgress.BytesTransferred += file->read_size;
406 LeaveCriticalSection(&job->cs);
407 }
408
409 CloseHandle(handle);
410
411 done:
412 WinHttpCloseHandle(req);
413 WinHttpCloseHandle(con);
414 WinHttpCloseHandle(ses);
415 if (!ret) DeleteFileW(tmpfile);
416
417 SetEvent(job->done);
418 return ret;
419 }
420
421 static DWORD CALLBACK progress_callback_local(LARGE_INTEGER totalSize, LARGE_INTEGER totalTransferred,
422 LARGE_INTEGER streamSize, LARGE_INTEGER streamTransferred,
423 DWORD streamNum, DWORD reason, HANDLE srcFile,
424 HANDLE dstFile, LPVOID obj)
425 {
426 BackgroundCopyFileImpl *file = obj;
427 BackgroundCopyJobImpl *job = file->owner;
428 ULONG64 diff;
429
430 EnterCriticalSection(&job->cs);
431 diff = (file->fileProgress.BytesTotal == BG_SIZE_UNKNOWN
432 ? totalTransferred.QuadPart
433 : totalTransferred.QuadPart - file->fileProgress.BytesTransferred);
434 file->fileProgress.BytesTotal = totalSize.QuadPart;
435 file->fileProgress.BytesTransferred = totalTransferred.QuadPart;
436 job->jobProgress.BytesTransferred += diff;
437 LeaveCriticalSection(&job->cs);
438
439 return (job->state == BG_JOB_STATE_TRANSFERRING
440 ? PROGRESS_CONTINUE
441 : PROGRESS_CANCEL);
442 }
443
444 static BOOL transfer_file_local(BackgroundCopyFileImpl *file, const WCHAR *tmpname)
445 {
446 static const WCHAR fileW[] = {'f','i','l','e',':','/','/',0};
447 BackgroundCopyJobImpl *job = file->owner;
448 const WCHAR *ptr;
449 BOOL ret;
450
451 transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSFERRING);
452
453 if (strlenW(file->info.RemoteName) > 7 && !memicmpW(file->info.RemoteName, fileW, 7))
454 ptr = file->info.RemoteName + 7;
455 else
456 ptr = file->info.RemoteName;
457
458 if (!(ret = CopyFileExW(ptr, tmpname, progress_callback_local, file, NULL, 0)))
459 {
460 WARN("Local file copy failed: error %u\n", GetLastError());
461 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
462 }
463
464 SetEvent(job->done);
465 return ret;
466 }
467
468 BOOL processFile(BackgroundCopyFileImpl *file, BackgroundCopyJobImpl *job)
469 {
470 static const WCHAR prefix[] = {'B','I','T', 0};
471 WCHAR tmpDir[MAX_PATH], tmpName[MAX_PATH];
472 WCHAR host[MAX_PATH], path[MAX_PATH];
473 URL_COMPONENTSW uc;
474 BOOL ret;
475
476 if (!GetTempPathW(MAX_PATH, tmpDir))
477 {
478 ERR("Couldn't create temp file name: %d\n", GetLastError());
479 /* Guessing on what state this should give us */
480 transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSIENT_ERROR);
481 return FALSE;
482 }
483
484 if (!GetTempFileNameW(tmpDir, prefix, 0, tmpName))
485 {
486 ERR("Couldn't create temp file: %d\n", GetLastError());
487 /* Guessing on what state this should give us */
488 transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSIENT_ERROR);
489 return FALSE;
490 }
491
492 EnterCriticalSection(&job->cs);
493 file->fileProgress.BytesTotal = BG_SIZE_UNKNOWN;
494 file->fileProgress.BytesTransferred = 0;
495 file->fileProgress.Completed = FALSE;
496 LeaveCriticalSection(&job->cs);
497
498 TRACE("Transferring: %s -> %s -> %s\n",
499 debugstr_w(file->info.RemoteName),
500 debugstr_w(tmpName),
501 debugstr_w(file->info.LocalName));
502
503 uc.dwStructSize = sizeof(uc);
504 uc.nScheme = 0;
505 uc.lpszScheme = NULL;
506 uc.dwSchemeLength = 0;
507 uc.lpszUserName = NULL;
508 uc.dwUserNameLength = 0;
509 uc.lpszPassword = NULL;
510 uc.dwPasswordLength = 0;
511 uc.lpszHostName = host;
512 uc.dwHostNameLength = sizeof(host)/sizeof(host[0]);
513 uc.nPort = 0;
514 uc.lpszUrlPath = path;
515 uc.dwUrlPathLength = sizeof(path)/sizeof(path[0]);
516 uc.lpszExtraInfo = NULL;
517 uc.dwExtraInfoLength = 0;
518 ret = WinHttpCrackUrl(file->info.RemoteName, 0, 0, &uc);
519 if (!ret)
520 {
521 TRACE("WinHttpCrackUrl failed, trying local file copy\n");
522 if (!transfer_file_local(file, tmpName)) return FALSE;
523 }
524 else if (!transfer_file_http(file, &uc, tmpName))
525 {
526 WARN("HTTP transfer failed\n");
527 return FALSE;
528 }
529
530 if (transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_QUEUED) ||
531 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_QUEUED))
532 {
533 lstrcpyW(file->tempFileName, tmpName);
534
535 EnterCriticalSection(&job->cs);
536 file->fileProgress.Completed = TRUE;
537 job->jobProgress.FilesTransferred++;
538 LeaveCriticalSection(&job->cs);
539
540 return TRUE;
541 }
542 else
543 {
544 DeleteFileW(tmpName);
545 return FALSE;
546 }
547 }