Check-in [6af8fdc230]
Not logged in
Overview

SHA1 Hash:6af8fdc230b34ef61f6baab75a370c2647d8e470
Date: 2007-12-04 13:05:35
User: drh
Comment:Generate CGI replies as separate header and body so that the header can be extended during the construction of the body.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/cgi.c from [1c05503871] to [a795dc49f1].

@@ -57,10 +57,17 @@
 #define P(x)        cgi_parameter((x),0)
 #define PD(x,y)     cgi_parameter((x),(y))
 #define QP(x)       quotable_string(cgi_parameter((x),0))
 #define QPD(x,y)    quotable_string(cgi_parameter((x),(y)))
 
+
+/*
+** Destinations for output text.
+*/
+#define CGI_HEADER   0
+#define CGI_BODY     1
+
 #endif /* INTERFACE */
 
 /*
 ** Provide a reliable implementation of a caseless string comparison
 ** function.
@@ -67,40 +74,80 @@
 */
 #define stricmp sqlite3StrICmp
 extern int sqlite3StrICmp(const char*, const char*);
 
 /*
-** The body of the HTTP reply text is stored here.
-*/
-static Blob cgiContent = BLOB_INITIALIZER;
+** The HTTP reply is generated in two pieces: the header and the body.
+** These pieces are generated separately because they are not necessary
+** produced in order.  Parts of the header might be built after all or
+** part of the body.  The header and body are accumulated in separate
+** Blob structures then output sequentially once everything has been
+** built.
+**
+** The cgi_destination() interface switch between the buffers.
+*/
+static Blob *pContent;
+static Blob cgiContent[2] = { BLOB_INITIALIZER, BLOB_INITIALIZER };
+
+/*
+** Set the destination buffer into which to accumulate CGI content.
+*/
+void cgi_destination(int dest){
+  switch( dest ){
+    case CGI_HEADER: {
+      pContent = &cgiContent[0];
+      break;
+    }
+    case CGI_BODY: {
+      pContent = &cgiContent[1];
+      break;
+    }
+    default: {
+      cgi_panic("bad destination");
+    }
+  }
+}
 
 /*
 ** Append reply content to what already exists.
 */
 void cgi_append_content(const char *zData, int nAmt){
-  blob_append(&cgiContent, zData, nAmt);
+  blob_append(pContent, zData, nAmt);
 }
 
 /*
 ** Reset the HTTP reply text to be an empty string.
 */
 void cgi_reset_content(void){
-  blob_reset(&cgiContent);
+  blob_reset(&cgiContent[0]);
+  blob_reset(&cgiContent[1]);
 }
 
 /*
 ** Return a pointer to the CGI output blob.
 */
 Blob *cgi_output_blob(void){
-  return &cgiContent;
+  return pContent;
+}
+
+/*
+** Combine the header and body of the CGI into a single string.
+*/
+static void cgi_combine_header_and_body(void){
+  int size = blob_size(&cgiContent[1]);
+  if( size>0 ){
+    blob_append(&cgiContent[0], blob_buffer(&cgiContent[1]), size);
+    blob_reset(&cgiContent[1]);
+  }
 }
 
 /*
 ** Return a pointer to the HTTP reply text.
 */
 char *cgi_extract_content(int *pnAmt){
-  return blob_buffer(&cgiContent);
+  cgi_combine_header_and_body();
+  return blob_buffer(&cgiContent[0]);
 }
 
 /*
 ** Additional information used to form the HTTP reply
 */
@@ -119,12 +166,13 @@
 
 /*
 ** Set the reply content to the specified BLOB.
 */
 void cgi_set_content(Blob *pNewContent){
-  blob_reset(&cgiContent);
-  cgiContent = *pNewContent;
+  cgi_reset_content();
+  cgi_destination(CGI_HEADER);
+  cgiContent[0] = *pNewContent;
   blob_zero(pNewContent);
 }
 
 /*
 ** Set the reply status code
@@ -220,10 +268,11 @@
 
 /*
 ** Do a normal HTTP reply
 */
 void cgi_reply(void){
+  int total_size;
   if( iReplyStatus<=0 ){
     iReplyStatus = 200;
     zReplyStatus = "OK";
   }
 
@@ -273,19 +322,27 @@
   printf( "Content-Type: %s; charset=%s\r\n", zContentType, nl_langinfo(CODESET));
 #else
   printf( "Content-Type: %s; charset=ISO-8859-1\r\n", zContentType);
 #endif
   if( strcmp(zContentType,"application/x-fossil")==0 ){
-    blob_compress(&cgiContent, &cgiContent);
+    cgi_combine_header_and_body();
+    blob_compress(&cgiContent[0], &cgiContent[0]);
   }
 
   if( iReplyStatus != 304 ) {
-    printf( "Content-Length: %d\r\n", blob_size(&cgiContent) );
+    total_size = blob_size(&cgiContent[0]) + blob_size(&cgiContent[1]);
+    printf( "Content-Length: %d\r\n", total_size);
   }
   printf("\r\n");
-  if( blob_size(&cgiContent)>0 && iReplyStatus != 304 ){
-    fwrite(blob_buffer(&cgiContent), 1, blob_size(&cgiContent), stdout);
+  if( total_size>0 && iReplyStatus != 304 ){
+    int i, size;
+    for(i=0; i<2; i++){
+      size = blob_size(&cgiContent[i]);
+      if( size>0 ){
+        fwrite(blob_buffer(&cgiContent[i]), 1, size, stdout);
+      }
+    }
   }
   CGIDEBUG(("DONE\n"));
 }
 
 /*
@@ -611,10 +668,11 @@
 */
 void cgi_init(void){
   char *z;
   const char *zType;
   int len;
+  cgi_destination(CGI_BODY);
   z = (char*)P("QUERY_STRING");
   if( z ){
     z = mprintf("%s",z);
     add_param_list(z, '&');
   }
@@ -939,20 +997,20 @@
 ** extra formatting capabilities such as %h and %t.
 */
 void cgi_printf(const char *zFormat, ...){
   va_list ap;
   va_start(ap,zFormat);
-  vxprintf(&cgiContent,zFormat,ap);
+  vxprintf(pContent,zFormat,ap);
   va_end(ap);
 }
 
 /*
 ** This routine works like "vprintf" except that it has the
 ** extra formatting capabilities such as %h and %t.
 */
 void cgi_vprintf(const char *zFormat, va_list ap){
-  vxprintf(&cgiContent,zFormat,ap);
+  vxprintf(pContent,zFormat,ap);
 }
 
 
 /*
 ** Send a reply indicating that the HTTP request was malformed
@@ -976,11 +1034,11 @@
   cgi_printf(
     "<html><body><h1>Internal Server Error</h1>\n"
     "<plaintext>"
   );
   va_start(ap, zFormat);
-  vxprintf(&cgiContent,zFormat,ap);
+  vxprintf(pContent,zFormat,ap);
   va_end(ap);
   cgi_reply();
   exit(1);
 }
 

Modified src/manifest.c from [9573b5d563] to [53c15e41ed].

@@ -783,11 +783,11 @@
         int cid = db_column_int(&q, 0);
         add_mlink(rid, &m, cid, 0);
       }
       db_finalize(&q);
       db_multi_exec(
-        "INSERT INTO event(type,mtime,objid,user,comment,"
+        "REPLACE INTO event(type,mtime,objid,user,comment,"
         "                  bgcolor,brbgcolor,euser,ecomment)"
         "VALUES('ci',%.17g,%d,%Q,%Q,"
         " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype=1),"
         "(SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype!=1),"
         "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
@@ -845,11 +845,11 @@
     if( prior ){
       content_deltify(prior, rid, 0);
     }
     zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle);
     db_multi_exec(
-      "INSERT INTO event(type,mtime,objid,user,comment,"
+      "REPLACE INTO event(type,mtime,objid,user,comment,"
       "                  bgcolor,brbgcolor,euser,ecomment)"
       "VALUES('w',%.17g,%d,%Q,%Q,"
       " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype=1),"
       "(SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype!=1),"
       "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
@@ -870,15 +870,15 @@
     zTag = mprintf("tkt-%s", m.zTicketUuid);
     tag_insert(zTag, 1, 0, rid, m.rDate, rid);
     free(zTag);
     zComment = mprintf("Changes to ticket [%.10s]", m.zTicketUuid);
     db_multi_exec(
-      "INSERT INTO event(type,mtime,objid,user,comment)"
+      "REPLACE INTO event(type,mtime,objid,user,comment)"
       "VALUES('t',%.17g,%d,%Q,%Q)",
       m.rDate, rid, m.zUser, zComment
     );
     free(zComment);
   }
   db_end_transaction(0);
   manifest_clear(&m);
   return 1;
 }

Modified src/style.c from [773050316b] to [80b7efbe0d].

@@ -82,10 +82,11 @@
   const char *zLogInOut = "Login";
   const char *zHeader = db_get("header", (char*)zDefaultHeader);
   login_check_credentials();
 
   if( pInterp ) return;
+  cgi_destination(CGI_HEADER);
 
   /* Generate the header up through the main menu */
   pInterp = SbS_Create();
   SbS_Store(pInterp, "project_name",
                      db_get("project-name","Unnamed Fossil Project"), 0);
@@ -97,11 +98,11 @@
     SbS_Store(pInterp, "login", g.zLogin, 0);
     zLogInOut = "Logout";
   }
   SbS_Render(pInterp, zHeader);
 
-  /* Generate the main menu and the submenu (if any) */
+  /* Generate the main menu */
   @ <div class="mainmenu">
   @ <a href="%s(g.zBaseURL)/home">Home</a>
   if( g.okRead ){
     @ <a href="%s(g.zBaseURL)/leaves">Leaves</a>
     @ <a href="%s(g.zBaseURL)/timeline">Timeline</a>
@@ -119,12 +120,29 @@
   }
   if( !g.noPswd ){
     @ <a href="%s(g.zBaseURL)/login">%s(zLogInOut)</a>
   }
   @ </div>
+  cgi_destination(CGI_BODY);
+  g.cgiPanic = 1;
+}
+
+/*
+** Draw the footer at the bottom of the page.
+*/
+void style_footer(void){
+  const char *zFooter;
+
+  if( pInterp==0 ) return;
+
+  /* Go back and put the submenu at the top of the page.  We delay the
+  ** creation of the submenu until the end so that we can add elements
+  ** to the submenu while generating page text.
+  */
   if( nSubmenu>0 ){
     int i;
+    cgi_destination(CGI_HEADER);
     @ <div class="submenu">
     qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
     for(i=0; i<nSubmenu; i++){
       struct Submenu *p = &aSubmenu[i];
       if( p->zLink==0 ){
@@ -132,22 +150,16 @@
       }else{
         @ <a class="label" href="%s(p->zLink)">%h(p->zLabel)</a>
       }
     }
     @ </div>
-  }
-  @ <div class="content">
-  g.cgiPanic = 1;
-}
+    cgi_destination(CGI_BODY);
+  }
 
-/*
-** Draw the footer at the bottom of the page.
-*/
-void style_footer(void){
-  const char *zFooter;
-
-  if( pInterp==0 ) return;
+  /* Put the footer at the bottom of the page.
+  */
+  @ <div class="content">
   zFooter = db_get("footer", (char*)zDefaultFooter);
   @ </div>
   SbS_Render(pInterp, zFooter);
   SbS_Destroy(pInterp);
   pInterp = 0;