1 /* 2 * Copyright 2007-2009 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 */ 9 10 /* 11 * urlwrapper: wraps URL mime types around command line apps 12 * or other apps that don't handle them directly. 13 */ 14 #define DEBUG 0 15 16 #include <ctype.h> 17 #include <stdio.h> 18 #include <unistd.h> 19 20 #include <Alert.h> 21 #include <Debug.h> 22 #include <NodeInfo.h> 23 #include <Roster.h> 24 #include <String.h> 25 #include <Url.h> 26 27 #include "urlwrapper.h" 28 29 30 const char* kAppSig = "application/x-vnd.Haiku-urlwrapper"; 31 const char* kTrackerSig = "application/x-vnd.Be-TRAK"; 32 const char* kTerminalSig = "application/x-vnd.Haiku-Terminal"; 33 const char* kBeShareSig = "application/x-vnd.Sugoi-BeShare"; 34 const char* kIMSig = "application/x-vnd.m_eiman.sample_im_client"; 35 const char* kVLCSig = "application/x-vnd.videolan-vlc"; 36 37 const char* kURLHandlerSigBase = "application/x-vnd.Be.URL."; 38 39 40 UrlWrapper::UrlWrapper() : BApplication(kAppSig) 41 { 42 } 43 44 45 UrlWrapper::~UrlWrapper() 46 { 47 } 48 49 50 status_t 51 UrlWrapper::_Warn(const char* url) 52 { 53 BString message("An application has requested the system to open the " 54 "following url: \n"); 55 message << "\n" << url << "\n\n"; 56 message << "This type of URL has a potential security risk.\n"; 57 message << "Proceed anyway?"; 58 BAlert* alert = new BAlert("Warning", message.String(), "Proceed", "Stop", NULL, 59 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 60 int32 button; 61 button = alert->Go(); 62 if (button == 0) 63 return B_OK; 64 65 return B_ERROR; 66 } 67 68 69 void 70 UrlWrapper::RefsReceived(BMessage* msg) 71 { 72 char buff[B_PATH_NAME_LENGTH]; 73 int32 index = 0; 74 entry_ref ref; 75 char* args[] = { const_cast<char*>("urlwrapper"), buff, NULL }; 76 status_t err; 77 78 while (msg->FindRef("refs", index++, &ref) == B_OK) { 79 BFile f(&ref, B_READ_ONLY); 80 BNodeInfo ni(&f); 81 BString mimetype; 82 BString extension(ref.name); 83 extension.Remove(0, extension.FindLast('.') + 1); 84 if (f.InitCheck() == B_OK && ni.InitCheck() == B_OK) { 85 ni.GetType(mimetype.LockBuffer(B_MIME_TYPE_LENGTH)); 86 mimetype.UnlockBuffer(); 87 88 // Internet Explorer Shortcut 89 if (mimetype == "text/x-url" || extension == "url") { 90 // http://filext.com/file-extension/URL 91 // http://www.cyanwerks.com/file-format-url.html 92 off_t size; 93 if (f.GetSize(&size) < B_OK) 94 continue; 95 BString contents; 96 BString url; 97 if (f.ReadAt(0LL, contents.LockBuffer(size), size) < B_OK) 98 continue; 99 contents.UnlockBuffer(); 100 while (contents.Length()) { 101 BString line; 102 int32 cr = contents.FindFirst('\n'); 103 if (cr < 0) 104 cr = contents.Length(); 105 //contents.MoveInto(line, 0, cr); 106 contents.CopyInto(line, 0, cr); 107 contents.Remove(0, cr+1); 108 line.RemoveAll("\r"); 109 if (!line.Length()) 110 continue; 111 if (!line.ICompare("URL=", 4)) { 112 line.MoveInto(url, 4, line.Length()); 113 break; 114 } 115 } 116 if (url.Length()) { 117 BPrivate::Support::BUrl u(url.String()); 118 args[1] = (char*)u.String(); 119 mimetype = kURLHandlerSigBase; 120 mimetype += u.Proto(); 121 err = be_roster->Launch(mimetype.String(), 1, args+1); 122 if (err < B_OK) 123 err = be_roster->Launch(kAppSig, 1, args+1); 124 continue; 125 } 126 } 127 if (mimetype == "text/x-webloc" || extension == "webloc") { 128 // OSX url shortcuts 129 // XML file + resource fork 130 off_t size; 131 if (f.GetSize(&size) < B_OK) 132 continue; 133 BString contents; 134 BString url; 135 if (f.ReadAt(0LL, contents.LockBuffer(size), size) < B_OK) 136 continue; 137 contents.UnlockBuffer(); 138 int state = 0; 139 while (contents.Length()) { 140 BString line; 141 int32 cr = contents.FindFirst('\n'); 142 if (cr < 0) 143 cr = contents.Length(); 144 contents.CopyInto(line, 0, cr); 145 contents.Remove(0, cr+1); 146 line.RemoveAll("\r"); 147 if (!line.Length()) 148 continue; 149 int32 s, e; 150 switch (state) { 151 case 0: 152 if (!line.ICompare("<?xml", 5)) 153 state = 1; 154 break; 155 case 1: 156 if (!line.ICompare("<plist", 6)) 157 state = 2; 158 break; 159 case 2: 160 if (!line.ICompare("<dict>", 6)) 161 state = 3; 162 break; 163 case 3: 164 if (line.IFindFirst("<key>URL</key>") > -1) 165 state = 4; 166 break; 167 case 4: 168 if ((s = line.IFindFirst("<string>")) > -1 && (e = line.IFindFirst("</string>")) > s) { 169 state = 5; 170 s += 8; 171 line.MoveInto(url, s, e - s); 172 break; 173 } else 174 state = 3; 175 break; 176 default: 177 break; 178 } 179 if (state == 5) { 180 break; 181 } 182 } 183 if (url.Length()) { 184 BPrivate::Support::BUrl u(url.String()); 185 args[1] = (char*)u.String(); 186 mimetype = kURLHandlerSigBase; 187 mimetype += u.Proto(); 188 err = be_roster->Launch(mimetype.String(), 1, args+1); 189 if (err < B_OK) 190 err = be_roster->Launch(kAppSig, 1, args+1); 191 continue; 192 } 193 } 194 195 // NetPositive Bookmark or any file with a META:url attribute 196 if (f.ReadAttr("META:url", B_STRING_TYPE, 0LL, buff, 197 B_PATH_NAME_LENGTH) > 0) { 198 BPrivate::Support::BUrl u(buff); 199 args[1] = (char*)u.String(); 200 mimetype = kURLHandlerSigBase; 201 mimetype += u.Proto(); 202 err = be_roster->Launch(mimetype.String(), 1, args+1); 203 if (err < B_OK) 204 err = be_roster->Launch(kAppSig, 1, args+1); 205 continue; 206 } 207 } 208 } 209 } 210 211 212 void 213 UrlWrapper::ArgvReceived(int32 argc, char** argv) 214 { 215 if (argc <= 1) 216 return; 217 218 const char* failc = " || read -p 'Press any key'"; 219 const char* pausec = " ; read -p 'Press any key'"; 220 char* args[] = { (char *)"/bin/sh", (char *)"-c", NULL, NULL}; 221 222 BPrivate::Support::BUrl url(argv[1]); 223 224 BString full = url.Full(); 225 BString proto = url.Proto(); 226 BString host = url.Host(); 227 BString port = url.Port(); 228 BString user = url.User(); 229 BString pass = url.Pass(); 230 BString path = url.Path(); 231 232 if (url.InitCheck() < 0) { 233 fprintf(stderr, "malformed url: '%s'\n", url.String()); 234 return; 235 } 236 237 // XXX: debug 238 PRINT(("PROTO='%s'\n", proto.String())); 239 PRINT(("HOST='%s'\n", host.String())); 240 PRINT(("PORT='%s'\n", port.String())); 241 PRINT(("USER='%s'\n", user.String())); 242 PRINT(("PASS='%s'\n", pass.String())); 243 PRINT(("PATH='%s'\n", path.String())); 244 245 if (proto == "telnet") { 246 BString cmd("telnet "); 247 if (url.HasUser()) 248 cmd << "-l " << user << " "; 249 cmd << host; 250 if (url.HasPort()) 251 cmd << " " << port; 252 PRINT(("CMD='%s'\n", cmd.String())); 253 cmd << failc; 254 args[2] = (char*)cmd.String(); 255 be_roster->Launch(kTerminalSig, 3, args); 256 return; 257 } 258 259 // see draft: 260 // http://tools.ietf.org/wg/secsh/draft-ietf-secsh-scp-sftp-ssh-uri/ 261 if (proto == "ssh") { 262 BString cmd("ssh "); 263 264 if (url.HasUser()) 265 cmd << "-l " << user << " "; 266 if (url.HasPort()) 267 cmd << "-oPort=" << port << " "; 268 cmd << host; 269 PRINT(("CMD='%s'\n", cmd.String())); 270 cmd << failc; 271 args[2] = (char*)cmd.String(); 272 be_roster->Launch(kTerminalSig, 3, args); 273 // TODO: handle errors 274 return; 275 } 276 277 if (proto == "ftp") { 278 BString cmd("ftp "); 279 280 /* 281 if (user.Length()) 282 cmd << "-l " << user << " "; 283 cmd << host; 284 */ 285 cmd << full; 286 PRINT(("CMD='%s'\n", cmd.String())); 287 cmd << failc; 288 args[2] = (char*)cmd.String(); 289 be_roster->Launch(kTerminalSig, 3, args); 290 // TODO: handle errors 291 return; 292 } 293 294 if (proto == "sftp") { 295 BString cmd("sftp "); 296 297 //cmd << url; 298 if (url.HasPort()) 299 cmd << "-oPort=" << port << " "; 300 if (url.HasUser()) 301 cmd << user << "@"; 302 cmd << host; 303 if (url.HasPath()) 304 cmd << ":" << path; 305 PRINT(("CMD='%s'\n", cmd.String())); 306 cmd << failc; 307 args[2] = (char*)cmd.String(); 308 be_roster->Launch(kTerminalSig, 3, args); 309 // TODO: handle errors 310 return; 311 } 312 313 if (proto == "finger") { 314 BString cmd("/bin/finger "); 315 316 if (url.HasUser()) 317 cmd << user; 318 if (url.HasHost() == 0) 319 host = "127.0.0.1"; 320 cmd << "@" << host; 321 PRINT(("CMD='%s'\n", cmd.String())); 322 cmd << pausec; 323 args[2] = (char*)cmd.String(); 324 be_roster->Launch(kTerminalSig, 3, args); 325 // TODO: handle errors 326 return; 327 } 328 329 if (proto == "http") { 330 BString cmd("/bin/wget "); 331 332 //cmd << url; 333 if (url.HasUser()) 334 cmd << user << "@"; 335 cmd << full; 336 PRINT(("CMD='%s'\n", cmd.String())); 337 cmd << pausec; 338 args[2] = (char*)cmd.String(); 339 be_roster->Launch(kTerminalSig, 3, args); 340 // TODO: handle errors 341 return; 342 } 343 344 if (proto == "file") { 345 BMessage m(B_REFS_RECEIVED); 346 entry_ref ref; 347 _DecodeUrlString(path); 348 if (get_ref_for_path(path.String(), &ref) < B_OK) 349 return; 350 m.AddRef("refs", &ref); 351 be_roster->Launch(kTrackerSig, &m); 352 return; 353 } 354 355 // XXX:TODO: split options 356 if (proto == "query") { 357 // mktemp ? 358 BString qname("/tmp/query-url-temp-"); 359 qname << getpid() << "-" << system_time(); 360 BFile query(qname.String(), O_CREAT|O_EXCL); 361 // XXX: should check for failure 362 363 BString s; 364 int32 v; 365 366 _DecodeUrlString(full); 367 // TODO: handle options (list of attrs in the column, ...) 368 369 v = 'qybF'; // QuerY By Formula XXX: any #define for that ? 370 query.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0LL, &v, sizeof(v)); 371 s = "TextControl"; 372 query.WriteAttr("_trk/focusedView", B_STRING_TYPE, 0LL, s.String(), 373 s.Length()+1); 374 s = full; 375 PRINT(("QUERY='%s'\n", s.String())); 376 query.WriteAttr("_trk/qryinitstr", B_STRING_TYPE, 0LL, s.String(), 377 s.Length()+1); 378 query.WriteAttr("_trk/qrystr", B_STRING_TYPE, 0LL, s.String(), 379 s.Length()+1); 380 s = "application/x-vnd.Be-query"; 381 query.WriteAttr("BEOS:TYPE", 'MIMS', 0LL, s.String(), s.Length()+1); 382 383 384 BEntry e(qname.String()); 385 entry_ref er; 386 if (e.GetRef(&er) >= B_OK) 387 be_roster->Launch(&er); 388 return; 389 } 390 391 if (proto == "sh") { 392 BString cmd(full); 393 if (_Warn(url.String()) != B_OK) 394 return; 395 PRINT(("CMD='%s'\n", cmd.String())); 396 cmd << pausec; 397 args[2] = (char*)cmd.String(); 398 be_roster->Launch(kTerminalSig, 3, args); 399 // TODO: handle errors 400 return; 401 } 402 403 if (proto == "beshare") { 404 team_id team; 405 BMessenger msgr(kBeShareSig); 406 // if no instance is running, or we want a specific server, start it. 407 if (!msgr.IsValid() || url.HasHost()) { 408 be_roster->Launch(kBeShareSig, (BMessage*)NULL, &team); 409 msgr = BMessenger(NULL, team); 410 } 411 if (url.HasHost()) { 412 BMessage mserver('serv'); 413 mserver.AddString("server", host); 414 msgr.SendMessage(&mserver); 415 416 } 417 if (url.HasPath()) { 418 BMessage mquery('quer'); 419 mquery.AddString("query", path); 420 msgr.SendMessage(&mquery); 421 } 422 // TODO: handle errors 423 return; 424 } 425 426 if (proto == "icq" || proto == "msn") { 427 // TODO 428 team_id team; 429 be_roster->Launch(kIMSig, (BMessage*)NULL, &team); 430 BMessenger msgr(NULL, team); 431 if (url.HasHost()) { 432 BMessage mserver(B_REFS_RECEIVED); 433 mserver.AddString("server", host); 434 msgr.SendMessage(&mserver); 435 436 } 437 // TODO: handle errors 438 return; 439 } 440 441 if (proto == "mms" || proto == "rtp" || proto == "rtsp") { 442 args[0] = (char*)url.String(); 443 be_roster->Launch(kVLCSig, 1, args); 444 return; 445 } 446 447 // Audio: ? 448 449 // vnc: ? 450 // irc: ? 451 // 452 // svn: ? 453 // cvs: ? 454 // smb: cifsmount ? 455 // nfs: mount_nfs ? 456 // 457 // mailto: ? Mail & Beam both handle it already (not fully though). 458 // 459 // mid: cid: as per RFC 2392 460 // http://www.rfc-editor.org/rfc/rfc2392.txt query MAIL:cid 461 // 462 // itps: pcast: podcast: s//http/ + parse xml to get url to mp3 stream... 463 // audio: s//http:/ + default MediaPlayer 464 // -- see http://forums.winamp.com/showthread.php?threadid=233130 465 // 466 // gps: ? I should submit an RFC for that one :) 467 468 } 469 470 471 status_t 472 UrlWrapper::_DecodeUrlString(BString& string) 473 { 474 // TODO: check for %00 and bail out! 475 int32 length = string.Length(); 476 int i; 477 for (i = 0; string[i] && i < length - 2; i++) { 478 if (string[i] == '%' && isxdigit(string[i+1]) 479 && isxdigit(string[i+2])) { 480 int c; 481 sscanf(string.String() + i + 1, "%02x", &c); 482 string.Remove(i, 3); 483 string.Insert((char)c, 1, i); 484 length -= 2; 485 } 486 } 487 488 return B_OK; 489 } 490 491 492 void 493 UrlWrapper::ReadyToRun(void) 494 { 495 Quit(); 496 } 497 498 499 // #pragma mark 500 501 502 int main(int argc, char** argv) 503 { 504 UrlWrapper app; 505 if (be_app) 506 app.Run(); 507 return 0; 508 } 509 510