This repository has been archived on 2024-01-04. You can view files and clone it, but cannot push or open issues or pull requests.
ncsa-mosaic/src/pan.c

915 lines
25 KiB
C

/****************************************************************************
* 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. *
****************************************************************************/
#include "../config.h"
#include "mosaic.h"
#include "pan.h"
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "libhtmlw/HTML.h"
#include <libutils/flatpakCheck.h>
/*for memset*/
#include <memory.h>
#ifndef S_IRUSR
#define S_IRUSR 400
#define S_IWUSR 200
#define S_IXUSR 100
#endif
#ifndef DISABLE_TRACE
extern int srcTrace;
#endif
/* ------------------------- Personal Annotations ------------------------- */
/*
This file contains support for personal annotations (pans).
We have a directory called ~/personal-annotations (by default).
In this directory is a file called LOG.
The LOG file consists of the following:
ncsa-mosaic-personal-annotation-log-format-1 [cookie]
Personal [name of log]
url # # # # [first word is url,
subsequent words are
numbers of annotations
matching this url]
[1-line sequence for single document repeated as necessary]
...
Thus, a given document can have 0 or more annotation ID's associated
with it. Each annotation is named pan-#.html and is stored in
the same directory as LOG.
Annotations are formatted like this:
<ncsa-annotation-format-1> [cookie]
<title>This is the title.</title> [title]
<h1>This is the title, again.</h1> [line skipped; expected
to be title]
<address>This is the author.</address> [author]
<address>This is the date.</address> [date of annotation/
modification]
_______________________ [ignored separator]
<pre> [line skipped; expected
to be pre]
[arbitrary text] [until end of message]
So long as the annotation remains in this format, you will be
able to operate on it later with the 'Edit This Annotation' option
in the X Mosaic menubar. Else, it will just be another document.
*/
/* ------------------------------------------------------------------------ */
/* --------------------------- GLOBAL PAN LIST ---------------------------- */
/* ------------------------------------------------------------------------ */
#define NCSA_PAN_LOG_FORMAT_COOKIE_ONE \
"ncsa-mosaic-personal-annotation-log-format-1"
#define PAN_LOG_FILENAME "LOG"
#define PAN_ANNOTATION_PREFIX "PAN-"
#define NCSA_ANNOTATION_FORMAT_ONE \
"<ncsa-annotation-format-1>"
#define HASHSIZE 100
#define MAX_PANS_PER_HREF 20
/* Cached string for home directory. */
static char *home;
typedef struct entry
{
char *href;
int num_pans;
int an[MAX_PANS_PER_HREF];
struct entry *next;
} entry;
typedef struct bucket
{
entry *head;
int count;
} bucket;
/* The largest ID for any pan known so far.
The next pan created will have id max_pan_id + 1. */
static int max_pan_id = 0;
static bucket hash_table[HASHSIZE];
static int locate_id (int id, entry **lptr);
static void ensure_pan_directory_exists (void);
static int hash_url (char *url);
static void dump_bucket_counts (void);
static entry *new_entry (int buck, char *href);
static entry *fetch_entry (char *href);
static void add_an_to_entry (entry *l, int an);
static void remove_an_from_entry (entry *l, int an);
static void mo_read_pan_file (char *filename);
static mo_status mo_init_pan (void);
static mo_status mo_write_pan (int id, char *title, char *author, char *text);
static char *smart_append (char *s1, char *s2);
static char *extract_meat (char *s, int offset);
static int hash_url (char *url)
{
int len, i, val;
if (!url)
return 0;
len = strlen (url);
val = 0;
for (i = 0; i < 10; i++)
val += url[(i * val + 7) % len];
return val % HASHSIZE;
}
static void dump_bucket_counts (void)
{
int i;
#ifndef DISABLE_TRACE
if (srcTrace) {
for (i = 0; i < HASHSIZE; i++)
fprintf (stderr, "Bucket %03d, count %03d\n", i, hash_table[i].count);
}
#endif
return;
}
/* Presumably href isn't already in the bucket. */
static entry *new_entry (int buck, char *href)
{
bucket *bkt = &(hash_table[buck]);
entry *l;
l = (entry *)malloc (sizeof (entry));
l->href = strdup (href);
l->num_pans = 0;
/* bzero ((void *)(l->an), MAX_PANS_PER_HREF * 4);*/
memset((void *)(l->an), 0, MAX_PANS_PER_HREF * 4);
l->next = NULL;
if (bkt->head == NULL)
bkt->head = l;
else
{
l->next = bkt->head;
bkt->head = l;
}
bkt->count += 1;
return l;
}
/* If an entry already exists for an href, then return the entry.
Else, return NULL. */
static entry *fetch_entry (char *href)
{
int hash = hash_url (href);
entry *l;
if (hash_table[hash].count)
for (l = hash_table[hash].head; l != NULL; l = l->next)
{
if (!strcmp (l->href, href))
return l;
}
return NULL;
}
/* Given an entry and an annotation id, do the right thing
to the entry. */
static void add_an_to_entry (entry *l, int an)
{
if (l->num_pans == MAX_PANS_PER_HREF)
return;
l->an[l->num_pans] = an;
l->num_pans++;
}
/* Given an entry and an annotation id, remove the annotation
from the entry. */
static void remove_an_from_entry (entry *l, int an)
{
int i, place;
for (i = 0; i < l->num_pans; i++)
{
if (l->an[i] == an)
{
place = i;
goto ok;
}
}
/* Couldn't find an in the list of annoations. */
return;
ok:
/* Found an in the list of annotations. */
l->an[place] = 0;
/* If place is 3 and num_pans is 6,
then i goes from 4 to 5; 3 gets 4's value and then 4 gets 5's. */
for (i = place + 1; i < l->num_pans; i++)
l->an[i-1] = l->an[i];
l->num_pans--;
/* Don't have to remove an empty entry, since an empty entry won't
be written out upon exit. */
return;
}
/* For a given ID, look through the hash table, find the corresponding
entry, and return both it and the position of the id in it. */
int locate_id (int id, entry **lptr)
{
entry *l;
int i, j;
for (i = 0; i < HASHSIZE; i++)
{
if (hash_table[i].count)
for (l = hash_table[i].head; l != NULL; l = l->next)
{
for (j = 0; j < l->num_pans; j++)
{
if (l->an[j] == id)
{
*lptr = l;
return j;
}
}
}
}
*lptr = NULL;
return -1;
}
/* ------------------------------------------------------------------------ */
static void mo_read_pan_file (char *filename)
{
FILE *fp;
char line[MO_LINE_LENGTH];
char *status;
entry *l;
fp = fopen (filename, "r");
if (!fp)
goto screwed_no_file;
status = fgets (line, MO_LINE_LENGTH, fp);
if (!status || !(*line))
goto screwed_with_file;
/* See if it's our format. */
if (strncmp (line, NCSA_PAN_LOG_FORMAT_COOKIE_ONE,
strlen (NCSA_PAN_LOG_FORMAT_COOKIE_ONE)))
goto screwed_with_file;
/* Go fetch the name on the next line. */
status = fgets (line, MO_LINE_LENGTH, fp);
if (!status || !(*line))
goto screwed_with_file;
/* Start grabbing documents and lists of annotations. */
while (1)
{
char *url;
char *p;
status = fgets (line, MO_LINE_LENGTH, fp);
if (!status || !(*line))
goto done;
url = strtok (line, " ");
if (!url)
goto screwed_with_file;
url = strdup (url);
/* We don't use the last-accessed date... yet. */
/* lastdate = strtok (NULL, "\n"); blah blah blah... */
l = new_entry (hash_url (url), url);
free (url);
while (p = strtok (NULL, " "))
{
int a = atoi (p);
if (a)
{
add_an_to_entry (l, a);
if (a > max_pan_id)
max_pan_id = a;
}
}
}
done:
fclose (fp);
return;
screwed_with_file:
fclose (fp);
return;
screwed_no_file:
return;
}
static char *cached_global_pan_fname = NULL;
static mo_status mo_init_pan (void)
{
int i;
/* Initialize the pan structs. */
for (i = 0; i < HASHSIZE; i++)
{
hash_table[i].count = 0;
hash_table[i].head = 0;
}
return mo_succeed;
}
/* --------------------- ensure_pan_directory_exists ---------------------- */
void ensure_pan_directory_exists (void)
{
char *default_directory = get_pref_string(ePRIVATE_ANNOTATION_DIRECTORY);
char filename[500];
struct stat buf;
int r;
sprintf (filename, "%s/%s", home, default_directory);
r = stat (filename, &buf);
if (r == -1)
{
#ifdef NeXT
mkdir (filename);
#else
mkdir (filename, S_IRUSR | S_IWUSR | S_IXUSR);
#endif
}
return;
}
/* ------------------ Interface For Personal Annotations ------------------ */
mo_status mo_is_editable_pan (char *text)
{
if (!text)
return mo_fail;
if (!strncmp (text, NCSA_ANNOTATION_FORMAT_ONE,
strlen (NCSA_ANNOTATION_FORMAT_ONE)))
return mo_succeed;
else
return mo_fail;
}
/* First, call mo_init_pan() to set up internal hash table.
Then, snarf a value for home.
Then, snarf a value for the log file.
Then, read the log file and load up the hash table
by calling mo_read_pan_file. */
mo_status mo_setup_pan_list (void)
{
char *default_directory = get_pref_string(ePRIVATE_ANNOTATION_DIRECTORY);
char *default_filename = PAN_LOG_FILENAME;
char *filename;
mo_init_pan ();
home = flatpakCheck();
/* This shouldn't happen. */
if (!home)
home = "/tmp";
filename = (char *)malloc
((strlen (home) + strlen (default_directory) +
strlen (default_filename) + 8) * sizeof (char));
sprintf (filename, "%s/%s/%s", home, default_directory, default_filename);
cached_global_pan_fname = filename;
mo_read_pan_file (filename);
return mo_succeed;
}
/* Write out the log file. */
mo_status mo_write_pan_list (void)
{
FILE *fp;
int i;
entry *l;
ensure_pan_directory_exists ();
fp = fopen (cached_global_pan_fname, "w");
if (!fp)
return mo_fail;
fprintf (fp, "%s\n%s\n", NCSA_PAN_LOG_FORMAT_COOKIE_ONE, "Personal");
for (i = 0; i < HASHSIZE; i++)
for (l = hash_table[i].head; l != NULL; l = l->next)
if (l->num_pans)
{
int j;
fprintf (fp, "%s", l->href);
for (j = 0; j < l->num_pans; j++)
fprintf (fp, " %d", l->an[j]);
fprintf (fp, "\n");
}
fclose (fp);
return mo_succeed;
}
static mo_status mo_write_pan (int id, char *title, char *author, char *text)
{
char *default_directory = get_pref_string(ePRIVATE_ANNOTATION_DIRECTORY);
char filename[500];
FILE *fp;
time_t foo = time (NULL);
char *ts = ctime (&foo);
ts[strlen(ts)-1] = '\0';
ensure_pan_directory_exists ();
/* Write the new annotation to its appropriate file. */
sprintf (filename, "%s/%s/%s%d.html", home, default_directory,
PAN_ANNOTATION_PREFIX, id);
fp = fopen (filename, "w");
if (!fp)
return mo_fail;
fprintf (fp, "%s\n", NCSA_ANNOTATION_FORMAT_ONE);
fprintf (fp, "<title>%s</title>\n", title);
fprintf (fp, "<h1>%s</h1>\n", title);
fprintf (fp, "<address>%s</address>\n", author);
foo = time (NULL);
fprintf (fp, "<address>%s</address>\n", ts);
fprintf (fp, "______________________________________\n");
fprintf (fp, "<pre>\n");
fprintf (fp, "%s", text);
fclose (fp);
return mo_succeed;
}
/* We're making a new annotation to a url. Pass in the title,
author, and text of the annotation. */
mo_status mo_new_pan (char *url, char *title, char *author, char *text)
{
entry *l = fetch_entry (url);
int id = ++max_pan_id;
if (!title || !*title)
title = strdup ("Annotation with no title");
if (!author || !*author)
author = strdup ("No author name");
/* Create a new entry if we have to. */
if (!l)
l = new_entry (hash_url (url), url);
/* Register the new annotation id with the entry. */
add_an_to_entry (l, id);
mo_write_pan (id, title, author, text);
return mo_succeed;
}
/* We're just outright deleting an annotation. */
mo_status mo_delete_pan (int id)
{
entry *l;
int place;
char filename[500]/*, *cmd*/;
char *default_directory = get_pref_string(ePRIVATE_ANNOTATION_DIRECTORY);
place = locate_id (id, &l);
if (!l)
/* Weird -- no URL associated with the edited annotation. */
return mo_fail;
remove_an_from_entry (l, id);
/* Remove the annotation itself. */
sprintf (filename, "%s/%s/%s%d.html", home, default_directory,
PAN_ANNOTATION_PREFIX, id);
/*
cmd = (char *)malloc ((strlen (filename) + 32) * sizeof (char));
sprintf (cmd, "/bin/rm -f %s &", filename);
system (cmd);
free (cmd);
*/
unlink(filename);
#ifdef HAVE_AUDIO_ANNOTATIONS
#if defined(__sgi)
/* Remove a possible audio annotation. */
sprintf (filename, "%s/%s/%s%d.aiff", home, default_directory,
PAN_ANNOTATION_PREFIX, id);
/*
cmd = (char *)malloc ((strlen (filename) + 32) * sizeof (char));
sprintf (cmd, "/bin/rm -f %s &", filename);
system (cmd);
free (cmd);
*/
unlink(filename);
#else /* sun or HP, probably */
/* Remove a possible audio annotation. */
sprintf (filename, "%s/%s/%s%d.au", home, default_directory,
PAN_ANNOTATION_PREFIX, id);
/*
cmd = (char *)malloc ((strlen (filename) + 32) * sizeof (char));
sprintf (cmd, "/bin/rm -f %s &", filename);
system (cmd);
free (cmd);
*/
unlink(filename);
#endif
#endif /* HAVE_AUDIO_ANNOTATIONS */
return mo_succeed;
}
/* We're modifying an existing pan. Pass in the id,
the title, author, and text.
Check for null text -- NOT AT THE MOMENT */
mo_status mo_modify_pan (int id, char *title, char *author,
char *text)
{
if (!title || !*title)
title = strdup ("Annotation with no title");
if (!author || !*author)
author = strdup ("No author name");
mo_write_pan (id, title, author, text);
return mo_succeed;
}
/* s1 is the existing string (or NULL for a new string)
s2 is the string to append
(s1 is assumed free'able, s2 not) */
static char *smart_append (char *s1, char *s2)
{
if (!s1)
return strdup (s2);
else
{
char *foo = malloc (strlen (s1) + strlen (s2) + 8);
strcpy (foo, s1);
strcat (foo, s2);
free (s1);
return foo;
}
}
/* Given a string (s) and an offset,
make a copy of the string starting at the offset;
step through that string and nil'ify the first '<' we see.
Return the resulting string.
Example:
extract_meat ("<foo>barblegh</foo>", 5) returns "barblegh". */
static char *extract_meat (char *s, int offset)
{
char *foo = s + offset;
char *bar = strdup (foo);
char *ptr = bar;
while (*bar != '\0' && *bar != '<')
bar++;
if (*bar == '<')
*bar = '\0';
return ptr;
}
char *mo_fetch_personal_annotations (char *url)
{
entry *l = fetch_entry (url);
char *msg = NULL;
char line[MO_LINE_LENGTH];
char *default_directory = get_pref_string(ePRIVATE_ANNOTATION_DIRECTORY);
int i, count = 0;
if (!l || !l->num_pans)
{
return NULL;
}
/* OK, now we've got the entry. Basically, we step through
and append all anotations for the given url together
*/
sprintf (line, "Private-Annotation: %d\r\n", l->num_pans);
msg = smart_append (msg, line);
for (i = 0; i < l->num_pans; i++)
{
/* What's the annotation called? */
char filename[500];
FILE *fp;
/* Do we have to assume we're opening a file with
suffix .html??? */
sprintf (filename, "%s/%s/%s%d.html", home, default_directory,
PAN_ANNOTATION_PREFIX, l->an[i]);
/* This whole routine assumes there are no NULLs in the file. */
fp = fopen (filename, "r");
if (fp)
{
char* an_anno = NULL;
while (fgets (line, MO_LINE_LENGTH, fp)) {
an_anno = smart_append (an_anno, line);
}
sprintf (line, "Content-Length: %d\r\n", strlen(an_anno));
msg = smart_append (msg, line);
msg = smart_append (msg, an_anno);
if (an_anno)
free(an_anno);
}
fclose (fp);
}
return msg;
}
/* For the given url, fetch an HTML-format hyperlink table
to be appended to the document text. */
char *mo_fetch_pan_links (char *url, int on_top)
{
entry *l = fetch_entry (url);
char *msg = NULL;
char line[MO_LINE_LENGTH];
char *status;
char *default_directory = get_pref_string(ePRIVATE_ANNOTATION_DIRECTORY);
int i, count = 0;
if (!l)
{
return NULL;
}
if (!l->num_pans)
{
return NULL;
}
/* OK, now we've got the entry. Basically, we step through
the available annotations and manufacture links:
<h2>Personal Annotations</h2>
<ul>
<li> Title, Date
<li> Title, Date
</ul>
*/
msg = smart_append (msg, "<h2>Personal Annotations</h2>\n<ul>\n");
for (i = 0; i < l->num_pans; i++)
{
/* What's the annotation called? */
char filename[500];
FILE *fp;
/* Do we have to assume we're opening a file with
suffix .html??? */
sprintf (filename, "%s/%s/%s%d.html", home, default_directory,
PAN_ANNOTATION_PREFIX, l->an[i]);
fp = fopen (filename, "r");
if (fp)
{
status = fgets (line, MO_LINE_LENGTH, fp);
if (status && *line)
{
/* See if it's our format. */
if (!strncmp (line, NCSA_ANNOTATION_FORMAT_ONE,
strlen (NCSA_ANNOTATION_FORMAT_ONE)))
{
char chunk[500];
count++;
/* Second line is the title. */
status = fgets (line, MO_LINE_LENGTH, fp);
sprintf (chunk, "<li> <a href=\"file://%s%s\">%s</a> ",
"localhost", filename, extract_meat (line, 7));
msg = smart_append (msg, chunk);
/* Third line is skipped. */
status = fgets (line, MO_LINE_LENGTH, fp);
/* Fourth line is the author; skipped. */
status = fgets (line, MO_LINE_LENGTH, fp);
/* Fifth line is the date. */
status = fgets (line, MO_LINE_LENGTH, fp);
sprintf (chunk, "(%s)\n", extract_meat (line, 9));
msg = smart_append (msg, chunk);
/* That's it. */
}
}
fclose (fp);
}
}
/* If we made it all this way and it turns out we don't actually
have anything yet, then punt. */
if (!count)
{
free (msg);
return NULL;
}
msg = smart_append (msg, "</ul>\n");
return msg;
}
mo_status mo_grok_pan_pieces (char *url, char *t,
char **title, char **author, char **text,
int *id, char **fn)
{
char line[MO_LINE_LENGTH];
char *status;
/* char *default_directory = Rdata.private_annotation_directory;*/
FILE *fp;
char *filename;
/* We're now including the hostname in the URL. Jump past it. */
filename = strstr (url, "//");
if (!filename)
return mo_fail;
filename = strstr (filename + 2, "/");
if (!filename)
return mo_fail;
*fn = filename;
fp = fopen (filename, "r");
if (fp)
{
status = fgets (line, MO_LINE_LENGTH, fp);
if (status && *line)
{
/* See if it's our format. */
if (!strncmp (line, NCSA_ANNOTATION_FORMAT_ONE,
strlen (NCSA_ANNOTATION_FORMAT_ONE)))
{
/*char chunk[500];*/
/* Second line is the title. */
status = fgets (line, MO_LINE_LENGTH, fp);
*title = extract_meat (line, 7);
/* Third line is skipped. */
status = fgets (line, MO_LINE_LENGTH, fp);
/* Fourth line is the author. */
status = fgets (line, MO_LINE_LENGTH, fp);
*author = extract_meat (line, 9);
/* Fifth line is the date. */
status = fgets (line, MO_LINE_LENGTH, fp);
/* Sixth line is separator. */
status = fgets (line, MO_LINE_LENGTH, fp);
/* Sixth line is pre. */
status = fgets (line, MO_LINE_LENGTH, fp);
/* Remaining lines are the text. */
*text = NULL;
while (1)
{
status = fgets (line, MO_LINE_LENGTH, fp);
if (status && *line)
*text = smart_append (*text, line);
else
goto got_it;
}
got_it:
/* That's it. */
;
}
}
fclose (fp);
}
else
{
return mo_fail;
}
{
char *start = strstr (filename, "PAN-") + 4;
char *foo = strdup (start);
char *keepit = foo;
while (*foo != '\0' && *foo != '.')
foo++;
if (*foo == '.')
*foo = '\0';
*id = atoi (keepit);
free (keepit);
}
return mo_succeed;
}
/* Return the ID of the next pan to be issued. */
int mo_next_pan_id (void)
{
return max_pan_id + 1;
}