/****************************************************************************
 * NCSA Mosaic for the X Window System                                      *
 * Software Development Group                                               *
 * National Center for Supercomputing Applications                          *
 * University of Illinois at Urbana-Champaign                               *
 * 605 E. Springfield, Champaign IL 61820                                   *
 * mosaic@ncsa.uiuc.edu                                                     *
 *                                                                          *
 * Copyright (C) 1993, Board of Trustees of the University of Illinois      *
 *                                                                          *
 * NCSA Mosaic software, both binary and source (hereafter, Software) is    *
 * copyrighted by The Board of Trustees of the University of Illinois       *
 * (UI), and ownership remains with the UI.                                 *
 *                                                                          *
 * The UI grants you (hereafter, Licensee) a license to use the Software    *
 * for academic, research and internal business purposes only, without a    *
 * fee.  Licensee may distribute the binary and source code (if released)   *
 * to third parties provided that the copyright notice and this statement   *
 * appears on all copies and that no charge is associated with such         *
 * copies.                                                                  *
 *                                                                          *
 * Licensee may make derivative works.  However, if Licensee distributes    *
 * any derivative work based on or derived from the Software, then          *
 * Licensee will (1) notify NCSA regarding its distribution of the          *
 * derivative work, and (2) clearly notify users that such derivative       *
 * work is a modified version and not the original NCSA Mosaic              *
 * distributed by the UI.                                                   *
 *                                                                          *
 * Any Licensee wishing to make commercial use of the Software should       *
 * contact the UI, c/o NCSA, to negotiate an appropriate license for such   *
 * commercial use.  Commercial use includes (1) integration of all or       *
 * part of the source code into a product for sale or license by or on      *
 * behalf of Licensee to third parties, or (2) distribution of the binary   *
 * code or source code to third parties that need it to utilize a           *
 * commercial product sold or licensed by or on behalf of Licensee.         *
 *                                                                          *
 * UI MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR   *
 * ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED          *
 * WARRANTY.  THE UI SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY THE    *
 * USERS OF THIS SOFTWARE.                                                  *
 *                                                                          *
 * By using or copying this Software, Licensee agrees to abide by the       *
 * copyright law and all other applicable laws of the U.S. including, but   *
 * not limited to, export control laws, and the terms of this license.      *
 * UI shall have the right to terminate this license immediately by         *
 * written notice upon Licensee's breach of, or non-compliance with, any    *
 * of its terms.  Licensee may be held legally responsible for any          *
 * copyright infringement that is caused or encouraged by Licensee's        *
 * failure to abide by the terms of this license.                           *
 *                                                                          *
 * Comments and questions are welcome and can be sent to                    *
 * mosaic-x@ncsa.uiuc.edu.                                                  *
 ****************************************************************************/

/* Copyright (C) 1998, 1999, 2000, 2004, 2005, 2006 - The VMS Mosaic Project */

#include "../config.h"

/* Moved libwww above mosaic.h to avoid VAXC compiler errors, PGE */
#include "../libwww2/htutils.h"
#include "../libwww2/htstring.h"
#ifdef MULTINET
#if defined(__TIME_T) && !defined(__TYPES_LOADED) && !defined(__TYPES)
#define __TYPES_LOADED
#endif /* Different defs in OpenVMS and MultiNet include files, BSN */
#endif /* MULTINET, BSN */
#include "../libwww2/tcp.h"
#include "../libwww2/httcp.h"
#include "../libwww2/htparse.h"
#include "../libwww2/htaccess.h"
#include "../libwww2/html.h"
#include "../libwww2/htext.h"
#include "../libwww2/htlist.h"
#include "../libwww2/htinit.h"
#include "../libwww2/htmime.h"
#include "../libnut/system.h"
#ifdef VAXC
#include "mosaic.h"
#endif /* VAXC, GEC */
#include "../libhtmlw/html.h"

#ifndef VAXC
#include "mosaic.h"
#endif /* VAXC, Move above HTML.H to work around caddr_t problem, GEC */
#include "mo-www.h"
#include "globalhist.h"
#include "gui-dialogs.h"
#include "gui-documents.h"
#include "gui.h"
#include "../libnut/str-tools.h"
#include <ctype.h>
#include <stdio.h>
#include <unixio.h>
#include <Xm/FilesB.h>

#ifdef __GNUC__
#include <stat.h>
#endif

#ifndef VMS
#include <memory.h>	/* For memcpy */
#else
#include <string.h>
#include <unixlib.h>
#if defined(MULTINET) && defined(__DECC)
#define getname decc$getname
#define open    decc$open
#endif
#endif

#ifdef CCI
extern int cci_docommand;
extern int cci_get;
#endif
#define __MAX_HOME_LEN__ 256
#define __SRC__
#include "../libwww2/htaautil.h"
extern int securityType;

extern XtAppContext app_context;
extern mo_window *current_win;

extern int is_uncompressed;

extern Pixmap *IconPix;

#ifndef DISABLE_TRACE
extern int srcTrace;
#endif

/* Binary save filename */
char *saveFileName = NULL;

/* Grumble grumble... */
#if defined(__sgi) && !defined(__STDC__)
#define __STDC__
#endif

#define MO_BUFFER_SIZE 8192

/* Bare minimum. */
struct _HText {
  char *expandedAddress;
  char *simpleAddress;

  /* This is what we should parse and display; it is *not* safe to free. */
  char *htmlSrc;
  /* This is what we should free. */
  char *htmlSrcHead;
  int srcalloc;    /* Amount of space allocated */
  int srclen;      /* Amount of space used */
};

/* Mosaic does NOT use either the anchor or style sheet systems  of libwww. */

HText *HTMainText = 0;                  /* Equivalent of main window */

/* These are used in libwww */
char *HTAppName = "VMS_Mosaic";
char *HTAppVersion;  /* Now set this in gui.c -- mo_do_gui() */
extern char *HTLibraryVersion;

int force_dump_to_file = 0;             /* Hook to force dumping binary data
                                           straight to file named by... */
char *force_dump_filename = NULL;       /* this filename. */

/* From gui-documents.c */
extern int interrupted;

/* From HTAccess.c. */
extern char *use_this_url_instead;

/* From HTTP.c */
extern int do_post;
extern char *post_content_type;
extern char *post_data;
extern int do_put;
extern int put_file_size;
extern FILE *put_fp;
extern char *encrypt_cipher;

/* From HTMIME.c */
extern MIMEInfo MIME_http;

/* Agent Spoofing Public Variables */
#define MAX_AGENTS 51
char **agent;
int selectedAgent = 0;

#ifdef VMS
static char *mail_fnam = NULL;
#endif /* VMS, BSN, GEC */

/****************************************************************************
 * name:    hack_htmlsrc (PRIVATE)
 * purpose: Do really nasty things to a stream of HTML that just got
 *          pulled over from a server.
 * inputs:  
 *   - none (global HTMainText is assumed to contain current
 *           HText object)
 * returns: 
 *   - HTMainText->htmlSrc (char *)
 * remarks: 
 *   This is ugly but it gets the job done.
 *   The job is this:
 *     - Filter out repeated instances of <PLAINTEXT>.
 *     - Discover if <PLAINTEXT> has been improperly applied
 *       to the head of an HTML document (we discover HTML documents
 *       by checking to see if a <TITLE> element is on the next line).
 *     - Same as above but for <HEAD> and <HTML>.
 *     - Filter out leading <! ... >
 *   We advance the character pointer HTMainText->htmlSrc by the
 *   appropriate remark to make adjustments, and keep the original
 *   head of the allocated block of text in HTMainText->htmlSrcHead.
 ****************************************************************************/
static char *hack_htmlsrc(void)
{
  char *loc;

  if (!HTMainText)
      return NULL;

  if (!HTMainText->htmlSrc) {
      HTMainText->htmlSrcHead = NULL;
      return NULL;
  }

  /* Keep pointer to real head of htmlSrc memory block. */
  HTMainText->htmlSrcHead = HTMainText->htmlSrc;
  
  if (HTMainText->srclen > 30) {
      int count = 0;

      /* Remove any nulls, but give up if is garbage */
      while ((count++ < 100) &&
	     strlen(HTMainText->htmlSrc) < (HTMainText->srclen - 1)) {
	  loc = HTMainText->htmlSrc + strlen(HTMainText->htmlSrc);
	  *loc = '\n';
      }
      if (!my_strncasecmp(HTMainText->htmlSrc, "<PLAINTEXT>", 11)) {
          if (!my_strncasecmp(HTMainText->htmlSrc + 11, "<PLAINTEXT>", 11)) {
              HTMainText->htmlSrc += 11;
          } else if (!my_strncasecmp(HTMainText->htmlSrc + 11,
				     "\n<PLAINTEXT>", 12) ||
		   !my_strncasecmp(HTMainText->htmlSrc + 11, "\n<TITLE>", 8) ||
		   !my_strncasecmp(HTMainText->htmlSrc + 11, "\n<HEAD>", 7) ||
                   !my_strncasecmp(HTMainText->htmlSrc + 11, "\n<HTML>", 7) ||
                   !my_strncasecmp(HTMainText->htmlSrc + 11, "\n\n<HTML>", 8) ||
                   !my_strncasecmp(HTMainText->htmlSrc + 11, "\n<BASE",  6) ||
                   !my_strncasecmp(HTMainText->htmlSrc + 11, "\n<!--",  5) ||
	  	   !strncmp(HTMainText->htmlSrc + 11, "\n<!DOCTYPE HTML", 15)) {
              HTMainText->htmlSrc += 12;
          }
      }
      if (!strncmp(HTMainText->htmlSrc, 
                   "<TITLE>Document</TITLE>\n<PLAINTEXT>", 35)) {
          if (!my_strncasecmp(HTMainText->htmlSrc + 35, "\n<TITLE>", 8))
              HTMainText->htmlSrc += 36;
      }
  }

  return HTMainText->htmlSrc;
}


/****************************************************************************
 * name:    doit (PRIVATE)
 * purpose: Given a URL, go fetch information.
 * inputs:  
 *   - char       *url: The URL to fetch.
 *   - char **texthead: Return pointer for the head of the allocated
 *                      text block (which may be different than the
 *                      return text intended for display).
 * returns: 
 *   The text intended for display (char *).
 ****************************************************************************/
static char *doit(char *url, char **texthead)
{
  char *msg;
  int rv;
  mo_window *win = current_win;

  /* Hmmmmmmm... */
  if (HTMainText) {
      free(HTMainText);
      HTMainText = NULL;
  }

  XmxApplyPixmapToLabelWidget(win->logo, IconPix[0]);

  is_uncompressed = 0;

  rv = HTLoadAbsolute(url);

  if (rv == 1) {
      char *txt = hack_htmlsrc();

      if (HTMainText) {
          *texthead = HTMainText->htmlSrcHead;
      } else {
          *texthead = NULL;
      }
      return txt;
  } else if (rv == -1) {
      interrupted = 1;
      *texthead = NULL;
      return NULL;
  }

   /*
   ** Just because we errored out, doesn't mean there isn't markup to 
   ** look at.  For example, an FTP site that doesn't let a user in because
   ** the maximum number of users has been reached often has a message
   ** telling about other mirror sites.  The failed FTP connection returns
   ** a message that is taken care of below.  
   */
   if (HTMainText) {
	char *txt = hack_htmlsrc();

	*texthead = HTMainText->htmlSrcHead;

#ifdef CCI
	if (cci_get) {
	    if (txt) {
		return txt;
	    } else {
		/* Take care of failed local access */
		txt = strdup("<H1>ERROR</H1>"); 
	    }
	}
#endif
	return txt;
   }

  /* Return proper error message if we experienced redirection. */
  if (use_this_url_instead)
      url = use_this_url_instead;
  msg = (char *)malloc((strlen(url) + 200) * sizeof(char));
  sprintf(msg,
	"<H1>ERROR</H1> Requested document (URL %s) could not be accessed.<p>The information server either is not accessible or is refusing to serve the document to you.<p>",
	url);
   
  *texthead = msg;
  securityType = HTAA_UNKNOWN;
  return msg;
}


/****************************************************************************
 * name:    mo_pull_er_over
 * purpose: Given a URL, pull 'er over.
 * inputs:  
 *   - char       *url: The URL to pull over.
 *   - char **texthead: Return pointer to head of allocated block.
 * returns: 
 *   Text to display (char *).
 * remarks: 
 *   
 ****************************************************************************/
char *mo_pull_er_over(char *url, char **texthead)
{
  char *rtext;
  extern int binary_transfer;
 
  /* Always reset the icon interrupt */
  mo_gui_clear_icon();

  if (binary_transfer) {
      force_dump_to_file = 1;
      force_dump_filename = mo_tmpnam(url);
  }
  if (saveFileName)
      free(saveFileName);
  saveFileName = strdup(url);

  if (encrypt_cipher) {
      free(encrypt_cipher);
      encrypt_cipher = NULL;
  }

  rtext = doit(url, texthead);

  if (binary_transfer) {
      force_dump_to_file = 0;
      force_dump_filename = NULL;
  }

  return rtext;
}


char *mo_post_pull_er_over(char *url, char *content_type, char *data, 
                           char **texthead)
{
  char *rtext;
  extern int binary_transfer;

  /* Always reset the icon interrupt */
  mo_gui_clear_icon();

  do_post = 1;
  post_content_type = content_type;
  post_data = data;

  if (binary_transfer) {
      force_dump_to_file = 1;
      force_dump_filename = mo_tmpnam(url);
  }

  rtext = doit(url, texthead);

  if (binary_transfer) {
      force_dump_to_file = 0;
      force_dump_filename = NULL;
  }

  do_post = 0;

  return rtext;
}  


/****************************************************************************
 * name:    mo_pull_er_over_virgin
 * purpose: Given a URL, pull 'er over in such a way that no format
 *          handling takes place and the data gets dumped in the filename
 *          of the calling routine's choice.
 * inputs:  
 *   - char  *url: The URL to pull over.
 *   - char *fnam: Filename in which to dump the received data.
 * returns: 
 *   mo_succeed on success; mo_fail otherwise.
 * remarks: 
 *   This routine is called when we know there's data out there we
 *   want to get and we know we just want it dumped in a file, no
 *   questions asked, by the WWW library.  Appropriate global flags
 *   are set to make this happen.
 *   This must be made cleaner.
 ****************************************************************************/
mo_status mo_pull_er_over_virgin(char *url, char *fnam)
{
  int rv;

  if (mo_gui_check_icon(0)) {
      force_dump_to_file = 0;
      interrupted = 1;
      return mo_fail;
  }
  /* Force dump to file. */
  force_dump_to_file = 1;
  force_dump_filename = fnam;
  if (saveFileName)
      free(saveFileName);

  saveFileName = strdup(url);

  is_uncompressed = 0;

  rv = HTLoadAbsolute(url);

  if (rv == 1) {
      force_dump_to_file = 0;
      return mo_succeed;
  } else if (rv == -1) {
      force_dump_to_file = 0;
      interrupted = 1;
      return mo_fail;
  } else {
      force_dump_to_file = 0;
      return mo_fail;
  }
}


mo_status mo_re_init_formats(void)
{
  HTReInit();
  return mo_succeed;
}

/* ------------------------------------------------------------------------ */

HText *HText_new(void)
{
  HText *htObj = (HText *)malloc(sizeof(HText));

  htObj->expandedAddress = NULL;
  htObj->simpleAddress = NULL;
  htObj->htmlSrc = NULL;
  htObj->htmlSrcHead = NULL;
  htObj->srcalloc = 0;
  htObj->srclen = 0;

  /* Free the struct but not the text, as it will be handled
     by Mosaic proper -- apparently. */
  if (HTMainText)
      free(HTMainText);

  HTMainText = htObj;

  return htObj;
}

void HText_free(HText *me)
{
  if (me) {
      if (me->htmlSrcHead)
          free(me->htmlSrcHead);
      free(me);
  }
  return;
}

void HText_beginAppend(HText *text)
{
  HTMainText = text;
  return;
}

void HText_endAppend(HText *text)
{
  if (text)
      HText_appendCharacter(text, '\0');

  HTMainText = text;
  return;
}

void HText_doAbort(HText *text)
{
  /* Clean up -- we want to free htmlSrc here because htmlSrcHead
     doesn't get assigned until hack_htmlsrc, and by the time we
     reach that, this should never be called. */
  if (text) {
      if (text->htmlSrc)
          free(text->htmlSrc);
      text->htmlSrc = NULL;
      text->htmlSrcHead = NULL;
      text->srcalloc = 0;
      text->srclen = 0;
  }
  return;
}

void HText_clearOutForNewContents(HText *text)
{
  if (text) {
      if (text->htmlSrc)
          free(text->htmlSrc);
      text->htmlSrc = NULL;
      text->htmlSrcHead = NULL;
      text->srcalloc = 0;
      text->srclen = 0;
  }
  return;
}

static void new_chunk(HText *text)
{
  if (text->srcalloc == 0) {
      text->htmlSrc = (char *)malloc(MO_BUFFER_SIZE);
      text->htmlSrc[0] = '\0';
  } else {
      text->htmlSrc = (char *)realloc(text->htmlSrc,
				      text->srcalloc + MO_BUFFER_SIZE);
  }

  text->srcalloc += MO_BUFFER_SIZE;

  return;
}

void HText_appendCharacter(HText *text, char ch)
{
  if (!text)
      return;

  if (text->srcalloc < text->srclen + 1)
      new_chunk(text);
  
  text->htmlSrc[text->srclen++] = ch;

  return;
}

void HText_appendText(HText *text, char *str)
{
  int len;

  if (!str || !text)
      return;

  len = strlen(str);

  while (text->srcalloc < text->srclen + len + 1)
      new_chunk(text);

  memcpy((text->htmlSrc + text->srclen), str, len);

  text->srclen += len;
  text->htmlSrc[text->srclen] = '\0';

  return;
}

void HText_appendBlock(HText *text, char *data, int len)
{
  if (!data || !text)
      return;
  
  while (text->srcalloc < text->srclen + len + 1)
      new_chunk(text);

  memcpy((text->htmlSrc + text->srclen), data, len);

  text->srclen += len;
  text->htmlSrc[text->srclen] = '\0';

  return;
}

void HText_appendParagraph(HText *text)
{
  /* Boy, talk about a misnamed function. */
  char *str = " <p> \n";

  HText_appendText(text, str);

  return;
}

void HText_beginAnchor(HText *text, char *anc)
{
  HText_appendText(text, "<A HREF=\"");
  HText_appendText(text, anc);
  HText_appendText(text, "\">");
  return;
}

void HText_endAnchor(HText *text)
{
  HText_appendText(text, "</A>");
  return;
}

void HText_dump(HText *me)
{
  return;
}

char *HText_getText(HText *me)
{
  if (me) {
      return me->htmlSrc;
  } else {
      return NULL;
  }
}

char **HText_getPtrToText(HText *me)
{
  if (me) {
      return &(me->htmlSrc);
  } else {
      return NULL;
  }
}

int HText_getTextLength(HText *me)
{
  if (me) {
      return me->srclen;
  } else {
      return 0;
  }
}


/****************************************************************************
 * name:    fileOrServer
 * purpose: Given a string, checks to see if it can stat it. If so, it is
 *   assumed the user expects to open the file, not a web site. If not, we
 *   assume it is supposed to be a server and prepend the default protocol.
 * inputs:  
 *   - char    *url: URL to canonicalize.
 * returns: 
 *   The canonical representation of the URL.
 * remarks: 
 *   Written by spowers@ncsa.uiuc.edu
 ****************************************************************************/
static char *fileOrServer(char *url)
{
#if (stat != decc$stat) || !defined(MULTINET)
    struct stat buf;
#else
#undef stat
    struct stat buf;
#define stat decc$stat
#endif /* VMS MultiNet work around, GEC */
    char *xurl;
#ifdef VMS
    int fd;
    char fname[256];
    char *ptr;
#endif
    static int init = 0;
    static char *defproto;

    if (!init) {
	init = 1;
	defproto = get_pref_string(eDEFAULT_PROTOCOL);
    }

    /* At this point we know the URL specified is of the form:
     *   shire.ncsa.uiuc.edu[:PORT]/path/to/something
     * or is a VMS file spec 
     */
    if (!stat(url, &buf)) {  /* It's a file and we have access */
#ifdef VMS
	/* Get file spec in UNIX syntax */
	fd = open(url, O_RDONLY, 0);
	if (getname(fd, fname, 0)) {
	    /* Strip off version, unless explicity given */
	    if (!strchr(url, ';') && (ptr = strchr(fname, '.'))) {
		if (ptr = strchr(++ptr, '.'))
		    *ptr = '\0';
	    }
	    xurl = mo_url_canonicalize_local(fname);
	} else {
	    xurl = mo_url_canonicalize_local(url);
	}
	close(fd);
#else
	xurl = mo_url_canonicalize_local(url);
#endif
    } else if (!defproto || !*defproto) {
	xurl = (char *)calloc(strlen(url) + 15, sizeof(char));
	sprintf(xurl, "http://%s", url);
    } else {
	xurl = (char *)calloc(strlen(url) + strlen(defproto) + 10,
			      sizeof(char));
	sprintf(xurl, "%s://%s", defproto, url);
    }

    return(xurl);
}


/****************************************************************************
 * name:    mo_url_prepend_protocol
 * purpose: To prepend the proper protocol to the url if it is not present.
 * inputs:  
 *   - char    *url: URL to canonicalize.
 * returns: 
 *   The canonical representation of the URL.
 * remarks: 
 *   Contributed by martin@gizmo.lut.ac.uk, modified by spowers@ncsa.uiuc.edu
 ****************************************************************************/
char *mo_url_prepend_protocol(char *url)
{
    char *xurl;
    static int check1 = -1;
    static int check2;

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

    if (check1 == -1) {     /* Avoid repeating routine call */
	check1 = get_pref_boolean(eEXPAND_URLS);
	check2 = get_pref_boolean(eEXPAND_URLS_WITH_NAME);
    }
    if (!check1) {
	if (!strchr(url, ':')) {
	    /* No colon found, treat as file */
	    xurl = mo_url_canonicalize_local(url);
	} else {
	    /* It's prolly a real url */
	    xurl = strdup(url);
	}
    } else if (my_strncasecmp(url, "mailto:", 7) &&
	       my_strncasecmp(url, "news:", 5) &&
	       my_strncasecmp(url, "about:", 6) &&
	       my_strncasecmp(url, "cookiejar:", 10) &&
	       !strstr(url, "://")) {	/* No protocol specified, default */
	if (check2) {
	    if (!my_strncasecmp(url, "www.", 4)) {
		xurl = (char *)malloc(strlen(url) + (8 * sizeof(char)));
		sprintf(xurl, "http://%s", url);

	    } else if (!my_strncasecmp(url, "ftp.", 4)) {
		xurl = (char *)malloc(strlen(url) + (7 * sizeof(char)));
		sprintf(xurl, "ftp://%s", url);

	    } else if (!my_strncasecmp(url, "gopher.", 7)) {
		xurl = (char *)malloc(strlen(url) + (10 * sizeof(char)));
		sprintf(xurl, "gopher://%s", url);

	    } else if (!my_strncasecmp(url, "wais.", 5)) {
		xurl = (char *)malloc(strlen(url) + (8 * sizeof(char)));
		sprintf(xurl, "wais://%s", url);

	    } else {
		xurl = fileOrServer(url);
	    }
	} else {
	    xurl = fileOrServer(url);
	}
    } else {	/* Protocol was specified */
	xurl = strdup(url);
    }

    return(xurl);
}


/****************************************************************************
 * name:    mo_url_canonicalize
 * purpose: Turn a URL into its canonical form, based on the previous
 *          URL in this context (if appropriate).  
 *          INTERNAL ANCHORS ARE STRIPPED OFF.
 * inputs:  
 *   - char    *url: URL to canonicalize.
 *   - char *oldurl: The previous URL in this context.
 * returns: 
 *   The canonical representation of the URL.
 * remarks: 
 *   All we do is call HTParse.
 ****************************************************************************/
char *mo_url_canonicalize(char *url, char *oldurl)
{
  return HTParse(url, oldurl,
                 PARSE_ACCESS | PARSE_HOST | PARSE_PATH | PARSE_PUNCTUATION);
}


/****************************************************************************
 * name:    mo_url_canonicalize_keep_anchor
 * purpose: Turn a URL into its canonical form, based on the previous
 *          URL in this context (if appropriate).  
 *          INTERNAL ANCHORS ARE *NOT* STRIPPED OFF.
 * inputs:  
 *   - char    *url: URL to canonicalize.
 *   - char *oldurl: The previous URL in this context.
 * returns: 
 *   The canonical representation of the URL.
 * remarks: 
 *   All we do is call HTParse.
 ****************************************************************************/
char *mo_url_canonicalize_keep_anchor(char *url, char *oldurl)
{
  char *newurl;

  /* We KEEP anchor information already present in url,
   * but NOT in oldurl. */
  oldurl = HTParse(oldurl, "", PARSE_ACCESS | PARSE_HOST | PARSE_PATH |
                   PARSE_PUNCTUATION);
  newurl = HTParse(url, oldurl, PARSE_ACCESS | PARSE_HOST | PARSE_PATH |
                   PARSE_PUNCTUATION | PARSE_ANCHOR);
  /* We made a new copy of oldurl, so free it. */
  free(oldurl);
  return newurl;
}


/****************************************************************************
 * name:    mo_url_extract_anchor
 * purpose: Given a URL (presumably in canonical form), extract
 *          the internal anchor, if any.
 * inputs:  
 *   - char *url: URL to use.
 * returns: 
 *   Internal anchor, if one exists in the URL; else NULL string.
 * remarks: 
 *   
 ****************************************************************************/
char *mo_url_extract_anchor(char *url)
{
  return HTParse(url, "", PARSE_ANCHOR);
}


/****************************************************************************
 * name:    mo_url_extract_access
 * purpose: Given a URL (presumably in canonical form), extract
 *          the access method, if any.
 * inputs:  
 *   - char *url: URL to use.
 * returns: 
 *   Access method, if one exists in the URL; else NULL.
 * remarks: 
 *   
 ****************************************************************************/
char *mo_url_extract_access(char *url, char *oldurl)
{
  return HTParse(url, oldurl, PARSE_ACCESS);
}


char *mo_url_canonicalize_local(char *url)
{
  /* Convex OS apparently sucks. */
#ifdef CONVEX
  char blah[129];
  char *cwd = getcwd(blah, 128);
#else
#ifndef VMS
  char *cwd = getcwd(NULL, 128);
#else
  /* Force it to return UNIX syntax */
#ifndef __GNUC__
  char *cwd = getcwd(NULL, 128, 0);
#else
  char *cwd = (char *)getcwd(NULL, 128, 0);
#endif /* GNU C is strange, GEC */
#endif
#endif
  char *tmp;

  if (!url)
      return NULL;

  tmp = (char *)malloc((strlen(url) + strlen(cwd) + 32));
  if (url[0] == '/') {
      sprintf(tmp, "file://localhost%s", url);
  } else {
#ifndef VMS
      sprintf(tmp, "file://localhost%s/%s", cwd, url);
#else
      if (strchr(url, ':')) {
          /* Have a device spec */
          sprintf(tmp, "file://localhost/%s\0", url);
      } else {
          sprintf(tmp, "file://localhost%s/%s\0", cwd, url);
      }
#endif
  }

  /* Sigh... */
#ifndef CONVEX
  free(cwd);
#endif
  
  return tmp;
}


/****************************************************************************
 * name:    mo_tmpnam
 * purpose: Make a temporary, unique filename.
 * inputs:  
 *   none
 * returns: 
 *   The new temporary filename.
 * remarks: 
 *   We make up an unique filename and use the preference
 *   TMP_DIRECTORY, if it has a value, for the directory.
 ****************************************************************************/
#ifndef L_tmpnam
#define L_tmpnam 32
#endif
char *mo_tmpnam(char *url)
{
#ifdef CCI
  extern void MoCCIAddFileURLToList(char *, char *);
#endif
  char *tmp = (char *)malloc(sizeof(char) * (L_tmpnam + 32));
  static char *tmp_dir;
  static char *unique;
  static int init = 0;
  static int count = 0;

  if (!tmp) {
      fprintf(stderr, "Unable to get storage for tmp name\n");
      return NULL;
  }

  /* Get unique string for this process */
  if (!init) {
      unique = strdup(tmpnam(NULL));
      unique += 3;
      tmp_dir = get_pref_string(eTMP_DIRECTORY);
      init = 1;
  }
  count++;

  sprintf(tmp, "MOSAIC-TMP%d_%s", count, unique);

  if (!tmp_dir) {
      /* Fast path. */
#ifdef CCI
      if (url)
	  MoCCIAddFileURLToList(tmp, url);
#endif
      return tmp;
  } else {
      /* OK, reconstruct to go in the directory of our choice. */
      char *oldtmp = tmp;
      int i;

#ifndef VMS
      /* Start at the back and work our way forward. */
      for (i = strlen(oldtmp) - 1; i >= 0; i--) {
          if (oldtmp[i] == '/')
              goto found_it;
      }
      
      /* No luck, just punt. */
#ifdef CCI
      if (url)
	  MoCCIAddFileURLToList(tmp, url);
#endif
      return tmp;
#else
      i = 0;
#endif

    found_it:
      tmp = (char *)malloc(sizeof(char) * (strlen(tmp_dir) + 
                                           strlen(&(oldtmp[i])) + 8));
      if (!tmp) {
  	  fprintf(stderr, "Unable to get second storage for tmp name\n");
	  return oldtmp;
      }
#ifndef VMS
      if (tmp_dir[strlen(tmp_dir) - 1] == '/') {
          /* Trailing slash in tmp_directory spec. */
          sprintf(tmp, "%s%s", tmp_dir, &(oldtmp[i]) + 1);
      } else {
          /* No trailing slash. */
          sprintf(tmp, "%s%s", tmp_dir, &(oldtmp[i]));
      }
#else
      if ((tmp_dir[strlen(tmp_dir) - 1] == ']') ||
          (tmp_dir[strlen(tmp_dir) - 1] == ':')) {
          sprintf(tmp, "%s%s", tmp_dir, oldtmp);
      } else {
          sprintf(tmp, "%s:%s", tmp_dir, oldtmp);
      }
#endif

#ifdef CCI
      MoCCIAddFileURLToList(tmp, url);
#endif
      free(oldtmp);
      return tmp;
  }
}


/* ------------------------------ dumb stuff ------------------------------ */

/* Grumble grumble... */
#if defined(ultrix) || (defined(VMS) && (!defined(__GNUC__) || defined(vax)) && (!defined(__VMS_VER) || (__VMS_VER < 70000000))) || defined(NeXT) || defined(M4310)
char *strdup(char *str)
{
  char *dup;

  if (!str)
      return NULL;

  dup = (char *)malloc(strlen(str) + 1);
  if (!dup)
      return NULL;

  dup = strcpy(dup, str);

  return dup;
}
#endif


/* Error from the library */
void application_error(char *str, char *title)
{
  XmxMakeErrorDialogWait(current_win->base, app_context, str, title, "OK");

  return;
}

/* Warning from the library */
void application_warning(char *str, char *title)
{
  XmxMakeWarningDialogWait(current_win->base, app_context, str, title, "OK");

  return;
}

/* Feedback from the library. */
void application_user_feedback(char *str)
{
  extern Widget toplevel;

  XmxMakeInfoDialog(toplevel, str, "VMS Mosaic: Application Feedback");
  XmxManageRemanage(Xmx_w);
}

void application_user_info_wait(char *str)
{

  XmxMakeInfoDialogWait(current_win->base, app_context, str,
			"VMS Mosaic: Application Feedback", "OK");
}

char *prompt_for_string(char *questionstr)
{

  return XmxModalPromptForString(current_win->base, app_context,
                                 questionstr, "OK", "Cancel");
}

char *prompt_for_password(char *questionstr)
{

  return XmxModalPromptForPassword(current_win->base, app_context,
                                   questionstr, "OK", "Cancel");
}

int prompt_for_yes_or_no(char *questionstr)
{

  return XmxModalYesOrNo(current_win->base, app_context,
                         questionstr, "Yes", "No");
}

char *mo_get_html_return(char **texthead)
{
  char *txt = hack_htmlsrc();

  *texthead = HTMainText->htmlSrcHead;
  return txt;
}


/* Convert all newlines and CRs to spaces, and remove leading whitespace. */
/* Returns True if converted any newlines. */
Boolean mo_convert_newlines_to_spaces(char *str)
{
  int i;
  char *tptr;
  Boolean converted = False;

  if (!str)
      return False;

  for (i = 0; i < strlen(str); i++) {
      if ((str[i] == '\n') || (str[i] == '\r')) {
          str[i] = ' ';
          converted = True;
      }
  }

  tptr = str;
  while (*tptr && (isspace((int)(*tptr))))
      tptr++;

  if (tptr != str)
      memcpy(str, tptr, (strlen(tptr) + 1));

  return converted;
}

/* ---------------------------- escaping code ----------------------------- */

static unsigned char isAcceptable[96] =
/*   0 1 2 3 4 5 6 7 8 9 A B C D E F */
{    0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,	/* 2x   !"#$%&'()*+,-./	 */
     1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,	/* 3x  0123456789:;<=>?	 */
     1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,	/* 4x  @ABCDEFGHIJKLMNO  */
     1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,	/* 5x  PQRSTUVWXYZ[\]^_	 */
     0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,	/* 6x  `abcdefghijklmno	 */
     1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0 };	/* 7x  pqrstuvwxyz{\}~	DEL */

#define MO_HEX(i) (i < 10 ? '0' + i : 'A' + i - 10)

/* The string returned from here, if any, can be free'd by caller. */
char *mo_escape_part(char *part)
{
  char *q;
  char *p;		/* Pointers into keywords */
  char *escaped;

  if (!part)
      return NULL;

  escaped = (char *)malloc(strlen(part) * 3 + 1);
  
  for (q = escaped, p = part; *p != '\0'; p++) {
      /*
       * This makes sure that values 128 and over don't get
       * converted to negative values.
       */
      int c = (int)((unsigned char)(*p));

      if (*p == ' ') {
          *q++ = '+';
      } else if (c >= 32 && c <= 127 && isAcceptable[c - 32]) {
          *q++ = *p;
      } else {
          *q++ = '%';
          *q++ = MO_HEX(c / 16);
          *q++ = MO_HEX(c % 16);
      }
  }
  
  *q = 0;
  
  return escaped;
}


/* Create new string with no LFs, CRs, tabs, leading and tailing spaces, */
/* and with other spaces and controls escaped. */
char *mo_clean_and_escape_url(char *url, int free_it)
{
  char *q;
  char *p;
  char *escaped;

  if (!url)
      return NULL;

  escaped = (char *)malloc((strlen(url) * 3) + 1);

  /* Remove tailing spaces */
  q = url + strlen(url) - 1;
  while ((q >= url) && (*q == ' '))
      *q-- = '\0';

  p = url;

  /* Skip over leading spaces */
  while (*p == ' ')
      p++;

  for (q = escaped; *p != '\0'; p++) {
      /*
       * This makes sure that values 128 and over don't get
       * converted to negative values.
       */
      int c = (int)((unsigned char)(*p));

      if (c >= 33 && c <= 127) {
          *q++ = *p;
      } else if ((*p != '\n') && (*p != '\r') && (*p != '\t')) {
          *q++ = '%';
          *q++ = MO_HEX(c / 16);
          *q++ = MO_HEX(c % 16);
      }
  }
  
  *q = 0;

  if (free_it)
      free(url);
  
  return escaped;
}


static char mo_from_hex(char c)
{
  return ((c >= '0' && c <= '9') ? (c - '0') : 
          ((c >= 'A' && c <= 'F') ? (c - 'A' + 10) : 
           (c - 'a' + 10)));
}

char *mo_unescape_part(char *str)
{
  char *p = str, *q = str;

  while (*p) {
      /* Plus's turn back into spaces. */
      if (*p == '+') {
          *q++ = ' ';
          p++;
      } else if (*p == '%') {
          p++;
          if (*p) 
              *q = mo_from_hex(*p++) * 16;
          if (*p) 
              *q += mo_from_hex(*p++);
          q++;
      } else {
          *q++ = *p++; 
      }
  }
  
  *q++ = 0;
  return str;
}


/* ---------------------------- Agent Spoofing ---------------------------- */

/*
 * Agent Spoofing is simple.  Mosaic's real agent is always a member of the
 * menu.  Any more than that, you can add to the file in your home directory
 * called ".mosaic-spoof-agents".
 */
static int readAgents(int numAgents)
{
    FILE *fp;
    char fname[BUFSIZ], buf[512];
    char *homedir, *ptr;

    if ((get_home(&homedir) != 0) || !homedir) {
	fprintf(stderr, "Agents: Could not get your home directory.\n");
	return numAgents;
    }
#ifndef VMS  /* PGE */
    sprintf(fname, "%s/.mosaic-spoof-agents", homedir);
#else
    sprintf(fname, "%smosaic-spoof-agents", homedir);
#endif
    free(homedir);

    if (!(fp = fopen(fname, "r")))
	return numAgents;

    while (!feof(fp)) {
	fgets(buf, 511, fp);
	if (feof(fp))
	    break;
	if (*buf && *buf != '#') {
	    buf[strlen(buf) - 1] = '\0';
	    for (ptr = buf; *ptr && isspace(*ptr); ptr++)
		;
	    if (*ptr == '+') {	/* This is to be the default */
		if (*(ptr + 1)) {
		    agent[numAgents] = strdup(ptr + 1);
		    selectedAgent = numAgents;
		} else {
		    continue;
		}
	    } else if (*ptr) {
		agent[numAgents] = strdup(ptr);
	    } else {
		continue;
	    }
	    numAgents++;
	    if (numAgents == MAX_AGENTS) {	/* Limit reached */
		fprintf(stderr,
		        "WARNING: Hard limit reached for agent spoof file.\n");
		break;
	    }
	}
    }
    fclose(fp);

    return numAgents;
}


int loadAgents(void)
{
  char buf[512];
  int numAgents;

  agent = (char **)calloc(MAX_AGENTS + 1, sizeof(char *));
  sprintf(buf, "%s/%s  libwww/%s",
	  HTAppName ? HTAppName : "unknown",
	  HTAppVersion ? HTAppVersion : "0.0",
	  HTLibraryVersion);
  agent[0] = strdup(buf);
  agent[1] = strdup(
	"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; VMS Mosaic)");
  agent[2] = strdup(
	"Mozilla/5.0 (X11; U; VMS Mosaic; en-US; rv:1.7.8) Gecko/20050511 Firefox/1.0.4");
  agent[3] = strdup("Lynx/2.7 libwww-FM/2.14 (VMS Mosaic)");
  agent[4] = strdup(
	"Mozilla/5.0 (X11; U; OpenVMS Alpha; en-US; rv:1.7) Gecko/20040621)");
  agent[5] = strdup("Mozilla/4.0 (VMS Mosaic)");
  numAgents = 6;

  numAgents = readAgents(numAgents);

  return numAgents;
}


#ifdef TIMING
/* ----------------------------- Timing stuff ----------------------------- */

#ifndef VMS
#include <sys/types.h>
#include <sys/times.h>
#include <sys/param.h>
#else
#include <time.h>
#endif /* VMS, BSN*/

/* Time globals. */
#ifndef VMS
static struct tms tbuf;
static int gtime;
#else
static time_t tbuf;
static time_t gtime;
#define times time
#endif /* VMS, BSN*/

void StartClock(void) 
{
  gtime = times(&tbuf);
  
  return;
}

void StopClock()
{
#ifndef VMS
  int donetime;
#else
  time_t donetime;
#endif /* VMS, BSN*/

  donetime = times(&tbuf);

#ifndef DISABLE_TRACE
  if (srcTrace)
      fprintf(stderr, "Elapsed time %d\n", donetime - gtime);
#endif

  return;
}
#endif


/* ------------------------- upload stuff -------------------------- */
static char *mo_put_er_over(char *url, char **texthead)
{
    char *rtext;

    do_put = do_post = 1;

    if (saveFileName)
	free(saveFileName);
    saveFileName = strdup(url);

    rtext = doit(url, texthead);

    do_put = do_post = 0;

    return(rtext);
}  


static mo_status upload(mo_window *win, FILE *fp, char *fname)
{
    char *put_url, *xurl;
    char *newtext;
    char *newtexthead = NULL;
    int res = mo_fail;

    if (!win)
	return(res);

    put_url = prompt_for_string("Enter the URL to upload the file as:");
    if (!put_url)
	return(res);

    xurl = mo_url_prepend_protocol(put_url);
    free(put_url);
    put_url = xurl;

    fseek(fp, 0, SEEK_END);
    put_file_size = ftell(fp);
    rewind(fp);
    put_fp = fp;

    if (win->target_anchor)
	free(win->target_anchor);
    win->target_anchor = NULL;

    mo_set_current_cached_win(win);

    newtext = mo_put_er_over(put_url, &newtexthead);

    if (newtext && strncmp(newtext, "<H1>ERROR<H1>", 10) &&
	strncmp(newtext, "<HEAD><TITLE>404 Not Found</TITLE></HEAD>", 28))
	res = mo_succeed;

    /* Check for redirect URL */
    if (use_this_url_instead) {
	mo_here_we_are_son(put_url);
	free(put_url);
	put_url = use_this_url_instead;

	/* Go get another target_anchor. */
	if (win->target_anchor)
	    free(win->target_anchor);
	win->target_anchor = NULL;
    }

    if (newtext) {
	mo_do_window_text(win, put_url, newtext, newtexthead,
			  1, NULL, MIME_http.last_modified,
			  MIME_http.expires, MIME_http.charset);
	HTMLTraverseTabGroups(win->view, XmTRAVERSE_HOME);
    }

    if (win->current_node)
	mo_gui_check_security_icon(win->current_node->authType);

    free(put_url);

    mo_gui_done_with_icon();

    return(res);
}


mo_status mo_upload_window(mo_window *win, char *fname)
{
    char *efname = (char *)malloc(sizeof(char) * (__MAX_HOME_LEN__ * 2));
    FILE *fp;
    int res;

    if (pathEval(efname, fname) < 0) {
#ifndef DISABLE_TRACE
	if (srcTrace)
	    fprintf(stderr, "Error in evaluating the path.\n");
#endif
    }
    if (!(fp = fopen(efname, "r"))) {
	char *buf, *final;
	char tmpbuf[80];
	int final_len;

#ifdef CCI
	/* Don't display dialog if command issued by cci application */
	if (cci_docommand)
	    return(mo_fail);
#endif
	buf = my_strerror(errno);
	if (!buf || !*buf || !strcmp(buf, "Error 0")) {
	    sprintf(tmpbuf, "Unknown Error");
	    buf = tmpbuf;
	}
	final_len = 30 + ((!efname || !*efname ? 3 : strlen(efname)) + 13) +
		    15 + (strlen(buf) + 3);
	final = (char *)calloc(final_len, sizeof(char));

	sprintf(final,
	        "\nUnable to upload document:\n   %s\n\nUpload Error:\n   %s\n",
		(!efname || !*efname ? " " : efname), buf);

	application_error(final, "Upload Error");

	if (final) {
	    free(final);
	    final = NULL;
	}
	free(efname);

	return(mo_fail);
    }

    res = upload(win, fp, efname);
    fclose(fp);
    free(efname);

    return(res);
}


static XmxCallback(upload_win_cb)
{
    char *fname = (char *)malloc(sizeof(char) * 128);
    mo_window *win = mo_fetch_window_by_id(XmxExtractUniqid((int)client_data));

    XtUnmanageChild(win->upload_win);
  
    XmStringGetLtoR(((XmFileSelectionBoxCallbackStruct *)call_data)->value,
		    XmSTRING_DEFAULT_CHARSET, &fname);

    mo_upload_window(win, fname);

    free(fname);
  
    return;
}


mo_status mo_post_upload_window(mo_window *win)
{
    XmxSetUniqid(win->id);

    if (!win->upload_win) {
	Widget frame, workarea;

	win->upload_win = XmxMakeFileSBDialog(win->base,
					      "VMS Mosaic: Upload Document",
					      "Name of document to upload:",
					      upload_win_cb, 0);
	/* This makes a frame as a work area for the dialog box. */
	XmxSetArg(XmNmarginWidth, 5);
	XmxSetArg(XmNmarginHeight, 5);
	frame = XmxMakeFrame(win->upload_win, XmxShadowEtchedIn);
	workarea = XmxMakeForm(frame);
    } else {
	XmFileSelectionDoSearch(win->upload_win, NULL);
    }
  
    XmxManageRemanage(win->upload_win);

    return mo_succeed;
}
