9327ec291e6f7795169c3c8bbe42392f888db640
[citadel] / webcit / vcard_edit.c
1 /*
2  * Copyright (c) 1996-2020 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         wcsession *WCC = WC;
549
550         msgnum = StrBufExtract_long(WCC->Hdr->HR.ReqLine, 0, '/');
551         
552         vcard = load_mimepart(msgnum,"1");
553         v = VCardLoad(vcard);
554         
555         photosrc = vcard_get_prop(v, "PHOTO", 1,0,0);
556         FlushStrBuf(WCC->WBuf);
557         StrBufAppendBufPlain(WCC->WBuf, photosrc, -1, 0);
558         if (StrBufDecodeBase64(WCC->WBuf) <= 0) {
559                 FlushStrBuf(WCC->WBuf);
560                 
561                 hprintf("HTTP/1.1 500 %s\n","Unable to get photo");
562                 output_headers(0, 0, 0, 0, 0, 0);
563                 hprintf("Content-Type: text/plain\r\n");
564                 begin_burst();
565                 wc_printf(_("Could Not decode vcard photo\n"));
566                 end_burst();
567                 return;
568         }
569         contentType = GuessMimeType(ChrPtr(WCC->WBuf), StrLength(WCC->WBuf));
570         http_transmit_thing(contentType, 0);
571         free(v);
572         free(photosrc);
573 }
574
575 wc_mime_attachment *load_vcard(message_summary *Msg) 
576 {
577         HashPos  *it;
578         StrBuf *FoundCharset = NewStrBuf();
579         StrBuf *Error;
580         void *vMime;
581         const char *Key;
582         long len;
583         wc_mime_attachment *Mime;
584         wc_mime_attachment *VCMime = NULL;
585
586         Msg->MsgBody =  (wc_mime_attachment*) malloc(sizeof(wc_mime_attachment));
587         memset(Msg->MsgBody, 0, sizeof(wc_mime_attachment));
588         Msg->MsgBody->msgnum = Msg->msgnum;
589
590         load_message(Msg, FoundCharset, &Error);
591
592         FreeStrBuf(&FoundCharset);
593         /* look up the vcard... */
594         it = GetNewHashPos(Msg->AllAttach, 0);
595         while (GetNextHashPos(Msg->AllAttach, it, &len, &Key, &vMime) && 
596                (vMime != NULL)) 
597         {
598                 Mime = (wc_mime_attachment*) vMime;
599                 if ((strcmp(ChrPtr(Mime->ContentType),
600                            "text/x-vcard") == 0) ||
601                     (strcmp(ChrPtr(Mime->ContentType),
602                             "text/vcard") == 0))
603                 {
604                         VCMime = Mime;
605                         break;
606                 }
607         }
608         DeleteHashPos(&it);
609         if (VCMime == NULL)
610                 return NULL;
611
612         if (VCMime->Data == NULL)
613                 MimeLoadData(VCMime);
614         return VCMime;
615 }
616
617 /*
618  * Edit the vCard component of a MIME message.  
619  * Supply the message number
620  * and MIME part number to fetch.  Or, specify -1 for the message number
621  * to start with a blank card.
622  */
623 void do_edit_vcard(long msgnum, char *partnum, 
624                    message_summary *VCMsg,
625                    wc_mime_attachment *VCAtt,
626                    const char *return_to, 
627                    const char *force_room) {
628         WCTemplputParams SubTP;
629         wcsession *WCC = WC;
630         message_summary *Msg = NULL;
631         wc_mime_attachment *VCMime = NULL;
632         struct vCard *v;
633         char whatuser[256];
634         addrbookent ab;
635
636         memset(&ab, 0, sizeof(addrbookent));
637         ab.VC = NewHash(0, lFlathash);
638         /* Display the form */
639         output_headers(1, 1, 1, 0, 0, 0);
640
641         safestrncpy(whatuser, "", sizeof whatuser);
642
643         if ((msgnum >= 0) || 
644             ((VCMsg != NULL) && (VCAtt != NULL)))
645         {
646                 if ((VCMsg == NULL) && (VCAtt == NULL)) {
647
648                         Msg = (message_summary *) malloc(sizeof(message_summary));
649                         memset(Msg, 0, sizeof(message_summary));
650                         Msg->msgnum = msgnum;
651                         VCMime = load_vcard(Msg);
652                         if (VCMime == NULL) {
653                                 convenience_page("770000", _("Error"), "");/*TODO: important message*/
654                                 DestroyMessageSummary(Msg);
655                                 return;
656                                 DeleteHash(&ab.VC);
657                         }
658                 
659                         v = VCardLoad(VCMime->Data);
660                 }
661                 else {
662                         v = VCardLoad(VCAtt->Data);
663                 }
664
665                 parse_vcard(WCC->WBuf, v, ab.VC, NULL);
666         
667         
668                 vcard_free(v);
669         }
670
671         memset(&SubTP, 0, sizeof(WCTemplputParams));    
672         {
673                 WCTemplputParams *TP = NULL;
674                 WCTemplputParams SubTP;
675
676                 StackContext(TP, &SubTP, &ab, CTX_VCARD, 0, NULL);
677
678                 DoTemplate(HKEY("vcard_edit"), WCC->WBuf, &SubTP);
679                 UnStackContext(&SubTP);
680         }
681         DeleteHash(&ab.VC);
682
683
684         wDumpContent(1);
685         if (Msg != NULL) {
686                 DestroyMessageSummary(Msg);
687         }
688 }
689
690
691 /*
692  *  commit the edits to the citadel server
693  */
694 void edit_vcard(void) {
695         long msgnum;
696         char *partnum;
697
698         msgnum = lbstr("msgnum");
699         partnum = bstr("partnum");
700         do_edit_vcard(msgnum, partnum, NULL, NULL, "", NULL);
701 }
702
703 /*
704  *  parse edited vcard from the browser
705  */
706 void submit_vcard(void) {
707         struct vCard *v;
708         char *serialized_vcard;
709         StrBuf *Buf;
710         const StrBuf *ForceRoom;
711         HashList* postVcard;
712         HashPos *it, *itSub;
713         const char *Key;
714         long len;
715         void *pv;
716         StrBuf *SubStr;
717         const StrBuf *s;
718         const char *Pos = NULL;
719
720         if (!havebstr("ok_button")) { 
721                 readloop(readnew, eUseDefault);
722                 return;
723         }
724
725         if (havebstr("force_room")) {
726                 ForceRoom = sbstr("force_room");
727                 if (gotoroom(ForceRoom) != 200) {
728                         AppendImportantMessage(_("Unable to enter the room to save your message"), -1);
729                         AppendImportantMessage(HKEY(": "));
730                         AppendImportantMessage(SKEY(ForceRoom));
731                         AppendImportantMessage(HKEY("; "));
732                         AppendImportantMessage(_("Aborting."), -1);
733
734                         if (!strcmp(bstr("return_to"), "select_user_to_edit")) {
735                                 select_user_to_edit(NULL);
736                         }
737                         else if (!strcmp(bstr("return_to"), "do_welcome")) {
738                                 do_welcome();
739                         }
740                         else if (!IsEmptyStr(bstr("return_to"))) {
741                                 http_redirect(bstr("return_to"));
742                         }
743                         else {
744                                 readloop(readnew, eUseDefault);
745                         }
746                         return;
747                 }
748         }
749
750         postVcard = getSubStruct(HKEY("VC"));
751         if (postVcard == NULL) {
752                 AppendImportantMessage(_("An error has occurred."), -1);
753                 edit_vcard();
754                 return;/*/// more details*/
755         }
756         
757         Buf = NewStrBuf();
758         serv_write(HKEY("ENT0 1|||4\n"));
759         if (!StrBuf_ServGetln(Buf) && (GetServerStatus(Buf, NULL) != 4))
760         {
761                 edit_vcard();
762                 return;
763         }
764         
765         /* Make a vCard structure out of the data supplied in the form */
766         StrBufPrintf(Buf, "begin:vcard\r\n%s\r\nend:vcard\r\n",
767                      bstr("extrafields")
768         );
769         v = VCardLoad(Buf);     /* Start with the extra fields */
770         if (v == NULL) {
771                 AppendImportantMessage(_("An error has occurred."), -1);
772                 edit_vcard();
773                 FreeStrBuf(&Buf);
774                 return;
775         }
776
777         SubStr = NewStrBuf();
778         it = GetNewHashPos(DefineToToken, 0);
779         while (GetNextHashPos(DefineToToken, it, &len, &Key, &pv) && 
780                (pv != NULL)) 
781         {
782                 char buf[32];
783                 long blen;
784                 vcField *t = (vcField*) pv;
785
786                 if (t->Sub != NULL){
787                         vcField *Sub;
788                         FlushStrBuf(SubStr);
789                         itSub = GetNewHashPos(DefineToToken, 0);
790                         while (GetNextHashPos(DefineToToken, itSub, &len, &Key, &pv) && 
791                                (pv != NULL)) 
792                         {
793                                 Sub = (vcField*) pv;
794                                 if (Sub->parentCVal == t->cval) {
795                                         if (StrLength(SubStr) > 0)
796                                                 StrBufAppendBufPlain(SubStr, HKEY(";"), 0);
797
798
799
800                                         blen = snprintf(buf, sizeof(buf), "%ld", Sub->cval);
801                                         s = SSubBstr(postVcard, buf, blen);
802                         
803                                         if ((s != NULL) && (StrLength(s) > 0)) {
804                                                 /// todo: utf8 qp
805                                                 StrBufAppendBuf(SubStr, s, 0);
806                                         }
807                                 }
808                         }
809                         if (StrLength(SubStr) > 0) {
810                                 vcard_add_prop(v, t->STR.Key, ChrPtr(SubStr));
811                         }
812                         DeleteHashPos(&itSub);
813                 }
814                 else if (t->parentCVal == 0) {
815                         blen = snprintf(buf, sizeof(buf), "%ld", t->cval);
816                         s = SSubBstr(postVcard, buf, blen);
817                         
818                         if ((s != NULL) && (StrLength(s) > 0)) {
819                                 vcard_add_prop(v, t->STR.Key, ChrPtr(s));
820                         }
821                 }
822         }
823         DeleteHashPos(&it);
824
825         s = sbstr("other_inetemail");
826         if (StrLength(s) > 0) {
827                 FlushStrBuf(SubStr);
828                 while (StrBufSipLine(SubStr, s, &Pos), ((Pos!=StrBufNOTNULL) && (Pos!=NULL)) ) {
829                         if (StrLength(SubStr) > 0) {
830                                 vcard_add_prop(v, "email;internet", ChrPtr(SubStr));
831                         }
832                 }
833         }
834
835         FreeStrBuf(&SubStr);
836
837
838         serialized_vcard = vcard_serialize(v);
839         vcard_free(v);
840         if (serialized_vcard == NULL) {
841                 AppendImportantMessage(_("An error has occurred."), -1);
842                 edit_vcard();
843                 FreeStrBuf(&Buf);
844                 return;
845         }
846
847         printf("%s", serialized_vcard);
848         serv_write(HKEY("Content-type: text/x-vcard; charset=UTF-8\n"));
849         serv_write(HKEY("\n"));
850         serv_printf("%s\r\n", serialized_vcard);
851         serv_write(HKEY("000\n"));
852         free(serialized_vcard);
853
854         if (!strcmp(bstr("return_to"), "select_user_to_edit")) {
855                 select_user_to_edit(NULL);
856         }
857         else if (!strcmp(bstr("return_to"), "do_welcome")) {
858                 do_welcome();
859         }
860         else if (!IsEmptyStr(bstr("return_to"))) {
861                 http_redirect(bstr("return_to"));
862         }
863         else {
864                 readloop(readnew, eUseDefault);
865         }
866         FreeStrBuf(&Buf);
867 }
868
869 /******************************************************************************
870  *              Render Addressbooks                                           *
871  ******************************************************************************/
872
873 typedef struct _vcardview_struct {
874         long is_singlecard;
875         HashList *addrbook;
876
877 } vcardview_struct;
878
879 int vcard_GetParamsGetServerCall(SharedMessageStatus *Stat, 
880                                  void **ViewSpecific, 
881                                  long oper, 
882                                  char *cmd, 
883                                  long len,
884                                  char *filter,
885                                  long flen)
886 {
887         vcardview_struct *VS;
888
889         VS = (vcardview_struct*) malloc (sizeof(vcardview_struct));
890         memset(VS, 0, sizeof(vcardview_struct));
891         *ViewSpecific = (void*)VS;
892
893         VS->is_singlecard = ibstr("is_singlecard");
894         if (VS->is_singlecard != 1) {
895                 VS->addrbook = NewHash(0, NULL);
896                 if (oper == do_search) {
897                         snprintf(cmd, len, "MSGS SEARCH|%s", bstr("query"));
898                 }
899                 else {
900                         strcpy(cmd, "MSGS ALL");
901                 }
902                 Stat->maxmsgs = 9999999;
903         }
904         return 200;
905 }
906
907 int vcard_LoadMsgFromServer(SharedMessageStatus *Stat, 
908                             void **ViewSpecific, 
909                             message_summary* Msg, 
910                             int is_new, 
911                             int i)
912 {
913         wcsession *WCC = WC;
914         WCTemplputParams *TP = NULL;
915         WCTemplputParams SubTP;
916         vcardview_struct *VS;
917         wc_mime_attachment *VCMime = NULL;
918         struct vCard *v;
919         addrbookent* abEntry;
920
921         VS = (vcardview_struct*) *ViewSpecific;
922
923         VCMime = load_vcard(Msg);
924         if (VCMime == NULL)
925                 return 0;
926
927         v = VCardLoad(VCMime->Data);
928
929         if (v == NULL) return 0;
930
931         abEntry = (addrbookent*) malloc(sizeof(addrbookent));
932         memset(abEntry, 0, sizeof(addrbookent));
933         abEntry->name = NewStrBuf();
934         abEntry->VC = NewHash(0, lFlathash);
935         abEntry->ab_msgnum = Msg->msgnum;
936
937         parse_vcard(WCC->WBuf, v, abEntry->VC, VCMime);
938
939         memset(&SubTP, 0, sizeof(WCTemplputParams));    
940         StackContext(TP, &SubTP, abEntry, CTX_VCARD, 0, NULL);
941
942         // No, don't display the name, it just shits all over the screen
943         // DoTemplate(HKEY("vcard_list_name"), WCC->WBuf, &SubTP);
944
945         UnStackContext(&SubTP);
946
947         if (StrLength(abEntry->name) == 0) {
948                 StrBufPlain(abEntry->name, _("(no name)"), -1);
949         }
950
951         syslog(LOG_DEBUG, "abEntry->name : %s", ChrPtr(abEntry->name));
952
953         vcard_free(v);
954         
955         Put(VS->addrbook, SKEY(abEntry->name), abEntry, deleteAbEnt);
956         return 0;
957 }
958
959
960 /*
961  * Render the address book using info we gathered during the scan
962  *
963  * addrbook     the addressbook to render
964  * num_ab       the number of the addressbook
965  */
966 static int NAMESPERPAGE = 60;
967 void do_addrbook_view(vcardview_struct* VS) {
968         long i = 0;
969         int num_pages = 0;
970         int tabfirst = 0;
971         int tablast = 0;
972         StrBuf **tablabels;
973         int num_ab = GetCount(VS->addrbook);
974         HashList *headlines;
975         wcsession *WCC = WC;
976
977         WCTemplputParams *TP = NULL;
978         WCTemplputParams SubTP;
979
980         memset(&SubTP, 0, sizeof(WCTemplputParams));    
981         
982         if (num_ab == 0) {
983                 do_template("vcard_list_empty");
984                 return;
985         }
986
987         if (num_ab > 1) {
988                 SortByHashKey(VS->addrbook, 1);
989         }
990
991         num_pages = (GetCount(VS->addrbook) / NAMESPERPAGE) + 1;
992
993         tablabels = malloc(num_pages * sizeof (StrBuf *));
994         if (tablabels == NULL) {
995                 return;
996         }
997
998         headlines = NewHash(0, lFlathash);
999         for (i=0; i<num_pages; ++i) {
1000                 void *v1 = NULL;
1001                 void *v2 = NULL;
1002                 long hklen1, hklen2;
1003                 const char *c1, *c2;
1004                 StrBuf *headline;
1005                 addrbookent *a1, *a2;
1006
1007                 tabfirst = i * NAMESPERPAGE;
1008                 tablast = tabfirst + NAMESPERPAGE - 1;
1009                 if (tablast > (num_ab - 1)) tablast = (num_ab - 1);
1010
1011                 headline = NewStrBufPlain(NULL, StrLength(v1) + StrLength(v2) + 10);
1012                 if (GetHashAt(VS->addrbook, tabfirst, &hklen1, &c1, &v1)) {
1013                         a1 = (addrbookent*) v1;
1014                         StrBufAppendBuf(headline, a1->name, 0);
1015                         StrBuf_Utf8StrCut(headline, 3);
1016                         if (GetHashAt(VS->addrbook, tablast, &hklen2, &c2, &v2)) {
1017
1018                                 a2 = (addrbookent*) v2;
1019                                 StrBufAppendBufPlain(headline, HKEY(" - "), 0);
1020                                 StrBufAppendBuf(headline, a2->name, 0);
1021                                 StrBuf_Utf8StrCut(headline, 9);
1022                         }
1023                 }
1024                 tablabels[i] = headline;
1025                 Put(headlines, LKEY(i), headline, HFreeStrBuf);
1026         }
1027         StrTabbedDialog(WC->WBuf, num_pages, tablabels);
1028         StackContext(TP, &SubTP, VS->addrbook, CTX_VCARD_LIST, 0, NULL);
1029
1030         DoTemplate(HKEY("vcard_list"), WCC->WBuf, &SubTP);
1031         UnStackContext(&SubTP);
1032         DeleteHash(&headlines);
1033         free(tablabels);
1034         StrBufAppendBufPlain(WCC->WBuf, HKEY("</div>"), 0);/* closes: id=global */
1035 }
1036
1037
1038
1039 int vcard_RenderView_or_Tail(SharedMessageStatus *Stat, void **ViewSpecific, long oper)
1040 {
1041         const StrBuf *Mime;
1042         vcardview_struct *VS;
1043
1044         VS = (vcardview_struct*) *ViewSpecific;
1045         if (VS->is_singlecard) {
1046                 read_message(WC->WBuf, HKEY("view_message"), lbstr("startmsg"), NULL, &Mime, NULL);
1047         }
1048         else {
1049                 do_addrbook_view(VS);   /* Render the address book */
1050         }
1051         return 0;
1052 }
1053
1054 int vcard_Cleanup(void **ViewSpecific)
1055 {
1056         vcardview_struct *VS;
1057
1058         VS = (vcardview_struct*) *ViewSpecific;
1059         wDumpContent(1);
1060         if ((VS != NULL) && (VS->addrbook != NULL)) {
1061                 DeleteHash(&VS->addrbook);
1062         }
1063         if (VS != NULL)  {
1064                 free(VS);
1065         }
1066
1067         return 0;
1068 }
1069
1070 void render_MIME_VCard(StrBuf *Target, WCTemplputParams *TP, StrBuf *FoundCharset)
1071 {
1072         wc_mime_attachment *Mime = (wc_mime_attachment *) CTX(CTX_MIME_ATACH);
1073         wcsession *WCC = WC;
1074         if (StrLength(Mime->Data) == 0) {
1075                 MimeLoadData(Mime);
1076         }
1077         if (StrLength(Mime->Data) > 0) {
1078                 struct vCard *v;
1079                 StrBuf *Buf;
1080
1081                 Buf = NewStrBuf();
1082                 /** If it's my vCard I can edit it */
1083                 if (    (!strcasecmp(ChrPtr(WCC->CurRoom.name), USERCONFIGROOM))
1084                         || ((StrLength(WCC->CurRoom.name) > 11) &&
1085                             (!strcasecmp(&(ChrPtr(WCC->CurRoom.name)[11]), USERCONFIGROOM)))
1086                         || (WCC->CurRoom.view == VIEW_ADDRESSBOOK)
1087                         ) {
1088                         StrBufAppendPrintf(Buf, "<a href=\"edit_vcard?msgnum=%ld?partnum=%s\">",
1089                                 Mime->msgnum, ChrPtr(Mime->PartNum));
1090                         StrBufAppendPrintf(Buf, "[%s]</a>", _("edit"));
1091                 }
1092
1093                 /* In all cases, display the full card */
1094
1095                 v = VCardLoad(Mime->Data);
1096
1097                 if (v != NULL) {
1098                         WCTemplputParams *TP = NULL;
1099                         WCTemplputParams SubTP;
1100                         addrbookent ab;
1101                         memset(&ab, 0, sizeof(addrbookent));
1102
1103                         ab.VC = NewHash(0, lFlathash);
1104                         ab.ab_msgnum = Mime->msgnum;
1105
1106                         parse_vcard(Target, v, ab.VC, Mime);
1107
1108                         memset(&SubTP, 0, sizeof(WCTemplputParams));    
1109                         StackContext(TP, &SubTP, &ab, CTX_VCARD, 0, NULL);
1110
1111                         DoTemplate(HKEY("vcard_msg_display"), Target, &SubTP);
1112                         UnStackContext(&SubTP);
1113                         DeleteHash(&ab.VC);
1114                         vcard_free(v);
1115
1116                 }
1117                 else {
1118                         StrBufPlain(Buf, _("failed to load vcard"), -1);
1119                 }
1120                 FreeStrBuf(&Mime->Data);
1121                 Mime->Data = Buf;
1122         }
1123
1124 }
1125
1126 void 
1127 ServerStartModule_VCARD
1128 (void)
1129 {
1130 }
1131
1132 void 
1133 ServerShutdownModule_VCARD
1134 (void)
1135 {
1136         DeleteHash(&DefineToToken);
1137         DeleteHash(&vcNames);
1138         DeleteHash(&VCTokenToDefine);
1139 }
1140
1141 void 
1142 InitModule_VCARD
1143 (void)
1144 {
1145         StrBuf *Prefix  = NewStrBufPlain(HKEY("VC:"));
1146         DefineToToken   = NewHash(1, lFlathash);
1147         vcNames         = NewHash(1, lFlathash);
1148         VCTokenToDefine = NewHash(1, NULL);
1149         autoRegisterTokens(&VCEnumCounter, VCStrE, Prefix, 0, 0);
1150         FreeStrBuf(&Prefix);
1151
1152         REGISTERTokenParamDefine(NAMESPERPAGE);
1153
1154
1155         RegisterCTX(CTX_VCARD);
1156         RegisterCTX(CTX_VCARD_LIST);
1157         RegisterCTX(CTX_VCARD_TYPE);
1158
1159         RegisterReadLoopHandlerset(
1160                 VIEW_ADDRESSBOOK,
1161                 vcard_GetParamsGetServerCall,
1162                 NULL,
1163                 NULL,
1164                 NULL, 
1165                 vcard_LoadMsgFromServer,
1166                 vcard_RenderView_or_Tail,
1167                 vcard_Cleanup,
1168                 NULL);
1169
1170         RegisterIterator("MAIL:VCARDS", 0, NULL, CtxGetVcardList, NULL, NULL, CTX_VCARD, CTX_VCARD_LIST, IT_NOFLAG);
1171
1172
1173         WebcitAddUrlHandler(HKEY("edit_vcard"), "", 0, edit_vcard, 0);
1174         WebcitAddUrlHandler(HKEY("submit_vcard"), "", 0, submit_vcard, 0);
1175         WebcitAddUrlHandler(HKEY("vcardphoto"), "", 0, display_vcard_photo_img, NEED_URL);
1176
1177         RegisterNamespace("VC:ITEM", 2, 2, tmpl_vcard_item, preeval_vcard_item, CTX_VCARD);
1178         RegisterNamespace("VC:CTXITEM", 1, 1, tmpl_vcard_context_item, NULL, CTX_VCARD_TYPE);
1179         RegisterNamespace("VC:NAME", 1, 1, tmpl_vcard_name_str, preeval_vcard_name_str, CTX_VCARD);
1180         RegisterNamespace("VC:MSGNO", 0, 1, tmpl_vcard_msgno, NULL, CTX_VCARD);
1181         RegisterNamespace("VC:CTXNAME", 1, 1, tmpl_vcard_context_name_str, NULL, CTX_VCARD_TYPE);
1182         REGISTERTokenParamDefine(FlatString);
1183         REGISTERTokenParamDefine(StringCluster);
1184         REGISTERTokenParamDefine(PhoneNumber);
1185         REGISTERTokenParamDefine(EmailAddr);
1186         REGISTERTokenParamDefine(Street);
1187         REGISTERTokenParamDefine(Number);
1188         REGISTERTokenParamDefine(AliasFor);
1189         REGISTERTokenParamDefine(Base64BinaryAttachment);
1190         REGISTERTokenParamDefine(TerminateList);
1191         REGISTERTokenParamDefine(Address);
1192
1193         RegisterConditional("VC:HAVE:TYPE",                     1,      conditional_VC_Havetype, CTX_VCARD);
1194         RegisterConditional("COND:VC:SUPPRESS_EMAIL_FIELDS",    1,      conditional_VC_SuppressEmailFields, CTX_VCARD);
1195
1196         RegisterFilteredIterator("VC:TYPE", 1, DefineToToken, NULL, NULL, NULL, filter_VC_ByType, CTX_VCARD_TYPE, CTX_VCARD, IT_NOFLAG);
1197         RegisterFilteredIterator("VC:TYPE:ITEMS", 0, NULL, getContextVcard, NULL, NULL, filter_VC_ByContextType, CTX_STRBUF, CTX_VCARD_TYPE, IT_NOFLAG);
1198
1199         RegisterMimeRenderer(HKEY("text/x-vcard"), render_MIME_VCard, 1, 201);
1200         RegisterMimeRenderer(HKEY("text/vcard"), render_MIME_VCard, 1, 200);
1201 }