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