* change CtdlReadMessageBody to use StrBuf for concattenating the lines
[citadel.git] / citadel / msgbase.c
index c90ef0e2c28c2bd0392da2e4d1c586ec46c6a6af..ef7bdf8c0c9d397927d75641ca941a8aacc03b53 100644 (file)
@@ -2,7 +2,22 @@
  * $Id$
  *
  * Implements the message store.
+ * 
+ * Copyright (c) 1987-2010 by the citadel.org team
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
  *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include "sysdep.h"
@@ -281,6 +296,25 @@ void headers_listing(long msgnum, void *userdata)
        CtdlFreeMessage(msg);
 }
 
+/*
+ * Back end for the MSGS command: output EUID header.
+ */
+void headers_euid(long msgnum, void *userdata)
+{
+       struct CtdlMessage *msg;
+
+       msg = CtdlFetchMessage(msgnum, 0);
+       if (msg == NULL) {
+               cprintf("%ld||\n", msgnum);
+               return;
+       }
+
+       cprintf("%ld|%s|\n", msgnum, (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""));
+       CtdlFreeMessage(msg);
+}
+
+
+
 
 
 /* Determine if a given message matches the fields in a message template.
@@ -565,7 +599,7 @@ void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
 int CtdlForEachMessage(int mode, long ref, char *search_string,
                        char *content_type,
                        struct CtdlMessage *compare,
-                       void (*CallBack) (long, void *),
+                        ForEachMsgCallback CallBack,
                        void *userdata)
 {
 
@@ -593,7 +627,7 @@ int CtdlForEachMessage(int mode, long ref, char *search_string,
        }
 
        /* Learn about the user and room in question */
-       getuser(&CC->user, CC->curr_user);
+       CtdlGetUser(&CC->user, CC->curr_user);
        CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
 
        /* Load the message list */
@@ -723,6 +757,7 @@ int CtdlForEachMessage(int mode, long ref, char *search_string,
                                       || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
                                   || ((mode == MSGS_FIRST) && (a < ref))
                                || ((mode == MSGS_GT) && (thismsg > ref))
+                               || ((mode == MSGS_LT) && (thismsg < ref))
                                || ((mode == MSGS_EQ) && (thismsg == ref))
                            )
                            ) {
@@ -758,14 +793,26 @@ void cmd_msgs(char *cmdbuf)
        int i;
        int with_template = 0;
        struct CtdlMessage *template = NULL;
-       int with_headers = 0;
        char search_string[1024];
+       ForEachMsgCallback CallBack;
 
        extract_token(which, cmdbuf, 0, '|', sizeof which);
        cm_ref = extract_int(cmdbuf, 1);
        extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
        with_template = extract_int(cmdbuf, 2);
-       with_headers = extract_int(cmdbuf, 3);
+       switch (extract_int(cmdbuf, 3))
+       {
+       default:
+       case MSG_HDRS_BRIEF:
+               CallBack = simple_listing;
+               break;
+       case MSG_HDRS_ALL:
+               CallBack = headers_listing;
+               break;
+       case MSG_HDRS_EUID:
+               CallBack = headers_euid;
+               break;
+       }
 
        strcat(which, "   ");
        if (!strncasecmp(which, "OLD", 3))
@@ -778,6 +825,8 @@ void cmd_msgs(char *cmdbuf)
                mode = MSGS_LAST;
        else if (!strncasecmp(which, "GT", 2))
                mode = MSGS_GT;
+       else if (!strncasecmp(which, "LT", 2))
+               mode = MSGS_LT;
        else if (!strncasecmp(which, "SEARCH", 6))
                mode = MSGS_SEARCH;
        else
@@ -821,13 +870,12 @@ void cmd_msgs(char *cmdbuf)
        }
 
        CtdlForEachMessage(mode,
-                       ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
-                       ( (mode == MSGS_SEARCH) ? search_string : NULL ),
-                       NULL,
-                       template,
-                       (with_headers ? headers_listing : simple_listing),
-                       NULL
-       );
+                          ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
+                          ( (mode == MSGS_SEARCH) ? search_string : NULL ),
+                          NULL,
+                          template,
+                          CallBack,
+                          NULL);
        if (template != NULL) CtdlFreeMessage(template);
        cprintf("000\n");
 }
@@ -879,79 +927,92 @@ void do_help_subst(char *buffer)
  */
 void memfmout(
        char *mptr,             /* where are we going to get our text from? */
-       char subst,             /* nonzero if we should do substitutions */
-       char *nl)               /* string to terminate lines with */
+       const char *nl)         /* string to terminate lines with */
 {
-       int a, b, c;
-       int real = 0;
-       int old = 0;
-       cit_uint8_t ch;
-       char aaa[140];
-       char buffer[SIZ];
-       static int width = 80;
-
-       strcpy(aaa, "");
-       old = 255;
-       strcpy(buffer, "");
-       c = 1;                  /* c is the current pos */
-
+       StrBuf *OutBuf;
+       char *LineStart;
+       char *LastBlank;
+       size_t len;
+       size_t NLLen;
+       char *eptr;
+       int NLFound, NLFoundLastTime;
+       int Found;
+
+       len = strlen (mptr);
+       NLLen = strlen (nl);
+       eptr = mptr + len;
+
+       OutBuf = NewStrBufPlain(NULL, 200);
+       
+       NLFound = NLFoundLastTime = 0;
        do {
-               if (subst) {
-                       while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
-                               ch = *mptr++;
-                               buffer[strlen(buffer) + 1] = 0;
-                               buffer[strlen(buffer)] = ch;
-                       }
-
-                       if (buffer[0] == '^')
-                               do_help_subst(buffer);
-
-                       buffer[strlen(buffer) + 1] = 0;
-                       a = buffer[0];
-                       strcpy(buffer, &buffer[1]);
-               } else {
-                       ch = *mptr++;
-               }
-
-               old = real;
-               real = ch;
-
-               if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
-                       ch = 32;
-               }
-               if (((old == 13) || (old == 10)) && (isspace(real))) {
-                       cprintf("%s", nl);
-                       c = 1;
-               }
-               if (ch > 32) {
-                       if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
-                               cprintf("%s%s", nl, aaa);
-                               c = strlen(aaa);
-                               aaa[0] = 0;
-                       }
-                       b = strlen(aaa);
-                       aaa[b] = ch;
-                       aaa[b + 1] = 0;
-               }
-               if (ch == 32) {
-                       if ((strlen(aaa) + c) > (width - 5)) {
-                               cprintf("%s", nl);
-                               c = 1;
+               size_t i;
+
+               LineStart = LastBlank = mptr;
+               Found = 'x';
+               i = 0;
+               while (Found == 'x')
+               {
+                       if (LineStart[i] == '\n')
+                               Found = '\n';
+                       else if (LineStart[i] == '\r')
+                               Found = '\r';
+                       else if (LineStart[i] == ' ') 
+                       {
+                               LastBlank = &LineStart[i];
+                               i++;
                        }
-                       cprintf("%s ", aaa);
-                       ++c;
-                       c = c + strlen(aaa);
-                       strcpy(aaa, "");
+                       else if ((i > 80) && (LineStart != LastBlank))
+                               Found = ' ';
+                       else if (LineStart[i] == '\0')
+                               Found = '\0';
+                       else i++;
                }
-               if ((ch == 13) || (ch == 10)) {
-                       cprintf("%s%s", aaa, nl);
-                       c = 1;
-                       strcpy(aaa, "");
+               switch (Found)
+               {
+               case '\n':
+                       if (LineStart[i + 1] == '\r')
+                               mptr = &LineStart[i + 2];
+                       else 
+                               mptr = &LineStart[i + 1];
+                       i--;
+                       NLFound = 1;
+                       break;
+               case '\r':
+                       if (LineStart[i + 1] == '\n')
+                               mptr = &LineStart[i + 2];
+                       else 
+                               mptr = &LineStart[i + 1];
+                       i--;
+                       NLFound = 1;
+                       break;
+               case '\0':
+                       mptr = &LineStart[i + 1];
+                       i--;
+                       NLFound = 0;
+                       break;
+               case ' ':
+                       mptr = LastBlank + 1;
+                       i = LastBlank - LineStart;
+                       NLFound = 0;
+                       break;
+               case 'x':
+                       /* WHUT? */
+                       while (*mptr != '\0') mptr++;
+                       break;
                }
-
-       } while (ch > 0);
-
-       cprintf("%s%s", aaa, nl);
+               if (NLFoundLastTime)
+                       StrBufPlain(OutBuf, HKEY(" "));
+               else
+                       FlushStrBuf(OutBuf);
+               StrBufAppendBufPlain(OutBuf, LineStart, i, 0);
+               StrBufAppendBufPlain(OutBuf, nl, NLLen, 0);
+
+               cputbuf(OutBuf);
+               NLFoundLastTime = NLFound;
+       } while (*mptr != '\0');
+
+       FreeStrBuf(&OutBuf);
 }
 
 
@@ -967,8 +1028,15 @@ void list_this_part(char *name, char *filename, char *partnum, char *disp,
        
        ma = (struct ma_info *)cbuserdata;
        if (ma->is_ma == 0) {
-               cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
-                       name, filename, partnum, disp, cbtype, (long)length, cbid);
+               cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
+                       name, 
+                       filename, 
+                       partnum, 
+                       disp, 
+                       cbtype, 
+                       (long)length, 
+                       cbid, 
+                       cbcharset);
        }
 }
 
@@ -1056,11 +1124,12 @@ void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
        ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
        ) {
                *found_it = 1;
-               cprintf("%d %d|-1|%s|%s\n",
+               cprintf("%d %d|-1|%s|%s|%s\n",
                        BINARY_FOLLOWS,
                        (int)length,
                        filename,
-                       cbtype
+                       cbtype,
+                       cbcharset
                );
                client_write(content, length);
        }
@@ -1695,7 +1764,7 @@ int CtdlOutputPreLoadedMsg(
        char allkeys[30];
        char display_name[256];
        char *mptr, *mpptr;
-       char *nl;       /* newline string */
+       const char *nl; /* newline string */
        int suppress_f = 0;
        int subject_found = 0;
        struct ma_info ma;
@@ -2118,7 +2187,7 @@ START_TEXT:
                if (mode == MT_MIME) {
                        cprintf("Content-type: text/x-citadel-variformat\n\n");
                }
-               memfmout(mptr, 0, nl);
+               memfmout(mptr, nl);
        }
 
        /* If the message on disk is format 4 (MIME), we've gotta hand it
@@ -2654,7 +2723,7 @@ void ReplicationChecks(struct CtdlMessage *msg) {
        /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
                msg->cm_fields['E'], CC->room.QRname);*/
 
-       old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
+       old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
        if (old_msgnum > 0L) {
                CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
                CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
@@ -2669,7 +2738,7 @@ void ReplicationChecks(struct CtdlMessage *msg) {
 long CtdlSubmitMsg(struct CtdlMessage *msg,    /* message to save */
                   struct recptypes *recps,     /* recipients (if mail) */
                   char *force,                 /* force a particular room? */
-                  int flags                    /* should the bessage be exported clean? */
+                  int flags                    /* should the message be exported clean? */
 ) {
        char submit_filename[128];
        char generated_timestamp[32];
@@ -2694,7 +2763,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,       /* message to save */
        struct addresses_to_be_filed *aptr = NULL;
        char *saved_rfc822_version = NULL;
        int qualified_for_journaling = 0;
-       struct CitContext *CCC = CC;            /* CachedCitContext - performance boost */
+       CitContext *CCC = CC;           /* CachedCitContext - performance boost */
        char bounce_to[1024] = "";
        size_t tmp = 0;
        int rv = 0;
@@ -2885,9 +2954,9 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,       /* message to save */
 
        /* Bump this user's messages posted counter. */
        CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
-       lgetuser(&CCC->user, CCC->curr_user);
+       CtdlGetUserLock(&CCC->user, CCC->curr_user);
        CCC->user.posted = CCC->user.posted + 1;
-       lputuser(&CCC->user);
+       CtdlPutUserLock(&CCC->user);
 
        /* Decide where bounces need to be delivered */
        if ((recps != NULL) && (recps->bounce_to != NULL)) {
@@ -2909,12 +2978,12 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
                                        '|', sizeof recipient);
                CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
                        recipient);
-               if (getuser(&userbuf, recipient) == 0) {
+               if (CtdlGetUser(&userbuf, recipient) == 0) {
                        // Add a flag so the Funambol module knows its mail
                        msg->cm_fields['W'] = strdup(recipient);
-                       MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
+                       CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
                        CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
-                       BumpNewMailCounter(userbuf.usernum);
+                       CtdlBumpNewMailCounter(userbuf.usernum);
                        if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
                        /* Generate a instruction message for the Funambol notification
                         * server, in the same style as the SMTP queue
@@ -3050,7 +3119,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,       /* message to save */
        if (collected_addresses != NULL) {
                aptr = (struct addresses_to_be_filed *)
                        malloc(sizeof(struct addresses_to_be_filed));
-               MailboxName(actual_rm, sizeof actual_rm,
+               CtdlMailboxName(actual_rm, sizeof actual_rm,
                        &CCC->user, USERCONTACTSROOM);
                aptr->roomname = strdup(actual_rm);
                aptr->collected_addresses = collected_addresses;
@@ -3098,6 +3167,10 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,      /* message to save */
 
 
 
+void aide_message (char *text, char *subject)
+{
+       quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
+}
 
 
 /*
@@ -3151,101 +3224,71 @@ void quickie_message(const char *from, const char *fromaddr, char *to, char *roo
  * Back end function used by CtdlMakeMessage() and similar functions
  */
 char *CtdlReadMessageBody(char *terminator,    /* token signalling EOT */
+                         long tlen,
                        size_t maxlen,          /* maximum message length */
                        char *exist,            /* if non-null, append to it;
                                                   exist is ALWAYS freed  */
                        int crlf,               /* CRLF newlines instead of LF */
                        int sock                /* socket handle or 0 for this session's client socket */
-                       ) {
+                       ) 
+{
+       StrBuf *Message;
+       StrBuf *LineBuf;
        char buf[1024];
-       int linelen;
-       size_t message_len = 0;
-       size_t buffer_len = 0;
-       char *ptr;
-       char *m;
        int flushing = 0;
        int finished = 0;
        int dotdot = 0;
 
+       LineBuf = NewStrBufPlain(NULL, SIZ);
        if (exist == NULL) {
-               m = malloc(4096);
-               m[0] = 0;
-               buffer_len = 4096;
-               message_len = 0;
+               Message = NewStrBufPlain(NULL, 4 * SIZ);
        }
        else {
-               message_len = strlen(exist);
-               buffer_len = message_len + 4096;
-               m = realloc(exist, buffer_len);
-               if (m == NULL) {
-                       free(exist);
-                       return m;
-               }
+               Message = NewStrBufPlain(exist, -1);
+               free(exist);
        }
 
        /* Do we need to change leading ".." to "." for SMTP escaping? */
-       if (!strcmp(terminator, ".")) {
+       if ((tlen == 1) && (*terminator == '.')) {
                dotdot = 1;
        }
 
-       /* flush the input if we have nowhere to store it */
-       if (m == NULL) {
-               flushing = 1;
-       }
-
        /* read in the lines of message text one by one */
        do {
                if (sock > 0) {
                        if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
                }
                else {
-                       if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
-               }
-               if (!strcmp(buf, terminator)) finished = 1;
-               if (crlf) {
-                       strcat(buf, "\r\n");
-               }
-               else {
-                       strcat(buf, "\n");
-               }
-
-               /* Unescape SMTP-style input of two dots at the beginning of the line */
-               if (dotdot) {
-                       if (!strncmp(buf, "..", 2)) {
-                               strcpy(buf, &buf[1]);
-                       }
+                       if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
                }
+               if ((StrLength(LineBuf) == tlen) && 
+                   (!strcmp(ChrPtr(LineBuf), terminator)))
+                       finished = 1;
 
                if ( (!flushing) && (!finished) ) {
-                       /* Measure the line */
-                       linelen = strlen(buf);
-       
-                       /* augment the buffer if we have to */
-                       if ((message_len + linelen) >= buffer_len) {
-                               ptr = realloc(m, (buffer_len * 2) );
-                               if (ptr == NULL) {      /* flush if can't allocate */
-                                       flushing = 1;
-                               } else {
-                                       buffer_len = (buffer_len * 2);
-                                       m = ptr;
-                                       CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
-                               }
+                       if (crlf) {
+                               StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
                        }
-       
-                       /* Add the new line to the buffer.  NOTE: this loop must avoid
-                       * using functions like strcat() and strlen() because they
-                       * traverse the entire buffer upon every call, and doing that
-                       * for a multi-megabyte message slows it down beyond usability.
-                       */
-                       strcpy(&m[message_len], buf);
-                       message_len += linelen;
+                       else {
+                               StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
+                       }
+                       
+                       /* Unescape SMTP-style input of two dots at the beginning of the line */
+                       if ((dotdot) &&
+                           (StrLength(LineBuf) == 2) && 
+                           (!strcmp(ChrPtr(LineBuf), "..")))
+                       {
+                               StrBufCutLeft(LineBuf, 1);
+                       }
+                       
+                       StrBufAppendBuf(Message, LineBuf, 0);
                }
 
                /* if we've hit the max msg length, flush the rest */
-               if (message_len >= maxlen) flushing = 1;
+               if (StrLength(Message) >= maxlen) flushing = 1;
 
        } while (!finished);
-       return(m);
+       return SmashStrBuf(&Message);
 }
 
 
@@ -3374,7 +3417,7 @@ struct CtdlMessage *CtdlMakeMessage(
                msg->cm_fields['M'] = preformatted_text;
        }
        else {
-               msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
+               msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
        }
 
        return(msg);
@@ -3636,7 +3679,7 @@ struct recptypes *validate_recipients(char *supplied_recipients,
                                        CC->room = tempQR2;
 
                                }
-                               else if (getuser(&tempUS, this_recp) == 0) {
+                               else if (CtdlGetUser(&tempUS, this_recp) == 0) {
                                        ++ret->num_local;
                                        strcpy(this_recp, tempUS.fullname);
                                        if (!IsEmptyStr(ret->recp_local)) {
@@ -3644,7 +3687,7 @@ struct recptypes *validate_recipients(char *supplied_recipients,
                                        }
                                        strcat(ret->recp_local, this_recp);
                                }
-                               else if (getuser(&tempUS, this_recp_cooked) == 0) {
+                               else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
                                        ++ret->num_local;
                                        strcpy(this_recp, tempUS.fullname);
                                        if (!IsEmptyStr(ret->recp_local)) {
@@ -4152,7 +4195,10 @@ int CtdlDeleteMessages(char *room_name,          /* which room */
                cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
                          msglist, (int)(num_msgs * sizeof(long)));
 
-               qrbuf.QRhighest = msglist[num_msgs - 1];
+               if (num_msgs > 0)
+                       qrbuf.QRhighest = msglist[num_msgs - 1];
+               else
+                       qrbuf.QRhighest = 0;
        }
        CtdlPutRoomLock(&qrbuf);
 
@@ -4281,7 +4327,7 @@ void cmd_move(char *args)
                return;
        }
 
-       getuser(&CC->user, CC->curr_user);
+       CtdlGetUser(&CC->user, CC->curr_user);
        CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
 
        /* Check for permission to perform this operation.
@@ -4566,7 +4612,7 @@ void CtdlWriteObject(char *req_room,                      /* Room to stuff it in */
        char *encoded_message = NULL;
 
        if (is_mailbox != NULL) {
-               MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
+               CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
        }
        else {
                safestrncpy(roomname, req_room, sizeof(roomname));
@@ -4759,18 +4805,20 @@ void cmd_isme(char *argbuf) {
 
 CTDL_MODULE_INIT(msgbase)
 {
-       CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
-       CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
-       CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
-       CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
-       CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
-       CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
-       CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
-       CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
-       CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
-       CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
-       CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
-       CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
+               CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
+               CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
+               CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
+               CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
+               CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
+               CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
+               CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
+               CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
+               CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
+               CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
+               CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
+       }
 
         /* return our Subversion id for the Log */
        return "$Id$";