/* GetURL
Retrieves a html page from a web server
This version uses a QProtocolStopMarkerHook with functionality according to alternative 1.
Entire lines of data are returned to the client. The HTTP header comes as a QMessage with
qm_ID=1 and the body comes as one or more QMessages with qm_ID=2.
The buffer to return is also limited to 100 bytes below with the QRAWSESSION_MAXBUFFERSIZE tag.
Because 100 bytes probably isn't enough to store the entire HTTP header we will see an example
where amarquee.library automatically increase the buffersize to what is needed. The buffer size
is increased with a factor of two.

Written by Håkan Parting <hakan@parting.nu>
2000-08-05
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <dos/dos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>

#include <clib/AMarquee_protos.h>

#include <pragmas/AMarquee_pragmas.h>
/* #define ASYNC_CONNECT */

struct Library * AMarqueeBase = NULL;
struct QSession * session     = NULL;

/* New in version 51. Use a hook */
__regargs ULONG HTMLProtocolStopMarkerHookFunc(__A2 struct QSession *session, __A1 struct QProtocolStopMarkerMessage *msg); 
static const struct Hook HTMLProtocolStopMarkerHook = { { NULL,NULL },(void *)HTMLProtocolStopMarkerHookFunc,NULL,NULL };

/* New in version 50. Return error code in a variable */
/* Limit the buffer to 100 bytes so our hook is used */
/* Use a hook that make sure that only entire lines are sent to the client */
/* We want to receive the last bytes of data even though the hook didn't recognize a line end */
LONG errid=0;
struct TagItem tags[]={QSESSION_ERRORCODEPTR,(ULONG)&errid,
QRAWSESSION_MAXBUFFERSIZE,100,
QRAWSESSION_PROTOCOLSTOPHOOK,(ULONG)&HTMLProtocolStopMarkerHook,
QRAWSESSION_RECEIVE_EXCEEDING_DATA,TRUE,
TAG_DONE};

void CleanExit(void)
{
   printf("\nCleaning up...\n");
   if (session)      QFreeSession(session);        /* This MUST be done before we close the library! */
   if (AMarqueeBase) CloseLibrary(AMarqueeBase);  
   printf("All done.\n");
}

/* Main program */
int main(int argc, char ** argv)
{
   char * connectTo;
   int port;
   char *connectStr="", *loip;
   BOOL terminate=FALSE;
    
   printf("Usage Note:  GetURL [url=localhost] [port=80]\n");  
   printf("e.g. GetURL \"ACS.hostile.cx\" port=80\n");

   atexit(CleanExit);
  
   connectTo = (argc>1) ? argv[1] : "localhost";
   port      = (argc>2) ? atoi(argv[2]) : 80;

   if ((AMarqueeBase = OpenLibrary("amarquee.library",51L)) == NULL)
   {
      printf("Couldn't open amarquee.library v51!\n");
      exit(RETURN_ERROR);
   }
   printf("Connecting to %s:%i\n",connectTo, port);

   #ifdef ASYNC_CONNECT
   if ((session = QNewSocketSessionAsync(connectTo, port,tags)) == NULL)
   {
      printf("Couldn't connect to server %s:%i\nError: %s\n",connectTo, port, QErrorName(errid));
      exit(RETURN_WARN);
   }
   #else
   if ((session = QNewSocketSession(connectTo, port,tags)) == NULL)
   {
      printf("Couldn't connect to server %s:%i\nError: %s\n",connectTo, port, QErrorName(errid));
      exit(RETURN_WARN);
   }
   #endif

   printf("Connected to server %s:%i from local IP %s\n",connectTo, port,(loip=QGetLocalIP(session)) ? loip : "");
   printf("Retrieving index.html from %s ...\n", connectTo);

   connectStr="GET / HTTP/1.0\n\n";
   QSendRawOp(session,connectStr,strlen(connectStr));
   QGo(session,0L); /* Send it! */

   /* Wait for messages */
   while(!terminate)
   {
      struct QMessage * qMsg;
      ULONG signals = (1L << session->qMsgPort->mp_SigBit) | (SIGBREAKF_CTRL_C);

      /* Wait for next message from the server */
      signals = Wait(signals);
    
      if (signals & (1L << session->qMsgPort->mp_SigBit))
      {
         while(qMsg = (struct QMessage *) GetMsg(session->qMsgPort))
         {
            if (qMsg->qm_Status!=QERROR_NO_ERROR) {
               // Got an error!
               if (qMsg->qm_Status==QERROR_NO_CONNECTION) {
                  terminate=TRUE;
                  printf("Connection to remote host was closed.\n");
               }
            }
            else if (qMsg->qm_ID==0 && qMsg->qm_DataLen>0) { // Exceeding data sent to us (HTTP body)
               // Print it out
               char *buffer;
               if ((buffer=malloc(qMsg->qm_DataLen+1))) {   
                  memcpy(buffer,qMsg->qm_Data,qMsg->qm_DataLen); buffer[qMsg->qm_DataLen]=0;
                  printf("**** START OF EXCEEDING DATA PACKET ****\n%s\n**** END OF EXCEEDING DATA PACKET ****\n",buffer);
                  free(buffer);
               }
            }
            else if (qMsg->qm_ID==1 && qMsg->qm_DataLen>0) { // Data sent to us (HTTP header)
               // Print it out
               char *buffer;
               if ((buffer=malloc(qMsg->qm_DataLen+1))) {
                  memcpy(buffer,qMsg->qm_Data,qMsg->qm_DataLen); buffer[qMsg->qm_DataLen]=0;
                  printf("**** START OF HEADER DATA PACKET ****\n%s**** END OF HEADER DATA PACKET ****\n",buffer);
                  free(buffer);
               }
            }
            else if (qMsg->qm_ID==2 && qMsg->qm_DataLen>0) { // Data sent to us (HTTP body)
               // Print it out
               char *buffer;
               if ((buffer=malloc(qMsg->qm_DataLen+1))) {
                  memcpy(buffer,qMsg->qm_Data,qMsg->qm_DataLen); buffer[qMsg->qm_DataLen]=0;
                  printf("**** START OF BODY DATA PACKET ****\n%s**** END OF BODY DATA PACKET ****\n",buffer);
                  free(buffer);
               }
            }

            FreeQMessage(session, qMsg);
         }
      }
      if (signals & SIGBREAKF_CTRL_C) break;  /* Quit if CTRL-C pressed */
   }
   /* CleanExit() called here! */
}

/*
A hook that make sure the client only get entire lines of HTML data
Note: this function is run by the AMarquee process. The stack size is low so make
sure to not overflow the stack.
*/
__regargs ULONG HTMLProtocolStopMarkerHookFunc(__A2 struct QSession *session, __A1 struct QProtocolStopMarkerMessage *msg) {
    int i;

    /* depending on the state variable msg->userdata check if we have enough data */
    /* msg->userdata is set to 0L and msg->id is set to 0 at the start.
       The first thing we will receive is the HTTP header */
    switch (msg->userdata) {
        case 0:   /* HTTP HEADER */
        {
            int lf=0;
            /* the HTTP header ends with two line ends (10,10) or (13,10,13,10) */
            /* Set msg->userdata to 1 when we have successfully received the header
               so we know that the body will be the next thing to look for
            */
            for (i=0; i<msg->length; i++) {
                if (msg->buffer[i]==10)
                    lf++;
                else if (msg->buffer[i]!=13)
                    lf=0;
                /* break if we found two line feeds */
                if (lf==2) {
                    msg->userdata=1; /* next state */
                    msg->id=1;
                    break;
                }
            }
        }
        break;
        case 1:   /* HTTP BODY */
            /* search for the last line end */
            for (i=msg->length-1; i>=0; i--) {
                if (msg->buffer[i]==10) {
                    msg->id=2;
                    break;
                }
            }
        break;
    }
     
    
    /* if msg->id is zero we didn't have enough data, then return zero length */
    if (msg->id==0)
        i=0;
    else
        i++; /* the length is the offset + 1 */
         
    return i;
} 

