/*\ ||| Written By Fredrik Hübinette ||| All rights reserved. No warrenties, use at your own risk. ||| This source is distributed under the GNU GENERAL PUBLIC LICENCE, ||| see the file "COPYING" for more information. \*/ /* * Note to self: * * Filter functionality for next version: * filter: pnmtogif "$file" "$outfile" * * implementation: * o let plugger register a property notification * handler for the plugin window. * o the child will process the data into temporary files * and send the url back to plugger by setting the property * o the child then waits around for a while and deletes * the files afterwards. * o Make sure that plugger doesn't kill the child, then it * cannot cleanup the temporary files. * * * Todo: * o Make plugger create a hardlink in streamasfile * and send the name of the hardlink to the subprocess. * Hardlink should be deleted when the helper process exits. * This should fix two things: * 1) proper extension can be used, even when the file had * the wrong extension to begin with * 2) /etc/plugtmp/ files can be used more safely. * * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plugger.h" /* UGLY */ #define WINDOW (*(Window*) &(THIS->windata.window)) struct plugger_buffer { struct plugger_buffer *next; int begin, end; char data[CHUNK_SIZE]; }; struct argument { char *var; char *value; }; struct data { Display *display; char *displayname; int pid1; int pid2; NPWindow windata; char *mimetype; char *url; int repeats; int flags; char *command; char *winname; char embedded; char autostart; int waitfd; int num_arguments; struct argument *args; NPStream *stream; #ifdef STREAMER int fd; int peekfd; struct timeval start_buffering; int buffering; char buffered; struct plugger_buffer *buffer; #endif }; #define THIS (*(struct data **) &(instance->pdata)) #define FEND(X,Y) (strchr(X,Y)?strchr(X,Y):(X)+strlen(X)) #define xalloc malloc #define xfree free /* * Helper functions */ /* Check weather the first word in 'file' is somewhere to be found in * the users PATH. If so, return 1 otherwise 0. */ static int inpath(char *file) { struct stat buf; char tmp[16384]; char *path=getenv("PATH"); char *pos; if(!path) return 0; D("inpath(%s)\n",file); if(file[0]=='/') { pos=FEND(file,' '); memcpy(tmp,file,pos-file); tmp[pos-file]=0; return !stat(tmp, &buf); } if(!strncmp(file,"internal:",9)) return 1; if(file[0] == '$') return 1; /* Ugly ugly ugly */ if(file[0]=='i' && file[1]=='f' && isspace(file[2])) file+=3; /* Insert magical shell-script tests here */ D("Hmm? PATH=%s\n",path); pos=path; while(1) { char *next; next=FEND(pos,':'); if(next!=pos) { char *ptr=tmp; memcpy(ptr=tmp,pos,next-pos); ptr+=next-pos; *(ptr++)='/'; memcpy(ptr, file, FEND(file,' ')-file); ptr+=FEND(file,' ')-file; *ptr=0; D("stat(%s)\n",tmp); if(!stat(tmp, &buf)) return 1; D("nope\n"); } if(!*next) return 0; pos=next+1; } D("GAAHHH!\n"); } #ifndef MAX_FD #define MAX_FD 1024 #endif /* Shell for fork which handles some magic needed to prevent * netscape from freaking out. It also prevents interference * from signals. */ static int low_fork(int fd1, int fd2) { int pid; sigset_t set,oset; sigfillset(& set); D(">>>>>>>>Forking<<<<<<<<,\n"); sigprocmask(SIG_SETMASK,&set,&oset); pid=fork(); if(pid==-1) return pid; if(!pid) { int f; int signum; alarm(0); for(signum=0;signumflags & H_DAEMON)) MY_SETPGRP(); THIS->display=0; }else{ #ifdef STREAMER if(THIS->peekfd>=0) close(THIS->peekfd); #endif D("Child running with pid=%d\n",pid); } return pid; } static char *helper_fname=NULL; static char *controller_fname=NULL; static char *oohelper_fname=NULL; static char *config_fname=NULL; #define ENV_BUFFER 16384 void my_putenv(char *buffer, int *offset, const char *var, const char *value) { int l=strlen(var) + strlen(value) + 2; if(*offset + l >= ENV_BUFFER) { fprintf(stderr,"Plugger: Buffer overflow in putenv(%s=%s)\n",var, value); return; } sprintf(buffer+*offset, "%s=%s", var, value); D("putenv(%s)\n",buffer+*offset); putenv(buffer+*offset); *offset += l; } #define PUTENV(VAR,VAL) my_putenv(buffer, &offset, (VAR), (VAL)) static void run(NPP instance, const char *file) { char buffer[ENV_BUFFER]; int offset=0; int e; char *url; sprintf(buffer,"%d,%d,%d,%d,%d,%d,%d,%d", THIS->flags, THIS->repeats, THIS->waitfd, THIS->windata.window, THIS->windata.x, THIS->windata.y, THIS->windata.width, THIS->windata.height); D("Executing helper: %s %s %s %s %s %s\n", helper_fname, buffer, file, THIS->displayname, THIS->command, THIS->mimetype); offset=strlen(buffer)+1; if(THIS->url) { char *url=THIS->url; /* Massage file urls into filenames */ /* FIXME: we probably need to fix %20 and other stuff in here too */ if(!strncmp(url,"file:",5)) { url+=5; if(!strncmp(url,"///",3)) url+=2; } PUTENV("url",url); } if(THIS->mimetype) PUTENV("mimetype",THIS->mimetype); if(controller_fname) PUTENV("controller",controller_fname); if(oohelper_fname) PUTENV("oohelper",oohelper_fname); if(THIS->winname) PUTENV("winname",THIS->winname); if(THIS->displayname) PUTENV("DISPLAY",THIS->displayname); if(file) PUTENV("file",file); PUTENV("autostart", THIS->autostart ? "1" : "0"); for(e=0;enum_arguments;e++) PUTENV(THIS->args[e].var, THIS->args[e].value); execlp(helper_fname, helper_fname, buffer, THIS->command, 0); D("EXECLP FAILED!\n"); _exit(69); /* EX_UNAVAILABLE */ } /* * Functions below * are called from inside netscape/mozilla/opera */ struct mimetype { struct mimetype *next; char *line; }; struct command { struct command *next; int flags; char *cmd; char *winname; }; struct handle { struct handle *next; struct mimetype *types; struct command *commands; }; static struct handle *first_handle=NULL; long config_timestamp=0; long num_mime_types=0; static int gobble(char *line, char *kw) { return !strncasecmp(line,kw,strlen(kw)) && !isalnum(line[strlen(kw)]); } /* read configuration file into memory */ static void read_config(FILE *f) { int lineno=0; struct stat stat_tmp; struct handle **handlep; struct command **commandp=0; struct mimetype **typep=0; char buffer[16384]; int have_commands=1; D("read_config\n"); fstat(fileno(f), &stat_tmp); config_timestamp = stat_tmp.st_mtime - 1050382913; handlep=&first_handle; while(!feof(f)) { char *key; read_next_line: fgets(buffer,sizeof(buffer),f); lineno++; D("::: %s",buffer); if(buffer[0]=='#' || !buffer[0] || buffer[0]=='\n') continue; if(buffer[strlen(buffer)-1]=='\n') buffer[strlen(buffer)-1]=0; if(isspace(buffer[0])) { char *x=buffer+1; while(isspace(*x)) x++; if(!*x) { D("Empty line.\n"); continue; } D("New command\n"); *commandp=(struct command *)xalloc(sizeof(struct command)); if(!*commandp) { D("xalloc failed\n"); return; } (*commandp)->flags=0; (*commandp)->cmd=0; (*commandp)->winname=0; (*commandp)->next=0; /* Command */ while(*x!=':' && *x) { D("Parsing %s\n",x); switch(*x) { case ',': case ' ': case '\t': x++; break; default: #define GOBBLE(X,Y) \ if(gobble(x,X)) { D("GOBBLE: %s\n",X); x+=strlen(X); (*commandp)->flags|=Y; break; } GOBBLE("repeat",H_REPEATCOUNT); GOBBLE("loop",H_LOOP); GOBBLE("stream",H_STREAM); GOBBLE("preload",H_PRELOAD); GOBBLE("many",H_MANY); GOBBLE("ignore_errors",H_IGNORE_ERRORS); GOBBLE("exits",H_EXITS); GOBBLE("nokill",H_DAEMON); GOBBLE("maxaspect",H_MAXASPECT); GOBBLE("filter",H_FILTER); GOBBLE("fill",H_FILL); GOBBLE("url",H_URL); GOBBLE("embed",H_EMBED); GOBBLE("noembed",H_NOEMBED); GOBBLE("nocheck",H_NOCHECK); GOBBLE("hidden",H_HIDDEN); #ifdef H_NOISY GOBBLE("noisy",H_NOISY); #endif if(gobble(x,"controls")) { (*commandp)->flags |= H_CONTROLS | H_SWALLOW | H_FILL; (*commandp)->winname="plugger-controller"; x+=8; break; } key=0; if(gobble(x, "swallow")) { key = "swallow"; (*commandp)->flags|=H_SWALLOW; } else if(gobble(x,"have")) key = "have"; if(key) { char *end; x+=strlen(key); while(isspace(*x)) x++; if(*x != '(') { fprintf(stderr,"pluggerrc, line %d: Expected '(' after '%s'\n",lineno,key); continue; } x++; end=strchr(x,')'); if(!end) { fprintf(stderr,"pluggerrc, line %d: Expected ')' after '%s'\n",lineno,key); break; } if(key[0] == 's') /* swallow */ { (*commandp)->winname=xalloc(end - x +1); memcpy((*commandp)->winname, x, end-x); (*commandp)->winname[end-x]=0; } else /* have */ { *end=0; D("BURK\n"); if(!inpath(x)) { D("BURK YES!\n"); xfree(*commandp); D("BURK YES 222!\n"); (*commandp)=0; D("BURK YES 888!\n"); have_commands++; goto read_next_line; } *end=')'; } D("BURK YES 999!\n"); x=end+1; D("BORK: %s!\n",x); break; } D("Unknown directive: %s\n",x); /* Unknown directive */ fprintf(stderr,"pluggerrc, line %d: Unknown directive: %s\n",lineno,x); x+=strlen(x); } } have_commands++; if(*x==':') { D("COMMAND: %s\n",x); x++; while(isspace(*x)) x++; (*commandp)->cmd=strdup(x); }else{ D("No colon? (%s)\n",x); } if(!(*commandp)->cmd) { xfree(*commandp); *commandp=0; D("strdup failed %s\n",x); return; } D("GLOP\n"); commandp=&((*commandp)->next); D("PLOP\n"); }else{ /* Mime type */ if(have_commands) { D("New handle\n"); if(commandp) D("Commandp=%p\n",*commandp); *handlep=(struct handle *)xalloc(sizeof(struct handle)); if(!*handlep) return; (*handlep)->commands=0; (*handlep)->types=0; (*handlep)->next=0; commandp=&((*handlep)->commands); typep=&((*handlep)->types); handlep=&((*handlep)->next); have_commands=0; } D("New mimetype\n"); *typep=(struct mimetype *)xalloc(sizeof(struct mimetype)); num_mime_types++; if(!*typep) return; (*typep)->next=0; (*typep)->line=strdup(buffer); if(!(*typep)->line) { xfree(*typep); *typep=0; return; } typep=&((*typep)->next); } } } int find_helper_file(char *basename, int (*cb)(char *,void *data), void *data) { static char fname[8192]; char *tmp; D("find_helper_file '%s'\n",basename); if((tmp=getenv("HOME")) && strlen(tmp)<8000) { sprintf(fname,"%s/.plugger/%s",tmp,basename); if(cb(fname,data)) return 1; sprintf(fname,"%s/.mozilla/%s",tmp,basename); if(cb(fname,data)) return 1; sprintf(fname,"%s/.opera/%s",tmp,basename); if(cb(fname,data)) return 1; sprintf(fname,"%s/.netscape/%s",tmp,basename); if(cb(fname,data)) return 1; } if((tmp=getenv("MOZILLA_HOME")) && strlen(tmp)<8000) { sprintf(fname,"%s/%s",tmp,basename); if(cb(fname, data)) return 1; } if((tmp=getenv("OPERA_DIR")) && strlen(tmp)<8000) { sprintf(fname,"%s/%s",tmp,basename); if(cb(fname, data)) return 1; } #ifdef SYSCONFDIR sprintf(fname,SYSCONFDIR "/%s",basename); if(cb(fname, data)) return 1; #endif return 0; } static int read_config_cb(char *fname, void *data) { FILE *f; int m4out[2]; int pid,fd; D("READ_CONFIG(%s)\n",fname); fd=open(fname, O_RDONLY); if(fd < 0) return 0; config_fname=strdup(fname); if(pipe(m4out) < 0) { perror("pipe"); return 0; } D("PIPE[0]=%d PIPE[1]=%d\n",m4out[0],m4out[1]); pid=low_fork(fd,m4out[1]); if(pid == -1) return 0; if(!pid) { dup2(fd, 0); dup2(m4out[1], 1); close(fd); close(m4out[1]); execlp("m4","m4",0); exit(69); } close(fd); close(m4out[1]); f=fdopen(m4out[0],"r"); read_config(f); waitpid(pid, 0, 0); fclose(f); return 1; } static int find_plugger_helper_cb(char *fname, void *data) { struct stat buf; if(stat(fname, &buf)) return 0; helper_fname=strdup(fname); return 1; } static int find_plugger_oohelper_cb(char *fname, void *data) { struct stat buf; if(stat(fname, &buf)) return 0; oohelper_fname=strdup(fname); return 1; } static int find_plugger_controller_cb(char *fname, void *data) { struct stat buf; if(stat(fname, &buf)) return 0; controller_fname=strdup(fname); return 1; } /* Find configuration file and read it into memory */ static void do_read_config(void) { if(first_handle) return; D("do_read_config\n"); if(!find_helper_file("pluggerrc-" VERSION,read_config_cb,0) && !find_helper_file("pluggerrc",read_config_cb,0)) { fprintf(stderr,"Plugger: Unable to find pluggerrc file!\n"); return; } D("do_read_config done\n"); if(!find_helper_file("plugger-" VERSION, find_plugger_helper_cb, 0)) if(inpath("plugger-" VERSION)) helper_fname="plugger-" VERSION; if(!find_helper_file("plugger-controller", find_plugger_controller_cb, 0)) if(inpath("plugger-controller")) controller_fname="plugger-controller"; if(!find_helper_file("plugger-oohelper", find_plugger_oohelper_cb, 0)) if(inpath("plugger-oohelper")) oohelper_fname="plugger-oohelper"; if(!helper_fname) fprintf(stderr,"Plugger: Unable to find plugger-" VERSION "\n"); } /* Construct a MIME Description string for the browser * so that the browsser shall know when to call us back. */ char *NPP_GetMIMEDescription(void) { char *x,*y; struct handle *h; struct mimetype *m; int size_required; do_read_config(); D("Getmimedescription\n"); size_required=0; for(h=first_handle;h;h=h->next) for(m=h->types;m;m=m->next) size_required+=strlen(m->line)+1; D("Size required=%d\n",size_required); x=(char *)xalloc(size_required+1); if(!x) return 0; D("Malloc did not fail\n"); y=x; for(h=first_handle;h;h=h->next) { D("Foo: %p\n",h->commands); for(m=h->types;m;m=m->next) { char *tmp; /* D("appending: %s\n",m->line); */ memcpy(y,m->line,strlen(m->line)); y+=strlen(m->line); *(y++)=';'; } } if(x!=y) y--; *(y++)=0; D("Getmimedescription done: %s\n",x); return x; } /* Go through the commands in the config file * and find one that fits our needs. */ static int find_command(NPP instance, int streaming, int embedded) { struct handle *h; struct mimetype *m; struct command *c; D("find_command\n"); do_read_config(); D("find_command...\n"); for(h=first_handle;h;h=h->next) { D("commands for this handle at (%p)\n",h->commands); for(m=h->types;m;m=m->next) { char *tmp1=FEND(m->line,':'); char tmp2; int tmp3; D("Checking '%s'\n",m->line); while(tmp1>m->line && isspace(tmp1[-1])) tmp1--; D("tmp1=%s\n",tmp1); tmp2=*tmp1; *tmp1=0; D("Checking '%s' ?= '%s'\n",m->line,THIS->mimetype); tmp3=strcasecmp(THIS->mimetype, m->line); *tmp1=tmp2; if(!tmp3) { D("Match found!\n"); break; }else{ D("Not same.\n"); } } if(m) { for(c=h->commands;c;c=c->next) { D("Checking command: %s (%x)\n",c->cmd, c->flags); if(embedded && (c->flags & H_NOEMBED)) continue; if((!embedded) && (c->flags & H_EMBED)) continue; if((c->flags & H_LOOP) && THIS->repeats != MAXINT) continue; if( (!!streaming) != (!!(c->flags & H_STREAM))) continue; if(!(c->flags & H_NOCHECK)) if(!inpath(c->cmd)) continue; if(!strcmp(c->cmd, "internal:dwim")) { char *url_ext, *u; int e; for(e=0;enum_arguments;e++) { if(!strcasecmp(THIS->args[e].var, "var_type") || !strcasecmp(THIS->args[e].var, "var_mimetype") ) { char *omt = THIS->mimetype; THIS->mimetype = NPN_MemAlloc(strlen(THIS->args[e].value)+1); strcpy(THIS->mimetype, THIS->args[e].value); D("New mimetype by argument: %s\n",THIS->mimetype); if(find_command(instance, streaming, embedded)) { if(omt) NPN_MemFree(omt); return 1; } NPN_MemFree(THIS->mimetype); THIS->mimetype=omt; } } url_ext = 0; for(u=THIS->url;u && *u;u++) if(*u=='.') url_ext=u+1; if(url_ext) { struct handle *h2; struct mimetype *m2; D("Url ext: %s\n", url_ext); for(h2=first_handle;h2;h2=h2->next) { D("commands for this handle at (%p)\n",h2->commands); for(m2=h2->types;m2;m2=m2->next) { char *x, *end; x=strchr(m2->line,':'); if(!x) continue; x++; end=strchr(m2->line,':'); if(!end) continue; while(1) { char *ext; while(*x==' ' || *x=='.' || *x==',' || *x=='\t') x++; if(*x == ':' || !*x) break; ext=x; while(*x!=':' && *x!=',' && *x!=' ' && *x!='\t') x++; D("Checking ext (len=%d): %s\n",x-ext,ext); if(strlen(url_ext) == x - ext && !strncasecmp(url_ext, ext, x-ext)) { char *mend=FEND(m2->line,':'); char *omt = THIS->mimetype; THIS->mimetype = NPN_MemAlloc(mend - m2->line + 1); memcpy(THIS->mimetype, m2->line, mend - m2->line); THIS->mimetype[mend - m2->line]=0; D("New mimetype by extension: %s\n",THIS->mimetype); if(find_command(instance, streaming, embedded)) { if(omt) NPN_MemFree(omt); return 1; } NPN_MemFree(THIS->mimetype); THIS->mimetype=omt; } } } } } }else{ D("Match found!\n"); THIS->command=c->cmd; THIS->flags=c->flags; THIS->winname=c->winname; return 1; } } } } D("No match found\n"); return 0; } /* Let netscape know things about plugger */ NPError NPP_GetValue(void *future, NPPVariable variable, void *value) { static char name_buffer[256]; static char desc_buffer[8192]; NPError err = NPERR_NO_ERROR; D("Getvalue %d\n",variable); do_read_config(); switch (variable) { case NPPVpluginNameString: D("GET Plugin name\n"); sprintf(name_buffer, "Plugger %s.(%x) handles QuickTime Windows Media Player Plugin", VERSION, config_timestamp/5); *((char **)value) = name_buffer; break; case NPPVpluginDescriptionString: D("GET Plugin description\n"); sprintf(desc_buffer, "" "Plugger " "version " VERSION ", written by " "Fredrik Hubinette " "<hubbe@hubbe.net>. " "For documentation on how to configure plugger, go to the plugger " " homepage " "or check the man page. (type man plugger)" " Currently handling %ld mime types. " " " " " " " " " " " "
Config file:%s
Helper binary:%s
Controller binary:%s
OpenOffice helper:%s
" "
", num_mime_types, config_fname ? config_fname : "Not found!", helper_fname ? helper_fname : "Not found!", controller_fname ? controller_fname : "Not found!", oohelper_fname ? oohelper_fname : "Not found!"); *((char **)value) = desc_buffer; break; default: err = NPERR_GENERIC_ERROR; } return err; } static int my_atoi(char *s, int my_true, int my_false) { switch(s[0]) { case 't': case 'T': case 'y': case 'Y': return my_true; case 'f': case 'F': case 'n': case 'N': return my_false; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return atoi(s); } } static int isabsurl(char *data) { for(;;data++) { if(!*data) return 0; if(data[0] >= 'a' && data[0] <= 'z') continue; if(data[0] >= 'A' && data[0] <= 'Z') continue; if(data[0] >= '0' && data[0] <= '9') continue; return data[0] == ':' && data[1] == '/' && data[2] == '/'; } } static NPError really_find_command(NPP instance, int seekable) { int wantstream; D("really_find_command!\n"); THIS->command=0; THIS->flags=0; THIS->winname=0; D("Url is %s (seekable=%d)\n",THIS->url,seekable); #ifdef STREAMER /* Hmm, this seems weird... */ wantstream=!(seekable && !strncasecmp(THIS->url,"file:",5)); #else wantstream=0; #endif if(!find_command(instance, wantstream, THIS->embedded)) { if(!find_command(instance, !wantstream, THIS->embedded)) { if(!inpath("xmessage")) { NPN_Status(instance, "No appropriate application found!"); return NPERR_GENERIC_ERROR; }else{ D("Using xmessage!!!\n"); /* Hard-coded default */ THIS->command="xmessage -geometry +9000+9000 -buttons '' \"Plugger: No appropriate application for type $mimetype found!\""; THIS->flags = H_REPEATCOUNT | H_FILL | H_SWALLOW | H_IGNORE_ERRORS | H_URL; THIS->winname="Xmessage"; } } } return NPERR_NO_ERROR; } static NPError start_standalone(NPP instance) { int bar[2]; D("start_standalone!\n"); if(!THIS->command) { D("no command!\n"); return; } if(!WINDOW) { D("No window!\n"); return; } if(THIS->flags & H_URL) { if(!THIS->url) { D("No url!\n"); return; } }else{ if(!THIS->stream) { D("No stream!\n"); return; } } if(THIS->waitfd!=-1) { D("Already started!\n"); return; } D("Actually starging!\n"); if(socketpair(AF_UNIX, SOCK_STREAM, 0, bar) < 0) { NPN_Status(instance, "Plugger: Failed to create a pipe!"); return NPERR_GENERIC_ERROR; } THIS->pid1=my_fork(instance, bar[1], -1); if(THIS->pid1==-1) { NPN_Status(instance, "My_Fork failed!"); return; } if(!THIS->pid1) { D("Streaming child running\n"); THIS->repeats=1; THIS->waitfd=bar[1]; D("CHILD RUNNING run() [1]\n"); run(instance, 0); _exit(69); }else{ THIS->waitfd=bar[0]; close(bar[1]); } } /* Initiate another instance of plugger, it is important to know * that there might be several instances going at one time. */ NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char* argn[], char* argv[], NPSavedData* saved) { int q; int e; NPError tmp; D("NEW (%s)\n",pluginType); if (!instance) return NPERR_INVALID_INSTANCE_ERROR; THIS = NPN_MemAlloc(sizeof(struct data)); if(!THIS) return NPERR_OUT_OF_MEMORY_ERROR; memset((char *)THIS, 0, sizeof(*THIS)); /* Change default to repeat once? */ THIS->repeats=1; THIS->autostart=1; THIS->pid1=-1; THIS->pid2=-1; WINDOW=0; THIS->displayname=0; THIS->display=0; THIS->waitfd=-1; #ifdef STREAMER THIS->fd=-1; THIS->peekfd=-1; #endif if(pluginType) { if(THIS->mimetype) { NPN_MemFree(THIS->mimetype); THIS->mimetype=0; } THIS->mimetype = NPN_MemAlloc(strlen(pluginType)+1); if(!THIS->mimetype) return NPERR_OUT_OF_MEMORY_ERROR; strcpy(THIS->mimetype, pluginType); } THIS->embedded = mode == NP_EMBED; THIS->args = (struct argument *) NPN_MemAlloc( sizeof(struct argument) * argc ); for(q=e=0;eurl = NPN_MemAlloc(strlen(argv[e])+1); if(!THIS->url) return NPERR_OUT_OF_MEMORY_ERROR; strcpy(THIS->url, argv[e]); } } if(!strcasecmp("loop",argn[e])) THIS->repeats = my_atoi(argv[e], 1 , MAXINT); if(!strcasecmp("autostart",argn[e]) || !strcasecmp("autoplay",argn[e])) THIS->autostart=!!my_atoi(argv[e], 1, 0); THIS->args[q].var = (char *)malloc(strlen(argn[e]) + 5); memcpy(THIS->args[q].var,"VAR_",4); memcpy(THIS->args[q].var+4,argn[e],strlen(argn[e])+1); THIS->args[q].value = strdup(argv[e]); q++; } THIS->num_arguments = q; if(THIS->mimetype && THIS->url) really_find_command(instance, 0); return NPERR_NO_ERROR; } /* Free data, kill processes, it is time for this instance to die */ NPError NPP_Destroy(NPP instance, NPSavedData** save) { int e; if (!instance) return NPERR_INVALID_INSTANCE_ERROR; D("Destroy\n"); if (THIS) { if(THIS->pid1 > 0) plugger_kill_group(THIS->pid1,0 ); if(THIS->pid2 > 0) plugger_kill_group(THIS->pid2,0 ); D("Freeing memory %p\n",THIS->mimetype); if(THIS->mimetype) { NPN_MemFree(THIS->mimetype); THIS->mimetype=0; } if(THIS->url) { NPN_MemFree(THIS->url); THIS->url=0; } #ifdef STREAMER D("Closing fds\n"); if(THIS->fd >= 0) { close(THIS->fd); THIS->fd=-1; } if(THIS->peekfd>=0) { close(THIS->peekfd); THIS->peekfd=-1; } if(THIS->waitfd >= 0) { close(THIS->waitfd); THIS->waitfd=-1; } while(THIS->buffer) { struct plugger_buffer *tmp=THIS->buffer; THIS->buffer=tmp->next; NPN_MemFree(tmp); } for(e=0;enum_arguments;e++) { free((char *) THIS->args[e].var); THIS->args[e].var=0; free((char *) THIS->args[e].value); THIS->args[e].value=0; } THIS->num_arguments=0; NPN_MemFree( (char *) THIS->args); THIS->args=0; NPN_MemFree(THIS); THIS = 0; #endif } D("Destroy finished\n"); return NPERR_NO_ERROR; } #if defined(DEBUG) && defined(STREAMER) static void validate_buffers(NPP instance) { struct plugger_buffer *b; int total=0; for(b=THIS->buffer;b;b=b->next) { if(b->begin < 0 || b->end > CHUNK_SIZE || b->begin > b->end) { D("!!!!!!!VALIDATE FAILED, begin=%d end=%d\n",b->begin, b->end); abort(); } if(b->next && b->end != CHUNK_SIZE) { D("!!!!!!!VALIDATE FAILED, begin=%d end=%d next=%p\n", b->begin, b->end, b->next); abort(); } total+=b->end - b->begin; } if(total != THIS->buffered) { D("!!!!!!!!!VALIDATE FAILED, total (%d) != THIS->buffered (%d)\n", total, THIS->buffered); abort(); } } #else #define validate_buffers() #endif /* Open a new stream, * each instance can only handle one stream at a time. */ NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream *stream, NPBool seekable, uint16 *stype) { D("Newstream ... \n"); if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; /* This is because mozilla is b0rken, it sends * streams twice to embedded plugins. BAD MOZILLA! */ if(!THIS->stream) THIS->stream = stream; if(THIS->stream != stream) return NPERR_GENERIC_ERROR; if(THIS->waitfd!=-1) return NPERR_GENERIC_ERROR; /* This is a stupid special case and should be coded into * pluggerc instead... */ if(!strncasecmp("image/",type,6) || !strncasecmp("x-image/",type,6)) THIS->repeats=1; D("Mime type %s\n",type); if(THIS->mimetype) { NPN_MemFree(THIS->mimetype); THIS->mimetype=0; } THIS->mimetype = NPN_MemAlloc(strlen(type)+1); if(!THIS->mimetype) return NPERR_OUT_OF_MEMORY_ERROR; strcpy(THIS->mimetype, type); if(THIS->url) { NPN_MemFree(THIS->url); THIS->url=0; } THIS->url = NPN_MemAlloc(strlen(stream->url)+1); if(!THIS->url) return NPERR_OUT_OF_MEMORY_ERROR; strcpy(THIS->url, stream->url); really_find_command(instance, seekable); if(!THIS->command) return NPERR_GENERIC_ERROR; /* Let the command download the file on it's own */ if(THIS->flags & H_URL) { start_standalone(instance); /* This will prevent mozilla from trying to download the file */ if(THIS->waitfd!=-1) return NPERR_GENERIC_ERROR; } #ifdef STREAMER if((THIS->flags & H_STREAM) && strncasecmp(stream->url,"file:",5)) { int foo[2],bar[2]; if(THIS->repeats == 1 || (THIS->flags & H_LOOP) || (THIS->flags & H_DAEMON) || (THIS->flags & H_REPEATCOUNT)) *stype=NP_NORMAL; else *stype=NP_ASFILE; if(socketpair(AF_UNIX, SOCK_STREAM, 0, foo)<0 || socketpair(AF_UNIX, SOCK_STREAM, 0, bar) < 0) { NPN_Status(instance, "Streamer: Failed to create a pipe!"); return NPERR_GENERIC_ERROR; } D("SOCKETS: %d<->%d && %d<->%d\n",foo[0],foo[1],bar[0],bar[1]); THIS->pid1=my_fork(instance, bar[1], foo[0]); if(THIS->pid1==-1) { NPN_Status(instance, "Streamer: My_Fork failed!"); return; } if(!THIS->pid1) { D("Streaming child running\n"); dup2(foo[0],0); close(foo[0]); THIS->repeats=1; THIS->waitfd=bar[1]; D("CHILD RUNNING run() [1]\n"); run(instance, 0); _exit(69); }else{ if(THIS->repeats < MAXINT) THIS->repeats--; THIS->fd=foo[1]; /* This is the FD we write data to */ D("FD to parent = %d\n",THIS->fd); fcntl(THIS->fd, F_SETFL, O_NDELAY); THIS->waitfd=bar[0]; close(bar[1]); if(THIS->flags & H_PRELOAD) { gettimeofday(& THIS->start_buffering,0); THIS->buffering=1; THIS->peekfd=foo[0]; }else{ close(foo[0]); } } D("Ok\n"); }else #endif *stype = NP_ASFILEONLY; return NPERR_NO_ERROR; } #ifdef STREAMER /* Try to write data to our children */ static int trywrite(NPP instance) { D("trywrite (%d bytes buffered) fd=%d\n",THIS->buffered,THIS->fd); validate_buffers(instance); if(THIS->buffering) { struct timeval t; int p, p1, p2, p3; char b[256]; gettimeofday(&t, 0); p3=p1=THIS->buffered * 100; p1/=PRELOAD; p3/=BUFSIZE; p2=((t.tv_sec - THIS->start_buffering.tv_sec) * 100 + (t.tv_usec - THIS->start_buffering.tv_usec) / (10000) ) / PRELOAD_TIME; /* If we have less than PRELOAD bytes, * or, if we have been buffering for less than * PRELOAD_TIME seconds, we keep buffering */ p=MAXIMUM(p3,MINIMUM(p1,p2)); sprintf(b,"Buffering ... %2d%%",MINIMUM(p,100)); D("%s\n",b); NPN_Status(instance, b); if(p<100) return 1; THIS->buffering=0; } validate_buffers(instance); if(THIS->peekfd>=0) { D("Checking waitfd\n"); if(plugger_data_available(THIS->waitfd)) { close(THIS->peekfd); THIS->peekfd=-1; } } validate_buffers(instance); while(THIS->buffered) { int i; struct plugger_buffer *buffer=THIS->buffer; validate_buffers(instance); #ifdef DEBUG if(!buffer) D("THIS->buffered out of sync!!!!!\n"); #endif D("Bufferred: %d, begin=%d end=%d diff=%d next=%p\n", THIS->buffered, buffer->begin, buffer->end, buffer->end - buffer->begin, buffer->next); if(buffer->begin == CHUNK_SIZE) { THIS->buffer=buffer->next; NPN_MemFree(buffer); continue; } do { D("trywrite %d bytes\n", buffer->end - buffer->begin); i=write(THIS->fd, buffer->data+buffer->begin, buffer->end - buffer->begin); D("Wrote %d bytes (errno = %d)\n",i, errno); } while(i<0 && errno==EINTR); if(i<0) { switch(errno) { case EALREADY: case EAGAIN: return 1; default: D("trywrite: Errno = %d\n",errno); return 0; } } if(!i) return 1; buffer->begin+=i; THIS->buffered-=i; validate_buffers(instance); } validate_buffers(instance); D("Checking preload\n"); /* Hmm, this will not work properly with * programs which buffer more data than we do.. * With luck it doesn't affect streaming though. */ if((THIS->flags & H_PRELOAD) && THIS->peekfd>=0 && !plugger_data_available(THIS->peekfd)) { D("(Re)-starting preload\n"); THIS->buffering=1; gettimeofday(& THIS->start_buffering,0); } D("trywrite-exit: errno = %d\n",errno); return 1; } #endif /* */ int32 NPP_WriteReady(NPP instance, NPStream *stream) { #ifdef STREAMER int ret; D("Writeready\n"); if(!instance) return 0; /* BAD MOZILLA */ if(THIS->stream != stream) return 0x7fffffff; trywrite(instance); ret=THIS->buffered % CHUNK_SIZE; return ret ? ret : CHUNK_SIZE; #else return 0x7fffffff; #endif } int32 NPP_Write(NPP instance, NPStream *stream, int32 offset, int32 len, void *b) { #ifdef STREAMER struct plugger_buffer *buffer; int32 origlen=len; int i; char *buf=b; D("Write(len=%d, offset=%d)\n",len,offset); if (!instance) return 0; /* BAD MOZILLA */ if(THIS->stream != stream) return len; if(THIS->fd==-1) { THIS->buffered=0; return len; } if(!trywrite(instance)) return -1; if((buffer=THIS->buffer)) while(buffer->next) buffer=buffer->next; validate_buffers(instance); while(len) { struct plugger_buffer **bufp; validate_buffers(instance); if(buffer) { bufp=&buffer->next; if(buffer->end < CHUNK_SIZE) { int l=MIN(len, CHUNK_SIZE - buffer->end); validate_buffers(instance); memcpy(buffer->data + buffer->end, buf, l); buffer->end+=l; THIS->buffered+=l; len-=l; buf+=l; validate_buffers(instance); if(!len) break; } }else{ bufp=&THIS->buffer; } *bufp=buffer=(struct plugger_buffer *)NPN_MemAlloc( sizeof(struct plugger_buffer)); if(!buffer) { D("OUT OF MEMORY!!!\n"); return origlen - len; } buffer->end=buffer->begin=0; buffer->next=0; validate_buffers(instance); } if(!trywrite(instance)) return -1; if(THIS->buffered > BUFSIZE) { struct timeval timeout; int x=THIS->buffered*100/BUFSIZE; x-=100; timeout.tv_sec=x/100; timeout.tv_usec=(x%100)*10000; D("Plugger sleeing briefly.. (buffered=%d) (%d:%d)\n", THIS->buffered, timeout.tv_sec, timeout.tv_usec); select(0,0,0,0,&timeout); D("Plugger sleep done....\n"); } D("Write returns %d\n",origlen-len); return origlen-len; #else return len; #endif } NPError NPP_DestroyStream(NPP instance, NPStream *stream, NPError reason) { D("Destroystream %d\n", reason); /* BAD MOZILLA */ if(THIS->stream != stream) return NPERR_NO_ERROR; THIS->stream=(NPStream *)-1; #ifdef STREAMER if(THIS->flags & H_STREAM) { THIS->buffering=0; if(THIS->peekfd>=0) { close(THIS->peekfd); THIS->peekfd=-1; } if(trywrite(instance)) { if(THIS->buffered) { /* We fork again, somebody has to send the rest of our * stream to our children. */ int pid=my_fork(instance,THIS->fd,-1); if(pid==-1) return NPERR_GENERIC_ERROR; if(!pid) { fcntl(THIS->fd, F_SETFL, 0); while(THIS->buffered && trywrite(instance)); D("Buffer-cleanup done\n"); _exit(0); } } } close(THIS->fd); THIS->fd=-1; } D("Destroystream done\n"); #endif return NPERR_NO_ERROR; } void NPP_StreamAsFile(NPP instance, NPStream *stream, const char* fname) { int pid; D("Streamasfile\n"); if(!fname) return; if (instance == NULL) return; if(THIS->stream != stream) return; NPN_Status (instance, "Running helper ..."); if(!strcmp(THIS->command, "internal:url") && fname) { int fd; long len; struct stat buf; char *url; if(stat(fname, &buf)) { if(stream->end) { len=stream->end; }else{ NPN_Status(instance,"Plugger: Failed to determine length of file\n"); return; } }else{ len=buf.st_size; } url=NPN_MemAlloc(len+1); D("INTERNAL URL\n"); fd=open(fname,O_RDONLY); if(fd<0) { NPN_Status(instance,"Plugger: Hey, where did the file go?\n"); } else { if(read(fd, url, len) == len) { url[len]=0; FEND(url,'\n')[0]=0; D("URL: %s\n",url); NPN_GetURL(instance, url, 0); } close(fd); } NPN_MemFree(url); }else{ int bar[2]; bar[0]=-1; bar[1]=-1; socketpair(AF_UNIX, SOCK_STREAM, 0, bar); D("......going to fork......\n"); THIS->pid2=my_fork(instance,THIS->waitfd,bar[1]); if(THIS->pid2==-1) return; if(!THIS->pid2) { D("CHILD RUNNING run() [7]\n"); #ifdef STREAMER if(THIS->flags & H_STREAM) { char foo[1]; D("Waiting for streaming child to exit.\n"); while(read(THIS->waitfd, foo, 1) < 0 && errno==EINTR); if(THIS->repeats < MAXINT) THIS->repeats--; } #endif D("CHILD RUNNING run() [9]\n"); THIS->waitfd=bar[1]; #if 0 if(!find_command(instance, 0, THIS->embedded)) { if(!find_command(instance, 1, THIS->embedded)) { if(inpath("xmessage")) { D("Using xmessage!!!\n"); /* Hard-coded default */ THIS->command="xmessage -center -buttons '' \"Plugger: No appropriate application for type $mimetype found!\""; THIS->flags = H_REPEATCOUNT | H_FILL | H_SWALLOW | H_IGNORE_ERRORS; THIS->winname="Xmessage"; }else{ _exit(EX_UNAVAILABLE); } } } #endif D("CHILD RUNNING run() [2]\n"); run(instance, fname); _exit(69); }else{ THIS->waitfd=bar[0]; close(bar[1]); } } } NPError NPP_Initialize(void) { D("init\n"); return NPERR_NO_ERROR; } jref NPP_GetJavaClass() { D("Getjavaclass\n"); return NULL; } void NPP_Shutdown(void) { D("Shutdown\n"); } NPError NPP_SetWindow(NPP instance, NPWindow* window) { D("SETWINDOW\n"); if (!instance) return NPERR_INVALID_INSTANCE_ERROR; if (!window) return NPERR_NO_ERROR; if (!window->window) return (NPERR_NO_ERROR); if (!window->ws_info) return (NPERR_NO_ERROR); THIS->display= ((NPSetWindowCallbackStruct *)window->ws_info)->display; THIS->displayname=XDisplayName( DisplayString(THIS->display)); THIS->windata = *window; D("Displayname=%s Window=%x %d %d %d %d\n", THIS->displayname, WINDOW, window->x,window->y, window->width, window->height); XResizeWindow(THIS->display, WINDOW, window->width, window->height); XSync (THIS->display, FALSE); if(THIS->waitfd!=-1) { D("Writing WIN to fd %d\n",THIS->waitfd); plugger_write(THIS->waitfd, (char *)window, sizeof(*window)); usleep(4000); }else{ start_standalone(instance); } return NPERR_NO_ERROR; } void NPP_Print(NPP instance, NPPrint* printInfo) { D("PRINT\n"); if(printInfo == NULL) return; if (instance != NULL) { if (printInfo->mode == NP_FULL) { void* platformPrint = printInfo->print.fullPrint.platformPrint; NPBool printOne = printInfo->print.fullPrint.printOne; /* Do the default*/ printInfo->print.fullPrint.pluginPrinted = FALSE; } else { /* If not fullscreen, we must be embedded */ NPWindow* printWindow = &(printInfo->print.embedPrint.window); void* platformPrint = printInfo->print.embedPrint.platformPrint; } } }