/*
 * http_mime.c: Sends/gets MIME headers for requests
 * 
 * All code contained herein is covered by the Copyright as distributed
 * in the README file in the main directory of the distribution of 
 * NCSA HTTPD.
 *
 * Based on NCSA HTTPd 1.3 by Rob McCool
 * 
 * 
 * 03/19/95 blong
 *      Added set_stat_line as part of making user config error messages work
 *      The correct status line should now be sent back
 *
 * 04/20/95 blong
 *	Added a modified "B18" from apache patches by Rob Hartill
 */


#include "httpd.h"
#include "new.h"

struct mime_ext {
    char *ext;
    char *ct;
    struct mime_ext *next;
};

#if 1
#define hash(i) (isalpha(i) ? (tolower(i)) - 'a' : 26)
#else
#define hash(i) ((i) % 27)
#endif

/* Hash table */
struct mime_ext *types[27];
struct mime_ext *forced_types;
struct mime_ext *encoding_types;
struct mime_ext *Saved_Forced;
struct mime_ext *Saved_Encoding;

int content_length;
char content_type[MAX_STRING_LEN];
char content_encoding[MAX_STRING_LEN];

char location[MAX_STRING_LEN];
static char last_modified[MAX_STRING_LEN];

char auth_line[MAX_STRING_LEN];

char *out_headers = NULL;
char **in_headers_env = NULL;
char *status_line = NULL;
char ims[MAX_STRING_LEN]; /* If-modified-since */

extern FILE *agent_log;
extern FILE *referer_log;
extern char referer_ignore[MAX_STRING_LEN];
char referer[HUGE_STRING_LEN];

void hash_insert(struct mime_ext *me) {
    register int i = hash(me->ext[0]);
    register struct mime_ext *p, *q;

    if(!(q=types[i])) {
        types[i]=me;
        return;
    }
    if((!(p=q->next)) && (strcmp(q->ext,me->ext) >= 0)) {
        types[i]=me;
        me->next=q;
        return;
    }
    while(p) {
        if(strcmp(p->ext,me->ext) >= 0) break;
        q=p;
        p=p->next;
    }
    me->next=p;
    q->next=me;
}

void kill_mime() {
    register struct mime_ext *p,*q;
    register int x;

    for(x=0;x<27;x++) {
        p=types[x];
        while(p) {
            free(p->ext);
            free(p->ct);
            q=p;
            p=p->next;
            free(q);
        }
    }
    p=forced_types;
    while(p) {
        free(p->ext);
        free(p->ct);
        q=p;
        p=p->next;
        free(q);
    }
    p=encoding_types;
    while(p) {
        free(p->ext);
        free(p->ct);
        q=p;
        p=p->next;
        free(q);
    }
}

void init_mime() {
    char l[MAX_STRING_LEN],w[MAX_STRING_LEN],*ct;
    FILE *f;
    register struct mime_ext *me;
    register int x;

    if(!(f = fopen(types_confname,"r"))) {
        fprintf(stderr,"httpd: could not open mime types file %s\n",
                types_confname);
        perror("fopen");
        exit(1);
    }

    for(x=0;x<27;x++) 
        types[x] = NULL;
    forced_types = NULL;
    encoding_types = NULL;

    while(!(cfg_getline(l,MAX_STRING_LEN,f))) {
        if(l[0] == '#') continue;
        cfg_getword(w,l);
        if(!(ct = (char *)malloc(sizeof(char) * (strlen(w) + 1))))
            die(NO_MEMORY,"init_mime",stderr);
        strcpy(ct,w);

        while(l[0]) {
            cfg_getword(w,l);
            if(!(me = (struct mime_ext *)malloc(sizeof(struct mime_ext))))
                die(NO_MEMORY,"init_mime",stderr);
            if(!(me->ext = (char *)malloc(sizeof(char) * (strlen(w)+1))))
                die(NO_MEMORY,"init_mime",stderr);
            for(x=0;w[x];x++)
                me->ext[x] = (islower(w[x]) ? w[x] : tolower(w[x]));
            me->ext[x] = '\0';
            if(!(me->ct=strdup(ct)))
                die(NO_MEMORY,"init_mime",stderr);
            me->next=NULL;
            hash_insert(me);
        }
        free(ct);
    }
    fclose(f);
}

void dump_types() {
    struct mime_ext *p;
    register int x;

/*    for(x=0;x<27;x++) {
        p=types[x];
        while(p) {
            fprintf(stderr,"ext %s: %s\n",p->ext,p->ct);
            p=p->next;
        }
    } */
    p=forced_types;
    while(p) {
        fprintf(stderr,"file %s: %s\n",p->ext,p->ct);
        p=p->next;
    }
}

int is_content_type(char *type) {
    return(!strcmp(content_type,type));
}

void find_ct(char *file, int store_encoding) {
    int i,l,l2;
    struct mime_ext *p;
    char fn[MAX_STRING_LEN];

    lim_strcpy(fn,file, MAX_STRING_LEN);
/*    l = strlen(fn);
    if (fn[l-1] == '/') 
      fn[l-1] = '\0'; */
    if((i=rind(fn,'.')) >= 0) {
        ++i;
        l=strlen(fn);
        p = encoding_types;

        while(p) {
            if(!strcmp(p->ext,&fn[i])) {
                fn[i-1] = '\0';
                if(store_encoding) {
                    if(content_encoding[0])
                        sprintf(content_encoding,"%s, %s",content_encoding,
                                p->ct);
                    else
                        strcpy(content_encoding,p->ct);
                }
                if((i=rind(fn,'.')) < 0)
                    break;
                ++i;
                l=strlen(fn);
                p=encoding_types;
            }
            else
                p=p->next;
        }
    }
    p=forced_types;
    l=strlen(fn);

    while(p) {
        l2=l-strlen(p->ext);
        if((l2 >= 0) && (!strcasecmp(p->ext,&fn[l2]))) {
            strcpy(content_type,p->ct);
            return;
        }
        p=p->next;
    }

    if((i = rind(fn,'.')) < 0) {
	if (local_default_type[0] != '\0') 
	  strcpy(content_type,local_default_type);
         else strcpy(content_type,default_type);
        return;
    }
    ++i;
    p=types[hash(fn[i])];

    while(p) {
        if(!strcasecmp(p->ext,&fn[i])) {
            strcpy(content_type,p->ct);
            return;
        }
        p=p->next;
    }
    if (local_default_type[0] != '\0') 
      strcpy(content_type,local_default_type);
     else strcpy(content_type,default_type);
}



void probe_content_type(char *file) {
    find_ct(file,0);
}

void set_content_type(char *file) {
    find_ct(file,1);
}

int scan_script_header(FILE *f, FILE *fd) {
    char w[MAX_STRING_LEN];
    char *l;
    int p;

    while(1) {
        if(getline(w,MAX_STRING_LEN-1,fileno(f),timeout))
            die(SERVER_ERROR,"httpd: malformed header from script",fd);

/* Always return zero, so as not to cause redirect+sleep3+kill */
        if(w[0] == '\0') {
	    if (content_type[0] == '\0') {
	       if (location[0] != '\0') {
		 strcpy(content_type,"text/html");
	       } else {
	         if (local_default_type[0] != '\0')
		   strcpy(content_type,local_default_type);
	          else strcpy(content_type,default_type);
	       }
            }
	    return 0;
        }                            
        if(!(l = strchr(w,':')))
            l = w;
        *l++ = '\0';
        if(!strcasecmp(w,"Content-type")) {
	  /* Thanks Netscape for showing this bug to everyone */
	  /* delete trailing whitespace, esp. for "server push" */
	  char *endp = l + strlen(l) - 1;
	  while ((endp > l) && isspace(*endp)) *endp-- = '\0';
            sscanf(l,"%s",content_type);
        } 
        else if(!strcasecmp(w,"Location")) {
	/* If we don't already have a status line, make one */
	    if (!&status_line[0]) {
      	      status = 302;
      	      set_stat_line();
	    }
      	    sscanf(l,"%s",location);
	} 
        else if(!strcasecmp(w,"Status")) {
            for(p=0;isspace(l[p]);p++);
            sscanf(&l[p],"%d",&status);
            if(!(status_line = strdup(&l[p])))
                die(NO_MEMORY,"scan_script_header",fd);
        }
        else {
            *(--l) = ':';
            for(p=0;w[p];p++);
            w[p] = LF;
            w[++p] = '\0';
            if(!out_headers) {
                if(!(out_headers = strdup(w)))
                    die(NO_MEMORY,"scan_script_header",fd);
            }
            else {
                int loh = strlen(out_headers);
                out_headers = (char *) realloc(out_headers,
                                               (loh+strlen(w)+1)*sizeof(char));
                if(!out_headers)
                    die(NO_MEMORY,"scan_script_header",fd);
                strcpy(&out_headers[loh],w);
            }
        }
    }
}



/* Should remove all the added types from .htaccess files when the 
   child sticks around */

void reset_mime_vars() {
  struct mime_ext *mimes,*tmp;

  mimes = forced_types;
  tmp = mimes;
  while (mimes != Saved_Forced) {
    mimes = mimes->next;
    free(tmp->ext);
    free(tmp->ct);
    free(tmp);
    tmp = mimes;
  }

  forced_types = Saved_Forced;

  mimes = encoding_types;
  tmp = mimes;

  while (mimes != Saved_Encoding) {
    mimes = mimes->next;
    free(tmp->ext);
    free(tmp->ct);
    free(tmp);
    tmp = mimes;
  }

  encoding_types = Saved_Encoding;
}

void add_type(char *fn, char *t, FILE *out) {
    struct mime_ext *n;

    if(!(n=(struct mime_ext *)malloc(sizeof(struct mime_ext))))
        die(NO_MEMORY,"add_type",out);

    if(!(n->ext = strdup(fn)))
        die(NO_MEMORY,"add_type",out);
    if(!(n->ct = strdup(t)))
        die(NO_MEMORY,"add_type",out);
    n->next = forced_types;
    forced_types = n;
}

void add_encoding(char *fn, char *t,FILE *out) {
    struct mime_ext *n;

    if(!(n=(struct mime_ext *)malloc(sizeof(struct mime_ext))))
        die(NO_MEMORY,"add_encoding",out);

    if(!(n->ext = strdup(fn)))
        die(NO_MEMORY,"add_encoding",out);
    if(!(n->ct = strdup(t)))
        die(NO_MEMORY,"add_encoding",out);
    n->next = encoding_types;
    encoding_types = n;
}

void set_content_length(int l) {
    content_length = l;
}

int set_last_modified(time_t t, FILE *out) {
    struct tm *tms;
    char ts[MAX_STRING_LEN];

    tms = gmtime(&t);
    strftime(ts,MAX_STRING_LEN,HTTP_TIME_FORMAT,tms);
    strcpy(last_modified,ts);

    if(!ims[0])
        return 0;

    if(later_than(tms, ims))
        return die(USE_LOCAL_COPY,NULL,out);

    return 0;
}

void init_header_vars() 
{
    referer[0] = '\0';
    content_type[0] = '\0';
    last_modified[0] = '\0';
    content_length = -1;
    auth_line[0] = '\0';
    content_encoding[0] = '\0';
    location[0] = '\0';
    ims[0] = '\0';
    if (status_line != NULL) free(status_line);
    status_line = NULL;
    if (out_headers != NULL) free(out_headers);
    out_headers = NULL;

    if (in_headers_env != NULL) {
       free_env(in_headers_env);
       in_headers_env = NULL;
    } 
    
}

int merge_header(char *h, char *v, FILE *out) {
    register int l,lt;
    char **t;

    for(l=0;h[l];++l);
    h[l] = '=';
    h[++l] = '\0';

    for(t=in_headers_env;*t;++t) {
        if(!strncmp(*t,h,l)) {
            lt = strlen(*t);
            if(!(*t = (char *) realloc(*t,(lt+strlen(v)+3)*sizeof(char))))
                die(NO_MEMORY,"merge_header",out);
            (*t)[lt++] = ',';
            (*t)[lt++] = ' ';
            strcpy(&((*t)[lt]),v);
            return 1;
        }
    }
    h[l-1] = '\0';
    return 0;
}

void get_mime_headers(int fd, FILE *out, char* url) {
    char w[MAX_STRING_LEN];
    char l[MAX_STRING_LEN];
    int num_inh, num_processed;
    char *t;

    num_inh = 0;
    num_processed = 0;

    while(!(getline(w,MAX_STRING_LEN-1,fd,timeout))) {
        if(!w[0]) 
            return;
        if((++num_processed) > MAX_HEADERS)
            die(BAD_REQUEST,"too many header lines",out);
        if(!(t = strchr(w,':')))
            continue;
        *t++ = '\0';
        while(isspace(*t)) ++t;
        strcpy(l,t);

        if(!strcasecmp(w,"Content-type")) {
            strcpy(content_type,l);
            continue;
        }
        if(!strcasecmp(w,"Authorization")) {
            strcpy(auth_line,l);
            continue;
        }
        if(!strcasecmp(w,"Content-length")) {
            sscanf(l,"%d",&content_length);
            continue;
        }
        if(!strcasecmp(w,"User-agent")) {
            fprintf(agent_log, "%s\n", l);
	    fflush(agent_log);
        }
        if(!strcasecmp(w,"Referer")) {
	    strcpy(referer,l);
	    if ((!strlen(referer_ignore)) || (!strstr(l,referer_ignore))){
		fprintf(referer_log, "%s -> %s\n", l, url);
		fflush(referer_log);
	    }
        }
        if(!strcasecmp(w,"If-modified-since"))
            strcpy(ims,l);

        http2cgi(w);
        if(in_headers_env) {
            if(!merge_header(w,l,out)) {
                in_headers_env = 
                    (char **) realloc(in_headers_env,
                                      (num_inh+2)*sizeof(char *));
                if(!in_headers_env)
                    die(NO_MEMORY,"get_mime_headers",out);
                in_headers_env[num_inh++] = make_env_str(w,l,out);
                in_headers_env[num_inh] = NULL;
            }
        }
        else {
            if(!(in_headers_env = (char **) malloc(2*sizeof(char *))))
                die(NO_MEMORY,"get_mime_headers",out);
            in_headers_env[num_inh++] = make_env_str(w,l,out);
            in_headers_env[num_inh] = NULL;
        }
    }
}


void dump_default_header(FILE *fd) {
    fprintf(fd,"Date: %s%c",gm_timestr_822(time(NULL)),LF);
    fprintf(fd,"Server: %s%c",SERVER_VERSION,LF);
   
    if (annotation_server[0])
	fprintf(fd,"Annotations-cgi: %s%c",annotation_server,LF);

/* Not part of HTTP spec, removed. */
/*    fprintf(fd,"MIME-version: 1.0%c",LF); */
}

char* set_stat_line() {
    if (status_line) free(status_line);
    switch (status) {
    case 302:
	status_line = strdup((char *)StatLine302);
	break;
    case 304:
	status_line = strdup((char *)StatLine304);
	break;
    case 400:
	status_line = strdup((char *)StatLine400);
	break;
    case 401:
	status_line = strdup((char *)StatLine401);
	break;
    case 403:
	status_line = strdup((char *)StatLine403);
	break;
    case 404:
	status_line = strdup((char *)StatLine404);
	break;
    case 500:
	status_line = strdup((char *)StatLine500);
	break;
    case 501:
	status_line = strdup((char *)StatLine501);
	break;
    default:
	status_line = strdup((char *)StatLine200);
	break;
    }
    return status_line;
}
	
void send_http_header(FILE *fd) {
    if(!status_line) {
        if(location[0]) {
            status = 302;
	    status_line = strdup((char *)StatLine302);
        }
        else {
	    set_stat_line();
        }
    }            
    begin_http_header(fd,status_line);
    if(content_type[0])
        fprintf(fd,"Content-type: %s%c",content_type,LF);
    if(last_modified[0])
        fprintf(fd,"Last-modified: %s%c",last_modified,LF);
    if(content_length >= 0) 
        fprintf(fd,"Content-length: %d%c",content_length,LF);
    if(location[0])
        fprintf(fd,"Location: %s%c",location,LF);
    if(content_encoding[0])
        fprintf(fd,"Content-encoding: %s%c",content_encoding,LF);
    if(out_headers)
        fprintf(fd,"%s",out_headers);
    fprintf(fd,"%c",LF);
    fflush(fd);
}
