aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--param.ccl17
-rw-r--r--src/Content.c582
-rw-r--r--src/http_Content.h30
3 files changed, 566 insertions, 63 deletions
diff --git a/param.ccl b/param.ccl
index 718d530..444cdd3 100644
--- a/param.ccl
+++ b/param.ccl
@@ -26,6 +26,23 @@ BOOLEAN pause "Pause ?" STEERABLE = ALWAYS
{
} "no"
+# Username and password for controlling cactus
+STRING user "The username for Cactus Control "
+{
+ ".+" :: "Any name of one or more characters"
+} "anon"
+
+STRING password "The password for Cactus Control"
+{
+ ".*" :: "Any password"
+} "anon"
+
+KEYWORD encryption_scheme "How the password is encrypted"
+{
+ "none" :: "Not encrypted"
+ "crypt" :: "crypt(3) (standard UNIX passwd format)"
+} "none"
+
##################################################################
# Tuning parameters
diff --git a/src/Content.c b/src/Content.c
index c423d64..993747a 100644
--- a/src/Content.c
+++ b/src/Content.c
@@ -27,7 +27,10 @@
#include "http_Auth.h"
#include "http_Steer.h"
+#include "http_Content.h"
+
#include "cctk_Arguments.h"
+#include "cctk_Parameters.h"
static char *rcsid = "$Header$";
@@ -44,6 +47,15 @@ struct httpStaticPage
char *mime_type;
};
+struct httpLink
+{
+ struct httpLink *next;
+ char *URL;
+ char *name;
+ char *description;
+ int flags;
+};
+
/********************************************************************
********************* Local Routine Prototypes *********************
********************************************************************/
@@ -57,10 +69,12 @@ static int ThornParameterPage(cGH *cctkGH, httpRequest *request, void *data);
static int ShowStaticPage(cGH *cctkGH, httpRequest *request, void *data);
-static int AuthPage(cGH *cctkGH, httpRequest *request, void *data);
-
static int CompareStrings(const void *string1, const void *string2);
+static int ControlPage(cGH *cctkGH, httpRequest *request, void *data);
+static int ControlSet(cGH *cctkGH, httpRequest *request);
+static int ControlTerminationPage(cGH *cctkGH, httpRequest *request);
+
/********************************************************************
********************* Other Routine Prototypes *********************
********************************************************************/
@@ -69,6 +83,8 @@ static int CompareStrings(const void *string1, const void *string2);
********************* Local Data *****************************
********************************************************************/
+static struct httpLink *ContentLinks = NULL;
+
/********************************************************************
********************* External Routines **********************
********************************************************************/
@@ -110,6 +126,7 @@ void HTTP_ContentWork(CCTK_ARGUMENTS)
@@*/
int HTTP_RegisterPages(void)
{
+ DECLARE_CCTK_PARAMETERS
#ifdef HTTP_DEBUG
printf("Registering index.html\n");
#endif
@@ -121,16 +138,78 @@ int HTTP_RegisterPages(void)
RegisterParameterPages();
- /* Register a test authentication page. */
+ /* Register parameter control stuff. */
+
+ HTTP_RegisterPage("/control.html", ControlPage, NULL);
- HTTP_RegisterPage("/auth_page", AuthPage, NULL);
+ HTTP_ContentLink("/control.html", "Cactus Control",
+ "Control Panel for this run",
+ HTTP_QUICKLINK);
- HTTP_AuthAddUser("user","goodale","foo","none");
+ HTTP_AuthAddUser("user",user,password,encryption_scheme);
/* Register images */
RegisterImages();
}
+ /*@@
+ @routine HTTP_ContentLink
+ @date Sun Sep 17 13:17:56 2000
+ @author Tom Goodale
+ @desc
+ Register content links.
+ @enddesc
+ @calls
+ @calledby
+ @history
+
+ @endhistory
+
+@@*/
+int HTTP_ContentLink(const char *URL,
+ const char *name,
+ const char *description,
+ int flags)
+{
+ int retval;
+ struct httpLink *link;
+ struct httpLink *last;
+ struct httpLink *current;
+
+ link = (struct httpLink *)malloc(sizeof(struct httpLink));
+
+ if(link)
+ {
+ link->URL = Util_Strdup(URL);
+ link->name = Util_Strdup(name);
+ link->description = Util_Strdup(description);
+ link->flags = flags;
+ link->next = NULL;
+
+ /* Put on list */
+ if(ContentLinks)
+ {
+ for(current=ContentLinks; current; current = current->next)
+ {
+ last = current;
+ }
+ last->next = link;
+ }
+ else
+ {
+ ContentLinks = link;
+ }
+
+ retval = 0;
+ }
+ else
+ {
+ retval = -1;
+ }
+
+ return retval;
+}
+
/********************************************************************
********************* Local Routines *************************
********************************************************************/
@@ -212,6 +291,7 @@ static int MainPage(cGH *cctkGH, httpRequest *request, void *data)
{
int retval;
char message[4098];
+ struct httpLink *link;
/* Status message */
strcpy(message,"HTTP/1.0 200 OK\r\n");
@@ -231,6 +311,30 @@ static int MainPage(cGH *cctkGH, httpRequest *request, void *data)
/* Write out the main header part */
HTTP_Write(request, cactus_mainheader, strlen(cactus_mainheader));
+ strcpy(message, "<center><h1>Simulation Home Page</h1></center>");
+
+ /* ADD QUICK LINKS (e.g. FOR PALM) */
+
+ if(ContentLinks)
+ {
+ strcpy(message, "<center>Quick links: ");
+
+ for(link = ContentLinks; link; link=link->next)
+ {
+ if(link->flags & HTTP_QUICKLINK)
+ {
+ sprintf(message,
+ "%s[<A HREF=\"%s\">%s</a>]",
+ message,
+ link->URL,
+ link->name);
+ }
+ }
+ strcat(message,"</center>\n<br>\n");
+ HTTP_Write(request, message, strlen(message));
+ }
+
+
/* Some blurb */
strcpy(message, "<br>"
"<center><table cellspacing=5 cellpadding=5 border=0><tr><td valign=top>"
@@ -373,6 +477,10 @@ static int RegisterParameterPages(void)
HTTP_RegisterPage("/Parameters/index.html", MainParameterPage, NULL);
+ HTTP_ContentLink("/Parameters/index.html", "Parameters",
+ "Parameter Informatiiona and Control",
+ HTTP_QUICKLINK);
+
for (i = 0; i < CCTK_NumCompiledThorns (); i++)
{
thorn = CCTK_CompiledThorn(i);
@@ -475,6 +583,10 @@ static int MainParameterPage(cGH *cctkGH, httpRequest *request, void *data)
return retval;
}
+static const char *notauthorized_page =
+"<HTML>\n<HEAD><TITLE>Error 401: Not Authorized</TITLE></HEAD>\
+<BODY>You are not authorized to access this page</BODY>\n<HTML>\n";
+
/*@@
@routine ThornParameterPage
@date Sat Sep 16 15:13:55 2000
@@ -509,37 +621,85 @@ static int ThornParameterPage(cGH *cctkGH, httpRequest *request, void *data)
const char *prefix;
const httpArg *argument;
+ int notauthorised;
+ int readonly;
+
thorn = (const char *)data;
+ notauthorised = HTTP_AuthenticateBasic(request, "user");
+
+ readonly = notauthorised;
+
if(request->n_arguments > 0)
{
/* This is a parameter set request */
- /* Queue parameters for steering */
- first = 1;
- while(argument = HTTP_ArgumentWalk(request, first))
+ if(!notauthorised)
{
- first = 0;
- fprintf(stderr, "Setting %s::%s to %s\n", thorn, argument->arg, argument->value);
- HTTP_SteerQueue(thorn, argument->arg, argument->value);
- }
+ if(!readonly)
+ {
+ /* Queue parameters for steering */
+ first = 1;
+ while(argument = HTTP_ArgumentWalk(request, first))
+ {
+ first = 0;
+ fprintf(stderr, "Setting %s::%s to %s\n", thorn, argument->arg, argument->value);
+ HTTP_SteerQueue(thorn, argument->arg, argument->value);
+ }
+
+ /* Now redirect the browser to the normal page */
+ /* Status message */
+ if(request->http_major_version < 1 ||
+ (request->http_major_version == 1 && request->http_minor_version < 1))
+ {
+ /* Older browsers don't understand 303 */
+ strcpy(message,"HTTP/1.0 302 Found\r\n");
+ }
+ else
+ {
+ strcpy(message,"HTTP/1.0 303 See Other\r\n");
+ }
- /* Now redirect the browser to the normal page */
- /* Status message */
- if(request->http_major_version < 1 ||
- (request->http_major_version == 1 && request->http_minor_version < 1))
- {
- /* Older browsers don't understand 303 */
- strcpy(message,"HTTP/1.0 302 Found\r\n");
+ sprintf(message, "%sLocation: /Parameters/%s/\r\n\r\n", message, thorn);
+
+ HTTP_Write(request, message, strlen(message));
+ }
+ else
+ {
+ /* Redirect the browser to the normal page */
+ /* Status message */
+ if(request->http_major_version < 1 ||
+ (request->http_major_version == 1 && request->http_minor_version < 1))
+ {
+ /* Older browsers don't understand 303 */
+ strcpy(message,"HTTP/1.0 302 Found\r\n");
+ }
+ else
+ {
+ strcpy(message,"HTTP/1.0 303 See Other\r\n");
+ }
+
+ sprintf(message, "%sLocation: /Parameters/%s/\r\n\r\n", message, thorn);
+
+ HTTP_Write(request, message, strlen(message));
+ }
}
else
{
- strcpy(message,"HTTP/1.0 303 See Other\r\n");
- }
-
- sprintf(message, "%sLocation: /Parameters/%s/\r\n\r\n", message, thorn);
+ strcpy(message,"HTTP/1.0 401 Unauthorized\r\n");
+
+ HTTP_Write(request, message, strlen(message));
- HTTP_Write(request, message, strlen(message));
+ strcpy(message,"WWW-Authenticate: Basic realm=\"foo\"\r\n");
+
+ HTTP_Write(request, message, strlen(message));
+
+ strcpy(message,"Content-Type: text/html\r\n\r\n");
+
+ HTTP_Write(request, message, strlen(message));
+
+ HTTP_Write(request, notauthorized_page, strlen(notauthorized_page));
+ }
}
else
@@ -591,9 +751,12 @@ static int ThornParameterPage(cGH *cctkGH, httpRequest *request, void *data)
HTTP_Write(request, message, strlen(message));
- sprintf(message,"<FORM ACTION=\"/Parameters/%s/\"><TR><TD COLSPAN=2 ALIGN=CENTER><INPUT TYPE=SUBMIT VALUE=\"Update all parameters\"></TD></TR>\n", thorn);
- HTTP_Write(request, message, strlen(message));
-
+ if(!readonly)
+ {
+ sprintf(message,"<FORM ACTION=\"/Parameters/%s/\"><TR><TD COLSPAN=2 ALIGN=CENTER><INPUT TYPE=SUBMIT VALUE=\"Update all parameters\"></TD></TR>\n", thorn);
+ HTTP_Write(request, message, strlen(message));
+ }
+
/* Walk through all parameters of given implementation. */
first = 1;
@@ -614,7 +777,7 @@ static int ThornParameterPage(cGH *cctkGH, httpRequest *request, void *data)
prefix = CCTK_ThornImplementation (pData->thorn);
}
- if (pData->steerable == CCTK_STEERABLE_ALWAYS)
+ if (pData->steerable == CCTK_STEERABLE_ALWAYS && !readonly)
{
sprintf(message,
"%s<TR><TD align=left valign=center><font color=red>%s::%s</font></TD>"
@@ -624,10 +787,17 @@ static int ThornParameterPage(cGH *cctkGH, httpRequest *request, void *data)
}
else
{
- sprintf(message,
- "%s<TR><TD align=left valign=center>%s::%s</TD>"
- "<TD>%s</TD></TR>\n",
- message, prefix,pData->name, value);
+ /* FIXME: This is a hack - should put in parameter tags. */
+ if(strcmp(thorn,CCTK_THORNSTRING) ||
+ (strcmp(pData->name,"user") &&
+ strcmp(pData->name,"password") &&
+ strcmp(pData->name,"encryption_scheme")))
+ {
+ sprintf(message,
+ "%s<TR><TD align=left valign=center>%s::%s</TD>"
+ "<TD>%s</TD></TR>\n",
+ message, prefix,pData->name, value);
+ }
}
free (value);
}
@@ -635,10 +805,15 @@ static int ThornParameterPage(cGH *cctkGH, httpRequest *request, void *data)
}
- strcpy(message,
- "<TR><TD COLSPAN=2 ALIGN=CENTER><INPUT TYPE=SUBMIT VALUE=\"Update all parameters\"></TD></TR>\n"
- "</TABLE><P></BLOCKQUOTE>\n"
- "</FORM>\n");
+ if(!readonly)
+ {
+ strcpy(message,
+ "<TR><TD COLSPAN=2 ALIGN=CENTER><INPUT TYPE=SUBMIT VALUE=\"Update all parameters\"></TD></TR>\n"
+ "</FORM>\n");
+ HTTP_Write(request, message, strlen(message));
+ }
+
+ strcpy(message, "</TABLE><P></BLOCKQUOTE>\n");
HTTP_Write(request, message, strlen(message));
}
@@ -744,25 +919,12 @@ static int ShowStaticPage(cGH *cctkGH, httpRequest *request, void *data)
}
-/******************************************************************************
- ********************** Authentication test page ******************************
- ******************************************************************************/
-
-
-static const char *notauthorized_page =
-"<HTML>\n<HEAD><TITLE>Error 401: Not Authorized</TITLE></HEAD>\
-<BODY>You are not authorized to access this page</BODY>\n<HTML>\n";
-
-static const char *authorized_page =
-"<HTML>\n<HEAD><TITLE>Authorized</TITLE></HEAD>\
-<BODY>Congratulations !</BODY>\n<HTML>\n";
-
/*@@
- @routine AuthPage
- @date Fri Sep 15 12:52:37 2000
+ @routine ControlPage
+ @date Sun Sep 17 14:37:59 2000
@author Tom Goodale
@desc
- Test page for authentication.
+ Cactus Control Panel
@enddesc
@calls
@calledby
@@ -771,38 +933,113 @@ static const char *authorized_page =
@endhistory
@@*/
-static int AuthPage(cGH *cctkGH, httpRequest *request, void *data)
+static int ControlPage(cGH *cctkGH, httpRequest *request, void *data)
{
+ DECLARE_CCTK_PARAMETERS
+
int retval;
- char message[1024];
+ char message[4096];
int notauthorised;
+ struct httpLink *link;
notauthorised = HTTP_AuthenticateBasic(request, "user");
if(!notauthorised)
{
- strcpy(message,"HTTP/1.0 200 Ok\r\n");
+ /* Ok the person is authorised. */
+ if(request->n_arguments == 0)
+ {
+ /* No arguments, so just display the page */
+ strcpy(message,"HTTP/1.0 200 Ok\r\n");
- HTTP_Write(request, message, strlen(message));
+ HTTP_Write(request, message, strlen(message));
- strcpy(message,"WWW-Authenticate: Basic realm=\"foo\"\r\n");
+ strcpy(message,"WWW-Authenticate: Basic realm=\"Cactus Control\"\r\n");
- HTTP_Write(request, message, strlen(message));
+ HTTP_Write(request, message, strlen(message));
- strcpy(message,"Content-Type: text/html\r\n\r\n");
+ strcpy(message,"Content-Type: text/html\r\n\r\n");
- HTTP_Write(request, message, strlen(message));
-
- HTTP_Write(request, authorized_page, strlen(authorized_page));
+ HTTP_Write(request, message, strlen(message));
+
+ /* Start the page */
+ strcpy(message, "<HTML><HEAD><TITLE>Cactus Control and Status Page</TITLE>\n");
+ strcat(message, cactus_mainheader);
+ strcat(message, "<center><h1>Simulation Home Page</h1></center>");
+
+ HTTP_Write(request, message, strlen(message));
+
+ if(ContentLinks)
+ {
+ strcpy(message, "<center>Quick links: ");
+
+ for(link = ContentLinks; link; link=link->next)
+ {
+ if(link->flags & HTTP_QUICKLINK)
+ {
+ sprintf(message,
+ "%s[<A HREF=\"%s\">%s</a>]",
+ message,
+ link->URL,
+ link->name);
+ }
+ }
+ strcat(message,"</center>\n<br>\n");
+
+ HTTP_Write(request, message, strlen(message));
+ }
+
+ strcpy(message,
+ "<CENTER>\n"
+ "<FORM action=\"/control.html\" method=\"GET\">\n"
+ "<TABLE BORDER= 0 CELLSPACING=5 CELLPADDING=5>\n"
+ "<TR>\n");
+ HTTP_Write(request, message, strlen(message));
+
+ sprintf(message,
+ "<TD align=left valign=center><INPUT type=\"radio\" name=\"runstate\" %s value=\"PAUSE\"> PAUSE<BR></TD>\n",
+ pause ? "checked" : "");
+ HTTP_Write(request, message, strlen(message));
+
+ sprintf(message,
+ "<TD align=left valign=center><INPUT type=\"radio\" name=\"runstate\" %s value=\"RUN\"> RUN<BR></TD>\n",
+ pause ? "" : "checked");
+ HTTP_Write(request, message, strlen(message));
+
+ strcpy(message,
+ "<TD align=left valign=center><INPUT type=\"radio\" name=\"runstate\" value=\"TERMINATE\"> TERMINATE<BR></TD>\n");
+ HTTP_Write(request, message, strlen(message));
+
+ strcpy(message,
+ "</TR></TABLE>\n"
+ "<TABLE>\n"
+ "<TR><TD><INPUT type=\"submit\" value=\"OK\"></TD><TD> <INPUT type=\"reset\"></TD>\n"
+ "</TR>\n"
+ "</TABLE>\n"
+ "</FORM>\n"
+ "</CENTER>\n");
+
+ HTTP_Write(request, message, strlen(message));
+
+ /* Write out the footer part. */
+
+ retval = HTTP_Write(request, cactus_footer, strlen(cactus_footer));
+ }
+ else
+ {
+ /* Arguments, so control simulation */
+ ControlSet(cctkGH, request);
+ }
}
else
{
+ /* Not authorised */
strcpy(message,"HTTP/1.0 401 Unauthorized\r\n");
HTTP_Write(request, message, strlen(message));
- strcpy(message,"WWW-Authenticate: Basic realm=\"foo\"\r\n");
+ strcpy(message,"WWW-Authenticate: Basic realm=\"Cactus Control\"\r\n");
HTTP_Write(request, message, strlen(message));
@@ -813,5 +1050,224 @@ static int AuthPage(cGH *cctkGH, httpRequest *request, void *data)
HTTP_Write(request, notauthorized_page, strlen(notauthorized_page));
}
+
return 0;
}
+
+ /*@@
+ @routine ControlSet
+ @date Sun Sep 17 14:39:50 2000
+ @author Tom Goodale
+ @desc
+ Set the status of the simulation based on the controls.
+ @enddesc
+ @calls
+ @calledby
+ @history
+
+ @endhistory
+
+@@*/
+static int ControlSet(cGH *cctkGH, httpRequest *request)
+{
+ char message[4096];
+ const char *runstate;
+
+ runstate = HTTP_ArgumentValue(request,"runstate");
+
+ switch(*runstate)
+ {
+ case 'T' : HTTP_SteerQueue(CCTK_THORNSTRING, "terminate", "yes");
+ ControlTerminationPage(cctkGH, request);
+ break;
+ case 'P' : HTTP_SteerQueue(CCTK_THORNSTRING, "pause", "yes");
+ break;
+ case 'R' : HTTP_SteerQueue(CCTK_THORNSTRING, "pause", "no");
+ break;
+ default :
+ fprintf(stderr, "Unknown runstate '%s'\n", runstate);
+ }
+
+ /* Now redirect the browser to the normal page */
+ /* Status message */
+ if(request->http_major_version < 1 ||
+ (request->http_major_version == 1 && request->http_minor_version < 1))
+ {
+ /* Older browsers don't understand 303 */
+ strcpy(message,"HTTP/1.0 302 Found\r\n");
+ }
+ else
+ {
+ strcpy(message,"HTTP/1.0 303 See Other\r\n");
+ }
+
+ sprintf(message, "%sLocation: /control.html/\r\n\r\n", message);
+
+ HTTP_Write(request, message, strlen(message));
+
+ return 0;
+}
+
+ /*@@
+ @routine ControlTerminationPage
+ @date Sun Sep 17 14:40:16 2000
+ @author Tom Goodale
+ @desc
+ Page to be shown on termination of Cactus.
+ @enddesc
+ @calls
+ @calledby
+ @history
+
+ @endhistory
+
+@@*/
+static int ControlTerminationPage(cGH *cctkGH, httpRequest *request)
+{
+ int retval;
+ char message[4096];
+
+ /* Status message */
+ strcpy(message,"HTTP/1.0 200 OK\r\n");
+
+ HTTP_Write(request, message, strlen(message));
+
+ /* Content-Type */
+ strcpy(message,"Content-Type: text/html\r\n\r\n");
+
+ HTTP_Write(request, message, strlen(message));
+
+ /* Start the page */
+ strcpy(message,"<HTML><HEAD><TITLE>Running CACTUS Status Information : Terminated</TITLE>\n");
+
+ HTTP_Write(request, message, strlen(message));
+
+ /* Write out the main header part */
+ HTTP_Write(request, cactus_mainheader, strlen(cactus_mainheader));
+
+ strcpy(message, "<center><h1>Simulation Home Page</h1></center>");
+
+ HTTP_Write(request, message, strlen(message));
+
+ /* Some blurb */
+ strcpy(message, "<br>"
+ "<center><table cellspacing=5 cellpadding=5 border=0><tr><td valign=top>"
+ "<h3>Simulation web server:</h3>"
+ "<p>This browser is connected to a Cactus simulation which contains "
+ "a web server thorn. This thorn allows you to monitor the simulation, "
+ "and view and change parameters</p>"
+ "<p>Depending on which other thorns are active, there may be additional "
+ "features available, such as the viewing and downloading of output files</p>");
+
+ HTTP_Write(request, message, strlen(message));
+
+
+ /* CONFIGURATION DETAILS */
+
+ sprintf(message, "<h3>Configuration:</h3>"
+ "<ul> <li>Flesh version <FONT COLOR=RED> %s"
+ "</FONT></li>\n"
+ "<li>Code compiled on <FONT COLOR=RED>"
+ __DATE__ "</FONT> at <FONT COLOR=RED>"__TIME__ "</font></li>\n"
+ "</ul>", CCTK_FullVersion());
+
+ HTTP_Write(request, message, strlen(message));
+
+ /******************************************************************************/
+
+ /* NEW COLUMN */
+
+ strcpy(message, "</td><td valign=top>");
+
+ HTTP_Write(request, message, strlen(message));
+
+ /*******************************************************************************/
+
+ /* CURRENT STATE OF SIMULATION */
+
+ if (cctkGH)
+ {
+
+ sprintf(message, "<H3> Current state:</h3> "
+ "<ul><li>Physical time <FONT COLOR=RED> %f"
+ "</FONT> \n"
+ "<li>Iteration number <FONT COLOR=RED> %d"
+ "</FONT></li>\n",
+ cctkGH->cctk_time,
+ cctkGH->cctk_iteration);
+
+ }
+ else
+ {
+ strcpy(message, "<li>Current cactus time is unknown</li>\n");
+ }
+
+ HTTP_Write(request, message, strlen(message));
+
+ strcpy(message, "<li>This Cactus run is over.</li>\n");
+
+ HTTP_Write(request, message, strlen(message));
+
+ strcpy(message, "</ul>");
+
+ HTTP_Write(request, message, strlen(message));
+
+ /* LIST COMPILED THORNS */
+
+ {
+ int i;
+ int nthorns;
+ const char **thorns;
+
+ nthorns = CCTK_NumCompiledThorns();
+
+ thorns = (const char **)malloc(nthorns * sizeof(char *));
+
+ for(i=0; i < nthorns; i++)
+ {
+ thorns[i] = CCTK_CompiledThorn (i);
+ }
+
+ /* Sort the thorns */
+ qsort(thorns, nthorns, sizeof(char *), CompareStrings);
+
+ strcpy(message, "<h3>Compiled thorns:</h3>"
+ "<p>This list shows all thorns compiled into the executable, "
+ "those thorns which have been activated in the parameter "
+ "file for this simulation are shown in "
+ "<font color=red>red</font></p>"
+ "<UL>");
+
+ for(i=0; i < nthorns; i++)
+ {
+ strcat(message, "<LI>");
+ if (CCTK_IsThornActive(thorns[i]))
+ {
+ sprintf(message,"%s <FONT COLOR=red> %s </FONT>\n", message, thorns[i]);
+ }
+ else
+ {
+ strcat(message, thorns[i]);
+ }
+ }
+ strcat(message, "</UL>");
+
+ free(thorns);
+ }
+
+ HTTP_Write(request, message, strlen(message));
+
+
+ /* Finish table started by blurb */
+ strcpy(message, "</td></tr></table>");
+
+ HTTP_Write(request, message, strlen(message));
+
+ /* Write out the footer part. */
+
+ retval = HTTP_Write(request, cactus_footer, strlen(cactus_footer));
+
+ /* retval = HTTP_Write(request, base_page, strlen(base_page)); */
+
+ return retval;
+}
diff --git a/src/http_Content.h b/src/http_Content.h
new file mode 100644
index 0000000..c0a620e
--- /dev/null
+++ b/src/http_Content.h
@@ -0,0 +1,30 @@
+ /*@@
+ @header http_Content.h
+ @date Sun Sep 17 14:19:23 2000
+ @author Tom Goodale
+ @desc
+ Routines exported by the Content stuff.
+ @enddesc
+ @version $Header$
+ @@*/
+
+#ifndef __HTTP_CONTENT_H__
+#define __HTTP_CONTENT_H__ 1
+
+#define HTTP_QUICKLINK 1
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+int HTTP_ContentLink(const char *URL,
+ const char *name,
+ const char *description,
+ int flags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __HTTP_CONENT_H__ */