Assorted whitespace cleanup and typo fixes.
[haiku.git] / src / bin / urlwrapper.cpp
blob9fea225f4a7e23aa6dfb5e162ae94486c2e32adc
1 /*
2 * Copyright 2007-2010 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * François Revol, revol@free.fr
7 * Jonas Sundström, jonas@kirilla.com
8 * Stephan Aßmus <superstippi@gmx.de>
9 */
12 * urlwrapper: wraps URL mime types around command line apps
13 * or other apps that don't handle them directly.
15 #define DEBUG 0
17 #include <ctype.h>
18 #include <stdio.h>
19 #include <unistd.h>
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>
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()
52 status_t
53 UrlWrapper::_Warn(const char* url)
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;
68 return B_ERROR;
72 void
73 UrlWrapper::RefsReceived(BMessage* msg)
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;
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();
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;
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;
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;
183 if (state == 5) {
184 break;
187 if (url.Length()) {
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);
195 continue;
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 BUrl u(buff);
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);
209 continue;
216 void
217 UrlWrapper::ArgvReceived(int32 argc, char** argv)
219 if (argc <= 1)
220 return;
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 status_t err;
227 BUrl url(argv[1]);
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());
239 return;
242 // XXX: debug
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") {
251 app_info info;
252 BString sig;
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);
259 return;
261 if (be_roster->Launch(sig.String(), &msg) == B_OK)
262 return;
263 be_roster->Launch("application/x-vnd.Haiku-About");
264 return;
267 if (proto == "telnet") {
268 BString cmd("telnet ");
269 if (url.HasUserInfo())
270 cmd << "-l " << user << " ";
271 cmd << host;
272 if (url.HasPort())
273 cmd << " " << port;
274 PRINT(("CMD='%s'\n", cmd.String()));
275 cmd << failc;
276 args[2] = (char*)cmd.String();
277 be_roster->Launch(kTerminalSig, 3, args);
278 return;
281 // see draft:
282 // http://tools.ietf.org/wg/secsh/draft-ietf-secsh-scp-sftp-ssh-uri/
283 if (proto == "ssh") {
284 BString cmd("ssh ");
286 if (url.HasUserInfo())
287 cmd << "-l " << user << " ";
288 if (url.HasPort())
289 cmd << "-oPort=" << port << " ";
290 cmd << host;
291 PRINT(("CMD='%s'\n", cmd.String()));
292 cmd << failc;
293 args[2] = (char*)cmd.String();
294 be_roster->Launch(kTerminalSig, 3, args);
295 // TODO: handle errors
296 return;
299 if (proto == "ftp") {
300 BString cmd("ftp ");
302 cmd << proto << "://";
304 if (user.Length())
305 cmd << "-l " << user << " ";
306 cmd << host;
308 cmd << full;
309 PRINT(("CMD='%s'\n", cmd.String()));
310 cmd << failc;
311 args[2] = (char*)cmd.String();
312 be_roster->Launch(kTerminalSig, 3, args);
313 // TODO: handle errors
314 return;
317 if (proto == "sftp") {
318 BString cmd("sftp ");
320 //cmd << url;
321 if (url.HasPort())
322 cmd << "-oPort=" << port << " ";
323 if (url.HasUserInfo())
324 cmd << user << "@";
325 cmd << host;
326 if (url.HasPath())
327 cmd << ":" << path;
328 PRINT(("CMD='%s'\n", cmd.String()));
329 cmd << failc;
330 args[2] = (char*)cmd.String();
331 be_roster->Launch(kTerminalSig, 3, args);
332 // TODO: handle errors
333 return;
336 if (proto == "finger") {
337 BString cmd("/bin/finger ");
339 if (url.HasUserInfo())
340 cmd << user;
341 if (url.HasHost() == 0)
342 host = "127.0.0.1";
343 cmd << "@" << host;
344 PRINT(("CMD='%s'\n", cmd.String()));
345 cmd << pausec;
346 args[2] = (char*)cmd.String();
347 be_roster->Launch(kTerminalSig, 3, args);
348 // TODO: handle errors
349 return;
352 if (proto == "http" || proto == "https" /*|| proto == "ftp"*/) {
353 BString cmd("/bin/wget ");
355 //cmd << url;
356 cmd << proto << "://";
357 if (url.HasUserInfo())
358 cmd << user << "@";
359 cmd << full;
360 PRINT(("CMD='%s'\n", cmd.String()));
361 cmd << pausec;
362 args[2] = (char*)cmd.String();
363 be_roster->Launch(kTerminalSig, 3, args);
364 // TODO: handle errors
365 return;
368 if (proto == "file") {
369 BMessage m(B_REFS_RECEIVED);
370 entry_ref ref;
371 _DecodeUrlString(path);
372 if (get_ref_for_path(path.String(), &ref) < B_OK)
373 return;
374 m.AddRef("refs", &ref);
375 be_roster->Launch(kTrackerSig, &m);
376 return;
379 // XXX:TODO: split options
380 if (proto == "query") {
381 // mktemp ?
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
387 BString s;
388 int32 v;
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));
395 s = "TextControl";
396 query.WriteAttr("_trk/focusedView", B_STRING_TYPE, 0LL, s.String(),
397 s.Length()+1);
398 s = full;
399 PRINT(("QUERY='%s'\n", s.String()));
400 query.WriteAttr("_trk/qryinitstr", B_STRING_TYPE, 0LL, s.String(),
401 s.Length()+1);
402 query.WriteAttr("_trk/qrystr", B_STRING_TYPE, 0LL, s.String(),
403 s.Length()+1);
404 s = "application/x-vnd.Be-query";
405 query.WriteAttr("BEOS:TYPE", 'MIMS', 0LL, s.String(), s.Length()+1);
408 BEntry e(qname.String());
409 entry_ref er;
410 if (e.GetRef(&er) >= B_OK)
411 be_roster->Launch(&er);
412 return;
415 if (proto == "sh") {
416 BString cmd(full);
417 if (_Warn(url.UrlString()) != B_OK)
418 return;
419 PRINT(("CMD='%s'\n", cmd.String()));
420 cmd << pausec;
421 args[2] = (char*)cmd.String();
422 be_roster->Launch(kTerminalSig, 3, args);
423 // TODO: handle errors
424 return;
427 if (proto == "beshare") {
428 team_id team;
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);
435 if (url.HasHost()) {
436 BMessage mserver('serv');
437 mserver.AddString("server", host);
438 msgr.SendMessage(&mserver);
441 if (url.HasPath()) {
442 BMessage mquery('quer');
443 mquery.AddString("query", path);
444 msgr.SendMessage(&mquery);
446 // TODO: handle errors
447 return;
450 if (proto == "icq" || proto == "msn") {
451 // TODO
452 team_id team;
453 be_roster->Launch(kIMSig, (BMessage*)NULL, &team);
454 BMessenger msgr(NULL, team);
455 if (url.HasHost()) {
456 BMessage mserver(B_REFS_RECEIVED);
457 mserver.AddString("server", host);
458 msgr.SendMessage(&mserver);
461 // TODO: handle errors
462 return;
465 if (proto == "mms" || proto == "rtp" || proto == "rtsp") {
466 args[0] = (char*)url.UrlString().String();
467 be_roster->Launch(kVLCSig, 1, args);
468 return;
471 if (proto == "nfs") {
472 BString parameter(host);
473 _DecodeUrlString(path);
474 if (url.HasPort())
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 == "/")
481 prettyPath = "root";
482 prettyPath << " on " << host;
483 prettyPath.Prepend("/");
484 if (mkdir(prettyPath.String(), 0755) < 0) {
485 perror("mkdir");
486 return;
488 dev_t volume;
489 uint32 flags = 0;
490 fprintf(stderr, "parms:'%s'\n", parameter.String());
491 volume = fs_mount_volume(prettyPath.String(), NULL, "nfs4", flags,
492 parameter.String());
493 if (volume < B_OK) {
494 fprintf(stderr, "fs_mount_volume: %s\n", strerror(volume));
495 return;
498 BMessage m(B_REFS_RECEIVED);
499 entry_ref ref;
500 if (get_ref_for_path(prettyPath.String(), &ref) < B_OK)
501 return;
502 m.AddRef("refs", &ref);
503 be_roster->Launch(kTrackerSig, &m);
504 return;
507 if (proto == "doi") {
508 BString url("http://dx.doi.org/");
509 BString mimetype;
511 url << full;
512 BUrl u(url.String());
513 args[0] = const_cast<char*>("urlwrapper"); //XXX
514 args[1] = (char*)u.UrlString().String();
515 args[2] = NULL;
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
523 return;
528 More ?
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)
534 vnc: ?
535 irc: ?
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
543 smb: cifsmount ?
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)
570 status_t
571 UrlWrapper::_DecodeUrlString(BString& string)
573 // TODO: check for %00 and bail out!
574 int32 length = string.Length();
575 int i;
576 for (i = 0; string[i] && i < length - 2; i++) {
577 if (string[i] == '%' && isxdigit(string[i+1])
578 && isxdigit(string[i+2])) {
579 int c;
580 sscanf(string.String() + i + 1, "%02x", &c);
581 string.Remove(i, 3);
582 string.Insert((char)c, 1, i);
583 length -= 2;
587 return B_OK;
591 void
592 UrlWrapper::ReadyToRun(void)
594 Quit();
598 // #pragma mark
601 int main(int argc, char** argv)
603 UrlWrapper app;
604 if (be_app)
605 app.Run();
606 return 0;