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 /* 283 if (user.Length()) 284 cmd << "-l " << user << " "; 285 cmd << host; 286 */ 287 cmd << full; 288 PRINT(("CMD='%s'\n", cmd.String())); 289 cmd << failc; 290 args[2] = (char*)cmd.String(); 291 be_roster->Launch(kTerminalSig, 3, args); 292 // TODO: handle errors 293 return; 294 } 295 296 if (proto == "sftp") { 297 BString cmd("sftp "); 298 299 //cmd << url; 300 if (url.HasPort()) 301 cmd << "-oPort=" << port << " "; 302 if (url.HasUser()) 303 cmd << user << "@"; 304 cmd << host; 305 if (url.HasPath()) 306 cmd << ":" << path; 307 PRINT(("CMD='%s'\n", cmd.String())); 308 cmd << failc; 309 args[2] = (char*)cmd.String(); 310 be_roster->Launch(kTerminalSig, 3, args); 311 // TODO: handle errors 312 return; 313 } 314 315 if (proto == "finger") { 316 BString cmd("/bin/finger "); 317 318 if (url.HasUser()) 319 cmd << user; 320 if (url.HasHost() == 0) 321 host = "127.0.0.1"; 322 cmd << "@" << host; 323 PRINT(("CMD='%s'\n", cmd.String())); 324 cmd << pausec; 325 args[2] = (char*)cmd.String(); 326 be_roster->Launch(kTerminalSig, 3, args); 327 // TODO: handle errors 328 return; 329 } 330 331 if (proto == "http") { 332 BString cmd("/bin/wget "); 333 334 //cmd << url; 335 if (url.HasUser()) 336 cmd << user << "@"; 337 cmd << full; 338 PRINT(("CMD='%s'\n", cmd.String())); 339 cmd << pausec; 340 args[2] = (char*)cmd.String(); 341 be_roster->Launch(kTerminalSig, 3, args); 342 // TODO: handle errors 343 return; 344 } 345 346 if (proto == "file") { 347 BMessage m(B_REFS_RECEIVED); 348 entry_ref ref; 349 _DecodeUrlString(path); 350 if (get_ref_for_path(path.String(), &ref) < B_OK) 351 return; 352 m.AddRef("refs", &ref); 353 be_roster->Launch(kTrackerSig, &m); 354 return; 355 } 356 357 // XXX:TODO: split options 358 if (proto == "query") { 359 // mktemp ? 360 BString qname("/tmp/query-url-temp-"); 361 qname << getpid() << "-" << system_time(); 362 BFile query(qname.String(), O_CREAT|O_EXCL); 363 // XXX: should check for failure 364 365 BString s; 366 int32 v; 367 368 _DecodeUrlString(full); 369 // TODO: handle options (list of attrs in the column, ...) 370 371 v = 'qybF'; // QuerY By Formula XXX: any #define for that ? 372 query.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0LL, &v, sizeof(v)); 373 s = "TextControl"; 374 query.WriteAttr("_trk/focusedView", B_STRING_TYPE, 0LL, s.String(), 375 s.Length()+1); 376 s = full; 377 PRINT(("QUERY='%s'\n", s.String())); 378 query.WriteAttr("_trk/qryinitstr", B_STRING_TYPE, 0LL, s.String(), 379 s.Length()+1); 380 query.WriteAttr("_trk/qrystr", B_STRING_TYPE, 0LL, s.String(), 381 s.Length()+1); 382 s = "application/x-vnd.Be-query"; 383 query.WriteAttr("BEOS:TYPE", 'MIMS', 0LL, s.String(), s.Length()+1); 384 385 386 BEntry e(qname.String()); 387 entry_ref er; 388 if (e.GetRef(&er) >= B_OK) 389 be_roster->Launch(&er); 390 return; 391 } 392 393 if (proto == "sh") { 394 BString cmd(full); 395 if (_Warn(url.String()) != B_OK) 396 return; 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 if (proto == "beshare") { 406 team_id team; 407 BMessenger msgr(kBeShareSig); 408 // if no instance is running, or we want a specific server, start it. 409 if (!msgr.IsValid() || url.HasHost()) { 410 be_roster->Launch(kBeShareSig, (BMessage*)NULL, &team); 411 msgr = BMessenger(NULL, team); 412 } 413 if (url.HasHost()) { 414 BMessage mserver('serv'); 415 mserver.AddString("server", host); 416 msgr.SendMessage(&mserver); 417 418 } 419 if (url.HasPath()) { 420 BMessage mquery('quer'); 421 mquery.AddString("query", path); 422 msgr.SendMessage(&mquery); 423 } 424 // TODO: handle errors 425 return; 426 } 427 428 if (proto == "icq" || proto == "msn") { 429 // TODO 430 team_id team; 431 be_roster->Launch(kIMSig, (BMessage*)NULL, &team); 432 BMessenger msgr(NULL, team); 433 if (url.HasHost()) { 434 BMessage mserver(B_REFS_RECEIVED); 435 mserver.AddString("server", host); 436 msgr.SendMessage(&mserver); 437 438 } 439 // TODO: handle errors 440 return; 441 } 442 443 if (proto == "mms" || proto == "rtp" || proto == "rtsp") { 444 args[0] = (char*)url.String(); 445 be_roster->Launch(kVLCSig, 1, args); 446 return; 447 } 448 449 // Audio: ? 450 451 // vnc: ? 452 // irc: ? 453 // 454 // svn: ? 455 // cvs: ? 456 // smb: cifsmount ? 457 // nfs: mount_nfs ? 458 // 459 // mailto: ? Mail & Beam both handle it already (not fully though). 460 // 461 // mid: cid: as per RFC 2392 462 // http://www.rfc-editor.org/rfc/rfc2392.txt query MAIL:cid 463 // 464 // itps: pcast: podcast: s//http/ + parse xml to get url to mp3 stream... 465 // audio: s//http:/ + default MediaPlayer 466 // -- see http://forums.winamp.com/showthread.php?threadid=233130 467 // 468 // gps: ? I should submit an RFC for that one :) 469 470 } 471 472 473 status_t 474 UrlWrapper::_DecodeUrlString(BString& string) 475 { 476 // TODO: check for %00 and bail out! 477 int32 length = string.Length(); 478 int i; 479 for (i = 0; string[i] && i < length - 2; i++) { 480 if (string[i] == '%' && isxdigit(string[i+1]) 481 && isxdigit(string[i+2])) { 482 int c; 483 sscanf(string.String() + i + 1, "%02x", &c); 484 string.Remove(i, 3); 485 string.Insert((char)c, 1, i); 486 length -= 2; 487 } 488 } 489 490 return B_OK; 491 } 492 493 494 void 495 UrlWrapper::ReadyToRun(void) 496 { 497 Quit(); 498 } 499 500 501 // #pragma mark 502 503 504 int main(int argc, char** argv) 505 { 506 UrlWrapper app; 507 if (be_app) 508 app.Run(); 509 return 0; 510 } 511 512