--- parser3/src/classes/mail.C 2002/08/13 15:55:41 1.78 +++ parser3/src/classes/mail.C 2016/09/08 20:41:47 1.129 @@ -1,13 +1,12 @@ /** @file Parser: @b mail parser class. - Copyright (c) 2001, 2002 ArtLebedev Group (http://www.artlebedev.com) + Copyright (c) 2001-2015 Art. Lebedev Studio (http://www.artlebedev.com) Author: Alexandr Petrosian (http://paf.design.ru) */ -static const char* IDENT_MAIL_C="$Date: 2002/08/13 15:55:41 $"; - #include "pa_config_includes.h" +#include "pa_vmethod_frame.h" #include "pa_common.h" #include "pa_request.h" @@ -16,337 +15,142 @@ static const char* IDENT_MAIL_C="$Date: #include "pa_charsets.h" #include "pa_charset.h" #include "pa_uue.h" +#include "pa_vmail.h" -#ifdef _MSC_VER -# include "smtp/smtp.h" -#endif +#include "smtp.h" + +volatile const char * IDENT_MAIL_C="$Id: mail.C,v 1.129 2016/09/08 20:41:47 moko Exp $"; // defines #define MAIL_CLASS_NAME "mail" - -#define MAIL_NAME "MAIL" +#define SENDMAIL_NAME "sendmail" // consts const int ATTACHMENT_WEIGHT=100; -// global variable - -Methoded *mail_base_class; - // class -class MMail : public Methoded { -public: - MMail(Pool& pool); +class MMail: public Methoded { public: // Methoded bool used_directly() { return false; } void configure_user(Request& r); -private: - String mail_name; -}; - -/** ^mail:send[$attach[$type[uue|mime64] $value[DATA]]] - @todo solve - bad with html attaches -*/ -static const String& attach_hash_to_string(Request& r, const String& origin_string, - Hash& attach_hash) { - Pool& pool=r.pool(); - - Value *vformat=static_cast(attach_hash.get(*new(pool) String(pool, "format"))); - - const VFile *vfile; - if(Value *value=static_cast(attach_hash.get(*value_name))) - vfile=value->as_vfile(String::UL_AS_IS); // bad with html attaches. todo: solve - else - throw Exception("parser.runtime", - &origin_string, - "has no $value"); - - const String *file_name; - if(Value *vfile_name=static_cast(attach_hash.get( - *new(pool) String(pool, "file-name")))) // specified $file-name - file_name=&vfile_name->as_string(); - else // no $file-name, VFile surely knows name - file_name=&static_cast(vfile->fields().get(*name_name))->as_string(); - const char *file_name_cstr=file_name->cstr(); - - String& result=*new(pool) String(pool); - - // content-type: application/octet-stream - result << "content-type: " << r.mime_type_of(file_name_cstr) - << "; name=\"" << file_name_cstr << "\"\n"; - // content-disposition: attachment; filename="user_file_name" - result << "content-disposition: attachment; filename=\"" << file_name_cstr << "\"\n"; - - const String *type=vformat?&vformat->as_string():0; - if(!type/*default = uue*/ || *type=="uue") { - pa_uuencode(result, file_name_cstr, *vfile); - } else // for now - throw Exception("parser.runtime", - type, - "unknown attachment encode format"); - - return result; -} - -#ifndef DOXYGEN -struct Mail_info { - Charset *charset; const char *content_charset_name; - String *header; - const String **from, **to; - const String *errors_to; +public: + MMail(); }; -#endif -const String& extractEmail(const String& string); -static void add_header_attribute(const Hash::Key& raw_element_name, Hash::Val *aelement_value, - void *info) { - Value& element_value=*static_cast(aelement_value); - const String& low_element_name=raw_element_name.change_case(raw_element_name.pool(), String::CC_LOWER); - Mail_info& mi=*static_cast(info); - - // exclude one attribute [body] - if(low_element_name==BODY_NAME - || low_element_name==CHARSET_NAME) - return; +// global variable - // fetch from & to from header for SMTP - if(mi.from && low_element_name=="from") - *mi.from=&extractEmail(element_value.as_string()); - if(mi.to && low_element_name=="to") - *mi.to=&extractEmail(element_value.as_string()); - if(low_element_name=="errors-to") - mi.errors_to=&extractEmail(element_value.as_string()); - - // append header line - *mi.header << - low_element_name << ":" << - attributed_meaning_to_string(element_value, String::UL_MAIL_HEADER). - cstr(String::UL_UNSPECIFIED, 0, mi.charset, mi.content_charset_name) << - "\n"; -} +DECLARE_CLASS_VAR(mail, new MMail); -#ifndef DOXYGEN -struct Mail_seq_item { - int weight; - const String *name; - Value *value; -}; -#endif -static int get_part_name_weight(const Hash::Key& part_name) { - const char *cstr=part_name.cstr(); - int offset=0; - if(strncmp(cstr, "text", 4)==0) { - cstr+=4; - } else if(strncmp(cstr, "attach", 6)==0) { - cstr+=6; - offset=ATTACHMENT_WEIGHT; - } else - throw Exception("parser.runtime", - &part_name, - "is neither text# nor attach#"); +// defines for statics - char *error_pos; - return strtol(cstr, &error_pos, 10)+offset; -} -static void add_part(const Hash::Key& part_name, Hash::Val *part_value, - void *info) { - Mail_seq_item **seq_ref=static_cast(info); - (**seq_ref).weight=get_part_name_weight(part_name); - (**seq_ref).name=&part_name; - (**seq_ref).value=static_cast(part_value); - (*seq_ref)++; -} -static int key_of_part(const void *item) { - return static_cast(item)->weight; -} -static int sort_cmp_string_double_value(const void *a, const void *b) { - return key_of_part(a)-key_of_part(b); -} -static const String& message_hash_to_string(Request& r, const String& method_name, - Hash& message_hash, int level, - const String **from, const String **to) { - Pool& pool=r.pool(); - - // prepare header: 'hash' without "body" - String& result=*new(pool) String(pool); - - Charset *charset; - if(Value *vrecodecharset_name=static_cast(message_hash.get(*charset_name))) - charset=&charsets->get_charset(vrecodecharset_name->as_string()); - else - charset=&pool.get_source_charset(); - - const char *content_charset_name=0; - if(Value *vcontent_type=static_cast(message_hash.get(*content_type_name))) - if(Hash *hcontent_type=vcontent_type->get_hash(0)) - if(Value *vcontentcharset_name=static_cast(hcontent_type->get(*charset_name))) - content_charset_name=vcontentcharset_name->as_string().cstr(); - - if(from) - *from=0; - if(to) - *to=0; - Mail_info mail_info={ - charset, content_charset_name, - &result, - from, to - }; - message_hash.for_each(add_header_attribute, &mail_info); - if(!mail_info.errors_to) - result << "errors-to: postmaster\n"; // errors-to: default - - if(Value *body_element=static_cast(message_hash.get(*body_name))) { - if(Hash *body_hash=body_element->get_hash(&method_name)) { - // body parts.. - // ..collect - Mail_seq_item *seq=(Mail_seq_item *)pool.malloc(sizeof(Mail_seq_item)*body_hash->size()); - Mail_seq_item *seq_ref=seq; body_hash->for_each(add_part, &seq_ref); - // ..sort - _qsort(seq, body_hash->size(), sizeof(Mail_seq_item), - sort_cmp_string_double_value); - - bool multipart=body_hash->size()>1; - // header - char *boundary=0; - if(multipart) { - boundary=(char *)pool.malloc(MAX_NUMBER); - snprintf(boundary, MAX_NUMBER-5/*lEvEl*/, "lEvEl%d", level); - // multi-part - result << "content-type: multipart/mixed; boundary=\"" << boundary << "\"\n"; - result << "\n" - "This is a multi-part message in MIME format."; - } - - // ..insert in 'seq' order - for(int i=0; isize(); i++) { - if(multipart) { - // intermediate boundary - result << "\n--" << boundary << "\n"; - } - - if(Hash *part_hash=seq[i].value->get_hash(&method_name)) - if(seq[i].weight>=ATTACHMENT_WEIGHT) - result << attach_hash_to_string(r, *seq[i].name, *part_hash); - else - result << message_hash_to_string(r, method_name, *part_hash, - level+1, 0, 0); - else - throw Exception("parser.runtime", - seq[i].name, - "part is not hash"); - } - - // finish boundary - if(multipart) { - result << "\n--" << boundary << "--\n"; - } - } else { - result << - "\n"; // header|body separator - - const String& body=body_element->as_string(); - const char *body_ptr=body.cstr(String::UL_UNSPECIFIED); // body - size_t body_size=strlen(body_ptr); // body - const void *mail_ptr; - size_t mail_size; - Charset::transcode(pool, - pool.get_source_charset(), body_ptr, body_size, - *charset/*always set - either mail.charset or $request:charset*/, mail_ptr, mail_size); - result.APPEND_CLEAN((const char*)mail_ptr, mail_size, 0, 0); - } - } else - throw Exception("parser.runtime", - &method_name, - "has no $body"); - - return result; -} +#define MAIL_NAME "MAIL" -static void sendmail(Request& r, const String& method_name, - const String& message, - const String *from, const String *to) { - Pool& pool=r.pool(); +// statics + +static const String mail_name(MAIL_NAME); +static const String mail_sendmail_name(SENDMAIL_NAME); - char *message_cstr=message.cstr(String::UL_UNSPECIFIED); - Hash *mail_conf=static_cast(r.classes_conf.get(mail_base_class->name())); +// helpers - const char *exception_type="email.format"; - if(!from) +static void sendmail( + Value* +#ifndef WIN32 + vmail_conf +#endif + , Value* smtp_server_port, + const String& message, + const String* from, + const String* to, + const String* +#ifndef WIN32 + options +#endif + ) { + const char* exception_type="email.format"; + if(!from) // we use in sendmail -f {from} && SMTP MAIL from: {from} throw Exception(exception_type, - &method_name, + 0, "parameter does not specify 'from' header field"); - if(!to) - throw Exception(exception_type, - &method_name, - "parameter does not specify 'to' header field"); -#ifdef _MSC_VER - - SMTP& smtp=*new(pool) SMTP(pool, method_name); - Value *server_port; - // $MAIN:MAIL.SMTP[mail.yourdomain.ru[:port]] - if(mail_conf && - (server_port=static_cast(mail_conf->get( - *new(pool) String(pool, "SMTP"))))) { - char *server=server_port->as_string().cstr(); - const char *port=rsplit(server, ':'); + + const char* message_cstr=message.untaint_cstr(String::L_AS_IS); + + if(smtp_server_port) { + if(!to) // we use only in SMTP RCPT to: {to} + throw Exception(exception_type, + 0, + "parameter does not specify 'to' header field"); + + SMTP smtp; + char* server=smtp_server_port->as_string().cstrm(); + const char* port=rsplit(server, ':'); if(!port) port="25"; - smtp.Send(server, port, message_cstr, from->cstr(), to->cstr()); - } else - throw Exception("parser.runtime", - &method_name, - "$"MAIN_CLASS_NAME":"MAIL_NAME".SMTP not defined"); + smtp.Send(server, port, message_cstr, from->cstrm(), to->cstrm()); + return; + } + +#ifdef WIN32 + // win32 without SMTP server configured + throw Exception(PARSER_RUNTIME, + 0, + "$"MAIN_CLASS_NAME":"MAIL_NAME".SMTP not defined"); #else // unix // $MAIN:MAIL.sendmail["/usr/sbin/sendmail -t -i -f postmaster"] default // $MAIN:MAIL.sendmail["/usr/lib/sendmail -t -i -f postmaster"] default - const String *sendmail_command; + String* sendmail_command=new String; + if(vmail_conf) { #ifdef PA_FORCED_SENDMAIL - sendmail_command=new(pool) String(pool, PA_FORCED_SENDMAIL); + throw Exception(PARSER_RUNTIME, + 0, + "Parser was configured with --with-sendmail=" PA_FORCED_SENDMAIL + " key, to change sendmail you should reconfigure and recompie it"); #else - const char *sendmailkey_cstr="sendmail"; - if(mail_conf) { - if(Value *sendmail_value=static_cast(mail_conf->get(String(pool, sendmailkey_cstr)))) - sendmail_command=&sendmail_value->as_string(); + if(Value* sendmail_value=vmail_conf->get_hash()->get(mail_sendmail_name)) + *sendmail_command<as_string(); else - throw Exception("parser.runtime", - &method_name, - "$"MAIN_CLASS_NAME":"MAIL_NAME".%s not defined", - sendmailkey_cstr); + throw Exception(PARSER_RUNTIME, + 0, + "$" MAIN_CLASS_NAME ":" MAIL_NAME "." SENDMAIL_NAME " not defined"); +#endif } else { - String *test=new(pool) String(pool, "/usr/sbin/sendmail"); +#ifdef PA_FORCED_SENDMAIL + *sendmail_command<APPEND_CONST(" -t -i -f postmaster"); - sendmail_command=test; - } + test=new String("/usr/lib/sendmail"); + *sendmail_command<<*test; + *sendmail_command<<" -t -i -f postmaster"; #endif + } + if(options) + *sendmail_command<<" "<<*options; // we know sendmail_command here, should replace "postmaster" with "$from" from message - int at_postmaster=sendmail_command->pos("postmaster"); - if(at_postmaster>0) { + size_t at_postmaster=sendmail_command->pos("postmaster"); + if(at_postmaster!=STRING_NOT_FOUND) { String& reconstructed=sendmail_command->mid(0, at_postmaster); reconstructed << *from; - reconstructed << sendmail_command->mid(at_postmaster+10/*postmaster*/, sendmail_command->size()); + reconstructed << sendmail_command->mid(at_postmaster+10/*postmaster*/, sendmail_command->length()); sendmail_command=&reconstructed; } // execute it - Array argv(pool); - const String *file_spec; - int after_file_spec=sendmail_command->pos(" ", 1); - if(after_file_spec<=0) + ArrayString argv; + const String* file_spec; + size_t after_file_spec=sendmail_command->pos(' '); + if(after_file_spec==STRING_NOT_FOUND || after_file_spec==0) file_spec=sendmail_command; else { - size_t pos_after=after_file_spec; - file_spec=&sendmail_command->mid(0, pos_after++); - sendmail_command->split(argv, &pos_after, " ", 1, String::UL_AS_IS); + file_spec=&sendmail_command->mid(0, after_file_spec); + sendmail_command->split(argv, after_file_spec+1, " ", String::L_AS_IS); } if(!file_executable(*file_spec)) @@ -356,14 +160,13 @@ static void sendmail(Request& r, const S #ifdef PA_FORCED_SENDMAIL " Use configure key \"--with-sendmail=appropriate sendmail command\"" #else - " Set $"MAIN_CLASS_NAME":"MAIL_NAME".%s to appropriate sendmail command", - sendmailkey_cstr + " Set $" MAIN_CLASS_NAME ":" MAIL_NAME "." SENDMAIL_NAME " to appropriate sendmail command" #endif ); - String in(pool, message_cstr); String out(pool); String err(pool); - int exit_status=pa_exec( + String in(message_cstr); + PA_exec_result exec=pa_exec( // forced_allow #ifdef PA_FORCED_SENDMAIL true @@ -371,66 +174,73 @@ static void sendmail(Request& r, const S false #endif , *file_spec, - 0/*default env*/, - &argv, - in, out, err); - if(exit_status || err.size()) + 0 /* pass env */, + argv, + in); + if(exec.status || exec.err.length()) throw Exception("email.send", - &method_name, + 0, "'%s' reported problem: %s (%d)", file_spec->cstr(), - err.size()?err.cstr():"UNKNOWN", - exit_status); -#endif + exec.err.length()?exec.err.cstr():"UNKNOWN", + exec.status); +#endif //WIN32 } - // methods -static void _send(Request& r, const String& method_name, MethodParams *params) { - Pool& pool=r.pool(); +static void _send(Request& r, MethodParams& params) { + HashStringValue* hash=params.as_hash(0, "message"); + if(!hash || !hash->count()) + return; + // todo@ check if enough options are specified. + // now ^mail:send[^hash::create[]] and ^mail:send[$.print-debug(1)] "work". + + const String* soptions=0; + if(Value* voptions=hash->get(MAIL_OPTIONS_NAME)) + soptions=&voptions->as_string(); + + bool print_debug=false; + if(Value* vdebug=hash->get(MAIL_DEBUG_NAME)) + print_debug=vdebug->as_bool(); + + Value* vmail_conf=static_cast(r.classes_conf.get(mail_class->type())); + Value* smtp_server_port=0; + if(vmail_conf) { + // $MAIN:MAIL.SMTP[mail.yourdomain.ru[:port]] + smtp_server_port=vmail_conf->get_hash()->get("SMTP"); + } - Value& vhash=params->as_no_junction(0, "message must not be code"); - Hash *hash=vhash.get_hash(&method_name); - if(!hash) - throw Exception("parser.runtime", - &method_name, - "message must be hash"); - - const String *from, *to; - const String& message=hash->get(*body_name)/*old format*/? - message_hash_to_string(r, method_name, *hash, 0, &from, &to) : // old - static_cast(r.self)->message_hash_to_string(r, &method_name, hash, 0, &from, &to); // new - //r.write_pass_lang(message); - sendmail(r, method_name, message, from, to); + const String* from=0; + String* to=0; + const String& message= + GET_SELF(r, VMail).message_hash_to_string(r, hash, 0, from, + smtp_server_port?true:false /*send by SMTP=strip to?*/, to); + + if(print_debug) + r.write_pass_lang(message); + else + sendmail(vmail_conf, smtp_server_port, message, from, to, soptions); } // constructor & configurator -MMail::MMail(Pool& apool) : Methoded(apool, MAIL_CLASS_NAME), - mail_name(apool, MAIL_NAME) -{ +MMail::MMail(): Methoded(MAIL_CLASS_NAME) { // ^mail:send{hash} add_native_method("send", Method::CT_STATIC, _send, 1, 1); } void MMail::configure_user(Request& r) { - Pool& pool=r.pool(); // $MAIN:MAIL[$SMTP[mail.design.ru]] - if(Value *mail_element=r.main_class->get_element(mail_name, r.main_class, false)) - if(Hash *mail_conf=mail_element->get_hash(0)) - r.classes_conf.put(name(), mail_conf); + if(Value* mail_element=r.main_class.get_element(mail_name)) { + if(mail_element->get_hash()) + r.classes_conf.put(type(), mail_element); else if( !mail_element->is_string() ) - throw Exception("parser.runtime", + throw Exception(PARSER_RUNTIME, 0, "$" MAIL_CLASS_NAME ":" MAIL_NAME " is not hash"); -} - -// creator - -Methoded *MMail_create(Pool& pool) { - return mail_base_class=new(pool) MMail(pool); + } }