Annotation of parser3/src/targets/apache13/mod_parser3.C, revision 1.2
1.1 parser 1: /** @file
2: Parser: apache 1.3 module.
3:
4: Copyright (c) 2001 ArtLebedev Group (http://www.artlebedev.com)
5: Author: Alexander Petrosyan <paf@design.ru> (http://design.ru/paf)
6:
1.2 ! parser 7: $Id: mod_parser3.C,v 1.1 2001/10/11 08:29:21 parser Exp $
1.1 parser 8: */
9:
10: #include "httpd.h"
11: #include "http_config.h"
12: #include "http_core.h"
13: #include "http_log.h"
14: #include "http_main.h"
15: #include "http_protocol.h"
16: #include "util_script.h"
17:
18: #include "pa_sapi.h"
19: #include "classes.h"
20: #include "pa_common.h"
21: #include "pa_globals.h"
22: #include "pa_request.h"
23: #include "pa_version.h"
24: #include "pa_socks.h"
25:
1.2 ! parser 26: #ifdef XML
! 27: #include <XalanTransformer/XalanCAPI.h>
! 28: #endif
! 29:
1.1 parser 30: #ifdef _DEBUG
31: # define DEBUG_PREFIX "debug_"
32: # define PARSER3_MODULE debug_parser3_module
33: #else
34: # define DEBUG_PREFIX
35: # define PARSER3_MODULE parser3_module
36: #endif
37:
38: // consts
39:
40: extern const char *main_RCSIds[];
41: #ifdef USE_SMTP
42: extern const char *smtp_RCSIds[];
43: #endif
44: extern const char *gd_RCSIds[];
45: extern const char *classes_RCSIds[];
46: extern const char *types_RCSIds[];
47: extern const char *ApacheModuleParser3_RCSIds[];
48: #ifdef XML
49: extern const char *xalan_patched_RCSIds[];
50: #endif
51: const char **RCSIds[]={
52: main_RCSIds,
53: #ifdef USE_SMTP
54: smtp_RCSIds,
55: #endif
56: gd_RCSIds,
57: classes_RCSIds,
58: types_RCSIds,
59: ApacheModuleParser3_RCSIds,
60: #ifdef XML
61: xalan_patched_RCSIds,
62: #endif
63: 0
64: };
65:
66:
67: /// apache parser module configuration [httpd.conf + .htaccess-es]
68: struct Parser_module_config {
69: const char* parser_root_config_filespec; ///< filespec of admin's config file
70: const char* parser_site_config_filespec; ///< filespec of site's config file
71: };
72:
1.2 ! parser 73: #ifdef XML
! 74: /**
! 75: * Terminate Xalan and Xerces.
! 76: *
! 77: * Should be called only once per process after deleting all
! 78: * instances of XalanTransformer. Once a process has called
! 79: * this function, it cannot use the API for the remaining
! 80: * lifetime of the process.
! 81:
! 82:
! 83: this requirement is fullfilled by using Pool::register_cleanup
! 84: */
! 85: void callXalanTerminate(void *) {
! 86: //_asm int 3;
! 87: XalanTerminate();
! 88: }
! 89: #endif
! 90:
1.1 parser 91: /*
92: * Declare ourselves so the configuration routines can find and know us.
93: * We'll fill it in at the end of the module.
94: */
95: extern "C" module MODULE_VAR_EXPORT PARSER3_MODULE;
96:
97: /*
98: * Locate our directory configuration record for the current request.
99: */
100: static Parser_module_config *our_dconfig(request_rec *r) {
101: return (Parser_module_config *)
102: ap_get_module_config(r->per_dir_config, &PARSER3_MODULE);
103: }
104:
105: static const char *cmd_parser_config(cmd_parms *cmd, void *mconfig, char *file_spec) {
106: Parser_module_config *cfg = (Parser_module_config *) mconfig;
107:
108: // remember assigned filespec into cfg
109: (cmd->info?cfg->parser_root_config_filespec:cfg->parser_site_config_filespec)=file_spec;
110:
111: return NULL;
112: }
113:
114:
115: /*--------------------------------------------------------------------------*/
116: /* */
117: /* Now we declare our content handlers, which are invoked when the server */
118: /* encounters a document which our module is supposed to have a chance to */
119: /* see. (See mod_mime's SetHandler and AddHandler directives, and the */
120: /* mod_info and mod_status examples, for more details.) */
121: /* */
122: /* Since content handlers are dumping data directly into the connexion */
123: /* (using the r*() routines, such as rputs() and rprintf()) without */
124: /* intervention by other parts of the server, they need to make */
125: /* sure any accumulated HTTP headers are sent first. This is done by */
126: /* calling send_http_header(). Otherwise, no header will be sent at all, */
127: /* and the output sent to the client will actually be HTTP-uncompliant. */
128: /*--------------------------------------------------------------------------*/
129: /*
130: * Sample content handler. All this does is display the call list that has
131: * been built up so far.
132: *
133: * The return value instructs the caller concerning what happened and what to
134: * do next:
135: * OK ("we did our thing")
136: * DECLINED ("this isn't something with which we want to get involved")
137: * HTTP_mumble ("an error status should be reported")
138: */
139:
140:
141: //@{
142: /// SAPI func decl
143: void SAPI::log(Pool& pool, const char *fmt, ...) {
144: request_rec *r=static_cast<request_rec *>(pool.context());
145:
146: va_list args;
147: va_start(args,fmt);
148: char buf[MAX_STRING];
149: size_t size=vsnprintf(buf, MAX_STRING, fmt, args);
150: remove_crlf(buf, buf+size);
151: ap_log_rerror(0, 0, APLOG_ERR | APLOG_NOERRNO, r, "%s", buf);
152: va_end(args);
153: }
154:
155: const char *SAPI::get_env(Pool& pool, const char *name) {
156: request_rec *r=static_cast<request_rec *>(pool.context());
157: return (const char *)ap_table_get(r->subprocess_env, name);
158: }
159:
160: size_t SAPI::read_post(Pool& pool, char *buf, size_t max_bytes) {
161: request_rec *r=static_cast<request_rec *>(pool.context());
162:
163: /* ap_log_error(APLOG_MARK, APLOG_DEBUG, r->server,
164: "mod_parser3: SAPI::read_post(max=%u)", max_bytes);
165: */
166: int retval;
167: if((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
168: return 0;
169: if(!ap_should_client_block(r))
170: return 0;
171:
172: uint total_read_bytes=0;
173: void (*handler)(int)=signal(SIGPIPE, SIG_IGN);
174: while (total_read_bytes<max_bytes) {
175: ap_hard_timeout("Read POST information", r); /* start timeout timer */
176: uint read_bytes=
177: ap_get_client_block(r, buf+total_read_bytes, max_bytes-total_read_bytes);
178: ap_reset_timeout(r);
179: if (read_bytes<=0)
180: break;
181: total_read_bytes+=read_bytes;
182: }
183: signal(SIGPIPE, handler);
184: return total_read_bytes;
185: }
186:
187: void SAPI::add_header_attribute(Pool& pool, const char *key, const char *value) {
188: request_rec *r=static_cast<request_rec *>(pool.context());
189:
190: if(strcasecmp(key, "location")==0)
191: r->status=302;
192:
193: if(strcasecmp(key, "content-type")==0) {
194: /* r->content_type, *not* r->headers_out("Content-type"). If you don't
195: * set it, it will be filled in with the server's default type (typically
196: * "text/plain"). You *must* also ensure that r->content_type is lower
197: * case.
198: */
199: r->content_type = value;
200: } else if(strcasecmp(key, "status")==0)
201: r->status=atoi(value);
202: else
203: ap_table_addn(r->headers_out, key, value);
204: }
205:
206: void SAPI::send_header(Pool& pool) {
207: request_rec *r=static_cast<request_rec *>(pool.context());
208:
209: ap_hard_timeout("Send header", r);
210: ap_send_http_header(r);
211: ap_kill_timeout(r);
212: }
213:
214: void SAPI::send_body(Pool& pool, const void *buf, size_t size) {
215: request_rec *r=static_cast<request_rec *>(pool.context());
216:
217: ap_hard_timeout("Send body", r);
218: ap_rwrite(buf, size, r);
219: ap_kill_timeout(r);
220: }
221:
222: //@}
223:
224: /**
225: main workhorse
226:
227: @todo intelligent cache-control
228: */
229: static int parser_handler(request_rec *r) {
230: // _asm int 3;
231: if(r->finfo.st_mode == 0)
232: return NOT_FOUND;
233:
234: Pool pool(r->pool);
235: pool.set_context(r);
236:
1.2 ! parser 237: #ifdef XML
! 238: /**
! 239: * Initialize Xerces and Xalan.
! 240: *
! 241: * Should be called only once per process before making
! 242: * any other API calls.
! 243: */
! 244: //_asm int 3;
! 245: XalanInitialize();
! 246: pool.register_cleanup(callXalanTerminate, 0);
! 247: #endif
! 248:
1.1 parser 249: Parser_module_config *dcfg=our_dconfig(r);
250:
251:
252: /* A flag which modules can set, to indicate that the data being
253: * returned is volatile, and clients should be told not to cache it.
254: */
255: r->no_cache=1;
256:
257: PTRY { // global try
258: ap_add_common_vars(r);
259: ap_add_cgi_vars(r);
260:
261: // Request info
262: Request::Info request_info;
263: request_info.document_root=SAPI::get_env(pool, "DOCUMENT_ROOT");
264: request_info.path_translated=r->filename;
265: request_info.method=r->method;
266: request_info.query_string=r->args;
267: request_info.uri=SAPI::get_env(pool, "REQUEST_URI");
268: request_info.content_type=SAPI::get_env(pool, "CONTENT_TYPE");
269: const char *content_length=SAPI::get_env(pool, "CONTENT_LENGTH");
270: request_info.content_length=content_length?atoi(content_length):0;
271: request_info.cookie=SAPI::get_env(pool, "HTTP_COOKIE");
272: request_info.user_agent=SAPI::get_env(pool, "HTTP_USER_AGENT");
273:
274: //_asm int 3;
275: // prepare to process request
276: Request request(pool,
277: request_info,
278: String::UL_USER_HTML
279: );
280:
281: // process the request
282: request.core(
283: dcfg->parser_root_config_filespec, true, // /path/to/admin/config
284: dcfg->parser_site_config_filespec, true, // /path/to/site/config
285: r->header_only!=0);
286: // no actions with request' data past this point
287: // request.exception not not handled here, but all
288: // request' data are associated with it's pool=exception
289:
290: // successful finish
291: } PCATCH(e) { // global problem
292: // don't allocate anything on pool here:
293: // possible pool' exception not catch-ed now
294: // and there could be out-of-memory exception
295: const char *body=e.comment();
296: // log it
297: SAPI::log(pool, "exception in request exception handler: %s", body);
298:
299: //
300: int content_length=strlen(body);
301:
302: // prepare header
303: SAPI::add_header_attribute(pool, "content-type", "text/plain");
304: char content_length_cstr[MAX_NUMBER];
305: snprintf(content_length_cstr, MAX_NUMBER, "%u", content_length);
306: SAPI::add_header_attribute(pool, "content-length", content_length_cstr);
307:
308: // send header
309: SAPI::send_header(pool);
310:
311: // send body
312: if(!r->header_only)
313: SAPI::send_body(pool, body, content_length);
314:
315: // unsuccessful finish
316: }
317: PEND_CATCH
318:
319: /*
320: * We did what we wanted to do, so tell the rest of the server we
321: * succeeded.
322: */
323: return OK;
324: }
325:
326: /*--------------------------------------------------------------------------*/
327: /* */
328: /* Now let's declare routines for each of the callback phase in order. */
329: /* (That's the order in which they're listed in the callback list, *not */
330: /* the order in which the server calls them! See the command_rec */
331: /* declaration near the bottom of this file.) Note that these may be */
332: /* called for situations that don't relate primarily to our function - in */
333: /* other words, the fixup handler shouldn't assume that the request has */
334: /* to do with "example" stuff. */
335: /* */
336: /* With the exception of the content handler, all of our routines will be */
337: /* called for each request, unless an earlier handler from another module */
338: /* aborted the sequence. */
339: /* */
340: /* Handlers that are declared as "int" can return the following: */
341: /* */
342: /* OK Handler accepted the request and did its thing with it. */
343: /* DECLINED Handler took no action. */
344: /* HTTP_mumble Handler looked at request and found it wanting. */
345: /* */
346: /* What the server does after calling a module handler depends upon the */
347: /* handler's return value. In all cases, if the handler returns */
348: /* DECLINED, the server will continue to the next module with an handler */
349: /* for the current phase. However, if the handler return a non-OK, */
350: /* non-DECLINED status, the server aborts the request right there. If */
351: /* the handler returns OK, the server's next action is phase-specific; */
352: /* see the individual handler comments below for details. */
353: /* */
354: /*--------------------------------------------------------------------------*/
355: /*
356: * This function is called during server initialisation. Any information
357: * that needs to be recorded must be in static cells, since there's no
358: * configuration record.
359: *
360: * There is no return value.
361: */
362:
363: static void setup_module_cells() {
364: static bool globals_inited=false;
365: if(globals_inited)
366: return;
367: globals_inited=true;
368:
369: /*
370: * allocate our module-private pool.
371: */
372: static Pool pool(ap_make_sub_pool(NULL)); // global pool
373: PTRY {
374: // init socks
375: init_socks(pool);
1.2 ! parser 376:
! 377: #ifdef XML
! 378: /**
! 379: * Initialize Xerces and Xalan.
! 380: *
! 381: * Should be called only once per process before making
! 382: * any other API calls.
! 383: */
! 384: XalanInitialize();
! 385: pool.register_cleanup(callXalanTerminate, 0);
! 386: #endif
1.1 parser 387:
388: // init global classes
389: init_methoded_array(pool);
390: // init global variables
391: pa_globals_init(pool);
392: } PCATCH(e) { // global problem
393: ap_log_error(APLOG_MARK, APLOG_EMERG, 0,
394: "setup_module_cells failed: ", e.comment());
395: exit(1);
396: }
397: PEND_CATCH
398: }
399:
400: static void parser_server_init(server_rec *s, pool *p) {
401: #if MODULE_MAGIC_NUMBER >= 19980527
402: ap_add_version_component("Parser/"PARSER_VERSION);
403: #endif
404:
405: /*
406: * Set up any module cells that ought to be initialised.
407: */
408: setup_module_cells();
409: }
410:
411: /*
412: * This function gets called to create a per-directory configuration
413: * record. This will be called for the "default" server environment, and for
414: * each directory for which the parser finds any of our directives applicable.
415: * If a directory doesn't have any of our directives involved (i.e., they
416: * aren't in the .htaccess file, or a <Location>, <Directory>, or related
417: * block), this routine will *not* be called - the configuration for the
418: * closest ancestor is used.
419: *
420: * The return value is a pointer to the created module-specific
421: * structure.
422: */
423: static void *parser_create_dir_config(pool *p, char *dirspec) {
424: /*
425: * Allocate the space for our record from the pool supplied.
426: */
427: Parser_module_config *cfg=
428: (Parser_module_config *) ap_pcalloc(p, sizeof(Parser_module_config));
429: /*
430: * Now fill in the defaults. If there are any `parent' configuration
431: * records, they'll get merged as part of a separate callback.
432: */
433: cfg->parser_root_config_filespec = 0;
434: cfg->parser_site_config_filespec = 0;
435: return (void *) cfg;
436: }
437:
438: /*
439: * This function gets called to merge two per-directory configuration
440: * records. This is typically done to cope with things like .htaccess files
441: * or <Location> directives for directories that are beneath one for which a
442: * configuration record was already created. The routine has the
443: * responsibility of creating a new record and merging the contents of the
444: * other two into it appropriately. If the module doesn't declare a merge
445: * routine, the record for the closest ancestor location (that has one) is
446: * used exclusively.
447: *
448: * The routine MUST NOT modify any of its arguments!
449: *
450: * The return value is a pointer to the created module-specific structure
451: * containing the merged values.
452: */
453: static void *parser_merge_dir_config(pool *p, void *parent_conf,
454: void *newloc_conf) {
455: Parser_module_config *merged_config =
456: (Parser_module_config *) ap_pcalloc(p, sizeof(Parser_module_config));
457: Parser_module_config *pconf = (Parser_module_config *) parent_conf;
458: Parser_module_config *nconf = (Parser_module_config *) newloc_conf;
459:
460: // always from parent
461: merged_config->parser_root_config_filespec = ap_pstrdup(p, pconf->parser_root_config_filespec);
462: /*
463: * Some things get copied directly from the more-specific record, rather
464: * than getting merged.
465: */
466: merged_config->parser_site_config_filespec = ap_pstrdup(p, nconf->parser_site_config_filespec?
467: nconf->parser_site_config_filespec:pconf->parser_site_config_filespec);
468:
469: return (void *) merged_config;
470: }
471:
472: /*
473: * This function gets called to create a per-server configuration
474: * record. It will always be called for the "default" server.
475: *
476: * The return value is a pointer to the created module-specific
477: * structure.
478: */
479: static void *parser_create_server_config(pool *p, server_rec *s) {
480: /*
481: * As with the parser_create_dir_config() reoutine, we allocate and fill
482: * in an empty record.
483: */
484: Parser_module_config *cfg=
485: (Parser_module_config *) ap_pcalloc(p, sizeof(Parser_module_config));
486:
487: cfg->parser_root_config_filespec = 0;
488: cfg->parser_site_config_filespec = 0;
489:
490: return (void *) cfg;
491: }
492:
493: /*
494: * This function gets called to merge two per-server configuration
495: * records. This is typically done to cope with things like virtual hosts and
496: * the default server configuration The routine has the responsibility of
497: * creating a new record and merging the contents of the other two into it
498: * appropriately. If the module doesn't declare a merge routine, the more
499: * specific existing record is used exclusively.
500: *
501: * The routine MUST NOT modify any of its arguments!
502: *
503: * The return value is a pointer to the created module-specific structure
504: * containing the merged values.
505: */
506: static void *parser_merge_server_config(pool *p, void *server1_conf,
507: void *server2_conf)
508: {
509:
510: Parser_module_config *merged_config =
511: (Parser_module_config *) ap_pcalloc(p, sizeof(Parser_module_config));
512: Parser_module_config *s1conf = (Parser_module_config *) server1_conf;
513: Parser_module_config *s2conf = (Parser_module_config *) server2_conf;
514:
515: /*
516: * Our inheritance rules are our own, and part of our module's semantics.
517: * Basically, just note whence we came.
518: */
519: merged_config->parser_root_config_filespec = ap_pstrdup(p, s2conf->parser_root_config_filespec?
520: s2conf->parser_root_config_filespec:s1conf->parser_root_config_filespec);
521: merged_config->parser_site_config_filespec = ap_pstrdup(p, s2conf->parser_site_config_filespec?
522: s2conf->parser_site_config_filespec:s1conf->parser_site_config_filespec);
523:
524: return (void *) merged_config;
525: }
526:
527: /*
528: * This routine gives our module an opportunity to translate the URI into an
529: * actual filename. If we don't do anything special, the server's default
530: * rules (Alias directives and the like) will continue to be followed.
531: *
532: * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no
533: * further modules are called for this phase.
534: */
535: static int parser_translate_handler(request_rec *r) {
536: Parser_module_config *cfg=our_dconfig(r);
537: return DECLINED;
538: }
539:
540: /*
541: * This routine is called to check the authentication information sent with
542: * the request (such as looking up the user in a database and verifying that
543: * the [encrypted] password sent matches the one in the database).
544: *
545: * The return value is OK, DECLINED, or some HTTP_mumble error (typically
546: * HTTP_UNAUTHORIZED). If we return OK, no other modules are given a chance
547: * at the request during this phase.
548: */
549: static int parser_check_user_id(request_rec *r) {
550: Parser_module_config *cfg=our_dconfig(r);
551: return DECLINED;
552: }
553:
554: /*
555: * This routine is called to check to see if the resource being requested
556: * requires authorisation.
557: *
558: * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no
559: * other modules are called during this phase.
560: *
561: * If *all* modules return DECLINED, the request is aborted with a server
562: * error.
563: */
564: static int parser_auth_checker(request_rec *r) {
565: Parser_module_config *cfg=our_dconfig(r);
566: return DECLINED;
567: }
568:
569: /*
570: * This routine is called to check for any module-specific restrictions placed
571: * upon the requested resource. (See the mod_access module for an example.)
572: *
573: * The return value is OK, DECLINED, or HTTP_mumble. All modules with an
574: * handler for this phase are called regardless of whether their predecessors
575: * return OK or DECLINED. The first one to return any other status, however,
576: * will abort the sequence (and the request) as usual.
577: */
578: static int parser_access_checker(request_rec *r) {
579:
580: Parser_module_config *cfg=our_dconfig(r);
581: return DECLINED;
582: }
583:
584: /*--------------------------------------------------------------------------*/
585: /* */
586: /* All of the routines have been declared now. Here's the list of */
587: /* directives specific to our module, and information about where they */
588: /* may appear and how the command parser should pass them to us for */
589: /* processing. Note that care must be taken to ensure that there are NO */
590: /* collisions of directive names between modules. */
591: /* */
592: /*--------------------------------------------------------------------------*/
593: /*
594: * List of directives specific to our module.
595: */
596: static const command_rec parser_cmds[] =
597: {
598: {
599: DEBUG_PREFIX"ParserRootConfig", /* directive name */
600: (const char *(*)(void))((void *)cmd_parser_config), // config action routine
601: (void*)true, /* argument to include in call */
602: (int)(ACCESS_CONF|RSRC_CONF), /* where available */
603: TAKE1, /* arguments */
604: "Parser root config filespec (Admin)" // directive description
605: },
606: {
607: DEBUG_PREFIX"ParserSiteConfig", /* directive name */
608: (const char *(*)(void))((void *)cmd_parser_config), // config action routine
609: (void*)false, /* argument to include in call */
610: (int)(OR_OPTIONS), /* where available */
611: TAKE1, /* arguments */
612: "Parser site config filespec" // directive description
613: },
614: {NULL}
615: };
616:
617: /*--------------------------------------------------------------------------*/
618: /* */
619: /* Now the list of content handlers available from this module. */
620: /* */
621: /*--------------------------------------------------------------------------*/
622: /*
623: * List of content handlers our module supplies. Each handler is defined by
624: * two parts: a name by which it can be referenced (such as by
625: * {Add,Set}Handler), and the actual routine name. The list is terminated by
626: * a NULL block, since it can be of variable length.
627: *
628: * Note that content-handlers are invoked on a most-specific to least-specific
629: * basis; that is, a handler that is declared for "text/plain" will be
630: * invoked before one that was declared for "text / *". Note also that
631: * if a content-handler returns anything except DECLINED, no other
632: * content-handlers will be called.
633: */
634: static const handler_rec parser_handlers[] =
635: {
636: {DEBUG_PREFIX"parser3-handler", parser_handler},
637: {NULL}
638: };
639:
640: /*--------------------------------------------------------------------------*/
641: /* */
642: /* Finally, the list of callback routines and data structures that */
643: /* provide the hooks into our module from the other parts of the server. */
644: /* */
645: /*--------------------------------------------------------------------------*/
646: /*
647: * Module definition for configuration. If a particular callback is not
648: * needed, replace its routine name below with the word NULL.
649: *
650: * The number in brackets indicates the order in which the routine is called
651: * during request processing. Note that not all routines are necessarily
652: * called (such as if a resource doesn't have access restrictions).
653: */
654: module MODULE_VAR_EXPORT PARSER3_MODULE =
655: {
656: STANDARD_MODULE_STUFF,
657: parser_server_init, /* module initializer */
658: parser_create_dir_config, /* per-directory config creator */
659: parser_merge_dir_config, /* dir config merger */
660: parser_create_server_config, /* server config creator */
661: parser_merge_server_config, /* server config merger */
662: parser_cmds, /* command table */
663: parser_handlers, /* [9] list of handlers */
664: parser_translate_handler, /* [2] filename-to-URI translation */
665: parser_check_user_id, /* [5] check/validate user_id */
666: parser_auth_checker, /* [6] check user_id is valid *here* */
667: parser_access_checker, /* [4] check access by host address */
668: 0, /* [7] MIME type checker/setter */
669: 0, /* [8] fixups */
670: 0 /* [10] logger */
671: };
672:
673: #if defined(_MSC_VER)
674: # define APACHE_WIN32_SRC "/parser3project/win32apache13/src"
675: # ifdef _DEBUG
676: # pragma comment(lib, APACHE_WIN32_SRC "/CoreD/ApacheCore.lib")
677: # else
678: # pragma comment(lib, APACHE_WIN32_SRC "/CoreR/ApacheCore.lib")
679: # endif
680: #endif
E-mail: