xref: /haiku/src/bin/urlwrapper.cpp (revision 1acbe440b8dd798953bec31d18ee589aa3f71b73)
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