/* ====================================================================
 * Copyright (c) 1995-1998 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

#include "mod_perl.h"

static const char c2x_table[] = "0123456789abcdef";

static unsigned char *c2x(unsigned what, unsigned char *where)
{
    *where++ = '_';
    *where++ = c2x_table[what >> 4];
    *where++ = c2x_table[what & 0xf];
    return where;
}

/*
 * s/([^A-Za-z0-9\/])/sprintf("_%2x",unpack("C",$1))/eg;
 */
static char *uri2perlish(char *segment, int slen) {
    register int x,y;
    char *copy = (char *)safemalloc(3 * slen + 1);

    for(x=0,y=0; segment[x]; x++,y++) {
	char c = segment[x];
	if((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c >'9')
	   && c != '/')
        {
	    c2x(c, &copy[y]);
	    y += 2;
        }
	else
	    copy[y] = c;
    }
    copy[y] = '\0';
    return copy;
}

/*
 * s{
 *   (/+)       # directory
 *   (\d?)      # package's first character
 *  }[
 *   "::" . ($2 ? sprintf("_%2x",unpack("C",$2)) : "")
 *   ]egx;
 */
static SV *slash2stash(const char *segment) {
    register int x,y;
    SV *sv = newSV(3 * strlen(segment));

    for(x=0,y=0; segment[x]; x++,y++) {
	char c=segment[x];
	if(c == '/') {
	    SvPVX(sv)[y] = ':';
	    SvPVX(sv)[++y] = ':';
	    if(isDIGIT(segment[x+1])) {
		char d = segment[++x];
		c2x(d, &SvPVX(sv)[++y]);
		y += 2;
	    }
        }
	else
	    SvPVX(sv)[y] = c;
    }
    SvPVX(sv)[y] = '\0';
    SvCUR_set(sv, y);
    SvPOK_on(sv);
    return sv;
}

#define ApachePerlRun_import_exit() \
    "use Apache 'exit';\n"

#define ApachePerlRun_chdir_scwd() \
    chdir(SvPV(perl_get_sv("Apache::Server::CWD", TRUE),na))

#ifndef ApachePerlRun_name_with_virtualhost
#define ApachePerlRun_name_with_virtualhost() \
    perl_get_sv("Apache::Registry::NameWithVirtualHost", FALSE)
#endif

SV *ApachePerlRun_namespace(request_rec *r, char *root)
{
    char *copy, *uri;
    int uri_len;
    SV *esc, *RETVAL;

    uri = (char *)pstrdup(r->pool, r->uri);
    uri_len = strlen(uri);  
    if(r->path_info) {
	int n = strlen(r->path_info);
	int chop = (uri_len - n);
	uri[chop] = '\0';
    }
    if(r->server->is_virtual && ApachePerlRun_name_with_virtualhost()) {
	uri = pstrcat(r->pool, r->server->server_hostname, uri, NULL);
	uri_len += strlen(r->server->server_hostname);
    }
    copy = uri2perlish(uri, uri_len);
    RETVAL = newSVpv(root ? root : "Apache::ROOT",0);
    esc = slash2stash(copy);	
    sv_setsv(perl_get_sv("Apache::Registry::curstash", TRUE), esc);
    sv_catsv(RETVAL, esc);
    safefree(copy);
    SvREFCNT_dec(esc);
    return RETVAL;
}

#define log_scripterror(r, rc, msg) \
    aplog_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server, \
		"%s: %s", msg, r->filename); \
    return rc

int ApachePerlRun_can_compile(request_rec *r)
{
    if (!(allow_options(r) & OPT_EXECCGI)) {
	log_scripterror(r, FORBIDDEN, 
			"Options ExecCGI is off in this directory");
    }
    if (r->finfo.st_mode == 0) {
	log_scripterror(r, NOT_FOUND,
			"script not found or unable to stat");
    }
    if (S_ISDIR(r->finfo.st_mode)) {
	return DECLINED;
    }
    if (!can_exec(&r->finfo)) {
	log_scripterror(r, FORBIDDEN,
			"file permissions deny server execution");
    }
    return OK;
}

void ApachePerlRun_compile(request_rec *r, SV *code_ref)
{
     SV *code;

     if(SvROK(code_ref))
	code = (SV*)SvRV(code_ref);
     else
        code = code_ref;

     perl_eval_sv(code, G_DISCARD|G_KEEPERR);
}

/*
 * {
 *   local $/ = undef;
 *   my $fh = gensym;
 *   open $fh, $r->filename;
 *   my $code = <$fh>;
 *   close $fh;
 *   return \$code;
 * }
 */

#define ApachePerlRun_readscript mod_perl_slurp_filename

SV *ApachePerlRun_parse_cmdline(request_rec *r, SV *code)
{
    char *pos = (char *)strstr(SvPVX(code), "\n"), *shebang;
    int plen = pos - SvPVX(code);
    SV *sv;

    if(!pos) return Nullsv;
    sv = newSVpv("",0);
    shebang = (char*)safemalloc(sizeof(char)+plen);
    strncpy(shebang, SvPVX(code), plen);  
    
    if(*shebang == '#') {
	if(strstr(shebang, "-w")) {
	    sv_catpv(sv, "BEGIN {$^W = 1;}; $^W = 1;\n");
	}
    }

    safefree(shebang);
    return sv;
}

int ApachePerlRun_error_check(request_rec *r)
{
    dTHR;
    if((perl_eval_ok(r->server) != 0) && !strnEQ(SvPVX(ERRSV), " at ", 4)) {
	hv_store(ERRHV, r->uri, strlen(r->uri), ERRSV, FALSE);
	sv_setpv(ERRSV, "");
	return SERVER_ERROR;
    }
    else
	return OK;
}

void ApachePerlRun_set_scriptname(request_rec *r)
{
    SV *script_name = perl_get_sv("0", TRUE);
    /*save_item(script_name);*/
    sv_setpv(script_name, r->filename);
}

int handler(request_rec *r)
{
    dTHR;
    int rc = ApachePerlRun_can_compile(r);
    SV *package, *code, *eval, *cmdline;
    if(rc != OK)
	return rc;

    ENTER;
    package = ApachePerlRun_namespace(r, NULL);
    SAVEFREESV(package);
    code = ApachePerlRun_readscript(r);
    SAVEFREESV(code);
    eval = newSV(0);
    SAVEFREESV(eval);
    if((cmdline = ApachePerlRun_parse_cmdline(r, (SV*)SvRV(code)))) {
	sv_catsv(eval, cmdline);
	SvREFCNT_dec(cmdline);
    }
    ApachePerlRun_set_scriptname(r);
    chdir_file(r->filename);

    SAVEI32(hints);
    hints = 0; 

    sv_setpvf(eval, "package %_;\n", package);
    sv_catpv(eval, ApachePerlRun_import_exit());
    sv_catpvf(eval, "#line 1 %s\n", r->filename);
    sv_catsv(eval, (SV*)SvRV(code));
    sv_catpvn(eval, "\n", 1);
    ApachePerlRun_compile(r, eval);

    /*flush the namespace*/
    hv_clear(gv_stashpv(SvPVX(package), TRUE));

    ApachePerlRun_chdir_scwd();
    LEAVE;
    return ApachePerlRun_error_check(r);
}

static int registry_handler(request_rec *r)
{
    dTHR;
    int rc = ApachePerlRun_can_compile(r);
    SV *code, *package;
    SV *rgy_cache_rv = perl_get_sv("Apache::Registry", TRUE);
    HV *rgy_cache, *pkg_ent = Nullhv;
    bool do_compile = FALSE;
    if(rc != OK)
	return rc;

    if(!SvTRUE(rgy_cache_rv))
	sv_setsv(rgy_cache_rv, newRV((SV*)newHV()));

    rgy_cache = (HV*)SvRV(rgy_cache_rv);

    ENTER;
    package = ApachePerlRun_namespace(r, NULL);
    SAVEFREESV(package);

    ApachePerlRun_set_scriptname(r);
    chdir_file(r->filename);
    
    SAVEI32(hints);
    hints = FALSE;
    SAVEI32(dowarn);
    dowarn = FALSE;

    chdir(SvPV(perl_get_sv("Apache::Server::CWD", TRUE),na));
    if(hv_exists(rgy_cache, SvPVX(package), SvCUR(package))) {
	SV **rv = hv_fetch(rgy_cache, SvPVX(package), SvCUR(package), FALSE);
	SV *mtime;
	pkg_ent = (HV*)SvRV(*rv);
	mtime = *hv_fetch(pkg_ent, "mtime", 5, FALSE);
	if(SvTRUE(mtime) && ((int)SvIV(mtime) <= r->finfo.st_mtime)) {
	    /*we have compiled this subroutine already, nothing left to do*/
	}
	else 
	    do_compile = TRUE;
    }
    else
	do_compile = TRUE;

    if(do_compile) {
	int i = 0;
	SV *eval = newSVpv("",0), *cmdline;
	code = ApachePerlRun_readscript(r);
	SAVEFREESV(code);

	if((cmdline = ApachePerlRun_parse_cmdline(r, (SV*)SvRV(code)))) {
	    sv_catsv(eval, cmdline);
	    SvREFCNT_dec(cmdline);
	}

	sv_catpvf(eval, "package %_;\n", package);
	sv_catpv(eval, ApachePerlRun_import_exit());
	sv_catpv(eval, "sub handler {\n");
	sv_catpvf(eval, "#line 1 %s\n", r->filename);
	sv_catsv(eval, (SV*)SvRV(code));
	sv_catpvn(eval, "\n}", 2);
	ApachePerlRun_compile(r, eval);
	perl_stash_rgy_endav(r->uri, 
			     perl_get_sv("Apache::Registry::curstash", TRUE));
	SvREFCNT_dec(eval);
	rc = ApachePerlRun_error_check(r); 
	if(rc != OK) {
	    LEAVE;
	    return rc;
	}
	mod_perl_clear_rgy_endav(r, package);
	while (!pkg_ent) {
	    SV **svp = hv_fetch(rgy_cache, 
				SvPVX(package), SvCUR(package), FALSE);
	    if(svp) {
		pkg_ent = (HV*)SvRV(*svp);
		break;
	    }
	    hv_store(rgy_cache, SvPVX(package), SvCUR(package), 
		     newRV((SV*)newHV()), FALSE);
	    if(++i > 10) {
		fprintf(stderr, "STUCK\n");
		break;
	    }
	}

	hv_store(pkg_ent, "mtime", 5, newSViv(r->finfo.st_mtime), FALSE);
    }

    {
	dSP;
	int count;
	SV *sub = newSVsv(package);
	sv_catpvn(sub, "::handler", 9);
	ENTER;SAVETMPS;PUSHMARK(sp);
	XPUSHs((SV*)perl_bless_request_rec(r)); 
	PUTBACK;
	count = perl_call_sv(sub, G_EVAL | G_SCALAR);
	SvREFCNT_dec(sub);
	FREETMPS;LEAVE;
    }

    ApachePerlRun_chdir_scwd();
    LEAVE;
    if((rc = ApachePerlRun_error_check(r)) != OK)
	return rc;

    return r->status;
}

MODULE = Apache::PerlRunXS PACKAGE = Apache::RegistryXS PREFIX = registry_

int
registry_handler(r)
    Apache r

MODULE = Apache::PerlRunXS PACKAGE = Apache::PerlRunXS PREFIX = ApachePerlRun_

PROTOTYPES: DISABLE

BOOT:
    items = items; /*avoid warning*/ 

int
handler(r)
    Apache r

SV *
ApachePerlRun_namespace(r, root="Apache::ROOT")
    Apache r
    char *root

void
ApachePerlRun_can_compile(r)
    Apache r

    PREINIT:
    int retval = OK;

    PPCODE:
    retval = ApachePerlRun_can_compile(r);
    XPUSHs(sv_2mortal(newSViv(retval)));
    if(GIMME == G_ARRAY) {
	XPUSHs(sv_2mortal(newSViv(r->finfo.st_mtime)));
    }

void
ApachePerlRun_compile(r, code_ref)
     Apache r
     SV *code_ref

SV *
ApachePerlRun_readscript(r)
    Apache r

int
ApachePerlRun_error_check(r)
    Apache r