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