xref: /haiku/src/bin/urlwrapper.cpp (revision 6c4a44e36ba846c54467103f884d65dfa13e7fcb)
1 /*
2  * Copyright 2007-2010 Haiku Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		François Revol, revol@free.fr
7  *		Jonas Sundström, jonas@kirilla.com
8  *		Stephan Aßmus <superstippi@gmx.de>
9  */
10 
11 /*
12  * urlwrapper: wraps URL mime types around command line apps
13  * or other apps that don't handle them directly.
14  */
15 #define DEBUG 0
16 
17 #include <ctype.h>
18 #include <stdio.h>
19 #include <unistd.h>
20 
21 #include <fs_volume.h>
22 #include <Alert.h>
23 #include <Debug.h>
24 #include <NodeInfo.h>
25 #include <Roster.h>
26 #include <String.h>
27 #include <Url.h>
28 
29 #include "urlwrapper.h"
30 
31 
32 const char* kAppSig = "application/x-vnd.Haiku-urlwrapper";
33 const char* kTrackerSig = "application/x-vnd.Be-TRAK";
34 const char* kTerminalSig = "application/x-vnd.Haiku-Terminal";
35 const char* kBeShareSig = "application/x-vnd.Sugoi-BeShare";
36 const char* kIMSig = "application/x-vnd.m_eiman.sample_im_client";
37 const char* kVLCSig = "application/x-vnd.videolan-vlc";
38 
39 const char* kURLHandlerSigBase = "application/x-vnd.Be.URL.";
40 
41 
42 UrlWrapper::UrlWrapper() : BApplication(kAppSig)
43 {
44 }
45 
46 
47 UrlWrapper::~UrlWrapper()
48 {
49 }
50 
51 
52 status_t
53 UrlWrapper::_Warn(const char* url)
54 {
55 	BString message("An application has requested the system to open the "
56 		"following url: \n");
57 	message << "\n" << url << "\n\n";
58 	message << "This type of URL has a potential security risk.\n";
59 	message << "Proceed anyway?";
60 	BAlert* alert = new BAlert("Warning", message.String(), "Proceed", "Stop", NULL,
61 		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
62 	alert->SetShortcut(1, B_ESCAPE);
63 	int32 button;
64 	button = alert->Go();
65 	if (button == 0)
66 		return B_OK;
67 
68 	return B_ERROR;
69 }
70 
71 
72 void
73 UrlWrapper::RefsReceived(BMessage* msg)
74 {
75 	char buff[B_PATH_NAME_LENGTH];
76 	int32 index = 0;
77 	entry_ref ref;
78 	char* args[] = { const_cast<char*>("urlwrapper"), buff, NULL };
79 	status_t err;
80 
81 	while (msg->FindRef("refs", index++, &ref) == B_OK) {
82 		BFile f(&ref, B_READ_ONLY);
83 		BNodeInfo ni(&f);
84 		BString mimetype;
85 		BString extension(ref.name);
86 		extension.Remove(0, extension.FindLast('.') + 1);
87 		if (f.InitCheck() == B_OK && ni.InitCheck() == B_OK) {
88 			ni.GetType(mimetype.LockBuffer(B_MIME_TYPE_LENGTH));
89 			mimetype.UnlockBuffer();
90 
91 			// Internet Explorer Shortcut
92 			if (mimetype == "text/x-url" || extension == "url") {
93 				// http://filext.com/file-extension/URL
94 				// http://www.cyanwerks.com/file-format-url.html
95 				off_t size;
96 				if (f.GetSize(&size) < B_OK)
97 					continue;
98 				BString contents;
99 				BString url;
100 				if (f.ReadAt(0LL, contents.LockBuffer(size), size) < B_OK)
101 					continue;
102 				contents.UnlockBuffer();
103 				while (contents.Length()) {
104 					BString line;
105 					int32 cr = contents.FindFirst('\n');
106 					if (cr < 0)
107 						cr = contents.Length();
108 					//contents.MoveInto(line, 0, cr);
109 					contents.CopyInto(line, 0, cr);
110 					contents.Remove(0, cr+1);
111 					line.RemoveAll("\r");
112 					if (!line.Length())
113 						continue;
114 					if (!line.ICompare("URL=", 4)) {
115 						line.MoveInto(url, 4, line.Length());
116 						break;
117 					}
118 				}
119 				if (url.Length()) {
120 					BPrivate::Support::BUrl u(url.String());
121 					args[1] = (char*)u.String();
122 					mimetype = kURLHandlerSigBase;
123 					mimetype += u.Proto();
124 					err = be_roster->Launch(mimetype.String(), 1, args + 1);
125 					if (err != B_OK && err != B_ALREADY_RUNNING)
126 						err = be_roster->Launch(kAppSig, 1, args + 1);
127 					continue;
128 				}
129 			}
130 			if (mimetype == "text/x-webloc" || extension == "webloc") {
131 				// OSX url shortcuts
132 				// XML file + resource fork
133 				off_t size;
134 				if (f.GetSize(&size) < B_OK)
135 					continue;
136 				BString contents;
137 				BString url;
138 				if (f.ReadAt(0LL, contents.LockBuffer(size), size) < B_OK)
139 					continue;
140 				contents.UnlockBuffer();
141 				int state = 0;
142 				while (contents.Length()) {
143 					BString line;
144 					int32 cr = contents.FindFirst('\n');
145 					if (cr < 0)
146 						cr = contents.Length();
147 					contents.CopyInto(line, 0, cr);
148 					contents.Remove(0, cr+1);
149 					line.RemoveAll("\r");
150 					if (!line.Length())
151 						continue;
152 					int32 s, e;
153 					switch (state) {
154 						case 0:
155 							if (!line.ICompare("<?xml", 5))
156 								state = 1;
157 							break;
158 						case 1:
159 							if (!line.ICompare("<plist", 6))
160 								state = 2;
161 							break;
162 						case 2:
163 							if (!line.ICompare("<dict>", 6))
164 								state = 3;
165 							break;
166 						case 3:
167 							if (line.IFindFirst("<key>URL</key>") > -1)
168 								state = 4;
169 							break;
170 						case 4:
171 							if ((s = line.IFindFirst("<string>")) > -1
172 								&& (e = line.IFindFirst("</string>")) > s) {
173 								state = 5;
174 								s += 8;
175 								line.MoveInto(url, s, e - s);
176 								break;
177 							} else
178 								state = 3;
179 							break;
180 						default:
181 							break;
182 					}
183 					if (state == 5) {
184 						break;
185 					}
186 				}
187 				if (url.Length()) {
188 					BPrivate::Support::BUrl u(url.String());
189 					args[1] = (char*)u.String();
190 					mimetype = kURLHandlerSigBase;
191 					mimetype += u.Proto();
192 					err = be_roster->Launch(mimetype.String(), 1, args + 1);
193 					if (err != B_OK && err != B_ALREADY_RUNNING)
194 						err = be_roster->Launch(kAppSig, 1, args + 1);
195 					continue;
196 				}
197 			}
198 
199 			// NetPositive Bookmark or any file with a META:url attribute
200 			if (f.ReadAttr("META:url", B_STRING_TYPE, 0LL, buff,
201 				B_PATH_NAME_LENGTH) > 0) {
202 				BPrivate::Support::BUrl u(buff);
203 				args[1] = (char*)u.String();
204 				mimetype = kURLHandlerSigBase;
205 				mimetype += u.Proto();
206 				err = be_roster->Launch(mimetype.String(), 1, args + 1);
207 				if (err != B_OK && err != B_ALREADY_RUNNING)
208 					err = be_roster->Launch(kAppSig, 1, args + 1);
209 				continue;
210 			}
211 		}
212 	}
213 }
214 
215 
216 void
217 UrlWrapper::ArgvReceived(int32 argc, char** argv)
218 {
219 	if (argc <= 1)
220 		return;
221 
222 	const char* failc = " || read -p 'Press any key'";
223 	const char* pausec = " ; read -p 'Press any key'";
224 	char* args[] = { (char *)"/bin/sh", (char *)"-c", NULL, NULL};
225 
226 	BPrivate::Support::BUrl url(argv[1]);
227 
228 	BString full = url.Full();
229 	BString proto = url.Proto();
230 	BString host = url.Host();
231 	BString port = url.Port();
232 	BString user = url.User();
233 	BString pass = url.Pass();
234 	BString path = url.Path();
235 
236 	if (url.InitCheck() < 0) {
237 		fprintf(stderr, "malformed url: '%s'\n", url.String());
238 		return;
239 	}
240 
241 	// XXX: debug
242 	PRINT(("PROTO='%s'\n", proto.String()));
243 	PRINT(("HOST='%s'\n", host.String()));
244 	PRINT(("PORT='%s'\n", port.String()));
245 	PRINT(("USER='%s'\n", user.String()));
246 	PRINT(("PASS='%s'\n", pass.String()));
247 	PRINT(("PATH='%s'\n", path.String()));
248 
249 	if (proto == "about") {
250 		app_info info;
251 		BString sig;
252 		// BUrl could get an accessor for the full - proto part...
253 		sig = host << "/" << path;
254 		BMessage msg(B_ABOUT_REQUESTED);
255 		if (be_roster->GetAppInfo(sig.String(), &info) == B_OK) {
256 			BMessenger msgr(sig.String());
257 			msgr.SendMessage(&msg);
258 			return;
259 		}
260 		if (be_roster->Launch(sig.String(), &msg) == B_OK)
261 			return;
262 		be_roster->Launch("application/x-vnd.Haiku-About");
263 		return;
264 	}
265 
266 	if (proto == "telnet") {
267 		BString cmd("telnet ");
268 		if (url.HasUser())
269 			cmd << "-l " << user << " ";
270 		cmd << host;
271 		if (url.HasPort())
272 			cmd << " " << port;
273 		PRINT(("CMD='%s'\n", cmd.String()));
274 		cmd << failc;
275 		args[2] = (char*)cmd.String();
276 		be_roster->Launch(kTerminalSig, 3, args);
277 		return;
278 	}
279 
280 	// see draft:
281 	// http://tools.ietf.org/wg/secsh/draft-ietf-secsh-scp-sftp-ssh-uri/
282 	if (proto == "ssh") {
283 		BString cmd("ssh ");
284 
285 		if (url.HasUser())
286 			cmd << "-l " << user << " ";
287 		if (url.HasPort())
288 			cmd << "-oPort=" << port << " ";
289 		cmd << host;
290 		PRINT(("CMD='%s'\n", cmd.String()));
291 		cmd << failc;
292 		args[2] = (char*)cmd.String();
293 		be_roster->Launch(kTerminalSig, 3, args);
294 		// TODO: handle errors
295 		return;
296 	}
297 
298 	if (proto == "ftp") {
299 		BString cmd("ftp ");
300 
301 		cmd << proto << "://";
302 		/*
303 		if (user.Length())
304 			cmd << "-l " << user << " ";
305 		cmd << host;
306 		*/
307 		cmd << full;
308 		PRINT(("CMD='%s'\n", cmd.String()));
309 		cmd << failc;
310 		args[2] = (char*)cmd.String();
311 		be_roster->Launch(kTerminalSig, 3, args);
312 		// TODO: handle errors
313 		return;
314 	}
315 
316 	if (proto == "sftp") {
317 		BString cmd("sftp ");
318 
319 		//cmd << url;
320 		if (url.HasPort())
321 			cmd << "-oPort=" << port << " ";
322 		if (url.HasUser())
323 			cmd << user << "@";
324 		cmd << host;
325 		if (url.HasPath())
326 			cmd << ":" << 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 (proto == "finger") {
336 		BString cmd("/bin/finger ");
337 
338 		if (url.HasUser())
339 			cmd << user;
340 		if (url.HasHost() == 0)
341 			host = "127.0.0.1";
342 		cmd << "@" << 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 	if (proto == "http" || proto == "https" /*|| proto == "ftp"*/) {
352 		BString cmd("/bin/wget ");
353 
354 		//cmd << url;
355 		cmd << proto << "://";
356 		if (url.HasUser())
357 			cmd << user << "@";
358 		cmd << 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 
367 	if (proto == "file") {
368 		BMessage m(B_REFS_RECEIVED);
369 		entry_ref ref;
370 		_DecodeUrlString(path);
371 		if (get_ref_for_path(path.String(), &ref) < B_OK)
372 			return;
373 		m.AddRef("refs", &ref);
374 		be_roster->Launch(kTrackerSig, &m);
375 		return;
376 	}
377 
378 	// XXX:TODO: split options
379 	if (proto == "query") {
380 		// mktemp ?
381 		BString qname("/tmp/query-url-temp-");
382 		qname << getpid() << "-" << system_time();
383 		BFile query(qname.String(), O_CREAT|O_EXCL);
384 		// XXX: should check for failure
385 
386 		BString s;
387 		int32 v;
388 
389 		_DecodeUrlString(full);
390 		// TODO: handle options (list of attrs in the column, ...)
391 
392 		v = 'qybF'; // QuerY By Formula XXX: any #define for that ?
393 		query.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0LL, &v, sizeof(v));
394 		s = "TextControl";
395 		query.WriteAttr("_trk/focusedView", B_STRING_TYPE, 0LL, s.String(),
396 			s.Length()+1);
397 		s = full;
398 		PRINT(("QUERY='%s'\n", s.String()));
399 		query.WriteAttr("_trk/qryinitstr", B_STRING_TYPE, 0LL, s.String(),
400 			s.Length()+1);
401 		query.WriteAttr("_trk/qrystr", B_STRING_TYPE, 0LL, s.String(),
402 			s.Length()+1);
403 		s = "application/x-vnd.Be-query";
404 		query.WriteAttr("BEOS:TYPE", 'MIMS', 0LL, s.String(), s.Length()+1);
405 
406 
407 		BEntry e(qname.String());
408 		entry_ref er;
409 		if (e.GetRef(&er) >= B_OK)
410 			be_roster->Launch(&er);
411 		return;
412 	}
413 
414 	if (proto == "sh") {
415 		BString cmd(full);
416 		if (_Warn(url.String()) != B_OK)
417 			return;
418 		PRINT(("CMD='%s'\n", cmd.String()));
419 		cmd << pausec;
420 		args[2] = (char*)cmd.String();
421 		be_roster->Launch(kTerminalSig, 3, args);
422 		// TODO: handle errors
423 		return;
424 	}
425 
426 	if (proto == "beshare") {
427 		team_id team;
428 		BMessenger msgr(kBeShareSig);
429 		// if no instance is running, or we want a specific server, start it.
430 		if (!msgr.IsValid() || url.HasHost()) {
431 			be_roster->Launch(kBeShareSig, (BMessage*)NULL, &team);
432 			msgr = BMessenger(NULL, team);
433 		}
434 		if (url.HasHost()) {
435 			BMessage mserver('serv');
436 			mserver.AddString("server", host);
437 			msgr.SendMessage(&mserver);
438 
439 		}
440 		if (url.HasPath()) {
441 			BMessage mquery('quer');
442 			mquery.AddString("query", path);
443 			msgr.SendMessage(&mquery);
444 		}
445 		// TODO: handle errors
446 		return;
447 	}
448 
449 	if (proto == "icq" || proto == "msn") {
450 		// TODO
451 		team_id team;
452 		be_roster->Launch(kIMSig, (BMessage*)NULL, &team);
453 		BMessenger msgr(NULL, team);
454 		if (url.HasHost()) {
455 			BMessage mserver(B_REFS_RECEIVED);
456 			mserver.AddString("server", host);
457 			msgr.SendMessage(&mserver);
458 
459 		}
460 		// TODO: handle errors
461 		return;
462 	}
463 
464 	if (proto == "mms" || proto == "rtp" || proto == "rtsp") {
465 		args[0] = (char*)url.String();
466 		be_roster->Launch(kVLCSig, 1, args);
467 		return;
468 	}
469 
470 	if (proto == "nfs") {
471 		BString parameter(host);
472 		_DecodeUrlString(path);
473 		if (url.HasPort())
474 			parameter << ":" << port;
475 		//XXX: should not always be absolute! FIXME
476 		parameter << ":/" << path;
477 		BString prettyPath(path);
478 		prettyPath.Remove(0, prettyPath.FindLast("/") + 1);
479 		if (path == "" || path == "/")
480 			prettyPath = "root";
481 		prettyPath << " on " << host;
482 		prettyPath.Prepend("/");
483 		if (mkdir(prettyPath.String(), 0755) < 0) {
484 			perror("mkdir");
485 			return;
486 		}
487 		dev_t volume;
488 		uint32 flags = 0;
489 		fprintf(stderr, "parms:'%s'\n", parameter.String());
490 		volume = fs_mount_volume(prettyPath.String(), NULL, "nfs4", flags,
491 			parameter.String());
492 		if (volume < B_OK) {
493 			fprintf(stderr, "fs_mount_volume: %s\n", strerror(volume));
494 			return;
495 		}
496 
497 		BMessage m(B_REFS_RECEIVED);
498 		entry_ref ref;
499 		if (get_ref_for_path(prettyPath.String(), &ref) < B_OK)
500 			return;
501 		m.AddRef("refs", &ref);
502 		be_roster->Launch(kTrackerSig, &m);
503 		return;
504 	}
505 
506 	/*
507 
508 	More ?
509 	cf. http://en.wikipedia.org/wiki/URI_scheme
510 	cf. http://www.iana.org/assignments/uri-schemes.html
511 
512 	Audio: (SoundPlay specific, identical to http:// to a shoutcast server)
513 
514 	vnc: ?
515 	irc: ?
516 	im: http://tools.ietf.org/html/rfc3860
517 
518 	svn: handled by checkitout
519 	cvs: handled by checkitout
520 	git: handled by checkitout
521 	rsync: handled by checkitout - http://tools.ietf.org/html/rfc5781
522 
523 	smb: cifsmount ?
524 	nfs: mount_nfs ? http://tools.ietf.org/html/rfc2224
525 	ipp: http://tools.ietf.org/html/rfc3510
526 
527 	mailto: ? Mail & Beam both handle it already (not fully though).
528 	imap: to describe mail accounts ? http://tools.ietf.org/html/rfc5092
529 	pop: http://tools.ietf.org/html/rfc2384
530 	mid: cid: as per RFC 2392
531 	http://www.rfc-editor.org/rfc/rfc2392.txt query MAIL:cid
532 	message:<MID> http://daringfireball.net/2007/12/message_urls_leopard_mail
533 
534 	itps: pcast: podcast: s//http/ + parse xml to get url to mp3 stream...
535 	audio: s//http:/ + default MediaPlayer
536 	-- see http://forums.winamp.com/showthread.php?threadid=233130
537 
538 	gps: ? I should submit an RFC for that one :)
539 
540 	webcal: (is http: to .ics file)
541 
542 	data: (but it's dangerous)
543 
544 	*/
545 
546 
547 }
548 
549 
550 status_t
551 UrlWrapper::_DecodeUrlString(BString& string)
552 {
553 	// TODO: check for %00 and bail out!
554 	int32 length = string.Length();
555 	int i;
556 	for (i = 0; string[i] && i < length - 2; i++) {
557 		if (string[i] == '%' && isxdigit(string[i+1])
558 			&& isxdigit(string[i+2])) {
559 			int c;
560 			sscanf(string.String() + i + 1, "%02x", &c);
561 			string.Remove(i, 3);
562 			string.Insert((char)c, 1, i);
563 			length -= 2;
564 		}
565 	}
566 
567 	return B_OK;
568 }
569 
570 
571 void
572 UrlWrapper::ReadyToRun(void)
573 {
574 	Quit();
575 }
576 
577 
578 // #pragma mark
579 
580 
581 int main(int argc, char** argv)
582 {
583 	UrlWrapper app;
584 	if (be_app)
585 		app.Run();
586 	return 0;
587 }
588 
589