1 /*********************************************************************
2 plugin.c: mime types and plugins.
3 Run audio players, pdf converters, etc, based on suffix or content-type.
4 *********************************************************************/
5 
6 #include "eb.h"
7 
8 #ifdef DOSLIKE
9 #include <process.h>		// for _getpid(),...
10 #define getpid _getpid
11 #endif
12 
13 /* create an input or an output file for edbrowse under /tmp.
14  * Since an external program may act upon this file, a certain suffix
15  * may be required.
16  * Fails if /tmp/.edbrowse does not exist or cannot be created. */
17 static char *tempin, *tempout;
18 
makeTempFilename(const char * suffix,int idx,bool output)19 static bool makeTempFilename(const char *suffix, int idx, bool output)
20 {
21 	char *filename;
22 
23 // if no temp directory then we can't proceed
24 	if (!ebUserDir) {
25 		setError(MSG_TempNone);
26 		return false;
27 	}
28 
29 	if (!suffix)
30 		suffix = "eb";
31 	if (asprintf(&filename, "%s/pf%d-%d.%s",
32 		     ebUserDir, getpid(), idx, suffix) < 0)
33 		i_printfExit(MSG_MemAllocError, strlen(ebUserDir) + 24);
34 
35 	if (output) {
36 // free the last one, don't need it any more.
37 		nzFree(tempout);
38 		tempout = filename;
39 	} else {
40 		nzFree(tempin);
41 		tempin = filename;
42 	}
43 
44 	return true;
45 }				/* makeTempFilename */
46 
47 static int tempIndex;
48 
findMimeBySuffix(const char * suffix)49 static const struct MIMETYPE *findMimeBySuffix(const char *suffix)
50 {
51 	int i;
52 	int len = strlen(suffix);
53 	const struct MIMETYPE *m = mimetypes;
54 
55 	if (!len)
56 		return NULL;
57 
58 	for (i = 0; i < maxMime; ++i, ++m) {
59 		const char *s = m->suffix, *t;
60 		if (!s)
61 			continue;
62 		while (*s) {
63 			t = strchr(s, ',');
64 			if (!t)
65 				t = s + strlen(s);
66 			if (t - s == len && memEqualCI(s, suffix, len))
67 				return m;
68 			if (*t)
69 				++t;
70 			s = t;
71 		}
72 	}
73 
74 	return NULL;
75 }				/* findMimeBySuffix */
76 
file2suffix(const char * filename)77 static char *file2suffix(const char *filename)
78 {
79 	static char suffix[12];
80 	const char *post, *s;
81 	post = filename + strlen(filename);
82 	for (s = post - 1; s >= filename && *s != '.' && *s != '/'; --s) ;
83 	if (s < filename || *s != '.')
84 		return NULL;
85 	++s;
86 	if (post >= s + sizeof(suffix))
87 		return NULL;
88 	strncpy(suffix, s, post - s);
89 	suffix[post - s] = 0;
90 	return suffix;
91 }
92 
url2suffix(const char * url)93 static char *url2suffix(const char *url)
94 {
95 	static char suffix[12];
96 	const char *post, *s;
97 
98 /*********************************************************************
99 We need to skip past protocol and host, suffix should not be the suffix on the host.
100 Example http://foo.bar.mobi when you have a suffix = mobi plugin.
101 But the urls we create for our own purposes don't look like protocol://host/file
102 They are what I call free_syntax in url.c.
103 See the plugins and protocols for zip files in the edbrowse wiki.
104 So I only run this check for the 3 recognized transport protocols
105 that might bring data into the edbrowse buffer.
106 The various ftp protocols all download data to files
107 and don't run plugins at all. We don't have to check for those.
108 *********************************************************************/
109 
110 	if (memEqualCI(url, "http:/", 6) ||
111 	    memEqualCI(url, "https:/", 7) || memEqualCI(url, "gopher:/", 8)) {
112 		s = strstr(url, "://");
113 		if (!s)		// should never happen
114 			s = url;
115 		else
116 			s += 3;
117 		s = strchr(s, '/');
118 		if (!s)
119 			return 0;
120 		url = s + 1;	// start here
121 	}
122 // lop off post data, get data, hash
123 	post = url + strcspn(url, "?\1");
124 	for (s = post - 1; s >= url && *s != '.' && *s != '/'; --s) ;
125 	if (s < url || *s != '.')
126 		return NULL;
127 	++s;
128 	if (post >= s + sizeof(suffix))
129 		return NULL;
130 	strncpy(suffix, s, post - s);
131 	suffix[post - s] = 0;
132 	return suffix;
133 }
134 
findMimeByProtocol(const char * prot)135 static const struct MIMETYPE *findMimeByProtocol(const char *prot)
136 {
137 	int i;
138 	int len = strlen(prot);
139 	const struct MIMETYPE *m = mimetypes;
140 	for (i = 0; i < maxMime; ++i, ++m) {
141 		const char *s = m->prot, *t;
142 		if (!s)
143 			continue;
144 		while (*s) {
145 			t = strchr(s, ',');
146 			if (!t)
147 				t = s + strlen(s);
148 			if (t - s == len && memEqualCI(s, prot, len))
149 				return m;
150 			if (*t)
151 				++t;
152 			s = t;
153 		}
154 	}
155 
156 	return NULL;
157 }				/* findMimeByProtocol */
158 
159 // look for match on protocol, suffix, or url string
findMimeByURL(const char * url,uchar * sxfirst)160 const struct MIMETYPE *findMimeByURL(const char *url, uchar * sxfirst)
161 {
162 	const char *prot, *suffix;
163 	const struct MIMETYPE *mt, *m;
164 	int i, j, l, url_length;
165 	char *s, *t;
166 
167 // protocol first, unless sxfirst is 1, then suffix first.
168 // If sxfirst = 2 then protocol only.
169 	if (*sxfirst == 1) {
170 		if ((suffix = url2suffix(url))
171 		    && (mt = findMimeBySuffix(suffix)))
172 			return mt;
173 	}
174 
175 	if ((prot = getProtURL(url)) && (mt = findMimeByProtocol(prot))) {
176 		*sxfirst = 0;
177 		return mt;
178 	}
179 	if (*sxfirst == 2)
180 		return 0;
181 
182 	url_length = strlen(url);
183 	m = mimetypes;
184 	for (i = 0; i < maxMime; ++i, ++m) {
185 		s = m->urlmatch;
186 		if (!s)
187 			continue;
188 		while (*s) {
189 			t = strchr(s, '|');
190 			if (!t)
191 				t = s + strlen(s);
192 			l = t - s;
193 			if (l && l <= url_length) {
194 				for (j = 0; j + l <= url_length; ++j) {
195 					if (memEqualCI(s, url + j, l)) {
196 						*sxfirst = 0;
197 						return m;
198 					}
199 				}
200 			}
201 			if (*t)
202 				++t;
203 			s = t;
204 		}
205 	}
206 
207 	if (!*sxfirst) {
208 		if ((suffix = url2suffix(url))
209 		    && (mt = findMimeBySuffix(suffix))) {
210 			*sxfirst = 1;
211 			return mt;
212 		}
213 	}
214 
215 	return NULL;
216 }				/* findMimeByURL */
217 
findMimeByFile(const char * filename)218 const struct MIMETYPE *findMimeByFile(const char *filename)
219 {
220 	char *suffix = file2suffix(filename);
221 	if (suffix)
222 		return findMimeBySuffix(suffix);
223 	return NULL;
224 }				/* findMimeByFile */
225 
findMimeByContent(const char * content)226 const struct MIMETYPE *findMimeByContent(const char *content)
227 {
228 	int i;
229 	int len = strlen(content);
230 	const struct MIMETYPE *m = mimetypes;
231 
232 	for (i = 0; i < maxMime; ++i, ++m) {
233 		const char *s = m->content, *t;
234 		if (!s)
235 			continue;
236 		while (*s) {
237 			t = strchr(s, ',');
238 			if (!t)
239 				t = s + strlen(s);
240 			if (t - s == len && memEqualCI(s, content, len))
241 				return m;
242 			if (*t)
243 				++t;
244 			s = t;
245 		}
246 	}
247 
248 	return NULL;
249 }				/* findMimeByContent */
250 
251 /*********************************************************************
252 Notes on where and why runPluginCommand is run.
253 
254 The pb (play buffer) command,
255 if the file has a suffix plugin for playing, not rendering.
256 .xxx will overrule the suffix.
257 As you would imagine, this runs through playBuffer() below.
258 If you override with .xxx I spin the buffer data out to a temp file.
259 I assume this is necessary, as the player would not accept the file
260 with its current suffix or filename, or you downloaded it
261 from the internet or some other source.
262 
263 g in directory mode if the file is playable, also goes through playBuffer().
264 
265 If a new buffer, (not an r command or fetching javascript
266 in the background or some such),
267 or if the match is on protocol and the plugin is a converter,
268 (whence the plugin is necessary just to get the data),
269 then httpConnect sets the plugin cf->mt,
270 if such can be determine from protocol or content type or suffix.
271 If this plugin is url allowed, and does not require url download,
272 then run the plugin. Set cf->render1 if it's a rendering plugin.
273 If rendered by suffix, then so indicate, but I've never seen this happen.
274 Music players can take a url, converters not.
275 pdftohtml for instance, doesn't take a url,
276 you have to download the data from the internet,
277 whence this plugin does not run, at least not here, not from httpConnect.
278 
279 When browsing a new buffer, b whatever, and it's a local file,
280 in readFile() in buffers.c, set cf->mt by suffix, and if it's rendering,
281 call the plugin so we don't have to pull the entire file into memory,
282 just the output of the plugin's converter.
283 Change b to e if the output is text, cause there's no html to browse.
284 This rendering will always be by suffix, there is no protocol or content type.
285 Mark it accordingly.
286 
287 If we're browsing a new buffer, and httpConnect hasn't already rendered,
288 and http code is 200 or 201,
289 and cf->mt indicates render, then do so in readFile().
290 Mark as rendered by url or by suffix.
291 Again, if outtype is t then change b to e.
292 
293 If we're browsing a new buffer, and http code is 200 or 201,
294 and suffix indicates play, then do so in readFile().
295 Return nothing, so we don't push a new buffer.
296 
297 If we're browsing a new buffer, and a suffix plugin would render,
298 but plugins are inactive, change b to e so we don't go through browseCurrentBuffer().
299 
300 In browseCurrentBuffer():
301 If this has not yet been rendered via suffix,
302 and you can find a plugin by suffix,
303 and it renders,
304 and it's different from the attached plugin,
305 then render it now. Set render2.
306 Why does it have to be different? Look at pdf.
307 httpConnect might find it by content = application/pdf.
308 That's not by suffix, so render2 is not set.
309 Here we are and we find it by suffix, but it's the same plugin,
310 so don't render it again.
311 *********************************************************************/
312 
runPluginCommand(const struct MIMETYPE * m,const char * inurl,const char * infile,const char * indata,int inlength,char ** outdata,int * outlength)313 bool runPluginCommand(const struct MIMETYPE * m,
314 		      const char *inurl, const char *infile, const char *indata,
315 		      int inlength, char **outdata, int *outlength)
316 {
317 	const char *s;
318 	char *cmd = NULL, *t;
319 	char *outfile;
320 	char *suffix;
321 	int len, inlen, outlen;
322 	bool has_o = false;
323 
324 	if (indata) {
325 // calling function has gathered the data for us,
326 // maybe we could pipe it to the program but for now
327 // I'm just putting it in a temp file having the same suffix.
328 		suffix = NULL;
329 		if (infile)
330 			suffix = file2suffix(infile);
331 		else
332 			suffix = url2suffix(inurl);
333 		++tempIndex;
334 		if (!makeTempFilename(suffix, tempIndex, false)) {
335 			cnzFree(indata);
336 			return false;
337 		}
338 		if (!memoryOutToFile(tempin, indata, inlength,
339 				     MSG_TempNoCreate2, MSG_NoWrite2)) {
340 			cnzFree(indata);
341 			return false;
342 		}
343 		infile = tempin;
344 	} else if (inurl)
345 		infile = inurl;
346 
347 // reserve an output file, whether we need it or not
348 	++tempIndex;
349 	suffix = "out";
350 	if (m->outtype == 't')
351 		suffix = "txt";
352 	if (m->outtype == 'h')
353 		suffix = "html";
354 	if (!makeTempFilename(suffix, tempIndex, true)) {
355 		if (indata) {
356 			cnzFree(indata);
357 			unlink(tempin);
358 		}
359 		return false;
360 	}
361 	outfile = tempout;
362 
363 	len = 0;
364 	inlen = shellProtectLength(infile);
365 	outlen = shellProtectLength(outfile);
366 	for (s = m->program; *s; ++s) {
367 		if (*s == '%' && s[1] == 'i') {
368 			len += inlen;
369 			++s;
370 			continue;
371 		}
372 		if (*s == '%' && s[1] == 'o') {
373 			has_o = true;
374 			len += outlen;
375 			++s;
376 			continue;
377 		}
378 		++len;
379 	}
380 	++len;
381 
382 // reserve space for > outfile
383 	cmd = allocMem(len + outlen + 3);
384 	t = cmd;
385 // pass 2
386 	for (s = m->program; *s; ++s) {
387 		if (*s == '%' && s[1] == 'i') {
388 			shellProtect(t, infile);
389 			t += inlen;
390 			++s;
391 			continue;
392 		}
393 		if (*s == '%' && s[1] == 'o') {
394 			shellProtect(t, outfile);
395 			t += outlen;
396 			++s;
397 			continue;
398 		}
399 		*t++ = *s;
400 	}
401 	*t = 0;
402 
403 /*********************************************************************
404 if there is no output, or the program has %o, then just run it,
405 otherwise we have to send its output over to outdata,
406 which should be present.
407 There's no popen on windows, so here is a unix only
408 fragment to use popen, which can be more efficient.
409 *********************************************************************/
410 
411 #ifndef DOSLIKE
412 	if (m->outtype && !has_o) {
413 		FILE *p;
414 		bool rc;
415 		debugPrint(3, "plugin %s", cmd);
416 		p = popen(cmd, "r");
417 		if (!p) {
418 			setError(MSG_NoSpawn, cmd, errno);
419 			goto fail;
420 		}
421 		rc = fdIntoMemory(fileno(p), outdata, outlength);
422 		pclose(p);
423 		if (!rc)
424 			goto fail;
425 		goto success;
426 	}
427 #endif
428 
429 	if (m->outtype && !has_o) {
430 		strcat(cmd, " > ");
431 		strcat(cmd, outfile);
432 	}
433 
434 	debugPrint(3, "plugin %s", cmd);
435 
436 // time to run the command.
437 	if (eb_system(cmd, !m->outtype) < 0)
438 		goto success;
439 
440 	if (!outdata)		// not capturing output
441 		goto success;
442 	if (!fileIntoMemory(outfile, outdata, outlength))
443 		goto fail;
444 // fall through
445 
446 success:
447 	nzFree(cmd);
448 	if (indata) {
449 		unlink(tempin);
450 		cnzFree(indata);
451 	}
452 	unlink(tempout);
453 	return true;
454 
455 fail:
456 	nzFree(cmd);
457 	if (indata) {
458 		unlink(tempin);
459 		cnzFree(indata);
460 	}
461 	unlink(tempout);
462 	return false;
463 }
464 
465 /* play the contents of the current buffer, or otherwise
466  * act upon it based on the program corresponding to its mine type.
467  * This is called from twoLetter() in buffers.c, and should return:
468 * 0 error, 1 success, 2 not a play buffer command */
playBuffer(const char * line,const char * playfile)469 int playBuffer(const char *line, const char *playfile)
470 {
471 	const struct MIMETYPE *mt = 0;
472 	const char *suffix = NULL;
473 	bool rc;
474 	char c = line[2];
475 	if (c && c != '.')
476 		return 2;
477 
478 	if (playfile) {
479 /* play the file passed in */
480 		mt = findMimeByFile(playfile);
481 // We wouldn't be here unless the file was playable,
482 // so this check and error return isn't really necessary.
483 #if 0
484 		if (!mt || mt->outtype) {
485 			suffix = file2suffix(playfile);
486 			if (!suffix)
487 				setError(MSG_NoSuffix);
488 			else
489 				setError(MSG_SuffixBad, suffix);
490 			return 0;
491 		}
492 #endif
493 		return runPluginCommand(mt, 0, playfile, 0, 0, 0, 0);
494 	}
495 
496 	if (!cw->dol) {
497 		setError(cw->dirMode ? MSG_EmptyBuffer : MSG_AudioEmpty);
498 		return 0;
499 	}
500 	if (cw->browseMode) {
501 		setError(MSG_AudioBrowse);
502 		return 0;
503 	}
504 	if (cw->sqlMode) {
505 		setError(MSG_AudioDB);
506 		return 0;
507 	}
508 	if (cw->dirMode) {
509 		setError(MSG_AudioDir);
510 		return 0;
511 	}
512 
513 	if (c) {
514 		char *buf;
515 		int buflen;
516 		suffix = line + 3;
517 		mt = findMimeBySuffix(suffix);
518 		if (!mt) {
519 			setError(MSG_SuffixBad, suffix);
520 			return 0;
521 		}
522 		if (mt->outtype) {
523 			setError(MSG_NotPlayer);
524 			return 0;
525 		}
526 // If you had to specify suffix then we have to run from the buffer.
527 		if (!unfoldBuffer(context, false, &buf, &buflen))
528 			return 0;
529 // runPluginCommand always frees the input data.
530 		return runPluginCommand(mt, 0, line, buf, buflen, 0, 0);
531 	}
532 
533 	if (!mt && cf->fileName) {
534 		if (isURL(cf->fileName)) {
535 			uchar sxfirst = 1;
536 			suffix = url2suffix(cf->fileName);
537 			mt = findMimeByURL(cf->fileName, &sxfirst);
538 		} else {
539 			suffix = file2suffix(cf->fileName);
540 			mt = findMimeByFile(cf->fileName);
541 		}
542 	}
543 	if (!mt) {
544 		if (suffix)
545 			setError(MSG_SuffixBad, suffix);
546 		else
547 			setError(MSG_NoSuffix);
548 		return 0;
549 	}
550 
551 	if (mt->outtype) {
552 		setError(MSG_NotPlayer);
553 		return 0;
554 	}
555 
556 	if (isURL(cf->fileName))
557 		rc = runPluginCommand(mt, cf->fileName, 0, 0, 0, 0, 0);
558 	else
559 		rc = runPluginCommand(mt, 0, cf->fileName, 0, 0, 0, 0);
560 	return rc;
561 }
562