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