1 /* Copyright (C) 2004 Christian Schmidt
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 inherit Events.Listener;
26 annotation->set_acquire(mail); //inherit access-rights of mail
28 #include <attributes.h>
39 * This module is used for the new message-system of sTeam 1.5
40 * It provides methods to access different sTeam-objects as mailboxes
41 * for example via IMAP
50 #define LOG_MSG(s, args...) werror("messaging: "+s+"\n", args)
52 #define LOG_MSG(s, args...)
55 string get_quoted_string ( string str ) {
56 if ( !stringp(str) || is_ascii(str) ) return str;
57 return MIME.encode_word( ({ str, "utf-8" }), "q" );
61 string get_quoted_name ( object user ) {
62 if ( !objectp(user) ) return "";
63 string name = user->query_attribute( USER_LASTNAME );
64 if ( !stringp(name) ) name = "";
65 string firstname = user->query_attribute( USER_FIRSTNAME );
66 if ( stringp(firstname) && sizeof(firstname) > 0 )
67 name = firstname + " " + name;
68 if ( sizeof(name) < 1 ) return "";
69 if ( !is_ascii(name) ) name = MIME.encode_word( ({ name, "utf-8" }), "q" );
70 return "\"" + name + "\" ";
75 int is_ascii( string text ) {
78 for ( int i = 0; i < strlen(text); i++ )
88 * implements imap-mailflags
89 * used by message-class below
90 * names of flags can be found in mail.h
96 private int iFlag; //stores the current value of the flag
99 * create a new flag-object
101 * @param void|int flag - the initial value of this flag, defaults to 0
103 void create(void|int tflag)
105 if(tflag) iFlag=tflag;
110 * get the current value of the flag
112 * @return the value of the flag
114 int get() { return iFlag; }
117 * set a new value for the flag
119 * @param int tflag - the new value
122 void set(int tflag) { iFlag = tflag; }
125 * add a specific flag to the current value
127 * @param int tflag - the flag to add
129 void add(int tflag) { iFlag = iFlag | tflag; }
132 * remove a specific flag from the current value
134 * @param int tflag - the flag to remove
136 void del(int tflag) { iFlag = iFlag & (~tflag); }
139 * check if a flag is set
141 * @param int tflag - the flag to query
142 * @return 1 if flag is set, 0 if not
144 int has(int tflag) { return iFlag & tflag; }
148 * needed for "virtual reply mails"
149 * no changes possible to state of flag
154 private int iFlag; //stores the current value of the flag
156 void create(void|int tflag)
161 int get() { return iFlag; }
162 void set(int tflag) {}
163 void add(int tflag) {}
164 void del(int tflag) {}
165 int has(int tflag) { return iFlag & tflag; }
170 * represents a single message
171 * documents and possibly other sTeam-objects may be accessed
172 * as mails/messages this way
177 object oMessage; //the sTeam-Object this message refers to
178 string sSubject, sSender, sBody, sBoundary, sType, sSubtype;
179 array(Message) aoAttachment; //keeps all attachments of the messages
180 mapping(string:string) mHeader; //the e-mail headers
181 int iUid,iSize,iIsAttachment;
186 string sServer = _Server->query_config("machine");
187 string sDomain = _Server->query_config("domain");
188 string sFQDN = sServer+"."+sDomain;
190 final mixed `->(string func) {
191 function f = this_object()[func];
192 // local functions which do not require building the Message
193 if ( func == "get_object_id" ||
194 func == "describe" ||
195 func == "get_object" ||
200 // all calls should be fine immediately
207 int is_mail() { return 1; }
209 void build_message() {
210 if(zero_type(oMessage->query_attribute(MAIL_MIMEHEADERS)))
212 add_header(oRcptUser); //header is missing, add a new one
213 if ( stringp(sSender) )
214 mHeader->from = sSender;
216 LOG_MSG("adding header within creation");
220 mHeader = oMessage->query_attribute(MAIL_MIMEHEADERS);
221 if ( mappingp(mHeader) &&
222 mappingp(oMessage->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL)) )
223 mHeader = oMessage->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL) | mHeader;
227 fFlag = Flag(oMessage->query_attribute(MAIL_IMAPFLAGS));
229 fFlag = 0; //Attachments have no flags
231 int type=oMessage->get_object_class();
232 type-=CLASS_OBJECT; //all sTeam-Objects have type CLASS_OBJECT...
235 case CLASS_DOCUMENT|CLASS_DOCHTML:
236 init_document( oRcptUser ); //see init_document() below
239 case CLASS_DOCUMENT|CLASS_IMAGE:
240 sBody="this message is an image, look at the attachment(s)";
241 sSubject=mHeader["subject"];
244 aoAttachment = ({oMessage});
247 LOG_MSG("Message->create() called for unknown class: "+type);
248 LOG_MSG("subject, sender an body are EMPTY!!!");
249 LOG_MSG("Please add support for this class in Messaging.pmod");
255 * create a new message-object from a sTeam-object
257 * @param object target - sTeam-object to encapsulate
258 * @param int|void isAttachment - set to 1 if this message is "only" an attachment
260 void create(object target, int|void isAttachment, void|object rcptUser, void|string from)
262 if (objectp(target) && functionp(target->is_mail) && target->is_mail() ) {
263 object targetMsg = target->get_msg_object();
264 if (!objectp(targetMsg))
265 steam_error("Cannot duplicate empty message!");
266 sBody = target->body();
267 sSubject = target->subject();
268 sSender = target->sender();
269 sBoundary = target->get_boundary();
270 sType = target->type();
271 sSubtype = target->subtype();
272 fFlag = target->flag();
273 mHeader = copy_value(target->header());
274 iUid = target->uid();
275 iSize = target->size();
276 iIsAttachment = target->is_attachment();
277 oRcptUser = target->get_rcptUser();;
278 oMessage = targetMsg->duplicate();
279 array attached = target->attachments();
280 foreach(attached, object msg) {
281 aoAttachment += ({ Message(msg) });
286 isBuild = 0; // Message has not been build yet
295 oRcptUser = rcptUser;
299 protected string get_body_encoded()
303 mixed content = oMessage->get_content();
304 if ( !stringp(content) || sizeof(content) < 1 ) content = "";
305 string encoding=mHeader["content-transfer-encoding"];
306 if(stringp(encoding)) //determine the transfer-encoding and apply it
308 switch(lower_case(encoding))
311 result=MIME.encode_base64(content)+"\r\n";
313 case "quoted-printable":
314 result=MIME.encode_qp(content)+"\r\n";
316 //"7bit", "8bit" and "binary" mean "no encoding at all", see rfc2045
321 result=content+"\r\n";
325 else result=content+"\r\n";
331 * called if this object encapsulates a sTeam-Document
332 * there should be no need to call this manually
336 void init_document( void|object rcptUser )
341 sBody=get_body_encoded();
344 FATAL( "Failed to read body of message: %O, %O", err[0], err[1] );
345 sBody = "Failed to read body - check errors / contact admin";
348 sSubject=mHeader["subject"]||"";
349 if(!stringp(sSender))
350 sSender=mHeader["from"]||"";
352 sType="MULTIPART"; //default values
354 array tmp = oMessage->get_annotations();
355 for(int i=sizeof(tmp)-1;i>=0;i--)
356 //reverse array, get_annotations returns "wrong" order
357 aoAttachment += ({ Message( tmp[i], 1, rcptUser ) });
358 if(sizeof(aoAttachment)>0) //message has attachments
360 string tmp=mHeader["content-type"];
361 if(tmp!=0) //lookup MIME-boundary-string (see rfc 2045-2049)
363 int start=search(lower_case(tmp),"boundary=");
366 start+=10; //skip info at start of header-line
367 int end=search(tmp,"\"",start);
368 sBoundary=tmp[start..end-1]; //copy found string to sBoundary
374 if(missing) //the email-headers had no boundary, generate one
376 sBoundary=MIME.generate_boundary();
377 tmp="MULTIPART/MIXED; BOUNDARY=\""+sBoundary+"\"";
378 mHeader["content-type"]=tmp;
379 oMessage->set_attribute(MAIL_MIMEHEADERS,mHeader);
380 LOG_MSG("Boundary generated: "+sBoundary);
383 else //message has no attachments
385 sBoundary=""; //non-MIME Message has no boundary-string
386 string tmp=mHeader["content-type"];
388 tmp=oMessage->query_attribute(DOC_MIME_TYPE);
389 int i=sscanf(tmp,"%s/%s;", sType, sSubtype);
396 if (mHeader["content-type"]) {
397 string ct = mHeader["content-type"];
399 if (sscanf(ct, "%*s; charset=%s;%*s", charset)==0)
400 sscanf(ct, "%*s; charset=%s", charset);
402 oMessage->set_attribute(DOC_ENCODING, lower_case(charset));
405 iSize=sizeof(complete_text());
410 string get_subject() {
414 void set_subject( string subject ) {
415 mHeader["subject"] = (is_ascii(subject) ? subject :
416 MIME.encode_word( ({ subject, "utf-8" }), "q" ));
419 string get_boundary() {
423 object get_rcptUser() {
428 * converts a mapping [header:value] to one string
429 * there should be no need to call this manually
431 * @param mapping header - the mapping containing the header of the message
432 * @return one string with the contents of the header (incl. linebreaks)
435 string header2text(mapping header)
438 foreach(indices(header),string key) {
442 ktext = "Content-Type";
445 ktext = "Message-ID";
448 ktext = "Mime-Version";
450 case "content-transfer-encoding":
451 ktext = "Content-Transfer-Encoding";
454 ktext[0] = upper_case(ktext[0..0])[0];
457 dummy+=ktext+": "+header[key]+"\r\n";
459 if ( zero_type(header["mime-version"]) && zero_type(header["Mime-Version"])
460 && zero_type(header["MIME-Version"]) )
461 dummy += "Mime-Version: 1.0 (generated by open-sTeam)\r\n";
467 object get_msg_object() { return oMessage; }
470 * write non permanent data to the sTeam-object of this message
471 * must be called after flag-changes for example
478 oMessage->set_attribute(MAIL_IMAPFLAGS,fFlag->get());
480 if(err) LOG_MSG("error on storing flags: %O",err);
484 * permanently adds a rfc2822-compliant header to the sTeam-object
485 * no need to call this manually
489 void add_header(void|object rcptUser)
491 LOG_MSG("Adding RFC2822 header to object-id: "+oMessage->get_object_id());
494 string tmp=oMessage->query_attribute(OBJ_NAME);
495 if ( !stringp(tmp) ) tmp = "";
497 mHeader["subject"] = (is_ascii(tmp) ? tmp :
498 MIME.encode_word( ({ tmp, "utf-8" }), "q" ));
500 mHeader["date"] = timelib.smtp_time( oMessage->query_attribute(OBJ_CREATION_TIME) );
502 int id=oMessage->get_object_id();
503 tmp="<"+sprintf("%010d",id)+"@"+sFQDN+">";
504 mHeader["message-id"]=tmp;
506 object test=oMessage->query_attribute(DOC_USER_MODIFIED);
507 if(!objectp(test)) test=oMessage->get_creator();
509 tmp = get_quoted_name( test ) + "<" + test->get_identifier() + "@" +
513 if ( !stringp(sSender) )
514 sSender = get_quoted_name( test ) + "<"+test->get_steam_email()+">";
517 FATAL("Warning: Failed to set Sender of Message !");
518 // add header for "to"
519 if ( objectp(test=oMessage->query_attribute("mailto")) )
520 mHeader["to"] = get_quoted_name( test ) + "<" + test->get_identifier()
521 + "@" + _Server->get_server_name() + ">";
522 else if ( objectp(rcptUser) )
523 mHeader["to"] = get_quoted_name( rcptUser ) + "<" +
524 rcptUser->get_user_name() + "@" + _Server->get_server_name() + ">";
526 steam_error("oMessage_ID: " + oMessage->get_object_id() + " Unable to determine \"To:\"-Header for E-Mail !\n");
528 tmp = oMessage->query_attribute(DOC_MIME_TYPE);
529 if ( !stringp(tmp) || sizeof(tmp) < 1 )
530 tmp = "application/x-unknown-content-type";
531 if ( search(lower_case(tmp),"text") >= 0 ) {
532 mHeader["content-type"]=tmp+"; charset=\"utf-8\"";
533 mHeader["content-transfer-encoding"] = "8bit";
536 mHeader["content-type"]=tmp;
537 mHeader["content-transfer-encoding"] = "base64";
539 if ( mappingp(mHeader) &&
540 mappingp(oMessage->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL)) )
541 mHeader = oMessage->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL) | mHeader;
542 mixed err = catch(oMessage->set_attribute(MAIL_MIMEHEADERS,mHeader));
544 FATAL("Failed to add mimeheader (setting attribute) for %O, %O, %O",
545 oMessage, err[0], err[1]);
552 * add additional informations to the header of this message
553 * @param mapping add - the header-lines and values to add
555 void add_to_header(mapping add)
557 foreach(add; string index;)
559 if(zero_type(mHeader[lower_case(index)])) //only add if not already existing
560 mHeader[lower_case(index)]=add[index];
562 oMessage->set_attribute(MAIL_MIMEHEADERS,mHeader);
566 * delete this message (that is: delete the sTeam-object)
567 * existing attachments are removed too
572 foreach(aoAttachment, Message msg)
574 aoAttachment-=({msg});
575 msg->delete(); //remove all attachments
579 if(objectp(fFlag)) destruct(fFlag);
583 * get the uid of this message
585 * @return int unique-identifier
587 int uid() { return iUid; }
590 * get the flag of this message
594 Flag flag() { return fFlag; }
597 * get the subject of this message
599 * @return string subject
601 string subject() { return sSubject; }
604 * get the sender of this message
606 * @return string sender
608 string sender() { return sSender; }
611 * check if this message is an attachment
613 * @return int 1 if message is attachment, 0 if it is a "real" message
615 int is_attachment() { return iIsAttachment; }
618 * get all attachments of this message
620 * @return array(Message) an array of message-objects with attachments of this message
622 array(Message) attachments() { return aoAttachment; }
625 * get the complete header of this message
627 * @return mapping(string:string) mapping with all header-informations
629 mapping(string:string) header() { return mHeader; }
632 * get the body of this message
634 * @return string body
636 string body() { return sBody; }
639 * get the date of this message
641 * @return int date of creation
645 return oMessage->query_attribute(OBJ_CREATION_TIME);
649 * get the size of this message
651 * @return int size in bytes
653 int size() { return iSize; }
656 * get the size of the body of this message
658 * @return int size of the body in bytes
660 int body_size() { return sizeof(sBody); }
663 * get the object-id of this message
665 * @return int object-id
667 int get_object_id() { return oMessage->get_object_id(); }
670 * check if this message has attachments
672 * @return int 0 if no attachments exists or the number of attachments
674 int has_attachments() { return sizeof(aoAttachment); }
676 MIME.Message mime_message()
679 if(sizeof(aoAttachment))
680 msg = MIME.Message(sBody, mHeader, aoAttachment->mime_message());
682 msg = MIME.Message(sBody, mHeader);
683 msg->setboundary(sBoundary);
688 * returns the text-version of this message (rfc2822-style)
690 * @return string rfc2822-text of the message
692 string complete_text(void|int attach)
696 mHeader["content-type"] += "; name=\"" + sSubject + "\"";
698 dummy+=header2text(mHeader);
700 if(sizeof(aoAttachment)>0)
702 dummy += "--"+sBoundary+"\r\ncontent-type: " +
703 oMessage->query_attribute(DOC_MIME_TYPE) + "\r\n\r\n";
705 for(int i=0;i<sizeof(aoAttachment);i++)
707 dummy+="\r\n\r\n--"+sBoundary+"\r\n";
708 // insert all attachments
709 dummy+=aoAttachment[i]->complete_text(1);
711 dummy+="\r\n\r\n--"+sBoundary+"--\r\n\r\n";
720 * get the mimetype (first part) of this message
722 * @return string mimetype (first part)
724 string type() { return sType; }
727 * get the mimetype (second part) of this message
729 * @return string mimetype (second part)
731 string subtype() { return sSubtype; }
734 * duplicates a message, including all attachments
736 * @return Message duplicated message
740 Message msg = Message(this_object());
743 //return Message(copy);
747 * gives all access-rights on this message to a user
748 * has to be called by a user who has sufficient rights
751 * @param object user the user-object to grant access to
752 * @return 1 if successful, 0 otherwise
754 int grant_access(object user)
758 oMessage->sanction_object(user,SANCTION_ALL);
759 foreach(aoAttachment, Message msg)
760 msg->grant_access(user);
764 LOG_MSG("error on granting access to "+user->get_identifier()+" on #"+oMessage->get_object_id()+" :%O",err);
771 class ContainerMessage
775 void create(object target, int|void isAttachment)
777 aoAttachment=({}); //default: no attachments
780 LOG_MSG("ContainerMessage->create("+target->get_object_id()+")");
784 LOG_MSG("creating as attachment");
786 else iIsAttachment = 0;
788 if (zero_type(target->query_attribute(MAIL_MIMEHEADERS)))
791 if ( mappingp(mHeader) &&
792 mappingp(target->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL)) )
793 mHeader = target->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL) | mHeader;
798 mHeader = target->query_attribute(MAIL_MIMEHEADERS); //copy header from target
799 m_delete(mHeader,"content-transfer-encoding");
800 m_delete(mHeader,"content-disposition");
801 sBoundary="'sTeaMail-RaNdOm-StRiNg-/=_."+target->get_object_id()+"."+target->query_attribute(OBJ_CREATION_TIME)+":";
802 mHeader["content-type"]="multipart/mixed; boundary=\""+sBoundary+"\"";
803 mHeader["mime-version"]="1.0";
804 sBody="This is a multi-part message in MIME format.";
805 aoAttachment=({ContainerMessage(target,1)}); //store target as first attachment
806 oMessage=aoAttachment[0];
807 fFlag = Flag(target->query_attribute(MAIL_IMAPFLAGS));
812 array tmp = target->get_annotations();
813 for(int i=sizeof(tmp)-1;i>=0;i--)
814 aoAttachment+=({ContainerMessage(tmp[i],1)});
821 mapping temp=oMessage->query_attribute(MAIL_MIMEHEADERS);
823 string test=temp["content-transfer-encoding"];
824 if(stringp(test)) mHeader["content-transfer-encoding"]=test;
825 test=temp["content-disposition"];
826 if(stringp(test)) mHeader["content-disposition"]=test;
827 test=temp["content-type"];
830 mHeader["content-type"]=test;
831 sscanf(test,"%s/%s;",sType,sSubtype);
833 else //unknown content-type...
838 sBody=get_body_encoded();
839 fFlag = 0; //Attachments have no flags
841 if(objectp(target->get_annotating())) //is this a "real" attachment or just the document itself?
843 array tmp = oMessage->get_annotations();
844 for(int i=sizeof(tmp)-1;i>=0;i--)
845 aoAttachment+=({ContainerMessage(tmp[i],1)});
852 * called if this object encapsulates a sTeam-Document
853 * there should be no need to call this manually
859 sSubject=mHeader["subject"];
860 if(!stringp(sSubject)) sSubject="";
861 sSender=mHeader["from"];
862 if(!stringp(sSender)) sSender="";
864 iSize=sizeof(complete_text());
869 object get_msg_object() { return oMessage; }
872 if(objectp(oMessage)) return oMessage;
886 if(iIsAttachment) return;
888 { //flag is stored in first attachment as message-object is non-existant!
890 if(err) LOG_MSG("error on storing flags: %O",err);
895 return oMessage->get_object_id();
900 if(iIsAttachment) return oMessage->query_attribute(OBJ_CREATION_TIME);
901 else return aoAttachment[0]->internal_date();
906 foreach(aoAttachment, Message msg)
908 aoAttachment-=({msg});
909 msg->delete(); //remove all attachments
912 if(iIsAttachment) oMessage->delete();
913 if(objectp(fFlag)) destruct(fFlag);
916 int grant_access(object user)
920 // oMessage->sanction_object(user,SANCTION_ALL);
921 foreach(aoAttachment, Message msg)
922 msg->grant_access(user);
934 * subclass of Message
935 * used for accessing sTeam-messageboards entries
937 class MessageboardMessage
941 void create(object target, int|void isAttachment)
943 LOG_MSG("MessageboardMessage->create("+target->get_object_id()+")");
945 aoAttachment = ({}); //messageboard-entries have no attachments...
948 fFlag = Flag(oMessage->query_attribute(MAIL_IMAPFLAGS));
950 if(zero_type(oMessage->query_attribute(MAIL_MIMEHEADERS)))
951 add_header(); //header is missing, add a new one
953 mHeader = oMessage->query_attribute(MAIL_MIMEHEADERS);
954 if ( mappingp(mHeader) &&
955 mappingp(oMessage->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL)) )
956 mHeader = oMessage->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL) | mHeader;
959 int type=oMessage->get_object_class();
960 if(type & CLASS_DOCUMENT)
961 init_document(); //see init_document() below
964 LOG_MSG("MessageboardMessage->create() called for unknown class: "+type);
965 LOG_MSG("subject, sender an body are EMPTY!!!");
966 LOG_MSG("Please add support for this class in Messaging.pmod");
968 LOG_MSG("NessageboardMessage->create("+target->get_object_id()+") finished!");
974 sBody=oMessage->get_content()+"\r\n";
976 string charset = oMessage->query_attribute(DOC_ENCODING);
977 if ( stringp(charset) && charset != "utf-8" ) {
978 object enc = Locale.Charset.encoder("utf-8");
979 object dec = Locale.Charset.decoder(charset);
980 sBody = dec->feed(sBody)->drain();
981 sBody = enc->feed(sBody)->drain();
982 // now body should be utf
985 sSubject=mHeader["subject"];
988 iSize=sizeof(complete_text());
989 if(!stringp(mHeader["reply-to"]) || mHeader["reply-to"]=="")
991 mHeader["reply-to"]="<"+oMessage->get_object_id()+"@"+sFQDN+">";
992 oMessage->set_attribute(MAIL_MIMEHEADERS,mHeader);
1001 LOG_MSG("Adding RFC2822 header to object-id: "+oMessage->get_object_id());
1004 mHeader["subject"] = oMessage->query_attribute(OBJ_NAME) || "";
1005 mHeader["date"] = timelib.smtp_time(
1006 oMessage->query_attribute(OBJ_CREATION_TIME));
1008 int id=oMessage->get_object_id();
1009 mixed tmp="<"+sprintf("%010d",id)+"@"+sFQDN+">";
1010 //replies should go to the messageboard, not the sender!
1011 mHeader["reply-to"]=tmp;
1012 mHeader["message-id"]=tmp; //use id@server as message-id, too
1014 object test=oMessage->query_attribute(DOC_USER_MODIFIED);
1016 mHeader["from"] = get_quoted_name( test ) + "<" + test->get_identifier() + "@" + sFQDN + ">";
1018 test=oMessage->get_annotating();
1019 if(objectp(test) && !(test->get_object_class() & CLASS_MESSAGEBOARD) )
1021 mapping parentheader = test->query_attribute(MAIL_MIMEHEADERS);
1022 if(mappingp(parentheader)) {
1023 if(stringp(parentheader["message-id"]))
1024 mHeader["in-reply-to"]=parentheader["message-id"];
1025 if(stringp(parentheader["references"]))
1026 mHeader["references"]=parentheader["message-id"]+","+
1027 parentheader["references"];
1029 mHeader["references"]=parentheader["message-id"];
1033 tmp=oMessage->query_attribute(DOC_MIME_TYPE) || MIMETYPE_UNKNOWN;;
1034 mHeader["content-type"]=tmp+"; charset=\"utf-8\"";
1035 LOG_MSG("new header is:%O",mHeader);
1036 mapping add_headers = oMessage->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL) || ([ ]);
1037 oMessage->set_attribute(MAIL_MIMEHEADERS,mHeader|add_headers);
1044 * class for "virtual reply mails", subclass of Message
1045 * these mails are shown in every mailbox of type container or messageboard
1046 * in order to give a simple way to adress mails to the mailbox itself
1048 * this means that a message of this class has a reply-adress with the
1049 * object-id of the mailbox it is shown in
1056 private object oMailBox;
1059 * create new ReplyMessage
1060 * @param object target - the mailbox this message is created in
1062 void create(object target, int|void isAttachment)
1066 iIsAttachment = 0; //reply-mails are no attachments...
1067 aoAttachment = ({}); //...and have no attachments
1069 fFlag = NullFlag(); //can't store any flags on this message
1071 iUid = oMailBox->get_object_id(); //we have no uid, so get uid of mailbox we are in
1072 iTime = oMailBox->query_attribute(OBJ_CREATION_TIME);
1074 //header is always the same...
1075 mHeader = ([ "from":"\"sTeam-Mail\" <"+iUid+"@"+sFQDN+">",
1076 "subject":"neues Dokument erzeugen -create new document",
1077 "date":ctime(iTime)-"\n",
1078 "message-id":"<"+sprintf("%010d",iUid)+"@"+sFQDN+">",
1079 "reply-to":"<"+sprintf("%010d",iUid)+"@"+sFQDN+">",
1081 "x-msmail-priority":"High"]);
1083 //content only describes use of this reply-mail
1084 sBody = "Um ein neues Dokument in diesem Raum zu erzeugen, beantworte einfach diese Mail!\n";
1085 sSubject = mHeader["subject"];
1086 sSender = mHeader["from"];
1088 //message-object does not exist!
1091 iSize=sizeof(complete_text());
1093 sType = "text"; //obvious...
1097 //there is no sTeam-object for this message...
1099 //...so flags don't change...
1102 //...and header's won't do so, too
1104 void add_header() {}
1107 void add_to_header(mapping add) {}
1109 //just delete the flag-object
1120 int get_object_id() { return iUid; }
1122 //senseless, but if someone calls it... (may happen when copying mails)
1125 return ReplyMessage(oMailBox);
1128 int grant_access(object user)
1136 * Listener for for events called by "external" programs,
1137 * for example by sTeam-operations on a encapsulated mailbox
1138 * or another mailbox-object (more than one mailbox-object may
1139 * exist for a single sTeam-object)
1141 class MessagingListener {
1144 function fCallback; //stores the callback function
1145 void create(int events, object obj, function callback) {
1146 ::create(events, PHASE_NOTIFY, obj, 0);
1147 fCallback = callback;
1148 obj->listen_event(this_object());
1151 void notify(int event, mixed args) {
1152 if ( functionp(fCallback) )
1153 fCallback(event, @args);
1155 mapping save() { return 0; }
1158 return "MessagingListener()";
1163 * BaseMailBox is the root-class of all mailbox-types
1164 * it's pretty useless alone...
1165 * think of it as an "interface"
1170 object oMailBox; //sTeam-object this box is built from
1171 array(Message) aoMessages;
1172 mapping oidMessageCache;
1173 int iAllowedTypes, iFolderTypes;
1174 array aiEnterEvent, aiLeaveEvent;
1175 array(MessagingListener) alEnter, alLeave;
1179 * create a new BaseMailBox-object
1181 * @param object target - the sTeam-object to build MailBox around
1183 void create(object target)
1186 oidMessageCache = ([ ]);
1187 //add code in sub-classes
1191 * callback function for events when inventory changes
1194 void notify_me(int event, mixed ... args)
1196 LOG_MSG("Event "+event+"%O",args);
1202 object get_object() {
1207 * needed for sTeam-security
1209 * @return the sTeam-object for this MailBox
1216 * get the messages in this mailbox
1218 * @return array of message-objects
1220 array(Message) messages()
1226 * get the number of messages in this mailbox
1228 * @return int containing the number of messages
1230 int get_num_messages()
1232 return sizeof(aoMessages);
1236 * get a specific message
1238 * @param int msn - the message sequence number of the desired mail, 1 is first message
1239 * @return a message-object containing the desired message
1241 Message get_message(int msn)
1243 if(msn<=sizeof(aoMessages))
1244 return aoMessages[msn-1]; //MSNs are counted from 1, not from 0
1247 FATAL("get_message() called for illegal msn: "+msn+
1248 " (max is "+sizeof(aoMessages)+")");
1252 Message get_message_by_oid(int oid) {
1253 if ( oidMessageCache[oid] )
1254 return oidMessageCache[oid];
1255 for ( int i = 0; i < sizeof(aoMessages); i++ ) {
1256 if ( objectp(aoMessages[i]) )
1257 oidMessageCache[aoMessages[i]->get_object_id()] = aoMessages[i];
1259 return oidMessageCache[oid];
1263 * delete all messages which have the "deleted"-flag set
1264 * (like "expunge" in IMAP)
1271 LOG_MSG("delete_mails() called for "+oMailBox->get_object_id());
1272 for(int i=sizeof(aoMessages)-1;i>=0;i--)
1274 LOG_MSG("flags for #"+i+": "+aoMessages[i]->flag()->get());
1275 if(aoMessages[i]->flag()->has(DELETED))
1277 LOG_MSG("deleting #"+i);
1278 Message tmp = aoMessages[i];
1279 aoMessages -= ({tmp});
1287 * delete this mailbox and all messages inside
1292 for(int i=0;i<sizeof(aoMessages);i++) //remove all messages
1293 aoMessages[i]->delete();
1294 oMailBox->delete(); //remove mailbox
1298 * convert unique-ids to message sequence numbers
1300 * @param array - the uids to convert
1301 * @return array of matching msns
1303 array uid_to_num(int|array uids)
1306 mapping(int:int) temp=([]);
1307 for(int i=0;i<sizeof(aoMessages);i++) //map ALL ids to sequence numbers
1308 temp+=([aoMessages[i]->get_object_id():i+1]); //numbers start at 1
1309 foreach(uids, int i) //take requested elements from complete mapping
1310 if(zero_type(temp[i])!=1) res+=({temp[i]});
1314 int map_object(object msg, int rbegin, int rend)
1316 if ( objectp(msg) ) {
1317 int id = msg->get_object_id();
1318 if ( id >= rbegin && id <= rend )
1321 return 0; // left out
1324 array filter_uids(array uids)
1327 foreach(uids, mixed range) {
1328 if ( arrayp(range) ) {
1329 res += (map(aoMessages, map_object, range[0], range[1]) - ({ 0 }));
1332 Message msg = get_message_by_oid(range);
1341 * get a mapping of unique ids to message sequence numbers
1343 * @return mapping(int:int) of all existing uids in this box to msns
1345 mapping(int:int) get_uid2msn_mapping()
1347 mapping(int:int) temp=([]);
1348 for(int i=0;i<sizeof(aoMessages);i++)
1349 temp+=([aoMessages[i]->get_object_id():i+1]); //msns start at 1, not at 0
1354 * get the total size of this box (that is: size of all messages)
1356 * @return int containing the size in bytes
1361 foreach(aoMessages, Message msg)
1367 * get the object id of this box (-> the sTeam-object of this box)
1369 * @return int object-id
1373 return oMailBox->get_object_id();
1377 * get the identifying string of this box
1379 * @return string containing the identifier
1381 string get_identifier()
1383 return oMailBox->get_identifier();
1387 * does this mailbox have subfolders?
1389 * @return int number of subfolders, 0 if no exist
1391 int has_subfolders() { return 0; }
1394 * get a subfolder of this box
1396 * @param string subfolder - the name of the subfolder to fetch
1397 * @return a MailBox-object of the subfolder, 0 if folder doesn't exist
1399 BaseMailBox get_subfolder(string subfolder) { return 0; }
1402 * list all subfolders of this mailbox
1404 * @param int recurse - how "deep" should be searched for folders, -1 means unlimited
1405 * @return array of mailbox-names, hierarchies seperated by "/"
1407 array list_subfolders(int recurse) { return({}); }
1410 * get the type of sTeam-objects which are allowed as messages in this box
1412 * @return int with class-type (see classes.h for values)
1414 int allowed_types() { return iAllowedTypes; }
1417 * get the event that is called when a message is removed from this box
1419 * @return int value of event (see events.h)
1421 array leave_event() { return aiLeaveEvent; }
1424 * get the event that is called when a message is added to this box
1426 * @return int value of event (see events.h)
1428 array enter_event() { return aiEnterEvent; }
1431 * add a message to this box
1433 * @param Message msg - message to add
1435 void add_message(Message msg) {};
1438 * rebuild the inventory of this mailbox
1439 * called via event if contents of the encapsulated sTeam-object change
1441 void rebuild_box() {};
1445 * UserMailBox manages access to the messages of a user-object
1446 * these are stored as annotations to the user
1453 void create(object target)
1456 iAllowedTypes = CLASS_DOCUMENT; //users store their mails as documents
1457 iFolderTypes = CLASS_CONTAINER;
1458 aiEnterEvent = ({EVENT_ANNOTATE});
1459 aiLeaveEvent = ({EVENT_REMOVE_ANNOTATION});
1463 LOG_MSG("UserMailBox->create() called:");
1464 LOG_MSG("target id: "+target->get_object_id());
1465 LOG_MSG("target name: "+target->get_identifier());
1467 array inv=target->get_annotations_by_class(iAllowedTypes);
1468 LOG_MSG("box size is: "+sizeof(inv));
1470 for(int i=sizeof(inv)-1;i>=0;i--) //reverse order of inv
1474 msg = Message(inv[i], 0, oTarget);
1477 FATAL("Failed to create Messaging.Message for %O\n", inv[i]);
1478 FATAL("%O: %O", err[0], err[1]);
1481 aoMessages+=({msg});
1483 alEnter=({MessagingListener(aiEnterEvent[0], oMailBox, notify_me)});
1484 alLeave=({MessagingListener(aiLeaveEvent[0], oMailBox, notify_me)});
1486 LOG_MSG("Summary of mailbox-creation:");
1487 for(int i=0;i<sizeof(aoMessages);i++)
1488 LOG_MSG("#"+i+" id:"+aoMessages[i]->get_object_id()+
1489 " Subject: "+aoMessages[i]->subject()+
1490 " ["+aoMessages[i]->has_attachments()+" Attachment(s)]");
1497 array tmp = oMailBox->get_annotations_by_class(iAllowedTypes);
1498 if(sizeof(tmp)==sizeof(aoMessages))
1499 return; //nothing changed...
1500 LOG_MSG("rebuilding box #"+oMailBox->get_object_id());
1504 for(i=0;i<sizeof(tmp);i++) new_ids+=({tmp[i]->get_object_id()});
1505 // LOG_MSG("new ids:%O",new_ids);
1507 for(i=0;i<sizeof(aoMessages);i++) old_ids+=({aoMessages[i]->get_object_id()});
1508 // LOG_MSG("old ids:%O",old_ids);
1509 if(sizeof(old_ids)>sizeof(new_ids)) //one ore more messages were removed
1511 array diff=old_ids-new_ids;
1512 // LOG_MSG("diff is:%O",diff);
1513 for(i=sizeof(aoMessages)-1;i>=0;i--)
1514 if(search(diff,aoMessages[i]->get_object_id())!=-1)
1516 LOG_MSG("removing lost #"+aoMessages[i]->get_object_id());
1517 aoMessages-=({aoMessages[i]});
1520 else //messages were added
1522 array diff=new_ids-old_ids;
1523 for(i=0;i<sizeof(diff);i++)
1525 LOG_MSG("adding new #"+diff[i]);
1527 object diffobj = _Database->find_object(diff[i]);
1528 if ( !objectp(diffobj) )
1531 msg = Message(diffobj, 0, oTarget);
1534 FATAL("Failed to create Messaging.Message (rebuilding box\
1535 ) for %O\n", diffobj);
1536 FATAL("%O: %O", err[0], err[1]);
1539 aoMessages+=({msg});
1547 int has_subfolders()
1549 array folders = oMailBox->get_annotations_by_class(iFolderTypes);
1550 return(sizeof(folders));
1554 * search for a specific folder inside a given object
1555 * no need to call this manually
1557 * @param object where - the sTeam-object to search in
1558 * @param string what - the name of the folder to search for
1559 * @return object with the folder, 0 if folder is not found
1562 private object search_for_folder(object where, string what)
1564 array folders = where->get_annotations_by_class(iFolderTypes);
1565 if(sizeof(folders)>0)
1568 while(i<sizeof(folders))
1570 if(folders[i]->get_identifier()==what)
1574 LOG_MSG("subfolder not found: "+what);
1581 BaseMailBox get_subfolder(string subfolder)
1584 array parts=subfolder/"/"; //it is possible to fetch a "deep" folder with one call
1586 object current = oMailBox;
1587 while(i<sizeof(parts) && !fail) //search for folders "deeper" into this box
1589 current = search_for_folder(current, parts[i]);
1590 if(objectp(current)) i++;
1593 if(fail) return 0; //couldn't find subfolder
1594 return UserMailBox(current);
1597 int create_subfolder(string subfolder)
1599 if(objectp(get_subfolder(subfolder))) return 0; //subfolder already exists
1600 object factory = _Server->get_factory(CLASS_CONTAINER);
1601 object tmp = factory->execute( (["name":subfolder]) );
1602 oMailBox->add_annotation(tmp);
1603 tmp->set_acquire(oMailBox);
1607 array list_subfolders(int recurse)
1610 if(recurse==0) return res; //nothing to do...
1611 if(recurse==1) //easy, just get anns of this box
1613 array folders = oMailBox->get_annotations_by_class(iFolderTypes);
1614 if(sizeof(folders)>0)
1616 for(int i=0;i<sizeof(folders);i++)
1617 res+=({folders[i]->get_identifier() });
1620 else return ({}); //no subfolders in this mailbox
1622 else //perform BFS on this mailbox
1624 int tid = oMailBox->get_object_id();
1625 array all = ({tid});
1626 array queue = ({tid});
1627 mapping(int:int) discover=([tid:0]);
1628 mapping(int:int) parent=([tid:0]); //starting vertex has no parent
1630 while(sizeof(queue)>0 && discover[queue[0]]!=recurse)
1633 array inv=_Database->find_object(tid)->get_annotations_by_class(iFolderTypes);
1634 foreach(inv, object v)
1636 int id=v->get_object_id();
1637 if(sizeof(all&({id}))==0)
1639 all+=({id}); queue+=({id});
1640 discover+=([id:discover[tid]+1]);
1641 parent[id]=tid; //this vertex has been discovered from "tid"
1644 queue-=({tid}); iter++;
1645 } //BFS is complete here
1646 int mboxid=oMailBox->get_object_id();
1647 m_delete(discover,mboxid); //mailbox is not part of result
1648 if(sizeof(discover)==0) return ({}); //no subfolders in mailbox
1649 foreach(indices(discover), tid)
1650 { //build the complete path to each discovered folder
1654 path="/"+_Database->find_object(tid)->get_identifier()+path;
1657 path=path[1..sizeof(path)-1]; //remove first "/";
1664 void add_message(Message msg)
1666 aoMessages+=({msg});
1669 LOG_MSG("added #"+msg->get_object_id()+" to user-box #"+oMailBox->get_object_id());
1674 * this class wraps a sTeam-Object of type "container" (but NOT "user")
1675 * for user-objects use "UserMailBox" instead
1677 class ContainerMailBox
1681 void create(object target)
1684 iAllowedTypes = CLASS_DOCUMENT | CLASS_IMAGE;
1685 iFolderTypes = CLASS_CONTAINER | CLASS_ROOM | CLASS_TRASHBIN | CLASS_EXIT | CLASS_MESSAGEBOARD;
1686 aiEnterEvent = ({EVENT_ENTER_INVENTORY});
1687 aiLeaveEvent = ({EVENT_LEAVE_INVENTORY});
1691 LOG_MSG("ContainerMailBox->create() called:");
1692 LOG_MSG("target id: "+target->get_object_id());
1693 LOG_MSG("target name: "+target->get_identifier());
1695 array inv=target->get_inventory_by_class(iAllowedTypes);
1696 LOG_MSG("box size is: "+sizeof(inv));
1698 Message reply = ReplyMessage(oMailBox);
1699 aoMessages+=({reply});
1700 LOG_MSG("added reply-mail to mailbox");
1702 for(int i=0;i<sizeof(inv);i++)
1705 Message msg=ContainerMessage(inv[i]); //ContainerMailBox has ContainerMessages...
1706 aoMessages+=({msg});
1709 FATAL("Failed to create Message: %O\n%O\n", err[0], err[1]);
1712 alEnter=({MessagingListener(aiEnterEvent[0] | aiLeaveEvent[0], oMailBox, notify_me)});
1714 LOG_MSG("Summary of mailbox-creation:");
1715 for(int i=0;i<sizeof(aoMessages);i++)
1716 LOG_MSG("#"+i+" id:"+aoMessages[i]->get_object_id()+
1717 " Subject: "+aoMessages[i]->subject()+
1718 " ["+aoMessages[i]->has_attachments()+" Attachment(s)]");
1725 array tmp = oMailBox->get_inventory_by_class(iAllowedTypes);
1726 if(sizeof(tmp)==sizeof(aoMessages)-1) //reply-mail doesn't exist in sTeam!!
1727 return; //nothing changed...
1728 LOG_MSG("rebuilding box #"+oMailBox->get_object_id());
1732 for(i=0;i<sizeof(tmp);i++) new_ids+=({tmp[i]->get_object_id()});
1733 // LOG_MSG("new ids:%O",new_ids);
1735 for(i=1;i<sizeof(aoMessages);i++) old_ids+=({aoMessages[i]->get_object_id()});
1736 // LOG_MSG("old ids:%O",old_ids);
1737 if(sizeof(old_ids)>sizeof(new_ids)) //one ore more messages were removed
1739 array diff=old_ids-new_ids;
1740 // LOG_MSG("diff is:%O",diff);
1741 for(i=sizeof(aoMessages)-1;i>=1;i--)
1742 if(search(diff,aoMessages[i]->get_object_id())!=-1)
1744 LOG_MSG("removing lost #"+aoMessages[i]->get_object_id());
1745 aoMessages-=({aoMessages[i]});
1748 else //messages were added
1750 array diff=new_ids-old_ids;
1751 // LOG_MSG("diff is:%O",diff);
1752 for(i=0;i<sizeof(diff);i++)
1754 LOG_MSG("adding new #"+diff[i]);
1755 Message msg=ContainerMessage(_Database->find_object(diff[i]));
1756 aoMessages+=({msg});
1763 int has_subfolders()
1765 array folders = oMailBox->get_inventory_by_class(iFolderTypes);
1766 for(int i=sizeof(folders);i>=0;i--) //remove users from list
1767 if(folders[i]->get_object_class() & CLASS_USER) folders-=({folders[i]});
1768 return(sizeof(folders));
1772 private object search_for_folder(object where, string what)
1774 if(where->get_object_class() & CLASS_EXIT) where=where->get_exit();
1775 array folders = where->get_inventory_by_class(iFolderTypes);
1776 if(sizeof(folders)>0)
1779 while(i<sizeof(folders))
1781 if(folders[i]->get_object_class() & CLASS_EXIT)
1782 folders[i]=folders[i]->get_exit();
1783 if(folders[i]->get_identifier()==what )
1787 LOG_MSG("subfolder not found: "+what);
1794 BaseMailBox get_subfolder(string subfolder)
1797 array parts=subfolder/"/"; //it is possible to fetch a "deep" folder with one call
1799 object current = oMailBox;
1800 while(i<sizeof(parts) && !fail) //search for folders "deeper" into this box
1802 current = search_for_folder(current, parts[i]);
1803 if(objectp(current)) i++;
1806 if(fail) return 0; //couldn't find subfolder
1807 if(current->get_object_class() & CLASS_EXIT)
1808 current=current->get_exit(); //get target object of exit
1809 if(current->get_object_class() & CLASS_CONTAINER)
1810 return ContainerMailBox(current);
1812 if(current->get_object_class() & CLASS_MESSAGEBOARD)
1813 return MessageboardMailBox(current);
1814 else return 0; //unsupported object-type
1817 int create_subfolder(string subfolder)
1819 if(objectp(get_subfolder(subfolder))) return 0; //subfolder already exists
1820 object factory = _Server->get_factory(CLASS_CONTAINER);
1821 object tmp = factory->execute( (["name":subfolder]) );
1822 mixed err=catch {tmp->move(oMailBox);};
1831 array list_subfolders(int recurse)
1834 if(recurse==0) return res; //nothing to do...
1835 if(recurse==1) //easy, just get subfolders of this box
1837 array folders = oMailBox->get_inventory_by_class(iFolderTypes);
1838 if(sizeof(folders)>0)
1840 for(int i=0;i<sizeof(folders);i++)
1841 res+=({folders[i]->get_identifier() });
1844 else return ({}); //no subfolders in this mailbox
1846 else //perform BFS on this mailbox
1848 int tid = oMailBox->get_object_id();
1849 array all = ({tid});
1850 array queue = ({tid});
1851 mapping(int:int) discover=([tid:0]);
1852 mapping(int:int) parent=([tid:0]); //starting vertex has no parent
1854 while(sizeof(queue)>0 && discover[queue[0]]!=recurse)
1857 object current=_Database->find_object(tid);
1859 if(current->get_object_class() & CLASS_MESSAGEBOARD)
1861 //messageboards have no subfolders, search for this vertex is complete
1863 inv=current->get_inventory_by_class(iFolderTypes);
1864 foreach(inv, object v)
1867 if(v->get_object_class() & CLASS_EXIT)
1869 mixed err=catch{id=v->get_exit()->get_object_id();};
1870 if(err!=0) continue; //can't access target of exit, ignore it
1873 id=v->get_object_id();
1874 if(sizeof(all&({id}))==0)
1876 all+=({id}); queue+=({id});
1877 discover+=([id:discover[tid]+1]);
1878 parent[id]=tid; //this vertex has been discovered from "tid"
1881 queue-=({tid}); iter++;
1882 } //BFS is complete here
1883 int mboxid=oMailBox->get_object_id();
1884 m_delete(discover,mboxid); //mailbox is not part of result
1885 if(sizeof(discover)==0) return ({}); //no subfolders in mailbox
1886 foreach(indices(discover), tid)
1887 { //build the complete path to each discovered folder
1891 path="/"+_Database->find_object(tid)->get_identifier()+path;
1894 path=path[1..sizeof(path)-1]; //remove first "/";
1901 void add_message(Message msg)
1903 LOG_MSG("adding #"+msg->get_object_id()+" to container-box #"+oMailBox->get_object_id());
1905 int i=msg->has_attachments();
1906 if(i==0) //no attachments -> store without modification
1908 mapping temp=msg->header();
1909 // FIXME: in-reply-to and references should not really be removed
1910 m_delete(temp,"in-reply-to");
1911 m_delete(temp,"references");
1912 object msgObj = msg->get_msg_object();
1913 msgObj->set_attribute(MAIL_MIMEHEADERS,temp);
1914 LOG_MSG("...stored");
1917 LOG_MSG("Type of #"+msg->get_object_id()+" is: "+msg->type()+"/"+msg->subtype());
1918 LOG_MSG("#"+msg->get_object_id()+" has "+i+" attachments, now converting...");
1919 array(Message) amAttachments=msg->attachments();
1920 array(Message) amText=({});
1921 array(Message) amNonText=({});
1922 for(int i=0; i<sizeof(amAttachments); i++)
1924 string type = amAttachments[i]->type();
1925 string subtype = amAttachments[i]->subtype();
1926 LOG_MSG("attachment #"+i+" : "+type+"/"+subtype);
1927 if(lower_case(type)=="text")
1929 amText+=({amAttachments[i]});
1930 LOG_MSG("-> comment to new document");
1934 amNonText+=({amAttachments[i]});
1935 LOG_MSG("-> new document");
1938 LOG_MSG("found "+sizeof(amText)+" textual part(s) and "+sizeof(amNonText)+" non-textual part(s)");
1941 array annotations = ({ });
1942 if ( sizeof(amText) >= 1 )
1944 LOG_MSG("converting text to annotation, non-text to document:");
1945 Message ann=amText[0];
1946 foreach ( amText, Message ann ) {
1949 if ( sizeof(amNonText) >=1 ) {
1950 foreach ( amNonText, Message doc ) {
1951 doc->add_to_header( msg->header() );
1952 object docObj = doc->get_msg_object();
1953 docObj->set_acquire(docObj->get_environment);
1954 foreach( annotations, object annotation )
1958 foreach ( annotations, object annotation )
1960 LOG_MSG("finished!");
1965 * this class is used for accessing sTeam-messageboards
1967 class MessageboardMailBox
1971 void create(object target)
1973 LOG_MSG("MessageboardMailBox->create() called:");
1975 iAllowedTypes = CLASS_DOCUMENT;
1976 iFolderTypes = 0; //messageboards have no subfolders
1977 aiEnterEvent = ({EVENT_ANNOTATE | EVENTS_MONITORED, EVENT_ANNOTATE});
1978 aiLeaveEvent = ({EVENT_REMOVE_ANNOTATION | EVENTS_MONITORED, EVENT_REMOVE_ANNOTATION});
1980 Message reply = ReplyMessage(oMailBox); //to simplify creation of new threads
1981 aoMessages=({reply});
1982 LOG_MSG("added reply-mail to mailbox");
1983 LOG_MSG("target id: "+target->get_object_id());
1984 LOG_MSG("target name: "+target->get_identifier());
1985 aoMessages+=scan_board_structure();
1986 LOG_MSG("box size is "+(sizeof(aoMessages)-1));
1987 alEnter=({MessagingListener(aiEnterEvent[0], oMailBox, notify_enter),
1988 MessagingListener(aiEnterEvent[1], oMailBox, notify_enter)});
1989 alLeave=({MessagingListener(aiLeaveEvent[0], oMailBox, notify_leave),
1990 MessagingListener(aiLeaveEvent[1], oMailBox, notify_leave)});
1993 void notify_enter(int event, mixed ... args)
1996 if(event & EVENTS_MONITORED) what=args[3];
1998 LOG_MSG("new message to box #"+oMailBox->get_object_id()+" :"+what->get_object_id());
1999 if(what->get_object_class() & iAllowedTypes)
2000 aoMessages+=({MessageboardMessage(what)});
2001 else LOG_MSG("... can't be converted to message - ignored");
2004 void notify_leave(int event, mixed ... args)
2007 if(event & EVENTS_MONITORED) what=args[3];
2009 int id=what->get_object_id();
2010 LOG_MSG("remove message in box #"+oMailBox->get_object_id()+" :"+id);
2011 if(what->get_object_class() & iAllowedTypes)
2015 while(found!=1 && i<sizeof(aoMessages))
2017 if(aoMessages[i]->get_object_id()==id) found=1;
2020 if(found) aoMessages-=({aoMessages[i]});
2021 else LOG_MSG("message not found, already removed");
2023 else LOG_MSG("... can't be converted to message - ignored");
2027 private array(Message) scan_board_structure()
2029 //perform BFS on anns of this board
2030 array(Message) res = ({});
2031 int tid = oMailBox->get_object_id();
2032 array all = ({tid});
2033 array queue = ({tid});
2034 mapping(int:int) discover=([tid:0]);
2035 mapping(int:int) parent=([tid:0]); //starting vertex has no parent
2037 while(sizeof(queue)>0)
2040 array inv=_Database->find_object(tid)->get_annotations();
2041 foreach(inv, object v)
2043 int id=v->get_object_id();
2044 if(sizeof(all&({id}))==0)
2046 all+=({id}); queue+=({id});
2047 discover+=([id:discover[tid]+1]);
2048 parent[id]=tid; //this vertex has been discovered from "tid"
2051 queue-=({tid}); iter++;
2052 } //BFS is complete here
2053 int mboxid=oMailBox->get_object_id();
2054 m_delete(discover,mboxid); //mailbox is not part of result
2055 if(sizeof(discover)==0) return ({}); //no anns in messageboard
2056 foreach(sort(indices(discover)), tid)
2057 { //check if all needed headers are set
2058 object tmp=_Database->find_object(tid);
2059 Message msg=MessageboardMessage(tmp);
2060 mapping header=msg->header();
2061 if(zero_type(header["in-reply-to"]) && parent[tid]!=mboxid)
2063 object parentTmp=_Database->find_object(parent[tid]);
2064 mapping parentHeader = parentTmp->query_attribute(MAIL_MIMEHEADERS);
2066 if(mappingp(parentHeader)) msgId=parentHeader["message-id"];
2067 if(stringp(msgId) && msgId!="")
2068 msg->add_to_header((["in-reply-to":msgId,"references":msgId]));
2077 void add_message(Message msg)
2079 aoMessages+=({msg});
2080 mapping temp=msg->header();
2081 m_delete(temp,"in-reply-to");
2082 m_delete(temp,"references");
2083 temp["reply-to"]="<"+msg->get_object_id()+"@"+_Server->query_config("machine")+"."+_Server->query_config("domain")+">";
2086 LOG_MSG("added #"+msg->get_object_id()+" to box #"+oMailBox->get_object_id());
2092 * creates a suitable MailBox-Object for the given target
2093 * support for more classes may be added in this function
2095 * @param object target - the sTeam-Object to create a mailbox from
2096 * @return an object (of a subclass) of Messaging.BaseMailBox
2098 BaseMailBox get_mailbox(object target)
2100 if(target->get_object_class() & CLASS_USER)
2101 return UserMailBox(target);
2102 if(target->get_object_class() & CLASS_CONTAINER)
2103 return ContainerMailBox(target);
2104 if(target->get_object_class() & CLASS_MESSAGEBOARD)
2105 return MessageboardMailBox(target);
2107 LOG_MSG("get_mailbox() called for unknown class: "+
2108 target->get_object_class());
2113 * stores a mail to a non-mailbox object
2114 * searches the proper target object according to the message-id in in-reply-to
2115 * or references headers
2116 * document of message is annotated to target object
2118 * @param Message msg - the message to store
2119 * @param object target - the sTeam-object to annotate to
2120 * @return 1 if successful, 0 otherwise
2122 int add_message_to_object(Message msg, object target)
2124 mapping header = msg->header();
2125 LOG_MSG("add_message_to_object(%s, %d)\n", header->subject, target->get_object_id());
2127 mapping missing_ids = target->query_attribute("OBJ_ANNO_MISSING_IDS");
2128 if(!mappingp(missing_ids))
2131 // seems like replies to this mail got here first, reattach them
2132 if(header["message-id"] && missing_ids[header["message-id"]])
2134 foreach(missing_ids[header["message-id"]];; int oid)
2136 object message = _Database->find_object(oid);
2137 message->get_annotating()->remove_annotation(message);
2139 m_delete(missing_ids, header["message-id"]);
2142 // now lets find our real parent
2143 if(header["in-reply-to"] || header["references"])
2145 mapping message_ids = target->query_attribute("OBJ_ANNO_MESSAGE_IDS");
2151 // now find the correct parent
2152 // best parent is a message with the id from in-reply-to
2153 // if we don't find that, store that id for reattaching
2155 if(header["in-reply-to"])
2156 ids += Array.flatten(array_sscanf(header["in-reply-to"], "%{<%[^>]>%*[^<]%}"));
2157 if(header["references"])
2158 ids += reverse(Array.flatten(array_sscanf(header["references"], "%{<%[^>]>%*[^<]%}")));
2160 foreach(ids; int count; string id)
2165 new_target = _Database->find_object(message_ids[id]);
2168 // the first reference is the best, save it, so the message
2169 // can be reattached, should the reference arrive later
2172 if(!missing_ids[id])
2173 missing_ids[id] = ({ msg->get_object_id() });
2175 missing_ids[id] += ({ msg->get_object_id() });
2178 target->set_attribute("OBJ_ANNO_MISSING_IDS", missing_ids);
2181 target = new_target;
2184 // we are back to our regular programming
2187 if ( (target->get_object_class() & CLASS_USER) ) {
2190 //attachments are already annotated to msg-object
2196 int id=target->get_object_id();
2197 string reply=header["in-reply-to"];
2199 //needed for creation of missing headers
2200 string sServer = _Server->query_config("machine");
2201 string sDomain = _Server->query_config("domain");
2202 string sFQDN = sServer+"."+sDomain;
2204 if(!stringp(reply) || reply=="")
2206 if(mappingp(target->query_attribute(MAIL_MIMEHEADERS)))
2208 reply=target->query_attribute(MAIL_MIMEHEADERS)["message-id"];
2209 if(!stringp(reply) || reply=="") reply="<"+sprintf("%010d",id)+"@"+sFQDN+">";
2211 else reply="<"+id+"@"+sFQDN+">";
2213 string references=header["references"];
2214 if(!stringp(references) || references=="") references=reply;
2216 string message_id=header["message-id"];
2217 if(!stringp(message_id) || !sscanf(message_id, "<%*s>"))
2218 message_id = sprintf("<%010d@%s>", msg->get_object_id(), sFQDN);
2220 msg->add_to_header((["in-reply-to":reply,
2221 "references":references,
2222 "message-id":message_id ]));
2224 // create a table of all annotations so they can be referenced by the
2225 // message-id to allow propper threading of incoming mails.
2226 mapping annotation_ids = target->query_attribute("OBJ_ANNO_MESSAGE_IDS");
2227 if(!mappingp(annotation_ids))
2228 annotation_ids = ([]);
2229 annotation_ids[message_id] = msg->get_object_id();
2230 target->set_attribute("OBJ_ANNO_MESSAGE_IDS", annotation_ids);
2237 * create an object of class Message
2239 * @input string raw - a string representing a MIME-message (rfc 2045-2049)
2240 * @return Message-object
2242 Message MIME2Message(string raw)
2244 MIME.Message msg = MIME.Message(raw);
2246 mapping msg_decoded_headers = ([]);
2247 foreach(msg->headers; string header; string value)
2252 value = replace(value, "\0", "\n");
2253 decoded = MIME.decode_words_text_remapped(value);
2255 msg_decoded_headers[header] = string_to_utf8(decoded||value);
2258 if ( !stringp(msg_decoded_headers->subject)
2259 || sizeof(msg_decoded_headers->subject) < 1 )
2260 msg_decoded_headers->subject = " no subject ";
2262 string mimetype=msg->type+"/"+msg->subtype;
2263 object factory = _Server->get_factory(CLASS_DOCUMENT);
2264 object mail = factory->execute(
2265 ([ "name": replace(msg_decoded_headers["subject"], "/", "_"),
2266 "mimetype": mimetype,
2267 "attributes": ([ MAIL_MIMEHEADERS: msg_decoded_headers,
2268 OBJ_DESC: msg_decoded_headers->subject ]),
2271 array (object) parts = msg->body_parts;
2272 if ( arrayp(parts) ) //multipart message, add parts as separate documents
2274 LOG_MSG("found "+sizeof(parts)+" parts, now processing...");
2275 foreach(parts, MIME.Message obj)
2277 mapping obj_decoded_headers = ([]);
2278 foreach(obj->headers; string header; string value)
2283 decoded = MIME.decode_words_text_remapped(value);
2285 obj_decoded_headers[header] = string_to_utf8(decoded||value);
2288 LOG_MSG("processing part:%O",obj);
2289 LOG_MSG("header is:%O",obj_decoded_headers);
2290 string description = obj_decoded_headers["subject"]
2291 || obj->get_filename()
2292 || msg_decoded_headers["subject"];
2293 string name = replace(description, "/", "_");
2294 mimetype=obj->type+"/"+obj->subtype;
2295 object annotation = factory->execute
2297 "mimetype" : mimetype]));
2298 if ( objectp(annotation) )
2299 annotation->set_attribute( OBJ_DESC, description );
2300 LOG_MSG("created sTeam-Annotation");
2301 if(obj->getdata()!=0)
2302 annotation->set_content(obj->getdata());
2304 annotation->set_content("dummy value, no real content right now");
2305 annotation->set_attribute(MAIL_MIMEHEADERS,obj_decoded_headers);
2306 mail->add_annotation(annotation);
2309 mixed maildata = msg->getdata();
2310 if ( !stringp(maildata) ||
2311 ( sizeof(maildata)<1 && arrayp(msg->body_parts) &&
2312 sizeof(msg->body_parts)>0 ) )
2313 mail->set_content("This document was received as a multipart e-mail,"
2314 "\nthe content(s) can be found in the annotations/attachments!");
2316 mail->set_content(msg->getdata());
2318 LOG_MSG("Finished conversion raw text -> message");
2319 return Message(mail);
2322 Message SimpleMessage(array target, string subject, string message)
2324 object factory = _Server->get_factory(CLASS_DOCUMENT);
2325 object mail = factory->execute
2326 ((["name": subject, "mimetype": "text/plain"]));
2327 mail->set_content(message+"\r\n");
2328 if ( objectp(mail) )
2329 mail->set_attribute( OBJ_DESC, subject );
2331 Message msg=Message(mail);
2332 mapping header=msg->header();
2333 string to = target*", ";
2339 //support for modified UTF7
2340 //see RFC3501, section 5.1.3 & RFC2152
2342 string mbase64tab="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890+,";
2344 string decode_mbase64(string encoded)
2346 //decode a modified-base64-encoded string
2347 switch(sizeof(encoded)%4)
2361 while(sizeof(encoded)>0)
2363 string part=encoded[0..3];
2364 if(sizeof(encoded)>3)
2365 encoded=encoded[4..sizeof(encoded)-1];
2367 int p1=search(mbase64tab,part[0..0]);
2368 int p2=search(mbase64tab,part[1..1]); if(p2==-1)p2=0;
2369 int p3=search(mbase64tab,part[2..2]); if(p3==-1)p3=0;
2370 int p4=search(mbase64tab,part[3..3]); if(p4==-1)p4=0;
2371 int b1= (p1<<2) | (p2&0b110000);
2372 int b2= (p2<<4)&255 | (p3&0b111100)>>2;
2373 int b3= (p3<<6)&255 | p4;
2374 result+=sprintf("%c%c%c",b1,b2,b3);
2376 if(sizeof(result)%2==1) result=result[0..sizeof(result)-2];
2377 result=unicode_to_string(result);
2381 string encode_mbase64(string input)
2383 //encode a string via modified-base64-encoding
2384 input=string_to_unicode(input);
2385 switch(sizeof(input)%3)
2397 while(sizeof(input)>0)
2399 string part=input[0..2];
2401 input=input[3..sizeof(input)-1];
2407 int b2=(p1&0b11)|(p2>>4);
2408 int b3=(p2&0b1111)<<2|(p3>>6);
2410 result+=mbase64tab[b1..b1];
2413 result+=mbase64tab[b2..b2];
2416 result+=mbase64tab[b3..b3];
2418 result+=mbase64tab[b4..b4];
2425 string decode_mutf7(string encoded)
2429 while(i<sizeof(encoded))
2432 i=search(encoded,"&",i+1);
2435 result+=encoded[start+1..sizeof(encoded)-1];
2440 result+=encoded[start+1..i-1];
2441 if(encoded[i+1]=='-')
2443 result+="&"; //sequence "&-" found
2448 int j=search(encoded,"-",i+1);
2451 result+=decode_mbase64(encoded[i+1..j-1]);
2454 else return 0; //syntax error in mutf7-string
2461 string encode_mutf7(string input)
2463 string result="",tombase64="";
2465 for(int i=0;i<sizeof(input);i++)
2473 result+="&"+encode_mbase64(tombase64)+"-";
2479 if(val>=0x20 && val<=0x7e)
2484 result+="&"+encode_mbase64(tombase64)+"-";
2487 result+=input[i..i];
2491 tombase64+=input[i..i];
2494 result+="&"+encode_mbase64(tombase64)+"-";
2498 //the following functions are only needed to avoid security-errors while creating
2499 //sTeam-objects in "MIME2MEssage":
2500 // get_object_id, this & get_object_class
2505 return get_object()->get_object_id();
2509 return get_object();
2514 object user = geteuid();
2515 if ( !objectp(user) )
2518 if ( objectp(user) )
2520 return get_module("forward");
2523 int get_object_class()
2525 return get_object()->get_object_class();
2528 string fix_html ( string text )
2530 string new_text = text;
2531 string tmp = lower_case( text );
2532 if ( search( tmp, "<body" ) < 0 ) new_text = "<BODY>\n" + new_text;
2533 if ( search( tmp, "</body>" ) < 0 ) new_text = new_text + "\n</BODY>";
2534 if ( search( tmp, "<html" ) < 0 ) new_text = "<HTML>\n" + new_text;
2535 if ( search( tmp, "</html>" ) < 0 ) new_text = new_text + "\n</HTML>";