]> code.citadel.org Git - citadel.git/blobdiff - webcit/messages.c
* migrate more of the upload handling to strbuf
[citadel.git] / webcit / messages.c
index 5d0209c71dad80a795a7b233989d3490cd416473..47f6f41c5eafb2e90c4efa992a7b57d1637f5268 100644 (file)
@@ -22,77 +22,55 @@ void jsonMessageListHdr(void);
 
 void display_enter(void);
 
-/*----------------------------------------------------------------------------*/
-
-
 typedef void (*MsgPartEvaluatorFunc)(message_summary *Sum, StrBuf *Buf);
 
 typedef struct _MsgPartEvaluatorStruct {
        MsgPartEvaluatorFunc f;
-}MsgPartEvaluatorStruct;
-
-
-/*----------------------------------------------------------------------------*/
-
+} MsgPartEvaluatorStruct;
 
-
-/*
- * I wanna SEE that message!
- *
- * msgnum              Message number to display
- * printable_view      Nonzero to display a printable view
- * section             Optional for encapsulated message/rfc822 submessage
- */
-int read_message(StrBuf *Target, const char *tmpl, long tmpllen, long msgnum, int printable_view, const StrBuf *PartNum) 
+int load_message(message_summary *Msg, 
+                StrBuf *FoundCharset,
+                StrBuf **Error)
 {
        wcsession *WCC = WC;
        StrBuf *Buf;
        StrBuf *HdrToken;
-       StrBuf *FoundCharset;
-       HashPos  *it;
-       void *vMime;
-       message_summary *Msg = NULL;
        headereval *Hdr;
        void *vHdr;
        char buf[SIZ];
        int Done = 0;
        int state=0;
-       long len;
-       const char *Key;
-       WCTemplputParams SubTP;
-
+       
        Buf = NewStrBuf();
-       lprintf(1, "----------%s---------MSG4 %ld|%s--------------\n", tmpl, msgnum, ChrPtr(PartNum));
-       serv_printf("MSG4 %ld|%s", msgnum, ChrPtr(PartNum));
+       lprintf(9, "MSG4 %ld|%s\n", Msg->msgnum, ChrPtr(Msg->PartNum));
+       if (Msg->PartNum != NULL) {
+               serv_printf("MSG4 %ld|%s", Msg->msgnum, ChrPtr(Msg->PartNum));
+       }
+       else {
+               serv_printf("MSG4 %ld", Msg->msgnum);
+       }
+
        StrBuf_ServGetln(Buf);
        if (GetServerStatus(Buf, NULL) != 1) {
-               StrBufAppendPrintf(Target, "<strong>");
-               StrBufAppendPrintf(Target, _("ERROR:"));
-               StrBufAppendPrintf(Target, "</strong> %s<br />\n", &buf[4]);
+               *Error = NewStrBuf();
+               StrBufAppendPrintf(*Error, "<strong>");
+               StrBufAppendPrintf(*Error, _("ERROR:"));
+               StrBufAppendPrintf(*Error, "</strong> %s<br />\n", &buf[4]);
                FreeStrBuf(&Buf);
                return 0;
        }
 
        /** begin everythingamundo table */
-
-
        HdrToken = NewStrBuf();
-       Msg = (message_summary *)malloc(sizeof(message_summary));
-       memset(Msg, 0, sizeof(message_summary));
-       Msg->msgnum = msgnum;
-       Msg->PartNum = PartNum;
-       Msg->MsgBody =  (wc_mime_attachment*) malloc(sizeof(wc_mime_attachment));
-       memset(Msg->MsgBody, 0, sizeof(wc_mime_attachment));
-       Msg->MsgBody->msgnum = msgnum;
-       FoundCharset = NewStrBuf();
-       while ((StrBuf_ServGetlnBuffered(Buf)>=0) && !Done) {
+       while ((StrBuf_ServGetln(Buf)>=0) && !Done) {
                if ( (StrLength(Buf)==3) && 
                    !strcmp(ChrPtr(Buf), "000")) 
                {
                        Done = 1;
                        if (state < 2) {
                                lprintf(1, _("unexpected end of message"));
-                               
+                               if (Msg->MsgBody->Data == NULL)
+                                       Msg->MsgBody->Data = NewStrBuf();
                                Msg->MsgBody->ContentType = NewStrBufPlain(HKEY("text/html"));
                                StrBufAppendPrintf(Msg->MsgBody->Data, "<div><i>");
                                StrBufAppendPrintf(Msg->MsgBody->Data, _("unexpected end of message"));
@@ -121,11 +99,12 @@ int read_message(StrBuf *Target, const char *tmpl, long tmpllen, long msgnum, in
                                if (Hdr->Type == 1) {
                                        state++;
                                }
-                       }
+                       }/* TODO: 
                        else LogError(Target, 
                                      __FUNCTION__,  
                                      "don't know how to handle message header[%s]\n", 
                                      ChrPtr(HdrToken));
+                        */
                        break;
                case 1:/* Message Mime Header */
                        if (StrLength(Buf) == 0) {
@@ -176,28 +155,6 @@ int read_message(StrBuf *Target, const char *tmpl, long tmpllen, long msgnum, in
        /* now we put the body mimepart we read above into the mimelist */
        Put(Msg->AllAttach, SKEY(Msg->MsgBody->PartNum), Msg->MsgBody, DestroyMime);
        
-       /* strip the bare contenttype, so we ommit charset etc. */
-       StrBufExtract_token(Buf, Msg->MsgBody->ContentType, 0, ';');
-       StrBufTrim(Buf);
-       /* look up the renderer, that will convert this mimeitem into the htmlized form */
-       if (GetHash(MimeRenderHandler, SKEY(Buf), &vHdr) &&
-           (vHdr != NULL)) {
-               RenderMimeFuncStruct *Render;
-               Render = (RenderMimeFuncStruct*)vHdr;
-               Render->f(Msg->MsgBody, NULL, FoundCharset);
-       }
-
-       if (StrLength(Msg->reply_references)> 0) {
-               /* Trim down excessively long lists of thread references.  We eliminate the
-                * second one in the list so that the thread root remains intact.
-                */
-               int rrtok = num_tokens(ChrPtr(Msg->reply_references), '|');
-               int rrlen = StrLength(Msg->reply_references);
-               if ( ((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10) ) {
-                       StrBufRemove_token(Msg->reply_references, 1, '|');
-               }
-       }
-
        /* Generate a reply-to address */
        if (StrLength(Msg->Rfca) > 0) {
                if (Msg->reply_to == NULL)
@@ -230,6 +187,69 @@ int read_message(StrBuf *Target, const char *tmpl, long tmpllen, long msgnum, in
                        StrBufAppendBuf(Msg->reply_to, Msg->from, 0);
                }
        }
+       FreeStrBuf(&Buf);
+       FreeStrBuf(&HdrToken);
+       return 1;
+}
+
+
+
+/*
+ * I wanna SEE that message!
+ *
+ * msgnum              Message number to display
+ * printable_view      Nonzero to display a printable view
+ * section             Optional for encapsulated message/rfc822 submessage
+ */
+int read_message(StrBuf *Target, const char *tmpl, long tmpllen, long msgnum, const StrBuf *PartNum, const StrBuf **OutMime) 
+{
+       StrBuf *Buf;
+       StrBuf *FoundCharset;
+       HashPos  *it;
+       void *vMime;
+       message_summary *Msg = NULL;
+       void *vHdr;
+       long len;
+       const char *Key;
+       WCTemplputParams SubTP;
+       StrBuf *Error = NULL;
+
+       Buf = NewStrBuf();
+       FoundCharset = NewStrBuf();
+       Msg = (message_summary *)malloc(sizeof(message_summary));
+       memset(Msg, 0, sizeof(message_summary));
+       Msg->msgnum = msgnum;
+       Msg->PartNum = PartNum;
+       Msg->MsgBody =  (wc_mime_attachment*) malloc(sizeof(wc_mime_attachment));
+       memset(Msg->MsgBody, 0, sizeof(wc_mime_attachment));
+       Msg->MsgBody->msgnum = msgnum;
+
+       if (!load_message(Msg, FoundCharset, &Error)) {
+               StrBufAppendBuf(Target, Error, 0);
+               FreeStrBuf(&Error);
+       }
+
+       /* strip the bare contenttype, so we ommit charset etc. */
+       StrBufExtract_token(Buf, Msg->MsgBody->ContentType, 0, ';');
+       StrBufTrim(Buf);
+       /* look up the renderer, that will convert this mimeitem into the htmlized form */
+       if (GetHash(MimeRenderHandler, SKEY(Buf), &vHdr) &&
+           (vHdr != NULL)) {
+               RenderMimeFuncStruct *Render;
+               Render = (RenderMimeFuncStruct*)vHdr;
+               Render->f(Msg->MsgBody, NULL, FoundCharset);
+       }
+
+       if (StrLength(Msg->reply_references)> 0) {
+               /* Trim down excessively long lists of thread references.  We eliminate the
+                * second one in the list so that the thread root remains intact.
+                */
+               int rrtok = num_tokens(ChrPtr(Msg->reply_references), '|');
+               int rrlen = StrLength(Msg->reply_references);
+               if ( ((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10) ) {
+                       StrBufRemove_token(Msg->reply_references, 1, '|');
+               }
+       }
 
        /* now check if we need to translate some mimeparts, and remove the duplicate */
        it = GetNewHashPos(Msg->AllAttach, 0);
@@ -242,45 +262,208 @@ int read_message(StrBuf *Target, const char *tmpl, long tmpllen, long msgnum, in
        memset(&SubTP, 0, sizeof(WCTemplputParams));
        SubTP.Filter.ContextType = CTX_MAILSUM;
        SubTP.Context = Msg;
-       DoTemplate(tmpl, tmpllen, Target, &SubTP);
+       *OutMime = DoTemplate(tmpl, tmpllen, Target, &SubTP);
 
        DestroyMessageSummary(Msg);
        FreeStrBuf(&FoundCharset);
-       FreeStrBuf(&HdrToken);
        FreeStrBuf(&Buf);
        return 1;
 }
 
 
+void
+HttpStatus(long CitadelStatus)
+{
+       long httpstatus = 502;
+       
+       switch (MAJORCODE(CitadelStatus))
+       {
+       case LISTING_FOLLOWS:
+       case CIT_OK:
+               httpstatus = 201;
+               break;
+       case ERROR:
+               switch (MINORCODE(CitadelStatus))
+               {
+               case INTERNAL_ERROR:
+                       httpstatus = 403;
+                       break;
+                       
+               case TOO_BIG:
+               case ILLEGAL_VALUE:
+               case HIGHER_ACCESS_REQUIRED:
+               case MAX_SESSIONS_EXCEEDED:
+               case RESOURCE_BUSY:
+               case RESOURCE_NOT_OPEN:
+               case NOT_HERE:
+               case INVALID_FLOOR_OPERATION:
+               case FILE_NOT_FOUND:
+               case ROOM_NOT_FOUND:
+                       httpstatus = 409;
+                       break;
+
+               case MESSAGE_NOT_FOUND:
+               case ALREADY_EXISTS:
+                       httpstatus = 403;
+                       break;
+
+               case NO_SUCH_SYSTEM:
+                       httpstatus = 502;
+                       break;
+
+               default:
+               case CMD_NOT_SUPPORTED:
+               case PASSWORD_REQUIRED:
+               case ALREADY_LOGGED_IN:
+               case USERNAME_REQUIRED:
+               case NOT_LOGGED_IN:
+               case SERVER_SHUTTING_DOWN:
+               case NO_SUCH_USER:
+               case ASYNC_GEXP:
+                       httpstatus = 502;
+                       break;
+               }
+               break;
+
+       default:
+       case BINARY_FOLLOWS:
+       case SEND_BINARY:
+       case START_CHAT_MODE:
+       case ASYNC_MSG:
+       case MORE_DATA:
+       case SEND_LISTING:
+               httpstatus = 502; /* aeh... whut? */
+               break;
+       }
+
+
+}
+
+/*
+ * Unadorned HTML output of an individual message, suitable
+ * for placing in a hidden iframe, for printing, or whatever
+ */
+void handle_one_message(void) 
+{
+       long CitStatus;
+       int CopyMessage = 0;
+       const StrBuf *Destination;
+       void *vLine;
+       const StrBuf *Mime;
+       long msgnum = 0L;
+       wcsession *WCC = WC;
+       const StrBuf *Tmpl;
+       StrBuf *CmdBuf = NULL;
+       const char *pMsg;
+
+
+       pMsg = strchr(ChrPtr(WCC->Hdr->HR.ReqLine), '/');
+       if (pMsg == NULL) {
+               HttpStatus(CitStatus);
+               return;
+       }
+
+       msgnum = atol(pMsg + 1);
+       StrBufCutAt(WCC->Hdr->HR.ReqLine, 0, pMsg);
+       gotoroom(WCC->Hdr->HR.ReqLine);
+       switch (WCC->Hdr->HR.eReqType)
+       {
+       case eGET:
+       case ePOST:
+               Tmpl = sbstr("template");
+               if (StrLength(Tmpl) > 0) 
+                       read_message(WCC->WBuf, SKEY(Tmpl), msgnum, NULL, &Mime);
+               else 
+                       read_message(WCC->WBuf, HKEY("view_message"), msgnum, NULL, &Mime);
+               http_transmit_thing(ChrPtr(Mime), 0);
+               break;
+       case eDELETE:
+               CmdBuf = NewStrBuf ();
+               if (WCC->wc_is_trash) { /** Delete from Trash is a real delete */
+                       serv_printf("DELE %ld", msgnum);        
+               }
+               else {                  /** Otherwise move it to Trash */
+                       serv_printf("MOVE %ld|_TRASH_|0", msgnum);
+               }
+               StrBuf_ServGetln(CmdBuf);
+               FlushStrBuf(WCC->ImportantMsg);
+               StrBufAppendBuf(WCC->ImportantMsg, CmdBuf, 4);
+               GetServerStatus(CmdBuf, &CitStatus);
+               HttpStatus(CitStatus);
+               break;
+       case eCOPY:
+               CopyMessage = 1;
+       case eMOVE:
+               if (GetHash(WCC->Hdr->HTTPHeaders, HKEY("DESTINATION"), &vLine) &&
+                   (vLine!=NULL)) {
+                       Destination = (StrBuf*) vLine;
+                       serv_printf("MOVE %ld|%s|%d", msgnum, ChrPtr(Destination), CopyMessage);
+                       StrBuf_ServGetln(CmdBuf);
+                       FlushStrBuf(WCC->ImportantMsg);
+                       StrBufAppendBuf(WCC->ImportantMsg, CmdBuf, 4);
+                       GetServerStatus(CmdBuf, &CitStatus);
+                       HttpStatus(CitStatus);
+               }
+               else
+                       HttpStatus(500);
+               break;
+       default:
+               break;
+
+       }
+}
+
 
 /*
  * Unadorned HTML output of an individual message, suitable
  * for placing in a hidden iframe, for printing, or whatever
- *
- * msgnum_as_string == Message number, as a string instead of as a long int
  */
 void embed_message(void) {
+       const StrBuf *Mime;
        long msgnum = 0L;
        wcsession *WCC = WC;
-       const StrBuf *Tmpl = sbstr("template");
+       const StrBuf *Tmpl;
+       StrBuf *CmdBuf = NULL;
 
-       msgnum = StrTol(WCC->UrlFragment2);
-       if (StrLength(Tmpl) > 0) 
-               read_message(WCC->WBuf, SKEY(Tmpl), msgnum, 0, NULL);
-       else 
-               read_message(WCC->WBuf, HKEY("view_message"), msgnum, 0, NULL);
+       msgnum = StrBufExtract_long(WCC->Hdr->HR.ReqLine, 0, '/');
+       switch (WCC->Hdr->HR.eReqType)
+       {
+       case eGET:
+       case ePOST:
+               Tmpl = sbstr("template");
+               if (StrLength(Tmpl) > 0) 
+                       read_message(WCC->WBuf, SKEY(Tmpl), msgnum, NULL, &Mime);
+               else 
+                       read_message(WCC->WBuf, HKEY("view_message"), msgnum, NULL, &Mime);
+               http_transmit_thing(ChrPtr(Mime), 0);
+               break;
+       case eDELETE:
+               CmdBuf = NewStrBuf ();
+               if (WCC->wc_is_trash) { /** Delete from Trash is a real delete */
+                       serv_printf("DELE %ld", msgnum);        
+               }
+               else {                  /** Otherwise move it to Trash */
+                       serv_printf("MOVE %ld|_TRASH_|0", msgnum);
+               }
+               StrBuf_ServGetln(CmdBuf);
+               FlushStrBuf(WCC->ImportantMsg);
+               StrBufAppendBuf(WCC->ImportantMsg, CmdBuf, 4);
+               break;
+       default:
+               break;
+
+       }
 }
 
 
 /*
  * Printable view of a message
- *
- * msgnum_as_string == Message number, as a string instead of as a long int
  */
 void print_message(void) {
        long msgnum = 0L;
+       const StrBuf *Mime;
 
-       msgnum = StrTol(WC->UrlFragment2);
+       msgnum = StrBufExtract_long(WC->Hdr->HR.ReqLine, 0, '/');
        output_headers(0, 0, 0, 0, 0, 0);
 
        hprintf("Content-type: text/html\r\n"
@@ -289,36 +472,35 @@ void print_message(void) {
 
        begin_burst();
 
-       read_message(WC->WBuf, HKEY("view_message_print"), msgnum, 1, NULL);
+       read_message(WC->WBuf, HKEY("view_message_print"), msgnum, NULL, &Mime);
 
        wDumpContent(0);
 }
 
 /* 
  * Mobile browser view of message
- *
- * @param msg_num_as_string Message number as a string instead of as a long int 
  */
-void mobile_message_view(void) {
-  long msgnum = 0L;
-  msgnum = StrTol(WC->UrlFragment2);
-  output_headers(1, 0, 0, 0, 0, 1);
-  begin_burst();
-  do_template("msgcontrols", NULL);
-  read_message(WC->WBuf, HKEY("view_message"), msgnum,1, NULL);
-  wDumpContent(0);
+void mobile_message_view(void) 
+{
+       long msgnum = 0L;
+       const StrBuf *Mime;
+  
+       msgnum = StrBufExtract_long(WC->Hdr->HR.ReqLine, 0, '/');
+       output_headers(1, 0, 0, 0, 0, 1);
+       begin_burst();
+       do_template("msgcontrols", NULL);
+       read_message(WC->WBuf, HKEY("view_message"), msgnum, NULL, &Mime);
+       wDumpContent(0);
 }
 
-/**
- * \brief Display a message's headers
- *
- * \param msgnum_as_string Message number, as a string instead of as a long int
+/*
+ * Display a message's headers
  */
 void display_headers(void) {
        long msgnum = 0L;
        char buf[1024];
 
-       msgnum = StrTol(WC->UrlFragment2);
+       msgnum = StrBufExtract_long(WC->Hdr->HR.ReqLine, 0, '/');
        output_headers(0, 0, 0, 0, 0, 0);
 
        hprintf("Content-type: text/plain\r\n"
@@ -354,7 +536,7 @@ message_summary *ReadOneMessageSummary(StrBuf *RawMessage, const char *DefaultSu
 
        serv_printf("MSG0 %ld|1", MsgNum);      /* ask for headers only */
        
-       StrBuf_ServGetlnBuffered(Buf);
+       StrBuf_ServGetln(Buf);
        if (GetServerStatus(Buf, NULL) == 1) {
                FreeStrBuf(&Buf);
                return NULL;
@@ -362,7 +544,7 @@ message_summary *ReadOneMessageSummary(StrBuf *RawMessage, const char *DefaultSu
 
        Msg = (message_summary*)malloc(sizeof(message_summary));
        memset(Msg, 0, sizeof(message_summary));
-       while (len = StrBuf_ServGetlnBuffered(Buf),
+       while (len = StrBuf_ServGetln(Buf),
               ((len != 3)  ||
                strcmp(ChrPtr(Buf), "000")== 0)){
                buf = ChrPtr(Buf);
@@ -388,7 +570,7 @@ message_summary *ReadOneMessageSummary(StrBuf *RawMessage, const char *DefaultSu
  * servcmd:            the citadel command to send to the citserver
  * with_headers:       also include some of the headers with the message numbers (more expensive)
  */
-int load_msg_ptrs(char *servcmd, int with_headers)
+int load_msg_ptrs(const char *servcmd, int with_headers)
 {
        StrBuf* FoundCharset = NULL;
         wcsession *WCC = WC;
@@ -399,7 +581,7 @@ int load_msg_ptrs(char *servcmd, int with_headers)
        long len;
        int n;
        int skipit;
-       const char *Ptr;
+       const char *Ptr = NULL;
 
        if (WCC->summ != NULL) {
                DeleteHash(&WCC->summ);
@@ -409,13 +591,13 @@ int load_msg_ptrs(char *servcmd, int with_headers)
        
        Buf = NewStrBuf();
        serv_puts(servcmd);
-       StrBuf_ServGetlnBuffered(Buf);
+       StrBuf_ServGetln(Buf);
        if (GetServerStatus(Buf, NULL) != 1) {
                FreeStrBuf(&Buf);
                return (nummsgs);
        }
        Buf2 = NewStrBuf();
-       while (len = StrBuf_ServGetlnBuffered(Buf),
+       while (len = StrBuf_ServGetln(Buf),
               ((len != 3)  ||
                strcmp(ChrPtr(Buf), "000")!= 0))
        {
@@ -427,6 +609,11 @@ int load_msg_ptrs(char *servcmd, int with_headers)
 
                        Msg->msgnum = StrBufExtractNext_long(Buf, &Ptr, '|');
                        Msg->date = StrBufExtractNext_long(Buf, &Ptr, '|');
+
+                       if ((Msg->msgnum == 0) && (StrLength(Buf) < 32)) {
+                               free(Msg);
+                               continue;
+                       }
                        /* 
                         * as citserver probably gives us messages in forward date sorting
                         * nummsgs should be the same order as the message date.
@@ -440,11 +627,11 @@ int load_msg_ptrs(char *servcmd, int with_headers)
                                Msg->from = NewStrBufPlain(NULL, StrLength(Buf));
                                StrBufExtract_NextToken(Buf2, Buf, &Ptr, '|');
                                if (StrLength(Buf2) != 0) {
-                                       /** Handle senders with RFC2047 encoding */
+                                       /* Handle senders with RFC2047 encoding */
                                        StrBuf_RFC822_to_Utf8(Msg->from, Buf2, WCC->DefaultCharset, FoundCharset);
                                }
                        
-                               /** Nodename */
+                               /* node name */
                                StrBufExtract_NextToken(Buf2, Buf, &Ptr, '|');
                                if ((StrLength(Buf2) !=0 ) &&
                                    ( ((WCC->room_flags & QR_NETWORK)
@@ -455,9 +642,9 @@ int load_msg_ptrs(char *servcmd, int with_headers)
                                        StrBufAppendBuf(Msg->from, Buf2, 0);
                                }
 
-                               /** Not used:
-                                   StrBufExtract_token(Msg->inetaddr, Buf, 4, '|');
-                               */
+                               /* Internet address (not used)
+                                *      StrBufExtract_token(Msg->inetaddr, Buf, 4, '|');
+                                */
                                StrBufSkip_NTokenS(Buf, &Ptr, '|', 1);
                                Msg->subj = NewStrBufPlain(NULL, StrLength(Buf));
                                StrBufExtract_NextToken(Buf2,  Buf, &Ptr, '|');
@@ -472,7 +659,6 @@ int load_msg_ptrs(char *servcmd, int with_headers)
                                        }
                                }
 
-
                                if ((StrLength(Msg->from) > 25) && 
                                    (StrBuf_Utf8StrLen(Msg->from) > 25)) {
                                        StrBuf_Utf8StrCut(Msg->from, 23);
@@ -521,7 +707,7 @@ long DrawMessageDropdown(StrBuf *Selector, long maxmsgs, long startmsg, int nMes
        memset(&SubTP, 0, sizeof(WCTemplputParams));
        SubTP.Filter.ContextType = CTX_LONGVECTOR;
        SubTP.Context = &vector;
-       TmpBuf = NewStrBuf();
+       TmpBuf = NewStrBufPlain(NULL, SIZ);
        At = GetNewHashPos(WCC->summ, nMessages);
        nItems = GetCount(WCC->summ);
        ret = nMessages;
@@ -562,7 +748,7 @@ long DrawMessageDropdown(StrBuf *Selector, long maxmsgs, long startmsg, int nMes
                }
                done = !GetNextHashPos(WCC->summ, At, &hklen, &key, &vMsg);
                
-               /**
+               /*
                 * Bump these because although we're thinking in zero base, the user
                 * is a drooling idiot and is thinking in one base.
                 */
@@ -666,14 +852,15 @@ void readloop(long oper)
        int defaultsortorder = 0;
        WCTemplputParams SubTP;
        char *ab_name;
+       const StrBuf *Mime;
 
        if (havebstr("is_summary") && (1 == (ibstr("is_summary"))))
                WCC->wc_view = VIEW_MAILBOX;
 
        if (!WCC->is_ajax) {
-       output_headers(1, 1, 1, 0, 0, 0);
+               output_headers(1, 1, 1, 0, 0, 0);
        } else if (WCC->wc_view == VIEW_MAILBOX) {
-         jsonMessageListHdr();
+               jsonMessageListHdr();
        }
 
        switch (WCC->wc_view) {
@@ -796,13 +983,13 @@ void readloop(long oper)
 
        if (load_seen) load_seen_flags();
        
-        /**
-        * If we're to print s.th. above the message list...
+        /*
+        * Print any inforation above the message list...
         */
        switch (WCC->wc_view) {
        case VIEW_BBS:
-               BBViewToolBar = NewStrBuf();
-               MessageDropdown = NewStrBuf();
+               BBViewToolBar = NewStrBufPlain(NULL, SIZ);
+               MessageDropdown = NewStrBufPlain(NULL, SIZ);
 
                maxmsgs = DrawMessageDropdown(MessageDropdown, maxmsgs, startmsg, num_displayed);
                if (num_displayed < 0) {
@@ -829,13 +1016,11 @@ void readloop(long oper)
        svputlong("READLOOP:TOTALMSGS", nummsgs);
        svputlong("READLOOP:STARTMSG", startmsg);
        svputlong("WCVIEW", WCC->wc_view);
+
        /*
         * iterate over each message. if we need to load an attachment, do it here. 
         */
        if (WCC->wc_view == VIEW_MAILBOX) goto NO_MSG_LOOP;
-       /*
-        * iterate over each message. if we need to load an attachment, do it here. 
-        */
        at = GetNewHashPos(WCC->summ, 0);
        num_displayed = i = 0;
        while (GetNextHashPos(WCC->summ, at, &HKLen, &HashKey, &vMsg)) {
@@ -857,6 +1042,8 @@ void readloop(long oper)
                        case VIEW_ADDRESSBOOK:
                                ab_name = NULL;
                                fetch_ab_name(Msg, &ab_name);
+                               if (ab_name == NULL) 
+                                       break;
                                ++num_ab;
                                addrbook = realloc(addrbook,
                                                   (sizeof(addrbookent) * num_ab) );
@@ -884,7 +1071,7 @@ void readloop(long oper)
        }
        DeleteHashPos(&at);
 
- NO_MSG_LOOP:  
+NO_MSG_LOOP:
        /*
         * Done iterating the message list. now tasks we want to do after.
         */
@@ -897,10 +1084,10 @@ void readloop(long oper)
                        /** if we do a split bbview in the future, begin messages div here */
                        
                        for (a=0; a<num_displayed; ++a) {
-                               read_message(WCC->WBuf, HKEY("view_message"), displayed_msgs[a], 0, NULL);
+                               read_message(WCC->WBuf, HKEY("view_message"), displayed_msgs[a], NULL, &Mime);
                        }
                        
-                       /** if we do a split bbview in the future, end messages div here */
+                       /* if we do a split bbview in the future, end messages div here */
                        
                        free(displayed_msgs);
                        displayed_msgs = NULL;
@@ -931,9 +1118,9 @@ DONE:
                break;
        case VIEW_ADDRESSBOOK:
                if (is_singlecard)
-                       read_message(WC->WBuf, HKEY("view_message"), lbstr("startmsg"), 0, NULL);
+                       read_message(WC->WBuf, HKEY("view_message"), lbstr("startmsg"), NULL, &Mime);
                else
-                       do_addrbook_view(addrbook, num_ab);     /** Render the address book */
+                       do_addrbook_view(addrbook, num_ab);     /* Render the address book */
                break;
        case VIEW_MAILBOX: 
        case VIEW_BBS:
@@ -1077,6 +1264,7 @@ void post_message(void)
        int is_anonymous = 0;
        const StrBuf *display_name = NULL;
        wcsession *WCC = WC;
+       StrBuf *Buf;
        
        if (havebstr("force_room")) {
                gotoroom(sbstr("force_room"));
@@ -1095,22 +1283,23 @@ void post_message(void)
                int n;
                char N[64];
 
-               lprintf(9, "%s:%d: we are uploading %d bytes\n", __FILE__, __LINE__, WCC->upload_length);
-               /** There's an attachment.  Save it to this struct... */
+               /* There's an attachment.  Save it to this struct... */
+               lprintf(9, "Client is uploading %d bytes\n", WCC->upload_length);
                att = malloc(sizeof(wc_mime_attachment));
                memset(att, 0, sizeof(wc_mime_attachment ));
                att->length = WCC->upload_length;
                att->ContentType = NewStrBufPlain(WCC->upload_content_type, -1);
                att->FileName = NewStrBufPlain(WCC->upload_filename, -1);
                
-               
-               if (WCC->attachments == NULL)
+               if (WCC->attachments == NULL) {
                        WCC->attachments = NewHash(1, NULL);
+               }
+
                /* And add it to the list. */
                n = snprintf(N, sizeof N, "%d", GetCount(WCC->attachments) + 1);
                Put(WCC->attachments, N, n, att, DestroyMime);
 
-               /**
+               /*
                 * Mozilla sends a simple filename, which is what we want,
                 * but Satan's Browser sends an entire pathname.  Reduce
                 * the path to just a filename if we need to.
@@ -1124,14 +1313,13 @@ void post_message(void)
                        StrBufCutLeft(att->FileName, pch - ChrPtr(att->FileName) + 1);
                }
 
-               /**
+               /*
                 * Transfer control of this memory from the upload struct
                 * to the attachment struct.
                 */
-               att->Data = NewStrBufPlain(WCC->upload, WCC->upload_length);
-               free(WCC->upload);
-               WCC->upload_length = 0;
+               att->Data = WCC->upload;
                WCC->upload = NULL;
+               WCC->upload_length = 0;
                display_enter();
                return;
        }
@@ -1155,11 +1343,31 @@ void post_message(void)
                const StrBuf *my_email_addr = NULL;
                StrBuf *CmdBuf = NULL;
                StrBuf *references = NULL;
+               int save_to_drafts;
+
+               save_to_drafts = havebstr("save_button");
+               Buf = NewStrBuf();
+
+               if (save_to_drafts) {
+                       /* temporarily change to the drafts room */
+                       serv_puts("GOTO _DRAFTS_");
+                       StrBuf_ServGetln(Buf);
+                       if (GetServerStatus(Buf, NULL) != 2) {
+                               /* You probably don't even have a dumb Drafts folder */
+                               StrBufCutLeft(Buf, 4);
+                               lprintf(9, "%s:%d: server save to drafts error: %s\n", __FILE__, __LINE__, ChrPtr(Buf));
+                               StrBufAppendBufPlain(WCC->ImportantMsg, _("Saved to Drafts failed: "), -1, 0);
+                               StrBufAppendBuf(WCC->ImportantMsg, Buf, 0);
+                               display_enter();
+                               FreeStrBuf(&Buf);
+                               return;
+                       }
+               }
 
                if (havebstr("references"))
                {
                        const StrBuf *ref = sbstr("references");
-                       references = NewStrBufPlain(ChrPtr(ref), StrLength(ref));
+                       references = NewStrBufDup(ref);
                        if (*ChrPtr(references) == '|') {       /* remove leading '|' if present */
                                StrBufCutLeft(references, 1);
                        }
@@ -1193,59 +1401,83 @@ void post_message(void)
 
                StrBufPrintf(CmdBuf, 
                             CMD,
-                            ChrPtr(Recp),
+                            save_to_drafts?"":ChrPtr(Recp),
                             is_anonymous,
                             ChrPtr(encoded_subject),
                             ChrPtr(display_name),
-                            ChrPtr(Cc),
-                            ChrPtr(Bcc),
+                            save_to_drafts?"":ChrPtr(Cc),
+                            save_to_drafts?"":ChrPtr(Bcc),
                             ChrPtr(Wikipage),
                             ChrPtr(my_email_addr),
                             ChrPtr(references));
                FreeStrBuf(&references);
+               FreeStrBuf(&encoded_subject);
 
                lprintf(9, "%s\n", ChrPtr(CmdBuf));
                serv_puts(ChrPtr(CmdBuf));
-               serv_getln(buf, sizeof buf);
                FreeStrBuf(&CmdBuf);
-               FreeStrBuf(&encoded_subject);
-               if (buf[0] == '4') {
+
+               StrBuf_ServGetln(Buf);
+               if (GetServerStatus(Buf, NULL) == 4) {
+                       if (save_to_drafts) {
+                               if (  (havebstr("recp"))
+                                   || (havebstr("cc"  ))
+                                   || (havebstr("bcc" )) ) {
+                                       /* save recipient headers or room to post to */
+                                       serv_printf("To: %s", ChrPtr(Recp));
+                                       serv_printf("Cc: %s", ChrPtr(Cc));
+                                       serv_printf("Bcc: %s", ChrPtr(Bcc));
+                               } else {
+                                       serv_printf("X-Citadel-Room: %s", ChrPtr(WC->wc_roomname));
+                               }
+                       }
                        post_mime_to_server();
-                       if (  (havebstr("recp"))
+                       if (save_to_drafts) {
+                               StrBufAppendBufPlain(WCC->ImportantMsg, _("Message has been saved to Drafts.\n"), -1, 0);
+                               gotoroom(WCC->wc_roomname);
+                               display_enter();
+                               FreeStrBuf(&Buf);
+                               return;
+                       } else if (  (havebstr("recp"))
                           || (havebstr("cc"  ))
                           || (havebstr("bcc" ))
                        ) {
-                               sprintf(WCC->ImportantMessage, _("Message has been sent.\n"));
+                               StrBufAppendBufPlain(WCC->ImportantMsg, _("Message has been sent.\n"), -1, 0);
                        }
                        else {
-                               sprintf(WC->ImportantMessage, _("Message has been posted.\n"));
+                               StrBufAppendBufPlain(WCC->ImportantMsg, _("Message has been posted.\n"), -1, 0);
                        }
                        dont_post = lbstr("postseq");
                } else {
-                       lprintf(9, "%s:%d: server post error: %s\n", __FILE__, __LINE__, buf);
-                       sprintf(WC->ImportantMessage, "%s", &buf[4]);
+                       StrBufCutLeft(Buf, 4);
+
+                       lprintf(9, "%s:%d: server post error: %s\n", __FILE__, __LINE__, ChrPtr(Buf));
+                       StrBufAppendBuf(WCC->ImportantMsg, Buf, 0);
+                       if (save_to_drafts) gotoroom(WCC->wc_roomname);
                        display_enter();
+                       FreeStrBuf(&Buf);
                        return;
                }
+               FreeStrBuf(&Buf);
        }
 
        DeleteHash(&WCC->attachments);
 
-       /**
+       /*
         *  We may have been supplied with instructions regarding the location
         *  to which we must return after posting.  If found, go there.
         */
        if (havebstr("return_to")) {
                http_redirect(bstr("return_to"));
        }
-       /**
+       /*
         *  If we were editing a page in a wiki room, go to that page now.
         */
        else if (havebstr("wikipage")) {
                snprintf(buf, sizeof buf, "wiki?page=%s", bstr("wikipage"));
                http_redirect(buf);
        }
-       /**
+       /*
         *  Otherwise, just go to the "read messages" loop.
         */
        else {
@@ -1256,8 +1488,8 @@ void post_message(void)
 
 
 
-/**
- * \brief display the message entry screen
+/*
+ * display the message entry screen
  */
 void display_enter(void)
 {
@@ -1282,14 +1514,14 @@ void display_enter(void)
                is_anonymous = 1;
        }
 
-       /** First test to see whether this is a room that requires recipients to be entered */
+       /* First test to see whether this is a room that requires recipients to be entered */
        serv_puts("ENT0 0");
        serv_getln(buf, sizeof buf);
 
-       if (!strncmp(buf, "570", 3)) {          /** 570 means that we need a recipient here */
+       if (!strncmp(buf, "570", 3)) {          /* 570 means that we need a recipient here */
                recipient_required = 1;
        }
-       else if (buf[0] != '2') {               /** Any other error means that we cannot continue */
+       else if (buf[0] != '2') {               /* Any other error means that we cannot continue */
                sprintf(WCC->ImportantMessage, "%s", &buf[4]);
                readloop(readnew);
                return;
@@ -1305,7 +1537,7 @@ void display_enter(void)
         * message" command really means "add new entry."
         */
        if (WCC->wc_default_view == VIEW_ADDRESSBOOK) {
-               do_edit_vcard(-1, "", "", ChrPtr(WCC->wc_roomname));
+               do_edit_vcard(-1, "", NULL, NULL, "",  ChrPtr(WCC->wc_roomname));
                return;
        }
 
@@ -1365,15 +1597,15 @@ void display_enter(void)
                serv_getln(buf, sizeof buf);
                FreeStrBuf(&CmdBuf);
 
-               if (!strncmp(buf, "570", 3)) {  /** 570 means we have an invalid recipient listed */
+               if (!strncmp(buf, "570", 3)) {  /* 570 means we have an invalid recipient listed */
                        if (havebstr("recp") && 
                            havebstr("cc"  ) && 
                            havebstr("bcc" )) {
                                recipient_bad = 1;
                        }
                }
-               else if (buf[0] != '2') {       /** Any other error means that we cannot continue */
-                       wprintf("<em>%s</em><br />\n", &buf[4]);/*TODO -> important message */
+               else if (buf[0] != '2') {       /* Any other error means that we cannot continue */
+                       wprintf("<em>%s</em><br />\n", &buf[4]);        /* TODO -> important message */
                        return;
                }
        }
@@ -1388,8 +1620,8 @@ void display_enter(void)
        return;
 }
 
-/**
- * \brief delete a message
+/*
+ * delete a message
  */
 void delete_msg(void)
 {
@@ -1398,22 +1630,21 @@ void delete_msg(void)
 
        msgid = lbstr("msgid");
 
-       if (WC->wc_is_trash) {  /** Delete from Trash is a real delete */
+       if (WC->wc_is_trash) {  /* Delete from Trash is a real delete */
                serv_printf("DELE %ld", msgid); 
        }
-       else {                  /** Otherwise move it to Trash */
+       else {                  /* Otherwise move it to Trash */
                serv_printf("MOVE %ld|_TRASH_|0", msgid);
        }
 
        serv_getln(buf, sizeof buf);
        sprintf(WC->ImportantMessage, "%s", &buf[4]);
-
        readloop(readnew);
 }
 
 
-/**
- * \brief move a message to another folder
+/*
+ * move a message to another room
  */
 void move_msg(void)
 {
@@ -1435,9 +1666,6 @@ void move_msg(void)
 }
 
 
-
-
-
 /*
  * Confirm move of a message
  */
@@ -1515,7 +1743,6 @@ void postpart(StrBuf *partnum, StrBuf *filename, int force_download)
                else {
                        content_type = NewStrBufDup(part->ContentType);
                }
-               output_headers(0, 0, 0, 0, 0, 0);
                StrBufAppendBuf(WC->WBuf, part->Data, 0);
                http_transmit_thing(ChrPtr(content_type), 0);
        } else {
@@ -1540,71 +1767,77 @@ void postpart(StrBuf *partnum, StrBuf *filename, int force_download)
  */
 void mimepart(int force_download)
 {
+       long msgnum;
+       StrBuf *att;
        wcsession *WCC = WC;
-
-       char buf[256];
+       StrBuf *Buf;
        off_t bytes;
-       char content_type[256];
-       const char *ContentType = &content_type[0];
+       StrBuf *ContentType = NewStrBufPlain(HKEY("application/octet-stream"));
+       const char *CT;
 
-       serv_printf("OPNA %s|%s", ChrPtr(WCC->UrlFragment2), ChrPtr(WCC->UrlFragment3));
-       serv_getln(buf, sizeof buf);
-       if (buf[0] == '2') {
-               bytes = extract_long(&buf[4], 0);
-               if (force_download) {
-                       strcpy(content_type, "application/octet-stream");
-               }
-               else {
-                       extract_token(content_type, &buf[4], 3, '|', sizeof content_type);
+       att = Buf = NewStrBuf();
+       msgnum = StrBufExtract_long(WCC->Hdr->HR.ReqLine, 0, '/');
+       StrBufExtract_token(att, WCC->Hdr->HR.ReqLine, 1, '/');
+
+       serv_printf("OPNA %ld|%s", msgnum, ChrPtr(att));
+       StrBuf_ServGetln(Buf);
+       if (GetServerStatus(Buf, NULL) == 2) {
+               StrBufCutLeft(Buf, 4);
+               bytes = StrBufExtract_long(Buf, 0, '|');
+               if (!force_download) {
+                       StrBufExtract_token(ContentType, Buf, 3, '|');
                }
 
-               read_server_binary(WCC->WBuf, bytes);
+               read_server_binary(WCC->WBuf, bytes, Buf);
                serv_puts("CLOS");
-               serv_getln(buf, sizeof buf);
+               StrBuf_ServGetln(Buf);
+               CT = ChrPtr(ContentType);
 
                if (!force_download) {
-                       if (!strcasecmp(ContentType, "application/octet-stream")) {
-                               ContentType = GuessMimeByFilename(SKEY(WCC->UrlFragment4));
+                       if (!strcasecmp(ChrPtr(ContentType), "application/octet-stream")) {
+                               StrBufExtract_token(Buf, WCC->Hdr->HR.ReqLine, 2, '/');
+                               CT = GuessMimeByFilename(SKEY(Buf));
                        }
-                       if (!strcasecmp(ContentType, "application/octet-stream")) {
-                               ContentType = GuessMimeType(SKEY(WCC->WBuf));
+                       if (!strcasecmp(ChrPtr(ContentType), "application/octet-stream")) {
+                               CT = GuessMimeType(SKEY(WCC->WBuf));
                        }
                }
-               output_headers(0, 0, 0, 0, 0, 0);
-               http_transmit_thing(ContentType, 0);
+               http_transmit_thing(CT, 0);
        } else {
-               hprintf("HTTP/1.1 404 %s\n", &buf[4]);
+               StrBufCutLeft(Buf, 4);
+               hprintf("HTTP/1.1 404 %s\n", ChrPtr(Buf));
                output_headers(0, 0, 0, 0, 0, 0);
                hprintf("Content-Type: text/plain\r\n");
-               wprintf(_("An error occurred while retrieving this part: %s\n"), &buf[4]);
+               wprintf(_("An error occurred while retrieving this part: %s\n"), 
+                       ChrPtr(Buf));
                end_burst();
        }
+       FreeStrBuf(&ContentType);
+       FreeStrBuf(&Buf);
 }
 
 
 /*
  * Read any MIME part of a message, from the server, into memory.
  */
-char *load_mimepart(long msgnum, char *partnum)
+StrBuf *load_mimepart(long msgnum, char *partnum)
 {
-       char buf[SIZ];
        off_t bytes;
-       char content_type[SIZ];
-       char *content;
+       StrBuf *Buf;
        
+       Buf = NewStrBuf();
        serv_printf("DLAT %ld|%s", msgnum, partnum);
-       serv_getln(buf, sizeof buf);
-       if (buf[0] == '6') {
-               bytes = extract_long(&buf[4], 0);
-               extract_token(content_type, &buf[4], 3, '|', sizeof content_type);
-
-               content = malloc(bytes + 2);
-               serv_read(content, bytes);
-
-               content[bytes] = 0;     /* null terminate for good measure */
-               return(content);
+       StrBuf_ServGetln(Buf);
+       if (GetServerStatus(Buf, NULL) == 6) {
+               StrBufCutLeft(Buf, 4);
+               bytes = StrBufExtract_long(Buf, 0, '|');
+               FreeStrBuf(&Buf);
+               Buf = NewStrBuf();
+               StrBuf_ServGetBLOBBuffered(Buf, bytes);
+               return(Buf);
        }
        else {
+               FreeStrBuf(&Buf);
                return(NULL);
        }
 }
@@ -1616,10 +1849,10 @@ void MimeLoadData(wc_mime_attachment *Mime)
 {
        StrBuf *Buf;
        off_t bytes;
-/* TODO: is there a chance the contenttype is different  to the one we know?    */
+       /* TODO: is there a chance the content type is different from the one we know? */
        serv_printf("DLAT %ld|%s", Mime->msgnum, ChrPtr(Mime->PartNum));
        Buf = NewStrBuf();
-       StrBuf_ServGetlnBuffered(Buf);
+       StrBuf_ServGetln(Buf);
        if (GetServerStatus(Buf, NULL) == 6) {
                bytes = extract_long(&(ChrPtr(Buf)[4]), 0);
                                     
@@ -1646,15 +1879,29 @@ void download_mimepart(void) {
 }
 
 void view_postpart(void) {
-       postpart(WC->UrlFragment2,
-                WC->UrlFragment3,
-                0);
+       StrBuf *filename = NewStrBuf();
+       StrBuf *partnum = NewStrBuf();
+
+       StrBufExtract_token(partnum, WC->Hdr->HR.ReqLine, 0, '/');
+       StrBufExtract_token(filename, WC->Hdr->HR.ReqLine, 1, '/');
+
+       postpart(partnum, filename, 0);
+
+       FreeStrBuf(&filename);
+       FreeStrBuf(&partnum);
 }
 
 void download_postpart(void) {
-       postpart(WC->UrlFragment2,
-                WC->UrlFragment3,
-                1);
+       StrBuf *filename = NewStrBuf();
+       StrBuf *partnum = NewStrBuf();
+
+       StrBufExtract_token(partnum, WC->Hdr->HR.ReqLine, 0, '/');
+       StrBufExtract_token(filename, WC->Hdr->HR.ReqLine, 1, '/');
+
+       postpart(partnum, filename, 1);
+
+       FreeStrBuf(&filename);
+       FreeStrBuf(&partnum);
 }
 
 void h_readnew(void) { readloop(readnew);}
@@ -1666,28 +1913,30 @@ void h_do_search(void) { readloop(do_search);}
 void jsonMessageListHdr(void) 
 {
        /* TODO: make a generic function */
-  hprintf("HTTP/1.1 200 OK\r\n");
-  hprintf("Content-type: application/json; charset=utf-8\r\n");
-  hprintf("Server: %s / %s\r\n", PACKAGE_STRING, ChrPtr(WC->serv_info->serv_software));
-  hprintf("Connection: close\r\n");
-  hprintf("Pragma: no-cache\r\nCache-Control: no-store\r\nExpires:-1\r\n");
-  begin_burst();
+       hprintf("HTTP/1.1 200 OK\r\n");
+       hprintf("Content-type: application/json; charset=utf-8\r\n");
+       hprintf("Server: %s / %s\r\n", PACKAGE_STRING, ChrPtr(WC->serv_info->serv_software));
+       hprintf("Connection: close\r\n");
+       hprintf("Pragma: no-cache\r\nCache-Control: no-store\r\nExpires:-1\r\n");
+       begin_burst();
 }
+
 /* Spit out the new summary view. This is basically a static page, so clients can cache the layout, all the dirty work is javascript :) */
 void new_summary_view(void) {
-  begin_burst();
-  DoTemplate(HKEY("msg_listview"),NULL,&NoCtx);
-  DoTemplate(HKEY("trailing"),NULL,&NoCtx);
-  end_burst();
+       begin_burst();
+       DoTemplate(HKEY("msg_listview"),NULL,&NoCtx);
+       DoTemplate(HKEY("trailing"),NULL,&NoCtx);
+       end_burst();
 }
-/** Output message list in JSON-format */
+
+/* Output message list in JSON format */
 void jsonMessageList(void) {
-  const StrBuf *room = sbstr("room");
-  long oper = (havebstr("query")) ? do_search : readnew;
-  WC->is_ajax = 1; 
-  gotoroom(room);
-  readloop(oper);
-  WC->is_ajax = 0;
+       const StrBuf *room = sbstr("room");
+       long oper = (havebstr("query")) ? do_search : readnew;
+       WC->is_ajax = 1; 
+       gotoroom(room);
+       readloop(oper);
+       WC->is_ajax = 0;
 }
 
 void 
@@ -1724,7 +1973,8 @@ InitModule_MSG
        WebcitAddUrlHandler(HKEY("move_msg"), move_msg, 0);
        WebcitAddUrlHandler(HKEY("delete_msg"), delete_msg, 0);
        WebcitAddUrlHandler(HKEY("confirm_move_msg"), confirm_move_msg, 0);
-       WebcitAddUrlHandler(HKEY("msg"), embed_message, NEED_URL|AJAX);
+       WebcitAddUrlHandler(HKEY("msg"), embed_message, NEED_URL);
+       WebcitAddUrlHandler(HKEY("message"), handle_one_message, NEED_URL|XHTTP_COMMANDS|COOKIEUNNEEDED|FORCE_SESSIONCLOSE);
        WebcitAddUrlHandler(HKEY("printmsg"), print_message, NEED_URL);
        WebcitAddUrlHandler(HKEY("mobilemsg"), mobile_message_view, NEED_URL);
        WebcitAddUrlHandler(HKEY("msgheaders"), display_headers, NEED_URL);
@@ -1738,3 +1988,10 @@ InitModule_MSG
        WebcitAddUrlHandler(HKEY("roommsgs"), jsonMessageList,0);
        return ;
 }
+
+void
+SessionDetachModule_MSG
+(wcsession *sess)
+{
+       DeleteHash(&sess->summ);
+}