Messaging._pmod
Go to the documentation of this file.
1 /* Copyright (C) 2004 Christian Schmidt
2  *
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.
7  *
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.
12  *
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
16  *
17  */
18  inherit Flag;
19  inherit Message;
20  inherit Message;
21  inherit Message;
22  inherit Events.Listener;
23  inherit BaseMailBox;
24  inherit BaseMailBox;
25  inherit BaseMailBox;
26  annotation->set_acquire(mail); //inherit access-rights of mail
27 #include <macros.h>
28 #include <attributes.h>
29 #include <database.h>
30 #include <classes.h>
31 #include <events.h>
32 #include <access.h>
33 #include <mail.h>
34 #include <config.h>
35 
36 
37 
38 /**
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
42  */
43 
44 
45 import Events;
46 
47 //#define DEBUG_MSG
48 
49 #ifdef DEBUG_MSG
50 #define LOG_MSG(s, args...) werror("messaging: "+s+"\n", args)
51 #else
52 #define LOG_MSG(s, args...)
53 #endif
54 
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" );
58 }
59 
60 
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 + "\" ";
71 }
72 
73 
74 protected:
75  int is_ascii( string text ) {
76  if ( !stringp(text) )
77  return 0;
78  for ( int i = 0; i < strlen(text); i++ )
79  if ( text[i] > 128 )
80  return 0;
81  return 1;
82 }
83 
84 public:
85 
86 
87 /**
88  * implements imap-mailflags
89  * used by message-class below
90  * names of flags can be found in mail.h
91  *
92  */
93 class Flag
94 {
95 public:
96  private int iFlag; //stores the current value of the flag
97 
98  /**
99  * create a new flag-object
100  *
101  * @param void|int flag - the initial value of this flag, defaults to 0
102  */
103  void create(void|int tflag)
104  {
105  if(tflag) iFlag=tflag;
106  else iFlag=0;
107  }
108 
109  /**
110  * get the current value of the flag
111  *
112  * @return the value of the flag
113  */
114  int get() { return iFlag; }
115 
116  /**
117  * set a new value for the flag
118  *
119  * @param int tflag - the new value
120  * @return
121  */
122  void set(int tflag) { iFlag = tflag; }
123 
124  /**
125  * add a specific flag to the current value
126  *
127  * @param int tflag - the flag to add
128  */
129  void add(int tflag) { iFlag = iFlag | tflag; }
130 
131  /**
132  * remove a specific flag from the current value
133  *
134  * @param int tflag - the flag to remove
135  */
136  void del(int tflag) { iFlag = iFlag & (~tflag); }
137 
138  /**
139  * check if a flag is set
140  *
141  * @param int tflag - the flag to query
142  * @return 1 if flag is set, 0 if not
143  */
144  int has(int tflag) { return iFlag & tflag; }
145 }
146 
147 /**
148  * needed for "virtual reply mails"
149  * no changes possible to state of flag
150  */
151 class NullFlag
152 {
153 public:
154  private int iFlag; //stores the current value of the flag
155 
156  void create(void|int tflag)
157  {
158  iFlag=SEEN;
159  }
160 
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; }
166 
167 }
168 
169 /**
170  * represents a single message
171  * documents and possibly other sTeam-objects may be accessed
172  * as mails/messages this way
173  */
174 class Message
175 {
176 public:
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;
182  Flag fFlag;
183  int isBuild = 0;
184  object oRcptUser;
185 
186  string sServer = _Server->query_config("machine");
187  string sDomain = _Server->query_config("domain");
188  string sFQDN = sServer+"."+sDomain;
189 
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" ||
196  func == "this" ||
197  func == "is_mail" )
198  return f;
199  if ( !isBuild ) {
200  // all calls should be fine immediately
201  isBuild = 1;
202  build_message();
203  }
204  return f;
205  }
206 
207  int is_mail() { return 1; }
208 
209  void build_message() {
210  if(zero_type(oMessage->query_attribute(MAIL_MIMEHEADERS)))
211  {
212  add_header(oRcptUser); //header is missing, add a new one
213  if ( stringp(sSender) )
214  mHeader->from = sSender;
215 
216  LOG_MSG("adding header within creation");
217  }
218  else
219  {
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;
224  }
225 
226  if(!iIsAttachment)
227  fFlag = Flag(oMessage->query_attribute(MAIL_IMAPFLAGS));
228  else
229  fFlag = 0; //Attachments have no flags
230 
231  int type=oMessage->get_object_class();
232  type-=CLASS_OBJECT; //all sTeam-Objects have type CLASS_OBJECT...
233  switch(type) {
234  case CLASS_DOCUMENT:
235  case CLASS_DOCUMENT|CLASS_DOCHTML:
236  init_document( oRcptUser ); //see init_document() below
237  break;
238  case CLASS_IMAGE:
239  case CLASS_DOCUMENT|CLASS_IMAGE:
240  sBody="this message is an image, look at the attachment(s)";
241  sSubject=mHeader["subject"];
242  sType="MULTIPART";
243  sSubtype="MIXED";
244  aoAttachment = ({oMessage});
245  break;
246  default:
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");
250  }
251  isBuild = 1;
252  }
253 
254  /**
255  * create a new message-object from a sTeam-object
256  *
257  * @param object target - sTeam-object to encapsulate
258  * @param int|void isAttachment - set to 1 if this message is "only" an attachment
259  */
260  void create(object target, int|void isAttachment, void|object rcptUser, void|string from)
261  {
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) });
282  }
283  isBuild = 1;
284  return;
285  }
286  isBuild = 0; // Message has not been build yet
287  if(isAttachment)
288  iIsAttachment=1;
289  else
290  iIsAttachment = 0;
291  if ( stringp(from) )
292  sSender = from;
293 
294  oMessage = target;
295  oRcptUser = rcptUser;
296  aoAttachment = ({});
297  }
298 
299  protected string get_body_encoded()
300  {
301  string result="";
302 
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
307  {
308  switch(lower_case(encoding))
309  {
310  case "base64":
311  result=MIME.encode_base64(content)+"\r\n";
312  break;
313  case "quoted-printable":
314  result=MIME.encode_qp(content)+"\r\n";
315  break;
316  //"7bit", "8bit" and "binary" mean "no encoding at all", see rfc2045
317  case "7bit":
318  case "8bit":
319  case "binary":
320  default:
321  result=content+"\r\n";
322  break;
323  }
324  }
325  else result=content+"\r\n";
326 
327  return result;
328  }
329 
330  /**
331  * called if this object encapsulates a sTeam-Document
332  * there should be no need to call this manually
333  *
334  */
335 private:
336  void init_document( void|object rcptUser )
337  {
338  int missing=0;
339 
340  mixed err = catch {
341  sBody=get_body_encoded();
342  };
343  if ( err != 0 ) {
344  FATAL( "Failed to read body of message: %O, %O", err[0], err[1] );
345  sBody = "Failed to read body - check errors / contact admin";
346  }
347 
348  sSubject=mHeader["subject"]||"";
349  if(!stringp(sSender))
350  sSender=mHeader["from"]||"";
351 
352  sType="MULTIPART"; //default values
353  sSubtype="MIXED";
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
359  {
360  string tmp=mHeader["content-type"];
361  if(tmp!=0) //lookup MIME-boundary-string (see rfc 2045-2049)
362  {
363  int start=search(lower_case(tmp),"boundary=");
364  if(start!=-1)
365  {
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
369  }
370  else missing=1;
371  }
372  else missing=1;
373 
374  if(missing) //the email-headers had no boundary, generate one
375  {
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);
381  }
382  }
383  else //message has no attachments
384  {
385  sBoundary=""; //non-MIME Message has no boundary-string
386  string tmp=mHeader["content-type"];
387  if(zero_type(tmp))
388  tmp=oMessage->query_attribute(DOC_MIME_TYPE);
389  int i=sscanf(tmp,"%s/%s;", sType, sSubtype);
390  if(!i)
391  {
392  sType="TEXT";
393  sSubtype="PLAIN";
394  }
395  }
396  if (mHeader["content-type"]) {
397  string ct = mHeader["content-type"];
398  string charset;
399  if (sscanf(ct, "%*s; charset=%s;%*s", charset)==0)
400  sscanf(ct, "%*s; charset=%s", charset);
401  if (charset)
402  oMessage->set_attribute(DOC_ENCODING, lower_case(charset));
403  }
404 
405  iSize=sizeof(complete_text());
406  }
407 
408 public:
409 
410  string get_subject() {
411  return sSubject;
412  }
413 
414  void set_subject( string subject ) {
415  mHeader["subject"] = (is_ascii(subject) ? subject :
416  MIME.encode_word( ({ subject, "utf-8" }), "q" ));
417  }
418 
419  string get_boundary() {
420  return sBoundary;
421  }
422 
423  object get_rcptUser() {
424  return oRcptUser;
425  }
426 
427  /**
428  * converts a mapping [header:value] to one string
429  * there should be no need to call this manually
430  *
431  * @param mapping header - the mapping containing the header of the message
432  * @return one string with the contents of the header (incl. linebreaks)
433  */
434 protected:
435  string header2text(mapping header)
436  {
437  string dummy="";
438  foreach(indices(header),string key) {
439  string ktext = key;
440  switch(ktext) {
441  case "content-type":
442  ktext = "Content-Type";
443  break;
444  case "message-id":
445  ktext = "Message-ID";
446  break;
447  case "mime-version":
448  ktext = "Mime-Version";
449  break;
450  case "content-transfer-encoding":
451  ktext = "Content-Transfer-Encoding";
452  break;
453  default:
454  ktext[0] = upper_case(ktext[0..0])[0];
455  break;
456  }
457  dummy+=ktext+": "+header[key]+"\r\n";
458  }
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";
462  dummy+="\r\n";
463  return dummy;
464  }
465 
466 public:
467  object get_msg_object() { return oMessage; }
468 
469  /**
470  * write non permanent data to the sTeam-object of this message
471  * must be called after flag-changes for example
472  *
473  */
474  void update()
475  {
476  mixed err = catch
477  {
478  oMessage->set_attribute(MAIL_IMAPFLAGS,fFlag->get());
479  };
480  if(err) LOG_MSG("error on storing flags: %O",err);
481  }
482 
483  /**
484  * permanently adds a rfc2822-compliant header to the sTeam-object
485  * no need to call this manually
486  *
487  */
488 protected:
489  void add_header(void|object rcptUser)
490  {
491  LOG_MSG("Adding RFC2822 header to object-id: "+oMessage->get_object_id());
492 
493  mHeader=([]);
494  string tmp=oMessage->query_attribute(OBJ_NAME);
495  if ( !stringp(tmp) ) tmp = "";
496 
497  mHeader["subject"] = (is_ascii(tmp) ? tmp :
498  MIME.encode_word( ({ tmp, "utf-8" }), "q" ));
499 
500  mHeader["date"] = timelib.smtp_time( oMessage->query_attribute(OBJ_CREATION_TIME) );
501 
502  int id=oMessage->get_object_id();
503  tmp="<"+sprintf("%010d",id)+"@"+sFQDN+">";
504  mHeader["message-id"]=tmp;
505 
506  object test=oMessage->query_attribute(DOC_USER_MODIFIED);
507  if(!objectp(test)) test=oMessage->get_creator();
508  if (objectp(test)) {
509  tmp = get_quoted_name( test ) + "<" + test->get_identifier() + "@" +
510  sFQDN + ">";
511  mHeader["from"]=tmp;
512 
513  if ( !stringp(sSender) )
514  sSender = get_quoted_name( test ) + "<"+test->get_steam_email()+">";
515  }
516  else
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() + ">";
525  else
526  steam_error("oMessage_ID: " + oMessage->get_object_id() + " Unable to determine \"To:\"-Header for E-Mail !\n");
527 
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";
534  }
535  else {
536  mHeader["content-type"]=tmp;
537  mHeader["content-transfer-encoding"] = "base64";
538  }
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));
543  if ( err ) {
544  FATAL("Failed to add mimeheader (setting attribute) for %O, %O, %O",
545  oMessage, err[0], err[1]);
546  }
547  }
548 
549 public:
550 
551  /**
552  * add additional informations to the header of this message
553  * @param mapping add - the header-lines and values to add
554  */
555  void add_to_header(mapping add)
556  {
557  foreach(add; string index;)
558  {
559  if(zero_type(mHeader[lower_case(index)])) //only add if not already existing
560  mHeader[lower_case(index)]=add[index];
561  }
562  oMessage->set_attribute(MAIL_MIMEHEADERS,mHeader);
563  }
564 
565  /**
566  * delete this message (that is: delete the sTeam-object)
567  * existing attachments are removed too
568  *
569  */
570  void delete()
571  {
572  foreach(aoAttachment, Message msg)
573  {
574  aoAttachment-=({msg});
575  msg->delete(); //remove all attachments
576  destruct(msg);
577  }
578  oMessage->delete();
579  if(objectp(fFlag)) destruct(fFlag);
580  }
581 
582  /**
583  * get the uid of this message
584  *
585  * @return int unique-identifier
586  */
587  int uid() { return iUid; }
588 
589  /**
590  * get the flag of this message
591  *
592  * @return Flag flag
593  */
594  Flag flag() { return fFlag; }
595 
596  /**
597  * get the subject of this message
598  *
599  * @return string subject
600  */
601  string subject() { return sSubject; }
602 
603  /**
604  * get the sender of this message
605  *
606  * @return string sender
607  */
608  string sender() { return sSender; }
609 
610  /**
611  * check if this message is an attachment
612  *
613  * @return int 1 if message is attachment, 0 if it is a "real" message
614  */
615  int is_attachment() { return iIsAttachment; }
616 
617  /**
618  * get all attachments of this message
619  *
620  * @return array(Message) an array of message-objects with attachments of this message
621  */
622  array(Message) attachments() { return aoAttachment; }
623 
624  /**
625  * get the complete header of this message
626  *
627  * @return mapping(string:string) mapping with all header-informations
628  */
629  mapping(string:string) header() { return mHeader; }
630 
631  /**
632  * get the body of this message
633  *
634  * @return string body
635  */
636  string body() { return sBody; }
637 
638  /**
639  * get the date of this message
640  *
641  * @return int date of creation
642  */
643  int internal_date()
644  {
645  return oMessage->query_attribute(OBJ_CREATION_TIME);
646  }
647 
648  /**
649  * get the size of this message
650  *
651  * @return int size in bytes
652  */
653  int size() { return iSize; }
654 
655  /**
656  * get the size of the body of this message
657  *
658  * @return int size of the body in bytes
659  */
660  int body_size() { return sizeof(sBody); }
661 
662  /**
663  * get the object-id of this message
664  *
665  * @return int object-id
666  */
667  int get_object_id() { return oMessage->get_object_id(); }
668 
669  /**
670  * check if this message has attachments
671  *
672  * @return int 0 if no attachments exists or the number of attachments
673  */
674  int has_attachments() { return sizeof(aoAttachment); }
675 
676  MIME.Message mime_message()
677  {
678  MIME.Message msg;
679  if(sizeof(aoAttachment))
680  msg = MIME.Message(sBody, mHeader, aoAttachment->mime_message());
681  else
682  msg = MIME.Message(sBody, mHeader);
683  msg->setboundary(sBoundary);
684  return msg;
685  }
686 
687  /**
688  * returns the text-version of this message (rfc2822-style)
689  *
690  * @return string rfc2822-text of the message
691  */
692  string complete_text(void|int attach)
693  {
694  string dummy="";
695  if ( attach )
696  mHeader["content-type"] += "; name=\"" + sSubject + "\"";
697 
698  dummy+=header2text(mHeader);
699 
700  if(sizeof(aoAttachment)>0)
701  {
702  dummy += "--"+sBoundary+"\r\ncontent-type: " +
703  oMessage->query_attribute(DOC_MIME_TYPE) + "\r\n\r\n";
704  dummy += sBody;
705  for(int i=0;i<sizeof(aoAttachment);i++)
706  {
707  dummy+="\r\n\r\n--"+sBoundary+"\r\n";
708  // insert all attachments
709  dummy+=aoAttachment[i]->complete_text(1);
710  }
711  dummy+="\r\n\r\n--"+sBoundary+"--\r\n\r\n";
712  }
713  else {
714  dummy += sBody;
715  }
716  return dummy;
717  }
718 
719  /**
720  * get the mimetype (first part) of this message
721  *
722  * @return string mimetype (first part)
723  */
724  string type() { return sType; }
725 
726  /**
727  * get the mimetype (second part) of this message
728  *
729  * @return string mimetype (second part)
730  */
731  string subtype() { return sSubtype; }
732 
733  /**
734  * duplicates a message, including all attachments
735  *
736  * @return Message duplicated message
737  */
738  Message duplicate()
739  {
740  Message msg = Message(this_object());
741 
742  return msg;
743  //return Message(copy);
744  }
745 
746  /**
747  * gives all access-rights on this message to a user
748  * has to be called by a user who has sufficient rights
749  * to do this
750  *
751  * @param object user the user-object to grant access to
752  * @return 1 if successful, 0 otherwise
753  */
754  int grant_access(object user)
755  {
756  mixed err = catch
757  {
758  oMessage->sanction_object(user,SANCTION_ALL);
759  foreach(aoAttachment, Message msg)
760  msg->grant_access(user);
761  };
762  if(err)
763  {
764  LOG_MSG("error on granting access to "+user->get_identifier()+" on #"+oMessage->get_object_id()+" :%O",err);
765  return 0;
766  }
767  else return 1;
768  }
769 }
770 
771 class ContainerMessage
772 {
773 public:
774 
775  void create(object target, int|void isAttachment)
776  {
777  aoAttachment=({}); //default: no attachments
778  oMessage = target;
779 
780  LOG_MSG("ContainerMessage->create("+target->get_object_id()+")");
781  if(isAttachment)
782  {
783  iIsAttachment=1;
784  LOG_MSG("creating as attachment");
785  }
786  else iIsAttachment = 0;
787 
788  if (zero_type(target->query_attribute(MAIL_MIMEHEADERS)))
789  add_header();
790  else {
791  if ( mappingp(mHeader) &&
792  mappingp(target->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL)) )
793  mHeader = target->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL) | mHeader;
794  }
795 
796  if(iIsAttachment==0)
797  {
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));
808 
809  sType="multipart";
810  sSubtype="mixed";
811 
812  array tmp = target->get_annotations();
813  for(int i=sizeof(tmp)-1;i>=0;i--)
814  aoAttachment+=({ContainerMessage(tmp[i],1)});
815 
816  }
817  else
818  {
819  mHeader=([]);
820  oMessage = target;
821  mapping temp=oMessage->query_attribute(MAIL_MIMEHEADERS);
822 
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"];
828  if(stringp(test))
829  {
830  mHeader["content-type"]=test;
831  sscanf(test,"%s/%s;",sType,sSubtype);
832  }
833  else //unknown content-type...
834  {
835  sType="test";
836  sSubtype="plain";
837  }
838  sBody=get_body_encoded();
839  fFlag = 0; //Attachments have no flags
840 
841  if(objectp(target->get_annotating())) //is this a "real" attachment or just the document itself?
842  {
843  array tmp = oMessage->get_annotations();
844  for(int i=sizeof(tmp)-1;i>=0;i--)
845  aoAttachment+=({ContainerMessage(tmp[i],1)});
846  }
847  }
848  init_document();
849  }
850 
851  /**
852  * called if this object encapsulates a sTeam-Document
853  * there should be no need to call this manually
854  *
855  */
856 private:
857  void init_document()
858  {
859  sSubject=mHeader["subject"];
860  if(!stringp(sSubject)) sSubject="";
861  sSender=mHeader["from"];
862  if(!stringp(sSender)) sSender="";
863 
864  iSize=sizeof(complete_text());
865  }
866 
867 public:
868 
869  object get_msg_object() { return oMessage; }
870 
871  {
872  if(objectp(oMessage)) return oMessage;
873  else
874  {
875  return geteuid();
876  }
877  }
878 
879  object get_object()
880  {
881  return geteuid();
882  }
883 
884  void update()
885  {
886  if(iIsAttachment) return;
887  mixed err = catch
888  { //flag is stored in first attachment as message-object is non-existant!
889  };
890  if(err) LOG_MSG("error on storing flags: %O",err);
891  }
892 
893  int get_object_id()
894  {
895  return oMessage->get_object_id();
896  }
897 
898  int internal_date()
899  {
900  if(iIsAttachment) return oMessage->query_attribute(OBJ_CREATION_TIME);
901  else return aoAttachment[0]->internal_date();
902  }
903 
904  void delete()
905  {
906  foreach(aoAttachment, Message msg)
907  {
908  aoAttachment-=({msg});
909  msg->delete(); //remove all attachments
910  destruct(msg);
911  }
912  if(iIsAttachment) oMessage->delete();
913  if(objectp(fFlag)) destruct(fFlag);
914  }
915 
916  int grant_access(object user)
917  {
918  mixed err = catch
919  {
920 // oMessage->sanction_object(user,SANCTION_ALL);
921  foreach(aoAttachment, Message msg)
922  msg->grant_access(user);
923  };
924  if(err)
925  {
926  return 0;
927  }
928  else return 1;
929  }
930 
931 }
932 
933 /**
934  * subclass of Message
935  * used for accessing sTeam-messageboards entries
936  */
937 class MessageboardMessage
938 {
939 public:
940 
941  void create(object target, int|void isAttachment)
942  {
943  LOG_MSG("MessageboardMessage->create("+target->get_object_id()+")");
944  iIsAttachment = 0;
945  aoAttachment = ({}); //messageboard-entries have no attachments...
946 
947  oMessage = target;
948  fFlag = Flag(oMessage->query_attribute(MAIL_IMAPFLAGS));
949 
950  if(zero_type(oMessage->query_attribute(MAIL_MIMEHEADERS)))
951  add_header(); //header is missing, add a new one
952  else {
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;
957  }
958 
959  int type=oMessage->get_object_class();
960  if(type & CLASS_DOCUMENT)
961  init_document(); //see init_document() below
962  else
963  {
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");
967  }
968  LOG_MSG("NessageboardMessage->create("+target->get_object_id()+") finished!");
969  }
970 
971 private:
972  void init_document()
973  {
974  sBody=oMessage->get_content()+"\r\n";
975 
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
983  }
984 
985  sSubject=mHeader["subject"];
986  sType="TEXT";
987  sSubtype="PLAIN";
988  iSize=sizeof(complete_text());
989  if(!stringp(mHeader["reply-to"]) || mHeader["reply-to"]=="")
990  {
991  mHeader["reply-to"]="<"+oMessage->get_object_id()+"@"+sFQDN+">";
992  oMessage->set_attribute(MAIL_MIMEHEADERS,mHeader);
993  }
994  }
995 
996 public:
997 
998 protected:
999  void add_header()
1000  {
1001  LOG_MSG("Adding RFC2822 header to object-id: "+oMessage->get_object_id());
1002 
1003  mHeader=([]);
1004  mHeader["subject"] = oMessage->query_attribute(OBJ_NAME) || "";
1005  mHeader["date"] = timelib.smtp_time(
1006  oMessage->query_attribute(OBJ_CREATION_TIME));
1007 
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
1013 
1014  object test=oMessage->query_attribute(DOC_USER_MODIFIED);
1015  if (objectp(test))
1016  mHeader["from"] = get_quoted_name( test ) + "<" + test->get_identifier() + "@" + sFQDN + ">";
1017 
1018  test=oMessage->get_annotating();
1019  if(objectp(test) && !(test->get_object_class() & CLASS_MESSAGEBOARD) )
1020  {
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"];
1028  else
1029  mHeader["references"]=parentheader["message-id"];
1030  }
1031  }
1032 
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);
1038  }
1039 
1040 public:
1041 }
1042 
1043 /**
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
1047  *
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
1050  */
1051 class ReplyMessage
1052 {
1053 public:
1054 
1055  private int iTime;
1056  private object oMailBox;
1057 
1058  /**
1059  * create new ReplyMessage
1060  * @param object target - the mailbox this message is created in
1061  */
1062  void create(object target, int|void isAttachment)
1063  {
1064  oMailBox = target;
1065 
1066  iIsAttachment = 0; //reply-mails are no attachments...
1067  aoAttachment = ({}); //...and have no attachments
1068 
1069  fFlag = NullFlag(); //can't store any flags on this message
1070 
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);
1073 
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+">",
1080  "x-priority":"1",
1081  "x-msmail-priority":"High"]);
1082 
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"];
1087 
1088  //message-object does not exist!
1089  oMessage = 0;
1090 
1091  iSize=sizeof(complete_text());
1092 
1093  sType = "text"; //obvious...
1094  sSubtype = "plain";
1095  }
1096 
1097  //there is no sTeam-object for this message...
1098 
1099  //...so flags don't change...
1100  void update() {}
1101 
1102  //...and header's won't do so, too
1103 protected:
1104  void add_header() {}
1105 
1106 public:
1107  void add_to_header(mapping add) {}
1108 
1109  //just delete the flag-object
1110  void delete()
1111  {
1112  destruct(fFlag);
1113  }
1114 
1115  int internal_date()
1116  {
1117  return iTime;
1118  }
1119 
1120  int get_object_id() { return iUid; }
1121 
1122  //senseless, but if someone calls it... (may happen when copying mails)
1123  Message duplicate()
1124  {
1125  return ReplyMessage(oMailBox);
1126  }
1127 
1128  int grant_access(object user)
1129  {
1130  return 1;
1131  }
1132 
1133 }
1134 
1135 /**
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)
1140  */
1141 class MessagingListener {
1142 public:
1143 
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());
1149  }
1150 
1151  void notify(int event, mixed args) {
1152  if ( functionp(fCallback) )
1153  fCallback(event, @args);
1154  }
1155  mapping save() { return 0; }
1156 
1157  string describe() {
1158  return "MessagingListener()";
1159  }
1160 }
1161 
1162 /**
1163  * BaseMailBox is the root-class of all mailbox-types
1164  * it's pretty useless alone...
1165  * think of it as an "interface"
1166  */
1167 class BaseMailBox
1168 {
1169 public:
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;
1176  object oTarget;
1177 
1178  /**
1179  * create a new BaseMailBox-object
1180  *
1181  * @param object target - the sTeam-object to build MailBox around
1182  */
1183  void create(object target)
1184  {
1185  oTarget = target;
1186  oidMessageCache = ([ ]);
1187  //add code in sub-classes
1188  }
1189 
1190  /**
1191  * callback function for events when inventory changes
1192  */
1193 protected:
1194  void notify_me(int event, mixed ... args)
1195  {
1196  LOG_MSG("Event "+event+"%O",args);
1197  rebuild_box();
1198  }
1199 
1200 public:
1201 
1202  object get_object() {
1203  return oMailBox;
1204  }
1205 
1206  /**
1207  * needed for sTeam-security
1208  *
1209  * @return the sTeam-object for this MailBox
1210  */
1211  {
1212  return oMailBox;
1213  }
1214 
1215  /**
1216  * get the messages in this mailbox
1217  *
1218  * @return array of message-objects
1219  */
1220  array(Message) messages()
1221  {
1222  return aoMessages;
1223  }
1224 
1225  /**
1226  * get the number of messages in this mailbox
1227  *
1228  * @return int containing the number of messages
1229  */
1230  int get_num_messages()
1231  {
1232  return sizeof(aoMessages);
1233  }
1234 
1235  /**
1236  * get a specific message
1237  *
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
1240  */
1241  Message get_message(int msn)
1242  {
1243  if(msn<=sizeof(aoMessages))
1244  return aoMessages[msn-1]; //MSNs are counted from 1, not from 0
1245  else
1246  {
1247  FATAL("get_message() called for illegal msn: "+msn+
1248  " (max is "+sizeof(aoMessages)+")");
1249  }
1250  }
1251 
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];
1258  }
1259  return oidMessageCache[oid];
1260  }
1261 
1262  /**
1263  * delete all messages which have the "deleted"-flag set
1264  * (like "expunge" in IMAP)
1265  *
1266  * @param
1267  * @return
1268  */
1269  void delete_mails()
1270  {
1271  LOG_MSG("delete_mails() called for "+oMailBox->get_object_id());
1272  for(int i=sizeof(aoMessages)-1;i>=0;i--)
1273  {
1274  LOG_MSG("flags for #"+i+": "+aoMessages[i]->flag()->get());
1275  if(aoMessages[i]->flag()->has(DELETED))
1276  {
1277  LOG_MSG("deleting #"+i);
1278  Message tmp = aoMessages[i];
1279  aoMessages -= ({tmp});
1280  tmp->delete();
1281  destruct(tmp);
1282  }
1283  }
1284  }
1285 
1286  /**
1287  * delete this mailbox and all messages inside
1288  *
1289  */
1290  void delete()
1291  {
1292  for(int i=0;i<sizeof(aoMessages);i++) //remove all messages
1293  aoMessages[i]->delete();
1294  oMailBox->delete(); //remove mailbox
1295  }
1296 
1297  /**
1298  * convert unique-ids to message sequence numbers
1299  *
1300  * @param array - the uids to convert
1301  * @return array of matching msns
1302  */
1303  array uid_to_num(int|array uids)
1304  {
1305  array res = ({});
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]});
1311  return res;
1312  }
1313 
1314  int map_object(object msg, int rbegin, int rend)
1315  {
1316  if ( objectp(msg) ) {
1317  int id = msg->get_object_id();
1318  if ( id >= rbegin && id <= rend )
1319  return id;
1320  }
1321  return 0; // left out
1322  }
1323 
1324  array filter_uids(array uids)
1325  {
1326  array res = ({});
1327  foreach(uids, mixed range) {
1328  if ( arrayp(range) ) {
1329  res += (map(aoMessages, map_object, range[0], range[1]) - ({ 0 }));
1330  }
1331  else {
1332  Message msg = get_message_by_oid(range);
1333  if ( objectp(msg) )
1334  res += ({ range });
1335  }
1336  }
1337  return res;
1338  }
1339 
1340  /**
1341  * get a mapping of unique ids to message sequence numbers
1342  *
1343  * @return mapping(int:int) of all existing uids in this box to msns
1344  */
1345  mapping(int:int) get_uid2msn_mapping()
1346  {
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
1350  return temp;
1351  }
1352 
1353  /**
1354  * get the total size of this box (that is: size of all messages)
1355  *
1356  * @return int containing the size in bytes
1357  */
1358  int size()
1359  {
1360  int t=0;
1361  foreach(aoMessages, Message msg)
1362  t+=msg->size();
1363  return t;
1364  }
1365 
1366  /**
1367  * get the object id of this box (-> the sTeam-object of this box)
1368  *
1369  * @return int object-id
1370  */
1371  int get_object_id()
1372  {
1373  return oMailBox->get_object_id();
1374  }
1375 
1376  /**
1377  * get the identifying string of this box
1378  *
1379  * @return string containing the identifier
1380  */
1381  string get_identifier()
1382  {
1383  return oMailBox->get_identifier();
1384  }
1385 
1386  /**
1387  * does this mailbox have subfolders?
1388  *
1389  * @return int number of subfolders, 0 if no exist
1390  */
1391  int has_subfolders() { return 0; }
1392 
1393  /**
1394  * get a subfolder of this box
1395  *
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
1398  */
1399  BaseMailBox get_subfolder(string subfolder) { return 0; }
1400 
1401  /**
1402  * list all subfolders of this mailbox
1403  *
1404  * @param int recurse - how "deep" should be searched for folders, -1 means unlimited
1405  * @return array of mailbox-names, hierarchies seperated by "/"
1406  */
1407  array list_subfolders(int recurse) { return({}); }
1408 
1409  /**
1410  * get the type of sTeam-objects which are allowed as messages in this box
1411  *
1412  * @return int with class-type (see classes.h for values)
1413  */
1414  int allowed_types() { return iAllowedTypes; }
1415 
1416  /**
1417  * get the event that is called when a message is removed from this box
1418  *
1419  * @return int value of event (see events.h)
1420  */
1421  array leave_event() { return aiLeaveEvent; }
1422 
1423  /**
1424  * get the event that is called when a message is added to this box
1425  *
1426  * @return int value of event (see events.h)
1427  */
1428  array enter_event() { return aiEnterEvent; }
1429 
1430  /**
1431  * add a message to this box
1432  *
1433  * @param Message msg - message to add
1434  */
1435  void add_message(Message msg) {};
1436 
1437  /**
1438  * rebuild the inventory of this mailbox
1439  * called via event if contents of the encapsulated sTeam-object change
1440  */
1441  void rebuild_box() {};
1442 }
1443 
1444 /**
1445  * UserMailBox manages access to the messages of a user-object
1446  * these are stored as annotations to the user
1447  *
1448  */
1449 class UserMailBox
1450 {
1451 public:
1452 
1453  void create(object target)
1454  {
1455  ::create( target );
1456  iAllowedTypes = CLASS_DOCUMENT; //users store their mails as documents
1457  iFolderTypes = CLASS_CONTAINER;
1458  aiEnterEvent = ({EVENT_ANNOTATE});
1459  aiLeaveEvent = ({EVENT_REMOVE_ANNOTATION});
1460  aoMessages=({});
1461  oMailBox=target;
1462 
1463  LOG_MSG("UserMailBox->create() called:");
1464  LOG_MSG("target id: "+target->get_object_id());
1465  LOG_MSG("target name: "+target->get_identifier());
1466 
1467  array inv=target->get_annotations_by_class(iAllowedTypes);
1468  LOG_MSG("box size is: "+sizeof(inv));
1469 
1470  for(int i=sizeof(inv)-1;i>=0;i--) //reverse order of inv
1471  {
1472  Message msg;
1473  mixed err = catch {
1474  msg = Message(inv[i], 0, oTarget);
1475  };
1476  if ( err != 0 ) {
1477  FATAL("Failed to create Messaging.Message for %O\n", inv[i]);
1478  FATAL("%O: %O", err[0], err[1]);
1479  }
1480  else
1481  aoMessages+=({msg});
1482  }
1483  alEnter=({MessagingListener(aiEnterEvent[0], oMailBox, notify_me)});
1484  alLeave=({MessagingListener(aiLeaveEvent[0], oMailBox, notify_me)});
1485 /*
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)]");
1491 */
1492  }
1493 
1494 protected:
1495  void rebuild_box()
1496  {
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());
1501 
1502  array new_ids=({});
1503  int i;
1504  for(i=0;i<sizeof(tmp);i++) new_ids+=({tmp[i]->get_object_id()});
1505 // LOG_MSG("new ids:%O",new_ids);
1506  array old_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
1510  {
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)
1515  {
1516  LOG_MSG("removing lost #"+aoMessages[i]->get_object_id());
1517  aoMessages-=({aoMessages[i]});
1518  }
1519  }
1520  else //messages were added
1521  {
1522  array diff=new_ids-old_ids;
1523  for(i=0;i<sizeof(diff);i++)
1524  {
1525  LOG_MSG("adding new #"+diff[i]);
1526  Message msg;
1527  object diffobj = _Database->find_object(diff[i]);
1528  if ( !objectp(diffobj) )
1529  continue;
1530  mixed err = catch {
1531  msg = Message(diffobj, 0, oTarget);
1532  };
1533  if ( err != 0 ) {
1534  FATAL("Failed to create Messaging.Message (rebuilding box\
1535 ) for %O\n", diffobj);
1536  FATAL("%O: %O", err[0], err[1]);
1537  }
1538  else
1539  aoMessages+=({msg});
1540  }
1541 
1542  }
1543  }
1544 
1545 public:
1546 
1547  int has_subfolders()
1548  {
1549  array folders = oMailBox->get_annotations_by_class(iFolderTypes);
1550  return(sizeof(folders));
1551  }
1552 
1553  /**
1554  * search for a specific folder inside a given object
1555  * no need to call this manually
1556  *
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
1560  */
1561 private:
1562  private object search_for_folder(object where, string what)
1563  {
1564  array folders = where->get_annotations_by_class(iFolderTypes);
1565  if(sizeof(folders)>0)
1566  {
1567  int i=0;
1568  while(i<sizeof(folders))
1569  {
1570  if(folders[i]->get_identifier()==what)
1571  return folders[i];
1572  i++;
1573  }
1574  LOG_MSG("subfolder not found: "+what);
1575  }
1576  return 0;
1577  }
1578 
1579 public:
1580 
1581  BaseMailBox get_subfolder(string subfolder)
1582  {
1583  int fail=0;
1584  array parts=subfolder/"/"; //it is possible to fetch a "deep" folder with one call
1585  int i=0;
1586  object current = oMailBox;
1587  while(i<sizeof(parts) && !fail) //search for folders "deeper" into this box
1588  {
1589  current = search_for_folder(current, parts[i]);
1590  if(objectp(current)) i++;
1591  else fail=1;
1592  }
1593  if(fail) return 0; //couldn't find subfolder
1594  return UserMailBox(current);
1595  }
1596 
1597  int create_subfolder(string subfolder)
1598  {
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);
1604  return 1;
1605  }
1606 
1607  array list_subfolders(int recurse)
1608  {
1609  array res = ({});
1610  if(recurse==0) return res; //nothing to do...
1611  if(recurse==1) //easy, just get anns of this box
1612  {
1613  array folders = oMailBox->get_annotations_by_class(iFolderTypes);
1614  if(sizeof(folders)>0)
1615  {
1616  for(int i=0;i<sizeof(folders);i++)
1617  res+=({folders[i]->get_identifier() });
1618  return res;
1619  }
1620  else return ({}); //no subfolders in this mailbox
1621  }
1622  else //perform BFS on this mailbox
1623  {
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
1629  int iter=0;
1630  while(sizeof(queue)>0 && discover[queue[0]]!=recurse)
1631  {
1632  tid=queue[0];
1633  array inv=_Database->find_object(tid)->get_annotations_by_class(iFolderTypes);
1634  foreach(inv, object v)
1635  {
1636  int id=v->get_object_id();
1637  if(sizeof(all&({id}))==0)
1638  {
1639  all+=({id}); queue+=({id});
1640  discover+=([id:discover[tid]+1]);
1641  parent[id]=tid; //this vertex has been discovered from "tid"
1642  }
1643  }
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
1651  string path="";
1652  while(tid!=mboxid)
1653  {
1654  path="/"+_Database->find_object(tid)->get_identifier()+path;
1655  tid=parent[tid];
1656  }
1657  path=path[1..sizeof(path)-1]; //remove first "/";
1658  res+=({path});
1659  }
1660  return sort(res);
1661  }
1662  }
1663 
1664  void add_message(Message msg)
1665  {
1666  aoMessages+=({msg});
1667  if(objectp(tmp))
1668 
1669  LOG_MSG("added #"+msg->get_object_id()+" to user-box #"+oMailBox->get_object_id());
1670  }
1671 }
1672 
1673 /**
1674  * this class wraps a sTeam-Object of type "container" (but NOT "user")
1675  * for user-objects use "UserMailBox" instead
1676  */
1677 class ContainerMailBox
1678 {
1679 public:
1680 
1681  void create(object target)
1682  {
1683  ::create( 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});
1688  aoMessages=({});
1689  oMailBox=target;
1690 
1691  LOG_MSG("ContainerMailBox->create() called:");
1692  LOG_MSG("target id: "+target->get_object_id());
1693  LOG_MSG("target name: "+target->get_identifier());
1694 
1695  array inv=target->get_inventory_by_class(iAllowedTypes);
1696  LOG_MSG("box size is: "+sizeof(inv));
1697 
1698  Message reply = ReplyMessage(oMailBox);
1699  aoMessages+=({reply});
1700  LOG_MSG("added reply-mail to mailbox");
1701 
1702  for(int i=0;i<sizeof(inv);i++)
1703  {
1704  mixed err = catch {
1705  Message msg=ContainerMessage(inv[i]); //ContainerMailBox has ContainerMessages...
1706  aoMessages+=({msg});
1707  };
1708  if ( err ) {
1709  FATAL("Failed to create Message: %O\n%O\n", err[0], err[1]);
1710  }
1711  }
1712  alEnter=({MessagingListener(aiEnterEvent[0] | aiLeaveEvent[0], oMailBox, notify_me)});
1713 /*
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)]");
1719 */
1720  }
1721 
1722 protected:
1723  void rebuild_box()
1724  {
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());
1729 
1730  array new_ids=({});
1731  int i;
1732  for(i=0;i<sizeof(tmp);i++) new_ids+=({tmp[i]->get_object_id()});
1733 // LOG_MSG("new ids:%O",new_ids);
1734  array old_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
1738  {
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)
1743  {
1744  LOG_MSG("removing lost #"+aoMessages[i]->get_object_id());
1745  aoMessages-=({aoMessages[i]});
1746  }
1747  }
1748  else //messages were added
1749  {
1750  array diff=new_ids-old_ids;
1751 // LOG_MSG("diff is:%O",diff);
1752  for(i=0;i<sizeof(diff);i++)
1753  {
1754  LOG_MSG("adding new #"+diff[i]);
1755  Message msg=ContainerMessage(_Database->find_object(diff[i]));
1756  aoMessages+=({msg});
1757  }
1758  }
1759  }
1760 
1761 public:
1762 
1763  int has_subfolders()
1764  {
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));
1769  }
1770 
1771 private:
1772  private object search_for_folder(object where, string what)
1773  {
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)
1777  {
1778  int i=0;
1779  while(i<sizeof(folders))
1780  {
1781  if(folders[i]->get_object_class() & CLASS_EXIT)
1782  folders[i]=folders[i]->get_exit();
1783  if(folders[i]->get_identifier()==what )
1784  return folders[i];
1785  i++;
1786  }
1787  LOG_MSG("subfolder not found: "+what);
1788  }
1789  return 0;
1790  }
1791 
1792 public:
1793 
1794  BaseMailBox get_subfolder(string subfolder)
1795  {
1796  int fail=0;
1797  array parts=subfolder/"/"; //it is possible to fetch a "deep" folder with one call
1798  int i=0;
1799  object current = oMailBox;
1800  while(i<sizeof(parts) && !fail) //search for folders "deeper" into this box
1801  {
1802  current = search_for_folder(current, parts[i]);
1803  if(objectp(current)) i++;
1804  else fail=1;
1805  }
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);
1811  else
1812  if(current->get_object_class() & CLASS_MESSAGEBOARD)
1813  return MessageboardMailBox(current);
1814  else return 0; //unsupported object-type
1815  }
1816 
1817  int create_subfolder(string subfolder)
1818  {
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);};
1823  if(err!=0)
1824  {
1825  tmp->delete();
1826  return 0;
1827  }
1828  else return 1;
1829  }
1830 
1831  array list_subfolders(int recurse)
1832  {
1833  array res = ({});
1834  if(recurse==0) return res; //nothing to do...
1835  if(recurse==1) //easy, just get subfolders of this box
1836  {
1837  array folders = oMailBox->get_inventory_by_class(iFolderTypes);
1838  if(sizeof(folders)>0)
1839  {
1840  for(int i=0;i<sizeof(folders);i++)
1841  res+=({folders[i]->get_identifier() });
1842  return res;
1843  }
1844  else return ({}); //no subfolders in this mailbox
1845  }
1846  else //perform BFS on this mailbox
1847  {
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
1853  int iter=0;
1854  while(sizeof(queue)>0 && discover[queue[0]]!=recurse)
1855  {
1856  tid=queue[0];
1857  object current=_Database->find_object(tid);
1858  array inv;
1859  if(current->get_object_class() & CLASS_MESSAGEBOARD)
1860  inv=({});
1861  //messageboards have no subfolders, search for this vertex is complete
1862  else
1863  inv=current->get_inventory_by_class(iFolderTypes);
1864  foreach(inv, object v)
1865  {
1866  int id;
1867  if(v->get_object_class() & CLASS_EXIT)
1868  { //"resolve" exits
1869  mixed err=catch{id=v->get_exit()->get_object_id();};
1870  if(err!=0) continue; //can't access target of exit, ignore it
1871  }
1872  else
1873  id=v->get_object_id();
1874  if(sizeof(all&({id}))==0)
1875  {
1876  all+=({id}); queue+=({id});
1877  discover+=([id:discover[tid]+1]);
1878  parent[id]=tid; //this vertex has been discovered from "tid"
1879  }
1880  }
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
1888  string path="";
1889  while(tid!=mboxid)
1890  {
1891  path="/"+_Database->find_object(tid)->get_identifier()+path;
1892  tid=parent[tid];
1893  }
1894  path=path[1..sizeof(path)-1]; //remove first "/";
1895  res+=({path});
1896  }
1897  return sort(res);
1898  }
1899  }
1900 
1901  void add_message(Message msg)
1902  {
1903  LOG_MSG("adding #"+msg->get_object_id()+" to container-box #"+oMailBox->get_object_id());
1904 
1905  int i=msg->has_attachments();
1906  if(i==0) //no attachments -> store without modification
1907  {
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");
1915  return;
1916  }
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++)
1923  {
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")
1928  {
1929  amText+=({amAttachments[i]});
1930  LOG_MSG("-> comment to new document");
1931  }
1932  else
1933  {
1934  amNonText+=({amAttachments[i]});
1935  LOG_MSG("-> new document");
1936  }
1937  }
1938  LOG_MSG("found "+sizeof(amText)+" textual part(s) and "+sizeof(amNonText)+" non-textual part(s)");
1939 
1940 
1941  array annotations = ({ });
1942  if ( sizeof(amText) >= 1 )
1943  {
1944  LOG_MSG("converting text to annotation, non-text to document:");
1945  Message ann=amText[0];
1946  foreach ( amText, Message ann ) {
1947  }
1948  }
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 )
1955  }
1956  }
1957  else {
1958  foreach ( annotations, object annotation )
1959  }
1960  LOG_MSG("finished!");
1961  }
1962 }
1963 
1964 /**
1965  * this class is used for accessing sTeam-messageboards
1966  */
1967 class MessageboardMailBox
1968 {
1969 public:
1970 
1971  void create(object target)
1972  {
1973  LOG_MSG("MessageboardMailBox->create() called:");
1974  ::create( target );
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});
1979  oMailBox=target;
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)});
1991  }
1992 
1993  void notify_enter(int event, mixed ... args)
1994  {
1995  object what;
1996  if(event & EVENTS_MONITORED) what=args[3];
1997  else what=args[2];
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");
2002  }
2003 
2004  void notify_leave(int event, mixed ... args)
2005  {
2006  object what;
2007  if(event & EVENTS_MONITORED) what=args[3];
2008  else what=args[2];
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)
2012  {
2013  int found=0;
2014  int i=0;
2015  while(found!=1 && i<sizeof(aoMessages))
2016  {
2017  if(aoMessages[i]->get_object_id()==id) found=1;
2018  else i++;
2019  }
2020  if(found) aoMessages-=({aoMessages[i]});
2021  else LOG_MSG("message not found, already removed");
2022  }
2023  else LOG_MSG("... can't be converted to message - ignored");
2024  }
2025 
2026 private:
2027  private array(Message) scan_board_structure()
2028  {
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
2036  int iter=0;
2037  while(sizeof(queue)>0)
2038  {
2039  tid=queue[0];
2040  array inv=_Database->find_object(tid)->get_annotations();
2041  foreach(inv, object v)
2042  {
2043  int id=v->get_object_id();
2044  if(sizeof(all&({id}))==0)
2045  {
2046  all+=({id}); queue+=({id});
2047  discover+=([id:discover[tid]+1]);
2048  parent[id]=tid; //this vertex has been discovered from "tid"
2049  }
2050  }
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)
2062  {
2063  object parentTmp=_Database->find_object(parent[tid]);
2064  mapping parentHeader = parentTmp->query_attribute(MAIL_MIMEHEADERS);
2065  string msgId;
2066  if(mappingp(parentHeader)) msgId=parentHeader["message-id"];
2067  if(stringp(msgId) && msgId!="")
2068  msg->add_to_header((["in-reply-to":msgId,"references":msgId]));
2069  }
2070  res+=({msg});
2071  }
2072  return res;
2073  }
2074 
2075 public:
2076 
2077  void add_message(Message msg)
2078  {
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")+">";
2084  if(objectp(tmp))
2085 
2086  LOG_MSG("added #"+msg->get_object_id()+" to box #"+oMailBox->get_object_id());
2087  }
2088 
2089 }
2090 
2091 /**
2092  * creates a suitable MailBox-Object for the given target
2093  * support for more classes may be added in this function
2094  *
2095  * @param object target - the sTeam-Object to create a mailbox from
2096  * @return an object (of a subclass) of Messaging.BaseMailBox
2097  */
2098 BaseMailBox get_mailbox(object target)
2099 {
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);
2106 
2107  LOG_MSG("get_mailbox() called for unknown class: "+
2108  target->get_object_class());
2109  return 0;
2110 }
2111 
2112 /**
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
2117  *
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
2121  */
2122 int add_message_to_object(Message msg, object target)
2123 {
2124  mapping header = msg->header();
2125  LOG_MSG("add_message_to_object(%s, %d)\n", header->subject, target->get_object_id());
2126 
2127  mapping missing_ids = target->query_attribute("OBJ_ANNO_MISSING_IDS");
2128  if(!mappingp(missing_ids))
2129  missing_ids = ([]);
2130 
2131  // seems like replies to this mail got here first, reattach them
2132  if(header["message-id"] && missing_ids[header["message-id"]])
2133  {
2134  foreach(missing_ids[header["message-id"]];; int oid)
2135  {
2136  object message = _Database->find_object(oid);
2137  message->get_annotating()->remove_annotation(message);
2138  }
2139  m_delete(missing_ids, header["message-id"]);
2140  }
2141 
2142  // now lets find our real parent
2143  if(header["in-reply-to"] || header["references"])
2144  {
2145  mapping message_ids = target->query_attribute("OBJ_ANNO_MESSAGE_IDS");
2146  if(!message_ids)
2147  message_ids = ([]);
2148 
2149  object new_target;
2150 
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
2154  array ids = ({});
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"], "%{<%[^>]>%*[^<]%}")));
2159 
2160  foreach(ids; int count; string id)
2161  {
2162  id="<"+id+">";
2163  if(message_ids[id])
2164  {
2165  new_target = _Database->find_object(message_ids[id]);
2166  break;
2167  }
2168  // the first reference is the best, save it, so the message
2169  // can be reattached, should the reference arrive later
2170  if(count==0)
2171  {
2172  if(!missing_ids[id])
2173  missing_ids[id] = ({ msg->get_object_id() });
2174  else
2175  missing_ids[id] += ({ msg->get_object_id() });
2176  }
2177  }
2178  target->set_attribute("OBJ_ANNO_MISSING_IDS", missing_ids);
2179 
2180  if(new_target)
2181  target = new_target;
2182  }
2183 
2184  // we are back to our regular programming
2185  mixed err = catch
2186  {
2187  if ( (target->get_object_class() & CLASS_USER) ) {
2188  }
2189  };
2190  //attachments are already annotated to msg-object
2191  if(err!=0)
2192  {
2193  return 0;
2194  }
2195 
2196  int id=target->get_object_id();
2197  string reply=header["in-reply-to"];
2198 
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;
2203 
2204  if(!stringp(reply) || reply=="")
2205  {
2206  if(mappingp(target->query_attribute(MAIL_MIMEHEADERS)))
2207  {
2208  reply=target->query_attribute(MAIL_MIMEHEADERS)["message-id"];
2209  if(!stringp(reply) || reply=="") reply="<"+sprintf("%010d",id)+"@"+sFQDN+">";
2210  }
2211  else reply="<"+id+"@"+sFQDN+">";
2212  }
2213  string references=header["references"];
2214  if(!stringp(references) || references=="") references=reply;
2215 
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);
2219 
2220  msg->add_to_header((["in-reply-to":reply,
2221  "references":references,
2222  "message-id":message_id ]));
2223 
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);
2231 
2232  return 1;
2233 }
2234 
2235 
2236 /**
2237  * create an object of class Message
2238  *
2239  * @input string raw - a string representing a MIME-message (rfc 2045-2049)
2240  * @return Message-object
2241  */
2242 Message MIME2Message(string raw)
2243 {
2244  MIME.Message msg = MIME.Message(raw);
2245 
2246  mapping msg_decoded_headers = ([]);
2247  foreach(msg->headers; string header; string value)
2248  {
2249  string decoded;
2250  catch
2251  {
2252  value = replace(value, "\0", "\n");
2253  decoded = MIME.decode_words_text_remapped(value);
2254  };
2255  msg_decoded_headers[header] = string_to_utf8(decoded||value);
2256  }
2257 
2258  if ( !stringp(msg_decoded_headers->subject)
2259  || sizeof(msg_decoded_headers->subject) < 1 )
2260  msg_decoded_headers->subject = " no subject ";
2261 
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 ]),
2269  ])
2270  );
2271  array (object) parts = msg->body_parts;
2272  if ( arrayp(parts) ) //multipart message, add parts as separate documents
2273  {
2274  LOG_MSG("found "+sizeof(parts)+" parts, now processing...");
2275  foreach(parts, MIME.Message obj)
2276  {
2277  mapping obj_decoded_headers = ([]);
2278  foreach(obj->headers; string header; string value)
2279  {
2280  string decoded;
2281  catch
2282  {
2283  decoded = MIME.decode_words_text_remapped(value);
2284  };
2285  obj_decoded_headers[header] = string_to_utf8(decoded||value);
2286  }
2287 
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
2296  ((["name": name,
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());
2303  else
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);
2307  }
2308  }
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!");
2315  else
2316  mail->set_content(msg->getdata());
2317 
2318  LOG_MSG("Finished conversion raw text -> message");
2319  return Message(mail);
2320 }
2321 
2322 Message SimpleMessage(array target, string subject, string message)
2323 {
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 );
2330 
2331  Message msg=Message(mail);
2332  mapping header=msg->header();
2333  string to = target*", ";
2334  header["to"]=to;
2335 
2336  return msg;
2337 }
2338 
2339 //support for modified UTF7
2340 //see RFC3501, section 5.1.3 & RFC2152
2341 //
2342  string mbase64tab="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890+,";
2343 
2344 string decode_mbase64(string encoded)
2345 {
2346  //decode a modified-base64-encoded string
2347  switch(sizeof(encoded)%4)
2348  {
2349  case 0:
2350  break;
2351  case 1:
2352  encoded+="\0\0\0";
2353  break;
2354  case 2:
2355  encoded+="\0\0";
2356  break;
2357  case 3:
2358  encoded+="\0";
2359  }
2360  string result="";
2361  while(sizeof(encoded)>0)
2362  {
2363  string part=encoded[0..3];
2364  if(sizeof(encoded)>3)
2365  encoded=encoded[4..sizeof(encoded)-1];
2366  else encoded="";
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);
2375  }
2376  if(sizeof(result)%2==1) result=result[0..sizeof(result)-2];
2377  result=unicode_to_string(result);
2378  return result;
2379 }
2380 
2381 string encode_mbase64(string input)
2382 {
2383  //encode a string via modified-base64-encoding
2384  input=string_to_unicode(input);
2385  switch(sizeof(input)%3)
2386  {
2387  case 0:
2388  break;
2389  case 1:
2390  input+="\0\0";
2391  break;
2392  case 2:
2393  input+="\0";
2394  break;
2395  }
2396  string result="";
2397  while(sizeof(input)>0)
2398  {
2399  string part=input[0..2];
2400  if(sizeof(input)>2)
2401  input=input[3..sizeof(input)-1];
2402  else input="";
2403  int p1=part[0];
2404  int p2=part[1];
2405  int p3=part[2];
2406  int b1=p1>>2;
2407  int b2=(p1&0b11)|(p2>>4);
2408  int b3=(p2&0b1111)<<2|(p3>>6);
2409  int b4=p3&0b111111;
2410  result+=mbase64tab[b1..b1];
2411  if(b2!=0)
2412  {
2413  result+=mbase64tab[b2..b2];
2414  if(b3!=0)
2415  {
2416  result+=mbase64tab[b3..b3];
2417  if(b4!=0)
2418  result+=mbase64tab[b4..b4];
2419  }
2420  }
2421  }
2422  return result;
2423 }
2424 
2425 string decode_mutf7(string encoded)
2426 {
2427  string result="";
2428  int i=-1;
2429  while(i<sizeof(encoded))
2430  {
2431  int start=i;
2432  i=search(encoded,"&",i+1);
2433  if(i==-1)
2434  {
2435  result+=encoded[start+1..sizeof(encoded)-1];
2436  i=sizeof(encoded);
2437  }
2438  else
2439  {
2440  result+=encoded[start+1..i-1];
2441  if(encoded[i+1]=='-')
2442  {
2443  result+="&"; //sequence "&-" found
2444  i++;
2445  }
2446  else
2447  {
2448  int j=search(encoded,"-",i+1);
2449  if(j!=-1)
2450  {
2451  result+=decode_mbase64(encoded[i+1..j-1]);
2452  i=j;
2453  }
2454  else return 0; //syntax error in mutf7-string
2455  }
2456  }
2457  }
2458  return result;
2459 }
2460 
2461 string encode_mutf7(string input)
2462 {
2463  string result="",tombase64="";
2464  int mode=0;
2465  for(int i=0;i<sizeof(input);i++)
2466  {
2467  int val=input[i];
2468  if(val==0x26)
2469  {
2470  if(mode)
2471  {
2472  mode=0;
2473  result+="&"+encode_mbase64(tombase64)+"-";
2474  tombase64="";
2475  }
2476  result+="&-";
2477  continue;
2478  }
2479  if(val>=0x20 && val<=0x7e)
2480  {
2481  if(mode)
2482  {
2483  mode=0;
2484  result+="&"+encode_mbase64(tombase64)+"-";
2485  tombase64="";
2486  }
2487  result+=input[i..i];
2488  continue;
2489  }
2490  mode=1;
2491  tombase64+=input[i..i];
2492  }
2493  if(mode)
2494  result+="&"+encode_mbase64(tombase64)+"-";
2495  return result;
2496 }
2497 
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
2501 
2502 
2503 int get_object_id()
2504 {
2505  return get_object()->get_object_id();
2506 }
2507 
2508 {
2509  return get_object();
2510 }
2511 
2512 object get_object()
2513 {
2514  object user = geteuid();
2515  if ( !objectp(user) )
2516  user = this_user();
2517 
2518  if ( objectp(user) )
2519  return user;
2520  return get_module("forward");
2521 }
2522 
2523 int get_object_class()
2524 {
2525  return get_object()->get_object_class();
2526 }
2527 
2528 string fix_html ( string text )
2529 {
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>";
2536  return new_text;
2537 }
2538 
2539 
2540 };