#charset "us-ascii"

#include <tads.h>
#include <tadsnet.h>

/* 
 *   Copyright (c) 1999, 2006 Michael J. Roberts
 *   
 *   This file is part of TADS 3
 *   
 *   This file defines classes and properties used with the tads-net
 *   intrinsic function set.  If you're using this function set, you should
 *   include this source file in your build by adding it to your project
 *   makefile.
 */

/* include the tads-net intrinsic function set interface definition */
#include <tadsnet.h>

/* ------------------------------------------------------------------------ */
/*
 *   A NetEvent instance describes an event read via the getNetEvent()
 *   function.
 *   
 *   In most cases, this base class will not be instantiated directly.
 *   getNetEvent() will always construct the appropriate subclass for the
 *   specific type of event being generated, if that subclass is defined in
 *   the game program.  However, it's possible that the game won't define
 *   all necessary subclasses.  For example, a game written for version 1
 *   of the networking package wouldn't include new subclasses added in
 *   version 2, because those subclasses weren't defined at the time the
 *   game was written.  When getNetEvent() needs to instantiate a subclass
 *   that isn't defined in the game program, it will instead create a base
 *   NetEvent object, which will simply store the subclass-specific
 *   arguments as a list.  This could be useful for debugging purposes,
 *   because it will at least let the programmer inspect the event details
 *   with the interactive debugger.  
 */
class NetEvent: object
    /*
     *   The event type.  This is a NetEvXxx value (see tadsnet.h)
     *   indicating which type of event this is.    
     */
    evType = nil

    /* 
     *   Construction.  getNetEvent() only constructs this object directly
     *   when the subclass it's looking for isn't defined in the game
     *   program.  
     */
    construct(t, [args])
    {
        evType = t;
        evArgs = args;
    }

    /*
     *   Extra event-specific arguments.  This is primarily for debugging
     *   purposes, since it's only used when getNetEvent() needs to
     *   construct a NetEvent subclass that isn't defined in the game.  In
     *   this case, the absence of a subclass definition in the game
     *   presumably means that the game isn't written to handle the type of
     *   event generated (for example, because it was written for an older
     *   interpreter version that didn't have the event type).  
     */
    evArgs = nil
;

/*
 *   Network Request Event.  This type of event occurs when a server (such
 *   as an HTTPServer object) receives a request from a network client.
 *   
 *   The evRequest member contains a request object describing the network
 *   request.  The class of this object depends on the type of server that
 *   received the request.  For example, for an HTTP server, this will be
 *   an HTTPRequest object.  To reply to the request, use the appropriate
 *   method(s) in the request object - for details, see the specific
 *   request classes for the server types you create in your program.
 */
class NetRequestEvent: NetEvent
    /* construction */
    construct(t, req)
    {
        inherited(t, req);
        evRequest = req;
    }

    evType = NetEvRequest

    /*
     *   The request object.  When the event type is NetEvRequest, this
     *   contains a request object describing the request.  The class of
     *   the request object varies according to the server type; you can
     *   use ofKind() to check which type of request it is.  For example,
     *   for an HTTP request, this will be an object of class HTTPRequest.
     */
    evRequest = nil
;

/*
 *   Network Timeout Event.  getNetEvent() returns this type of event when
 *   the timeout interval expires before any actual event occurs.  
 */
class NetTimeoutEvent: NetEvent
    evType = NetEvTimeout
;

/*
 *   Network Reply event.  This type of event occurs when we receive a
 *   reply to a network request made with sendNetRequest().
 */
class NetReplyEvent: NetEvent
    /* construction */
    construct(t, id, status, body, headers, loc)
    {
        inherited(t, id, body, headers, loc);
        statusCode = status;
        requestID = id;
        replyBody = body;
        replyHeadersRaw = headers;
        redirectLoc = loc;

        /* parse the headers into a lookup table keyed by header name */
        if (headers != nil)
        {
            /* create the lookup table */
            local ht = replyHeaders = new LookupTable();

            /* split the headers at the CR-LF separators */
            headers = headers.split('\r\n');

            /* the first line of the headers is actually the HTTP status */
            if (headers.length() > 1)
            {
                /* save the status line */
                httpStatusLine = headers[1];
                headers = headers.sublist(2);
            }
            
            /* process the rest of the headers */
            for (local h in headers)
            {
                /* split the header at the ":", and trim spaces */
                h = h.split(':', 2).mapAll(
                    { s: rexReplace('^<space>+|<space>+$', s, '') });
                
                /* 
                 *   If it looks like a header, add it to the table.  If
                 *   the header is repeated, append it to the previous
                 *   value with a comma delimiter. 
                 */
                if (h.length() == 2)
                {
                    local name = h[1].toLower(), val = h[2];
                    if (ht.isKeyPresent(name))
                        val = '<<ht[name]>>, <<val>>';
                    ht[name] = val;
                }
            }
        }
    }

    /* our default event type is NetEvReply */
    evType = NetEvReply

    /*
     *   The request identifier.  This is the ID value provided by the
     *   caller in the call to sendNetRequest(), so that the caller can
     *   relate the reply back to the corresponding request.
     */
    requestID = nil

    /*
     *   The network status code.  This is an integer value indicating
     *   whether the request was successful or failed with an error.  A
     *   negative value is a low-level TADS error indicating that the
     *   request couldn't be sent to the server, or that a network error
     *   occurred receiving the reply:
     *   
     *.    -1    - out of memory
     *.    -2    - couldn't connect to host
     *.    -3    - other network/socket error
     *.    -4    - invalid parameters
     *.    -5    - error reading the content data to send to the server
     *.    -6    - error saving the reply data received from the server
     *.    -7    - error retrieving reply headers
     *.    -8    - error starting background thread
     *.    -100  - other TADS/network error
     *   
     *   A positive value means that the network transaction itself was
     *   successful, and reflects the status information returned by the
     *   network server that handled the request.  This must be interpreted
     *   according to the protocol used to send the request:
     *   
     *   - For HTTP requests, the value is an HTTP status code.  A code in
     *   the 200 range generally indicates success, while other ranges
     *   generally indicate errors.
     */
    statusCode = nil

    /* the content body from the reply */
    replyBody = nil

    /* 
     *   the HTTP headers from the reply, as a lookup table indexed by
     *   header name 
     */
    replyHeaders = nil

    /* the HTTP status string (the first line of the headers) */
    httpStatusLine = nil

    /* 
     *   the HTTP headers from the reply, in the raw text format - this is
     *   simply a string of all the headers, separated by CR-LF (\r\n)
     *   sequences 
     */
    replyHeadersRaw = nil

    /* 
     *   Redirect location, if applicable.  By default, this will be nil
     *   whether or not a redirection took place, because sendNetRequest()
     *   normally follows redirection links transparently, returning only
     *   the final result from the final server we're redirected to.
     *   However, you can override automatic redirection with an option
     *   flag (NetReqNoRedirect) when calling sendNetRequest().  When that
     *   option is selected, the function won't follow redirection links at
     *   all, but will instead simply return the redirect information as
     *   the result from the request.  When that happens, this property is
     *   set to a string giving the target of the redirect.  You can then
     *   follow the redirect manually, if desired, by sending a new request
     *   to the target given here.
     */
    redirectLoc = nil
;

/*
 *   Network Reply Done event.  This type of event occurs when an
 *   asynchronous network reply (such as HTTPRequest.sendReplyAsync())
 *   completes.
 */
class NetReplyDoneEvent: NetEvent
    /* construction */
    construct(t, req, err, msg)
    {
        inherited(t, req, err, msg);
        requestObj = req;
        socketErr = err;
        errMsg = msg;
    }

    /* our default event type is NetEvReplyDone */
    evType = NetEvReplyDone

    /* 
     *   The object representing the request we replied to.  For HTTP
     *   requests, this is an HTTPRequest object.
     */
    requestObj = nil

    /* was the reply successfully sent? */
    isSuccessful() { return errMsg == nil; }

    /*
     *   The socket error, if any.  If the reply failed due to a network
     *   error, this contains the error number.  If no network error
     *   occurred, this is zero.
     */
    socketErr = 0

    /*
     *   Error message, if any.  If the reply failed, this contains a
     *   string with a description of the error that occurred.  If the
     *   reply was sent successfully, this is nil.
     */
    errMsg = nil
;

/* ------------------------------------------------------------------------ */
/*
 *   A FileUpload represents a file uploaded by a network client via a
 *   protocol server, such as an HTTPServer.
 *   
 *   When your program is acting as a network server, a FileUpload object
 *   represents a file received from the client.  For example,
 *   HTTPRequest.getFormFields() returns a FileUpload object to represent
 *   each <INPUT TYPE=FILE> field in the posted form.
 *   
 *   When your program acts as a network client (via sendNetRequest), you
 *   can create use FileUpload to post file attachments to posted forms.
 */
class FileUpload: object
    construct(file, contentType, filename)
    {
        self.file = file;
        self.contentType = contentType;
        self.filename = filename;
    }

    /*
     *   The file data.
     *   
     *   When you create the FileUpload object for use with
     *   sendNetRequest() to post form data, you must use a string or
     *   ByteArray value for this property.
     *   
     *   When the FileUpload is created by HTTPRequest.getFormFields(),
     *   this property contains a File object with the uploaded content.
     *   This is open for read-only access.  If the contentType parameter
     *   is a text type ("text/html", "text/plain", etc), and the
     *   interpreter recognizes the character set parameter in the
     *   contentType, the file is in Text mode (FileModeText) with the
     *   appropriate character mapper in effect.  Otherwise, the file is in
     *   raw binary mode (FileModeRaw).  If you need the file to be opened
     *   in a different mode, you can use setFileMode() on the file to
     *   change the mode.  
     */
    file = nil

    /*
     *   The content type.  This a string giving the MIME type specified by
     *   the client with the upload.  This is the full content-type string,
     *   including any attributes, such "charset" for a text type.  This
     *   can be nil if the client doesn't specify a content-type at all.
     *   
     *   It's important to recognize that this information is supplied by
     *   the client, and is NOT validated by the protocol server.  At best
     *   you should consider it a suggestion, and at worst a malicious lie.
     *   The client could be innocently mistaken about the type, or could
     *   even be intentionally misrepresenting it.  You should always
     *   validate the actual contents, rather than relying on the client's
     *   description of the format; in particular, be careful not to assume
     *   that expected data fields are present, in the valid range, etc.
     */
    contentType = nil

    /*
     *   The client-side filename, if specified.  This is a string giving
     *   the name of the file on the client machine.  This generally has no
     *   particular meaning to the server, since we can't infer anything
     *   about the directory structure or naming conventions on an
     *   arbitrary client.  However, this might be useful for reference,
     *   such as showing information about the upload in a user interface.
     *   It's sometimes also marginally useful to know the suffix
     *   (extension) for making further guesses about the content type -
     *   although as with the content-type, you can't rely upon this, but
     *   can only use it as a suggestion from the client.
     *   
     *   The client won't necessarily specify a filename at all, in which
     *   case this will be nil.  
     */
    filename = nil
;
    

/* ------------------------------------------------------------------------ */
/*
 *   A NetException is the base class for network errors. 
 */
class NetException: Exception
    construct(msg?, errno?)
    {
        if (errMsg != nil)
            errMsg = 'Network error: <<msg>>';
        if (errno != nil)
            errMsg += ' (system error code <<errno>>)';
    }
    displayException() { "<<errMsg>>"; }

    /* a descriptive error message provided by the system */
    errMsg = 'Network error'
;

/*
 *   A NetSafetyException is thrown when the program attempts to perform a
 *   network operation that isn't allowed by the current network safety
 *   level settings.  The user controls the safety level; the program can't
 *   override this.  
 */
class NetSafetyException: NetException
    errMsg = 'Network operation prohibited by user-specified '
             + 'network safety level'
;

/*
 *   A SocketDisconnectException is thrown when attempting to read or write
 *   a network socket that's been closed, either by us or by the peer (the
 *   computer on the other end of the network connection).  If we didn't
 *   close the socket on this side, this error usually means simply that
 *   the peer program has terminated or otherwise disconnected, so we
 *   should consider the conversation terminated.  
 */
class SocketDisconnectException: NetException
    errMsg = 'Network socket disconnected by peer or closed'
;

/* export the objects and properties used in the tads-net function set */
export NetEvent 'TadsNet.NetEvent';
export NetRequestEvent 'TadsNet.NetRequestEvent';
export NetTimeoutEvent 'TadsNet.NetTimeoutEvent';
export NetReplyEvent 'TadsNet.NetReplyEvent';
export NetReplyDoneEvent 'TadsNet.NetReplyDoneEvent';
export NetException 'TadsNet.NetException';
export SocketDisconnectException 'TadsNet.SocketDisconnectException';
export NetSafetyException 'TadsNet.NetSafetyException';
export FileUpload 'TadsNet.FileUpload';
export file 'TadsNet.FileUpload.file';
export contentType 'TadsNet.FileUpload.contentType';
export filename 'TadsNet.FileUpload.filename';

