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