/*
** GHUtilities.c - Useful stuff & stuff that doesn't fit anywhere else
** Copyright (C) 1996-97 Serge Emond
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version 2
** of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

// Verify errors returned by searching "BUZ"

#include "GrabHTTP.h"
#include <proto/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

/// Undefines
// We want NORMAL timevals, not shitty defines
#undef timeval
#undef tv_sec
#undef tv_usec
#undef tv_secs
#undef tv_micro
#undef timezone
///

/// Prototypes
#include "GrabHTTP_protos.h"
#include "GHRexxFcts_protos.h"
#include "GHUtilities_protos.h"
#include "GHWindows_protos.h"
///

/// Externs

extern struct Common c;
extern struct Specific s;

///

/// Constants

ULONG Days_of_Months[] = {  31, 28, 31, 30, 31, 30,
                            31, 31, 30, 31, 30, 31};
const TEXT *MonthsNames[]={ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
const TEXT *DaysNames[]={ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

struct Table DateTypes[3] = {
    //  Day Mon Y   H   Min Sec
        8,  4,  22, 11, 14, 17,     // type 0
        5,  8,  14, 17, 20, 23,     // type 1
        8,  11, 15, 18, 21, 24};    // type 2

const LONG UN=1;
///

/// BOOL Exists(file)

// Returns TRUE if file exists

BOOL Exists(TEXT *file)
{
    BPTR    l;
    
    l = Lock(file, ACCESS_READ);
    if (l)
    {
        UnLock(l);
        return(TRUE);
    }
    return(FALSE);
}
///

/// SpaceLeft (fdir)

// Returns the space left of the device containing the dir/dev fdir

ULONG SpaceLeft(TEXT *fdir)
{
    BPTR lock;
    struct InfoData infodata;
    ULONG space;
    
    space = 0;
    
    if (lock = Lock(fdir, ACCESS_READ))
    {
        if (Info(lock, &infodata))
        {
            space = (infodata.id_NumBlocks - infodata.id_NumBlocksUsed)
                    * infodata.id_BytesPerBlock;
        }
        UnLock(lock);
    }
    return(space);
}
///

/// ReadAVar (template, opts_array, rdargs, stuff)

// Initialises the rdargs struct and passes the string "stuff" to ReadArgs().

/*
** TEXT *template
**       ReadArgs template to use
** LONG *array
**       Array to put options to
** struct RDArgs *rdargs
** TEXT *p
**       Pointer to a string containing the arguments to parse
**
** Returns struct RDArgs * passed or NULL
*/

struct RDArgs *ReadAVar(STRPTR template, LONG *array, struct RDArgs *rdargs, STRPTR p)
{
    TEXT *tmp;
    ULONG len;
    struct RDArgs *rda;
    
    len = strlen(p);
    if (p[len-1] == 10) tmp = p;
    else
    {
        len++;
        tmp = (TEXT *)AllocMem(len+1, NULL);
        if (!tmp) return(NULL);
        strncpy(tmp, p, len-1);
        tmp[len-1] = 10;
        tmp[len] = 0;
    }
    
    rdargs->RDA_Source.CS_Buffer = tmp;
    rdargs->RDA_Source.CS_Length = len;
    rdargs->RDA_Source.CS_CurChr = NULL;
    rdargs->RDA_DAList = NULL;
    rdargs->RDA_Buffer = NULL;
    rdargs->RDA_BufSiz = NULL;
    rdargs->RDA_ExtHelp = NULL;
    rdargs->RDA_Flags = RDAF_NOPROMPT;
    
    rda = ReadArgs(template, array, rdargs);
    if (p == tmp) FreeMem(tmp, len+1);
    if (rda) return(rdargs);
    return(NULL);
}
///

/// Reply (RC, GHRES)

// Replies to an ARexx message with RC and GHRES set

void Reply(ULONG res, STRPTR ghres)
{
    ((struct RexxMsg *)c.msg)->rm_Result1 = res;
    /* We set a result variable */
    if (SetRexxVar(c.msg, RES_VAR, (ghres?ghres:NULL),
                (ghres?strlen(ghres):0)))
        ((struct RexxMsg *)c.msg)->rm_Result1 = 15;
    ReplyMsg(c.msg);
}
///

/// MakeDirs (path, touch)

// Creates all missing directories of "path".
// path is a DIRECTORY - must not contain a filename!!!

// If error, returns FALSE

/*
** Devrais-je utiliser un Exists() avec ACCESS_WRITE pour usagers de MultiUser?
*/

BOOL MakeDirs(TEXT *path, BOOL touch)
{
    TEXT tok[128], *p, *q;
    BPTR lock;
    ULONG tlen, len;
    struct DateStamp cds;
    
    tlen = strlen(path);
    
    /* return immediately if device only */
    if (path[tlen-1] == ':') return(TRUE);
    
    p = path;
    while (1)
    {
        if (q = strchr(p, '/')) p = q;
        else p = (TEXT *)((ULONG)path + tlen);
        
        len = (ULONG)(p-path);
        strncpy(tok, path, len);
        tok[len]=NULL;
        
        if (!Exists(tok))
        {
            /* Doesn't exists or don't have access write */
            lock = CreateDir(tok);
            if (!lock) return(FALSE);
            UnLock(lock);
        }
        else if (touch) {
            DateStamp(&cds);
            SetFileDate(tok, &cds);
        }

        if ((*p == NULL) || (*(++p) == NULL)) break;
    }
    return(TRUE);
}
///

/// SearchMemListType (struct MemNode *, ULONG)

// Searches a MemList list for a specific entry type

// If the argument is a list header, the browsing will include all nodes.
// If it is a node, it will begin at the following node.

struct MemNode *SearchMemListType(struct MemNode *head, ULONG type)
{
    struct MemNode *p;

    if (head->n.mln_Succ == 0)
        return(NULL);           // Empty list | end-of-list

    for (p = (struct MemNode *)head->n.mln_Succ; p->n.mln_Succ;
        p = (struct MemNode *)p->n.mln_Succ)
            if (p->t == type) break;

    if (p->n.mln_Succ)
        return (p);             // Type found
    return(NULL);               // Not found
}
///

/// SearchMemListHeader (struct MemNode *, TEXT *, ULONG)

// Searches for a string in a memlist.

// If the argument is a list header, the browsing will include all nodes.
// If it is a node, it will begin at the following node.

// len      number of BYTE to compare.  If len=0, ret = NULL
// str      pointer to the string to find

struct MemNode *SearchMemListHeader(struct MemNode *head, TEXT *str, ULONG len)
{
    struct MemNode *p;

    if (head->n.mln_Succ == 0 || len==0)
        return(NULL);           // Empty list / end-of-list or len NULL

    for (p = (struct MemNode *)head->n.mln_Succ; p->n.mln_Succ;
        p = (struct MemNode *)p->n.mln_Succ) {
        if (p->t != MEMNT_HEADER)
            continue;                   // Not an header..
        if (p->s < len)
            continue;                   // Cannot be.. string shorter!
        if (!strnicmp((TEXT *)p->p, str, len))
            break;                      // Got it!
    }

    if (p->n.mln_Succ)
        return (p);             // Found
    return(NULL);               // Not found
}
///

/// InitSpecific(struct Specific *spec)

struct Specific *InitSpecific(struct Specific *spec)
{
    // Just make sure it's not initialized..
    FreeSpecific(spec);

    spec->totticks = 0;
    spec->cps = 0;
    spec->size = 0;
    spec->recd = 0;
    spec->BarValue = -1;

    // Create Memory Pool
    spec->memlst.pool = CreatePool(NULL, DEF_MLST_PUDDLE, DEF_MLST_THRESH);
    if (!spec->memlst.pool)
        return (NULL);          // Failed

    // Initialize the list
    spec->memlst.l.mlh_Head = (struct MinNode *)&spec->memlst.l.mlh_Tail;
    spec->memlst.l.mlh_Tail = 0;
    spec->memlst.l.mlh_TailPred = (struct MinNode *)&spec->memlst.l.mlh_Head;

    return(spec);
}
///

/// FreeSpecific (struct Specific *)

void FreeSpecific(struct Specific *spec)
{
    if (spec->Initialized) {
        DeletePool(spec->memlst.pool);
        spec->Initialized = 0;
    }
}
///

/// GetSockAddr

LONG GetSockAddr(const char *host, int port, struct sockaddr_in *addr)
{
    struct hostent *remote;

    if ((remote = gethostbyname(host)) != NULL)
        memcpy(&addr->sin_addr, remote->h_addr, sizeof(addr->sin_addr));
    else if ((addr->sin_addr.s_addr = inet_addr(host)) == (unsigned long)-1)
        return(FALSE);

    addr->sin_port = htons(port);
    addr->sin_family = AF_INET;

    return(TRUE);
}
///

/// SendHTTPRequest (path, ds, headonly)

// Sends the HTTP request

// TEXT *path            Pointer to the path of the file wanted
// struct DateStamp *ds  Pointer to a ds struct containing last mod info
//                       Add IfModified if non NULL
//                       This filestamp is LOCAL and WILL be modified to be
//                       GMT

// Return TRUE if sent.  FALSE if there was an error while writing

BOOL SendHTTPRequest(TEXT *host, TEXT *p, struct DateStamp *ds, BOOL headonly)
{
    struct TimeDate td;
    char op[256];

    if (headonly) {
        if (send(s.sok, "HEAD ", 5, 0) != 5)
            return(FALSE);
    }
    else {
        if (send(s.sok, "GET ", 4, 0) != 4)
            return(FALSE);
    }


    if (send(s.sok, p, strlen(p), 0) != strlen(p))
        return(FALSE);

    if (send(s.sok, " HTTP/1.0\r\n", 11, 0) != 11)
        return(FALSE);

    // HTTP 1.1 stuff
    // Host:
    if (send(s.sok, "Host: ", 6, 0) != 6)
        return(FALSE);
    if (send(s.sok, host, strlen(host), 0) != strlen(host))
        return(FALSE);
    if (send(s.sok, "\r\n", 2, 0) != 2)
        return(FALSE);
    // Connection:
    if (send(s.sok, "Connection: close\r\n",19 , 0) != 19)
        return(FALSE);

    if (c.email.p) {
        if (send(s.sok, "From: ", 6, 0) != 6)
            return(FALSE);
        if (send(s.sok, c.email.p, strlen(c.email.p), 0) != strlen(c.email.p))
            return(FALSE);
        if (send(s.sok, "\r\n", 2, 0) != 2)
            return(FALSE);
    }
    
    if (c.auth.p) {
        if (send(s.sok, "Authorization: Basic ", 21, 0) != 21)
            return(FALSE);
        if (send(s.sok, c.auth.p, strlen(c.auth.p), 0) != strlen(c.auth.p))
            return(FALSE);
        if (send(s.sok, "\r\n", 2, 0) != 2)
            return(FALSE);
    }

    if (send(s.sok, "User-Agent: GrabHTTP/"SVER"\r\n",
                strlen("User-Agent: GrabHTTP/"SVER"\r\n"), 0)
                != strlen("User-Agent: GrabHTTP/"SVER"\r\n"))
        return(FALSE);
    
    if (send(s.sok, "Accept: ", 8, 0) != 8)
        return(FALSE);
    if (send(s.sok, (TEXT *)c.opts[OPT_ACCEPT],
            strlen((TEXT *)c.opts[OPT_ACCEPT]), 0)
            != strlen((TEXT *)c.opts[OPT_ACCEPT]))
        return(FALSE);
    if (send(s.sok, "\r\n", 2, 0) != 2)
        return(FALSE);

    if (ds)
    {
        DS_TD(ds, &td);
        
        sprintf(op, "If-Modified-Since: %s, %02lu %s %04lu"
                    " %02lu:%02lu:%02lu GMT\r\n",
                    DaysNames[td.Week_Day], td.Day,
                    MonthsNames[td.Month - 1], td.Year,
                    td.Hour, td.Mins, td.Secs);

        if (send(s.sok, op, strlen(op), 0) != strlen(op))
            return(FALSE);
    }
    if (send(s.sok, "\r\n", 2, 0) != 2)
        return(FALSE);

    return(TRUE);       // Request Sent :-)
}
///

/// ReadSocketLine
char *ReadSocketLine(LONG sok, char *buf, LONG msize)
{
    LONG i;

    i=0;
    while (recv(sok, &buf[i], 1, 0) == 1) {
        if (buf[i] == '\n') {
            ++i;
            break;
        }
        if (++i == msize)
            break;
    }
    buf[i] = 0;

    if (i == msize) return(0);
    return(buf);
}
///

/// GetHTTPHeader (fname, msize)

// Get HTTP info

// TEXT *fname   If non NULL, contains the name of the file where to put the
//               complete header
// ULONG msize   If > 0, contains the maximal size a file can have

// Return:
//  0       Ok
//  161     ReadError
//  162     Open/Write Error
//  163     Not HTTP/1.0
//  164     Can't alloc memory

// To Add:
//   - Enforce Content-Type
//   - Immediate 2 jump si URI (Hmmm..)
//   - Check if a tag is empty, if we go after the \0, etc..

LONG GetHTTPHeader(TEXT *fname, ULONG msize)
{
    TEXT    line[512], *tpos;
    struct MemNode *tnode;

    /* Check for HTTP/1.0 */
    if (!ReadSocketLine(s.sok, line, sizeof(line))) return(161);

    if (strncmp(line, "HTTP/1.", 7)) return(163);

    // Add this line to the list..
    tnode = (struct MemNode *)AllocPooled(s.memlst.pool,
                                          sizeof(struct MemNode));
    if (tnode) {
        tnode->s = strlen(line)+1;
        tnode->t = MEMNT_HEADER;
        tnode->p = AllocPooled(s.memlst.pool, tnode->s);
        if (tnode->p) {
            // Add to the list..
            AddTail((struct List *)&s.memlst, (struct Node *)tnode);
        }
        else
            return(164);
    }
    else
        return(164);

    strcpy((char *)tnode->p, line);

    if (fname)
    {
        /* Open Header File */
        s.ffh = Open(fname, MODE_NEWFILE);
        
        if (!s.ffh) return(162);    /* Can't open it */
        if (FPuts(s.ffh, line) == -1)
        {
            Close(s.ffh); s.ffh = NULL;
            return(162);
        }
    }

    if (tpos = strpbrk((char *)tnode->p, "\r\n"))
        ((char *)tnode->p)[(int)tpos - (int)(tnode->p)] = '\0';

    while (1)
    {
        if (!ReadSocketLine(s.sok, line, sizeof(line)))
        {
            if (fname) {Close(s.ffh); s.ffh = NULL;}
            return(161);
        }
        if (fname) if (FPuts(s.ffh, line) == -1)
        {
            Close(s.ffh); s.ffh = NULL;
            return(162);
        }
        
        if (tpos = strpbrk(line, "\r\n"))
            line[(int)tpos - (int)line] = '\0';

        if (!line[0])
            break;
        
        // Add this line to the list..
        tnode = (struct MemNode *)AllocPooled(s.memlst.pool,
                                              sizeof(struct MemNode));
        if (tnode) {
            tnode->s = strlen(line)+1;
            tnode->t = MEMNT_HEADER;
            tnode->p = AllocPooled(s.memlst.pool, tnode->s);
            if (tnode->p) {
                // Add to the list..
                AddTail((struct List *)&s.memlst, (struct Node *)tnode);
            }
            else
                return(164);
        }
        else
            return(164);
        strcpy((char *)tnode->p, line);

        if (!strnicmp(line, "content-length: ", 16))
        {
            // Got Size!  Yippy!
            s.size = atol(line+16);
        }
        
    }
    
    if (fname)
    {
        Close(s.ffh);
        s.ffh=NULL;
    }
    
    return(0);
}
///

/// GetHTTPBody (fname, doprog)

// Get the file itself

// TEXT * fname      FileName to put stuff to
// BOOL doprog       if present, display progress

// Return
//   150     Error Openning File
//   151     Didn't write all the bytes..
//   200     File incomplete.
//   201     Got Skip
//   202     Got Abort/Closewindow
//

#define iobufsize 4096

LONG GetHTTPBody(TEXT *fname, BOOL doprog)
{
    LONG got, ret;
    struct timeval tmpt, tini;
    struct cheaptime {  ULONG h, m, s; } done, rem;
    ULONG remsize, remsecs;
    char iobuf[iobufsize];

    s.bfh = BOpen(fname, MODE_NEWFILE, c.wbufsize);
    
    if (!s.bfh)
        return(150);                // Can't open it

    GetSysTime(&tini);

    ret = CheckWindow(&s, 0);
    while (!ret && (got = recv(s.sok, iobuf, iobufsize, 0)))
    {
        BWrite(s.bfh, iobuf, got);
        
        s.recd += got;
        
        GetSysTime(&tmpt);
        SubTime(&tmpt, &tini);
        s.totticks = (50 * tmpt.tv_secs) + (tmpt.tv_micro / 20000);
        
        if (doprog)
        {
            s.cps = (s.totticks ? 50*s.recd/s.totticks : 0);

            done.h = s.totticks / 180000;
            done.m = s.totticks / 3000 - done.h * 60;
            done.s = s.totticks / 50 - done.m * 60 - done.h * 3600;
            if (s.size)
            {
                remsize = s.size - s.recd;
                remsecs = (s.cps ? remsize/s.cps : 0);
//                remsecs = (s.recd ? ((s.totticks+25)/50) * (remsize /
//                            s.recd) : 0);
                rem.h = remsecs / 3600;
                rem.m = remsecs / 60 - rem.h * 60;
                rem.s = remsecs - rem.m * 60 - rem.h * 3600;
            }
            
            SetProgressSize(&s, s.size, s.recd);
            SetProgressActual(&s, done.h, done.m, done.s);
            if (s.size)
                SetProgressRemaining(&s, rem.h, rem.m, rem.s);
            SetProgresscps(&s, s.cps);
            if (s.size)
                SetProgressBar(&s, 100*s.recd/s.size);

        }
        ret = CheckWindow(&s, 0);
    }
    BClose(s.bfh); s.bfh=NULL;
    ret = CheckWindow(&s, ret);

    if (s.size && s.size>s.recd && !ret)
        ret = 200;
    else if (doprog)
        SetProgressBar(&s, 100);

    return (ret);
}
///

/// GetHTTPFile (url, fname, msize, headonly, ifmod, doprog, shead)

// Get an http file..

// TEXT *url         Url to get
// TEXT *fname       Filename to save to.  Build the filename if NULL
// ULONG msize       Maximal size of file if non-NULL
// BOOL  headonly    Don't get the file - only the header
// BOOL  ifmod       Only grab if modified
// BOOL  doprog      Display Progress Info?
// BOOL  shead       Saveheader?

// Return
//    97    Failed to create socket
//    98    Failed to initialise specific
//    99    Not enough memory
//   100    Received Error: Remote didn't sent OK
//   101    Bad/Unsupported transfer method
//   102    Failed to open socket
//   103    Failed to send HTTP request
//   104    Host not found
//   105    Failed to create directories
//   106    File too big (msize)
//   107    Not enough space left on device
// * 108    Forced IfModified - their file is older
//   150    Cannot open output file
//   151    Wrote less BYTE than received
//   200    File incomplete
//   201    Got Skip
//   202    Got Closewindow/Abort
// + 999    Special case: To break after header if headonly

LONG GetHTTPFile(iurl, ifname, msize, headonly, ifmod, doprog, shead, touchdirs)
TEXT *iurl, *ifname;
ULONG msize;
BOOL headonly, ifmod, doprog, shead, touchdirs;
{
    TEXT hfname[128], *ppath, fname[128], *p, host[128], hosto[128], fdir[128], *sptr;
    LONG ret, mins, port;
    BOOL doifmod;
    struct DateStamp ds;
//    struct DateStamp dsb;
//    struct TimeDate td;
    struct MemNode *tnode, *urlnode, *headnode;
    struct sockaddr_in addr = {0};

    if (strnicmp(iurl, "HTTP://", 7))
        return(101);

    if (!InitSpecific(&s))
        return(98);

    if (doprog) {
        OpenProgressWindow(&s);
    }

    // Alloc mem for URL's name
    tnode = (struct MemNode *)AllocPooled(s.memlst.pool,
                                          sizeof(struct MemNode));
    if (tnode) {
        tnode->s = strlen(iurl)+1;
        tnode->t = MEMNT_URL;
        tnode->p = AllocPooled(s.memlst.pool, tnode->s);
        if (tnode->p) {
            // Add to the list..
            AddTail((struct List *)&s.memlst, (struct Node *)tnode);
        }
        else
            return(99);
    }
    else
        return(99);
    
    strcpy((char *)tnode->p, iurl);
    urlnode = tnode;
    
    ppath = strchr((char *)tnode->p+7, '/');
    if (ppath[0] == 0)
    {
        // No '/' after host & no path - lets add '/'!
        ppath = (TEXT *)((ULONG)tnode->p + strlen((char *)tnode->p));
        // ppath points to '\0'
        ppath[0] = '/';
        ppath[1] = 0;
    }
    
    // Extract "host:port"
    strncpy(host, (char *)tnode->p+7, (unsigned int)ppath-(unsigned int)tnode->p-7);
    host[(LONG)ppath-(LONG)tnode->p-7] = 0;

    // Extract port if present
    if (p = strchr(host, ':')) {
        port = atoi(p+1);
        strncpy(hosto, host, (LONG)p-(LONG)host);
        hosto[(LONG)p-(LONG)host] = 0;
    }
    else {
        port = 80;
        strcpy(hosto, host);
    }
    
    if (!ifname)
    {
        // No Filename specified, create one...
        
        fname[0]=0;
        
        if (ppath[strlen(ppath)-1] == '/')
        {
            // ie "http://host/path/"
            strcpy(fname, "index.html");
        }
        else
        {
            // URL has a filename
            strcpy(fname, FilePart(ppath+1));
            
            // Strip any "#" contained in filename
            if (sptr = strchr(fname, '#')) fname[fname-sptr] = 0;
        }
    }
    else
    {
        strcpy(fname, ifname);
        
        // Strip any "#" contained in filename
        if (sptr = strchr(fname, '#')) fname[fname-sptr] = 0;
        
        if ((fname[0] == '\0') || (fname[strlen(fname)-1] == '/') || (fname[strlen(fname)-1] == ':'))
        {
            // There is a path, but no filename.
            strcat(fname, "index.html");
        }
    }

    if (shead)
    {
        // Wants to save header to disk..
        strcpy(hfname, fname);
        strcat(hfname, ".HDR");
    }
    
    // Create Dirs
    strcpy(fdir, fname);
    p = PathPart(fdir);
    p[0]=0;
    
    if (!MakeDirs(fdir, touchdirs)) return(105);
    
    ret = 0;
    
    if (ifmod)
    {
        if (doifmod = GetFileStamp(fname, &ds))
        {
            // We translate the DateStamp to GMT
            
            mins = ds.ds_Minute + c.timezone;
            if (mins > 1339)
            {
                // One day contained in minutes..
                ds.ds_Minute = mins - 1440;
                ds.ds_Days++;
            }
            else if (mins < 0)
            {
                // Minutes negative!
                ds.ds_Minute = mins + 1440;
                ds.ds_Days--;
            }
            else ds.ds_Minute = mins;
        }
    }
    else doifmod = FALSE;
    
    if (doprog && s.w)
    {
        SetProgressBar(&s, -1);
        SetProgressSize(&s, 0, 0);
        SetProgressActual(&s, -1, -1, -1);
        SetProgressRemaining(&s, -1, -1, -1);
        SetProgresscps(&s, 0);
        SetProgressURL(&s, iurl);
        SetProgressFile(&s, fname);
        SetProgressStatus(&s, "Looking up...");
    }
    
    // TCP stuff begins here

    SetSocketSignals( (s.w ? 1L<<s.w->UserPort->mp_SigBit : NULL), NULL, NULL);

    if (!ret) if (!GetSockAddr(hosto, port, &addr))
        ret = 104;        // BUZ
    else
        SetProgressStatus(&s, "Connecting...");

    if (!ret) if ((s.sok = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        ret = 97;        // BUZ
    }
    else {
        setsockopt(s.sok, SOL_SOCKET, SO_KEEPALIVE, &UN, sizeof UN);
//        SetSocketSignals(/*SIGBREAKF_CTRL_C |*/ s.w->UserPort->mp_SigBit, NULL, NULL);
    }

    if (!ret) if (connect(s.sok, (struct sockaddr *)&addr, sizeof (struct sockaddr_in)) >= 0)
    {
        // Socket opened!
        
        if (doprog)
        {
            SetProgressStatus(&s, "Sending request...");
        }

        ret = CheckWindow(&s, 0);

        if (!ret) if (SendHTTPRequest(hosto, ppath, (doifmod ? &ds : NULL), headonly))
        {
            // Request Sent
            if (doprog)
            {
                SetProgressStatus(&s, "Receiving header...");
            }
            
            ret = GetHTTPHeader((shead?hfname:NULL), msize);
            headnode = SearchMemListHeader((struct MemNode *)&s.memlst, "HTTP/1.", 7);
            
            // Check if not "OK"
            if (!ret) {
                if (((char *)headnode->p)[9] != '2')
                    ret = 100;
            }

            
            // Check if file too big
            if (!ret) if (msize && (msize < s.size)) ret = 106;
            
            // Check if enough place on device
            if (!ret) if (c.minspace)
                if (SpaceLeft(fdir) < s.size + c.minspace) ret = 107;
            
            // Enforce if modified
//            if (!ret && doifmod)
//            {
//                if (tnode = SearchMemListHeader((struct MemNode *)&s.memlst,
//                    "Last-Modified: ", 15))
//                if (ScanDate((TEXT *)tnode->p + 15, &td))
//                {
//                    // We suppose td is valid & correct
//
//                    TD_DS(&dsb, &td);
//
//                    // Them = dsb,  Us = ds;
//                    // datecmp ret -1 if ds > dsb
//                    //              1 if ds < dsb
//                    //              0 if ds = dsb
//                    if (datecmp(&ds, &dsb) < 0) ret = 108;
//                }
//            }
            
            // Get Header Only?
            if (!ret && headonly)
            {
                if (doprog)
                    SetProgressStatus(&s, "Received header ok.");
                ret = 999;
            }
            
            if (!ret)
            {
                // Got header
                if (doprog && s.size)
                        SetProgressSize(&s, s.size, s.recd);

                SetProgressStatus(&s, "Receiving file...");
                ret = GetHTTPBody(fname, doprog);
                // If ret = 0 - got the file
            }
        }
        else ret = 103;
        
    }
    else ret = 102;
    
    if (ret == 999) ret = 0;

    if (s.sok >= 0) {
        CloseSocket(s.sok);
        s.sok = NULL;
    }
//    SetSocketSignals(SIGBREAKF_CTRL_C, NULL, NULL);

    ret = CheckWindow(&s, ret);

    if (doprog) {
        switch (ret) {

            case 0:
                SetProgressStatus(&s, "Received OK.");
                SetComment(fname, iurl);
                break;
            case 97:
                SetProgressStatus(&s, "Failed to create socket.");
                break;
            case 98:
            case 99:
            case 164:
                SetProgressStatus(&s, "Out of memory.");
                break;
            case 100:
                SetProgressStatus(&s, (char *)headnode->p + 9);
                break;
            case 101:
                SetProgressStatus(&s, "Url is not HTTP://");
                break;
            case 102:
                SetProgressStatus(&s, "Could not connect.");
                break;
            case 103:
                SetProgressStatus(&s, "Error sending request.");
                break;
            case 104:
                SetProgressStatus(&s, "Host not found.");
                break;
            case 105:
                SetProgressStatus(&s, "Error making directories.");
                break;
            case 106:
                SetProgressStatus(&s, "File too big.");
                break;
            case 107:
                SetProgressStatus(&s, "Disk full.");
                break;
//            case 108:
//                SetProgressStatus(&s, "Not modified (enforced).");
//                break;
            case 150:
                SetProgressStatus(&s, "Error creating file.");
                break;
            case 151:
                SetProgressStatus(&s, "Error writing to file.");
                break;
            case 161:
                SetProgressStatus(&s, "Error getting header.");
                break;
            case 162:
                SetProgressStatus(&s, "Error writing to hfile.");
                break;
            case 163:
                SetProgressStatus(&s, "Bad HTTP header.");
                break;
            case 200:
                SetProgressStatus(&s, "File incomplete.");
                break;
            case 201:
                SetProgressStatus(&s, "Skipping this file.");
                break;
            case 202:
                SetProgressStatus(&s, "Transfer aborted.");
                break;
//            default:
//                SetProgressStatus(&s, "Error...");
//                break;
        }

    }

    return(ret);
}
///

/// DS_TD (DateStamp *, TimeDate *)

// Transforms a DateStamp into a TimeDate

void DS_TD(struct DateStamp *ds, struct TimeDate *td)
{
    ULONG   all_Days;           // Nb. de jours depuis 1.1.1978
    ULONG   year_Days;          // Nb. de jours ds l'annee
    ULONG   leap_year, years;   // Variables de boucle
    ULONG   month;
    ULONG   SysTimeDays;
    
    SysTimeDays = ds->ds_Days;
    if (SysTimeDays > (2*365))
    {
        SysTimeDays -= 2*365;
        td->Year = 1980;
        all_Days = 2*365;
    }
    else
    {
        all_Days = 0;
        td->Year = 1978;
        if (SysTimeDays > 365)
        {
            SysTimeDays -= 365;
            all_Days += 365;
            td->Year ++;
        }
        goto Get_month;
    }
    for (leap_year = 0; leap_year < 34; leap_year++)
    {
        if (SysTimeDays >= 366)
        {
            SysTimeDays -= 366;
            td->Year++;
            all_Days += 366;
        }
        for (years = 0; years < 3; years++)
        {
            if (SysTimeDays >= 365)
            {
                SysTimeDays -= 365;
                td->Year++;
                all_Days += 365;
            }
        }
    }
    UpdFeb(td->Year, Days_of_Months);
 Get_month:
    year_Days = 0;
    for (month = 0; month < 11; month++)
    {
        if (SysTimeDays >= Days_of_Months[month])
        {
            SysTimeDays -= Days_of_Months[month];
            all_Days += Days_of_Months[month];
            year_Days += Days_of_Months[month];
        }
        else break;
    }
    td->Month = month+1;
    td->Day = SysTimeDays+1;
    year_Days += SysTimeDays;
    all_Days += SysTimeDays;
    td->Hour = ds->ds_Minute / 60;
    td->Mins = ds->ds_Minute - td->Hour * 60;
    td->Secs = ds->ds_Tick / 50;
    td->Week = year_Days/7;
    td->Week_Day = all_Days % 7;
}
///

/// TD_DS (DateStamp *, TimeDate *)

// Transforms a TimeDate into a DateStamp

BOOL TD_DS(struct DateStamp *ds, struct TimeDate *td)
{
    ULONG i;
    
    UpdFeb(td->Year, Days_of_Months);
    
    if ((td->Hour > 24) || (td->Mins > 59) || (td->Secs > 59) ||
        (td->Year < 1978) || (td->Year > 2114) || (td->Month > 12) ||
        (td->Day > Days_of_Months[td->Month-1]) || (td->Day == 0))
        return (FALSE);
    
    ds->ds_Tick = td->Secs *50;
    ds->ds_Minute = td->Mins + td->Hour * 60;
    ds->ds_Days = td->Day-1;
    
    for (i=0; i<(td->Month-1); i++)
        ds->ds_Days += Days_of_Months[i];
    
    for (i=1978; i<td->Year; i++)
    if (IsBissextile(i)) ds->ds_Days += 366;
    else ds->ds_Days += 365;
}

///

/// GetFileStamp (fname, struct DateStamp *)

// TEXT *fname           filename to get DateStamp from
// struct DateStamp *ds  DateStamp to fill

BOOL GetFileStamp(TEXT *fname, struct DateStamp *ds)
{
    BPTR lock;
    struct FileInfoBlock fib;
    
    if (lock = Lock(fname, ACCESS_READ))
    {
        if (Examine(lock, &fib))
        {
            ds->ds_Days = fib.fib_Date.ds_Days;
            ds->ds_Minute = fib.fib_Date.ds_Minute;
            ds->ds_Tick = fib.fib_Date.ds_Tick;
            UnLock(lock);
            return(TRUE);
        }
        UnLock(lock);
    }
    ds->ds_Days = 0;
    ds->ds_Minute = 0;
    ds->ds_Tick = 0;
    return(FALSE);
}
///

/// ScanDate (datestring, timedate)

// Scans a string to find a date.

// TEXT *datestring
//       String containing a date.
//           (BEGIN | 1st space) Day (sp, - or /) StrMonth(3cars at least)
//               (" -/") Year (" ") Hour (":") Mins (":") Secs
// struct TimeDate *timedate
//       Pointer to a struct TimeDate

// Return
//       Ptr to timedate passed on success.  NULL if failed.  If failed,
//       timedate is NOT valid anymore.
/*
struct TimeDate *ScanDate(TEXT *string, struct TimeDate *td)
{
    TEXT *x;
    struct Table *tbl;
    
    memset((char *)td, 0L, sizeof(struct TimeDate));

    // Find date string type
    //  0   Sun Nov  6 08:49:37 1994
    //  1   Sun, 06 Nov 1994 08:49:37 GMT
    //  2   Sunday, 06-Nov-94 08:49:37 GMT
    x = strchr(string, ',');
    if (x == 0)
        tbl = &DateTypes[0];
    else if ((int)x-(int)string == 3)
        tbl = &DateTypes[1];
    else if ((int)x-(int)string == 6)
        tbl = &DateTypes[2];
    else
        return 0;

    // Scan Day
    td->Day = atol(string + tbl->Day);
    
    // Scan Month
    for (td->Month = 1; td->Month<13; td->Month++)
        if (!strnicmp(string + tbl->Month, MonthsNames[td->Month-1], 3))
            break;
    if (td->Month > 12)
        return(NULL);
    
    // Scan Year
    td->Year = 1900 + atol(string + tbl->Year);
    
    // Scan Hour
    td->Hour = atol(string + tbl->Hour);
    if (td->Hour > 23)
        return(NULL);
    
    // Scan Mins
    td->Mins = atol(string + tbl->Mins);
    if (td->Mins > 59)
        return(NULL);
    
    // Scan Secs
    td->Secs = atol(string + tbl->Secs);
    if (td->Secs > 59)
        return(NULL);
    
    // We've parsed a date and we suppose it to be OK
    return(td);
}
*/
///

/// Transl
// Translates all cars contained in "tok" to "to".
// Tok et to MUST have the same length.
// Returns 0 if to < tok or if to=0 or tok=0;

TEXT *Transl(TEXT *thestr, TEXT *tok, TEXT *to)
{
    LONG i, j, len, tlen;

    tlen = strlen(tok);
    if ((strlen(to) < tlen) || (!tok) || (!to))
        return(NULL);

    len = strlen(thestr);
    for (i=0; i<len; i++) {
        for (j=0; j<tlen; j++) {
            if (thestr[i] == tok[j]) {
                thestr[i] = to[j];
                break;
            }
        }
    }
    return(thestr);
}
///

