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