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