1 /* 2 * Copyright 2007, Haiku. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * François Revol, revol@free.fr 7 */ 8 9 /* 10 * urlwrapper: wraps URL mime types around command line apps 11 * or other apps that don't handle them directly. 12 */ 13 #define DEBUG 1 14 15 #include <Alert.h> 16 #include <Application.h> 17 #include <AppFileInfo.h> 18 #include <Mime.h> 19 #include <Message.h> 20 #include <TypeConstants.h> 21 #include <Roster.h> 22 #include <String.h> 23 #include <stdio.h> 24 #include <unistd.h> 25 #include <Debug.h> 26 27 /* compile-time configuration */ 28 #include "urlwrapper.h" 29 30 const char *kAppSig = APP_SIGNATURE; 31 const char *kTrackerSig = "application/x-vnd.Be-TRAK"; 32 33 #ifdef __HAIKU__ 34 const char *kTerminalSig = "application/x-vnd.Haiku-Terminal"; 35 #else 36 const char *kTerminalSig = "application/x-vnd.Be-SHEL"; 37 #endif 38 39 #ifdef HANDLE_BESHARE 40 const char *kBeShareSig = "application/x-vnd.Sugoi-BeShare"; 41 #endif 42 43 #ifdef HANDLE_IM 44 const char *kIMSig = "application/x-vnd.m_eiman.sample_im_client"; 45 #endif 46 47 #ifdef HANDLE_VLC 48 const char *kVLCSig = "application/x-vnd.videolan-vlc"; 49 #endif 50 51 // TODO: make a public BUrl class for use by apps ? 52 class Url : public BString { 53 public: 54 Url(const char *url) : BString(url) { fStatus = ParseAndSplit(); }; 55 ~Url() {}; 56 status_t InitCheck() const { return fStatus; }; 57 status_t ParseAndSplit(); 58 59 bool HasHost() const { return host.Length(); }; 60 bool HasPort() const { return port.Length(); }; 61 bool HasUser() const { return user.Length(); }; 62 bool HasPass() const { return pass.Length(); }; 63 bool HasPath() const { return path.Length(); }; 64 BString Proto() const { return BString(proto); }; 65 BString Full() const { return BString(full); }; // RFC1738's "sheme-part" 66 BString Host() const { return BString(host); }; 67 BString Port() const { return BString(port); }; 68 BString User() const { return BString(user); }; 69 BString Pass() const { return BString(pass); }; 70 71 BString proto; 72 BString full; 73 BString host; 74 BString port; 75 BString user; 76 BString pass; 77 BString path; 78 private: 79 status_t fStatus; 80 }; 81 82 class UrlWrapperApp : public BApplication 83 { 84 public: 85 UrlWrapperApp(); 86 ~UrlWrapperApp(); 87 status_t SplitUrl(const char *url, BString &host, BString &port, BString &user, BString &pass, BString &path); 88 status_t UnurlString(BString &s); 89 status_t Warn(const char *url); 90 virtual void RefsReceived(BMessage *msg); 91 virtual void ArgvReceived(int32 argc, char **argv); 92 virtual void ReadyToRun(void); 93 private: 94 95 }; 96 97 // proto:[//]user:pass@host:port/path 98 status_t Url::ParseAndSplit() 99 { 100 int32 v; 101 BString left; 102 103 v = FindFirst(":"); 104 if (v < 0) 105 return B_BAD_VALUE; 106 107 // TODO: proto and host should be lowercased. 108 // see http://en.wikipedia.org/wiki/URL_normalization 109 110 CopyInto(proto, 0, v); 111 CopyInto(left, v + 1, Length() - v); 112 // TODO: RFC1738 says the // part should indicate the uri follows the u:p@h:p/path convention, so it should be used to check for special cases. 113 if (left.FindFirst("//") == 0) 114 left.RemoveFirst("//"); 115 full = left; 116 117 // path part 118 // actually some apps handle file://[host]/path 119 // but I have no idea what proto it implies... 120 // or maybe it's just to emphasize on "localhost". 121 v = left.FindFirst("/"); 122 if (v == 0 || proto == "file") { 123 path = left; 124 return 0; 125 } 126 // some protos actually implies path if it's the only component 127 if ((v < 0) && (proto == "beshare" || proto == "irc")) { 128 path = left; 129 return 0; 130 } 131 132 if (v > -1) { 133 left.MoveInto(path, v+1, left.Length()-v); 134 left.Remove(v, 1); 135 } 136 137 // user:pass@host 138 v = left.FindFirst("@"); 139 if (v > -1) { 140 left.MoveInto(user, 0, v); 141 left.Remove(0, 1); 142 v = user.FindFirst(":"); 143 if (v > -1) { 144 user.MoveInto(pass, v, user.Length() - v); 145 pass.Remove(0, 1); 146 } 147 } else if (proto == "finger") { 148 // single component implies user 149 // see also: http://www.subir.com/lynx/lynx_help/lynx_url_support.html 150 user = left; 151 return 0; 152 } 153 154 // host:port 155 v = left.FindFirst(":"); 156 if (v > -1) { 157 left.MoveInto(port, v + 1, left.Length() - v); 158 left.Remove(v, 1); 159 } 160 161 // not much left... 162 host = left; 163 164 return 0; 165 } 166 167 status_t UrlWrapperApp::SplitUrl(const char *url, BString &host, BString &port, BString &user, BString &pass, BString &path) 168 { 169 Url u(url); 170 if (u.InitCheck() < 0) 171 return u.InitCheck(); 172 host = u.host; 173 port = u.port; 174 user = u.user; 175 pass = u.pass; 176 path = u.path; 177 return 0; 178 } 179 180 UrlWrapperApp::UrlWrapperApp() : BApplication(kAppSig) 181 { 182 #if 0 183 BMimeType mt(B_URL_TELNET); 184 if (mt.InitCheck()) 185 return; 186 if (!mt.IsInstalled()) { 187 mt.Install(); 188 } 189 #endif 190 #if 0 191 BAppFileInfo afi; 192 if (!afi.Supports(&mt)) { 193 //printf("adding support for telnet url\n"); 194 BMessage typemsg; 195 typemsg.AddString("types", url_mime); 196 afi.SetSupportedTypes(&typemsg, true); 197 } 198 #endif 199 } 200 201 UrlWrapperApp::~UrlWrapperApp() 202 { 203 } 204 205 206 207 status_t UrlWrapperApp::UnurlString(BString &s) 208 { 209 // TODO:WRITEME 210 return B_OK; 211 } 212 213 status_t UrlWrapperApp::Warn(const char *url) 214 { 215 BString message("An application has requested the system to open the following url: \n"); 216 message << "\n" << url << "\n\n"; 217 message << "This type of urls has a potential security risk.\n"; 218 message << "Proceed anyway ?"; 219 BAlert *alert = new BAlert("Warning", message.String(), "Ok", "No", NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 220 int32 v; 221 v = alert->Go(); 222 if (v == 0) 223 return B_OK; 224 return B_ERROR; 225 } 226 227 void UrlWrapperApp::RefsReceived(BMessage *msg) 228 { 229 char buff[B_PATH_NAME_LENGTH]; 230 int32 index = 0; 231 entry_ref ref; 232 char *args[] = { "urlwrapper", buff, NULL }; 233 status_t err; 234 235 while (msg->FindRef("refs", index++, &ref) == B_OK) { 236 BFile f(&ref, B_READ_ONLY); 237 BNodeInfo ni(&f); 238 BString mimetype; 239 if (f.InitCheck() == B_OK && ni.InitCheck() == B_OK) { 240 ni.GetType(mimetype.LockBuffer(B_MIME_TYPE_LENGTH)); 241 mimetype.UnlockBuffer(); 242 #ifdef HANDLE_URL_FILES 243 // http://filext.com/file-extension/URL 244 if (mimetype == "wwwserver/redirection" 245 || mimetype == "application/internet-shortcut" 246 || mimetype == "application/x-url" 247 || mimetype == "message/external-body" 248 || mimetype == "text/url" 249 || mimetype == "text/x-url") { 250 // http://www.cyanwerks.com/file-format-url.html 251 off_t size; 252 if (f.GetSize(&size) < B_OK) 253 continue; 254 BString contents, url; 255 if (f.ReadAt(0LL, contents.LockBuffer(size), size) < B_OK) 256 continue; 257 while (contents.Length()) { 258 BString line; 259 int32 cr = contents.FindFirst('\n'); 260 if (cr < 0) 261 cr = contents.Length(); 262 //contents.MoveInto(line, 0, cr); 263 contents.CopyInto(line, 0, cr); 264 contents.Remove(0, cr+1); 265 line.RemoveAll("\r"); 266 if (!line.Length()) 267 continue; 268 if (!line.ICompare("URL=", 4)) { 269 line.MoveInto(url, 4, line.Length()); 270 break; 271 } 272 } 273 if (url.Length()) { 274 args[1] = (char *)url.String(); 275 //ArgvReceived(2, args); 276 err = be_roster->Launch("application/x-vnd.Be.URL.http", 1, args+1); 277 continue; 278 } 279 } 280 #endif 281 /* eat everything as bookmark files */ 282 #ifdef HANDLE_BOOKMARK_FILES 283 if (f.ReadAttr("META:url", B_STRING_TYPE, 0LL, buff, B_PATH_NAME_LENGTH) > 0) { 284 //ArgvReceived(2, args); 285 err = be_roster->Launch("application/x-vnd.Be.URL.http", 1, args+1); 286 continue; 287 } 288 #endif 289 } 290 } 291 } 292 293 void UrlWrapperApp::ArgvReceived(int32 argc, char **argv) 294 { 295 #if 0 296 for (int i = 1; i < argc; i++) { 297 //printf("argv[%d]=%s\n", i, argv[i]); 298 } 299 #endif 300 if (argc <= 1) 301 return; 302 303 const char *failc = " || read -p 'Press any key'"; 304 const char *pausec = " ; read -p 'Press any key'"; 305 char *args[] = { "/bin/sh", "-c", NULL, NULL}; 306 307 Url u(argv[1]); 308 BString url = u.Full(); 309 if (u.InitCheck() < 0) { 310 fprintf(stderr, "malformed url: '%s'\n", u.String()); 311 return; 312 } 313 314 // XXX: debug 315 PRINT(("PROTO='%s'\n", u.proto.String())); 316 PRINT(("HOST='%s'\n", u.host.String())); 317 PRINT(("PORT='%s'\n", u.port.String())); 318 PRINT(("USER='%s'\n", u.user.String())); 319 PRINT(("PASS='%s'\n", u.pass.String())); 320 PRINT(("PATH='%s'\n", u.path.String())); 321 322 if (u.proto == "telnet") { 323 BString cmd("telnet "); 324 if (u.HasUser()) 325 cmd << "-l " << u.user << " "; 326 cmd << u.host; 327 if (u.HasPort()) 328 cmd << " " << u.port; 329 PRINT(("CMD='%s'\n", cmd.String())); 330 cmd << failc; 331 args[2] = (char *)cmd.String(); 332 be_roster->Launch(kTerminalSig, 3, args); 333 return; 334 } 335 336 // see draft: http://tools.ietf.org/wg/secsh/draft-ietf-secsh-scp-sftp-ssh-uri/ 337 if (u.proto == "ssh") { 338 BString cmd("ssh "); 339 340 if (u.HasUser()) 341 cmd << "-l " << u.user << " "; 342 if (u.HasPort()) 343 cmd << "-oPort=" << u.port << " "; 344 cmd << u.host; 345 PRINT(("CMD='%s'\n", cmd.String())); 346 cmd << failc; 347 args[2] = (char *)cmd.String(); 348 be_roster->Launch(kTerminalSig, 3, args); 349 // TODO: handle errors 350 return; 351 } 352 353 if (u.proto == "ftp") { 354 BString cmd("ftp "); 355 356 /* 357 if (user.Length()) 358 cmd << "-l " << user << " "; 359 cmd << host; 360 */ 361 cmd << u.full; 362 PRINT(("CMD='%s'\n", cmd.String())); 363 cmd << failc; 364 args[2] = (char *)cmd.String(); 365 be_roster->Launch(kTerminalSig, 3, args); 366 // TODO: handle errors 367 return; 368 } 369 370 if (u.proto == "sftp") { 371 BString cmd("sftp "); 372 373 //cmd << url; 374 if (u.HasPort()) 375 cmd << "-oPort=" << u.port << " "; 376 if (u.HasUser()) 377 cmd << u.user << "@"; 378 cmd << u.host; 379 if (u.HasPath()) 380 cmd << ":" << u.path; 381 PRINT(("CMD='%s'\n", cmd.String())); 382 cmd << failc; 383 args[2] = (char *)cmd.String(); 384 be_roster->Launch(kTerminalSig, 3, args); 385 // TODO: handle errors 386 return; 387 } 388 389 if (u.proto == "finger") { 390 BString cmd("/bin/finger "); 391 392 if (u.HasUser()) 393 cmd << u.user; 394 if (u.HasHost() == 0) 395 u.host = "127.0.0.1"; 396 cmd << "@" << u.host; 397 PRINT(("CMD='%s'\n", cmd.String())); 398 cmd << pausec; 399 args[2] = (char *)cmd.String(); 400 be_roster->Launch(kTerminalSig, 3, args); 401 // TODO: handle errors 402 return; 403 } 404 405 #ifdef HANDLE_HTTP_WGET 406 if (u.proto == "http") { 407 BString cmd("/bin/wget "); 408 409 //cmd << url; 410 if (u.HasUser()) 411 cmd << u.user << "@"; 412 cmd << u.full; 413 PRINT(("CMD='%s'\n", cmd.String())); 414 cmd << pausec; 415 args[2] = (char *)cmd.String(); 416 be_roster->Launch(kTerminalSig, 3, args); 417 // TODO: handle errors 418 return; 419 } 420 #endif 421 422 #ifdef HANDLE_FILE 423 if (u.proto == "file") { 424 BMessage m(B_REFS_RECEIVED); 425 entry_ref ref; 426 // UnurlString(path); 427 if (get_ref_for_path(u.path.String(), &ref) < B_OK) 428 return; 429 m.AddRef("refs", &ref); 430 be_roster->Launch(kTrackerSig, &m); 431 return; 432 } 433 #endif 434 435 #ifdef HANDLE_QUERY 436 // XXX:TODO: unencode the formula + split options 437 if (u.proto == "query") { 438 // mktemp ? 439 BString qname("/tmp/query-url-temp-"); 440 qname << getpid() << "-" << system_time(); 441 BFile query(qname.String(), O_CREAT|O_EXCL); 442 // XXX: should check for failure 443 444 BString s; 445 int32 v; 446 447 // TODO: 448 // UnurlString(full); 449 // TODO: handle options (list of attrs in the column, ...) 450 451 v = 'qybF'; // QuerY By Formula XXX: any #define for that ? 452 query.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0LL, &v, sizeof(v)); 453 s = "TextControl"; 454 query.WriteAttr("_trk/focusedView", B_STRING_TYPE, 0LL, s.String(), s.Length()+1); 455 s = u.full; 456 query.WriteAttr("_trk/qryinitstr", B_STRING_TYPE, 0LL, s.String(), s.Length()+1); 457 query.WriteAttr("_trk/qrystr", B_STRING_TYPE, 0LL, s.String(), s.Length()+1); 458 s = "application/x-vnd.Be-query"; 459 query.WriteAttr("BEOS:TYPE", 'MIMS', 0LL, s.String(), s.Length()+1); 460 461 462 BEntry e(qname.String()); 463 entry_ref er; 464 if (e.GetRef(&er) >= B_OK) 465 be_roster->Launch(&er); 466 return; 467 } 468 #endif 469 470 #ifdef HANDLE_SH 471 if (u.proto == "sh") { 472 BString cmd(u.Full()); 473 if (Warn(u.String()) != B_OK) 474 return; 475 PRINT(("CMD='%s'\n", cmd.String())); 476 cmd << pausec; 477 args[2] = (char *)cmd.String(); 478 be_roster->Launch(kTerminalSig, 3, args); 479 // TODO: handle errors 480 return; 481 } 482 #endif 483 484 #ifdef HANDLE_BESHARE 485 if (u.proto == "beshare") { 486 team_id team; 487 BMessenger msgr(kBeShareSig); 488 // if no instance is running, or we want a specific server, start it. 489 if (!msgr.IsValid() || u.HasHost()) { 490 be_roster->Launch(kBeShareSig, (BMessage *)NULL, &team); 491 msgr = BMessenger(NULL, team); 492 } 493 if (u.HasHost()) { 494 BMessage mserver('serv'); 495 mserver.AddString("server", u.host); 496 msgr.SendMessage(&mserver); 497 498 } 499 if (u.HasPath()) { 500 BMessage mquery('quer'); 501 mquery.AddString("query", u.path); 502 msgr.SendMessage(&mquery); 503 } 504 // TODO: handle errors 505 return; 506 } 507 #endif 508 509 #ifdef HANDLE_IM 510 if (u.proto == "icq" || u.proto == "msn") { 511 // TODO 512 team_id team; 513 be_roster->Launch(kIMSig, (BMessage *)NULL, &team); 514 BMessenger msgr(NULL, team); 515 if (u.HasHost()) { 516 BMessage mserver(B_REFS_RECEIVED); 517 mserver.AddString("server", u.host); 518 msgr.SendMessage(&httpmserver); 519 520 } 521 // TODO: handle errors 522 return; 523 } 524 #endif 525 526 #ifdef HANDLE_VLC 527 if (u.proto == "mms" || u.proto == "rtp" || u.proto == "rtsp") { 528 args[0] = "vlc"; 529 args[1] = (char *)u.String(); 530 be_roster->Launch(kVLCSig, 2, args); 531 return; 532 } 533 #endif 534 535 #ifdef HANDLE_AUDIO 536 // TODO 537 #endif 538 539 // vnc: ? 540 // irc: ? 541 // 542 // svn: ? 543 // cvs: ? 544 // smb: cifsmount ? 545 // nfs: mount_nfs ? 546 // 547 // mailto: ? but BeMail & Beam both handle it already (not fully though). 548 // 549 // itps: pcast: podcast: s//http/ + parse xml to get url to mp3 stream... 550 // audio: s//http:/ + default MediaPlayer -- see http://forums.winamp.com/showthread.php?threadid=233130 551 // 552 // gps: ? I should submit an RFC for that one :) 553 554 } 555 556 void UrlWrapperApp::ReadyToRun(void) 557 { 558 Quit(); 559 } 560 561 int main(int argc, char **argv) 562 { 563 UrlWrapperApp app; 564 if (be_app) 565 app.Run(); 566 return 0; 567 } 568