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