/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : Fetch.c                                */
/*                                                 */
/* Purpose: Mid-level fetch functions, concerned   */
/*          mostly with HStreams but not low level */
/*          HTMLLib interfacing. Compare with      */
/*          FetchPage.c, which provides a much     */
/*          higher level interface.                */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 25-Nov-96: Created.                    */
/*          17-Aug-97: Split up to form the        */
/*                     URLveneer.c and FetchHTML.c */
/*                     sources.                    */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "swis.h"
#include "flex.h"

#include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "svcprint.h"
#include "Global.h"
#include "MiscDefs.h"
#include "Utils.h"

#include "Authorise.h"
#include "Browser.h"
#include "Filetypes.h"
#include "FontManage.h"
#include "Forms.h"
#include "FetchHTML.h"
#include "Frames.h"
#include "History.h"
#include "Images.h"
#include "Memory.h"
#include "Meta.h"
#include "PlugIn.h"
#include "Protocols.h"
#include "Object.h"
#include "Redraw.h"
#include "Reformat.h"
#include "RMA.h"
#include "Save.h"
#include "SaveFile.h"
#include "SaveObject.h"
#include "Toolbars.h"
#include "URLutils.h"
#include "URLveneer.h"
#include "Windows.h"

#include "Fetch.h" /* (Which itself includes URLstat.h) */

/* Local definitons */

#define AuthorisationStr "Authorization: Basic "

/* Static function prototypes */

static HStream * fetch_find_anchor_token_r (browser_data * b, HStream * streambase, char * anchor);
static HStream * fetch_find_text_token_r   (browser_data * b, char * text, HStream ** last_t, int * last_o, int * offset, int cs, HStream * streambase, int * enabled);

/*************************************************/
/* fetch_start()                                 */
/*                                               */
/* Initiate a fetch for some URL.                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             for the browser window that the   */
/*             fetch relates to; the URL is      */
/*             pointed to in that structure, as  */
/*             the last item in the history.     */
/*                                               */
/* Returns:    A pointer to a _kernel_oserror    */
/*             structure if an error occured, or */
/*             NULL if there was no error.       */
/*************************************************/

_kernel_oserror * fetch_start(browser_data * b)
{
  _kernel_oserror * e;
  int               handle, method;

  #ifdef TRACE
    if (tl & (1u<<6)) Printf("\nfetch_start: Called\n");
  #endif

  /* (Order of evaluation ensures the check for the contents of */
  /* the memory pointed to by browser_fetch_url only occurs if  */
  /* the pointer isn't null)                                    */

  if (!browser_fetch_url(b) || !*browser_fetch_url(b))
  {
    b->fetch_status = BS_IDLE;
    toolbars_cancel_status(b, Toolbars_Status_Fetching);
    return NULL;
  }

  /* URL method is set to POST if there is forms data, or GET */
  /* if not (see Fetch.c for the definitions)                 */

  method = b->post_data ? URL_Method_http_POST : URL_Method_http_GET;

  /* Reset the encoding priority to default, awaiting any  */
  /* information in the HTTP header. Don't actually change */
  /* the encoding yet as we don't want the menu to change  */
  /* at this point.                                        */

  if (
       b->encoding_priority > priority_link &&
       b->encoding_priority < priority_user
     )
     b->encoding_priority = priority_default;

  /* Find out if this is an internal URL, and if so, */
  /* set the 'displayed' field in the browser_data   */
  /* struct appropriately.                           */
  /*                                                 */
  /* If we're saving out data in a link, then don't  */
  /* do this, as the page contents aren't actually   */
  /* changing (so leave the flag alone).             */

  if (!b->save_link) urlutils_set_displayed(b, b->urlfdata);

  /* Reset the data size counter */

  b->data_size = 0;

  /* Get, and start parsing the document */

  e = html_get(b->urlfdata,               /* Required document */
               (char **) (&b->post_data), /* Extra bits to append for POST etc */
               &handle,                   /* The library's handle for request */
               method,                    /* See above - POST or GET at this point */
               NULL,                      /* User name for Mailserv */
               1,                         /* Allow HTML parsing, 1 = yes, 0 = no */
               !b->reloading);            /* If 0, don't go through a proxy - e.g. for a reload */

  #ifdef TRACE
    if (b->post_data)
    {
      flexcount -= flex_size((flex_ptr) &b->post_data);
      if (tl & (1u<<13)) Printf("**   flexcount: %d\n",flexcount);
    }
  #endif

  if (b->post_data) flex_free((flex_ptr) &b->post_data);

  if (e)
  {
    b->fetch_status = BS_IDLE;
    toolbars_cancel_status(b, Toolbars_Status_Fetching);

    RetWarnE(e);
  }

  /* No error, so signal that the fetch has started. */

  b->fetch_handle = handle;
  b->fetch_status = BS_STARTED;
  toolbars_update_status(b, Toolbars_Status_Connecting);

  /* At this point e will always be NULL but that might change, */
  /* so the full trace code is being left in for now            */

  #ifdef TRACE
    if (tl & (1u<<6))
    {
      if (e) Printf("fetch_start: Exiting with error\n");
      else Printf("fetch_start: Successful\n");
    }
  #endif

  return e;
}

/*************************************************/
/* fetch_fetching()                              */
/*                                               */
/* Returns 1 if there is a fetch in progress     */
/* according to the contents of the data that    */
/* was pointed to (see Parameters), else 0.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the inquiry.          */
/*                                               */
/* Returns:    1 if a fetch is in progress, or   */
/*             0 if a fetch is not in progress.  */
/*************************************************/

int fetch_fetching(browser_data * b)
{
  /* This is currently very simple - a fetch is considered to be in */
  /* progress so long as the fetch_status doesn't indicate BS_IDLE. */

  return (b->fetch_status != BS_IDLE);
}

/*************************************************/
/* fetch_find_name_tag()                         */
/*                                               */
/* Finds the # separating an anchor name in a    */
/* URL.                                          */
/*                                               */
/* Parameters: A pointer to the URL string.      */
/*                                               */
/* Returns:    A pointer to the anchor string,   */
/*             including the leading #           */
/*************************************************/

char * fetch_find_name_tag(char * url)
{
  char * p;

  p = strrchr(url, '#');

//  p = strchr(url,'/'); /* Get past the first /, as in http:/ */
//
//  if (p) p = strchr(p + 1, '/'); /* Get past second /, as in http://                     */
//  if (p) p = strchr(p + 1, '/'); /* Get past site specifier, as in http://www.this.that/ */
//  if (p) p = strchr(p + 1, '#'); /* Find # in the document path                          */

  return p;
}

/*************************************************/
/* fetch_find_anchor_token()                     */
/*                                               */
/* Returns the address of the first token in the */
/* token list which has the given anchor name    */
/* associated with it, or NULL if none can be    */
/* found.                                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token list;       */
/*                                               */
/*             Pointer to the anchor name.       */
/*                                               */
/* Returns:    Pointer to the token associated   */
/*             with the given anchor name, or    */
/*             NULL if none is found.            */
/*************************************************/

HStream * fetch_find_anchor_token(browser_data * b, char * anchor)
{
  return fetch_find_anchor_token_r(b, b->stream, anchor);
}

/*************************************************/
/* fetch_find_anchor_token_r()                   */
/*                                               */
/* Recursive back-end to fetch_find_anchor_token */
/* - takes an extra parameter giving the top of  */
/* the HStream list to scan.                     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token list;       */
/*                                               */
/*             Pointer to first item in HStream  */
/*             list to scan;                     */
/*                                               */
/*             Pointer to the anchor name.       */
/*                                               */
/* Returns:    As fetch_find_anchor_token.       */
/*************************************************/

static HStream * fetch_find_anchor_token_r(browser_data * b, HStream * streambase, char * anchor)
{
  HStream * tp;

  tp = streambase;

  /* Go down the token list, checking if a token represents an   */
  /* anchor, has a name, and that name matches the given one. If */
  /* so, return the token address, else go onto the next token.  */

  while (tp && (tp->flags & HFlags_DealtWithToken))
  {
    /* A table token? */

    if (tp->tagno == TAG_TABLE)
    {
      table_stream   * table      = (table_stream *) tp;
      table_row      * row        = NULL;
      table_headdata * head       = NULL;
      HStream        * tf;
      int              cellcount  = 0;
      int              cellmax    = table->ColSpan * table->RowSpan;

      /* Scan the table for the token, using a recursive */
      /* call to this function for each cell.            */

      if (table->cells)
      {
        row = table->List;

        while (row && cellcount < cellmax)
        {
          head = row->List;

          while (head && cellcount < cellmax)
          {
            switch (head->Tag)
            {
              case TagTableData:
              case TagTableHead:
              {
                tf = fetch_find_anchor_token_r(b, (HStream *) head->List, anchor);
                if (tf) return tf;
              }
              break;
            }

            cellcount ++;

            head = head->Next;

          /* Closure of 'while (head && ...)' */
          }

          row = row->Next;

        /* Closure of 'while (row && ...)' */
        }

      /* Closure of 'if (table->cells)' */
      }

    /* Closure of check to see if token represents a table */
    }
    else if (
              (tp->style & A) &&
              tp->name        &&
              !utils_strcasecmp(tp->name, anchor)
            )
            return tp;

    tp = tp->next;
  }

  /* No match found - return NULL. */

  return NULL;
}

/*************************************************/
/* fetch_find_text_token()                       */
/*                                               */
/* Returns the address of the first token in the */
/* token list after a given one (or NULL for the */
/* very first) which points to a string holding  */
/* the given text, or NULL if none can be found. */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token list;       */
/*                                               */
/*             Pointer to a fragment of text to  */
/*             find;                             */
/*                                               */
/*             Pointer to the HStream where text */
/*             was last found (the function will */
/*             find the next), or NULL for the   */
/*             first call;                       */
/*                                               */
/*             The offset into that token the    */
/*             string was found at, or 0;        */
/*                                               */
/*             Pointer to an int, into which, if */
/*             text is found in a token, the     */
/*             offset into that text at which    */
/*             the search string was found;      */
/*                                               */
/*             1 for case sensitive comparison,  */
/*             else 0;                           */
/*                                               */
/*             1 to find the next item, else     */
/*             find the previous one.            */
/*                                               */
/* Returns:    Pointer to the token associated   */
/*             with the given anchor name, or    */
/*             NULL if none is found.            */
/*                                               */
/* Assumes:    The int pointer may NOT be NULL.  */
/*************************************************/

HStream * fetch_find_text_token(browser_data * b, char * text, HStream * last_t,
                                int last_o, int * offset, int cs, int forwards)
{
  int flags;

  if (!text || !*text) return NULL;

  /* Searching forwards is easy... */

  if (forwards)
  {
    flags = 0;

    return fetch_find_text_token_r(b,
                                   text,
                                   &last_t,
                                   &last_o,
                                   offset,
                                   cs,
                                   b->stream,
                                   &flags);
  }

  /* Going backwards is a bit trickier. Start from the top */
  /* of the page; remember if anything is found, and keep  */
  /* going until the last_t and last_o passed in here are  */
  /* matched. Return the previously found item (which may  */
  /* be NULL if you're already on the first).              */

  else
  {
    HStream * local_last_t  = NULL;
    int       local_last_o  = 0;

    HStream * local_start_t = NULL;
    int       local_start_o = 0;

    HStream * local_pass_t  = NULL;
    int       local_pass_o  = 0;

    for (;;)
    {
      /* The call may corrupt flags, local_start_t and */
      /* local_start_o, so reset flags before entry,   */
      /* and use a second copy of the other two.       */

      flags = 0;

      local_pass_t = local_start_t;
      local_pass_o = local_start_o;

      local_last_t = fetch_find_text_token_r(b,
                                             text,
                                             &local_pass_t,
                                             &local_pass_o,
                                             &local_last_o,
                                             cs,
                                             b->stream,
                                             &flags);

      if (local_last_t == last_t && local_last_o == last_o) break;
      if (!local_last_t) break;

      local_start_t = local_last_t;
      local_start_o = local_last_o;
    }

    *offset = local_start_o;

    return local_start_t;
  }
}

/*************************************************/
/* fetch_find_text_token_r()                     */
/*                                               */
/* Recursive back-end to fetch_find_text_token.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token list;       */
/*                                               */
/*             Pointer to a fragment of text to  */
/*             find;                             */
/*                                               */
/*             Pointer to a pointer to the       */
/*             HStream where text was last found */
/*             (the function will find the       */
/*             next), or pointer to NULL for the */
/*             first call (contents may be       */
/*             updated);                         */
/*                                               */
/*             Pointer to an int holding the     */
/*             offset into that token the string */
/*             was found at, or pointer to 0     */
/*             (again, contents may be updated); */
/*                                               */
/*             Pointer to an int, into which, if */
/*             text is found in a token, the     */
/*             offset into that text at which    */
/*             the search string was found;      */
/*                                               */
/*             1 for case sensitive comparison,  */
/*             else 0;                           */
/*                                               */
/*             Pointer to the first HStream in   */
/*             the list to search;               */
/*                                               */
/*             Pointer to an int, which contains */
/*             1 if string comparisons are to be */
/*             done yet, else 0 - this is so the */
/*             'find next' functionality can     */
/*             deal with recursion correctly.    */
/*             The int contents may be updated.  */
/*                                               */
/* Returns:    As fetch_find_text_token.         */
/*                                               */
/* Assumes:    As fetch_find_text_token, and     */
/*             that no other pointers are NULL   */
/*             unless stated (remember, there    */
/*             is a difference between being a   */
/*             NULL pointer and pointing to a    */
/*             NULL value!).                     */
/*************************************************/

static HStream * fetch_find_text_token_r(browser_data * b, char * text, HStream ** last_t, int * last_o,
                                         int * offset, int cs, HStream * streambase, int * enabled)
{
  HStream * tp;

  tp = streambase;

  /* Go down the token list, checking if a token has text. */
  /* If so, and comparisons are enabled, see if the given  */
  /* string lies in the token.                             */

  while (tp && (tp->flags & HFlags_DealtWithToken))
  {
    if (!(*last_t) || (*last_t) == tp) *enabled = 1;

    /* A table token? */

    if (tp->tagno == TAG_TABLE)
    {
      table_stream   * table      = (table_stream *) tp;
      table_row      * row        = NULL;
      table_headdata * head       = NULL;
      HStream        * tf;
      int              cellcount  = 0;
      int              cellmax    = table->ColSpan * table->RowSpan;

      /* Scan the table for the token, using a recursive */
      /* call to this function for each cell.            */

      if (table->cells)
      {
        row = table->List;

        while (row && cellcount < cellmax)
        {
          head = row->List;

          while (head && cellcount < cellmax)
          {
            switch (head->Tag)
            {
              case TagTableData:
              case TagTableHead:
              {
                tf = fetch_find_text_token_r(b,
                                             text,
                                             last_t,
                                             last_o,
                                             offset,
                                             cs,
                                             (HStream *) head->List,
                                             enabled);
                if (tf) return tf;
              }
              break;
            }

            cellcount ++;

            head = head->Next;

          /* Closure of 'while (head && ...)' */
          }

          row = row->Next;

        /* Closure of 'while (row && ...)' */
        }

      /* Closure of 'if (table->cells)' */
      }

    /* Closure of check to see if token represents a table */
    }
    else if (
              tp->text             &&
              (tp->style & PCDATA) &&
              *enabled
            )
    {
      const char * find = tp->text;

      /* If we're on the last found token, must make */
      /* sure we start looking just past the given   */
      /* offset.                                     */

      if (tp == (*last_t))
      {
        (*last_o) ++;

        /* If last_o is still within range of the token's */
        /* attached text, advance the pointer into that   */
        /* text past the start of the found offset.       */
        /* Otherwise, set last_o to -1 to signal that we  */
        /* have, in fact, finished with this token and    */
        /* should move onto the next one.                 */

        if ((*last_o) < strlen(find)) find += (*last_o), (*last_o) = 0;
        else (*last_o) = -1;
      }

      if ((*last_o) == 0)
      {
        const char * start_p   = find;
        const char * current_p = start_p;

        HStream    * current_t;

        const char * search_p  = text;

        char         search_b;
        char         current_b;

find_first_char:

        /* Get the byte of search string to look for, converting  */
        /* to lower case if this is a case insensitive comparison */

        current_t = tp;

        search_b = *search_p;
        if (!cs) search_b = tolower(search_b);

        do
        {
          /* Similarly, get a byte of the comparison string, possibly */
          /* converting to lower case.                                */

          current_b = *start_p;
          if (!cs) current_b = tolower(current_b);

          /* Keep looking as long as the strings don't run out and we */
          /* don't have a match. If we do find a match, we still want */
          /* to have start_p point past it in case the rest of the    */
          /* search string doesn't match and we need to jump back     */
          /* into this 'find the first character' routine.            */

          start_p++;
        }
        while (search_b && current_b && search_b != current_b);

        /* If we've still got a non-zero search string and comparison */
        /* string byte, they matched (otherwise, there's nothing      */
        /* else to do).                                               */

        if (search_b && current_b)
        {
          /* Now we step through both strings, not just the comparison */
          /* string, to make sure that all of the search string is     */
          /* contained within the comparison string.                   */

          current_p = start_p - 1;

check_rest_matches:

          do
          {
            search_p++;
            current_p++;

            search_b  = *search_p;
            current_b = *current_p;

            if (!cs) search_b = tolower(search_b), current_b = tolower(current_b);
          }
          while (search_b && current_b && search_b == current_b);

          /* If we've run out of search string, we have a match. If we've */
          /* run out of neither, the match failed; jump back to finding   */
          /* the first character in the rest of the comparison string.    */

          if (!search_b)
          {
            /* Found it - return the token pointer and offset */

            *offset = start_p - tp->text - 1; /* (-1 as start_p was advanced past the start point by 1) */
            return tp;
          }

          /* Mismatch - start looking again */

          if (search_b && current_b)
          {
            search_p = text;

            goto find_first_char;
          }

          /* If we've run out of *comparison* string, it might continue */
          /* in the next token (and the next, and the next, etc.)       */

          if (
               current_t->next                   &&
               current_t->next->text             &&
               (current_t->next->style & PCDATA)
             )
          {
            current_t = current_t->next;

            current_p = current_t->text - 1;
            search_p--;

            goto check_rest_matches;
          }
        }
      }
      else (*last_o) = 0;
    }

    tp = tp->next;
  }

  /* No match found - return NULL. */

  return NULL;
}

/*************************************************/
/* fetch_preprocess_token()                      */
/*                                               */
/* Takes a token for a given browser_data struct */
/* and preprocesses it - e.g. tells the image    */
/* library about image tokens so fetches can     */
/* start for those images.                       */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure relevant to the token;  */
/*                                               */
/*             Pointer to the token.             */
/*************************************************/

void fetch_preprocess_token(browser_data * b, HStream * tptr)
{
  /* final_token keeps track of the last token dealt with by this */
  /* routine, in the main token stream.                           */

  if (!tptr->parent) b->final_token = tptr;

  /* Deal with smart quotes etc. */

  // This could have side effects placed here...! Sort it out! - the function just returns at present.
  reformat_change_text(b, tptr);

  /* Deal with document body tags (not within HEAD, FRAMESET etc. containers) */

  if (ISBODY(tptr))
  {
    /* First, anything which isn't a table tag */

    if (tptr->tagno != TAG_TABLE)
    {
      /* If the 'style' entry has the image (IMG) bit set, ask */
      /* the image library to handle a new image. tptr->src    */
      /* will be a char * to the URL of the image.             */

      if (tptr->style & IMG)
      {
        if (fetch_chkerror(b,
                           image_new_image(b,
                                           tptr->src,
                                           tptr,
                                           0))) return;
      }

      /* Ask the Object library to handle a new OBJECT, EMBED or APPLET tag */

      if (ISOBJECT(tptr))
      {
        if (fetch_chkerror(b,
                           object_new_object(b,
                                             tptr))) return;
      }

      /* Handle some form tags */

      if (tptr->style & FORM)
      {
        switch (tptr->tagno)
        {
          /* If this is a FORM tag, create a new form. Otherwise, */
          /* any following form-related elements will be added to */
          /* the current form. We want to force a new header if   */
          /* the form ends - if another form starts, the dummy    */
          /* header is skipped (not a big problem), else any new  */
          /* forms elements without a FORM tag don't get          */
          /* accidentally associated with the earlier FORM.       */

          case TAG_FORM:
          {
            if (fetch_chkerror(b, form_new_form(b, tptr))) return;
          }
          break;

          case TAG_FORM_END:
          {
            if (fetch_chkerror(b, form_new_form(b, NULL))) return;
          }
          break;

          /* Deal with creating a new field as appropriate */

          case TAG_INPUT:
          {
            switch(HtmlINPUTtype(tptr))
            {
              case inputtype_TEXT:    if (fetch_chkerror(b, form_new_field(b, tptr, form_text,     tptr->text                     ))) return; break;
              case inputtype_PASSWORD:if (fetch_chkerror(b, form_new_field(b, tptr, form_password, tptr->text                     ))) return; break;
              case inputtype_CHECKBOX:if (fetch_chkerror(b, form_new_field(b, tptr, form_checkbox, (char *) HtmlINPUTchecked(tptr)))) return; break;
              case inputtype_RADIO:   if (fetch_chkerror(b, form_new_field(b, tptr, form_radio,    (char *) HtmlINPUTchecked(tptr)))) return; break;
              case inputtype_IMAGE:   if (fetch_chkerror(b, form_new_field(b, tptr, form_image,    NULL                           ))) return; break;
              case inputtype_HIDDEN:  if (fetch_chkerror(b, form_new_field(b, tptr, form_hidden,   NULL                           ))) return; break;
              case inputtype_SUBMIT:  if (fetch_chkerror(b, form_new_field(b, tptr, form_submit,   NULL                           ))) return; break;
              case inputtype_BUTTON:  if (fetch_chkerror(b, form_new_field(b, tptr, form_button,   NULL                           ))) return; break;
              case inputtype_RESET:   if (fetch_chkerror(b, form_new_field(b, tptr, form_reset,    NULL                           ))) return; break;
            }
          }
          break;

          /* Handle text areas */

          case TAG_TEXTAREA:
          {
            if (fetch_chkerror(b, form_new_field(b, tptr, form_textarea, tptr->text))) return;
          }
          break;

          /* Handle selection buttons */

          case TAG_SELECT:
          {
            if (fetch_chkerror(b, form_new_field(b, tptr, form_select, (char *) HtmlSELECToptions(tptr)))) return;
          }
          break;
        }
      }
    }

    /* Tables - need to preprocess any HStreams attached as part of a table */
    /* tag. Because any one token is only run through this preprocessor     */
    /* once, and because when this table tag is run through it all of the   */
    /* HStreams within may not have arrived yet (the page is only partially */
    /* fetched), it is still necessary to rescan the attached HStreams at a */
    /* later date (e.g. as part of the reformatting process) to ensure they */
    /* are all preprocessed correctly.                                      */

    else
    {
      table_stream   * table = (table_stream *) tptr;
      table_row      * R;
      table_headdata * D;
      HStream        * attached;

      R = table->List;

      /* Scan the rows and cells */

      while (R)
      {
        D = R->List;

        while (D)
        {
          if (D->Tag)
          {
            switch (D->Tag)
            {
              case TagTableData:
              case TagTableHead:
              {
                attached = (HStream *) D->List;

                /* Preprocess any attached HStream list - must */
                /* check table tags even if they've been done  */
                /* before to look for new HStreams, otherwise  */
                /* avoid preprocessing the same thing twice.   */

                while (attached)
                {
                  if (
                       attached->tagno == TAG_TABLE ||
                       !(attached->flags & HFlags_DealtWithToken)
                     )
                     fetch_preprocess_token(b, attached);

                  attached = attached->next;
                }
              }
              break;
            }
          }

          D = D->Next;
        }

        R = R->Next;
      }
    }

    /* Closure of long 'if' to see if the HStream structure represented */
    /* a body tag or header information - the code is run if it's a     */
    /* body tag.                                                        */
  }
  else if ISFRAMESET(tptr)
  {
    browser_data * parent;
    browser_data * child = b;

    parent = b->parent;
    if (!parent) parent = b;

    if (tptr->size)
    {
      int level = tptr->size;

      /* If filling_frame is equal to the number of children, then */
      /* they've all been filled - the frameset must be broken.    */

      if (
           !b->nchildren ||
           (
             b->nchildren                    &&
             b->filling_frame < b->nchildren
           )
         )
      {
        while (level > 1)
        {
          /* If in a nested frameset, find out what browser_data struct  */
          /* to put the frames in. This should go in the next frame that */
          /* is to be filled in according to the parent.                 */

          if (child->nchildren && child->filling_frame < child->nchildren)
          {
            child = (browser_data *) child->children[child->filling_frame];
            level--;
          }
          else
          {
            child = NULL;
            break;
          }
        }
      }
      else child = NULL;

      if (child)
      {
        /* If stepping down a level, i.e. after a /frameset tag, */
        /* will want to increment the filling_frame field for    */
        /* this browser to say that the child we just stepped    */
        /* down for has been filled with a frameset. There's the */
        /* complication of a /frameset being followed by another */
        /* frameset and the level therefore staying the same;    */
        /* this is dealt with in the frameset section below.     */

        if (tptr->size < parent->nesting_level)
        {
          child->filling_frame++;
        }

        /* The aforementioned frameset section... */

        if (!(tptr->style & FRAME))
        {
          /* Define a new frameset. */

          if (tptr->size <= parent->nesting_level && child->parent)
          {
            /* If at the same level as before on receiving a frameset  */
            /* tag, must be doing nested frames and just had some      */
            /* /framesets before this tag came along. So need to       */
            /* increment the filling_frame counter of the *parent*     */
            /* (remember, we're at the level of the frame to fill in,  */
            /* not in the level below as with the code above that      */
            /* checked the level has stepped down). Therefore, need    */
            /* to find out again what browser_data struct is to be     */
            /* given the frameset based on the new filled_frame value. */

            child->parent->filling_frame++;
            child = (browser_data *) child->parent->children[child->parent->filling_frame];
          }

          /* Must force scrollbars off in this current view, */
          /* as a frameset is about to appear over it.       */

          windows_check_tools(child, NULL);

          if (tptr->size > 1 && parent->frameset)
          {
            /* This is a nested frameset, so we must inherit some aspects */
            /* of the parent's frameset token to tptr.                    */

            if (parent->frameset)
            {
              /* Border colour */

              tptr->maxlen = parent->frameset->maxlen;

              /* Border width / frame spacing */

              tptr->indent = parent->frameset->indent;
            }
          }

          /* Finally, define the frameset at the required depth. */

          frames_define_frameset(child, tptr);
        }
        else
        {
          /* Fill in details of a frame. */

          frames_define_frame(child, tptr);
        }

        parent->nesting_level = tptr->size;
      }

      #ifdef STRICT_PARSER

        else
        {

          erb.errnum = Utils_Error_Custom_Message;

          StrNCpy0(erb.errmess,
                   lookup_token("FramNest:Frames definition is badly nested; could not complete the frames layout.",
                                0,0));

          show_error_ret(&erb);
        }

      #endif
    }
  }
  else if ISHEAD(tptr)
  {
    /* Deal with header (HEAD) tags */

    if (tptr->tagno == TAG_TITLE && tptr->text)
    {
      /* The tag is TITLE, and there is title text. */

      char   title[Limits_Title];
      char * p = title;
      char * end;

      /* Can't overflow maximum length so just crop the string to fit */

      StrNCpy0(title, tptr->text);

      /* Strip any spaces at the start */

      while (*p == ' ') p++;

      /* Strip any spaces at the end */

      end = (char *) ((int) p + strlen(p) - 1);
      if (end > p) while (*end == ' ') *end-- = 0;

      /* If there's anything left now... */

      if (*p != 0)
      {
        /* Set the title */

        if (!b->ancestor && fetch_chkerror(b, window_set_title(0, b->self_id, p))) return;

        /* Try adding this title to the history, ignoring any errors */

        history_add_title(browser_fetch_url(b), p);
      }
    }

    if (tptr->tag == BODY)
    {
      /* The BODY tag. All sorts of exciting stuff in here... */

      if (HtmlBODYbgcolour(tptr) != NULL_COLOUR)
      {
        /* Get the 24-bit background colour, if any. Do this before */
        /* starting a fetch for any background image as it will     */
        /* affect whether cross referencing can occur or not.       */

        b->background_colour = HtmlBODYbgcolour(tptr);

        #ifdef TRACE
          if (tl & (1u<<6)) Printf("fetch_preprocess_token: Background colour set to %d\n", b->background_colour);
        #endif

        /* If there's no actual background image, set the anti-alias */
        /* colour to be the same as the background colour.           */

        if (b->background_image < 0) b->antialias_colour = b->background_colour;

        browser_update_bottom(b, 0);
      }

      /* Background images */

      if (HtmlBODYbackground(tptr))
      {
        /* If there's a URL for the image, ask the image library for it */
        /* and remember the image number in the browser_data structure  */

        image_new_image(b, HtmlBODYbackground(tptr), tptr, 2);
      }

      /* Get the rest of the colour info out */

      if (HtmlBODYtext (tptr) != NULL_COLOUR) b->text_colour     = HtmlBODYtext (tptr);
      if (HtmlBODYlink (tptr) != NULL_COLOUR) b->link_colour     = HtmlBODYlink (tptr);
      if (HtmlBODYvlink(tptr) != NULL_COLOUR) b->used_colour     = HtmlBODYvlink(tptr);
      if (HtmlBODYalink(tptr) != NULL_COLOUR) b->followed_colour = HtmlBODYalink(tptr);

      /* Pull out the onload and onunload scripts */

      if (HtmlBODYonload  (tptr)) b->onload   = HtmlBODYonload  (tptr);
      if (HtmlBODYonunload(tptr)) b->onunload = HtmlBODYonunload(tptr);
    }

    /* Deal with META... tags */

    if (tptr->tag == META)
    {
      meta_process_tag(b, tptr);
    }

    /* Closure of long else to see if the HStream structure represented */
    /* a body tag or header information - the code is run if it's a     */
    /* head tag.                                                        */
  }

  /* If we've reached here, the token has been dealt with */
  /* successfully - so mark this in its flags word.       */

  tptr->flags |= HFlags_DealtWithToken;

  return;
}

/*************************************************/
/* fetch_fetcher()                               */
/*                                               */
/* The main part of the fetch routine. Handles   */
/* the processing of data from the URL module,   */
/* after fetch_start has asked it to start       */
/* getting data from a server.                   */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure, to which the fetch     */
/*             relates.                          */
/*************************************************/

void fetch_fetcher(browser_data * b)
{
  HStream * tptr;
  int       start = -1;
  int       i, remain, sofar, waiting;

  /* It really helps to understand this function if you realise that */
  /* it's coded more or less backwards (Merlyn was weird...). For    */
  /* example, the BS_DATAFETCH code below isn't executed unless the  */
  /* fetch state reaches BS_DATAFETCH, but it can't do that unless   */
  /* fetcher code much further down is run. So you really need to    */
  /* read this all in one go before trying to piece it together, or  */
  /* maybe even read it from the bottom up...                        */
  /*                                                                 */
  /* BS stands for Browser Status, by the way, and nothing else ;-)  */

  for (i = 0; i < 10; i ++) /* Get several tokens on each null event */
  {
    /* For BS_DATAFETCH, save the file */

    if (b->fetch_status == BS_DATAFETCH)
    {
      #ifdef TRACE
        if (tl & (1u<<6)) Printf("fetch_fetcher: fetch_status = BS_DATAFETCH / BS_DATAWHERE.\n");
      #endif

      /* This code gets called by the stuff further down advancing */
      /* the status to BS_DATAFETCH.                               */
      /*                                                           */
      /* If the save_file field is NULL, we're still waiting for   */
      /* the user to pull their finger out and say where to save   */
      /* the object.                                               */

      if (b->save_file) /* Proceed if there's a file to save to */
      {
        char              buffer[2048];
        int               success, bytes = -1, done = 0;
        _kernel_oserror * e;

        /* Get a chunk of data */

        e = fetch_get_raw_data(NULL,
                               b->fetch_handle,
                               buffer,
                               sizeof(buffer),
                               &done,
                               &bytes);

        /* If there's an error, show it but continue */

        if (e) show_error_ret(e);

        success = !e;

        /* If there was not an error, write a chunk of file */

        if (success && bytes) success = fwrite(buffer, 1, bytes, b->save_file);

        /* If the expected number of bytes was not written, */
        /* show whatever error fwrite generated             */

        if (success != bytes && bytes)
        {
          success = 0;
          erb     = *_kernel_last_oserror();
          show_error_ret(&erb);
        }

        /* If apparently successful and finished, read the pathname */
        /* of the file so the filetype can be set.                  */

        if (success && done)
        {
          _swix(OS_Args,
                _INR(0,2) | _IN(5),

                7, /* Read pathname of open file */
                b->save_file->__file,
                buffer,
                sizeof(buffer));
        }

        /* If finished or there was some error above, stop the fetch */

        if (!success || done) fetch_stop(b, 0); /* This closes the output file, too */

        /* If successful and finished, set the filetype */

        if (success && done)
        {
          _swix(OS_File,
                _INR(0,2),

                18,
                buffer,
                b->save_type);
        }

        /* Finally, ensure toolbars are up to date. */

        toolbars_update_progress(b);
      }

      return;
    }

    #ifdef TRACE
      if (tl & (1u<<6)) Printf("fetch_fetcher: Get next token\n");
    #endif

    /* Get the next token, with fetch_chkerror allowing us to exit */
    /* relatively cleanly should an error occur.                   */

    if (
         fetch_chkerror(
                         b, html_get_next_token(
                                                 b,
                                                 b->fetch_handle,
                                                 &remain,
                                                 &sofar,
                                                 &tptr,
                                                 &waiting,
                                                 (flex_ptr) &b->source,
                                                 browser_fetch_url(b),
                                                 0
                                               )
                       )
       )
       return;

    /* Show the fetch's progress */

    toolbars_update_progress(b);

    /* If waiting = 3 the data being fetched isn't parseable, or has */
    /* been marked as not for parsing so that it may be saved.       */

    if (waiting == 3)
    {
      /* Full screen browsers can't save objects out */

      if (choices.full_screen && b->full_screen)
      {
        erb.errnum = Utils_Error_Custom_Message;

        StrNCpy0(erb.errmess,
                 lookup_token("NotInternal:Can't save objects when running in full screen mode.",
                              0,
                              0));

        fetch_chkerror(b, &erb);

        return;
      }
      else
      {
        #ifdef TRACE
          if (tl & (1u<<6)) Printf("fetch_fetcher: fetch_status moved to BS_DATAFETCH\n");
        #endif

        b->fetch_status = BS_DATAFETCH;

        b->save_type    = remain;
        b->save_link    = 1;

        /* Flag that we're now fetching */

        toolbars_cancel_status(b, Toolbars_Status_Connecting);
        toolbars_update_status(b, Toolbars_Status_Fetching);

        if (b->save_type == FileType_DATA || b->save_type == 0x000)
        {
          /* If we've been given data or unknown, see if we can have a better guess! */

          b->save_type = urlutils_filetype_from_url(browser_fetch_url(b));
        }

        /* We may already have a save file if something knew the path before */
        /* the fetch started...                                              */

        if (!b->save_file)
        {
          /* Open a save dialogue for the object if we're not */
          /* spooling data for a Plug-In, else go straight to */
          /* a temporary file.                                */

          if (!b->pstream)
          {
            if (fetch_chkerror(b, saveobject_open_for(b))) return;
          }
          else
          {
            char unique[Limits_OS_Pathname];

            protocols_util_make_unique_name(unique, sizeof(unique));

            if (fetch_chkerror(b, save_save_object(unique, b))) return;

            /* If we haven't got an active stream, should now send a  */
            /* message to the Plug-In to tell it that we want to open */
            /* this stream. We need to record the filename, too.      */

            if (fetch_chkerror(b,
                               rma_claim(b,
                                         strlen(unique) + 1,
                                         (void **) &b->pstream->filename.ptr))) return;

            strcpy(b->pstream->filename.ptr, unique);

            if (fetch_chkerror(b,
                               plugin_send_original_stream_new(b))) return;
          }
        }
      }

      return;
    }

    /* If waiting = 2, a redirect has occurred */

    else if (waiting == 2)
    {
      char * url;
      int    internal = 0;

      #ifdef TRACE
        if (tl & (1u<<6)) Printf("fetch_fetcher: Redirect to %s\n",(char *) remain);
      #endif

      /* Get the new URL pointed to by 'url' */

      url = (char *) remain;

      if (b->displayed != Display_Fetched_Page) internal = 1;

      if (!internal)
      {
        /* Allocate space for new URL and copy it into that space */

        #ifdef TRACE
          if (tl & (1u<<12)) Printf("fetch_fetcher: Chunk CK_FURL set to %d\n",strlen(url) + 1);
        #endif

        if (fetch_chkerror(b, memory_set_chunk_size(b, NULL, CK_FURL, strlen(url) + 1))) return;
        strcpy(b->urlfdata, url);
      }
      else
      {
        char furl[Limits_URL];

        /* Allocate space for new URL plus old URL and separator, */
        /* and copy them into that space                          */

        #ifdef TRACE
          if (tl & (1u<<12)) Printf("fetch_fetcher: Chunk CK_FURL set to %d\n",strlen(url) + strlen(furl) + 2);
        #endif

        StrNCpy0(furl, browser_fetch_url(b));

        if (fetch_chkerror(b, memory_set_chunk_size(b, NULL, CK_FURL, strlen(url) + strlen(furl) + 2))) return;

        strcpy(b->urlfdata, url);
        strcat(b->urlfdata, ":");
        strcat(b->urlfdata, furl);
      }

      /* Reflect the new URL in the status and URL bars */

      toolbars_update_status(b, Toolbars_Status_Redirected);
      toolbars_update_url(b);
    }

    /* If we're waiting for new tokens, don't sit here in a loop   */
    /* single tasking - break out, allowing more external polling. */

    else if (waiting)
    {
      break;
    }

    /* We're not waiting for data. */

    else
    {
      /* If it isn't already non-zero, set 'start' to the number of */
      /* the last line in the line list (i.e. nlines - 1).          */

      if (start < 0) start = b->cell->nlines - 1;

      /* We're not waiting, have we got a token? */

      if (b->fetch_status == BS_STARTED)
      {
        /* Yes - this is the first token on this page. Get the window */
        /* ready for the new page - this includes ditching old data.  */

        int l;

        /* Make the current display URL = current fetch URL... */

        l = strlen(browser_fetch_url(b)); /* Get the fetching URL string length */

        /* Allocate memory for it, and copy the string across */

        #ifdef TRACE
          if (tl & (1u<<12)) Printf("fetch_fetcher: Chunk CK_DURL set to %d\n",l + 1);
        #endif

        if (fetch_chkerror(b, memory_set_chunk_size(b, NULL, CK_DURL, l + 1))) return;

        strcpy(b->urlddata, browser_fetch_url(b));
        toolbars_hide_internal(b->urlddata);

        /* Write to the global history */

        if (
             b->displayed == Display_Fetched_Page ||
             b->displayed == Display_External_Image
           )
           history_record(b, b->urlddata);

        /* Update the title bar */

        if (!b->ancestor) /* Child windows don't have title bars... */
        {
          char title[Limits_Title];

          StrNCpy0(title, b->urlddata);

          if (fetch_chkerror(b, window_set_title(0, b->self_id, title))) return;
        }

        /* Set status to FETCHING instead of STARTED */

        b->fetch_status = BS_FETCHING;

        /* If there was previous display data present, get rid of it */

        if ((b->display_handle) && (b->display_handle != b->fetch_handle))
        {
          html_close(b->display_handle);
          b->display_handle = 0;
        }

        b->save_oldstore = 0;

        /* Signal that the display data is coming from the fetch data, */
        /* so that the fetch data doesn't get accidentally ditched     */
        /* until it's finished with                                    */

        b->display_handle = b->fetch_handle;

        /* Initialise various things inside the browser_data structure */
        /* to do with colours and so-forth                             */

        #ifdef TRACE
          if (tl & (1u<<18)) Printf("New fetch for %p, stream %p\n",b,tptr);
        #endif

        b->stream            = tptr;                     /* Pointer to list of HStream structures */
        b->final_token       = NULL;                     /* Last HStream structure dealt with     */
        b->last_char         = ' ';                      /* Last character dealt with             */

        b->background_colour = -1;                       /* Background colour, or -1 for default  */

        b->background_image  = -1;                       /* Image no. of background image, 0=none */
        b->text_colour       = choices.text_colour;      /* Body text default colour              */
        b->link_colour       = choices.link_colour;      /* Link text default colour              */
        b->used_colour       = choices.used_colour;      /* Followed link default colour          */
        b->antialias_colour  = redraw_backcol(b);        /* Colour to anti-alias to, or -1=none   */
        b->followed_colour   = choices.followed_colour;  /* Following link default colour         */
        b->selected_colour   = choices.selected_colour;  /* Selected (highlighted) link colour    */

        b->onload            = NULL;                     /* <BODY onload> attribute               */
        b->onunload          = NULL;                     /* <BODY onunload> attribute             */

        /* If this was a History-based fetch, clear the flag */

        b->from_history      = 0;

        /* Ensure the nesting level and filling frame counters are reset */

        b->nesting_level     = 0;
        b->filling_frame     = 0;

        /* Cancel any pending automatic fetches */

        if (b->meta_refresh_at) deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) meta_check_refresh, b);
        b->meta_refresh_at  = 0;
        b->meta_refresh_url = NULL;

        /* Cancel pending reformats */

        reformat_stop_pending(b);

        /* Hideously long comment alert...                                  */
        /*                                                                  */
        /* Although if a frame loads a document containing another frameset */
        /* this is in one sense a nested frame defintion, in another the    */
        /* second document is independent of the first; certainly as far as */
        /* incrementing the filling_frame field of the parent goes, the     */
        /* <frame> tag that loaded this document into the frame in the      */
        /* first place will already have done that.                         */
        /*                                                                  */
        /* Consequently, whilst all child frames have an ancestor - the     */
        /* original, base browser that defined the first of possibly many   */
        /* framesets - only genuinely nested frameset arrays have parents.  */
        /* That is, a parent can only have children; it may not also be a   */
        /* child (i.e. have a parent), it may only have an ancestor.        */
        /*                                                                  */
        /* Genuinely nested frames consist of one document with more than   */
        /* one set of <frameset> tags. Here, filling_frame considerations   */
        /* demand the use of a nested_level count and a parent as well as   */
        /* an ancestor. For those single documents, we won't be running     */
        /* this code when second or further framesets come in, so the       */
        /* parent field will get estabilshed and remain as long as needed   */
        /* by the frames routines.                                          */

        b->parent = NULL;

        /* Yup - that whole comment for one tiny line of code. Woo...       */
        /*                                                                  */
        /* 'Course, that said, it's useful for every child to know who its  */
        /* parent is. That's what the real_parent field is for.             */

//        {
//          WimpGetWindowStateBlock state;
//
//          state.window_handle = b->window_handle;
//
//          if (!wimp_get_window_state(&state))
//          {
//            b->display_width = b->display_extent = state.visible_area.xmax - state.visible_area.xmin;
//          }
//        }

        /* Don't want to set the pointer_over field, as then it may not seem to */
        /* have changed from one fetch to another; the pointer can get 'stuck'  */
        /* in the 'link' shape.                                                 */

        b->highlight      = NULL; /* No tokens are highlighted */
        b->selected       = NULL; /* No tokens are selected    */
        b->selected_owner = NULL;

        #ifdef TRACE
          if (tl & (1u<<6)) Printf("\nfetch_fetcher: Document colours etc. set to default values\n");
        #endif

        /* Clear the status bar contents block for an ancestor */
        /* window beginning a new fetch.                       */

        if (!b->ancestor && b->nstatus)
        {
          #ifdef TRACE

            if (
                 (tl & (1u<<1)) ||
                 (tl & (1u<<6))
               )
               Printf("fetch_fetcher: Freeing status_contents array\n");

          #endif

          b->nstatus = 0;
          memory_set_chunk_size(b, NULL, CK_STAT, 0);
        }

        /* Clear allocated memory for the forms, and tell */
        /* the font library that the fonts aren't needed  */
//        /* anymore. Images are cleared after the fetch,   */
//        /* so that any images common between the two can  */
//        /* be preserved.                                  */

        form_discard(b);
        fm_lose_fonts(b);

        /* Flag that images need to be garbage collected later */

// Um... ToDo list time...
        image_discard(b);
//        b->clear_images = 1;

        /* Similarly, get rid of Objects */

        object_discard(b);

        /* IMPORTANT, must call the reformatter here to ensure that all various   */
        /* line list data is invalidated, discarded, and any new stuff is valid.  */
        /* Otherwise, could have bits of the application subsequently using old   */
        /* line data and things will go very wrong very quickly.                  */
        /*                                                                        */
        /* DON'T put anything that might try and read line data before this call! */

        b->display_extent = b->display_width; /* Ensure a new fetch starts with the horizontal extent matching the visible area */

        start = -1;
        reformat_format_from(b, -1, 1, -1);
        reformat_check_extent(b);

        /* If there's a save dialogue open for this frame, get rid of it */

        savefile_close(b->self_id, 0);

        /* Collapse any frames within this browser */

        frames_collapse_set(b);

        /* Ensure window tools are up to date */

        if (b->ancestor || b->full_screen) windows_set_tools(b, NULL, !b->ancestor, 0, 0, 0);

        /* If there's a # inside the URL (i.e. we're supposed to jump to an */
        /* anchor) then set the token to display first to be DISPLAY_NAMED, */
        /* a large number which acts as a flag to say 'jump to anchor'. The */
        /* fetch polling routine (see FetchPage.c) should notice this and   */
        /* start looking for a token with the appropriate name, and if it   */
        /* finds it, display that token.                                    */

        if (fetch_find_name_tag(browser_current_url(b))) b->display_request = DISPLAY_NAMED;

        /* Ensure the pointer shape is correct */

        browser_pointer_check(0, NULL, NULL, b);

        /* Reflect the new browser status */

        toolbars_cancel_status(b, Toolbars_Status_Connecting);
        toolbars_update_status(b, Toolbars_Status_Fetching);

        /* Since the new fetch is now official, update the current and previous */
        /* page variables                                                       */

        if (!b->ancestor)
        {
          /* Not speed critical, so avoid lots of nasty C-isms with malloc */
          /* and so-on, by running through OS_CLI.                         */

          _swix(OS_CLI,
                _IN(0),

                "Set Browse$PreviousPage <Browse$CurrentPage>");

          _swix(OS_SetVarVal,
                _INR(0,4),

                "Browse$CurrentPage",
                b->urlfdata,
                strlen(b->urlfdata),
                0,
                4);
        }

        /* (Initialisation to an empty state is now complete, so we're */
        /* ready to fetch a new page).                                 */
      }

      /* We're not waiting, but if there's also no data left to fetch, */
      /* then we're just chugging through the list of tokens that the  */
      /* library has generated, telling various bits of the code about */
      /* their contents (e.g. a new image, a new form). In this case,  */
      /* change the fetch status so the status bar can reflect the new */
      /* situation.                                                    */

      if (!remain)
      {
        b->fetch_status = BS_PROCESS;

        toolbars_update_status(b, Toolbars_Status_Processing);
        toolbars_update_progress(b);
      }

      /* If tptr is null, there are no HStream structures (see the */
      /* html_get_next_token call). But we're not waiting either,  */
      /* so must be at the end of the file - stop the fetch.       */

      if (!tptr)
      {
        if (b->last_token->tagno == TAG_TABLE)
        {
          /* If the last thing the reformatter dealt with was a table, */
          /* then extra table structures could have been added by      */
          /* HTMLLib. It is important to ensure that any tokens that   */
          /* were added to the token stream are preprocessed before    */
          /* starting a reformat.                                      */

          fetch_preprocess_token(b, b->last_token);

          /* Make sure the page is fully reformatted */

          start = -1;
          reformat_format_from(b, b->cell->nlines - 2, 1, -1);
        }

        #ifdef TRACE
          if (tl & (1u<<6)) Printf("\nfetch_fetcher: Finished, so stopping and exiting.\n");
        #endif

        fetch_stop(b, 1);

        break;
      }
      else fetch_preprocess_token(b, tptr);

    /* Closure of series of ifs that checked the state of 'waiting' */
    /* amongst other things, to handle redirections etc. The bulk   */
    /* of the code deals with a conventional fetch.                 */
    }

  /* Closure of for loop that deals with several fetches per null */
  }

  /* If start is >= 0, there is data that can be used for     */
  /* displaying the page; so start a reformat based on that   */
  /* data. Start from 'one line up' as the last line may have */
  /* been only partially finished when it was last redrawn.   */
  /* The reformat session can be deferred if the reformatter  */
  /* is not running, but if the reformatter is still going,   */
  /* push this request through immediately. Otherwise         */
  /* problems with long-delayed reformats way after the page  */
  /* has been more or less completely formatted can occur.    */

  if (start >= 0)// && !reformat_formatting(b))
  {
    reformat_format_from(b,
                         start - 1, /* '-1' as this parameter is the *last valid* line number */
                                    /* we want to keep, and 'start' holds the first line to   */
                                    /* start the reformat at.                                 */

                         reformat_formatting(b),
                         -1);
  }
}

/*************************************************/
/* fetch_chkerror()                              */
/*                                               */
/* Called by low level fetch routines instead of */
/* the ChkError macro, as it stops the current   */
/* fetch correctly before reporting the error.   */
/*                                               */
/* Parameters: Pointer to a browser_data         */
/*             structure relevant to the fetch;  */
/*                                               */
/*             Pointer to a _kernel_oserror      */
/*             structure, which contains the     */
/*             error to report (or NULL).        */
/*                                               */
/* Returns:    0 if there was no error, else 1.  */
/*************************************************/

int fetch_chkerror(browser_data * b, _kernel_oserror * e)
{
  if (e)
  {
    /* There is an error - cancel the fetch */

    fetch_cancel(b);

    /* Copy it locally if need be */

    if (&erb != e) erb = *e;

    /* Report the error, as a warning message only */

    erb.errnum = Utils_Error_Custom_Message;

    show_error_ret(&erb);

    /* Flag the error in the returned value */

    return 1;
  }

  return 0;
}

/*************************************************/
/* fetch_cancel()                                */
/*                                               */
/* Aborts a fetch, closing any relevant streams, */
/* freeing up any claimed memory that was only   */
/* relevant to the fetch, but leaves the page    */
/* fetched so far visible.                       */
/*                                               */
/* Parameters: A pointer to the browser_data     */
/*             structure relevant to the fetch   */
/*             to be cancelled.                  */
/*************************************************/

_kernel_oserror * fetch_cancel(browser_data * b)
{
  /* If there is a fetch, and the HTML data isn't being used by the  */
  /* display routines, close the fetch handle and free up any memory */
  /* associated with it.                                             */

  if ((b->fetch_handle) && ((b->fetch_handle) != (b->display_handle))) html_close(b->fetch_handle);
  b->fetch_handle = 0;

  /* If a META tag is about to do a reload, cancel this */

  if (b->meta_refresh_at) deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) meta_check_refresh, b);
  b->meta_refresh_at = 0;

  /* If not fetching, exit here */

  if (!fetch_fetching(b)) return NULL;

  /* If we have a Plug-In stream, mark it as abandoned */

  if (b->pstream) b->pstream->abandoned = 1;

  /* Stop everything else */

  fetch_stop(b, 1);

  /* Ensure the page is correctly formatted */

  if (b->cell->nlines) reformat_format_from(b, b->cell->nlines - 1, 1, -1);

  return(NULL);
}

/*************************************************/
/* fetch_stop()                                  */
/*                                               */
/* Stops a fetch, optionally discarding the      */
/* HTML source, making sure the browser window   */
/* state (buttons, status bar animation etc.) is */
/* correct, any open files are closed, and so    */
/* forth. In the UI sense this is higher level   */
/* than fetch_cancel, though fetch_cancel calls  */
/* this as part of doing other cancel actions,   */
/* and is therefore the higher level function.   */
/*                                               */
/* Parameters: A pointer to the browser_data     */
/*             structure relevant to the fetch   */
/*             to be stopped;                    */
/*                                               */
/*             1 to keep the HTML source, 0 to   */
/*             destroy it.                       */
/*************************************************/

void fetch_stop(browser_data * b, int keep_source)
{
  /* Destroy the source, provided the browser was fetching any */

  if (fetch_fetching(b) && !keep_source) browser_destroy_source(b);

  /* Set the fetch status to idle */

  b->fetch_status = BS_IDLE;

  /* The save_link flag tells the browser to save the next fetch as data, */
  /* even if it is parsable. Want to make sure that flag is clear now to  */
  /* avoid complications later on.                                        */

  b->save_link = 0;

  /* If data was being saved to a file, close that file */

  if (b->save_file)
  {
    char buffer[Limits_OS_Pathname];

    /* Leave the file intact, but set it to a Data filetype */

    if (
         !_swix(OS_Args,
                _INR(0,2) | _IN(5),

                7, /* Read pathname of open file */
                b->save_file->__file,
                buffer,
                sizeof(buffer))
       )
    {
      _swix(OS_File,
            _INR(0,2),

            18,
            buffer,
            FileType_DATA);
    }

    fclose(b->save_file);
    b->save_file = NULL;
  }

  if (b->save_dbox) saveobject_close(b);

  if (b->fetch_handle)
  {
    /* If there is a fetch, and the associated HTML document isn't being */
    /* used by the display routines, close that fetch handle and free    */
    /* any memory associated with it.                                    */

    if (b->fetch_handle != b->display_handle) html_close(b->fetch_handle);

    /* Otherwise, still have to call EndParse (which html_close would */
    /* do, if it were called).                                        */

    else
    {
      urlstat * up = urlstat_find_entry(b->fetch_handle);

      if (up && up->context)
      {
        HtmlEndParse(up->context);
        up->context = NULL;
      }
    }
  }

  b->fetch_handle = 0;

  /* Discard the URL being fetched */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("fetch_stop: Chunk CK_FURL set to 0\n");
  #endif

  memory_set_chunk_size(b, NULL, CK_FURL, 0);

  /* Update the status bar */

  toolbars_cancel_status(b, Toolbars_Status_Fetching);

  /* Check that the window extent is large enough to fit the whole page in */

  reformat_check_extent(b);

  /* Set up the window buttons */

  toolbars_set_button_states(b);
}

/*************************************************/
/* fetch_authorisation_proceed()                 */
/*                                               */
/* Given a browser_data structure with a URL     */
/* containing a host and a pointer to a realm    */
/* string, proceed with an authorisation         */
/* request based on the data in the global       */
/* 'authorise' flex block (handled by the        */
/* functions in Authorise.c).                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the authorisation     */
/*             request;                          */
/*                                               */
/*             Pointer to a urlstat structure    */
/*             giving the fetch context, or NULL */
/*             to get it from the browser_data   */
/*             structure's fetch_handle field    */
/*             (no good for images, obviously);  */
/*                                               */
/*             Pointer to a string containing    */
/*             the realm for the request;        */
/*                                               */
/*             Pointer to the request URL.       */
/*************************************************/

void fetch_authorisation_proceed(browser_data * b, urlstat * context, char * realm, char * url)
{
  int       ok, l, s, offset;
  char      host    [Limits_HostName];
  char      base64  [(Limits_AuthUserWrit + Limits_AuthPassWrit + 2) * 4 / 3];
  char      authcode[(Limits_AuthUserWrit + Limits_AuthPassWrit + 2)];
  urlstat * up;

  /* Clear the 'authorising' flag */

  authorising = 0;

  /* If required, find out the session handle */

  if (!context)
  {
    up = urlstat_find_entry(b->fetch_handle);

    if (!up)
    {
      fetch_cancel(b);

      erb.errnum = Utils_Error_Custom_Normal; /* Nasty error but can recover from it here */

      StrNCpy0(erb.errmess,
               lookup_token("StrNotFd:Internal error: Can't find structure in %0.",
                            0,
                            "fetch_authorisation_proceed"));

      show_error_ret(&erb);

      return;
    }
  }
  else up = context;

  /* Mark this fetch as authorised once already - if the server */
  /* resends an authorisation request the fetcher will know     */
  /* that the authorisation failed (see html_get_next_token).   */

  up->authorised = 2;

  if (up->extradata) s = flex_size((flex_ptr) &up->extradata);
  else               s = 0;

  /* Work out the host name */

  urlutils_host_name_from_url(url, host, sizeof(host));

  /* Store the details in the authcode block */

  offset = authorise_find_user_name(host, realm);
  if (offset < 0)
  {
    fetch_authorisation_fail(b);
    return;
  }

  strcpy(authcode, authorise + offset);

  strcat(authcode, ":");

  offset = authorise_find_password(host, realm);
  if (offset < 0)
  {
    fetch_authorisation_fail(b);
    return;
  }

  strcat(authcode, authorise + offset);

  /* Encode the block */

  l = encode_base64(authcode, strlen(authcode), base64);
  base64[l] = 0;

  /* Allocate memory for the encoded data as a */
  /* header entry.                             */
  /*                                           */
  /* +2 accounts for CR + LF termination.      */

  l += strlen(AuthorisationStr) + 2;

  if (s) ok = flex_extend((flex_ptr) &up->extradata, s + l);
  else   ok = flex_alloc((flex_ptr) &up->extradata, l + 1);

  if (!ok)
  {
    fetch_cancel(b);

    show_error_ret(make_no_fetch_memory_error(12));

    return;
  }

  if (s) memmove(up->extradata + l, up->extradata, s);

  /* Copy the data in */

  strcpy(up->extradata, AuthorisationStr);
  strncpy(up->extradata + strlen(AuthorisationStr), base64, l - 23);
  up->extradata[l - 2] = '\r';
  up->extradata[l - 1] = '\n';

  if (!s) up->extradata[l] = 0;

  /* Restart the fetch with authentication */

  fetch_chkerror(b, url_get_url(URL_GetURL_AgentGiven,
                    up->session,
                    up->method,
                    url,
                    &up->extradata,
                    NULL,
                    2));

  return;
}

/*************************************************/
/* fetch_authorisation_fail()                    */
/*                                               */
/* Called when authorisation for a URL fails in  */
/* some way. Reports an appropriate error and    */
/* stops the fetch.                              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the fetch.            */
/*************************************************/

void fetch_authorisation_fail(browser_data * b)
{
  /* Cancel the fetch */

  authorising = 0;
  fetch_cancel(b);

  /* Give the error */

  erb.errnum = Utils_Error_Custom_Message;

  StrNCpy0(erb.errmess,
           lookup_token("BadAuthor:Authorisation failed; you must use a valid user name and password.",
                        0,
                        0));

  show_error_ret(&erb);
}

/*************************************************/
/* fetch_get_raw_data()                          */
/*                                               */
/* Gets a chunk of data from a stream, assuming  */
/* that it is *not* HTML.                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the fetch;            */
/*                                               */
/*             The fetch handle;                 */
/*                                               */
/*             Pointer to buffer into which the  */
/*             fetched data will be placed (as   */
/*             a char *);                        */
/*                                               */
/*             Size of the buffer;               */
/*                                               */
/*             Pointer to an int into which 1 is */
/*             placed if the fetch is complete,  */
/*             else 0 is returned (this pointer  */
/*             may be NULL);                     */
/*                                               */
/*             Pointer to an int into which the  */
/*             number of bytes fetched is placed */
/*             (which may also be NULL).         */
/*                                               */
/* Assumes:    That if the browser_data struct   */
/*             pointer is NULL, the fetch is not */
/*             for an internal URL;              */
/*                                               */
/*             The int pointer to take the       */
/*             number of bytes fetched may not   */
/*             be NULL.                          */
/*************************************************/

_kernel_oserror * fetch_get_raw_data(browser_data * b, unsigned int handle, char * buffer,
                                     int size, int * done, int * bytes)
{
  _kernel_oserror * e;
  urlstat         * up;
  int               s, t;

//
// This function does not know about internal URLs yet (so parameter 'b' is currently unused)...
// BEWARE when using this, as of course this URL fetch may not be for page data under an
// internal URL.
//

  /* Find the urlstat structure for the fetch handle */

  up = urlstat_find_entry(handle);

  if (!up)
  {
    erb.errnum = Utils_Error_Custom_Fatal;

    StrNCpy0(erb.errmess,
             lookup_token("StrNotFd:Internal error: Can't find structure in %0.",
                          0,
                          "fetch_get_raw_data"));
    return &erb;
  }

  /* If we're not fetching, some earlier fetch process must have */
  /* shut things down; so say that we're finished and exit.      */

  if (!up->fetching)
  {
    if (done) *done = 1;
    *bytes = 0;

    return NULL;
  }

  /* Read some data */

  e = url_read_data(0,
                    handle,
                    buffer,
                    size,
                    NULL,
                    bytes,
                    &t);

  if (e) return e;

  /* Get the fetch status */

  e = url_status(0,
                 handle,
                 &s,
                 NULL,
                 NULL);

  if (e) return e;

  /* Fill in 'done' as appropriate to the fetch status and exit */

  if (done) *done = (s & URL_Status_Done) ? 1 : 0;

  return NULL;
}