mailto agent This W3A agent handles GET requests for a URL of the form mailto:e-mail-address. Of course, this a rather special type of URL, since it doesn't designate a document, but rather instructs a WWW browser to open an editor, let the user type text into it, and then send the result by mail to the indicated address. This agent handles the request in the same manner: it opens a new popup window with a text editor (in the future an HTML editor?) and when the user has finished typing, the agent uses [[sendmail]] to deliver the message to the address in the URL. The browser expects a document back from its agents, so this agent composes one, consisting of no more than a short text to the effect that the message has been sent. The user can see the e-mail address above the text that he types and he can even change it. The agent uses the e-mail address in the URL as no more than a default value. As an experiment, this agent also recognizes some experimental request types other than GET, viz. SUBSCRIBE and UNSUBSCRIBE. The agent will try to do its best to interpret the e-mail address as the name of a mailing list and compose a message to (un)subscribe the user to that list. Since there are a number of different types of mailing lists, the agent can do no more than guess, helped by the user, what type of list server sits behind it. Bert Bos , 24 Nov 1994 <<*>>= /* Copyright NBBI, Den Haag, 1994 */ #include #include #include #include #include #include #include #include #include #include #include #define REPLY_NONE 1 #define SUBSCRIBE_METHOD 901 #define UNSUBSCRIBE_METHOD 902 EXPORTDEF(SUBSCRIBE_METHOD) EXPORTDEF(UNSUBSCRIBE_METHOD) #define MAXREPLYSIZE 512 #ifndef MAILCMD #define MAILCMD "mailx -s '%s' %s" /* #define MAILCMD "elm -s %s %s" */ #endif #define min(a, b) ((a) < (b) ? (a) : (b)) typedef struct { char *address; char reply_document[MAXREPLYSIZE]; int reply_offset, reply_size; int flags; int fd; /* Other end of pipe */ Widget to, subject, message; } MailToInfo; static MailToInfo *info[FD_SETSIZE]; @ The [[send_cb]] callback is invoked when the user presses the `Send' button. It passes the typed text to [[MAILCMD]] for sending. The window is closed and a message is created in [[info[fd]->reply_document]], which will be read by the browser and displayed. A byte is put into the pipe [[fd]], to notify the browser (which may be waiting with [[select(2)]] until the pipe becomes readable), that [[readMailTo]] can now be called. <<*>>= static void send_cb(Widget w, XtPointer client_data, XtPointer call_data) { char *subject, mailcmd[1024], *text; int fd = (int) client_data; FILE *f; enter("send_cb\n"); /* Check that the argument is valid */ assert(fd > 2 && info[fd] != NULL); /* Check again, in a different way */ assert(info[fd]->reply_size >= 0 && info[fd]->reply_offset >= 0); assert(XtIsSubclass(XtParent(XtParent(w)), shellWidgetClass)); /* Pop down the dialog, make pipe fd ready for reading */ XtPopdown(XtParent(XtParent(w))); write(info[fd]->fd, "", 1); /* Get text from XmText widget, then pipe it to mail */ subject = XmTextFieldGetString(info[fd]->subject); dispose(info[fd]->address); info[fd]->address = XmTextFieldGetString(info[fd]->to); sprintf(mailcmd, MAILCMD, subject, info[fd]->address); dispose(subject); debug("mailcmd=%s\n", mailcmd); if (! (f = popen(mailcmd, "w"))) { info[fd]->reply_size = sprintf (info[fd]->reply_document, "Mail command failed:\n%s", mailcmd); } else { text = XmTextGetString(info[fd]->message); fputs(text, f); /* Send it */ dispose(text); (void) pclose(f); info[fd]->reply_size = sprintf (info[fd]->reply_document, "Message has been sent to %s", info[fd]->address); } leave("send_cb\n"); } @ The [[cancel_cb]] callback is invoked when the user presses the `Cancel' button. The window is closed and a message is created in [[info[fd]->reply_document]], which will be read by the browser and displayed. <<*>>= static void cancel_cb(Widget w, XtPointer client_data, XtPointer call_data) { int fd = (int) client_data; /* Pop down the dialog, make pipe fd ready for reading */ XtPopdown(XtParent(XtParent(w))); write(info[fd]->fd, "", 1); info[fd]->reply_size = sprintf (info[fd]->reply_document, "Message has not been sent."); } @ The [[handle_normal]] function is called by [[openMailTo]]. It pops up an editor where the user can enter a mail message and it installs a number of callbacks that handle the user's actions. One of the callbacks is attached to the `send' button. It causes the window to disappear and the message to be handed to [[sendmail]] for processing. It also creates a short text describing the success of the operation, which is stored in [[info[fd]->reply_document]]. <<*>>= static int handle_normal(int fd) { Widget shell, form, to_label, subject_label, send, cancel; /* The popup window */ shell = XtVaCreatePopupShell ("mail-compose", transientShellWidgetClass, W3Atoplevel(), NULL); /* The form inside the popup shell */ form = XtVaCreateManagedWidget ("form", xmFormWidgetClass, shell, XmNmarginHeight, 10, XmNmarginWidth, 10, XmNhorizontalSpacing, 10, XmNverticalSpacing, 10, NULL); /* Children of form */ to_label = XtVaCreateManagedWidget ("to_label", xmLabelGadgetClass, form, XtVaTypedArg, XmNlabelString, XtRString, "To:", 4, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, NULL); info[fd]->to = XtVaCreateManagedWidget ("to", xmTextFieldWidgetClass, form, XmNvalue, info[fd]->address, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, to_label, XmNrightAttachment, XmATTACH_FORM, NULL); subject_label = XtVaCreateManagedWidget ("subject_label", xmLabelGadgetClass, form, XtVaTypedArg, XmNlabelString, XtRString, "Subject:", 9, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, info[fd]->to, XmNleftAttachment, XmATTACH_FORM, NULL); info[fd]->subject = XtVaCreateManagedWidget ("subject", xmTextFieldWidgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, info[fd]->to, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, subject_label, XmNrightAttachment, XmATTACH_FORM, NULL); send = XtVaCreateManagedWidget ("send", xmPushButtonGadgetClass, form, XtVaTypedArg, XmNlabelString, XtRString, " Send it ", 10, XmNleftAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, NULL); cancel = XtVaCreateManagedWidget ("cancel", xmPushButtonGadgetClass, form, XtVaTypedArg, XmNlabelString, XtRString, " Cancel ", 9, XmNrightAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, NULL); info[fd]->message = XtVaCreateManagedWidget ("message", xmTextWidgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, info[fd]->subject, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_WIDGET, XmNbottomWidget, send, XmNeditMode, XmMULTI_LINE_EDIT, NULL); /* Callbacks for the two buttons */ XtAddCallback(send, XmNactivateCallback, send_cb, (XtPointer) fd); XtAddCallback(cancel, XmNactivateCallback, cancel_cb, (XtPointer) fd); /* Pop up the shell window */ XtPopup(shell, XtGrabNonexclusive); /* Return success */ return fd; } @ <<*>>= static int handle_subscribe(int fd) { info[fd]->reply_size = sprintf (info[fd]->reply_document, "subscribe to mailing list function not implemented yet"); return fd; } static int handle_unsubscribe(int fd) { info[fd]->reply_size = sprintf (info[fd]->reply_document, "unsubscribe from mailing list function not implemented yet"); return fd; } @ The agent has no local variables that need to be initialized. It only returns the protocols that it can handle. <<*>>= EXPORT Bool initMailTo(char ***protocols, int *nrprotocols) { static char *protos[] = {"mailto"}; *protocols = protos; *nrprotocols = 1; return TRUE; } @ [[openMailTo]] is passed a URL and a method. The method must be GET or (experimentally) SUBSCRIBE (=901) or UNSUBSCRIBE (=902). The three methods are handled by three different functions: [[handle_normal]] opens a text editor and installs callbacks to handle user interaction; [[handle_subscribe]] composes a `subscribe' message to be send to a listserver and opens a dialog box for confirmation from the user; [[handle_unsubscribe]] works similarly, but creates an `unsubscribe' message. The function opens a pipe, because it needs to pass back a file descriptor that the browser can use in a call to [[select(2)]]. If the browser uses non-blocking I/O, the following will happen: 1. The pipe is initially ready for writing, so the browser will call [[peekMailTo]]. [[peekMailTo]] returns 1, meaning that the agent is done writing and has something to read. 2. The browser will then use [[select(2)]] again, until the pipe is ready for reading. The agent will make sure that there is nothing in the pipe until the user has pressed a button in the dialog. When that happens, the agent puts a byte into the pipe. 3.As soon as there is something in the pipe to read, the browser will call [[readMailTo]], which returns the reply document. <<*>>= EXPORT int openMailTo(const char *url, int method, int flags, const char *referer) { char *local_url, *proto, *address, *h; int fd[2]; local_url = newstring(url); proto = tokenize(local_url, ":", &h); if (! proto || ! eq(proto, "mailto")) { errno = EURL; return -1; /* Incorrect URL */ } address = tokenize(h, "", &h); if (! address) { errno = EURL; return -1; /* Missing address */ } if (method != GET_METHOD && method != SUBSCRIBE_METHOD && method != UNSUBSCRIBE_METHOD) { errno = EMETHOD; return -1; /* Incorrect method */ } if (pipe(fd) < 0) { return -1; /* Couldn't open pipe */ } new(info[fd[0]]); info[fd[0]]->address = newstring(address); info[fd[0]]->reply_document[0] = '\0'; info[fd[0]]->reply_offset = 0; info[fd[0]]->reply_size = 0; info[fd[0]]->flags = flags; info[fd[0]]->fd = fd[1]; switch (method) { case GET_METHOD: return handle_normal(fd[0]); case SUBSCRIBE_METHOD: return handle_subscribe(fd[0]); case UNSUBSCRIBE_METHOD: return handle_unsubscribe(fd[0]); } /* NOTREACHED */ } @ When the browser wants to know the type of the document that the MailTo agent is going to return, it calls [[infoMailTo]]. MailTo fills in the [[W3ADocumentInfo]] structure with a [[mime_type]] of `text/html', since that is what it will generate. When the reply document is not available, the function either blocks until it is, or returns with [[errno = EAGAIN]], depending on the [[flags]] passed to [[openMailTo]]. <<*>>= EXPORT Bool infoMailTo(int fd, W3ADocumentInfo *buf) { XtAppContext app_context; /* Check that the argument is valid */ assert(fd > 2 && info[fd] != NULL); /* Check again, in a different way */ assert(info[fd]->reply_size >= 0 && info[fd]->reply_offset >= 0); if (! info[fd]->reply_document[0] && (info[fd]->flags & O_NONBLOCK)) { /* No reply yet, but don't wait for it */ errno = EAGAIN; return FALSE; } else { app_context = XtWidgetToApplicationContext(W3Atoplevel()); while (info[fd]->reply_document[0] == '\0') { /* No reply yet, wait for it */ XtAppProcessEvent(app_context, XtIMAll); } dispose(buf->mime_type); /* Remove current values */ dispose(buf->mime_params); dispose(buf->title); #if REPLY_NONE buf->mime_type = newstring("none"); #else buf->mime_type = newstring("text/html"); #endif buf->title = newstring("Mailer result"); /* Title could be fancier.. */ buf->size = info[fd]->reply_size; return TRUE; } } EXPORT Bool doneMailTo(int fd) { return TRUE; /* Nothing to write */ } @ The [[peekMailTo]] function returns the number of bytes that are available for reading. The browser can call this before doing a [[readMailTo]]. The function returns 1, efectively saying to the browser that the agent is done writing and that the browser can now call [[infoMailTo]] or [[readMailTo]]. <<*>>= EXPORT int peekMailTo(int fd) { /* Check that the argument is valid */ assert(fd > 2 && info[fd] != NULL); /* Check again, in a different way */ assert(info[fd]->reply_size >= 0 && info[fd]->reply_offset >= 0); /* Check that peekMailTo is not called after readMailTo returned 0 */ assert(info[fd]->reply_document[0] == '\0' || info[fd]->reply_size > 0); return info[fd]->reply_document[0] ? 1 : 0; } @ [[readMailTo]] returns the generated document that tells the user that the message has been sent (or not). While the user is busy typing the message, or answering one of the dialog boxes, there is no reply to give back to the browser, so [[readMailTo]] either waits or returns immediately with -1 and [[errno]] set to [[EAGAIN]]. The [[flags]] argument to [[openMailTo]] determines what the function does: when the [[O_NONBLOCK]] flags has not been used, it waits, otherwise it returns -1. When it waits, it processes Xt events in a loop, that ends when one of the callback functions has created a reply document. When [[readMailTo]] returns text, it returns as many characters as it has, but no more than [[nbytes]]. When, after several calls, there are no more characters to return, it returns 0, indicating the end of the document. The browser should then call [[closeMailTo]]. <<*>>= EXPORT int readMailTo(int fd, char *buf, size_t nbytes) { XtAppContext app_context; int m; /* Check that the argument is valid */ assert(fd > 2 && info[fd] != NULL); /* Check again, in a different way */ assert(info[fd]->reply_size >= 0 && info[fd]->reply_offset >= 0); if (! info[fd]->reply_document[0] && (info[fd]->flags & O_NONBLOCK)) { /* No reply yet, but don't wait for it */ errno = EAGAIN; return -1; } if (! info[fd]->reply_document[0]) { /* No reply yet, wait for it */ app_context = XtWidgetToApplicationContext(W3Atoplevel()); while (info[fd]->reply_document[0] == '\0') XtAppProcessEvent(app_context, XtIMAll); } /* There is a non-empty reply, return the next part of it */ m = min(nbytes, info[fd]->reply_size); strncpy(buf, info[fd]->reply_document + info[fd]->reply_offset, m); info[fd]->reply_offset += m; info[fd]->reply_size -= m; return m; /* 0 means end of doc. */ } @ Since the MailTo agent doesn't support the PUT or POST methods, the [[writeMailTo]] function simply returns -1 and sets [[errno]] to [[EMETHOD]] (illegal method error). <<*>>= EXPORT int writeMailTo(int fd, const char *buf, size_t nbytes) { errno = EMETHOD; return -1; } @ The browser calls [[closeMailTo]] after the agent has indicated to the browser that it has reached the end of the `document', by returning 0 bytes in [[readMailtTo]]. The agent closes the temporary file that it opened in [[openMailTo]], and removes the [[MailToInfo]] block that it allocated. <<*>>= EXPORT Bool closeMailTo(int fd) { Bool result; assert(fd > 2 && info[fd] != NULL && info[fd]->address != NULL); result = close(fd) >= 0 && close(info[fd]->fd) >= 0; dispose(info[fd]->address); dispose(info[fd]); return result; } @ The MailTo agent doesn't support the DELETE method. <<*>>= EXPORT Bool deleteMailTo(const char *url) { errno = EMETHOD; return FALSE; }