winmm/tests: Don't use same buffer for both input and output.
[wine] / programs / winebrowser / main.c
1 /*
2  * winebrowser - winelib app to launch native OS browser or mail client.
3  *
4  * Copyright (C) 2004 Chris Morgan
5  * Copyright (C) 2005 Hans Leidekker
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  *
21  * NOTES:
22  *  Winebrowser is a winelib application that will start the appropriate
23  *  native browser or mail client for a wine installation that lacks a 
24  *  windows browser/mail client. For example, you will be able to open
25  *  urls via native mozilla if no browser has yet been installed in wine.
26  *
27  *  The application to launch is chosen from a default set or, if set,
28  *  taken from a registry key.
29  *  
30  *  The argument may be a regular Windows file name, a file URL, an
31  *  URL or a mailto URL. In the first three cases the argument
32  *  will be fed to a web browser. In the last case the argument is fed
33  *  to a mail client. A mailto URL is composed as follows:
34  *
35  *   mailto:[E-MAIL]?subject=[TOPIC]&cc=[E-MAIL]&bcc=[E-MAIL]&body=[TEXT]
36  */
37
38 #define WIN32_LEAN_AND_MEAN
39
40 #include "config.h"
41 #include "wine/port.h"
42 #include "wine/debug.h"
43 #include "wine/unicode.h"
44
45 #include <windows.h>
46 #include <shlwapi.h>
47 #include <ddeml.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <errno.h>
51
52 WINE_DEFAULT_DEBUG_CHANNEL(winebrowser);
53
54 typedef LPSTR (*wine_get_unix_file_name_t)(LPCWSTR unixname);
55
56 static const WCHAR browser_key[] =
57     {'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\',
58      'W','i','n','e','B','r','o','w','s','e','r',0};
59
60 static char *strdup_unixcp( const WCHAR *str )
61 {
62     char *ret;
63     int len = WideCharToMultiByte( CP_UNIXCP, 0, str, -1, NULL, 0, NULL, NULL );
64     if ((ret = HeapAlloc( GetProcessHeap(), 0, len )))
65         WideCharToMultiByte( CP_UNIXCP, 0, str, -1, ret, len, NULL, NULL );
66     return ret;
67 }
68
69 static WCHAR *strdupW( const WCHAR *src )
70 {
71     WCHAR *dst;
72     if (!src) return NULL;
73     if ((dst = HeapAlloc( GetProcessHeap(), 0, (strlenW( src ) + 1) * sizeof(WCHAR) )))
74         strcpyW( dst, src );
75     return dst;
76 }
77
78 /* try to launch a unix app from a comma separated string of app names */
79 static int launch_app( WCHAR *candidates, const WCHAR *argv1 )
80 {
81     char *app, *applist, *cmdline;
82     const char *argv_new[3];
83
84     if (!(applist = strdup_unixcp( candidates ))) return 1;
85     if (!(cmdline = strdup_unixcp( argv1 )))
86     {
87         HeapFree( GetProcessHeap(), 0, applist );
88         return 1;
89     }
90     app = strtok( applist, "," );
91     while (app)
92     {
93         WINE_TRACE( "Considering: %s\n", wine_dbgstr_a(app) );
94         WINE_TRACE( "argv[1]: %s\n", wine_dbgstr_a(cmdline) );
95
96         argv_new[0] = app;
97         argv_new[1] = cmdline;
98         argv_new[2] = NULL;
99
100         spawnvp( _P_OVERLAY, app, argv_new );  /* only returns on error */
101         app = strtok( NULL, "," );  /* grab the next app */
102     }
103     WINE_ERR( "could not find a suitable app to run\n" );
104
105     HeapFree( GetProcessHeap(), 0, applist );
106     HeapFree( GetProcessHeap(), 0, cmdline );
107     return 1;
108 }
109
110 static int open_http_url( const WCHAR *url )
111 {
112 #ifdef __APPLE__
113     static const WCHAR defaultbrowsers[] =
114         { '/', 'u', 's', 'r', '/', 'b', 'i', 'n', '/', 'o', 'p', 'e', 'n', 0 };
115 #else
116     static const WCHAR defaultbrowsers[] =
117         {'x','d','g','-','o','p','e','n',',','f','i','r','e','f','o','x',',',
118          'k','o','n','q','u','e','r','o','r',',','m','o','z','i','l','l','a',',',
119          'n','e','t','s','c','a','p','e',',','g','a','l','e','o','n',',',
120          'o','p','e','r','a',',','d','i','l','l','o',0};
121 #endif
122     static const WCHAR browsersW[] =
123         {'B','r','o','w','s','e','r','s',0};
124
125     WCHAR browsers[256];
126     DWORD length, type;
127     HKEY key;
128     LONG r;
129
130     length = sizeof(browsers);
131     /* @@ Wine registry key: HKCU\Software\Wine\WineBrowser */
132     if  (!(r = RegOpenKeyW( HKEY_CURRENT_USER, browser_key, &key )))
133     {
134         r = RegQueryValueExW( key, browsersW, 0, &type, (LPBYTE)browsers, &length );
135         RegCloseKey( key );
136     }
137     if (r != ERROR_SUCCESS)
138         strcpyW( browsers, defaultbrowsers );
139
140     return launch_app( browsers, url );
141 }
142
143 static int open_mailto_url( const WCHAR *url )
144 {
145 #ifdef __APPLE__
146     static const WCHAR defaultmailers[] =
147         { '/', 'u', 's', 'r', '/', 'b', 'i', 'n', '/', 'o', 'p', 'e', 'n', 0 };
148 #else
149     static const WCHAR defaultmailers[] =
150         {'x','d','g','-','e','m','a','i','l',',',
151          'm','o','z','i','l','l','a','-','t','h','u','n','d','e','r','b','i','r','d',',',
152          't','h','u','n','d','e','r','b','i','r','d',',',
153          'e','v','o','l','u','t','i','o','n',0};
154 #endif
155     static const WCHAR mailersW[] =
156         {'M','a','i','l','e','r','s',0};
157
158     WCHAR mailers[256];
159     DWORD length, type;
160     HKEY key;
161     LONG r;
162
163     length = sizeof(mailers);
164     /* @@ Wine registry key: HKCU\Software\Wine\WineBrowser */
165     if (!(r = RegOpenKeyW( HKEY_CURRENT_USER, browser_key, &key )))
166     {
167         r = RegQueryValueExW( key, mailersW, 0, &type, (LPBYTE)mailers, &length );
168         RegCloseKey( key );
169     }
170     if (r != ERROR_SUCCESS)
171         strcpyW( mailers, defaultmailers );
172
173     return launch_app( mailers, url );
174 }
175
176 /*****************************************************************************
177  * DDE helper functions.
178  */
179
180 static WCHAR *ddeString = NULL;
181 static HSZ hszTopic = 0, hszReturn = 0;
182 static DWORD ddeInst = 0;
183
184 /* Dde callback, save the execute or request string for processing */
185 static HDDEDATA CALLBACK ddeCb(UINT uType, UINT uFmt, HCONV hConv,
186                                 HSZ hsz1, HSZ hsz2, HDDEDATA hData,
187                                 ULONG_PTR dwData1, ULONG_PTR dwData2)
188 {
189     DWORD size = 0, ret = 0;
190
191     WINE_TRACE("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n",
192                uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2);
193
194     switch (uType)
195     {
196         case XTYP_CONNECT:
197             if (!DdeCmpStringHandles(hsz1, hszTopic))
198                 return (HDDEDATA)TRUE;
199             return (HDDEDATA)FALSE;
200
201         case XTYP_EXECUTE:
202             if (!(size = DdeGetData(hData, NULL, 0, 0)))
203                 WINE_ERR("DdeGetData returned zero size of execute string\n");
204             else if (!(ddeString = HeapAlloc(GetProcessHeap(), 0, size)))
205                 WINE_ERR("Out of memory\n");
206             else if (DdeGetData(hData, (LPBYTE)ddeString, size, 0) != size)
207                 WINE_WARN("DdeGetData did not return %d bytes\n", size);
208             DdeFreeDataHandle(hData);
209             return (HDDEDATA)DDE_FACK;
210
211         case XTYP_REQUEST:
212             ret = -3; /* error */
213             if (!(size = DdeQueryStringW(ddeInst, hsz2, NULL, 0, CP_WINUNICODE)))
214                 WINE_ERR("DdeQueryString returned zero size of request string\n");
215             else if (!(ddeString = HeapAlloc(GetProcessHeap(), 0, (size + 1) * sizeof(WCHAR))))
216                 WINE_ERR("Out of memory\n");
217             else if (DdeQueryStringW(ddeInst, hsz2, ddeString, size + 1, CP_WINUNICODE) != size)
218                 WINE_WARN("DdeQueryString did not return %d characters\n", size);
219             else
220                 ret = -2; /* acknowledgment */
221             return DdeCreateDataHandle(ddeInst, (LPBYTE)&ret, sizeof(ret), 0,
222                                        hszReturn, CF_TEXT, 0);
223
224         default:
225             return NULL;
226     }
227 }
228
229 static WCHAR *get_url_from_dde(void)
230 {
231     static const WCHAR szApplication[] = {'I','E','x','p','l','o','r','e',0};
232     static const WCHAR szTopic[] = {'W','W','W','_','O','p','e','n','U','R','L',0};
233     static const WCHAR szReturn[] = {'R','e','t','u','r','n',0};
234
235     HSZ hszApplication = 0;
236     UINT_PTR timer = 0;
237     int rc;
238     WCHAR *ret = NULL;
239
240     rc = DdeInitializeW(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES | CBF_FAIL_POKES, 0);
241     if (rc != DMLERR_NO_ERROR)
242     {
243         WINE_ERR("Unable to initialize DDE, DdeInitialize returned %d\n", rc);
244         goto done;
245     }
246
247     hszApplication = DdeCreateStringHandleW(ddeInst, szApplication, CP_WINUNICODE);
248     if (!hszApplication)
249     {
250         WINE_ERR("Unable to initialize DDE, DdeCreateStringHandle failed\n");
251         goto done;
252     }
253
254     hszTopic = DdeCreateStringHandleW(ddeInst, szTopic, CP_WINUNICODE);
255     if (!hszTopic)
256     {
257         WINE_ERR("Unable to initialize DDE, DdeCreateStringHandle failed\n");
258         goto done;
259     }
260
261     hszReturn = DdeCreateStringHandleW(ddeInst, szReturn, CP_WINUNICODE);
262     if (!hszReturn)
263     {
264         WINE_ERR("Unable to initialize DDE, DdeCreateStringHandle failed\n");
265         goto done;
266     }
267
268     if (!DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER))
269     {
270         WINE_ERR("Unable to initialize DDE, DdeNameService failed\n");
271         goto done;
272     }
273
274     timer = SetTimer(NULL, 0, 5000, NULL);
275     if (!timer)
276     {
277         WINE_ERR("SetTimer failed to create timer\n");
278         goto done;
279     }
280
281     while (!ddeString)
282     {
283         MSG msg;
284         if (!GetMessageW(&msg, NULL, 0, 0)) break;
285         if (msg.message == WM_TIMER) break;
286         DispatchMessageW(&msg);
287     }
288
289     if (ddeString)
290     {
291         if (*ddeString == '"')
292         {
293             WCHAR *endquote = strchrW(ddeString + 1, '"');
294             if (!endquote)
295             {
296                 WINE_ERR("Unable to retrieve URL from string %s\n", wine_dbgstr_w(ddeString));
297                 goto done;
298             }
299             *endquote = 0;
300             ret = ddeString+1;
301         }
302         else
303             ret = ddeString;
304     }
305
306 done:
307     if (timer) KillTimer(NULL, timer);
308     if (ddeInst)
309     {
310         if (hszTopic && hszApplication) DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER);
311         if (hszReturn) DdeFreeStringHandle(ddeInst, hszReturn);
312         if (hszTopic) DdeFreeStringHandle(ddeInst, hszTopic);
313         if (hszApplication) DdeFreeStringHandle(ddeInst, hszApplication);
314         DdeUninitialize(ddeInst);
315     }
316     return ret;
317 }
318
319 /*****************************************************************************
320  * Main entry point. This is a console application so we have a wmain() not a
321  * winmain().
322  */
323 int wmain(int argc, WCHAR *argv[])
324 {
325     static const WCHAR nohomeW[] = {'-','n','o','h','o','m','e',0};
326     static const WCHAR mailtoW[] = {'m','a','i','l','t','o',':',0};
327     static const WCHAR fileW[] = {'f','i','l','e',':',0};
328
329     WCHAR *p, *filenameW = NULL, *fileurlW = NULL, *url = argv[1];
330     wine_get_unix_file_name_t wine_get_unix_file_name_ptr;
331     int ret = 1;
332
333     /* DDE used only if -nohome is specified; avoids delay in printing usage info
334      * when no parameters are passed */
335     if (url && !strcmpiW( url, nohomeW ))
336         url = argc > 2 ? argv[2] : get_url_from_dde();
337
338     if (!url)
339     {
340         WINE_ERR( "Usage: winebrowser URL\n" );
341         goto done;
342     }
343
344     /* handle an RFC1738 file URL */
345     if (!strncmpiW( url, fileW, 5 ))
346     {
347         DWORD len = strlenW( url ) + 1;
348
349         if (UrlUnescapeW( url, NULL, &len, URL_UNESCAPE_INPLACE ) != S_OK)
350         {
351             WINE_ERR( "unescaping URL failed: %s\n", wine_dbgstr_w(url) );
352             goto done;
353         }
354
355         /* look for a Windows path after 'file:' */
356         p = url + 5;
357         while (*p)
358         {
359             if (isalphaW( p[0] ) && (p[1] == ':' || p[1] == '|')) break;
360             p++;
361         }
362         if (!*p)
363         {
364             WINE_ERR( "no valid Windows path in: %s\n", wine_dbgstr_w(url) );
365             goto done;
366         }
367
368         if (p[1] == '|') p[1] = ':';
369         url = p;
370  
371         while (*p)
372         {
373             if (*p == '/') *p = '\\';
374             p++;
375         }
376     }
377
378     /* check if the argument is a local file */
379     wine_get_unix_file_name_ptr = (wine_get_unix_file_name_t)
380         GetProcAddress( GetModuleHandleA( "KERNEL32" ), "wine_get_unix_file_name" );
381
382     if (wine_get_unix_file_name_ptr == NULL)
383     {
384         WINE_ERR( "cannot get the address of 'wine_get_unix_file_name'\n" );
385     }
386     else
387     {
388         char *unixpath;
389         WCHAR c = 0;
390
391         if (!(filenameW = strdupW( url ))) goto done;
392         if ((p = strchrW( filenameW, '?' )) || (p = strchrW( filenameW, '#' )))
393         {
394             c = *p;
395             *p = 0;
396         }
397
398         if ((unixpath = wine_get_unix_file_name_ptr( filenameW )))
399         {
400             struct stat dummy;
401             if (stat( unixpath, &dummy ) >= 0)
402             {
403                 static const WCHAR schemeW[] = {'f','i','l','e',':','/','/',0};
404                 int len, len_scheme;
405
406                 len = len_scheme = strlenW( schemeW );
407                 len += MultiByteToWideChar( CP_UNIXCP, 0, unixpath, -1, NULL, 0 );
408                 if (p)
409                 {
410                     *p = c;
411                     len += strlenW( p );
412                 }
413
414                 if (!(fileurlW = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) goto done;
415
416                 strcpyW( fileurlW, schemeW );
417                 MultiByteToWideChar( CP_UNIXCP, 0, unixpath, -1, fileurlW + len_scheme, len - len_scheme );
418                 if (p) strcatW( fileurlW, p );
419
420                 ret = open_http_url( fileurlW );
421                 goto done;
422             }
423         }
424     }
425
426     if (!strncmpiW( url, mailtoW, 7 ))
427         ret = open_mailto_url( url );
428     else
429         /* let the browser decide how to handle the given url */
430         ret = open_http_url( url );
431
432 done:
433     HeapFree(GetProcessHeap(), 0, ddeString);
434     HeapFree( GetProcessHeap(), 0, filenameW );
435     HeapFree( GetProcessHeap(), 0, fileurlW );
436     return ret;
437 }