2 * Copyright 2007-2010 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 * François Revol, revol@free.fr
7 * Jonas Sundström, jonas@kirilla.com
8 * Stephan Aßmus <superstippi@gmx.de>
12 * urlwrapper: wraps URL mime types around command line apps
13 * or other apps that don't handle them directly.
21 #include <fs_volume.h>
29 #include "urlwrapper.h"
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";
39 const char* kURLHandlerSigBase
= "application/x-vnd.Be.URL.";
42 UrlWrapper::UrlWrapper() : BApplication(kAppSig
)
47 UrlWrapper::~UrlWrapper()
53 UrlWrapper::_Warn(const char* url
)
55 BString
message("An application has requested the system to open the "
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
);
73 UrlWrapper::RefsReceived(BMessage
* msg
)
75 char buff
[B_PATH_NAME_LENGTH
];
78 char* args
[] = { const_cast<char*>("urlwrapper"), buff
, NULL
};
81 while (msg
->FindRef("refs", index
++, &ref
) == B_OK
) {
82 BFile
f(&ref
, B_READ_ONLY
);
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();
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
96 if (f
.GetSize(&size
) < B_OK
)
100 if (f
.ReadAt(0LL, contents
.LockBuffer(size
), size
) < B_OK
)
102 contents
.UnlockBuffer();
103 while (contents
.Length()) {
105 int32 cr
= contents
.FindFirst('\n');
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");
114 if (!line
.ICompare("URL=", 4)) {
115 line
.MoveInto(url
, 4, line
.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);
130 if (mimetype
== "text/x-webloc" || extension
== "webloc") {
132 // XML file + resource fork
134 if (f
.GetSize(&size
) < B_OK
)
138 if (f
.ReadAt(0LL, contents
.LockBuffer(size
), size
) < B_OK
)
140 contents
.UnlockBuffer();
142 while (contents
.Length()) {
144 int32 cr
= contents
.FindFirst('\n');
146 cr
= contents
.Length();
147 contents
.CopyInto(line
, 0, cr
);
148 contents
.Remove(0, cr
+1);
149 line
.RemoveAll("\r");
155 if (!line
.ICompare("<?xml", 5))
159 if (!line
.ICompare("<plist", 6))
163 if (!line
.ICompare("<dict>", 6))
167 if (line
.IFindFirst("<key>URL</key>") > -1)
171 if ((s
= line
.IFindFirst("<string>")) > -1
172 && (e
= line
.IFindFirst("</string>")) > s
) {
175 line
.MoveInto(url
, s
, e
- s
);
188 BUrl
u(url
.String());
189 args
[1] = (char*)u
.UrlString().String();
190 mimetype
= kURLHandlerSigBase
;
191 mimetype
+= u
.Protocol();
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);
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) {
203 args
[1] = (char*)u
.UrlString().String();
204 mimetype
= kURLHandlerSigBase
;
205 mimetype
+= u
.Protocol();
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);
217 UrlWrapper::ArgvReceived(int32 argc
, char** argv
)
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
};
229 BString full
= BUrl(url
).SetProtocol(BString()).UrlString();
230 BString proto
= url
.Protocol();
231 BString host
= url
.Host();
232 BString port
= BString() << url
.Port();
233 BString user
= url
.UserInfo();
234 BString pass
= url
.Password();
235 BString path
= url
.Path();
237 if (!url
.IsValid()) {
238 fprintf(stderr
, "malformed url: '%s'\n", url
.UrlString().String());
243 PRINT(("PROTO='%s'\n", proto
.String()));
244 PRINT(("HOST='%s'\n", host
.String()));
245 PRINT(("PORT='%s'\n", port
.String()));
246 PRINT(("USER='%s'\n", user
.String()));
247 PRINT(("PASS='%s'\n", pass
.String()));
248 PRINT(("PATH='%s'\n", path
.String()));
250 if (proto
== "about") {
253 // BUrl could get an accessor for the full - proto part...
254 sig
= host
<< "/" << path
;
255 BMessage
msg(B_ABOUT_REQUESTED
);
256 if (be_roster
->GetAppInfo(sig
.String(), &info
) == B_OK
) {
257 BMessenger
msgr(sig
.String());
258 msgr
.SendMessage(&msg
);
261 if (be_roster
->Launch(sig
.String(), &msg
) == B_OK
)
263 be_roster
->Launch("application/x-vnd.Haiku-About");
267 if (proto
== "telnet") {
268 BString
cmd("telnet ");
269 if (url
.HasUserInfo())
270 cmd
<< "-l " << user
<< " ";
274 PRINT(("CMD='%s'\n", cmd
.String()));
276 args
[2] = (char*)cmd
.String();
277 be_roster
->Launch(kTerminalSig
, 3, args
);
282 // http://tools.ietf.org/wg/secsh/draft-ietf-secsh-scp-sftp-ssh-uri/
283 if (proto
== "ssh") {
286 if (url
.HasUserInfo())
287 cmd
<< "-l " << user
<< " ";
289 cmd
<< "-oPort=" << port
<< " ";
291 PRINT(("CMD='%s'\n", cmd
.String()));
293 args
[2] = (char*)cmd
.String();
294 be_roster
->Launch(kTerminalSig
, 3, args
);
295 // TODO: handle errors
299 if (proto
== "ftp") {
302 cmd
<< proto
<< "://";
305 cmd << "-l " << user << " ";
309 PRINT(("CMD='%s'\n", cmd
.String()));
311 args
[2] = (char*)cmd
.String();
312 be_roster
->Launch(kTerminalSig
, 3, args
);
313 // TODO: handle errors
317 if (proto
== "sftp") {
318 BString
cmd("sftp ");
322 cmd
<< "-oPort=" << port
<< " ";
323 if (url
.HasUserInfo())
328 PRINT(("CMD='%s'\n", cmd
.String()));
330 args
[2] = (char*)cmd
.String();
331 be_roster
->Launch(kTerminalSig
, 3, args
);
332 // TODO: handle errors
336 if (proto
== "finger") {
337 BString
cmd("/bin/finger ");
339 if (url
.HasUserInfo())
341 if (url
.HasHost() == 0)
344 PRINT(("CMD='%s'\n", cmd
.String()));
346 args
[2] = (char*)cmd
.String();
347 be_roster
->Launch(kTerminalSig
, 3, args
);
348 // TODO: handle errors
352 if (proto
== "http" || proto
== "https" /*|| proto == "ftp"*/) {
353 BString
cmd("/bin/wget ");
356 cmd
<< proto
<< "://";
357 if (url
.HasUserInfo())
360 PRINT(("CMD='%s'\n", cmd
.String()));
362 args
[2] = (char*)cmd
.String();
363 be_roster
->Launch(kTerminalSig
, 3, args
);
364 // TODO: handle errors
368 if (proto
== "file") {
369 BMessage
m(B_REFS_RECEIVED
);
371 _DecodeUrlString(path
);
372 if (get_ref_for_path(path
.String(), &ref
) < B_OK
)
374 m
.AddRef("refs", &ref
);
375 be_roster
->Launch(kTrackerSig
, &m
);
379 // XXX:TODO: split options
380 if (proto
== "query") {
382 BString
qname("/tmp/query-url-temp-");
383 qname
<< getpid() << "-" << system_time();
384 BFile
query(qname
.String(), O_CREAT
|O_EXCL
);
385 // XXX: should check for failure
390 _DecodeUrlString(full
);
391 // TODO: handle options (list of attrs in the column, ...)
393 v
= 'qybF'; // QuerY By Formula XXX: any #define for that ?
394 query
.WriteAttr("_trk/qryinitmode", B_INT32_TYPE
, 0LL, &v
, sizeof(v
));
396 query
.WriteAttr("_trk/focusedView", B_STRING_TYPE
, 0LL, s
.String(),
399 PRINT(("QUERY='%s'\n", s
.String()));
400 query
.WriteAttr("_trk/qryinitstr", B_STRING_TYPE
, 0LL, s
.String(),
402 query
.WriteAttr("_trk/qrystr", B_STRING_TYPE
, 0LL, s
.String(),
404 s
= "application/x-vnd.Be-query";
405 query
.WriteAttr("BEOS:TYPE", 'MIMS', 0LL, s
.String(), s
.Length()+1);
408 BEntry
e(qname
.String());
410 if (e
.GetRef(&er
) >= B_OK
)
411 be_roster
->Launch(&er
);
417 if (_Warn(url
.UrlString()) != B_OK
)
419 PRINT(("CMD='%s'\n", cmd
.String()));
421 args
[2] = (char*)cmd
.String();
422 be_roster
->Launch(kTerminalSig
, 3, args
);
423 // TODO: handle errors
427 if (proto
== "beshare") {
429 BMessenger
msgr(kBeShareSig
);
430 // if no instance is running, or we want a specific server, start it.
431 if (!msgr
.IsValid() || url
.HasHost()) {
432 be_roster
->Launch(kBeShareSig
, (BMessage
*)NULL
, &team
);
433 msgr
= BMessenger(NULL
, team
);
436 BMessage
mserver('serv');
437 mserver
.AddString("server", host
);
438 msgr
.SendMessage(&mserver
);
442 BMessage
mquery('quer');
443 mquery
.AddString("query", path
);
444 msgr
.SendMessage(&mquery
);
446 // TODO: handle errors
450 if (proto
== "icq" || proto
== "msn") {
453 be_roster
->Launch(kIMSig
, (BMessage
*)NULL
, &team
);
454 BMessenger
msgr(NULL
, team
);
456 BMessage
mserver(B_REFS_RECEIVED
);
457 mserver
.AddString("server", host
);
458 msgr
.SendMessage(&mserver
);
461 // TODO: handle errors
465 if (proto
== "mms" || proto
== "rtp" || proto
== "rtsp") {
466 args
[0] = (char*)url
.UrlString().String();
467 be_roster
->Launch(kVLCSig
, 1, args
);
471 if (proto
== "nfs") {
472 BString
parameter(host
);
473 _DecodeUrlString(path
);
475 parameter
<< ":" << port
;
476 //XXX: should not always be absolute! FIXME
477 parameter
<< ":/" << path
;
478 BString
prettyPath(path
);
479 prettyPath
.Remove(0, prettyPath
.FindLast("/") + 1);
480 if (path
== "" || path
== "/")
482 prettyPath
<< " on " << host
;
483 prettyPath
.Prepend("/");
484 if (mkdir(prettyPath
.String(), 0755) < 0) {
490 fprintf(stderr
, "parms:'%s'\n", parameter
.String());
491 volume
= fs_mount_volume(prettyPath
.String(), NULL
, "nfs4", flags
,
494 fprintf(stderr
, "fs_mount_volume: %s\n", strerror(volume
));
498 BMessage
m(B_REFS_RECEIVED
);
500 if (get_ref_for_path(prettyPath
.String(), &ref
) < B_OK
)
502 m
.AddRef("refs", &ref
);
503 be_roster
->Launch(kTrackerSig
, &m
);
507 if (proto
== "doi") {
508 BString
url("http://dx.doi.org/");
512 BUrl
u(url
.String());
513 args
[0] = const_cast<char*>("urlwrapper"); //XXX
514 args
[1] = (char*)u
.UrlString().String();
516 mimetype
= kURLHandlerSigBase
;
517 mimetype
+= u
.Protocol();
519 err
= be_roster
->Launch(mimetype
.String(), 1, args
+ 1);
520 if (err
!= B_OK
&& err
!= B_ALREADY_RUNNING
)
521 err
= be_roster
->Launch(kAppSig
, 1, args
+ 1);
522 // TODO: handle errors
529 cf. http://en.wikipedia.org/wiki/URI_scheme
530 cf. http://www.iana.org/assignments/uri-schemes.html
532 Audio: (SoundPlay specific, identical to http:// to a shoutcast server)
536 im: http://tools.ietf.org/html/rfc3860
538 svn: handled by checkitout
539 cvs: handled by checkitout
540 git: handled by checkitout
541 rsync: handled by checkitout - http://tools.ietf.org/html/rfc5781
544 nfs: mount_nfs ? http://tools.ietf.org/html/rfc2224
545 ipp: http://tools.ietf.org/html/rfc3510
547 mailto: ? Mail & Beam both handle it already (not fully though).
548 imap: to describe mail accounts ? http://tools.ietf.org/html/rfc5092
549 pop: http://tools.ietf.org/html/rfc2384
550 mid: cid: as per RFC 2392
551 http://www.rfc-editor.org/rfc/rfc2392.txt query MAIL:cid
552 message:<MID> http://daringfireball.net/2007/12/message_urls_leopard_mail
554 itps: pcast: podcast: s//http/ + parse xml to get url to mp3 stream...
555 audio: s//http:/ + default MediaPlayer
556 -- see http://forums.winamp.com/showthread.php?threadid=233130
558 gps: ? I should submit an RFC for that one :)
560 webcal: (is http: to .ics file)
562 data: (but it's dangerous)
571 UrlWrapper::_DecodeUrlString(BString
& string
)
573 // TODO: check for %00 and bail out!
574 int32 length
= string
.Length();
576 for (i
= 0; string
[i
] && i
< length
- 2; i
++) {
577 if (string
[i
] == '%' && isxdigit(string
[i
+1])
578 && isxdigit(string
[i
+2])) {
580 sscanf(string
.String() + i
+ 1, "%02x", &c
);
582 string
.Insert((char)c
, 1, i
);
592 UrlWrapper::ReadyToRun(void)
601 int main(int argc
, char** argv
)