1/*	UNCUserNotification.c
2	Copyright 2000, Apple Computer, Inc. All rights reserved.
3*/
4
5#if 0
6
7#include "UNCUserNotification.h"
8#include <stdlib.h>
9#include <unistd.h>
10#include <stdio.h>
11#include <mach/mach.h>
12#include <mach/error.h>
13#include <servers/bootstrap.h>
14#include <limits.h>
15#include <errno.h>
16
17#define MAX_STRING_LENGTH PATH_MAX
18#define MAX_STRING_COUNT 16
19#define MAX_PORT_NAME_LENGTH 63
20#define NOTIFICATION_PORT_NAME "com.apple.UNCUserNotification"
21#define NOTIFICATION_PORT_NAME_OLD "UNCUserNotification"
22#define NOTIFICATION_PORT_NAME_SUFFIX ".session."
23#define MESSAGE_TIMEOUT 100
24
25enum {
26    kUNCCancelFlag = (1 << 3),
27    kUNCUpdateFlag = (1 << 4)
28};
29
30/* backward compatibility */
31extern const char kUNCTextFieldLabelsKey[];
32extern const char kUNCCheckBoxLabelsKey[];
33
34/* forward compatibility */
35extern const char kUNCSessionIDKey[];
36
37const char kUNCTokenKey[] = "Token";
38const char kUNCTimeoutKey[] = "Timeout";
39const char kUNCAlertSourceKey[] = "AlertSource";
40const char kUNCIconPathKey[] = "IconPath";
41const char kUNCSoundPathKey[] = "SoundPath";
42const char kUNCLocalizationPathKey[] = "LocalizationPath";
43const char kUNCAlertHeaderKey[] = "AlertHeader";
44const char kUNCAlertMessageKey[] = "AlertMessage";
45const char kUNCDefaultButtonTitleKey[] = "DefaultButtonTitle";
46const char kUNCAlternateButtonTitleKey[] = "AlternateButtonTitle";
47const char kUNCOtherButtonTitleKey[] = "OtherButtonTitle";
48const char kUNCProgressIndicatorValueKey[] = "ProgressIndicatorValue";
49const char kUNCSessionIDKey[] = "SessionID";
50const char kUNCPopUpTitlesKey[] = "PopUpTitles";
51const char kUNCTextFieldTitlesKey[] = "TextFieldTitles";
52const char kUNCTextFieldLabelsKey[] = "TextFieldTitles";
53const char kUNCCheckBoxTitlesKey[] = "CheckBoxTitles";
54const char kUNCCheckBoxLabelsKey[] = "CheckBoxTitles";
55const char kUNCTextFieldValuesKey[] = "TextFieldValues";
56const char kUNCPopUpSelectionKey[] = "PopUpSelection";
57
58static const char *kUNCXMLPrologue = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
59static const char *kUNCDoctypePrologue = "<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">";
60static const char *kUNCPlistPrologue = "<plist version=\"0.9\">";
61static const char *kUNCDictionaryPrologue = "<dict>";
62static const char *kUNCDictionaryEpilogue = "</dict>";
63static const char *kUNCArrayPrologue = "<array>";
64static const char *kUNCArrayEpilogue = "</array>";
65static const char *kUNCKeyPrologue = "<key>";
66static const char *kUNCKeyEpilogue = "</key>";
67static const char *kUNCStringPrologue = "<string>";
68static const char *kUNCStringEpilogue = "</string>";
69static const char *kUNCIntegerPrologue = "<integer>";
70static const char *kUNCIntegerEpilogue = "</integer>";
71static const char *kUNCPlistEpilogue = "</plist>";
72
73struct __UNCUserNotification {
74    mach_port_t _replyPort;
75    int _token;
76    double _timeout;
77    unsigned long _requestFlags;
78    unsigned long _responseFlags;
79    char *_sessionID;
80    mach_msg_base_t *_response;
81    char **_responseContents;
82};
83
84static unsigned long UNCPackContents(char *buffer, const char **contents, int token, int itimeout, char *source) {
85    // if buffer is non-null, write XML into it; if buffer is null, return required size
86    // should consider escape sequences
87    unsigned long keyLen, valLen;
88    const char **p = contents, *key = NULL, *val = NULL, *nextKey = NULL, *previousKey = NULL;
89    char *b = buffer, tokenString[64], timeoutString[64];
90
91    snprintf(tokenString, sizeof(tokenString)-1, "%d", token); tokenString[sizeof(tokenString)-1] = '\0';
92    snprintf(timeoutString, sizeof(timeoutString)-1, "%d", itimeout); timeoutString[sizeof(timeoutString)-1] = '\0';
93#define APPEND(x) {if (buffer) strcpy(b, x); b += strlen(x);}
94#define APPENDN(x, n) {if (buffer) strncpy(b, x, n); b += n;}
95    APPEND(kUNCXMLPrologue); APPEND("\n");
96    APPEND(kUNCDoctypePrologue); APPEND("\n");
97    APPEND(kUNCPlistPrologue); APPEND("\n");
98    APPEND(kUNCDictionaryPrologue); APPEND("\n");
99    APPEND("\t"); APPEND(kUNCKeyPrologue); APPEND(kUNCTokenKey); APPEND(kUNCKeyEpilogue); APPEND("\n");
100    APPEND("\t"); APPEND(kUNCIntegerPrologue); APPEND(tokenString); APPEND(kUNCIntegerEpilogue); APPEND("\n");
101    APPEND("\t"); APPEND(kUNCKeyPrologue); APPEND(kUNCTimeoutKey); APPEND(kUNCKeyEpilogue); APPEND("\n");
102    APPEND("\t"); APPEND(kUNCIntegerPrologue); APPEND(timeoutString); APPEND(kUNCIntegerEpilogue); APPEND("\n");
103    APPEND("\t"); APPEND(kUNCKeyPrologue); APPEND(kUNCAlertSourceKey); APPEND(kUNCKeyEpilogue); APPEND("\n");
104    APPEND("\t"); APPEND(kUNCStringPrologue); APPEND(source); APPEND(kUNCStringEpilogue); APPEND("\n");
105
106    while (p && *p) {
107        key = *p++; val = *p++;
108        if (val) {
109            nextKey = *p;
110            keyLen = strlen(key); if (keyLen > MAX_STRING_LENGTH) keyLen = MAX_STRING_LENGTH;
111            valLen = strlen(val); if (valLen > MAX_STRING_LENGTH) valLen = MAX_STRING_LENGTH;
112            if (key != previousKey) {
113                APPEND("\t"); APPEND(kUNCKeyPrologue);
114                APPENDN(key, keyLen);
115                APPEND(kUNCKeyEpilogue); APPEND("\n");
116            }
117            if ((key != previousKey) && (key == nextKey)) {
118                APPEND("\t"); APPEND(kUNCArrayPrologue); APPEND("\n");
119            }
120            if ((key == previousKey) || (key == nextKey)) APPEND("\t");
121            APPEND("\t"); APPEND(kUNCStringPrologue);
122            APPENDN(val, valLen);
123            APPEND(kUNCStringEpilogue); APPEND("\n");
124            if ((key == previousKey) && (key != nextKey)) {
125                APPEND("\t"); APPEND(kUNCArrayEpilogue); APPEND("\n");
126            }
127            previousKey = key;
128        }
129    }
130
131    APPEND(kUNCDictionaryEpilogue); APPEND("\n");
132    APPEND(kUNCPlistEpilogue); APPEND("\n");
133#undef APPEND
134#undef APPENDN
135
136    return b - buffer;
137}
138
139static void convertEscapes(char *str) {
140    char *p = str, *q = str;
141    for (p = q = str; 0 != (*q = *p); p++, q++) {
142        if ('&' == *p) {
143            if ('g' == *(p+1) && 't' == *(p+2) && ';' == *(p+3)) {
144                *q = '>';
145                p += 3;
146            } else if ('l' == *(p+1) && 't' == *(p+2) && ';' == *(p+3)) {
147                *q = '<';
148                p += 3;
149            } else if ('a' == *(p+1) && 'm' == *(p+2) && 'p' == *(p+3) && ';' == *(p+4)) {
150                *q = '&';
151                p += 4;
152            }
153        }
154    }
155}
156
157static unsigned long UNCUnpackContents(char *buffer, char **contents) {
158    // if contents is non-null, unpack XML buffer into it; if contents is null, return required size
159    // if contents is non-null, as side effect, insert null string terminators in buffer and convert some escapes in place
160    char **p = contents, *key = NULL, *keyEnd = NULL, *previousKey = NULL, *value = NULL, *valueEnd = NULL, *b = buffer;
161
162    while (b) {
163        key = strstr(b, kUNCKeyPrologue); if (key) key += strlen(kUNCKeyPrologue);
164        value = strstr(b, kUNCStringPrologue); if (value) value += strlen(kUNCStringPrologue);
165        b = NULL;
166        if (key || (previousKey && value)) {
167            if (contents) *p = NULL;
168            if (0 != key && key < value) {
169                b = key;
170                keyEnd = strstr(b, kUNCKeyEpilogue);
171                if (keyEnd) {
172                    if (contents) {
173                        *p = key;
174                        *keyEnd = '\0';
175                    }
176                    previousKey = key;
177                    b = keyEnd + strlen(kUNCKeyEpilogue);
178                    value = strstr(b, kUNCStringPrologue); if (value) value += strlen(kUNCStringPrologue);
179                }
180            } else {
181                if (contents) *p = previousKey;
182            }
183            p++;
184
185            if (contents) *p = NULL;
186            if (0 != value) {
187                b = value;
188                valueEnd = strstr(b, kUNCStringEpilogue);
189                if (valueEnd) {
190                    if (contents) {
191                        *p = value;
192                        *valueEnd = '\0';
193                        convertEscapes(value);
194                    }
195                    b = valueEnd + strlen(kUNCStringEpilogue);
196                }
197            }
198            p++;
199        }
200    }
201    if (p > contents) {
202        if (contents) *p = NULL;
203        p++;
204    }
205
206    return p - contents;
207}
208
209static const char *UNCSessionIDForContents(const char **contents) {
210    const char **p = contents, *retval = NULL;
211
212    while (!retval && p && *p) {
213        if (0 == strcmp(*p++, kUNCSessionIDKey)) retval = *p;
214        p++;
215    }
216    return retval;
217}
218
219extern char ***_NSGetArgv(void);
220
221static int UNCSendRequest(const char *sessionID, mach_port_t replyPort, int token, double timeout, unsigned long flags, const char **contents) {
222    int retval = ERR_SUCCESS, itimeout = (timeout > 0.0 && timeout < INT_MAX) ? (int)timeout : 0;
223    mach_msg_base_t *msg = NULL;
224    mach_port_t bootstrapPort = MACH_PORT_NULL, serverPort = MACH_PORT_NULL;
225    unsigned long size;
226    char namebuffer[MAX_PORT_NAME_LENGTH + 1], oldnamebuffer[MAX_PORT_NAME_LENGTH + 1], *source = (*_NSGetArgv())[0], *p = source;
227
228    strcpy(namebuffer, NOTIFICATION_PORT_NAME);
229    strcpy(oldnamebuffer, NOTIFICATION_PORT_NAME_OLD);
230    if (sessionID) {
231        strcat(namebuffer, NOTIFICATION_PORT_NAME_SUFFIX);
232        strncat(namebuffer, sessionID, MAX_PORT_NAME_LENGTH - sizeof(NOTIFICATION_PORT_NAME) - sizeof(NOTIFICATION_PORT_NAME_SUFFIX));
233        namebuffer[MAX_PORT_NAME_LENGTH] = '\0';
234
235        strcat(oldnamebuffer, NOTIFICATION_PORT_NAME_SUFFIX);
236        strncat(oldnamebuffer, sessionID, MAX_PORT_NAME_LENGTH - sizeof(NOTIFICATION_PORT_NAME_OLD) - sizeof(NOTIFICATION_PORT_NAME_SUFFIX));
237        oldnamebuffer[MAX_PORT_NAME_LENGTH] = '\0';
238    }
239
240    retval = task_get_bootstrap_port(mach_task_self(), &bootstrapPort);
241    if (ERR_SUCCESS == retval && MACH_PORT_NULL != bootstrapPort) retval = bootstrap_look_up(bootstrapPort, namebuffer, &serverPort);
242    if (ERR_SUCCESS != retval || MACH_PORT_NULL == serverPort) retval = bootstrap_look_up(bootstrapPort, oldnamebuffer, &serverPort);
243    if (ERR_SUCCESS == retval && MACH_PORT_NULL != serverPort) {
244        while (*p) if ('/' == *p++) source = p;
245        size = sizeof(mach_msg_base_t) + ((UNCPackContents(NULL, contents, token, itimeout, source) + 3) & (~0x3));
246        msg = (mach_msg_base_t *)malloc(size);
247        if (msg) {
248            bzero(msg, size);
249            msg->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
250            msg->header.msgh_size = size;
251            msg->header.msgh_remote_port = serverPort;
252            msg->header.msgh_local_port = replyPort;
253            msg->header.msgh_id = flags;
254            msg->body.msgh_descriptor_count = 0;
255            UNCPackContents((char *)msg + sizeof(mach_msg_base_t), contents, token, itimeout, source);
256            retval = mach_msg((mach_msg_header_t *)msg, MACH_SEND_MSG|MACH_SEND_TIMEOUT, size, 0, MACH_PORT_NULL, MESSAGE_TIMEOUT, MACH_PORT_NULL);
257            free(msg);
258        } else {
259            retval = unix_err(ENOMEM);
260        }
261    }
262    return retval;
263}
264
265extern UNCUserNotificationRef UNCUserNotificationCreate(double timeout, unsigned long flags, int *error, const char **contents) {
266    UNCUserNotificationRef userNotification = NULL;
267    int retval = ERR_SUCCESS;
268    static unsigned short tokenCounter = 0;
269    int token = ((getpid()<<16) | (tokenCounter++));
270    const char *sessionID = UNCSessionIDForContents(contents);
271    mach_port_t replyPort = MACH_PORT_NULL;
272    size_t idlen;
273
274    retval = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &replyPort);
275    if (ERR_SUCCESS == retval && MACH_PORT_NULL != replyPort) retval = UNCSendRequest(sessionID, replyPort, token, timeout, flags, contents);
276    if (ERR_SUCCESS == retval) {
277        userNotification = (UNCUserNotificationRef)malloc(sizeof(struct __UNCUserNotification));
278        if (userNotification) {
279            bzero(userNotification, sizeof(struct __UNCUserNotification));
280            userNotification->_replyPort = replyPort;
281            userNotification->_token = token;
282            userNotification->_timeout = timeout;
283            userNotification->_requestFlags = flags;
284            userNotification->_responseFlags = 0;
285            userNotification->_sessionID = NULL;
286            userNotification->_response = NULL;
287            userNotification->_responseContents = NULL;
288            if (sessionID) {
289                idlen = strlen(sessionID);
290                if (idlen > MAX_PORT_NAME_LENGTH) idlen = MAX_PORT_NAME_LENGTH;
291                userNotification->_sessionID = (char *)malloc(idlen + 1);
292                strncpy(userNotification->_sessionID, sessionID, idlen);
293                userNotification->_sessionID[idlen] = '\0';
294            }
295        } else {
296            retval = unix_err(ENOMEM);
297        }
298    }
299    if (ERR_SUCCESS != retval && MACH_PORT_NULL != replyPort) mach_port_destroy(mach_task_self(), replyPort);
300    if (error) *error = retval;
301    return userNotification;
302}
303
304extern int UNCUserNotificationReceiveResponse(UNCUserNotificationRef userNotification, double timeout, unsigned long *responseFlags) {
305    int retval = ERR_SUCCESS;
306    mach_msg_timeout_t msgtime = (timeout > 0.0 && 1000.0 * timeout < INT_MAX) ? (mach_msg_timeout_t)(1000.0 * timeout) : 0;
307    mach_msg_base_t *msg = NULL;
308    unsigned long size = MAX_STRING_COUNT * MAX_STRING_LENGTH, contentSize = 0;
309
310    if (userNotification && MACH_PORT_NULL != userNotification->_replyPort) {
311        msg = (mach_msg_base_t *)malloc(size);
312        if (msg) {
313            bzero(msg, size);
314            msg->header.msgh_size = size;
315            if (msgtime > 0) {
316                retval = mach_msg((mach_msg_header_t *)msg, MACH_RCV_MSG|MACH_RCV_TIMEOUT, 0, size, userNotification->_replyPort, msgtime, MACH_PORT_NULL);
317            } else {
318                retval = mach_msg((mach_msg_header_t *)msg, MACH_RCV_MSG, 0, size, userNotification->_replyPort, 0, MACH_PORT_NULL);
319            }
320            if (ERR_SUCCESS == retval) {
321                if (responseFlags) *responseFlags = msg->header.msgh_id;
322                userNotification->_response = msg;
323                contentSize = UNCUnpackContents((char *)msg + sizeof(mach_msg_base_t), NULL);
324                if (0 < contentSize) {
325                    userNotification->_responseContents = (char **)malloc(contentSize * sizeof(char **));
326                    if (userNotification->_responseContents) {
327                        UNCUnpackContents((char *)msg + sizeof(mach_msg_base_t), userNotification->_responseContents);
328                    }
329                }
330                mach_port_destroy(mach_task_self(), userNotification->_replyPort);
331                userNotification->_replyPort = MACH_PORT_NULL;
332            } else {
333                free(msg);
334            }
335        } else {
336            retval = unix_err(ENOMEM);
337        }
338    }
339    return retval;
340}
341
342extern const char *UNCUserNotificationGetResponseValue(UNCUserNotificationRef userNotification, const char *key, unsigned long index) {
343    char **p, *retval = NULL;
344    if (userNotification && userNotification->_responseContents && key) {
345        p = userNotification->_responseContents;
346        while (!retval && *p) {
347            if (0 == strcmp(*p++, key) && 0 == index--) retval = *p;
348            p++;
349        }
350    }
351    return retval;
352}
353
354extern const char **UNCUserNotificationGetResponseContents(UNCUserNotificationRef userNotification) {
355    return userNotification ? (const char **)(userNotification->_responseContents) : NULL;
356}
357
358extern int UNCUserNotificationUpdate(UNCUserNotificationRef userNotification, double timeout, unsigned long flags, const char **contents) {
359    int retval = ERR_SUCCESS;
360    if (userNotification && MACH_PORT_NULL != userNotification->_replyPort) {
361        retval = UNCSendRequest(userNotification->_sessionID, userNotification->_replyPort, userNotification->_token, timeout, flags|kUNCUpdateFlag, contents);
362    }
363    return retval;
364}
365
366extern int UNCUserNotificationCancel(UNCUserNotificationRef userNotification) {
367    int retval = ERR_SUCCESS;
368    if (userNotification && MACH_PORT_NULL != userNotification->_replyPort) {
369        retval = UNCSendRequest(userNotification->_sessionID, userNotification->_replyPort, userNotification->_token, 0, kUNCCancelFlag, NULL);
370    }
371    return retval;
372}
373
374extern void UNCUserNotificationFree(UNCUserNotificationRef userNotification) {
375    if (userNotification) {
376        if (MACH_PORT_NULL != userNotification->_replyPort) mach_port_destroy(mach_task_self(), userNotification->_replyPort);
377        if (userNotification->_sessionID) free(userNotification->_sessionID);
378        if (userNotification->_responseContents) free(userNotification->_responseContents);
379        if (userNotification->_response) free(userNotification->_response);
380        free(userNotification);
381    }
382}
383
384extern int UNCDisplayNotice(double timeout, unsigned long flags, const char *iconPath, const char *soundPath, const char *localizationPath, const char *alertHeader, const char *alertMessage, const char *defaultButtonTitle) {
385    UNCUserNotificationRef userNotification;
386    int retval = ERR_SUCCESS;
387    const char *contents[13];
388    unsigned long i = 0;
389    if (iconPath) {contents[i++] = kUNCIconPathKey; contents[i++] = iconPath;}
390    if (soundPath) {contents[i++] = kUNCSoundPathKey; contents[i++] = soundPath;}
391    if (localizationPath) {contents[i++] = kUNCLocalizationPathKey; contents[i++] = localizationPath;}
392    if (alertHeader) {contents[i++] = kUNCAlertHeaderKey; contents[i++] = alertHeader;}
393    if (alertMessage) {contents[i++] = kUNCAlertMessageKey; contents[i++] = alertMessage;}
394    if (defaultButtonTitle) {contents[i++] = kUNCDefaultButtonTitleKey; contents[i++] = defaultButtonTitle;}
395    contents[i++] = NULL;
396    userNotification = UNCUserNotificationCreate(timeout, flags, &retval, contents);
397    if (userNotification) UNCUserNotificationFree(userNotification);
398    return retval;
399}
400
401extern int UNCDisplayAlert(double timeout, unsigned long flags, const char *iconPath, const char *soundPath, const char *localizationPath, const char *alertHeader, const char *alertMessage, const char *defaultButtonTitle, const char *alternateButtonTitle, const char *otherButtonTitle, unsigned long *responseFlags) {
402    UNCUserNotificationRef userNotification;
403    int retval = ERR_SUCCESS;
404    const char *contents[17];
405    unsigned long i = 0;
406    if (iconPath) {contents[i++] = kUNCIconPathKey; contents[i++] = iconPath;}
407    if (soundPath) {contents[i++] = kUNCSoundPathKey; contents[i++] = soundPath;}
408    if (localizationPath) {contents[i++] = kUNCLocalizationPathKey; contents[i++] = localizationPath;}
409    if (alertHeader) {contents[i++] = kUNCAlertHeaderKey; contents[i++] = alertHeader;}
410    if (alertMessage) {contents[i++] = kUNCAlertMessageKey; contents[i++] = alertMessage;}
411    if (defaultButtonTitle) {contents[i++] = kUNCDefaultButtonTitleKey; contents[i++] = defaultButtonTitle;}
412    if (alternateButtonTitle) {contents[i++] = kUNCAlternateButtonTitleKey; contents[i++] = alternateButtonTitle;}
413    if (otherButtonTitle) {contents[i++] = kUNCOtherButtonTitleKey; contents[i++] = otherButtonTitle;}
414    contents[i++] = NULL;
415    userNotification = UNCUserNotificationCreate(timeout, flags, &retval, contents);
416    if (userNotification) {
417        retval = UNCUserNotificationReceiveResponse(userNotification, timeout, responseFlags);
418        UNCUserNotificationFree(userNotification);
419    }
420    return retval;
421}
422
423#endif
424
425