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