indent -kr -i8 -brf -bbb -fnc -l132 -nce on all of webcit-classic
[citadel.git] / webcit / vcard_edit.c
1
2 /*
3  * Copyright (c) 1996-2021 by the citadel.org team
4  *
5  * This program is open source software.  You can redistribute it and/or
6  * modify it under the terms of the GNU General Public License, version 3.
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
14 #include "webcit.h"
15 #include "webserver.h"
16 #include "calendar.h"
17
18 CtxType CTX_VCARD = CTX_NONE;
19 CtxType CTX_VCARD_LIST = CTX_NONE;
20 CtxType CTX_VCARD_TYPE = CTX_NONE;
21 long VCEnumCounter = 0;
22
23 typedef enum _VCStrEnum {
24         FlatString,
25         StringCluster,
26         PhoneNumber,
27         EmailAddr,
28         Address,
29         Street,
30         Number,
31         AliasFor,
32         Base64BinaryAttachment,
33         UnKnown,
34         TerminateList
35 } VCStrEnum;
36 typedef struct vcField vcField;
37 struct vcField {
38         ConstStr STR;
39         VCStrEnum Type;
40         vcField *Sub;
41         long cval;
42         long parentCVal;
43         ConstStr Name;
44 };
45
46 vcField VCStr_Ns[] = {
47         { { HKEY("last")}, FlatString, NULL, 0, 0, { HKEY("Last Name")} },
48         { { HKEY("first")}, FlatString, NULL, 0, 0, { HKEY("First Name")} },
49         { { HKEY("middle")}, FlatString, NULL, 0, 0, { HKEY("Middle Name")} },
50         { { HKEY("prefix")}, FlatString, NULL, 0, 0, { HKEY("Prefix")} },
51         { { HKEY("suffix")}, FlatString, NULL, 0, 0, { HKEY("Suffix")} },
52         { { HKEY("")}, TerminateList, NULL, 0, 0, { HKEY("")} }
53 };
54
55 vcField VCStr_Addrs[] = {
56         { { HKEY("POBox")}, Address, NULL, 0, 0, { HKEY("PO box")} },
57         { { HKEY("extadr")}, Address, NULL, 0, 0, { HKEY("Address")} },
58         { { HKEY("street")}, Address, NULL, 0, 0, { HKEY("")} },
59         { { HKEY("city")}, Address, NULL, 0, 0, { HKEY("City")} },
60         { { HKEY("state")}, Address, NULL, 0, 0, { HKEY("State")} },
61         { { HKEY("zip")}, Address, NULL, 0, 0, { HKEY("ZIP code")} },
62         { { HKEY("country")}, Address, NULL, 0, 0, { HKEY("Country")} },
63         { { HKEY("")}, TerminateList, NULL, 0, 0, { HKEY("")} }
64 };
65
66 vcField VCStrE[] = {
67         { { HKEY("version")}, Number, NULL, 0, 0, { HKEY("")} },
68         { { HKEY("rev")}, Number, NULL, 0, 0, { HKEY("")} },
69         { { HKEY("label")}, FlatString, NULL, 0, 0, { HKEY("")} },
70         { { HKEY("uid")}, FlatString, NULL, 0, 0, { HKEY("")} },
71         { { HKEY("n")}, StringCluster, VCStr_Ns, 0, 0, { HKEY("")} },   /* N is name, but only if there's no FN already there */
72         { { HKEY("fn")}, FlatString, NULL, 0, 0, { HKEY("")} }, /* FN (full name) is a true 'display name' field */
73         { { HKEY("title")}, FlatString, NULL, 0, 0, { HKEY("Title:")} },
74         { { HKEY("org")}, FlatString, NULL, 0, 0, { HKEY("Organization:")} },   /* organization */
75         { { HKEY("email")}, EmailAddr, NULL, 0, 0, { HKEY("E-mail:")} },
76         { { HKEY("tel")}, PhoneNumber, NULL, 0, 0, { HKEY("Telephone:")} },
77         { { HKEY("adr")}, StringCluster, VCStr_Addrs, 0, 0, { HKEY("Address:")} },
78         { { HKEY("photo")}, Base64BinaryAttachment, NULL, 0, 0, { HKEY("Photo:")} },
79         { { HKEY("tel;home")}, PhoneNumber, NULL, 0, 0, { HKEY(" (home)")} },
80         { { HKEY("tel;work")}, PhoneNumber, NULL, 0, 0, { HKEY(" (work)")} },
81         { { HKEY("tel;fax")}, PhoneNumber, NULL, 0, 0, { HKEY(" (fax)")} },
82         { { HKEY("tel;cell")}, PhoneNumber, NULL, 0, 0, { HKEY(" (cell)")} },
83         { { HKEY("email;internet")}, EmailAddr, NULL, 0, 0, { HKEY("E-mail:")} },
84         { { HKEY("UNKNOWN")}, UnKnown, NULL, 0, 0, { HKEY("")} },
85         { { HKEY("")}, TerminateList, NULL, 0, 0, { HKEY("")} }
86 };
87
88 ConstStr VCStr[] = {
89         { HKEY("") },
90         { HKEY("n") },          /* N is name, but only if there's no FN already there */
91         { HKEY("fn") },         /* FN (full name) is a true 'display name' field */
92         { HKEY("title") },      /* title */
93         { HKEY("org") },        /* organization */
94         { HKEY("email") },
95         { HKEY("tel") },
96         { HKEY("work") },
97         { HKEY("home") },
98         { HKEY("cell") },
99         { HKEY("adr") },
100         { HKEY("photo") },
101         { HKEY("version") },
102         { HKEY("rev") },
103         { HKEY("label") },
104         { HKEY("uid") }
105 };
106
107 /*
108  * Address book entry (keep it short and sweet, it's just a quickie lookup
109  * which we can use to get to the real meat and bones later)
110  */
111 typedef struct _addrbookent {
112         StrBuf *name;
113         HashList *VC;
114         long ab_msgnum;         /* message number of address book entry */
115         StrBuf *msgNoStr;
116 } addrbookent;
117
118 void deleteAbEnt(void *v) {
119         addrbookent *vc = (addrbookent *) v;
120         DeleteHash(&vc->VC);
121         FreeStrBuf(&vc->name);
122         FreeStrBuf(&vc->msgNoStr);
123         free(vc);
124 }
125
126 HashList *DefineToToken = NULL;
127 HashList *VCTokenToDefine = NULL;
128 HashList *vcNames = NULL;       /* todo: fill with the name strings */
129 vcField *vcfUnknown = NULL;
130
131 /******************************************************************************
132  *                   initialize vcard structure                               *
133  ******************************************************************************/
134
135 void RegisterVCardToken(vcField * vf, StrBuf * name, int inTokenCount) {
136         if (vf->Type == UnKnown) {
137                 vcfUnknown = vf;
138         }
139         RegisterTokenParamDefine(SKEY(name), vf->cval);
140         Put(DefineToToken, LKEY(vf->cval), vf, reference_free_handler);
141         Put(vcNames, LKEY(vf->cval), NewStrBufPlain(CKEY(vf->Name)), HFreeStrBuf);
142
143         syslog(LOG_DEBUG, "Token: %s -> %ld, %d", ChrPtr(name), vf->cval, inTokenCount);
144
145 }
146
147 void autoRegisterTokens(long *enumCounter, vcField * vf, StrBuf * BaseStr, int layer, long parentCVal) {
148         int i = 0;
149         StrBuf *subStr = NewStrBuf();
150         while (vf[i].STR.len > 0) {
151                 FlushStrBuf(subStr);
152                 vf[i].cval = (*enumCounter)++;
153                 vf[i].parentCVal = parentCVal;
154                 StrBufAppendBuf(subStr, BaseStr, 0);
155                 if (StrLength(subStr) > 0) {
156                         StrBufAppendBufPlain(subStr, HKEY("."), 0);
157                 }
158                 StrBufAppendBufPlain(subStr, CKEY(vf[i].STR), 0);
159                 if (layer == 0) {
160                         Put(VCTokenToDefine, CKEY(vf[i].STR), &vf[i], reference_free_handler);
161                 }
162                 switch (vf[i].Type) {
163                 case FlatString:
164                         break;
165                 case StringCluster:
166                         {
167                                 autoRegisterTokens(enumCounter, vf[i].Sub, subStr, 1, vf[i].cval);
168                         }
169                         break;
170                 case PhoneNumber:
171                         break;
172                 case EmailAddr:
173                         break;
174                 case Street:
175                         break;
176                 case Number:
177                         break;
178                 case AliasFor:
179                         break;
180                 case Base64BinaryAttachment:
181                         break;
182                 case TerminateList:
183                         break;
184                 case Address:
185                         break;
186                 case UnKnown:
187                         break;
188                 }
189                 RegisterVCardToken(&vf[i], subStr, i);
190                 i++;
191         }
192         FreeStrBuf(&subStr);
193 }
194
195 /******************************************************************************
196  *               VCard template functions                                     *
197  ******************************************************************************/
198
199 int preeval_vcard_item(WCTemplateToken * Token) {
200         WCTemplputParams TPP;
201         WCTemplputParams *TP;
202         int searchFieldNo;
203         StrBuf *Target = NULL;
204
205         memset(&TPP, 0, sizeof(WCTemplputParams));
206         TP = &TPP;
207         TP->Tokens = Token;
208         searchFieldNo = GetTemplateTokenNumber(Target, TP, 0, 0);
209         if (searchFieldNo >= VCEnumCounter) {
210                 LogTemplateError(NULL, "VCardItem", ERR_PARM1, TP, "Invalid define");
211                 return 0;
212         }
213         return 1;
214 }
215
216 void tmpl_vcard_item(StrBuf * Target, WCTemplputParams * TP) {
217         void *vItem;
218         long searchFieldNo = GetTemplateTokenNumber(Target, TP, 0, 0);
219         addrbookent *ab = (addrbookent *) CTX(CTX_VCARD);
220         if (GetHash(ab->VC, LKEY(searchFieldNo), &vItem) && (vItem != NULL)) {
221                 StrBufAppendTemplate(Target, TP, (StrBuf *) vItem, 1);
222         }
223 }
224
225 void tmpl_vcard_context_item(StrBuf * Target, WCTemplputParams * TP) {
226         void *vItem;
227         vcField *t = (vcField *) CTX(CTX_VCARD_TYPE);
228         addrbookent *ab = (addrbookent *) CTX(CTX_VCARD);
229
230         if (t == NULL) {
231                 LogTemplateError(NULL, "VCard item", ERR_NAME, TP, "Missing context");
232                 return;
233         }
234
235         if (GetHash(ab->VC, LKEY(t->cval), &vItem) && (vItem != NULL)) {
236                 StrBufAppendTemplate(Target, TP, (StrBuf *) vItem, 0);
237         }
238         else {
239                 LogTemplateError(NULL, "VCard item", ERR_NAME, TP, "Doesn't have that key - did you miss to filter in advance?");
240         }
241 }
242 int preeval_vcard_name_str(WCTemplateToken * Token) {
243         WCTemplputParams TPP;
244         WCTemplputParams *TP;
245         int searchFieldNo;
246         StrBuf *Target = NULL;
247
248         memset(&TPP, 0, sizeof(WCTemplputParams));
249         TP = &TPP;
250         TP->Tokens = Token;
251         searchFieldNo = GetTemplateTokenNumber(Target, TP, 0, 0);
252         if (searchFieldNo >= VCEnumCounter) {
253                 LogTemplateError(NULL, "VCardName", ERR_PARM1, TP, "Invalid define");
254                 return 0;
255         }
256         return 1;
257 }
258
259 void tmpl_vcard_name_str(StrBuf * Target, WCTemplputParams * TP) {
260         void *vItem;
261         long searchFieldNo = GetTemplateTokenNumber(Target, TP, 0, 0);
262         /* todo: get descriptive string for this vcard type */
263         if (GetHash(vcNames, LKEY(searchFieldNo), &vItem) && (vItem != NULL)) {
264                 StrBufAppendTemplate(Target, TP, (StrBuf *) vItem, 1);
265         }
266         else {
267                 LogTemplateError(NULL, "VCard item type", ERR_NAME, TP, "No i18n string for this.");
268                 return;
269         }
270 }
271
272 void tmpl_vcard_msgno(StrBuf * Target, WCTemplputParams * TP) {
273         addrbookent *ab = (addrbookent *) CTX(CTX_VCARD);
274         if (ab->msgNoStr == NULL) {
275                 ab->msgNoStr = NewStrBufPlain(NULL, 64);
276         }
277         StrBufPrintf(ab->msgNoStr, "%ld", ab->ab_msgnum);
278         StrBufAppendTemplate(Target, TP, ab->msgNoStr, 0);
279 }
280 void tmpl_vcard_context_name_str(StrBuf * Target, WCTemplputParams * TP) {
281         void *vItem;
282         vcField *t = (vcField *) CTX(CTX_VCARD_TYPE);
283
284         if (t == NULL) {
285                 LogTemplateError(NULL, "VCard item type", ERR_NAME, TP, "Missing context");
286                 return;
287         }
288
289         if (GetHash(vcNames, LKEY(t->cval), &vItem) && (vItem != NULL)) {
290                 StrBufAppendTemplate(Target, TP, (StrBuf *) vItem, 1);
291         }
292         else {
293                 LogTemplateError(NULL, "VCard item type", ERR_NAME, TP, "No i18n string for this.");
294                 return;
295         }
296 }
297
298 int filter_VC_ByType(const char *key, long len, void *Context, StrBuf * Target, WCTemplputParams * TP) {
299         long searchType;
300         long type = 0;
301         void *v;
302         vcField *vf = (vcField *) Context;
303         int rc = 0;
304
305         memcpy(&type, key, sizeof(long));
306         searchType = GetTemplateTokenNumber(Target, TP, IT_ADDT_PARAM(0), 0);
307
308         if (vf->Type == searchType) {
309                 addrbookent *ab = (addrbookent *) CTX(CTX_VCARD);
310                 if (GetHash(ab->VC, LKEY(vf->cval), &v) && v != NULL) {
311                         return 1;
312                 }
313         }
314         return rc;
315 }
316
317 HashList *getContextVcard(StrBuf * Target, WCTemplputParams * TP) {
318         vcField *vf = (vcField *) CTX(CTX_VCARD_TYPE);
319         addrbookent *ab = (addrbookent *) CTX(CTX_VCARD);
320
321         if ((vf == NULL) || (ab == NULL)) {
322                 LogTemplateError(NULL, "VCard item type", ERR_NAME, TP, "Need VCard and Vcard type in context");
323
324                 return NULL;
325         }
326         return ab->VC;
327 }
328
329 int filter_VC_ByContextType(const char *key, long len, void *Context, StrBuf * Target, WCTemplputParams * TP) {
330         long searchType;
331         vcField *vf = (vcField *) CTX(CTX_VCARD_TYPE);
332
333         memcpy(&searchType, key, sizeof(long));
334
335         if (vf->cval == searchType) {
336                 return 1;
337         }
338         else {
339                 return 0;
340         }
341 }
342
343 int conditional_VC_Havetype(StrBuf * Target, WCTemplputParams * TP) {
344         addrbookent *ab = (addrbookent *) CTX(CTX_VCARD);
345         long HaveFieldType = GetTemplateTokenNumber(Target, TP, 2, 0);
346         int rc = 0;
347         void *vVCitem;
348         const char *Key;
349         long len;
350         HashPos *it = GetNewHashPos(ab->VC, 0);
351         while (GetNextHashPos(ab->VC, it, &len, &Key, &vVCitem) && (vVCitem != NULL)) {
352                 void *vvcField;
353                 long type = 0;
354                 memcpy(&type, Key, sizeof(long));
355                 if (GetHash(DefineToToken, LKEY(type), &vvcField) && (vvcField != NULL)) {
356                         vcField *t = (vcField *) vvcField;
357                         if (t && t->Type == HaveFieldType) {
358                                 rc = 1;
359                                 break;
360                         }
361                 }
362         }
363         DeleteHashPos(&it);
364         return rc;
365 }
366
367
368 /* Returns 1 to suppress the "email" fields in the vCard editor, if we're editing a user's contact info.
369  * Returns 0 to present those fields, if we're editing a vCard in an address book.
370  */
371 int conditional_VC_SuppressEmailFields(StrBuf * Target, WCTemplputParams * TP) {
372         return (atoi(bstr("suppress_email")));
373 }
374
375
376 /******************************************************************************
377  *              parse one VCard                                               *
378  ******************************************************************************/
379
380 void PutVcardItem(HashList * thisVC, vcField * thisField, StrBuf * ThisFieldStr, int is_qp, StrBuf * Swap) {
381         /* if we have some untagged QP, detect it here. */
382         if (is_qp || (strstr(ChrPtr(ThisFieldStr), "=?") != NULL)) {
383                 FlushStrBuf(Swap);
384                 StrBuf_RFC822_to_Utf8(Swap, ThisFieldStr, NULL, NULL);  /* default charset, current charset */
385                 SwapBuffers(Swap, ThisFieldStr);
386                 FlushStrBuf(Swap);
387         }
388         Put(thisVC, LKEY(thisField->cval), ThisFieldStr, HFreeStrBuf);
389 }
390
391 void parse_vcard(StrBuf * Target, struct vCard *v, HashList * VC, wc_mime_attachment * Mime) {
392         StrBuf *Swap = NULL;
393         int i, j, k;
394         char buf[SIZ];
395         int is_qp = 0;
396         int is_b64 = 0;
397         int ntokens, len;
398         StrBuf *thisname = NULL;
399         char firsttoken[SIZ];
400         StrBuf *thisVCToken;
401         void *vField = NULL;
402
403         Swap = NewStrBuf();
404         thisname = NewStrBuf();
405         thisVCToken = NewStrBufPlain(NULL, 63);
406         for (i = 0; i < (v->numprops); ++i) {
407                 FlushStrBuf(thisVCToken);
408                 is_qp = 0;
409                 is_b64 = 0;
410                 // syslog(LOG_DEBUG, "i: %d oneprop: %s - value: %s", i, v->prop[i].name, v->prop[i].value);
411                 StrBufPlain(thisname, v->prop[i].name, -1);
412                 StrBufLowerCase(thisname);
413
414                 extract_token(firsttoken, ChrPtr(thisname), 0, ';', sizeof firsttoken);
415                 ntokens = num_tokens(ChrPtr(thisname), ';');
416                 for (j = 0, k = 0; j < ntokens && k < 10; ++j) {
417                         len = extract_token(buf, ChrPtr(thisname), j, ';', sizeof buf);
418                         if (!strcasecmp(buf, "encoding=quoted-printable")) {
419                                 is_qp = 1;
420                         }
421                         else if (!strcasecmp(buf, "encoding=base64")) {
422                                 is_b64 = 1;
423                         }
424                         else {
425                                 if (StrLength(thisVCToken) > 0) {
426                                         StrBufAppendBufPlain(thisVCToken, HKEY(";"), 0);
427                                 }
428                                 StrBufAppendBufPlain(thisVCToken, buf, len, 0);
429                         }
430                 }
431
432                 vField = NULL;
433                 if ((StrLength(thisVCToken) > 0) && GetHash(VCTokenToDefine, SKEY(thisVCToken), &vField) && (vField != NULL)) {
434                         vcField *thisField = (vcField *) vField;
435                         StrBuf *ThisFieldStr = NULL;
436                         // syslog(LOG_DEBUG, "got this token: %s, found: %s", ChrPtr(thisVCToken), thisField->STR.Key);
437                         switch (thisField->Type) {
438                         case StringCluster:{
439                                         int j = 0;
440                                         const char *Pos = NULL;
441                                         StrBuf *thisArray = NewStrBufPlain(v->prop[i].value, -1);
442                                         StrBuf *Buf = NewStrBufPlain(NULL, StrLength(thisArray));
443                                         while (thisField->Sub[j].STR.len > 0) {
444                                                 StrBufExtract_NextToken(Buf, thisArray, &Pos, ';');
445                                                 ThisFieldStr = NewStrBufDup(Buf);
446                                                 PutVcardItem(VC, &thisField->Sub[j], ThisFieldStr, is_qp, Swap);
447                                                 j++;
448                                         }
449                                         FreeStrBuf(&thisArray);
450                                         FreeStrBuf(&Buf);
451                                 }
452                                 break;
453                         case Address:
454                         case FlatString:
455                         case PhoneNumber:
456                         case EmailAddr:
457                         case Street:
458                         case Number:
459                         case AliasFor:
460                                 /* copy over the payload into a StrBuf */
461                                 ThisFieldStr = NewStrBufPlain(v->prop[i].value, -1);
462                                 PutVcardItem(VC, thisField, ThisFieldStr, is_qp, Swap);
463
464                                 break;
465                         case Base64BinaryAttachment:
466                                 ThisFieldStr = NewStrBufPlain(v->prop[i].value, -1);
467                                 StrBufDecodeBase64(ThisFieldStr);
468                                 PutVcardItem(VC, thisField, ThisFieldStr, is_qp, Swap);
469                                 break;
470                         case TerminateList:
471                         case UnKnown:
472                                 break;
473                         }
474
475                 }
476                 else if (StrLength(thisVCToken) > 0) {
477                         /* Add it to the UNKNOWN field... */
478                         void *pv = NULL;
479                         StrBuf *oldVal;
480                         GetHash(VC, IKEY(vcfUnknown->cval), &pv);
481                         oldVal = (StrBuf *) pv;
482                         if (oldVal == NULL) {
483                                 oldVal = NewStrBuf();
484                                 Put(VC, IKEY(vcfUnknown->cval), oldVal, HFreeStrBuf);
485                         }
486                         else {
487                                 StrBufAppendBufPlain(oldVal, HKEY("\n"), 0);
488                         }
489
490                         StrBufAppendBuf(oldVal, thisVCToken, 0);
491                         StrBufAppendBufPlain(oldVal, HKEY(":"), 0);
492                         StrBufAppendBufPlain(oldVal, v->prop[i].value, -1, 0);
493                         continue;
494                 }
495         }
496         FreeStrBuf(&thisname);
497         FreeStrBuf(&Swap);
498         FreeStrBuf(&thisVCToken);
499 }
500
501 HashList *CtxGetVcardList(StrBuf * Target, WCTemplputParams * TP) {
502         HashList *pb = CTX(CTX_VCARD_LIST);
503         return pb;
504 }
505
506 /******************************************************************************
507  * Extract an embedded photo from a vCard for display on the client           *
508  ******************************************************************************/
509
510 void display_vcard_photo_img(void) {
511         long msgnum = 0L;
512         StrBuf *vcard;
513         struct vCard *v;
514         char *photosrc;
515         const char *contentType;
516
517         msgnum = StrBufExtract_long(WC->Hdr->HR.ReqLine, 0, '/');
518
519         vcard = load_mimepart(msgnum, "1");
520         v = VCardLoad(vcard);
521
522         photosrc = vcard_get_prop(v, "PHOTO", 1, 0, 0);
523         FlushStrBuf(WC->WBuf);
524         StrBufAppendBufPlain(WC->WBuf, photosrc, -1, 0);
525         if (StrBufDecodeBase64(WC->WBuf) <= 0) {
526                 FlushStrBuf(WC->WBuf);
527
528                 hprintf("HTTP/1.1 500 %s\n", "Unable to get photo");
529                 output_headers(0, 0, 0, 0, 0, 0);
530                 hprintf("Content-Type: text/plain\r\n");
531                 begin_burst();
532                 wc_printf(_("Could Not decode vcard photo\n"));
533                 end_burst();
534                 return;
535         }
536         contentType = GuessMimeType(ChrPtr(WC->WBuf), StrLength(WC->WBuf));
537         http_transmit_thing(contentType, 0);
538         free(v);
539         free(photosrc);
540 }
541
542 wc_mime_attachment *load_vcard(message_summary * Msg) {
543         HashPos *it;
544         StrBuf *FoundCharset = NewStrBuf();
545         StrBuf *Error;
546         void *vMime;
547         const char *Key;
548         long len;
549         wc_mime_attachment *Mime;
550         wc_mime_attachment *VCMime = NULL;
551
552         Msg->MsgBody = (wc_mime_attachment *) malloc(sizeof(wc_mime_attachment));
553         memset(Msg->MsgBody, 0, sizeof(wc_mime_attachment));
554         Msg->MsgBody->msgnum = Msg->msgnum;
555
556         load_message(Msg, FoundCharset, &Error);
557
558         FreeStrBuf(&FoundCharset);
559         /* look up the vcard... */
560         it = GetNewHashPos(Msg->AllAttach, 0);
561         while (GetNextHashPos(Msg->AllAttach, it, &len, &Key, &vMime) && (vMime != NULL)) {
562                 Mime = (wc_mime_attachment *) vMime;
563                 if ((strcmp(ChrPtr(Mime->ContentType),
564                             "text/x-vcard") == 0) || (strcmp(ChrPtr(Mime->ContentType), "text/vcard") == 0)) {
565                         VCMime = Mime;
566                         break;
567                 }
568         }
569         DeleteHashPos(&it);
570         if (VCMime == NULL)
571                 return NULL;
572
573         if (VCMime->Data == NULL)
574                 MimeLoadData(VCMime);
575         return VCMime;
576 }
577
578 /*
579  * Edit the vCard component of a MIME message.  
580  * Supply the message number
581  * and MIME part number to fetch.  Or, specify -1 for the message number
582  * to start with a blank card.
583  */
584 void do_edit_vcard(long msgnum, char *partnum,
585                    message_summary * VCMsg, wc_mime_attachment * VCAtt, const char *return_to, const char *force_room) {
586         WCTemplputParams SubTP;
587         message_summary *Msg = NULL;
588         wc_mime_attachment *VCMime = NULL;
589         struct vCard *v;
590         char whatuser[256];
591         addrbookent ab;
592
593         memset(&ab, 0, sizeof(addrbookent));
594         ab.VC = NewHash(0, lFlathash);
595         /* Display the form */
596         output_headers(1, 1, 1, 0, 0, 0);
597
598         safestrncpy(whatuser, "", sizeof whatuser);
599
600         if ((msgnum >= 0) || ((VCMsg != NULL) && (VCAtt != NULL))) {
601                 if ((VCMsg == NULL) && (VCAtt == NULL)) {
602
603                         Msg = (message_summary *) malloc(sizeof(message_summary));
604                         memset(Msg, 0, sizeof(message_summary));
605                         Msg->msgnum = msgnum;
606                         VCMime = load_vcard(Msg);
607                         if (VCMime == NULL) {
608                                 convenience_page("770000", _("Error"), "");     /*TODO: important message */
609                                 DestroyMessageSummary(Msg);
610                                 return;
611                                 DeleteHash(&ab.VC);
612                         }
613
614                         v = VCardLoad(VCMime->Data);
615                 }
616                 else {
617                         v = VCardLoad(VCAtt->Data);
618                 }
619
620                 parse_vcard(WC->WBuf, v, ab.VC, NULL);
621
622
623                 vcard_free(v);
624         }
625
626         memset(&SubTP, 0, sizeof(WCTemplputParams));
627         {
628                 WCTemplputParams *TP = NULL;
629                 WCTemplputParams SubTP;
630
631                 StackContext(TP, &SubTP, &ab, CTX_VCARD, 0, NULL);
632
633                 DoTemplate(HKEY("vcard_edit"), WC->WBuf, &SubTP);
634                 UnStackContext(&SubTP);
635         }
636         DeleteHash(&ab.VC);
637
638
639         wDumpContent(1);
640         if (Msg != NULL) {
641                 DestroyMessageSummary(Msg);
642         }
643 }
644
645
646 /*
647  *  commit the edits to the citadel server
648  */
649 void edit_vcard(void) {
650         long msgnum;
651         char *partnum;
652
653         msgnum = lbstr("msgnum");
654         partnum = bstr("partnum");
655         do_edit_vcard(msgnum, partnum, NULL, NULL, "", NULL);
656 }
657
658 /*
659  *  parse edited vcard from the browser
660  */
661 void submit_vcard(void) {
662         struct vCard *v;
663         char *serialized_vcard;
664         StrBuf *Buf;
665         const StrBuf *ForceRoom;
666         HashList *postVcard;
667         HashPos *it, *itSub;
668         const char *Key;
669         long len;
670         void *pv;
671         StrBuf *SubStr;
672         const StrBuf *s;
673         const char *Pos = NULL;
674
675         if (!havebstr("ok_button")) {
676                 readloop(readnew, eUseDefault);
677                 return;
678         }
679
680         if (havebstr("force_room")) {
681                 ForceRoom = sbstr("force_room");
682                 if (gotoroom(ForceRoom) != 200) {
683                         AppendImportantMessage(_("Unable to enter the room to save your message"), -1);
684                         AppendImportantMessage(HKEY(": "));
685                         AppendImportantMessage(SKEY(ForceRoom));
686                         AppendImportantMessage(HKEY("; "));
687                         AppendImportantMessage(_("Aborting."), -1);
688
689                         if (!strcmp(bstr("return_to"), "select_user_to_edit")) {
690                                 select_user_to_edit(NULL);
691                         }
692                         else if (!strcmp(bstr("return_to"), "do_welcome")) {
693                                 do_welcome();
694                         }
695                         else if (!IsEmptyStr(bstr("return_to"))) {
696                                 http_redirect(bstr("return_to"));
697                         }
698                         else {
699                                 readloop(readnew, eUseDefault);
700                         }
701                         return;
702                 }
703         }
704
705         postVcard = getSubStruct(HKEY("VC"));
706         if (postVcard == NULL) {
707                 AppendImportantMessage(_("An error has occurred."), -1);
708                 edit_vcard();
709                 return;         /*/// more details */
710         }
711
712         Buf = NewStrBuf();
713         serv_write(HKEY("ENT0 1|||4\n"));
714         if (!StrBuf_ServGetln(Buf) && (GetServerStatus(Buf, NULL) != 4)) {
715                 edit_vcard();
716                 return;
717         }
718
719         /* Make a vCard structure out of the data supplied in the form */
720         StrBufPrintf(Buf, "begin:vcard\r\n%s\r\nend:vcard\r\n", bstr("extrafields")
721             );
722         v = VCardLoad(Buf);     /* Start with the extra fields */
723         if (v == NULL) {
724                 AppendImportantMessage(_("An error has occurred."), -1);
725                 edit_vcard();
726                 FreeStrBuf(&Buf);
727                 return;
728         }
729
730         SubStr = NewStrBuf();
731         it = GetNewHashPos(DefineToToken, 0);
732         while (GetNextHashPos(DefineToToken, it, &len, &Key, &pv) && (pv != NULL)) {
733                 char buf[32];
734                 long blen;
735                 vcField *t = (vcField *) pv;
736
737                 if (t->Sub != NULL) {
738                         vcField *Sub;
739                         FlushStrBuf(SubStr);
740                         itSub = GetNewHashPos(DefineToToken, 0);
741                         while (GetNextHashPos(DefineToToken, itSub, &len, &Key, &pv) && (pv != NULL)) {
742                                 Sub = (vcField *) pv;
743                                 if (Sub->parentCVal == t->cval) {
744                                         if (StrLength(SubStr) > 0)
745                                                 StrBufAppendBufPlain(SubStr, HKEY(";"), 0);
746
747
748
749                                         blen = snprintf(buf, sizeof(buf), "%ld", Sub->cval);
750                                         s = SSubBstr(postVcard, buf, blen);
751
752                                         if ((s != NULL) && (StrLength(s) > 0)) {
753                                                 /// todo: utf8 qp
754                                                 StrBufAppendBuf(SubStr, s, 0);
755                                         }
756                                 }
757                         }
758                         if (StrLength(SubStr) > 0) {
759                                 vcard_add_prop(v, t->STR.Key, ChrPtr(SubStr));
760                         }
761                         DeleteHashPos(&itSub);
762                 }
763                 else if (t->parentCVal == 0) {
764                         blen = snprintf(buf, sizeof(buf), "%ld", t->cval);
765                         s = SSubBstr(postVcard, buf, blen);
766
767                         if ((s != NULL) && (StrLength(s) > 0)) {
768                                 vcard_add_prop(v, t->STR.Key, ChrPtr(s));
769                         }
770                 }
771         }
772         DeleteHashPos(&it);
773
774         s = sbstr("other_inetemail");
775         if (StrLength(s) > 0) {
776                 FlushStrBuf(SubStr);
777                 while (StrBufSipLine(SubStr, s, &Pos), ((Pos != StrBufNOTNULL) && (Pos != NULL))) {
778                         if (StrLength(SubStr) > 0) {
779                                 vcard_add_prop(v, "email;internet", ChrPtr(SubStr));
780                         }
781                 }
782         }
783
784         FreeStrBuf(&SubStr);
785
786
787         serialized_vcard = vcard_serialize(v);
788         vcard_free(v);
789         if (serialized_vcard == NULL) {
790                 AppendImportantMessage(_("An error has occurred."), -1);
791                 edit_vcard();
792                 FreeStrBuf(&Buf);
793                 return;
794         }
795
796         printf("%s", serialized_vcard);
797         serv_write(HKEY("Content-type: text/x-vcard; charset=UTF-8\n"));
798         serv_write(HKEY("\n"));
799         serv_printf("%s\r\n", serialized_vcard);
800         serv_write(HKEY("000\n"));
801         free(serialized_vcard);
802
803         if (!strcmp(bstr("return_to"), "select_user_to_edit")) {
804                 select_user_to_edit(NULL);
805         }
806         else if (!strcmp(bstr("return_to"), "do_welcome")) {
807                 do_welcome();
808         }
809         else if (!IsEmptyStr(bstr("return_to"))) {
810                 http_redirect(bstr("return_to"));
811         }
812         else {
813                 readloop(readnew, eUseDefault);
814         }
815         FreeStrBuf(&Buf);
816 }
817
818 /******************************************************************************
819  *              Render Addressbooks                                           *
820  ******************************************************************************/
821
822 typedef struct _vcardview_struct {
823         long is_singlecard;
824         HashList *addrbook;
825
826 } vcardview_struct;
827
828 int vcard_GetParamsGetServerCall(SharedMessageStatus * Stat,
829                                  void **ViewSpecific, long oper, char *cmd, long len, char *filter, long flen) {
830         vcardview_struct *VS;
831
832         VS = (vcardview_struct *) malloc(sizeof(vcardview_struct));
833         memset(VS, 0, sizeof(vcardview_struct));
834         *ViewSpecific = (void *) VS;
835
836         VS->is_singlecard = ibstr("is_singlecard");
837         if (VS->is_singlecard != 1) {
838                 VS->addrbook = NewHash(0, NULL);
839                 if (oper == do_search) {
840                         snprintf(cmd, len, "MSGS SEARCH|%s", bstr("query"));
841                 }
842                 else {
843                         strcpy(cmd, "MSGS ALL");
844                 }
845                 Stat->maxmsgs = 9999999;
846         }
847         return 200;
848 }
849
850 int vcard_LoadMsgFromServer(SharedMessageStatus * Stat, void **ViewSpecific, message_summary * Msg, int is_new, int i) {
851         WCTemplputParams *TP = NULL;
852         WCTemplputParams SubTP;
853         vcardview_struct *VS;
854         wc_mime_attachment *VCMime = NULL;
855         struct vCard *v;
856         addrbookent *abEntry;
857
858         VS = (vcardview_struct *) * ViewSpecific;
859
860         VCMime = load_vcard(Msg);
861         if (VCMime == NULL)
862                 return 0;
863
864         v = VCardLoad(VCMime->Data);
865
866         if (v == NULL)
867                 return 0;
868
869         abEntry = (addrbookent *) malloc(sizeof(addrbookent));
870         memset(abEntry, 0, sizeof(addrbookent));
871         abEntry->name = NewStrBuf();
872         abEntry->VC = NewHash(0, lFlathash);
873         abEntry->ab_msgnum = Msg->msgnum;
874
875         parse_vcard(WC->WBuf, v, abEntry->VC, VCMime);
876
877         memset(&SubTP, 0, sizeof(WCTemplputParams));
878         StackContext(TP, &SubTP, abEntry, CTX_VCARD, 0, NULL);
879
880         // No, don't display the name, it just shits all over the screen
881         // DoTemplate(HKEY("vcard_list_name"), WC->WBuf, &SubTP);
882
883         UnStackContext(&SubTP);
884
885         if (StrLength(abEntry->name) == 0) {
886                 StrBufPlain(abEntry->name, _("(no name)"), -1);
887         }
888
889         syslog(LOG_DEBUG, "abEntry->name : %s", ChrPtr(abEntry->name));
890
891         vcard_free(v);
892
893         Put(VS->addrbook, SKEY(abEntry->name), abEntry, deleteAbEnt);
894         return 0;
895 }
896
897
898 /*
899  * Render the address book using info we gathered during the scan
900  *
901  * addrbook     the addressbook to render
902  * num_ab       the number of the addressbook
903  */
904 static int NAMESPERPAGE = 60;
905 void do_addrbook_view(vcardview_struct * VS) {
906         long i = 0;
907         int num_pages = 0;
908         int tabfirst = 0;
909         int tablast = 0;
910         StrBuf **tablabels;
911         int num_ab = GetCount(VS->addrbook);
912         HashList *headlines;
913
914         WCTemplputParams *TP = NULL;
915         WCTemplputParams SubTP;
916
917         memset(&SubTP, 0, sizeof(WCTemplputParams));
918
919         if (num_ab == 0) {
920                 do_template("vcard_list_empty");
921                 return;
922         }
923
924         if (num_ab > 1) {
925                 SortByHashKey(VS->addrbook, 1);
926         }
927
928         num_pages = (GetCount(VS->addrbook) / NAMESPERPAGE) + 1;
929
930         tablabels = malloc(num_pages * sizeof(StrBuf *));
931         if (tablabels == NULL) {
932                 return;
933         }
934
935         headlines = NewHash(0, lFlathash);
936         for (i = 0; i < num_pages; ++i) {
937                 void *v1 = NULL;
938                 void *v2 = NULL;
939                 long hklen1, hklen2;
940                 const char *c1, *c2;
941                 StrBuf *headline;
942                 addrbookent *a1, *a2;
943
944                 tabfirst = i * NAMESPERPAGE;
945                 tablast = tabfirst + NAMESPERPAGE - 1;
946                 if (tablast > (num_ab - 1))
947                         tablast = (num_ab - 1);
948
949                 headline = NewStrBufPlain(NULL, StrLength(v1) + StrLength(v2) + 10);
950                 if (GetHashAt(VS->addrbook, tabfirst, &hklen1, &c1, &v1)) {
951                         a1 = (addrbookent *) v1;
952                         StrBufAppendBuf(headline, a1->name, 0);
953                         StrBuf_Utf8StrCut(headline, 3);
954                         if (GetHashAt(VS->addrbook, tablast, &hklen2, &c2, &v2)) {
955
956                                 a2 = (addrbookent *) v2;
957                                 StrBufAppendBufPlain(headline, HKEY(" - "), 0);
958                                 StrBufAppendBuf(headline, a2->name, 0);
959                                 StrBuf_Utf8StrCut(headline, 9);
960                         }
961                 }
962                 tablabels[i] = headline;
963                 Put(headlines, LKEY(i), headline, HFreeStrBuf);
964         }
965         StrTabbedDialog(WC->WBuf, num_pages, tablabels);
966         StackContext(TP, &SubTP, VS->addrbook, CTX_VCARD_LIST, 0, NULL);
967
968         DoTemplate(HKEY("vcard_list"), WC->WBuf, &SubTP);
969         UnStackContext(&SubTP);
970         DeleteHash(&headlines);
971         free(tablabels);
972         StrBufAppendBufPlain(WC->WBuf, HKEY("</div>"), 0);      /* closes: id=global */
973 }
974
975
976
977 int vcard_RenderView_or_Tail(SharedMessageStatus * Stat, void **ViewSpecific, long oper) {
978         const StrBuf *Mime;
979         vcardview_struct *VS;
980
981         VS = (vcardview_struct *) * ViewSpecific;
982         if (VS->is_singlecard) {
983                 read_message(WC->WBuf, HKEY("view_message"), lbstr("startmsg"), NULL, &Mime, NULL);
984         }
985         else {
986                 do_addrbook_view(VS);   /* Render the address book */
987         }
988         return 0;
989 }
990
991 int vcard_Cleanup(void **ViewSpecific) {
992         vcardview_struct *VS;
993
994         VS = (vcardview_struct *) * ViewSpecific;
995         wDumpContent(1);
996         if ((VS != NULL) && (VS->addrbook != NULL)) {
997                 DeleteHash(&VS->addrbook);
998         }
999         if (VS != NULL) {
1000                 free(VS);
1001         }
1002
1003         return 0;
1004 }
1005
1006 void render_MIME_VCard(StrBuf * Target, WCTemplputParams * TP, StrBuf * FoundCharset) {
1007         wc_mime_attachment *Mime = (wc_mime_attachment *) CTX(CTX_MIME_ATACH);
1008         if (StrLength(Mime->Data) == 0) {
1009                 MimeLoadData(Mime);
1010         }
1011         if (StrLength(Mime->Data) > 0) {
1012                 struct vCard *v;
1013                 StrBuf *Buf;
1014
1015                 Buf = NewStrBuf();
1016
1017                 /** If it's my vCard I can edit it */
1018                 if ((!strcasecmp(ChrPtr(WC->CurRoom.name), USERCONFIGROOM))
1019                     || ((StrLength(WC->CurRoom.name) > 11) && (!strcasecmp(&(ChrPtr(WC->CurRoom.name)[11]), USERCONFIGROOM)))
1020                     || (WC->CurRoom.view == VIEW_ADDRESSBOOK)
1021                     ) {
1022                         StrBufAppendPrintf(Buf, "<a href=\"edit_vcard?msgnum=%ld?partnum=%s\">",
1023                                            Mime->msgnum, ChrPtr(Mime->PartNum));
1024                         StrBufAppendPrintf(Buf, "[%s]</a>", _("edit"));
1025                 }
1026
1027                 /* In all cases, display the full card */
1028
1029                 v = VCardLoad(Mime->Data);
1030
1031                 if (v != NULL) {
1032                         WCTemplputParams *TP = NULL;
1033                         WCTemplputParams SubTP;
1034                         addrbookent ab;
1035                         memset(&ab, 0, sizeof(addrbookent));
1036
1037                         ab.VC = NewHash(0, lFlathash);
1038                         ab.ab_msgnum = Mime->msgnum;
1039
1040                         parse_vcard(Target, v, ab.VC, Mime);
1041
1042                         memset(&SubTP, 0, sizeof(WCTemplputParams));
1043                         StackContext(TP, &SubTP, &ab, CTX_VCARD, 0, NULL);
1044
1045                         DoTemplate(HKEY("vcard_msg_display"), Target, &SubTP);
1046                         UnStackContext(&SubTP);
1047                         DeleteHash(&ab.VC);
1048                         vcard_free(v);
1049
1050                 }
1051                 else {
1052                         StrBufPlain(Buf, _("failed to load vcard"), -1);
1053                 }
1054                 FreeStrBuf(&Mime->Data);
1055                 Mime->Data = Buf;
1056         }
1057
1058 }
1059
1060 void ServerStartModule_VCARD(void) {
1061 }
1062
1063 void ServerShutdownModule_VCARD(void) {
1064         DeleteHash(&DefineToToken);
1065         DeleteHash(&vcNames);
1066         DeleteHash(&VCTokenToDefine);
1067 }
1068
1069 void InitModule_VCARD(void) {
1070         StrBuf *Prefix = NewStrBufPlain(HKEY("VC:"));
1071         DefineToToken = NewHash(1, lFlathash);
1072         vcNames = NewHash(1, lFlathash);
1073         VCTokenToDefine = NewHash(1, NULL);
1074         autoRegisterTokens(&VCEnumCounter, VCStrE, Prefix, 0, 0);
1075         FreeStrBuf(&Prefix);
1076
1077         REGISTERTokenParamDefine(NAMESPERPAGE);
1078
1079
1080         RegisterCTX(CTX_VCARD);
1081         RegisterCTX(CTX_VCARD_LIST);
1082         RegisterCTX(CTX_VCARD_TYPE);
1083
1084         RegisterReadLoopHandlerset(VIEW_ADDRESSBOOK,
1085                                    vcard_GetParamsGetServerCall,
1086                                    NULL, NULL, NULL, vcard_LoadMsgFromServer, vcard_RenderView_or_Tail, vcard_Cleanup, NULL);
1087
1088         RegisterIterator("MAIL:VCARDS", 0, NULL, CtxGetVcardList, NULL, NULL, CTX_VCARD, CTX_VCARD_LIST, IT_NOFLAG);
1089
1090
1091         WebcitAddUrlHandler(HKEY("edit_vcard"), "", 0, edit_vcard, 0);
1092         WebcitAddUrlHandler(HKEY("submit_vcard"), "", 0, submit_vcard, 0);
1093         WebcitAddUrlHandler(HKEY("vcardphoto"), "", 0, display_vcard_photo_img, NEED_URL);
1094
1095         RegisterNamespace("VC:ITEM", 2, 2, tmpl_vcard_item, preeval_vcard_item, CTX_VCARD);
1096         RegisterNamespace("VC:CTXITEM", 1, 1, tmpl_vcard_context_item, NULL, CTX_VCARD_TYPE);
1097         RegisterNamespace("VC:NAME", 1, 1, tmpl_vcard_name_str, preeval_vcard_name_str, CTX_VCARD);
1098         RegisterNamespace("VC:MSGNO", 0, 1, tmpl_vcard_msgno, NULL, CTX_VCARD);
1099         RegisterNamespace("VC:CTXNAME", 1, 1, tmpl_vcard_context_name_str, NULL, CTX_VCARD_TYPE);
1100         REGISTERTokenParamDefine(FlatString);
1101         REGISTERTokenParamDefine(StringCluster);
1102         REGISTERTokenParamDefine(PhoneNumber);
1103         REGISTERTokenParamDefine(EmailAddr);
1104         REGISTERTokenParamDefine(Street);
1105         REGISTERTokenParamDefine(Number);
1106         REGISTERTokenParamDefine(AliasFor);
1107         REGISTERTokenParamDefine(Base64BinaryAttachment);
1108         REGISTERTokenParamDefine(TerminateList);
1109         REGISTERTokenParamDefine(Address);
1110
1111         RegisterConditional("VC:HAVE:TYPE", 1, conditional_VC_Havetype, CTX_VCARD);
1112         RegisterConditional("COND:VC:SUPPRESS_EMAIL_FIELDS", 1, conditional_VC_SuppressEmailFields, CTX_VCARD);
1113
1114         RegisterFilteredIterator("VC:TYPE", 1, DefineToToken, NULL, NULL, NULL, filter_VC_ByType, CTX_VCARD_TYPE, CTX_VCARD,
1115                                  IT_NOFLAG);
1116         RegisterFilteredIterator("VC:TYPE:ITEMS", 0, NULL, getContextVcard, NULL, NULL, filter_VC_ByContextType, CTX_STRBUF,
1117                                  CTX_VCARD_TYPE, IT_NOFLAG);
1118
1119         RegisterMimeRenderer(HKEY("text/x-vcard"), render_MIME_VCard, 1, 201);
1120         RegisterMimeRenderer(HKEY("text/vcard"), render_MIME_VCard, 1, 200);
1121 }