xref: /haiku/src/bin/urlwrapper.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
1 /*
2  * Copyright 2007, Haiku. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		François Revol, revol@free.fr
7  */
8 
9 /*
10  * urlwrapper: wraps URL mime types around command line apps
11  * or other apps that don't handle them directly.
12  */
13 #define DEBUG 1
14 
15 #include <Alert.h>
16 #include <Application.h>
17 #include <AppFileInfo.h>
18 #include <Mime.h>
19 #include <Message.h>
20 #include <TypeConstants.h>
21 #include <Roster.h>
22 #include <String.h>
23 #include <stdio.h>
24 #include <unistd.h>
25 #include <Debug.h>
26 
27 /* compile-time configuration */
28 #include "urlwrapper.h"
29 
30 const char *kAppSig = APP_SIGNATURE;
31 const char *kTrackerSig = "application/x-vnd.Be-TRAK";
32 
33 #ifdef __HAIKU__
34 const char *kTerminalSig = "application/x-vnd.Haiku-Terminal";
35 #else
36 const char *kTerminalSig = "application/x-vnd.Be-SHEL";
37 #endif
38 
39 #ifdef HANDLE_BESHARE
40 const char *kBeShareSig = "application/x-vnd.Sugoi-BeShare";
41 #endif
42 
43 #ifdef HANDLE_IM
44 const char *kIMSig = "application/x-vnd.m_eiman.sample_im_client";
45 #endif
46 
47 #ifdef HANDLE_VLC
48 const char *kVLCSig = "application/x-vnd.videolan-vlc";
49 #endif
50 
51 // TODO: make a public BUrl class for use by apps ?
52 class Url : public BString {
53 public:
54 		Url(const char *url) : BString(url) { fStatus = ParseAndSplit(); };
55 		~Url() {};
56 status_t	InitCheck() const { return fStatus; };
57 status_t	ParseAndSplit();
58 
59 bool		HasHost() const { return host.Length(); };
60 bool		HasPort() const { return port.Length(); };
61 bool		HasUser() const { return user.Length(); };
62 bool		HasPass() const { return pass.Length(); };
63 bool		HasPath() const { return path.Length(); };
64 BString		Proto() const { return BString(proto); };
65 BString		Full() const { return BString(full); }; // RFC1738's "sheme-part"
66 BString		Host() const { return BString(host); };
67 BString		Port() const { return BString(port); };
68 BString		User() const { return BString(user); };
69 BString		Pass() const { return BString(pass); };
70 
71 BString		proto;
72 BString		full;
73 BString		host;
74 BString		port;
75 BString		user;
76 BString		pass;
77 BString		path;
78 private:
79 status_t	fStatus;
80 };
81 
82 class UrlWrapperApp : public BApplication
83 {
84 public:
85 	UrlWrapperApp();
86 	~UrlWrapperApp();
87 status_t	SplitUrl(const char *url, BString &host, BString &port, BString &user, BString &pass, BString &path);
88 status_t	UnurlString(BString &s);
89 status_t	Warn(const char *url);
90 virtual void	RefsReceived(BMessage *msg);
91 virtual void	ArgvReceived(int32 argc, char **argv);
92 virtual void	ReadyToRun(void);
93 private:
94 
95 };
96 
97 // proto:[//]user:pass@host:port/path
98 status_t Url::ParseAndSplit()
99 {
100 	int32 v;
101 	BString left;
102 
103 	v = FindFirst(":");
104 	if (v < 0)
105 		return EINVAL;
106 
107 	// TODO: proto and host should be lowercased.
108 	// see http://en.wikipedia.org/wiki/URL_normalization
109 
110 	CopyInto(proto, 0, v);
111 	CopyInto(left, v + 1, Length() - v);
112 	// TODO: RFC1738 says the // part should indicate the uri follows the u:p@h:p/path convention, so it should be used to check for special cases.
113 	if (left.FindFirst("//") == 0)
114 		left.RemoveFirst("//");
115 	full = left;
116 
117 	// path part
118 	// actually some apps handle file://[host]/path
119 	// but I have no idea what proto it implies...
120 	// or maybe it's just to emphasize on "localhost".
121 	v = left.FindFirst("/");
122 	if (v == 0 || proto == "file") {
123 		path = left;
124 		return 0;
125 	}
126 	// some protos actually implies path if it's the only component
127 	if ((v < 0) && (proto == "beshare" || proto == "irc")) {
128 		path = left;
129 		return 0;
130 	}
131 
132 	if (v > -1) {
133 		left.MoveInto(path, v+1, left.Length()-v);
134 		left.Remove(v, 1);
135 	}
136 
137 	// user:pass@host
138 	v = left.FindFirst("@");
139 	if (v > -1) {
140 		left.MoveInto(user, 0, v);
141 		left.Remove(0, 1);
142 		v = user.FindFirst(":");
143 		if (v > -1) {
144 			user.MoveInto(pass, v, user.Length() - v);
145 			pass.Remove(0, 1);
146 		}
147 	} else if (proto == "finger") {
148 		// single component implies user
149 		// see also: http://www.subir.com/lynx/lynx_help/lynx_url_support.html
150 		user = left;
151 		return 0;
152 	}
153 
154 	// host:port
155 	v = left.FindFirst(":");
156 	if (v > -1) {
157 		left.MoveInto(port, v + 1, left.Length() - v);
158 		left.Remove(v, 1);
159 	}
160 
161 	// not much left...
162 	host = left;
163 
164 	return 0;
165 }
166 
167 status_t UrlWrapperApp::SplitUrl(const char *url, BString &host, BString &port, BString &user, BString &pass, BString &path)
168 {
169 	Url u(url);
170 	if (u.InitCheck() < 0)
171 		return u.InitCheck();
172 	host = u.host;
173 	port = u.port;
174 	user = u.user;
175 	pass = u.pass;
176 	path = u.path;
177 	return 0;
178 }
179 
180 UrlWrapperApp::UrlWrapperApp() : BApplication(kAppSig)
181 {
182 #if 0
183 	BMimeType mt(B_URL_TELNET);
184 	if (mt.InitCheck())
185 		return;
186 	if (!mt.IsInstalled()) {
187 		mt.Install();
188 	}
189 #endif
190 #if 0
191 	BAppFileInfo afi;
192 	if (!afi.Supports(&mt)) {
193 		//printf("adding support for telnet url\n");
194 		BMessage typemsg;
195 		typemsg.AddString("types", url_mime);
196 		afi.SetSupportedTypes(&typemsg, true);
197 	}
198 #endif
199 }
200 
201 UrlWrapperApp::~UrlWrapperApp()
202 {
203 }
204 
205 
206 
207 status_t UrlWrapperApp::UnurlString(BString &s)
208 {
209 	// TODO:WRITEME
210 	return B_OK;
211 }
212 
213 status_t UrlWrapperApp::Warn(const char *url)
214 {
215 	BString message("An application has requested the system to open the following url: \n");
216 	message << "\n" << url << "\n\n";
217 	message << "This type of urls has a potential security risk.\n";
218 	message << "Proceed anyway ?";
219 	BAlert *alert = new BAlert("Warning", message.String(), "Ok", "No", NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
220 	int32 v;
221 	v = alert->Go();
222 	if (v == 0)
223 		return B_OK;
224 	return B_ERROR;
225 }
226 
227 void UrlWrapperApp::RefsReceived(BMessage *msg)
228 {
229 	char buff[B_PATH_NAME_LENGTH];
230 	int32 index = 0;
231 	entry_ref ref;
232 	char *args[] = { "urlwrapper", buff, NULL };
233 	status_t err;
234 
235 	while (msg->FindRef("refs", index++, &ref) == B_OK) {
236 		BFile f(&ref, B_READ_ONLY);
237 		BNodeInfo ni(&f);
238 		BString mimetype;
239 		if (f.InitCheck() == B_OK && ni.InitCheck() == B_OK) {
240 			ni.GetType(mimetype.LockBuffer(B_MIME_TYPE_LENGTH));
241 			mimetype.UnlockBuffer();
242 #ifdef HANDLE_URL_FILES
243 			// http://filext.com/file-extension/URL
244 			if (mimetype == "wwwserver/redirection"
245 			 || mimetype == "application/internet-shortcut"
246 			 || mimetype == "application/x-url"
247 			 || mimetype == "message/external-body"
248 			 || mimetype == "text/url"
249 			 || mimetype == "text/x-url") {
250 				// http://www.cyanwerks.com/file-format-url.html
251 				off_t size;
252 				if (f.GetSize(&size) < B_OK)
253 					continue;
254 				BString contents, url;
255 				if (f.ReadAt(0LL, contents.LockBuffer(size), size) < B_OK)
256 					continue;
257 				while (contents.Length()) {
258 					BString line;
259 					int32 cr = contents.FindFirst('\n');
260 					if (cr < 0)
261 						cr = contents.Length();
262 					//contents.MoveInto(line, 0, cr);
263 					contents.CopyInto(line, 0, cr);
264 					contents.Remove(0, cr+1);
265 					line.RemoveAll("\r");
266 					if (!line.Length())
267 						continue;
268 					if (!line.ICompare("URL=", 4)) {
269 						line.MoveInto(url, 4, line.Length());
270 						break;
271 					}
272 				}
273 				if (url.Length()) {
274 					args[1] = (char *)url.String();
275 					//ArgvReceived(2, args);
276 					err = be_roster->Launch("application/x-vnd.Be.URL.http", 1, args+1);
277 					continue;
278 				}
279 			}
280 #endif
281 			/* eat everything as bookmark files */
282 #ifdef HANDLE_BOOKMARK_FILES
283 			if (f.ReadAttr("META:url", B_STRING_TYPE, 0LL, buff, B_PATH_NAME_LENGTH) > 0) {
284 				//ArgvReceived(2, args);
285 				err = be_roster->Launch("application/x-vnd.Be.URL.http", 1, args+1);
286 				continue;
287 			}
288 #endif
289 		}
290 	}
291 }
292 
293 void UrlWrapperApp::ArgvReceived(int32 argc, char **argv)
294 {
295 #if 0
296 	for (int i = 1; i < argc; i++) {
297 		//printf("argv[%d]=%s\n", i, argv[i]);
298 	}
299 #endif
300 	if (argc <= 1)
301 		return;
302 
303 	const char *failc = " || read -p 'Press any key'";
304 	const char *pausec = " ; read -p 'Press any key'";
305 	char *args[] = { "/bin/sh", "-c", NULL, NULL};
306 
307 	Url u(argv[1]);
308 	BString url = u.Full();
309 	if (u.InitCheck() < 0) {
310 		fprintf(stderr, "malformed url: '%s'\n", u.String());
311 		return;
312 	}
313 
314 	// XXX: debug
315 	PRINT(("PROTO='%s'\n", u.proto.String()));
316 	PRINT(("HOST='%s'\n", u.host.String()));
317 	PRINT(("PORT='%s'\n", u.port.String()));
318 	PRINT(("USER='%s'\n", u.user.String()));
319 	PRINT(("PASS='%s'\n", u.pass.String()));
320 	PRINT(("PATH='%s'\n", u.path.String()));
321 
322 	if (u.proto == "telnet") {
323 		BString cmd("telnet ");
324 		if (u.HasUser())
325 			cmd << "-l " << u.user << " ";
326 		cmd << u.host;
327 		PRINT(("CMD='%s'\n", cmd.String()));
328 		cmd << failc;
329 		args[2] = (char *)cmd.String();
330 		be_roster->Launch(kTerminalSig, 3, args);
331 		return;
332 	}
333 
334 	// see draft: http://tools.ietf.org/wg/secsh/draft-ietf-secsh-scp-sftp-ssh-uri/
335 	if (u.proto == "ssh") {
336 		BString cmd("ssh ");
337 
338 		if (u.HasUser())
339 			cmd << "-l " << u.user << " ";
340 		cmd << u.host;
341 		PRINT(("CMD='%s'\n", cmd.String()));
342 		cmd << failc;
343 		args[2] = (char *)cmd.String();
344 		be_roster->Launch(kTerminalSig, 3, args);
345 		// TODO: handle errors
346 		return;
347 	}
348 
349 	if (u.proto == "ftp") {
350 		BString cmd("ftp ");
351 
352 		/*
353 		if (user.Length())
354 			cmd << "-l " << user << " ";
355 		cmd << host;
356 		*/
357 		cmd << u.full;
358 		PRINT(("CMD='%s'\n", cmd.String()));
359 		cmd << failc;
360 		args[2] = (char *)cmd.String();
361 		be_roster->Launch(kTerminalSig, 3, args);
362 		// TODO: handle errors
363 		return;
364 	}
365 
366 	if (u.proto == "sftp") {
367 		BString cmd("sftp ");
368 
369 		//cmd << url;
370 		if (u.HasUser())
371 			cmd << u.user << "@";
372 		cmd << u.host;
373 		if (u.HasPath())
374 			cmd << ":" << u.path;
375 		PRINT(("CMD='%s'\n", cmd.String()));
376 		cmd << failc;
377 		args[2] = (char *)cmd.String();
378 		be_roster->Launch(kTerminalSig, 3, args);
379 		// TODO: handle errors
380 		return;
381 	}
382 
383 	if (u.proto == "finger") {
384 		BString cmd("/bin/finger ");
385 
386 		if (u.HasUser())
387 			cmd << u.user;
388 		if (u.HasHost() == 0)
389 			u.host = "127.0.0.1";
390 		cmd << "@" << u.host;
391 		PRINT(("CMD='%s'\n", cmd.String()));
392 		cmd << pausec;
393 		args[2] = (char *)cmd.String();
394 		be_roster->Launch(kTerminalSig, 3, args);
395 		// TODO: handle errors
396 		return;
397 	}
398 
399 #ifdef HANDLE_HTTP_WGET
400 	if (u.proto == "http") {
401 		BString cmd("/bin/wget ");
402 
403 		//cmd << url;
404 		if (u.HasUser())
405 			cmd << u.user << "@";
406 		cmd << u.full;
407 		PRINT(("CMD='%s'\n", cmd.String()));
408 		cmd << pausec;
409 		args[2] = (char *)cmd.String();
410 		be_roster->Launch(kTerminalSig, 3, args);
411 		// TODO: handle errors
412 		return;
413 	}
414 #endif
415 
416 #ifdef HANDLE_FILE
417 	if (u.proto == "file") {
418 		BMessage m(B_REFS_RECEIVED);
419 		entry_ref ref;
420 		// UnurlString(path);
421 		if (get_ref_for_path(u.path.String(), &ref) < B_OK)
422 			return;
423 		m.AddRef("refs", &ref);
424 		be_roster->Launch(kTrackerSig, &m);
425 		return;
426 	}
427 #endif
428 
429 #ifdef HANDLE_QUERY
430 	// XXX:TODO: unencode the formula + split options
431 	if (u.proto == "query") {
432 		// mktemp ?
433 		BString qname("/tmp/query-url-temp-");
434 		qname << getpid() << "-" << system_time();
435 		BFile query(qname.String(), O_CREAT|O_EXCL);
436 		// XXX: should check for failure
437 
438 		BString s;
439 		int32 v;
440 
441 		// TODO:
442 		// UnurlString(full);
443 		// TODO: handle options (list of attrs in the column, ...)
444 
445 		v = 'qybF'; // QuerY By Formula XXX: any #define for that ?
446 		query.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0LL, &v, sizeof(v));
447 		s = "TextControl";
448 		query.WriteAttr("_trk/focusedView", B_STRING_TYPE, 0LL, s.String(), s.Length()+1);
449 		s = u.full;
450 		query.WriteAttr("_trk/qryinitstr", B_STRING_TYPE, 0LL, s.String(), s.Length()+1);
451 		query.WriteAttr("_trk/qrystr", B_STRING_TYPE, 0LL, s.String(), s.Length()+1);
452 		s = "application/x-vnd.Be-query";
453 		query.WriteAttr("BEOS:TYPE", 'MIMS', 0LL, s.String(), s.Length()+1);
454 
455 
456 		BEntry e(qname.String());
457 		entry_ref er;
458 		if (e.GetRef(&er) >= B_OK)
459 			be_roster->Launch(&er);
460 		return;
461 	}
462 #endif
463 
464 #ifdef HANDLE_SH
465 	if (u.proto == "sh") {
466 		BString cmd(u.Full());
467 		if (Warn(u.String()) != B_OK)
468 			return;
469 		PRINT(("CMD='%s'\n", cmd.String()));
470 		cmd << pausec;
471 		args[2] = (char *)cmd.String();
472 		be_roster->Launch(kTerminalSig, 3, args);
473 		// TODO: handle errors
474 		return;
475 	}
476 #endif
477 
478 #ifdef HANDLE_BESHARE
479 	if (u.proto == "beshare") {
480 		team_id team;
481 		BMessenger msgr(kBeShareSig);
482 		// if no instance is running, or we want a specific server, start it.
483 		if (!msgr.IsValid() || u.HasHost()) {
484 			be_roster->Launch(kBeShareSig, (BMessage *)NULL, &team);
485 			msgr = BMessenger(NULL, team);
486 		}
487 		if (u.HasHost()) {
488 			BMessage mserver('serv');
489 			mserver.AddString("server", u.host);
490 			msgr.SendMessage(&mserver);
491 
492 		}
493 		if (u.HasPath()) {
494 			BMessage mquery('quer');
495 			mquery.AddString("query", u.path);
496 			msgr.SendMessage(&mquery);
497 		}
498 		// TODO: handle errors
499 		return;
500 	}
501 #endif
502 
503 #ifdef HANDLE_IM
504 	if (u.proto == "icq" || u.proto == "msn") {
505 		// TODO
506 		team_id team;
507 		be_roster->Launch(kIMSig, (BMessage *)NULL, &team);
508 		BMessenger msgr(NULL, team);
509 		if (u.HasHost()) {
510 			BMessage mserver(B_REFS_RECEIVED);
511 			mserver.AddString("server", u.host);
512 			msgr.SendMessage(&httpmserver);
513 
514 		}
515 		// TODO: handle errors
516 		return;
517 	}
518 #endif
519 
520 #ifdef HANDLE_VLC
521 	if (u.proto == "mms" || u.proto == "rtp" || u.proto == "rtsp") {
522 		args[0] = "vlc";
523 		args[1] = (char *)u.String();
524 		be_roster->Launch(kVLCSig, 2, args);
525 		return;
526 	}
527 #endif
528 
529 #ifdef HANDLE_AUDIO
530 	// TODO
531 #endif
532 
533 	// vnc: ?
534 	// irc: ?
535 	//
536 	// svn: ?
537 	// cvs: ?
538 	// smb: cifsmount ?
539 	// nfs: mount_nfs ?
540 	//
541 	// mailto: ? but BeMail & Beam both handle it already (not fully though).
542 	//
543 	// itps: pcast: podcast: s//http/ + parse xml to get url to mp3 stream...
544 	// audio: s//http:/ + default MediaPlayer -- see http://forums.winamp.com/showthread.php?threadid=233130
545 	//
546 	// gps: ? I should submit an RFC for that one :)
547 
548 }
549 
550 void UrlWrapperApp::ReadyToRun(void)
551 {
552 	Quit();
553 }
554 
555 int main(int argc, char **argv)
556 {
557 	UrlWrapperApp app;
558 	if (be_app)
559 		app.Run();
560 	return 0;
561 }
562