1 //
2 // "$Id: fl_open_uri.cxx 8063 2010-12-19 21:20:10Z matt $"
3 //
4 // fl_open_uri() code for FLTK.
5 //
6 // Test with:
7 //
8 //    gcc -I/fltk/dir -I/fltk/dir/src -DTEST -o fl_open_uri fl_open_uri.cxx -lfltk
9 //
10 // Copyright 2003-2010 by Michael R Sweet
11 //
12 // This library is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU Library General Public
14 // License as published by the Free Software Foundation; either
15 // version 2 of the License, or (at your option) any later version.
16 //
17 // This library is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 // Library General Public License for more details.
21 //
22 // You should have received a copy of the GNU Library General Public
23 // License along with this library; if not, write to the Free Software
24 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
25 // USA.
26 //
27 
28 //
29 // Include necessary headers...
30 //
31 
32 #include <FL/filename.H>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <errno.h>
36 #include <sys/types.h>
37 #include "flstring.h"
38 #ifdef WIN32
39 #  include <windows.h>
40 #  include <shellapi.h>
41 #else
42 #  include <sys/wait.h>
43 #  include <signal.h>
44 #  include <fcntl.h>
45 #  include <unistd.h>
46 #endif // WIN32
47 
48 
49 //
50 // Local functions...
51 //
52 
53 #if !defined(WIN32) && !defined(__APPLE__)
54 static char	*path_find(const char *program, char *filename, int filesize);
55 #endif // !WIN32 && !__APPLE__
56 #ifndef WIN32
57 static int	run_program(const char *program, char **argv, char *msg, int msglen);
58 #endif // !WIN32
59 
60 /** \addtogroup filenames
61  @{ */
62 
63 /**
64  * Opens the specified Uniform Resource Identifier (URI).
65  * Uses an operating-system dependent program or interface. For URIs
66  * using the "ftp", "http", or "https" schemes, the system default web
67  * browser is used to open the URI, while "mailto" and "news" URIs are
68  * typically opened using the system default mail reader and "file" URIs
69  * are opened using the file system navigator.
70  *
71  * On success, the (optional) msg buffer is filled with the command that
72  * was run to open the URI; on Windows, this will always be "open uri".
73  *
74  * On failure, the msg buffer is filled with an English error message.
75  *
76  * \b Example
77  * \code
78  * #include <FL/filename.H>
79  * [..]
80  * char errmsg[512];
81  * if ( !fl_open_uri("http://google.com/", errmsg, sizeof(errmsg)) ) {
82  *     char warnmsg[768];
83  *     sprintf(warnmsg, "Error: %s", errmsg);
84  *     fl_alert(warnmsg);
85  * }
86  * \endcode
87  *
88  * @param uri The URI to open
89  * @param msg Optional buffer which contains the command or error message
90  * @param msglen Length of optional buffer
91  * @return 1 on success, 0 on failure
92  */
93 
94 int
fl_open_uri(const char * uri,char * msg,int msglen)95 fl_open_uri(const char *uri, char *msg, int msglen) {
96   // Supported URI schemes...
97   static const char * const schemes[] = {
98     "file://",
99     "ftp://",
100     "http://",
101     "https://",
102     "mailto:",
103     "news://",
104     NULL
105   };
106 
107   // Validate the URI scheme...
108   int i;
109   for (i = 0; schemes[i]; i ++)
110     if (!strncmp(uri, schemes[i], strlen(schemes[i])))
111       break;
112 
113   if (!schemes[i]) {
114     if (msg) {
115       char scheme[255];
116       if (sscanf(uri, "%254[^:]", scheme) == 1) {
117         snprintf(msg, msglen, "URI scheme \"%s\" not supported.", scheme);
118       } else {
119         snprintf(msg, msglen, "Bad URI \"%s\"", uri);
120       }
121     }
122 
123     return 0;
124   }
125 
126 #ifdef WIN32
127   if (msg) snprintf(msg, msglen, "open %s", uri);
128 
129   return (int)(ShellExecute(HWND_DESKTOP, "open", uri, NULL, NULL, SW_SHOW) > (void *)32);
130 
131 #elif defined(__APPLE__)
132   char	*argv[3];			// Command-line arguments
133 
134   argv[0] = (char*)"open";
135   argv[1] = (char*)uri;
136   argv[2] = (char*)0;
137 
138   if (msg) snprintf(msg, msglen, "open %s", uri);
139 
140   return run_program("/usr/bin/open", argv, msg, msglen) != 0;
141 
142 #else // !WIN32 && !__APPLE__
143   // Run any of several well-known commands to open the URI.
144   //
145   // We give preference to the Portland group's xdg-utils
146   // programs which run the user's preferred web browser, etc.
147   // based on the current desktop environment in use.  We fall
148   // back on older standards and then finally test popular programs
149   // until we find one we can use.
150   //
151   // Note that we specifically do not support the MAILER and
152   // BROWSER environment variables because we have no idea whether
153   // we need to run the listed commands in a terminal program.
154 
155   char	command[FL_PATH_MAX],		// Command to run...
156 	*argv[4],			// Command-line arguments
157 	remote[1024];			// Remote-mode command...
158   const char * const *commands;		// Array of commands to check...
159   static const char * const browsers[] = {
160     "xdg-open", // Portland
161     "htmlview", // Freedesktop.org
162     "firefox",
163     "mozilla",
164     "netscape",
165     "konqueror", // KDE
166     "opera",
167     "hotjava", // Solaris
168     "mosaic",
169     NULL
170   };
171   static const char * const readers[] = {
172     "xdg-email", // Portland
173     "thunderbird",
174     "mozilla",
175     "netscape",
176     "evolution", // GNOME
177     "kmailservice", // KDE
178     NULL
179   };
180   static const char * const managers[] = {
181     "xdg-open", // Portland
182     "fm", // IRIX
183     "dtaction", // CDE
184     "nautilus", // GNOME
185     "konqueror", // KDE
186     NULL
187   };
188 
189   // Figure out which commands to check for...
190   if (!strncmp(uri, "file://", 7)) commands = managers;
191   else if (!strncmp(uri, "mailto:", 7) ||
192            !strncmp(uri, "news:", 5)) commands = readers;
193   else commands = browsers;
194 
195   // Find the command to run...
196   for (i = 0; commands[i]; i ++)
197     if (path_find(commands[i], command, sizeof(command))) break;
198 
199   if (!commands[i]) {
200     if (msg) {
201       snprintf(msg, msglen, "No helper application found for \"%s\"", uri);
202     }
203 
204     return 0;
205   }
206 
207   // Handle command-specific arguments...
208   argv[0] = (char *)commands[i];
209 
210   if (!strcmp(commands[i], "firefox") ||
211       !strcmp(commands[i], "mozilla") ||
212       !strcmp(commands[i], "netscape") ||
213       !strcmp(commands[i], "thunderbird")) {
214     // program -remote openURL(uri)
215     snprintf(remote, sizeof(remote), "openURL(%s)", uri);
216 
217     argv[1] = (char *)"-remote";
218     argv[2] = remote;
219     argv[3] = 0;
220   } else if (!strcmp(commands[i], "dtaction")) {
221     // dtaction open uri
222     argv[1] = (char *)"open";
223     argv[2] = (char *)uri;
224     argv[3] = 0;
225   } else {
226     // program uri
227     argv[1] = (char *)uri;
228     argv[2] = 0;
229   }
230 
231   if (msg) {
232     strlcpy(msg, argv[0], msglen);
233 
234     for (i = 1; argv[i]; i ++) {
235       strlcat(msg, " ", msglen);
236       strlcat(msg, argv[i], msglen);
237     }
238   }
239 
240   return run_program(command, argv, msg, msglen) != 0;
241 #endif // WIN32
242 }
243 
244 /**   @} */
245 
246 #if !defined(WIN32) && !defined(__APPLE__)
247 // Find a program in the path...
path_find(const char * program,char * filename,int filesize)248 static char *path_find(const char *program, char *filename, int filesize) {
249   const char	*path;			// Search path
250   char		*ptr,			// Pointer into filename
251 		*end;			// End of filename buffer
252 
253 
254   if ((path = getenv("PATH")) == NULL) path = "/bin:/usr/bin";
255 
256   for (ptr = filename, end = filename + filesize - 1; *path; path ++) {
257     if (*path == ':') {
258       if (ptr > filename && ptr[-1] != '/' && ptr < end) *ptr++ = '/';
259 
260       strlcpy(ptr, program, end - ptr + 1);
261 
262       if (!access(filename, X_OK)) return filename;
263 
264       ptr = filename;
265     } else if (ptr < end) *ptr++ = *path;
266   }
267 
268   if (ptr > filename) {
269     if (ptr[-1] != '/' && ptr < end) *ptr++ = '/';
270 
271     strlcpy(ptr, program, end - ptr + 1);
272 
273     if (!access(filename, X_OK)) return filename;
274   }
275 
276   return 0;
277 }
278 #endif // !WIN32 && !__APPLE__
279 
280 
281 #ifndef WIN32
282 // Run the specified program, returning 1 on success and 0 on failure
283 static int
run_program(const char * program,char ** argv,char * msg,int msglen)284 run_program(const char *program, char **argv, char *msg, int msglen) {
285   pid_t	pid;				// Process ID of first child
286   int status;				// Exit status from first child
287   sigset_t set, oldset;			// Signal masks
288 
289 
290   // Block SIGCHLD while we run the program...
291   //
292   // Note that I only use the POSIX signal APIs, however older operating
293   // systems may either not support POSIX signals or have side effects.
294   // IRIX, for example, provides three separate and incompatible signal
295   // APIs, so it is possible that an application setting a signal handler
296   // via signal() or sigset() will not have its SIGCHLD signals blocked...
297 
298   sigemptyset(&set);
299   sigaddset(&set, SIGCHLD);
300   sigprocmask(SIG_BLOCK, &set, &oldset);
301 
302   // Create child processes that actually run the program for us...
303   if ((pid = fork()) == 0) {
304     // First child comes here, fork a second child and exit...
305     if (!fork()) {
306       // Second child comes here, redirect stdin/out/err to /dev/null...
307       close(0);
308       open("/dev/null", O_RDONLY);
309 
310       close(1);
311       open("/dev/null", O_WRONLY);
312 
313       close(2);
314       open("/dev/null", O_WRONLY);
315 
316       // Detach from the current process group...
317       setsid();
318 
319       // Run the program...
320       execv(program, argv);
321       _exit(0);
322     } else {
323       // First child gets here, exit immediately...
324       _exit(0);
325     }
326   } else if (pid < 0) {
327     // Restore signal handling...
328     sigprocmask(SIG_SETMASK, &oldset, NULL);
329 
330     // Return indicating failure...
331     return 0;
332   }
333 
334   // Wait for the first child to exit...
335   while (waitpid(pid, &status, 0) < 0) {
336     if (errno != EINTR) {
337       // Someone else grabbed the child status...
338       if (msg) snprintf(msg, msglen, "waitpid(%ld) failed: %s", (long)pid,
339                         strerror(errno));
340 
341       // Restore signal handling...
342       sigprocmask(SIG_SETMASK, &oldset, NULL);
343 
344       // Return indicating failure...
345       return 0;
346     }
347   }
348 
349   // Restore signal handling...
350   sigprocmask(SIG_SETMASK, &oldset, NULL);
351 
352   // Return indicating success...
353   return 1;
354 }
355 #endif // !WIN32
356 
357 
358 #ifdef TEST
359 //
360 // Test code...
361 //
362 
363 // Open the URI on the command-line...
main(int argc,char ** argv)364 int main(int argc, char **argv) {
365   char msg[1024];
366 
367 
368   if (argc != 2) {
369     puts("Usage: fl_open_uri URI");
370     return 1;
371   }
372 
373   if (!fl_open_uri(argv[1], msg, sizeof(msg))) {
374     puts(msg);
375     return 1;
376   } else return 0;
377 }
378 #endif // TEST
379 
380 
381 //
382 // End of "$Id: fl_open_uri.cxx 8063 2010-12-19 21:20:10Z matt $".
383 //
384