cmd: Echoed prompts are preceded by a blank line.
[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         {
203             char *buffer = NULL;
204
205             if (!(size = DdeGetData(hData, NULL, 0, 0)))
206                 WINE_ERR("DdeGetData returned zero size of execute string\n");
207             else if (!(buffer = HeapAlloc(GetProcessHeap(), 0, size)))
208                 WINE_ERR("Out of memory\n");
209             else if (DdeGetData(hData, (LPBYTE)buffer, size, 0) != size)
210                 WINE_WARN("DdeGetData did not return %d bytes\n", size);
211             else
212             {
213                 int len = MultiByteToWideChar(CP_ACP, 0, buffer, -1, NULL, 0);
214                 if (!(ddeString = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR))))
215                     WINE_ERR("Out of memory\n");
216                 else
217                     MultiByteToWideChar(CP_ACP, 0, buffer, -1, ddeString, len);
218             }
219             HeapFree(GetProcessHeap(), 0, buffer);
220             DdeFreeDataHandle(hData);
221             return (HDDEDATA)DDE_FACK;
222         }
223         case XTYP_REQUEST:
224             ret = -3; /* error */
225             if (!(size = DdeQueryStringW(ddeInst, hsz2, NULL, 0, CP_WINUNICODE)))
226                 WINE_ERR("DdeQueryString returned zero size of request string\n");
227             else if (!(ddeString = HeapAlloc(GetProcessHeap(), 0, (size + 1) * sizeof(WCHAR))))
228                 WINE_ERR("Out of memory\n");
229             else if (DdeQueryStringW(ddeInst, hsz2, ddeString, size + 1, CP_WINUNICODE) != size)
230                 WINE_WARN("DdeQueryString did not return %d characters\n", size);
231             else
232                 ret = -2; /* acknowledgment */
233             return DdeCreateDataHandle(ddeInst, (LPBYTE)&ret, sizeof(ret), 0,
234                                        hszReturn, CF_TEXT, 0);
235
236         default:
237             return NULL;
238     }
239 }
240
241 static WCHAR *get_url_from_dde(void)
242 {
243     static const WCHAR szApplication[] = {'I','E','x','p','l','o','r','e',0};
244     static const WCHAR szTopic[] = {'W','W','W','_','O','p','e','n','U','R','L',0};
245     static const WCHAR szReturn[] = {'R','e','t','u','r','n',0};
246
247     HSZ hszApplication = 0;
248     UINT_PTR timer = 0;
249     int rc;
250     WCHAR *ret = NULL;
251
252     rc = DdeInitializeW(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES | CBF_FAIL_POKES, 0);
253     if (rc != DMLERR_NO_ERROR)
254     {
255         WINE_ERR("Unable to initialize DDE, DdeInitialize returned %d\n", rc);
256         goto done;
257     }
258
259     hszApplication = DdeCreateStringHandleW(ddeInst, szApplication, CP_WINUNICODE);
260     if (!hszApplication)
261     {
262         WINE_ERR("Unable to initialize DDE, DdeCreateStringHandle failed\n");
263         goto done;
264     }
265
266     hszTopic = DdeCreateStringHandleW(ddeInst, szTopic, CP_WINUNICODE);
267     if (!hszTopic)
268     {
269         WINE_ERR("Unable to initialize DDE, DdeCreateStringHandle failed\n");
270         goto done;
271     }
272
273     hszReturn = DdeCreateStringHandleW(ddeInst, szReturn, CP_WINUNICODE);
274     if (!hszReturn)
275     {
276         WINE_ERR("Unable to initialize DDE, DdeCreateStringHandle failed\n");
277         goto done;
278     }
279
280     if (!DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER))
281     {
282         WINE_ERR("Unable to initialize DDE, DdeNameService failed\n");
283         goto done;
284     }
285
286     timer = SetTimer(NULL, 0, 5000, NULL);
287     if (!timer)
288     {
289         WINE_ERR("SetTimer failed to create timer\n");
290         goto done;
291     }
292
293     while (!ddeString)
294     {
295         MSG msg;
296         if (!GetMessageW(&msg, NULL, 0, 0)) break;
297         if (msg.message == WM_TIMER) break;
298         DispatchMessageW(&msg);
299     }
300
301     if (ddeString)
302     {
303         if (*ddeString == '"')
304         {
305             WCHAR *endquote = strchrW(ddeString + 1, '"');
306             if (!endquote)
307             {
308                 WINE_ERR("Unable to retrieve URL from string %s\n", wine_dbgstr_w(ddeString));
309                 goto done;
310             }
311             *endquote = 0;
312             ret = ddeString+1;
313         }
314         else
315             ret = ddeString;
316     }
317
318 done:
319     if (timer) KillTimer(NULL, timer);
320     if (ddeInst)
321     {
322         if (hszTopic && hszApplication) DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER);
323         if (hszReturn) DdeFreeStringHandle(ddeInst, hszReturn);
324         if (hszTopic) DdeFreeStringHandle(ddeInst, hszTopic);
325         if (hszApplication) DdeFreeStringHandle(ddeInst, hszApplication);
326         DdeUninitialize(ddeInst);
327     }
328     return ret;
329 }
330
331 /*****************************************************************************
332  * Main entry point. This is a console application so we have a wmain() not a
333  * winmain().
334  */
335 int wmain(int argc, WCHAR *argv[])
336 {
337     static const WCHAR nohomeW[] = {'-','n','o','h','o','m','e',0};
338     static const WCHAR mailtoW[] = {'m','a','i','l','t','o',':',0};
339     static const WCHAR fileW[] = {'f','i','l','e',':',0};
340
341     WCHAR *p, *filenameW = NULL, *fileurlW = NULL, *url = argv[1];
342     wine_get_unix_file_name_t wine_get_unix_file_name_ptr;
343     int ret = 1;
344
345     /* DDE used only if -nohome is specified; avoids delay in printing usage info
346      * when no parameters are passed */
347     if (url && !strcmpiW( url, nohomeW ))
348         url = argc > 2 ? argv[2] : get_url_from_dde();
349
350     if (!url)
351     {
352         WINE_ERR( "Usage: winebrowser URL\n" );
353         goto done;
354     }
355
356     /* handle an RFC1738 file URL */
357     if (!strncmpiW( url, fileW, 5 ))
358     {
359         DWORD len = strlenW( url ) + 1;
360
361         if (UrlUnescapeW( url, NULL, &len, URL_UNESCAPE_INPLACE ) != S_OK)
362         {
363             WINE_ERR( "unescaping URL failed: %s\n", wine_dbgstr_w(url) );
364             goto done;
365         }
366
367         /* look for a Windows path after 'file:' */
368         p = url + 5;
369         while (*p)
370         {
371             if (isalphaW( p[0] ) && (p[1] == ':' || p[1] == '|')) break;
372             p++;
373         }
374         if (!*p)
375         {
376             WINE_ERR( "no valid Windows path in: %s\n", wine_dbgstr_w(url) );
377             goto done;
378         }
379
380         if (p[1] == '|') p[1] = ':';
381         url = p;
382  
383         while (*p)
384         {
385             if (*p == '/') *p = '\\';
386             p++;
387         }
388     }
389
390     /* check if the argument is a local file */
391     wine_get_unix_file_name_ptr = (wine_get_unix_file_name_t)
392         GetProcAddress( GetModuleHandleA( "KERNEL32" ), "wine_get_unix_file_name" );
393
394     if (wine_get_unix_file_name_ptr == NULL)
395     {
396         WINE_ERR( "cannot get the address of 'wine_get_unix_file_name'\n" );
397     }
398     else
399     {
400         char *unixpath;
401         WCHAR c = 0;
402
403         if (!(filenameW = strdupW( url ))) goto done;
404         if ((p = strchrW( filenameW, '?' )) || (p = strchrW( filenameW, '#' )))
405         {
406             c = *p;
407             *p = 0;
408         }
409
410         if ((unixpath = wine_get_unix_file_name_ptr( filenameW )))
411         {
412             struct stat dummy;
413             if (stat( unixpath, &dummy ) >= 0)
414             {
415                 static const WCHAR schemeW[] = {'f','i','l','e',':','/','/',0};
416                 int len, len_scheme;
417
418                 len = len_scheme = strlenW( schemeW );
419                 len += MultiByteToWideChar( CP_UNIXCP, 0, unixpath, -1, NULL, 0 );
420                 if (p)
421                 {
422                     *p = c;
423                     len += strlenW( p );
424                 }
425
426                 if (!(fileurlW = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) goto done;
427
428                 strcpyW( fileurlW, schemeW );
429                 MultiByteToWideChar( CP_UNIXCP, 0, unixpath, -1, fileurlW + len_scheme, len - len_scheme );
430                 if (p) strcatW( fileurlW, p );
431
432                 ret = open_http_url( fileurlW );
433                 goto done;
434             }
435         }
436     }
437
438     if (!strncmpiW( url, mailtoW, 7 ))
439         ret = open_mailto_url( url );
440     else
441         /* let the browser decide how to handle the given url */
442         ret = open_http_url( url );
443
444 done:
445     HeapFree(GetProcessHeap(), 0, ddeString);
446     HeapFree( GetProcessHeap(), 0, filenameW );
447     HeapFree( GetProcessHeap(), 0, fileurlW );
448     return ret;
449 }