]> code.citadel.org Git - citadel.git/blobdiff - citadel/msgbase.c
Add async message reading
[citadel.git] / citadel / msgbase.c
index f1aa9da9234e40e4be441a89dfa3132efd1dfd09..13e1b125806bc2c888ebac3da01f056a806708ec 100644 (file)
@@ -1,6 +1,4 @@
 /*
- * $Id$
- *
  * Implements the message store.
  *
  * Copyright (c) 1987-2010 by the citadel.org team
@@ -356,7 +354,7 @@ int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
  * Retrieve the "seen" message list for the current room.
  */
 void CtdlGetSeen(char *buf, int which_set) {
-       struct visit vbuf;
+       visit vbuf;
 
        /* Learn about the user and room in question */
        CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
@@ -381,7 +379,7 @@ void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
        int was_seen = 0;
        long lo = (-1L);
        long hi = (-1L);
-       struct visit vbuf;
+       visit vbuf;
        long *msglist;
        int num_msgs = 0;
        StrBuf *vset;
@@ -607,7 +605,7 @@ int CtdlForEachMessage(int mode, long ref, char *search_string,
 {
 
        int a, i, j;
-       struct visit vbuf;
+       visit vbuf;
        struct cdbdata *cdbfr;
        long *msglist = NULL;
        int num_msgs = 0;
@@ -799,6 +797,8 @@ void cmd_msgs(char *cmdbuf)
        char search_string[1024];
        ForEachMsgCallback CallBack;
 
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
        extract_token(which, cmdbuf, 0, '|', sizeof which);
        cm_ref = extract_int(cmdbuf, 1);
        extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
@@ -835,11 +835,6 @@ void cmd_msgs(char *cmdbuf)
        else
                mode = MSGS_ALL;
 
-       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
-               cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
-               return;
-       }
-
        if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
                cprintf("%d Full text index is not enabled on this server.\n",
                        ERROR + CMD_NOT_SUPPORTED);
@@ -933,14 +928,14 @@ void memfmout(
        const char *nl          /* string to terminate lines with */
 ) {
        int column = 0;
-       char ch = 0;
+       unsigned char ch = 0;
        char outbuf[1024];
        int len = 0;
        int nllen = 0;
 
        if (!mptr) return;
        nllen = strlen(nl);
-       while (ch=*(mptr++), ch > 0) {
+       while (ch=*(mptr++), ch != 0) {
 
                if (ch == '\n') {
                        client_write(outbuf, len);
@@ -1102,7 +1097,49 @@ void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
        }
 }
 
+#ifdef MESSAGE_IN_ROOM
+/*
+ * Check if a message is in the current room.
+ * This is used by CtdlFetchMessage to prevent random picking
+ * of messages from users private rooms
+ *
+ * The message list should probably be cached against the CC->room
+ */
+int CtdlMessageInRoom(long msgnum)
+{
+       visit vbuf;
+       struct cdbdata *cdbfr;
 
+       /* Learn about the user and room in question */
+       CtdlGetUser(&CC->user, CC->curr_user);
+       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+       /* Load the message list */
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+       if (cdbfr != NULL) {
+               long *msglist = NULL;
+               int num_msgs = 0;
+               int i;
+               int r = 0;
+               
+               msglist = (long *) cdbfr->ptr;
+               num_msgs = cdbfr->len / sizeof(long);
+
+               /* search for message msgnum */
+               for (i=0; i<num_msgs; i++) {
+                       if (msglist[i] == msgnum) {
+                               r = 1;
+                               break;
+                       }       
+               }
+
+               cdb_free(cdbfr);
+               return r;
+       } else {
+               return 0;
+       }
+}
+#endif
 
 /*
  * Load a message from disk into memory.
@@ -1122,6 +1159,13 @@ struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
 
        CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
 
+#ifdef MESSAGE_IN_ROOM
+       if (!CtdlMessageInRoom(msgnum)) {
+               CtdlLogPrintf(CTDL_DEBUG, "Message %ld not in current room\n", msgnum);
+               return NULL;
+       }
+#endif
+
        dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
        if (dmsgtext == NULL) {
                return NULL;
@@ -1383,16 +1427,27 @@ void choose_preferred(char *name, char *filename, char *partnum, char *disp,
 /*
  * Now that we've chosen our preferred part, output it.
  */
-void output_preferred(char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length,
-               char *encoding, char *cbid, void *cbuserdata)
+void output_preferred(char *name, 
+                     char *filename, 
+                     char *partnum, 
+                     char *disp,
+                     void *content, 
+                     char *cbtype, 
+                     char *cbcharset, 
+                     size_t length,
+                     char *encoding, 
+                     char *cbid, 
+                     void *cbuserdata)
 {
        int i;
        char buf[128];
        int add_newline = 0;
        char *text_content;
        struct ma_info *ma;
-       
+       char *decoded = NULL;
+       size_t bytes_decoded;
+       int rc = 0;
+
        ma = (struct ma_info *)cbuserdata;
 
        /* This is not the MIME part you're looking for... */
@@ -1405,8 +1460,21 @@ void output_preferred(char *name, char *filename, char *partnum, char *disp,
                extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
                if (!strcasecmp(buf, cbtype)) {
                        /* Yeah!  Go!  W00t!! */
+                       if (ma->dont_decode == 0) 
+                               rc = mime_decode_now (content, 
+                                                     length,
+                                                     encoding,
+                                                     &decoded,
+                                                     &bytes_decoded);
+                       if (rc < 0)
+                               break; /* Give us the chance, maybe theres another one. */
+
+                       if (rc == 0) text_content = (char *)content;
+                       else {
+                               text_content = decoded;
+                               length = bytes_decoded;
+                       }
 
-                       text_content = (char *)content;
                        if (text_content[length-1] != '\n') {
                                ++add_newline;
                        }
@@ -1424,16 +1492,34 @@ void output_preferred(char *name, char *filename, char *partnum, char *disp,
                        }
                        cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
                        cprintf("\n");
-                       client_write(content, length);
+                       client_write(text_content, length);
                        if (add_newline) cprintf("\n");
+                       if (decoded != NULL) free(decoded);
                        return;
                }
        }
 
        /* No translations required or possible: output as text/plain */
        cprintf("Content-type: text/plain\n\n");
-       fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
+       rc = 0;
+       if (ma->dont_decode == 0)
+               rc = mime_decode_now (content, 
+                                     length,
+                                     encoding,
+                                     &decoded,
+                                     &bytes_decoded);
+       if (rc < 0)
+               return; /* Give us the chance, maybe theres another one. */
+       
+       if (rc == 0) text_content = (char *)content;
+       else {
+               text_content = decoded;
+               length = bytes_decoded;
+       }
+
+       fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
                        length, encoding, cbid, cbuserdata);
+       if (decoded != NULL) free(decoded);
 }
 
 
@@ -1462,16 +1548,18 @@ void extract_encapsulated_message(char *name, char *filename, char *partnum, cha
                memcpy(encap->msg, content, length);
                return;
        }
-
 }
 
 
-
-
-
-
+/* 
+ * Determine whether the currently logged in session has permission to read
+ * messages in the current room.
+ */
 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
-       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
+       if (    (!(CC->logged_in))
+               && (!(CC->internal_pgm))
+               && (!config.c_guest_logins)
+       ) {
                return(om_not_logged_in);
        }
        return(om_ok);
@@ -1513,7 +1601,14 @@ int CtdlOutputMsg(long msg_num,          /* message number (local) to fetch */
                return(r);
        }
 
-       /* FIXME: check message id against msglist for this room */
+#ifdef MESSAGE_IN_ROOM
+       if (!CtdlMessageInRoom(msg_num)) {
+               CtdlLogPrintf(CTDL_DEBUG, "Message %ld not in current room\n", msg_num);
+               if (do_proto) cprintf("%d Can't locate msg %ld in room\n",
+                       ERROR + MESSAGE_NOT_FOUND, msg_num);
+               return(om_no_such_msg);
+       }
+#endif
 
        /*
         * Fetch the message from disk.  If we're in HEADERS_FAST mode,
@@ -2239,7 +2334,7 @@ START_TEXT:
                                (do_proto ? *list_this_part : NULL),
                                (do_proto ? *list_this_pref : NULL),
                                (do_proto ? *list_this_suff : NULL),
-                               (void *)&ma, 0);
+                               (void *)&ma, 1);
                }
                else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
                        Dump_RFC822HeadersBody(
@@ -2294,11 +2389,12 @@ START_TEXT:
                        ma.use_fo_hooks = 0;
                        strcpy(ma.chosen_part, "1");
                        ma.chosen_pref = 9999;
+                       ma.dont_decode = CC->msg4_dont_decode;
                        mime_parser(mptr, NULL,
                                *choose_preferred, *fixed_output_pre,
-                               *fixed_output_post, (void *)&ma, 0);
+                               *fixed_output_post, (void *)&ma, 1);
                        mime_parser(mptr, NULL,
-                               *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
+                               *output_preferred, NULL, NULL, (void *)&ma, 1);
                }
                else {
                        ma.use_fo_hooks = 1;
@@ -2529,7 +2625,7 @@ int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newms
                }
        }
 
-       CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
+       CtdlLogPrintf(CTDL_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
 
        /*
         * Now merge the new messages
@@ -2831,7 +2927,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? */
+                  const char *force,           /* force a particular room? */
                   int flags                    /* should the message be exported clean? */
 ) {
        char submit_filename[128];
@@ -3345,7 +3441,7 @@ StrBuf *CtdlReadMessageBodyBuf(char *terminator,  /* token signalling EOT */
        /* read in the lines of message text one by one */
        do {
                if (sock != NULL) {
-                       if ((CtdlSockGetLine(sock, LineBuf) < 0) ||
+                       if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
                            (*sock == -1))
                                finished = 1;
                }
@@ -3383,6 +3479,123 @@ StrBuf *CtdlReadMessageBodyBuf(char *terminator,        /* token signalling EOT */
        return Message;
 }
 
+void DeleteAsyncMsg(ReadAsyncMsg **Msg)
+{
+       if (*Msg == NULL)
+               return;
+       FreeStrBuf(&(*Msg)->MsgBuf);
+
+       free(*Msg);
+       *Msg = NULL;
+}
+
+ReadAsyncMsg *NewAsyncMsg(const char *terminator,      /* token signalling EOT */
+                         long tlen,
+                         size_t maxlen,                /* maximum message length */
+                         size_t expectlen,             /* if we expect a message, how long should it be? */
+                         char *exist,                  /* if non-null, append to it;
+                                                          exist is ALWAYS freed  */
+                         long eLen,                    /* length of exist */
+                         int crlf                      /* CRLF newlines instead of LF */
+       )
+{
+       ReadAsyncMsg *NewMsg;
+
+       NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
+       memset(NewMsg, 0, sizeof(ReadAsyncMsg));
+
+       if (exist == NULL) {
+               long len;
+
+               if (expectlen == 0) {
+                       len = 4 * SIZ;
+               }
+               else {
+                       len = expectlen + 10;
+               }
+               NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
+       }
+       else {
+               NewMsg->MsgBuf = NewStrBufPlain(exist, eLen);
+               free(exist);
+       }
+       /* Do we need to change leading ".." to "." for SMTP escaping? */
+       if ((tlen == 1) && (*terminator == '.')) {
+               NewMsg->dodot = 1;
+       }
+
+       NewMsg->terminator = terminator;
+       NewMsg->tlen = tlen;
+
+       NewMsg->maxlen = maxlen;
+
+       NewMsg->crlf = crlf;
+
+       return NewMsg;
+}
+
+/*
+ * Back end function used by CtdlMakeMessage() and similar functions
+ */
+eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
+{
+       ReadAsyncMsg *ReadMsg;
+       int MsgFinished = 0;
+       eReadState Finished = eMustReadMore;
+
+       ReadMsg = IO->ReadMsg;
+
+       /* read in the lines of message text one by one */
+       do {
+               Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
+               
+               switch (Finished) {
+               case eMustReadMore: /// read new from socket... 
+                   return Finished;
+                   break;
+               case eBufferNotEmpty: /* shouldn't happen... */
+               case eReadSuccess: /// done for now...
+                   break;
+               case eReadFail: /// WHUT?
+                   ///todo: shut down! 
+                       break;
+               }
+           
+
+               if ((StrLength(IO->IOBuf) == ReadMsg->tlen) && 
+                   (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
+                       MsgFinished = 1;
+               }
+               else if (!ReadMsg->flushing) {
+                       /* Unescape SMTP-style input of two dots at the beginning of the line */
+                       if ((ReadMsg->dodot) &&
+                           (StrLength(IO->IOBuf) == 2) &&  /* TODO: do we just unescape lines with two dots or any line? */
+                           (!strcmp(ChrPtr(IO->IOBuf), "..")))
+                       {
+                               StrBufCutLeft(IO->IOBuf, 1);
+                       }
+
+                       if (ReadMsg->crlf) {
+                               StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
+                       }
+                       else {
+                               StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
+                       }
+
+                       StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
+               }
+
+               /* if we've hit the max msg length, flush the rest */
+               if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
+
+       } while (!MsgFinished);
+
+       if (MsgFinished)
+               return eReadSuccess;
+       else 
+               return eAbort;
+}
+
 
 /*
  * Back end function used by CtdlMakeMessage() and similar functions
@@ -3436,6 +3649,8 @@ struct CtdlMessage *CtdlMakeMessage(
        char dest_node[256];
        char buf[1024];
        struct CtdlMessage *msg;
+       StrBuf *FakeAuthor;
+       StrBuf *FakeEncAuthor = NULL;
 
        msg = malloc(sizeof(struct CtdlMessage));
        memset(msg, 0, sizeof(struct CtdlMessage));
@@ -3465,11 +3680,14 @@ struct CtdlMessage *CtdlMakeMessage(
        msg->cm_fields['T'] = strdup(buf);
 
        if ((fake_name != NULL) && (fake_name[0])) {            /* author */
-               msg->cm_fields['A'] = strdup(fake_name);
+               FakeAuthor = NewStrBufPlain (fake_name, -1);
        }
        else {
-               msg->cm_fields['A'] = strdup(author->fullname);
+               FakeAuthor = NewStrBufPlain (author->fullname, -1);
        }
+       StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
+       msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
+       FreeStrBuf(&FakeAuthor);
 
        if (CC->room.QRflags & QR_MAILBOX) {            /* room */
                msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
@@ -3546,10 +3764,13 @@ struct CtdlMessage *CtdlMakeMessage(
  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
  * returns 0 on success.
  */
-int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, 
-                                         size_t n, 
-                                         const char* RemoteIdentifier,
-                                         int PostPublic) {
+int CtdlDoIHavePermissionToPostInThisRoom(
+       char *errmsgbuf, 
+       size_t n, 
+       const char* RemoteIdentifier,
+       int PostPublic,
+       int is_reply
+) {
        int ra;
 
        if (!(CC->logged_in) && 
@@ -3614,7 +3835,18 @@ int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
        }
 
        CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
-       if (!(ra & UA_POSTALLOWED)) {
+
+       if ( (!(ra & UA_POSTALLOWED)) && (ra & UA_REPLYALLOWED) && (!is_reply) ) {
+               /*
+                * To be thorough, we ought to check to see if the message they are
+                * replying to is actually a valid one in this room, but unless this
+                * actually becomes a problem we'll go with high performance instead.
+                */
+               snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
+               return (ERROR + HIGHER_ACCESS_REQUIRED);
+       }
+
+       else if (!(ra & UA_POSTALLOWED)) {
                snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
                return (ERROR + HIGHER_ACCESS_REQUIRED);
        }
@@ -3774,10 +4006,12 @@ struct recptypes *validate_recipients(const char *supplied_recipients,
                                        CC->room = tempQR;
                                        
                                        /* Check permissions to send mail to this room */
-                                       err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, 
-                                                                                   sizeof errmsg, 
-                                                                                   RemoteIdentifier,
-                                                                                   Flags
+                                       err = CtdlDoIHavePermissionToPostInThisRoom(
+                                               errmsg, 
+                                               sizeof errmsg, 
+                                               RemoteIdentifier,
+                                               Flags,
+                                               0                       /* 0 = not a reply */
                                        );
                                        if (err)
                                        {
@@ -3980,7 +4214,13 @@ void cmd_ent0(char *entargs)
 
        /* first check to make sure the request is valid. */
 
-       err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
+       err = CtdlDoIHavePermissionToPostInThisRoom(
+               errmsg,
+               sizeof errmsg,
+               NULL,
+               POST_LOGGED_IN,
+               (!IsEmptyStr(references))               /* is this a reply?  or a top-level post? */
+       );
        if (err)
        {
                cprintf("%d %s\n", err, errmsg);
@@ -4036,12 +4276,13 @@ void cmd_ent0(char *entargs)
        /* In mailbox rooms we have to behave a little differently --
         * make sure the user has specified at least one recipient.  Then
         * validate the recipient(s).  We do this for the Mail> room, as
-        * well as any room which has the "Mailbox" view set.
+        * well as any room which has the "Mailbox" view set - unless it
+        * is the DRAFTS room which does not require recipients
         */
 
-       if (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
-          || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
-       ) {
+       if (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
+            || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
+       ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
                if (CC->user.axlevel < AxProbU) {
                        strcpy(recp, "sysop");
                        strcpy(cc, "");
@@ -4943,5 +5184,5 @@ CTDL_MODULE_INIT(msgbase)
        }
 
         /* return our Subversion id for the Log */
-       return "$Id$";
+       return "msgbase";
 }