/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


/* 
 * time.c --
 *
 *	Manipulate times and dates; this is strongly influenced
 *	by HTSUtils.c from CERN. See also RFC 1123.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/httptime.c,v 1.9 2003/01/18 19:24:20 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

/*
 * Local functions defined in this file
 */

static int MakeNum(char *s);
static int MakeMonth(char *s);

/*
 * Static variables defined in this file
 */

static char *month_names[12] =
{
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};


/*
 *----------------------------------------------------------------------
 *
 * Ns_Httptime --
 *
 *	Convert a time_t into a time/date format used in HTTP
 *	(see RFC 1123). If passed-in time is null, then the
 *	current time will be used.
 *
 * Results:
 *	The string time, or NULL if error. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

char *
Ns_HttpTime(Ns_DString *pds, time_t *when)
{
    time_t     now;
    char       buf[40];
    struct tm *tmPtr;

    if (when == NULL) {
        now = time(0);
        when = &now;
    }
    tmPtr = ns_gmtime(when);
    if (tmPtr == NULL) {
        return NULL;
    }

    /*
     * This will most likely break if the locale is not an english one.
     * The format is RFC 1123: "Sun, 06 Nov 1997 09:12:45 GMT"
     */
    
    strftime(buf, 40, "%a, %d %b %Y %H:%M:%S GMT", tmPtr);

    Ns_DStringAppend(pds, buf);
    return pds->string;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ParseHttpTime --
 *
 *	Take a time in one of three formats and convert it to a time_t. 
 *	Formats are: "Thursday, 10-Jun-93 01:29:59 GMT", "Thu, 10 
 *	Jan 1993 01:29:59 GMT", or "Wed Jun  9 01:29:59 1993 GMT"
 *
 * Results:
 *	0 if error, or standard time_t.
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

time_t
Ns_ParseHttpTime(char *str)
{
    char      *s;
    struct tm  tm;
    time_t     t;

    if (str == NULL) {
        return 0;
    }

    /*
     * Find the comma after day-of-week
     *
     * Thursday, 10-Jun-93 01:29:59 GMT
     *         ^
     *         +-- s
     *
     * Thu, 10 Jan 1993 01:29:59 GMT
     *    ^
     *    +-- s
     */

    s = strchr(str, ',');
    if (s != NULL) {

	/*
	 * Advance S to the first non-space after the comma
	 * which should be the first digit of the day.
	 */
	
        s++;
        while (*s && *s == ' ') {
            s++;
	}

	/*
	 * Figure out which format it is in. If there is a hyphen, then
	 * it must be the first format.
	 */
	
        if (strchr(s, '-') != NULL) {
            if (strlen(s) < 18) {
                return 0;
            }

	    /*
	     * The format is:
	     *
	     * Thursday, 10-Jun-93 01:29:59 GMT
	     *           ^
	     *           +--s
	     */
	    
            tm.tm_mday = MakeNum(s);
            tm.tm_mon = MakeMonth(s + 3);
            tm.tm_year = MakeNum(s + 7);
            tm.tm_hour = MakeNum(s + 10);
            tm.tm_min = MakeNum(s + 13);
            tm.tm_sec = MakeNum(s + 16);
        } else {
            if ((int) strlen(s) < 20) {
                return 0;
            }

	    /*
	     * The format is:
	     *
	     * Thu, 10 Jan 1993 01:29:59 GMT
	     *      ^
	     *      +--s
	     */
	    
            tm.tm_mday = MakeNum(s);
            tm.tm_mon = MakeMonth(s + 3);
            tm.tm_year = (100 * MakeNum(s + 7) - 1900) + MakeNum(s + 9);
            tm.tm_hour = MakeNum(s + 12);
            tm.tm_min = MakeNum(s + 15);
            tm.tm_sec = MakeNum(s + 18);
        }
    } else {

	/*
	 * No commas, so it must be the third, fixed field, format:
	 *
	 * Wed Jun  9 01:29:59 1993 GMT
	 *
	 * Advance s to the first letter of the month.
	 */
	 
        s = str;
        while (*s && *s == ' ') {
            s++;
	}
        if ((int) strlen(s) < 24) {
            return 0;
        }
        tm.tm_mday = MakeNum(s + 8);
        tm.tm_mon = MakeMonth(s + 4);
        tm.tm_year = MakeNum(s + 22);
        tm.tm_hour = MakeNum(s + 11);
        tm.tm_min = MakeNum(s + 14);
        tm.tm_sec = MakeNum(s + 17);
    }

    /*
     * If there are any impossible values, then return an error.
     */
    
    if (tm.tm_sec < 0 || tm.tm_sec > 59 ||
        tm.tm_min < 0 || tm.tm_min > 59 ||
        tm.tm_hour < 0 || tm.tm_hour > 23 ||
        tm.tm_mday < 1 || tm.tm_mday > 31 ||
        tm.tm_mon < 0 || tm.tm_mon > 11 ||
        tm.tm_year < 70 || tm.tm_year > 120) {
        return 0;
    }
    tm.tm_isdst = 0;
#ifdef HAVE_TIMEGM
    t = timegm(&tm);
#else
    t = mktime(&tm) - timezone;
#endif
    return t;
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclParseHttpTimeObjCmd --
 *
 *	Implements ns_parsehttptime as obj command. 
 *
 * Results:
 *	Tcl result. 
 *
 * Side effects:
 *	See docs. 
 *
 *----------------------------------------------------------------------
 */

int
NsTclParseHttpTimeObjCmd(ClientData arg, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
    time_t time;

    if (objc != 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "httptime");
        return TCL_ERROR;
    }
    time = Ns_ParseHttpTime(Tcl_GetString(objv[1]));
    if (time == 0) {
	Tcl_AppendResult(interp, "invalid time: ",
		Tcl_GetString(objv[1]), NULL);
	return TCL_ERROR;
    }
    Tcl_SetLongObj(Tcl_GetObjResult(interp), time);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclHttpTimeObjCmd --
 *
 *	Implements ns_httptime as obj command. 
 *
 * Results:
 *	Tcl result. 
 *
 * Side effects:
 *	See docs. 
 *
 *----------------------------------------------------------------------
 */

int
NsTclHttpTimeObjCmd(ClientData arg, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
    Ns_DString ds;
    int        itime;
    time_t     time;

    if (objc != 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "time");
        return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[1], &itime) != TCL_OK) {
        return TCL_ERROR;
    }
    time = (time_t) itime;
    Ns_DStringInit(&ds);
    Ns_HttpTime(&ds, &time);
    Tcl_SetResult(interp, Ns_DStringExport(&ds), (Tcl_FreeProc *) ns_free);
    Ns_DStringFree(&ds);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * MakeNum --
 *
 *	Convert a one or two-digit day into an integer, allowing a 
 *	space in the first position. 
 *
 * Results:
 *	An integer.
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

static int
MakeNum(char *s)
{
    if (*s >= '0' && *s <= '9') {
        return (10 * (*s - '0')) + (*(s + 1) - '0');
    } else {
        return *(s + 1) - '0';
    }
}


/*
 *----------------------------------------------------------------------
 *
 * MakeMonth --
 *
 *	Convert a three-digit abbreviated month name into a number; 
 *	e.g., Jan=0, Feb=1, etc. 
 *
 * Results:
 *	An integral month number. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

static int
MakeMonth(char *s)
{
    int i;

    /*
     * Make sure it's capitalized like this:
     * "Jan"
     */
     
    *s = toupper(*s);
    *(s + 1) = tolower(*(s + 1));
    *(s + 2) = tolower(*(s + 2));

    for (i = 0; i < 12; i++) {
        if (!strncmp(month_names[i], s, 3)) {
            return i;
	}
    }
    return 0;
}

