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