/* 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   : Authorise.c                            */
/*                                                 */
/* Purpose: Browser remote authorisation services  */
/*          (aka. authentication).                 */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 24-Apr-97: Created.                    */
/***************************************************/

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

#include "flex.h"

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

#include "toolbox.h"
#include "window.h"
#include "gadgets.h"

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

#include "Browser.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "Toolbars.h"
#include "URLutils.h"
#include "Windows.h"

#include "Authorise.h"

/* Locals */

static ObjectId authorise_dbox = 0;
static char     current_realm[Limits_Realm];

/*************************************************/
/* authorise_create_dialogue                     */
/*                                               */
/* Creates an authorisation dialogue and         */
/* installs relevant event handlers with a given */
/* client handle. If the dialogue already exists */
/* then old handlers are removed and new ones    */
/* installed.                                    */
/*                                               */
/* Parameters: The client handle to pass to the  */
/*             event handlers, as a void *;      */
/*                                               */
/*             Pointer to an ObjectId, in which  */
/*             the ObjectId of the dialogue will */
/*             be written (may be NULL).         */
/*************************************************/

_kernel_oserror * authorise_create_dialogue(void * handle, ObjectId * id)
{
  if (authorise_dbox)
  {
    /* If there's already a dialogue, remove event handlers that */
    /* are already present prior to installing new ones. This is */
    /* to allow the client handle to change.                     */

    RetError(event_deregister_toolbox_handlers_for_object(authorise_dbox));
  }
  else
  {
    /* Otherwise, create the dialogue */

    RetError(toolbox_create_object(0, "Authorise", &authorise_dbox));

    /* Set a client handle of 0 for now */

    RetError(toolbox_set_client_handle(0, authorise_dbox, NULL));

    /* Modify writables if necessary */

    {
      char username[Limits_AuthUserWrit];
      char password[Limits_AuthPassWrit];

      /* First, the user name writable */

      RetError(windows_process_component_text(authorise_dbox, AuthUserWrit, username, sizeof(username), 0, 1));

      /* Next, the password writable */

      RetError(windows_process_component_text(authorise_dbox, AuthPassWrit, password, sizeof(password), 0, 1));
    }
  }

  if (id) *id = authorise_dbox;

  /* Install the event handlers and exit */

  RetError(event_register_toolbox_handler(authorise_dbox,
                                          Window_HasBeenHidden,
                                          authorise_cancel,
                                          handle));

  RetError(event_register_toolbox_handler(authorise_dbox,
                                          EAuthAuthorise,
                                          authorise_authorise,
                                          handle));

  /* Animation handler if there's an appropriate gadget */

  {
    int temp_type;

    if (
         controls.dbox_anims &&
         !gadget_get_type(0, authorise_dbox, StatusBarAnimAnim, &temp_type)
       )
       register_null_claimant(Wimp_ENull,
                              toolbars_animate_slow,
                              (void *) authorise_dbox);
  }

  return event_register_toolbox_handler(authorise_dbox,
                                        EAuthCancel,
                                        authorise_cancel,
                                        handle);
}

/*************************************************/
/* authorise_return_dialogue_id()                */
/*                                               */
/* Returns the ID of the current authorisation   */
/* dialogue (will be 0 if there is no dialogue   */
/* present at the moment).                       */
/*                                               */
/* Returns: Object ID of the authorisation dbox. */
/*************************************************/

ObjectId authorise_return_dialogue_id(void)
{
  return authorise_dbox;
}

/*************************************************/
/* authorise_authorise()                         */
/*                                               */
/* Accepts changes in an authorisation dialogue  */
/* and proceeds with the authorisation request.  */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int authorise_authorise(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  char              host     [Limits_HostName];
  char              username [Limits_AuthUserWrit];
  char              password [Limits_AuthPassWrit];
  _kernel_oserror * e;
  browser_data    * b = (browser_data *) handle;

  /* Read the user name and password from the authorisation dialogue */

  ChkError(writablefield_get_value(0, authorise_dbox, AuthUserWrit, username, sizeof(username), NULL));
  ChkError(writablefield_get_value(0, authorise_dbox, AuthPassWrit, password, sizeof(password), NULL));

  username[sizeof(username) - 1] = 0;
  password[sizeof(password) - 1] = 0;

  /* Work out the host name */

  urlutils_host_name_from_url(browser_fetch_url(b), host, sizeof(host));

  /* Try to remember the authentication details. Note that the current_realm */
  /* contents are taken to be correct as the realm for this request; this    */
  /* will always be OK at the time of writing the code as only one request   */
  /* can ever be in progress, but if this should change the code will need   */
  /* to be altered to cope. Can't see why it would though; the user only has */
  /* the one pair of hands and (usually) the one keyboard. Programming for   */
  /* aliens, perhaps...?                                                     */

  ChkError(authorise_remember(host, current_realm, username, password));

  /* Can get rid of the authorisation dialogue now */

  ChkError(event_deregister_toolbox_handlers_for_object(authorise_dbox));

  /* If there was a null handler for the dialogue, remove it */

  {
    int temp_type;

    if (
         controls.dbox_anims &&
         !gadget_get_type(0, authorise_dbox, StatusBarAnimAnim, &temp_type)
       )
       deregister_null_claimant(Wimp_ENull,
                                toolbars_animate_slow,
                                (browser_data *) authorise_dbox);
  }

  /* Can't use ChkError now; instead must run through fetch_authorisation_fail, */
  /* or the user could be left stuck in authentication mode.                    */

  e = toolbox_delete_object(0, authorise_dbox);
  if (e)
  {
    fetch_authorisation_fail(b);
    return 1;
  }

  authorise_dbox = 0;

  /* Call the fetcher's routines to proceed, and exit */

  fetch_authorisation_proceed(b, NULL, current_realm, browser_fetch_url(b));

  return 1;
}

/*************************************************/
/* authorise_cancel()                            */
/*                                               */
/* Discards any changes in an authorisation      */
/* dialogue, deleting it if Adjust isn't being   */
/* used at the time. If the dialogue is deleted  */
/* the appropriate steps are taken to indicate   */
/* authorisation failure to the user.            */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int authorise_cancel(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  WimpGetPointerInfoBlock info;

  if (!authorise_dbox) return 0;

  ChkError(wimp_get_pointer_info(&info));

  if (info.button_state & Wimp_MouseButtonAdjust)
  {
    /* Clear the password and user name writables */

    ChkError(writablefield_set_value(0, authorise_dbox, AuthPassWrit, ""));
    ChkError(writablefield_set_value(0, authorise_dbox, AuthUserWrit, ""));
  }
  else
  {
    /* Deregister event handlers and delete the dialogue. Note */
    /* the use of authorise_dbox rather than idb->self_id; if  */
    /* this handler managed to get called on some odd ID or    */
    /* or with a null ID block it'll still work.               */

    ChkError(event_deregister_toolbox_handlers_for_object(authorise_dbox));

    /* If there was a null handler for the dialogue, remove it */

    {
      int temp_type;

      if (
           controls.dbox_anims &&
           !gadget_get_type(0, authorise_dbox, StatusBarAnimAnim, &temp_type)
         )
         deregister_null_claimant(Wimp_ENull,
                                  toolbars_animate_slow,
                                  (void *) authorise_dbox);
    }

    /* No error check; want to drop through to the fail routine */
    /* regardless.                                              */

    toolbox_delete_object(0, authorise_dbox);
    authorise_dbox = 0;

    fetch_authorisation_fail((void *) handle);
  }

  return 1;
}

/*************************************************/
/* authorise_read_realm()                        */
/*                                               */
/* Given a pointer to a string holding an        */
/* appropriate part of an HTTP response header   */
/* detailing the realm for an authentication     */
/* request, extract the realm and put it into    */
/* the local static char array 'current_realm'.  */
/*                                               */
/* Parameters: Pointer to the header entry       */
/*             string.                           */
/*                                               */
/* Returns:    Pointer to the current_realm      */
/*             string.                           */
/*************************************************/

char * authorise_read_realm(char * header_entry)
{
  char * p = header_entry;

  /* The realm lies between two double quotes */

  while (*p && *p != '"') p++;
  if (*p) p++;

  StrNCpy0(current_realm, p);

  p = current_realm;

  while(*p && *p != '"') p++;

  *p = 0;

  return current_realm;
}

/*************************************************/
/* authorise_find_offset()                       */
/*                                               */
/* Returns the offset of the authorisation data  */
/* for a given realm and host, or -1 if none is  */
/* present / can be found.                       */
/*                                               */
/* Parameters: Pointer to the host string;       */
/*                                               */
/*             Pointer to the realm string.      */
/*                                               */
/* Returns:    Offset into the 'authorise' flex  */
/*             block that the given entry lies   */
/*             at, or -1 if not found.           */
/*************************************************/

int authorise_find_offset(char * host, char * realm)
{
  int o, s, l;

  /* Only proceed if there's allocated data to look at! */

  if (authorise)
  {
    o = 0;
    s = flex_size((flex_ptr) &authorise);

    while (o < s)
    {
      l = strlen(authorise + o) + 1; /* Length of host string  */

      /* If the host and realm match, return the current offset */

      if (!strcmp(authorise + o, host) && !strcmp(authorise + o + l, realm)) return o;

      /* Skip o past the host string and find the length of the realm string */

      o += l;
      l = strlen(authorise + o) + 1;

      /* Skip o past the realm string and find the length of the user name string */

      o += l;
      l = strlen(authorise + o) + 1;

      /* Skip o past the realm string and find the length of the password string */

      o += l;
      l = strlen(authorise + o) + 1;

      /* Skip o past the password string to get to the next entry */

      o += l;
    }
  }

  /* Failed to find the given host / realm, so return -1 */

  return -1;
}

/*************************************************/
/* authorise_find_user_name()                    */
/*                                               */
/* Returns an offset into the 'authorise' flex   */
/* block at which the user name for a given host */
/* and realm lies, or -1 for not found.          */
/*                                               */
/* Parameters: Pointer to the host string;       */
/*                                               */
/*             Pointer to the realm string.      */
/*                                               */
/* Returns:    Offset into the 'authorise' flex  */
/*             block that the user name lies at, */
/*             or -1 if not found.               */
/*************************************************/

int authorise_find_user_name(char * host, char * realm)
{
  int o;

  o = authorise_find_offset(host, realm);
  if (o < 0) return -1;

  o += strlen(authorise + o) + 1; /* Skip over host  */
  o += strlen(authorise + o) + 1; /* Skip over realm */

  return o;
}

/*************************************************/
/* authorise_find_password()                     */
/*                                               */
/* Returns an offset into the 'authorise' flex   */
/* block at which the password for a given host  */
/* and realm lies, or -1 for not found.          */
/*                                               */
/* Parameters: Pointer to the host string;       */
/*                                               */
/*             Pointer to the realm string.      */
/*                                               */
/* Returns:    Offset into the 'authorise' flex  */
/*             block that the password list at,  */
/*             or -1 if not found.               */
/*************************************************/

int authorise_find_password(char * host, char * realm)
{
  int o;

  o = authorise_find_offset(host, realm);
  if (o < 0) return -1;

  o += strlen(authorise + o) + 1; /* Skip over host      */
  o += strlen(authorise + o) + 1; /* Skip over realm     */
  o += strlen(authorise + o) + 1; /* Skip over user name */

  return o;
}

/*************************************************/
/* authorise_remember()                          */
/*                                               */
/* Stores authorisation data in the 'authorise'  */
/* flex block.                                   */
/*                                               */
/* Parameters: Pointer to the host string;       */
/*                                               */
/*             Pointer to the realm string;      */
/*                                               */
/*             Pointer to the user name string;  */
/*                                               */
/*             Pointer to the password string.   */
/*************************************************/

_kernel_oserror * authorise_remember(char * host, char * realm, char * username, char * password)
{
  int s, n, ok;

  /* Don't want to store something twice... */

  authorise_forget(host, realm);

  /* n is the entry length for the data, including terminators */

  n = strlen(host) + 1 + strlen(realm) + 1 + strlen(username) + 1 +strlen(password) + 1;

  /* Allocate memory for the entry */

  if (authorise) s = flex_size((flex_ptr) &authorise);
  else           s = 0;

  if (s) ok = flex_extend((flex_ptr) &authorise, s + n);
  else   ok = flex_alloc ((flex_ptr) &authorise, n);

  if (!ok) return make_no_fetch_memory_error(11);

  /* Copy the data in place */

  strcpy(authorise + s, host);     s += strlen(authorise + s) + 1;
  strcpy(authorise + s, realm);    s += strlen(authorise + s) + 1;
  strcpy(authorise + s, username); s += strlen(authorise + s) + 1;
  strcpy(authorise + s, password);

  return(NULL);
}

/*************************************************/
/* authorise_forget()                            */
/*                                               */
/* Discards remembered user name and password    */
/* data for a given host and realm.              */
/*                                               */
/* Parameters: Pointer to the host string;       */
/*                                               */
/*             Pointer to the realm string.      */
/*************************************************/

void authorise_forget(char * host, char * realm)
{
  int o;

  o = authorise_find_offset(host, realm);

  /* Only proceed if there does seem to be an entry for this host and realm */

  if (o >= 0)
  {
    int l, s, n;

    s = flex_size((flex_ptr) &authorise);
    n = 0;

    l = strlen(authorise + o) + 1; n += l; o += l; /* Skip host      */
    l = strlen(authorise + o) + 1; n += l; o += l; /* Skip realm     */
    l = strlen(authorise + o) + 1; n += l; o += l; /* Skip user name */
    l = strlen(authorise + o) + 1; n += l; o += l; /* Skip password  */

    /* If the entry above was as long or apparently longer than the flex block */
    /* itself, free the whole block.                                           */

    if (n >= s)
    {
      flex_free((flex_ptr) &authorise);
      authorise = NULL;
    }

    /* Otherwise, move data above the entry down over it and shrink the flex block */

    else
    {
      memmove(authorise + o, authorise + o + n, s - o - n);
      flex_extend((flex_ptr) &authorise, s - n);
    }
  }
}