xref: /haiku/src/bin/urlwrapper.cpp (revision 2613144749c757a35f2247cda65e32b7b98e2518)
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 
UrlWrapper()42 UrlWrapper::UrlWrapper() : BApplication(kAppSig)
43 {
44 }
45 
46 
~UrlWrapper()47 UrlWrapper::~UrlWrapper()
48 {
49 }
50 
51 
52 status_t
_Warn(const char * url)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",
61 		NULL, 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
RefsReceived(BMessage * msg)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 					BUrl u(url.String());
121 					args[1] = (char*)u.UrlString().String();
122 					mimetype = kURLHandlerSigBase;
123 					mimetype += u.Protocol();
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 				char *buffer = contents.LockBuffer(size);
139 				const char bplist_match[] = "bplist00\xd1\x01\x02SURL_\x10";
140 				if (f.ReadAt(0LL, buffer, size) < B_OK)
141 					continue;
142 				printf("webloc\n");
143 				if (size > (sizeof(bplist_match) + 2)
144 					&& !strncmp(buffer, bplist_match,
145 						sizeof(bplist_match) - 1)) {
146 					// binary plist, let's be crude
147 					uint8 len = buffer[sizeof(bplist_match) - 1];
148 					url.SetTo(buffer + sizeof(bplist_match), len);
149 					*buffer = 0; // make sure we don't try to interpret as xml
150 				}
151 				contents.UnlockBuffer();
152 				int state = 0;
153 				while (contents.Length()) {
154 					BString line;
155 					int32 cr = contents.FindFirst('\n');
156 					if (cr < 0)
157 						cr = contents.Length();
158 					contents.CopyInto(line, 0, cr);
159 					contents.Remove(0, cr+1);
160 					line.RemoveAll("\r");
161 					if (!line.Length())
162 						continue;
163 					int32 s, e;
164 					switch (state) {
165 						case 0:
166 							if (!line.ICompare("<?xml", 5))
167 								state = 1;
168 							break;
169 						case 1:
170 							if (!line.ICompare("<plist", 6))
171 								state = 2;
172 							break;
173 						case 2:
174 							if (!line.ICompare("<dict>", 6))
175 								state = 3;
176 							break;
177 						case 3:
178 							if (line.IFindFirst("<key>URL</key>") > -1)
179 								state = 4;
180 							break;
181 						case 4:
182 							if ((s = line.IFindFirst("<string>")) > -1
183 								&& (e = line.IFindFirst("</string>")) > s) {
184 								state = 5;
185 								s += 8;
186 								line.MoveInto(url, s, e - s);
187 								break;
188 							} else
189 								state = 3;
190 							break;
191 						default:
192 							break;
193 					}
194 					if (state == 5) {
195 						break;
196 					}
197 				}
198 				if (url.Length()) {
199 					BUrl u(url.String());
200 					args[1] = (char*)u.UrlString().String();
201 					mimetype = kURLHandlerSigBase;
202 					mimetype += u.Protocol();
203 					err = be_roster->Launch(mimetype.String(), 1, args + 1);
204 					if (err != B_OK && err != B_ALREADY_RUNNING)
205 						err = be_roster->Launch(kAppSig, 1, args + 1);
206 					continue;
207 				}
208 			}
209 
210 			// NetPositive Bookmark or any file with a META:url attribute
211 			if (f.ReadAttr("META:url", B_STRING_TYPE, 0LL, buff,
212 				B_PATH_NAME_LENGTH) > 0) {
213 				BUrl u(buff);
214 				args[1] = (char*)u.UrlString().String();
215 				mimetype = kURLHandlerSigBase;
216 				mimetype += u.Protocol();
217 				err = be_roster->Launch(mimetype.String(), 1, args + 1);
218 				if (err != B_OK && err != B_ALREADY_RUNNING)
219 					err = be_roster->Launch(kAppSig, 1, args + 1);
220 				continue;
221 			}
222 		}
223 	}
224 }
225 
226 
227 void
ArgvReceived(int32 argc,char ** argv)228 UrlWrapper::ArgvReceived(int32 argc, char** argv)
229 {
230 	if (argc <= 1)
231 		return;
232 
233 	const char* failc = " || read -p 'Press any key'";
234 	const char* pausec = " ; read -p 'Press any key'";
235 	char* args[] = { (char *)"/bin/sh", (char *)"-c", NULL, NULL};
236 	status_t err;
237 
238 	BUrl url(argv[1]);
239 
240 	BString full = BUrl(url).SetProtocol(BString()).UrlString();
241 	BString proto = url.Protocol();
242 	BString host = url.Host();
243 	BString port = BString() << url.Port();
244 	BString user = url.UserInfo();
245 	BString pass = url.Password();
246 	BString path = url.Path();
247 
248 	if (!url.IsValid()) {
249 		fprintf(stderr, "malformed url: '%s'\n", url.UrlString().String());
250 		return;
251 	}
252 
253 	// XXX: debug
254 	PRINT(("PROTO='%s'\n", proto.String()));
255 	PRINT(("HOST='%s'\n", host.String()));
256 	PRINT(("PORT='%s'\n", port.String()));
257 	PRINT(("USER='%s'\n", user.String()));
258 	PRINT(("PASS='%s'\n", pass.String()));
259 	PRINT(("PATH='%s'\n", path.String()));
260 
261 	if (proto == "about") {
262 		app_info info;
263 		BString sig;
264 		// BUrl could get an accessor for the full - proto part...
265 		sig = host << "/" << path;
266 		BMessage msg(B_ABOUT_REQUESTED);
267 		if (be_roster->GetAppInfo(sig.String(), &info) == B_OK) {
268 			BMessenger msgr(sig.String());
269 			msgr.SendMessage(&msg);
270 			return;
271 		}
272 		if (be_roster->Launch(sig.String(), &msg) == B_OK)
273 			return;
274 		be_roster->Launch("application/x-vnd.Haiku-About");
275 		return;
276 	}
277 
278 	if (proto == "telnet") {
279 		BString cmd("telnet ");
280 		if (url.HasUserInfo())
281 			cmd << "-l " << user << " ";
282 		cmd << host;
283 		if (url.HasPort())
284 			cmd << " " << port;
285 		PRINT(("CMD='%s'\n", cmd.String()));
286 		cmd << failc;
287 		args[2] = (char*)cmd.String();
288 		be_roster->Launch(kTerminalSig, 3, args);
289 		return;
290 	}
291 
292 	// see draft:
293 	// http://tools.ietf.org/wg/secsh/draft-ietf-secsh-scp-sftp-ssh-uri/
294 	if (proto == "ssh") {
295 		BString cmd("ssh ");
296 
297 		if (url.HasUserInfo())
298 			cmd << "-l " << user << " ";
299 		if (url.HasPort())
300 			cmd << "-oPort=" << port << " ";
301 		cmd << host;
302 		PRINT(("CMD='%s'\n", cmd.String()));
303 		cmd << failc;
304 		args[2] = (char*)cmd.String();
305 		be_roster->Launch(kTerminalSig, 3, args);
306 		// TODO: handle errors
307 		return;
308 	}
309 
310 	if (proto == "ftp") {
311 		BString cmd("ftp ");
312 
313 		cmd << proto << "://";
314 		/*
315 		if (user.Length())
316 			cmd << "-l " << user << " ";
317 		cmd << host;
318 		*/
319 		cmd << full;
320 		PRINT(("CMD='%s'\n", cmd.String()));
321 		cmd << failc;
322 		args[2] = (char*)cmd.String();
323 		be_roster->Launch(kTerminalSig, 3, args);
324 		// TODO: handle errors
325 		return;
326 	}
327 
328 	if (proto == "sftp") {
329 		BString cmd("sftp ");
330 
331 		//cmd << url;
332 		if (url.HasPort())
333 			cmd << "-oPort=" << port << " ";
334 		if (url.HasUserInfo())
335 			cmd << user << "@";
336 		cmd << host;
337 		if (url.HasPath())
338 			cmd << ":" << path;
339 		PRINT(("CMD='%s'\n", cmd.String()));
340 		cmd << failc;
341 		args[2] = (char*)cmd.String();
342 		be_roster->Launch(kTerminalSig, 3, args);
343 		// TODO: handle errors
344 		return;
345 	}
346 
347 	if (proto == "finger") {
348 		BString cmd("/bin/finger ");
349 
350 		if (url.HasUserInfo())
351 			cmd << user;
352 		if (url.HasHost() == 0)
353 			host = "127.0.0.1";
354 		cmd << "@" << host;
355 		PRINT(("CMD='%s'\n", cmd.String()));
356 		cmd << pausec;
357 		args[2] = (char*)cmd.String();
358 		be_roster->Launch(kTerminalSig, 3, args);
359 		// TODO: handle errors
360 		return;
361 	}
362 
363 	if (proto == "http" || proto == "https" /*|| proto == "ftp"*/) {
364 		BString cmd("/bin/wget ");
365 
366 		//cmd << url;
367 		cmd << proto << "://";
368 		if (url.HasUserInfo())
369 			cmd << user << "@";
370 		cmd << full;
371 		PRINT(("CMD='%s'\n", cmd.String()));
372 		cmd << pausec;
373 		args[2] = (char*)cmd.String();
374 		be_roster->Launch(kTerminalSig, 3, args);
375 		// TODO: handle errors
376 		return;
377 	}
378 
379 	if (proto == "file") {
380 		BMessage m(B_REFS_RECEIVED);
381 		entry_ref ref;
382 		_DecodeUrlString(path);
383 		if (get_ref_for_path(path.String(), &ref) < B_OK)
384 			return;
385 		m.AddRef("refs", &ref);
386 		be_roster->Launch(kTrackerSig, &m);
387 		return;
388 	}
389 
390 	// XXX:TODO: split options
391 	if (proto == "query") {
392 		// mktemp ?
393 		BString qname("/tmp/query-url-temp-");
394 		qname << getpid() << "-" << system_time();
395 		BFile query(qname.String(), O_CREAT|O_EXCL);
396 		// XXX: should check for failure
397 
398 		BString s;
399 		int32 v;
400 
401 		_DecodeUrlString(full);
402 		// TODO: handle options (list of attrs in the column, ...)
403 
404 		v = 'qybF'; // QuerY By Formula XXX: any #define for that ?
405 		query.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0LL, &v, sizeof(v));
406 		s = "TextControl";
407 		query.WriteAttr("_trk/focusedView", B_STRING_TYPE, 0LL, s.String(),
408 			s.Length()+1);
409 		s = full;
410 		PRINT(("QUERY='%s'\n", s.String()));
411 		query.WriteAttr("_trk/qryinitstr", B_STRING_TYPE, 0LL, s.String(),
412 			s.Length()+1);
413 		query.WriteAttr("_trk/qrystr", B_STRING_TYPE, 0LL, s.String(),
414 			s.Length()+1);
415 		s = "application/x-vnd.Be-query";
416 		query.WriteAttr("BEOS:TYPE", 'MIMS', 0LL, s.String(), s.Length()+1);
417 
418 
419 		BEntry e(qname.String());
420 		entry_ref er;
421 		if (e.GetRef(&er) >= B_OK)
422 			be_roster->Launch(&er);
423 		return;
424 	}
425 
426 	if (proto == "sh") {
427 		BString cmd(full);
428 		if (_Warn(url.UrlString()) != B_OK)
429 			return;
430 		PRINT(("CMD='%s'\n", cmd.String()));
431 		cmd << pausec;
432 		args[2] = (char*)cmd.String();
433 		be_roster->Launch(kTerminalSig, 3, args);
434 		// TODO: handle errors
435 		return;
436 	}
437 
438 	if (proto == "beshare") {
439 		team_id team;
440 		BMessenger msgr(kBeShareSig);
441 		// if no instance is running, or we want a specific server, start it.
442 		if (!msgr.IsValid() || url.HasHost()) {
443 			be_roster->Launch(kBeShareSig, (BMessage*)NULL, &team);
444 			msgr = BMessenger(NULL, team);
445 		}
446 		if (url.HasHost()) {
447 			BMessage mserver('serv');
448 			mserver.AddString("server", host);
449 			msgr.SendMessage(&mserver);
450 
451 		}
452 		if (url.HasPath()) {
453 			BMessage mquery('quer');
454 			mquery.AddString("query", path);
455 			msgr.SendMessage(&mquery);
456 		}
457 		// TODO: handle errors
458 		return;
459 	}
460 
461 	if (proto == "icq" || proto == "msn") {
462 		// TODO
463 		team_id team;
464 		be_roster->Launch(kIMSig, (BMessage*)NULL, &team);
465 		BMessenger msgr(NULL, team);
466 		if (url.HasHost()) {
467 			BMessage mserver(B_REFS_RECEIVED);
468 			mserver.AddString("server", host);
469 			msgr.SendMessage(&mserver);
470 
471 		}
472 		// TODO: handle errors
473 		return;
474 	}
475 
476 	if (proto == "mms" || proto == "rtp" || proto == "rtsp") {
477 		args[0] = (char*)url.UrlString().String();
478 		be_roster->Launch(kVLCSig, 1, args);
479 		return;
480 	}
481 
482 	if (proto == "nfs") {
483 		BString parameter(host);
484 		_DecodeUrlString(path);
485 		if (url.HasPort())
486 			parameter << ":" << port;
487 		//XXX: should not always be absolute! FIXME
488 		parameter << ":/" << path;
489 		BString prettyPath(path);
490 		prettyPath.Remove(0, prettyPath.FindLast("/") + 1);
491 		if (path == "" || path == "/")
492 			prettyPath = "root";
493 		prettyPath << " on " << host;
494 		prettyPath.Prepend("/");
495 		if (mkdir(prettyPath.String(), 0755) < 0) {
496 			perror("mkdir");
497 			return;
498 		}
499 		dev_t volume;
500 		uint32 flags = 0;
501 		fprintf(stderr, "parms:'%s'\n", parameter.String());
502 		volume = fs_mount_volume(prettyPath.String(), NULL, "nfs4", flags,
503 			parameter.String());
504 		if (volume < B_OK) {
505 			fprintf(stderr, "fs_mount_volume: %s\n", strerror(volume));
506 			return;
507 		}
508 
509 		BMessage m(B_REFS_RECEIVED);
510 		entry_ref ref;
511 		if (get_ref_for_path(prettyPath.String(), &ref) < B_OK)
512 			return;
513 		m.AddRef("refs", &ref);
514 		be_roster->Launch(kTrackerSig, &m);
515 		return;
516 	}
517 
518 	if (proto == "doi") {
519 		BString url("http://dx.doi.org/");
520 		BString mimetype;
521 
522 		url << full;
523 		BUrl u(url.String());
524 		args[0] = const_cast<char*>("urlwrapper"); //XXX
525 		args[1] = (char*)u.UrlString().String();
526 		args[2] = NULL;
527 		mimetype = kURLHandlerSigBase;
528 		mimetype += u.Protocol();
529 
530 		err = be_roster->Launch(mimetype.String(), 1, args + 1);
531 		if (err != B_OK && err != B_ALREADY_RUNNING)
532 			err = be_roster->Launch(kAppSig, 1, args + 1);
533 		// TODO: handle errors
534 		return;
535 	}
536 
537 	/*
538 
539 	More ?
540 	cf. http://en.wikipedia.org/wiki/URI_scheme
541 	cf. http://www.iana.org/assignments/uri-schemes.html
542 
543 	Audio: (SoundPlay specific, identical to http:// to a shoutcast server)
544 
545 	vnc: ?
546 	irc: ?
547 	im: http://tools.ietf.org/html/rfc3860
548 
549 	svn: handled by checkitout
550 	cvs: handled by checkitout
551 	git: handled by checkitout
552 	rsync: handled by checkitout - http://tools.ietf.org/html/rfc5781
553 
554 	smb: cifsmount ?
555 	nfs: mount_nfs ? http://tools.ietf.org/html/rfc2224
556 	ipp: http://tools.ietf.org/html/rfc3510
557 
558 	mailto: ? Mail & Beam both handle it already (not fully though).
559 	imap: to describe mail accounts ? http://tools.ietf.org/html/rfc5092
560 	pop: http://tools.ietf.org/html/rfc2384
561 	mid: cid: as per RFC 2392
562 	http://www.rfc-editor.org/rfc/rfc2392.txt query MAIL:cid
563 	message:<MID> http://daringfireball.net/2007/12/message_urls_leopard_mail
564 
565 	itps: pcast: podcast: s//http/ + parse xml to get url to mp3 stream...
566 	audio: s//http:/ + default MediaPlayer
567 	-- see http://forums.winamp.com/showthread.php?threadid=233130
568 
569 	gps: ? I should submit an RFC for that one :)
570 
571 	webcal: (is http: to .ics file)
572 
573 	data: (but it's dangerous)
574 
575 	*/
576 
577 
578 }
579 
580 
581 status_t
_DecodeUrlString(BString & string)582 UrlWrapper::_DecodeUrlString(BString& string)
583 {
584 	// TODO: check for %00 and bail out!
585 	int32 length = string.Length();
586 	int i;
587 	for (i = 0; string[i] && i < length - 2; i++) {
588 		if (string[i] == '%' && isxdigit(string[i+1])
589 			&& isxdigit(string[i+2])) {
590 			int c;
591 			sscanf(string.String() + i + 1, "%02x", &c);
592 			string.Remove(i, 3);
593 			string.Insert((char)c, 1, i);
594 			length -= 2;
595 		}
596 	}
597 
598 	return B_OK;
599 }
600 
601 
602 void
ReadyToRun(void)603 UrlWrapper::ReadyToRun(void)
604 {
605 	Quit();
606 }
607 
608 
609 // #pragma mark
610 
611 
main(int argc,char ** argv)612 int main(int argc, char** argv)
613 {
614 	UrlWrapper app;
615 	if (be_app)
616 		app.Run();
617 	return 0;
618 }
619 
620