msi/tests: Create only one log file and delete it afterwards.
[wine] / dlls / winhttp / session.c
1 /*
2  * Copyright 2008 Hans Leidekker for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include "config.h"
20 #include "wine/port.h"
21 #include "wine/debug.h"
22
23 #include <stdarg.h>
24
25 #include "windef.h"
26 #include "winbase.h"
27 #include "winhttp.h"
28 #include "wincrypt.h"
29
30 #include "winhttp_private.h"
31
32 WINE_DEFAULT_DEBUG_CHANNEL(winhttp);
33
34 void set_last_error( DWORD error )
35 {
36     /* FIXME */
37     SetLastError( error );
38 }
39
40 DWORD get_last_error( void )
41 {
42     /* FIXME */
43     return GetLastError();
44 }
45
46 void send_callback( object_header_t *hdr, DWORD status, LPVOID info, DWORD buflen )
47 {
48     TRACE("%p, 0x%08x, %p, %u\n", hdr, status, info, buflen);
49
50     if (hdr->notify_mask & status) hdr->callback( hdr->handle, hdr->context, status, info, buflen );
51 }
52
53 /***********************************************************************
54  *          WinHttpCheckPlatform (winhttp.@)
55  */
56 BOOL WINAPI WinHttpCheckPlatform( void )
57 {
58     TRACE("\n");
59     return TRUE;
60 }
61
62 /***********************************************************************
63  *          session_destroy (internal)
64  */
65 static void session_destroy( object_header_t *hdr )
66 {
67     session_t *session = (session_t *)hdr;
68
69     TRACE("%p\n", session);
70
71     heap_free( session->agent );
72     heap_free( session->proxy_server );
73     heap_free( session->proxy_bypass );
74     heap_free( session->proxy_username );
75     heap_free( session->proxy_password );
76     heap_free( session );
77 }
78
79 static BOOL session_set_option( object_header_t *hdr, DWORD option, LPVOID buffer, DWORD buflen )
80 {
81     switch (option)
82     {
83     case WINHTTP_OPTION_PROXY:
84     {
85         WINHTTP_PROXY_INFO *pi = buffer;
86
87         FIXME("%u %s %s\n", pi->dwAccessType, debugstr_w(pi->lpszProxy), debugstr_w(pi->lpszProxyBypass));
88         return TRUE;
89     }
90     case WINHTTP_OPTION_REDIRECT_POLICY:
91     {
92         DWORD policy = *(DWORD *)buffer;
93
94         TRACE("0x%x\n", policy);
95         hdr->redirect_policy = policy;
96         return TRUE;
97     }
98     default:
99         FIXME("unimplemented option %u\n", option);
100         return TRUE;
101     }
102 }
103
104 static const object_vtbl_t session_vtbl =
105 {
106     session_destroy,
107     NULL,
108     session_set_option
109 };
110
111 /***********************************************************************
112  *          WinHttpOpen (winhttp.@)
113  */
114 HINTERNET WINAPI WinHttpOpen( LPCWSTR agent, DWORD access, LPCWSTR proxy, LPCWSTR bypass, DWORD flags )
115 {
116     session_t *session;
117     HINTERNET handle = NULL;
118
119     TRACE("%s, %u, %s, %s, 0x%08x\n", debugstr_w(agent), access, debugstr_w(proxy), debugstr_w(bypass), flags);
120
121     if (!(session = heap_alloc_zero( sizeof(session_t) ))) return NULL;
122
123     session->hdr.type = WINHTTP_HANDLE_TYPE_SESSION;
124     session->hdr.vtbl = &session_vtbl;
125     session->hdr.flags = flags;
126     session->hdr.refs = 1;
127     session->access = access;
128
129     if (agent && !(session->agent = strdupW( agent ))) goto end;
130     if (proxy && !(session->proxy_server = strdupW( proxy ))) goto end;
131     if (bypass && !(session->proxy_bypass = strdupW( bypass ))) goto end;
132
133     if (!(handle = alloc_handle( &session->hdr ))) goto end;
134     session->hdr.handle = handle;
135
136 end:
137     release_object( &session->hdr );
138     TRACE("returning %p\n", handle);
139     return handle;
140 }
141
142 /***********************************************************************
143  *          connect_destroy (internal)
144  */
145 static void connect_destroy( object_header_t *hdr )
146 {
147     connect_t *connect = (connect_t *)hdr;
148
149     TRACE("%p\n", connect);
150
151     release_object( &connect->session->hdr );
152
153     heap_free( connect->hostname );
154     heap_free( connect->servername );
155     heap_free( connect->username );
156     heap_free( connect->password );
157     heap_free( connect );
158 }
159
160 static const object_vtbl_t connect_vtbl =
161 {
162     connect_destroy,
163     NULL,
164     NULL
165 };
166
167 /***********************************************************************
168  *          WinHttpConnect (winhttp.@)
169  */
170 HINTERNET WINAPI WinHttpConnect( HINTERNET hsession, LPCWSTR server, INTERNET_PORT port, DWORD reserved )
171 {
172     connect_t *connect;
173     session_t *session;
174     HINTERNET hconnect = NULL;
175
176     TRACE("%p, %s, %u, %x\n", hsession, debugstr_w(server), port, reserved);
177
178     if (!server)
179     {
180         set_last_error( ERROR_INVALID_PARAMETER );
181         return NULL;
182     }
183     if (!(session = (session_t *)grab_object( hsession )))
184     {
185         set_last_error( ERROR_INVALID_HANDLE );
186         return NULL;
187     }
188     if (session->hdr.type != WINHTTP_HANDLE_TYPE_SESSION)
189     {
190         release_object( &session->hdr );
191         set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE );
192         return NULL;
193     }
194     if (!(connect = heap_alloc_zero( sizeof(connect_t) )))
195     {
196         release_object( &session->hdr );
197         return NULL;
198     }
199     connect->hdr.type = WINHTTP_HANDLE_TYPE_CONNECT;
200     connect->hdr.vtbl = &connect_vtbl;
201     connect->hdr.refs = 1;
202     connect->hdr.flags = session->hdr.flags;
203     connect->hdr.callback = session->hdr.callback;
204     connect->hdr.notify_mask = session->hdr.notify_mask;
205     connect->hdr.context = session->hdr.context;
206
207     addref_object( &session->hdr );
208     connect->session = session;
209     list_add_head( &session->hdr.children, &connect->hdr.entry );
210
211     if (server && !(connect->hostname = strdupW( server ))) goto end;
212     connect->hostport = port ? port : (connect->hdr.flags & WINHTTP_FLAG_SECURE ? 443 : 80);
213
214     if (server && !(connect->servername = strdupW( server ))) goto end;
215     connect->serverport = port ? port : (connect->hdr.flags & WINHTTP_FLAG_SECURE ? 443 : 80);
216
217     if (!(hconnect = alloc_handle( &connect->hdr ))) goto end;
218     connect->hdr.handle = hconnect;
219
220     send_callback( &session->hdr, WINHTTP_CALLBACK_STATUS_HANDLE_CREATED, &hconnect, sizeof(hconnect) );
221
222 end:
223     release_object( &connect->hdr );
224
225     TRACE("returning %p\n", hconnect);
226     return hconnect;
227 }
228
229 /***********************************************************************
230  *          request_destroy (internal)
231  */
232 static void request_destroy( object_header_t *hdr )
233 {
234     request_t *request = (request_t *)hdr;
235     int i;
236
237     TRACE("%p\n", request);
238
239     release_object( &request->connect->hdr );
240
241     heap_free( request->verb );
242     heap_free( request->path );
243     heap_free( request->version );
244     heap_free( request->raw_headers );
245     heap_free( request->status_text );
246     for (i = 0; i < request->num_headers; i++)
247     {
248         heap_free( request->headers[i].field );
249         heap_free( request->headers[i].value );
250     }
251     heap_free( request->headers );
252     heap_free( request );
253 }
254
255 static BOOL request_query_option( object_header_t *hdr, DWORD option, LPVOID buffer, LPDWORD buflen )
256 {
257     switch (option)
258     {
259     case WINHTTP_OPTION_SECURITY_FLAGS:
260     {
261         DWORD flags = 0;
262
263         if (hdr->flags & WINHTTP_FLAG_SECURE) flags |= SECURITY_FLAG_SECURE;
264         *(DWORD *)buffer = flags;
265         *buflen = sizeof(DWORD);
266         return TRUE;
267     }
268     case WINHTTP_OPTION_SERVER_CERT_CONTEXT:
269     {
270         const CERT_CONTEXT *cert;
271         request_t *request = (request_t *)hdr;
272
273         if (!(cert = netconn_get_certificate( &request->netconn ))) return FALSE;
274         *(CERT_CONTEXT **)buffer = (CERT_CONTEXT *)cert;
275         *buflen = sizeof(cert);
276         return TRUE;
277     }
278     case WINHTTP_OPTION_SECURITY_KEY_BITNESS:
279     {
280         *(DWORD *)buffer = 128; /* FIXME */
281         *buflen = sizeof(DWORD);
282         return TRUE;
283     }
284     default:
285         FIXME("unimplemented option %u\n", option);
286         return FALSE;
287     }
288 }
289
290 static BOOL request_set_option( object_header_t *hdr, DWORD option, LPVOID buffer, DWORD buflen )
291 {
292     switch (option)
293     {
294     case WINHTTP_OPTION_PROXY:
295     {
296         WINHTTP_PROXY_INFO *pi = buffer;
297
298         FIXME("%u %s %s\n", pi->dwAccessType, debugstr_w(pi->lpszProxy), debugstr_w(pi->lpszProxyBypass));
299         return TRUE;
300     }
301     case WINHTTP_OPTION_DISABLE_FEATURE:
302     {
303         DWORD disable = *(DWORD *)buffer;
304
305         TRACE("0x%x\n", disable);
306         hdr->disable_flags &= disable;
307         return TRUE;
308     }
309     case WINHTTP_OPTION_AUTOLOGON_POLICY:
310     {
311         DWORD policy = *(DWORD *)buffer;
312
313         TRACE("0x%x\n", policy);
314         hdr->logon_policy = policy;
315         return TRUE;
316     }
317     case WINHTTP_OPTION_REDIRECT_POLICY:
318     {
319         DWORD policy = *(DWORD *)buffer;
320
321         TRACE("0x%x\n", policy);
322         hdr->redirect_policy = policy;
323         return TRUE;
324     }
325     default:
326         FIXME("unimplemented option %u\n", option);
327         return TRUE;
328     }
329 }
330
331 static const object_vtbl_t request_vtbl =
332 {
333     request_destroy,
334     request_query_option,
335     request_set_option
336 };
337
338 /***********************************************************************
339  *          WinHttpOpenRequest (winhttp.@)
340  */
341 HINTERNET WINAPI WinHttpOpenRequest( HINTERNET hconnect, LPCWSTR verb, LPCWSTR object, LPCWSTR version,
342                                      LPCWSTR referrer, LPCWSTR *types, DWORD flags )
343 {
344     request_t *request;
345     connect_t *connect;
346     HINTERNET hrequest = NULL;
347
348     TRACE("%p, %s, %s, %s, %s, %p, 0x%08x\n", hconnect, debugstr_w(verb), debugstr_w(object),
349           debugstr_w(version), debugstr_w(referrer), types, flags);
350
351     if (!(connect = (connect_t *)grab_object( hconnect )))
352     {
353         set_last_error( ERROR_INVALID_HANDLE );
354         return NULL;
355     }
356     if (connect->hdr.type != WINHTTP_HANDLE_TYPE_CONNECT)
357     {
358         release_object( &connect->hdr );
359         set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE );
360         return NULL;
361     }
362     if (!(request = heap_alloc_zero( sizeof(request_t) )))
363     {
364         release_object( &connect->hdr );
365         return NULL;
366     }
367     request->hdr.type = WINHTTP_HANDLE_TYPE_REQUEST;
368     request->hdr.vtbl = &request_vtbl;
369     request->hdr.refs = 1;
370     request->hdr.flags = flags;
371     request->hdr.callback = connect->hdr.callback;
372     request->hdr.notify_mask = connect->hdr.notify_mask;
373     request->hdr.context = connect->hdr.context;
374
375     addref_object( &connect->hdr );
376     request->connect = connect;
377     list_add_head( &connect->hdr.children, &request->hdr.entry );
378
379     if (!netconn_init( &request->netconn, request->hdr.flags & WINHTTP_FLAG_SECURE )) goto end;
380
381     if (verb && !(request->verb = strdupW( verb ))) goto end;
382     if (object && !(request->path = strdupW( object ))) goto end;
383     if (version && !(request->version = strdupW( version ))) goto end;
384
385     if (!(hrequest = alloc_handle( &request->hdr ))) goto end;
386     request->hdr.handle = hrequest;
387
388     send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_HANDLE_CREATED, &hrequest, sizeof(hrequest) );
389
390 end:
391     release_object( &request->hdr );
392
393     TRACE("returning %p\n", hrequest);
394     return hrequest;
395 }
396
397 /***********************************************************************
398  *          WinHttpCloseHandle (winhttp.@)
399  */
400 BOOL WINAPI WinHttpCloseHandle( HINTERNET handle )
401 {
402     object_header_t *hdr;
403
404     TRACE("%p\n", handle);
405
406     if (!(hdr = grab_object( handle )))
407     {
408         set_last_error( ERROR_INVALID_HANDLE );
409         return FALSE;
410     }
411     release_object( hdr );
412     free_handle( handle );
413     return TRUE;
414 }
415
416 static BOOL query_option( object_header_t *hdr, DWORD option, LPVOID buffer, LPDWORD buflen )
417 {
418     BOOL ret = FALSE;
419
420     switch (option)
421     {
422     case WINHTTP_OPTION_CONTEXT_VALUE:
423     {
424         *(DWORD_PTR *)buffer = hdr->context;
425         *buflen = sizeof(DWORD_PTR);
426         return TRUE;
427     }
428     default:
429     {
430         if (hdr->vtbl->query_option) ret = hdr->vtbl->query_option( hdr, option, buffer, buflen );
431         else FIXME("unimplemented option %u\n", option);
432     }
433     }
434     return ret;
435 }
436
437 /***********************************************************************
438  *          WinHttpQueryOption (winhttp.@)
439  */
440 BOOL WINAPI WinHttpQueryOption( HINTERNET handle, DWORD option, LPVOID buffer, LPDWORD buflen )
441 {
442     BOOL ret = FALSE;
443     object_header_t *hdr;
444
445     TRACE("%p, %u, %p, %p\n", handle, option, buffer, buflen);
446
447     if (!(hdr = grab_object( handle )))
448     {
449         set_last_error( ERROR_INVALID_HANDLE );
450         return FALSE;
451     }
452
453     ret = query_option( hdr, option, buffer, buflen );
454
455     release_object( hdr );
456     return ret;
457 }
458
459 static BOOL set_option( object_header_t *hdr, DWORD option, LPVOID buffer, DWORD buflen )
460 {
461     BOOL ret = TRUE;
462
463     switch (option)
464     {
465     case WINHTTP_OPTION_CONTEXT_VALUE:
466     {
467         hdr->context = *(DWORD_PTR *)buffer;
468         return TRUE;
469     }
470     default:
471     {
472         if (hdr->vtbl->set_option) ret = hdr->vtbl->set_option( hdr, option, buffer, buflen );
473         else FIXME("unimplemented option %u\n", option);
474     }
475     }
476     return ret;
477 }
478
479 /***********************************************************************
480  *          WinHttpSetOption (winhttp.@)
481  */
482 BOOL WINAPI WinHttpSetOption( HINTERNET handle, DWORD option, LPVOID buffer, DWORD buflen )
483 {
484     BOOL ret = FALSE;
485     object_header_t *hdr;
486
487     TRACE("%p, %u, %p, %u\n", handle, option, buffer, buflen);
488
489     if (!(hdr = grab_object( handle )))
490     {
491         set_last_error( ERROR_INVALID_HANDLE );
492         return FALSE;
493     }
494
495     ret = set_option( hdr, option, buffer, buflen );
496
497     release_object( hdr );
498     return ret;
499 }
500
501 /***********************************************************************
502  *          WinHttpDetectAutoProxyConfigUrl (winhttp.@)
503  */
504 BOOL WINAPI WinHttpDetectAutoProxyConfigUrl( DWORD flags, LPWSTR *url )
505 {
506     FIXME("0x%08x, %p\n", flags, url);
507
508     set_last_error( ERROR_WINHTTP_AUTODETECTION_FAILED );
509     return FALSE;
510 }
511
512 /***********************************************************************
513  *          WinHttpGetDefaultProxyConfiguration (winhttp.@)
514  */
515 BOOL WINAPI WinHttpGetDefaultProxyConfiguration( WINHTTP_PROXY_INFO *info )
516 {
517     FIXME("%p\n", info);
518
519     info->dwAccessType    = WINHTTP_ACCESS_TYPE_NO_PROXY;
520     info->lpszProxy       = NULL;
521     info->lpszProxyBypass = NULL;
522
523     return TRUE;
524 }
525
526 /***********************************************************************
527  *          WinHttpGetIEProxyConfigForCurrentUser (winhttp.@)
528  */
529 BOOL WINAPI WinHttpGetIEProxyConfigForCurrentUser( WINHTTP_CURRENT_USER_IE_PROXY_CONFIG *config )
530 {
531     TRACE("%p\n", config);
532
533     if (!config)
534     {
535         set_last_error( ERROR_INVALID_PARAMETER );
536         return FALSE;
537     }
538
539     /* FIXME: read from HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings */
540
541     FIXME("returning no proxy used\n");
542     config->fAutoDetect       = FALSE;
543     config->lpszAutoConfigUrl = NULL;
544     config->lpszProxy         = NULL;
545     config->lpszProxyBypass   = NULL;
546
547     return TRUE;
548 }
549
550 /***********************************************************************
551  *          WinHttpGetProxyForUrl (winhttp.@)
552  */
553 BOOL WINAPI WinHttpGetProxyForUrl( HINTERNET hsession, LPCWSTR url, WINHTTP_AUTOPROXY_OPTIONS *options,
554                                    WINHTTP_PROXY_INFO *info )
555 {
556     FIXME("%p, %s, %p, %p\n", hsession, debugstr_w(url), options, info);
557
558     set_last_error( ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR );
559     return FALSE;
560 }
561
562 /***********************************************************************
563  *          WinHttpSetDefaultProxyConfiguration (winhttp.@)
564  */
565 BOOL WINAPI WinHttpSetDefaultProxyConfiguration( WINHTTP_PROXY_INFO *info )
566 {
567     FIXME("%p [%u, %s, %s]\n", info, info->dwAccessType, debugstr_w(info->lpszProxy),
568           debugstr_w(info->lpszProxyBypass));
569     return TRUE;
570 }
571
572 /***********************************************************************
573  *          WinHttpSetStatusCallback (winhttp.@)
574  */
575 WINHTTP_STATUS_CALLBACK WINAPI WinHttpSetStatusCallback( HINTERNET handle, WINHTTP_STATUS_CALLBACK callback,
576                                                          DWORD flags, DWORD_PTR reserved )
577 {
578     object_header_t *hdr;
579     WINHTTP_STATUS_CALLBACK ret;
580
581     TRACE("%p, %p, 0x%08x, 0x%lx\n", handle, callback, flags, reserved);
582
583     if (!(hdr = grab_object( handle )))
584     {
585         set_last_error( ERROR_INVALID_HANDLE );
586         return WINHTTP_INVALID_STATUS_CALLBACK;
587     }
588     ret = hdr->callback;
589     hdr->callback = callback;
590     hdr->notify_mask = flags;
591
592     release_object( hdr );
593     return ret;
594 }
595
596 /***********************************************************************
597  *          WinHttpSetTimeouts (winhttp.@)
598  */
599 BOOL WINAPI WinHttpSetTimeouts( HINTERNET handle, int resolve, int connect, int send, int receive )
600 {
601     FIXME("%p, %d, %d, %d, %d\n", handle, resolve, connect, send, receive);
602     return TRUE;
603 }
604
605 static const WCHAR wkday[7][4] =
606     {{'S','u','n', 0}, {'M','o','n', 0}, {'T','u','e', 0}, {'W','e','d', 0},
607      {'T','h','u', 0}, {'F','r','i', 0}, {'S','a','t', 0}};
608 static const WCHAR month[12][4] =
609     {{'J','a','n', 0}, {'F','e','b', 0}, {'M','a','r', 0}, {'A','p','r', 0},
610      {'M','a','y', 0}, {'J','u','n', 0}, {'J','u','l', 0}, {'A','u','g', 0},
611      {'S','e','p', 0}, {'O','c','t', 0}, {'N','o','v', 0}, {'D','e','c', 0}};
612
613 /***********************************************************************
614  *           WinHttpTimeFromSystemTime (WININET.@)
615  */
616 BOOL WINAPI WinHttpTimeFromSystemTime( const SYSTEMTIME *time, LPWSTR string )
617 {
618     static const WCHAR format[] =
619         {'%','s',',',' ','%','0','2','d',' ','%','s',' ','%','4','d',' ','%','0',
620          '2','d',':','%','0','2','d',':','%','0','2','d',' ','G','M','T', 0};
621
622     TRACE("%p, %p\n", time, string);
623
624     if (!time || !string) return FALSE;
625
626     sprintfW( string, format,
627               wkday[time->wDayOfWeek],
628               time->wDay,
629               month[time->wMonth - 1],
630               time->wYear,
631               time->wHour,
632               time->wMinute,
633               time->wSecond );
634
635     return TRUE;
636 }
637
638 /***********************************************************************
639  *           WinHttpTimeToSystemTime (WININET.@)
640  */
641 BOOL WINAPI WinHttpTimeToSystemTime( LPCWSTR string, SYSTEMTIME *time )
642 {
643     unsigned int i;
644     const WCHAR *s = string;
645     WCHAR *end;
646
647     TRACE("%s, %p\n", debugstr_w(string), time);
648
649     if (!string || !time) return FALSE;
650
651     /* Windows does this too */
652     GetSystemTime( time );
653
654     /*  Convert an RFC1123 time such as 'Fri, 07 Jan 2005 12:06:35 GMT' into
655      *  a SYSTEMTIME structure.
656      */
657
658     while (*s && !isalphaW( *s )) s++;
659     if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0') return TRUE;
660     time->wDayOfWeek = 7;
661
662     for (i = 0; i < 7; i++)
663     {
664         if (toupperW( wkday[i][0] ) == toupperW( s[0] ) &&
665             toupperW( wkday[i][1] ) == toupperW( s[1] ) &&
666             toupperW( wkday[i][2] ) == toupperW( s[2] ) )
667         {
668             time->wDayOfWeek = i;
669             break;
670         }
671     }
672
673     if (time->wDayOfWeek > 6) return TRUE;
674     while (*s && !isdigitW( *s )) s++;
675     time->wDay = strtolW( s, &end, 10 );
676     s = end;
677
678     while (*s && !isalphaW( *s )) s++;
679     if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0') return TRUE;
680     time->wMonth = 0;
681
682     for (i = 0; i < 12; i++)
683     {
684         if (toupperW( month[i][0]) == toupperW( s[0] ) &&
685             toupperW( month[i][1]) == toupperW( s[1] ) &&
686             toupperW( month[i][2]) == toupperW( s[2] ) )
687         {
688             time->wMonth = i + 1;
689             break;
690         }
691     }
692     if (time->wMonth == 0) return TRUE;
693
694     while (*s && !isdigitW( *s )) s++;
695     if (*s == '\0') return TRUE;
696     time->wYear = strtolW( s, &end, 10 );
697     s = end;
698
699     while (*s && !isdigitW( *s )) s++;
700     if (*s == '\0') return TRUE;
701     time->wHour = strtolW( s, &end, 10 );
702     s = end;
703
704     while (*s && !isdigitW( *s )) s++;
705     if (*s == '\0') return TRUE;
706     time->wMinute = strtolW( s, &end, 10 );
707     s = end;
708
709     while (*s && !isdigitW( *s )) s++;
710     if (*s == '\0') return TRUE;
711     time->wSecond = strtolW( s, &end, 10 );
712     s = end;
713
714     time->wMilliseconds = 0;
715     return TRUE;
716 }