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