4fea084856abc38041e5f0dcde5365099e1e1633
[citadel.git] / webcit / subst.c
1 /*
2  * $Id$
3  */
4 /**
5  * \defgroup Subst Variable substitution type stuff
6  * \ingroup CitadelConfig
7  */
8
9 /*@{*/
10
11 #include "sysdep.h"
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15 #include <dirent.h>
16 #include <errno.h>
17
18 #include "webcit.h"
19 #include "webserver.h"
20
21 extern char *static_dirs[PATH_MAX];  /**< Disk representation */
22
23 HashList *WirelessTemplateCache;
24 HashList *WirelessLocalTemplateCache;
25 HashList *TemplateCache;
26 HashList *LocalTemplateCache;
27
28 HashList *GlobalNS;
29
30 typedef struct _TemplateToken {
31         const char *pTokenStart;
32         size_t TokenStart;
33         size_t TokenEnd;
34         const char *pTokenEnd;
35
36         const char *pName;
37         size_t NameEnd;
38
39         int HaveParameters;
40         int nParameters;
41         size_t ParamStart [10];
42         size_t ParamEnd [10];
43 } WCTemplateToken;
44
45 typedef struct _WCTemplate {
46         StrBuf *Data;
47         int nTokensUsed;
48         int TokenSpace;
49         WCTemplateToken **Tokens;
50 } WCTemplate;
51
52 typedef void (*WCHandlerFunc)(int nArgs, WCTemplateToken **Tokens); /*TODO: subset of that */
53 typedef struct _HashHandler {
54         int nMinArgs;
55         int nMaxArgs;
56         WCHandlerFunc HandlerFunc;
57 }HashHandler;
58
59 void RegisterNS(const char *NSName, long len, int nMinArgs, int nMaxArgs, WCHandlerFunc HandlerFunc)
60 {
61         HashHandler *NewHandler;
62         
63         NewHandler = (HashHandler*) malloc(sizeof(HashHandler));
64         NewHandler->nMinArgs = nMinArgs;
65         NewHandler->nMaxArgs = nMaxArgs;
66         NewHandler->HandlerFunc = HandlerFunc;  
67         Put(GlobalNS, NSName, len, NewHandler, NULL);
68 }
69
70
71 /**
72  * \brief debugging function to print array to log
73  */
74 void VarPrintTransition(void *vVar1, void *vVar2, int odd){}
75 /**
76  * \brief debugging function to print array to log
77  */
78 void VarPrintEntry(const char *Key, void *vSubst, int odd)
79 {
80         wcsubst *ptr;
81         lprintf(1,"Subst[%s] : ", Key);
82         ptr = (wcsubst*) vSubst;
83
84         switch(ptr->wcs_type) {
85         case WCS_STRING:
86                 lprintf(1, "  -> %s\n", ptr->wcs_value);
87                 break;
88         case WCS_SERVCMD:
89                 lprintf(1, "  -> Server [%s]\n", ptr->wcs_value);
90                 break;
91         case WCS_FUNCTION:
92                 lprintf(1, "  -> function at [%0xd]\n", ptr->wcs_function);
93                 break;
94         default:
95                 lprintf(1,"  WARNING: invalid type: [%ld]!\n", ptr->wcs_type);
96         }
97 }
98
99
100
101 /**
102  * \brief Clear out the list of substitution variables local to this session
103  */
104 void clear_substs(struct wcsession *wc) {
105
106         if (wc->vars != NULL) {
107         
108                 DeleteHash(&wc->vars);
109         }
110 }
111
112 /**
113  * \brief Clear out the list of substitution variables local to this session
114  */
115 void clear_local_substs(void) {
116         clear_substs (WC);
117 }
118
119 /**
120  * \brief destructor; kill one entry.
121  */
122 void deletevar(void *data)
123 {
124         wcsubst *ptr = (wcsubst*)data;
125 //              if ((wc->vars->wcs_type == WCS_STRING)
126 //                 || (wc->vars->wcs_type == WCS_SERVCMD)) {
127         if (ptr->wcs_type != WCS_FUNCTION)
128                 free(ptr->wcs_value);
129         free(ptr);      
130 }
131
132 /**
133  * \brief Add a substitution variable (local to this session) (strlen version...)
134  * \param keyname the replacementstring to substitute
135  * \param keytype the kind of the key
136  * \param format the format string ala printf
137  * \param ... the arguments to substitute in the formatstring
138  */
139 void SVPRINTF(char *keyname, int keytype, const char *format,...)
140 {
141         va_list arg_ptr;
142         char wbuf[SIZ];
143         void *vPtr;
144         wcsubst *ptr = NULL;
145         size_t keylen;
146         struct wcsession *WCC = WC;
147         
148         keylen = strlen(keyname);
149         /**
150          * First look if we're doing a replacement of
151          * an existing key
152          */
153         /*PrintHash(WCC->vars, VarPrintTransition, VarPrintEntry);*/
154         if (GetHash(WCC->vars, keyname, keylen, &vPtr)) {
155                 ptr = (wcsubst*)vPtr;
156                 if (ptr->wcs_value != NULL)
157                         free(ptr->wcs_value);
158         }
159         else    /** Otherwise allocate a new one */
160         {
161                 ptr = (wcsubst *) malloc(sizeof(wcsubst));
162                 safestrncpy(ptr->wcs_key, keyname, sizeof ptr->wcs_key);
163                 Put(WCC->vars, keyname, keylen, ptr,  deletevar);
164         }
165
166         /** Format the string and save it */
167
168         va_start(arg_ptr, format);
169         vsnprintf(wbuf, sizeof wbuf, format, arg_ptr);
170         va_end(arg_ptr);
171
172         ptr->wcs_function = NULL;
173         ptr->wcs_type = keytype;
174         ptr->wcs_value = strdup(wbuf);
175 }
176
177 /**
178  * \brief Add a substitution variable (local to this session)
179  * \param keyname the replacementstring to substitute
180  * \param keytype the kind of the key
181  * \param format the format string ala printf
182  * \param ... the arguments to substitute in the formatstring
183  */
184 void svprintf(char *keyname, size_t keylen, int keytype, const char *format,...)
185 {
186         va_list arg_ptr;
187         char wbuf[SIZ];
188         void *vPtr;
189         wcsubst *ptr = NULL;
190         struct wcsession *WCC = WC;
191         size_t len;
192         
193         /**
194          * First look if we're doing a replacement of
195          * an existing key
196          */
197         /*PrintHash(WCC->vars, VarPrintTransition, VarPrintEntry);*/
198         if (GetHash(WCC->vars, keyname, keylen, &vPtr)) {
199                 ptr = (wcsubst*)vPtr;
200                 if (ptr->wcs_value != NULL)
201                         free(ptr->wcs_value);
202         }
203         else    /** Otherwise allocate a new one */
204         {
205                 ptr = (wcsubst *) malloc(sizeof(wcsubst));
206                 safestrncpy(ptr->wcs_key, keyname, sizeof ptr->wcs_key);
207                 Put(WCC->vars, keyname, keylen, ptr,  deletevar);
208         }
209
210         /** Format the string and save it */
211
212         va_start(arg_ptr, format);
213         len = vsnprintf(wbuf, sizeof wbuf, format, arg_ptr);
214         va_end(arg_ptr);
215
216         ptr->wcs_value = (char*) malloc(len + 1);
217         memcpy(ptr->wcs_value, wbuf, len + 1);
218         ptr->wcs_function = NULL;
219         ptr->wcs_type = keytype;
220 }
221
222 /**
223  * \brief Add a substitution variable (local to this session)
224  * \param keyname the replacementstring to substitute
225  * \param keytype the kind of the key
226  * \param format the format string ala printf
227  * \param ... the arguments to substitute in the formatstring
228  */
229 void SVPut(char *keyname, size_t keylen, int keytype, char *Data)
230 {
231         void *vPtr;
232         wcsubst *ptr = NULL;
233         struct wcsession *WCC = WC;
234
235         
236         /**
237          * First look if we're doing a replacement of
238          * an existing key
239          */
240         /*PrintHash(WCC->vars, VarPrintTransition, VarPrintEntry);*/
241         if (GetHash(WCC->vars, keyname, keylen, &vPtr)) {
242                 ptr = (wcsubst*)vPtr;
243                 if (ptr->wcs_value != NULL)
244                         free(ptr->wcs_value);
245         }
246         else    /** Otherwise allocate a new one */
247         {
248                 ptr = (wcsubst *) malloc(sizeof(wcsubst));
249                 safestrncpy(ptr->wcs_key, keyname, sizeof ptr->wcs_key);
250                 Put(WCC->vars, keyname, keylen, ptr,  deletevar);
251         }
252
253         ptr->wcs_function = NULL;
254         ptr->wcs_type = keytype;
255         ptr->wcs_value = strdup(Data);
256 }
257
258 /**
259  * \brief Add a substitution variable (local to this session) that does a callback
260  * \param keyname the keystring to substitute
261  * \param fcn_ptr the function callback to give the substitution string
262  */
263 void SVCallback(char *keyname, size_t keylen, var_callback_fptr fcn_ptr)
264 {
265         wcsubst *ptr;
266         void *vPtr;
267         struct wcsession *WCC = WC;
268
269         /**
270          * First look if we're doing a replacement of
271          * an existing key
272          */
273         /*PrintHash(WCC->vars, VarPrintTransition, VarPrintEntry);*/
274         if (GetHash(WCC->vars, keyname, keylen, &vPtr)) {
275                 ptr = (wcsubst*)vPtr;
276                 if (ptr->wcs_value != NULL)
277                         free(ptr->wcs_value);
278         }
279         else    /** Otherwise allocate a new one */
280         {
281                 ptr = (wcsubst *) malloc(sizeof(wcsubst));
282                 safestrncpy(ptr->wcs_key, keyname, sizeof ptr->wcs_key);
283                 Put(WCC->vars, keyname, keylen, ptr,  deletevar);
284         }
285
286         ptr->wcs_value = NULL;
287         ptr->wcs_type = WCS_FUNCTION;
288         ptr->wcs_function = fcn_ptr;
289 }
290 inline void SVCALLBACK(char *keyname, var_callback_fptr fcn_ptr)
291 {
292         SVCallback(keyname, strlen(keyname), fcn_ptr);
293 }
294
295
296
297 /**
298  * \brief back end for print_value_of() ... does a server command
299  * \param servcmd server command to execute on the citadel server
300  */
301 void pvo_do_cmd(char *servcmd) {
302         char buf[SIZ];
303
304         serv_puts(servcmd);
305         serv_getln(buf, sizeof buf);
306
307         switch(buf[0]) {
308                 case '2':
309                 case '3':
310                 case '5':
311                         wprintf("%s\n", &buf[4]);
312                         break;
313                 case '1':
314                         fmout("CENTER");
315                         break;
316                 case '4':
317                         wprintf("%s\n", &buf[4]);
318                         serv_puts("000");
319                         break;
320         }
321 }
322
323 /**
324  * \brief Print the value of a variable
325  * \param keyname get a key to print
326  */
327 void print_value_of(const char *keyname, size_t keylen) {
328         struct wcsession *WCC = WC;
329         wcsubst *ptr;
330         void *fcn();
331         void *vVar;
332
333         /*if (WCC->vars != NULL) PrintHash(WCC->vars, VarPrintTransition, VarPrintEntry);*/
334         if (keyname[0] == '=') {
335                 DoTemplate(keyname+1, keylen - 1);
336         }
337         /** Page-local variables */
338         if ((WCC->vars!= NULL) && GetHash(WCC->vars, keyname, keylen, &vVar)) {
339                 ptr = (wcsubst*) vVar;
340                 switch(ptr->wcs_type) {
341                 case WCS_STRING:
342                         wprintf("%s", (const char*)ptr->wcs_value);
343                         break;
344                 case WCS_SERVCMD:
345                         pvo_do_cmd(ptr->wcs_value);
346                         break;
347                 case WCS_FUNCTION:
348                         (*ptr->wcs_function) ();
349                         break;
350                 default:
351                         lprintf(1,"WARNING: invalid value in SV-Hash at %s!", keyname);
352                 }
353         }
354 }
355
356
357 void PutNewToken(WCTemplate *Template, WCTemplateToken *NewToken)
358 {
359         if (Template->nTokensUsed + 1 >= Template->TokenSpace) {
360                 if (Template->TokenSpace <= 0) {
361                         Template->Tokens = (WCTemplateToken**)malloc(
362                                 sizeof(WCTemplateToken*) * 10);
363                         Template->TokenSpace = 10;
364                 }
365                 else {
366                         WCTemplateToken **NewTokens;
367                         NewTokens= (WCTemplateToken**)malloc(
368                                 sizeof(WCTemplateToken*) * 
369                                 Template->TokenSpace * 2);
370                         memcpy(NewTokens, Template->Tokens, 
371                                sizeof(WCTemplateToken) * Template->nTokensUsed);
372                         free(Template->Tokens);
373                         Template->TokenSpace *= 2;
374                         Template->Tokens = NewTokens;
375                 }
376         }
377         Template->Tokens[(Template->nTokensUsed)++] = NewToken;
378 }
379
380 WCTemplateToken *NewTemlpateSubstitute(const char *pStart, const char *pTmplStart, const char *pTmplEnd)
381 {
382         WCTemplateToken *NewToken = (WCTemplateToken*)malloc(sizeof(WCTemplateToken));
383
384         NewToken->pTokenStart = pTmplStart;
385         NewToken->TokenStart = pTmplStart - pStart;
386         NewToken->TokenEnd =  (pTmplEnd - pStart) - NewToken->TokenStart;
387         NewToken->pTokenEnd = pTmplEnd;
388         
389         NewToken->pName = pTmplStart + 2;
390         NewToken->NameEnd = NewToken->TokenEnd - 2;
391         NewToken->HaveParameters = 0;;
392         NewToken->nParameters = 0;
393         NewToken->ParamStart[0] = 0;
394         NewToken->ParamEnd[0] = 0;
395         return NewToken;
396 }
397
398 void FreeWCTemplate(void *vFreeMe)
399 {
400         int i;
401         WCTemplate *FreeMe = (WCTemplate*)vFreeMe;
402
403         if (FreeMe->TokenSpace > 0) {
404                 for (i = 0; i < FreeMe->nTokensUsed; i ++) {
405                         free(FreeMe->Tokens[i]);
406                 }
407                 free(FreeMe->Tokens);
408         }
409         free(FreeMe);
410 }
411
412 void EvaluateToken(StrBuf *Target, WCTemplateToken *Token)
413 {
414         void *vVar;
415 // much output, since pName is not terminated...
416 //      lprintf(1,"Doing token: %s\n",Token->pName);
417         if (GetHash(GlobalNS, Token->pName, Token->NameEnd, &vVar)) {
418                 HashHandler *Handler;
419                 Handler = (HashHandler*) vVar;
420                 if ((Token->nParameters < Handler->nMinArgs) || 
421                     (Token->nParameters < Handler->nMaxArgs)) {
422                         lprintf(1, "Handler [%s] doesn't work with %ld params", 
423                                 Token->pName,
424                                 Token->nParameters);
425                 }
426                 else {
427                         Handler->HandlerFunc(Token->nParameters,
428                                              &Token); /*TODO: subset of that */
429                 
430                         
431                 }
432         }
433         else {
434                 print_value_of(Token->pName, Token->NameEnd);
435         }
436 }
437
438 void ProcessTemplate(WCTemplate *Tmpl, StrBuf *Target)
439 {
440         int done = 0;
441         int i;
442         const char *pData, *pS;
443         long len;
444
445         pS = pData = ChrPtr(Tmpl->Data);
446         len = StrLength(Tmpl->Data);
447         i = 0;
448         while (!done) {
449                 if (i >= Tmpl->nTokensUsed) {
450                         StrBufAppendBufPlain(Target, pData, len, 0);
451                         done = 1;
452                 }
453                 else {
454                         StrBufAppendBufPlain(
455                                 Target, pData, 
456                                 Tmpl->Tokens[i]->pTokenStart - pData, 0);
457                         EvaluateToken(Target, Tmpl->Tokens[i]);
458                         pData = Tmpl->Tokens[i++]->pTokenEnd + 1;
459                 }
460         }
461 }
462
463
464
465 /**
466  * \brief Display a variable-substituted template
467  * \param templatename template file to load
468  */
469 void *load_template(StrBuf *filename, StrBuf *Key, HashList *PutThere)
470 {
471         int fd;
472         struct stat statbuf;
473         const char *pS, *pE, *pch, *Err;
474         int pos;
475         WCTemplate *NewTemplate;
476
477         fd = open(ChrPtr(filename), O_RDONLY);
478         if (fd <= 0) {
479                 lprintf(1, "ERROR: could not open template '%s' - %s\n",
480                         ChrPtr(filename), strerror(errno));
481                 return NULL;
482         }
483
484         if (fstat(fd, &statbuf) == -1) {
485                 lprintf(1, "ERROR: could not stat template '%s' - %s\n",
486                         ChrPtr(filename), strerror(errno));
487                 return NULL;
488         }
489
490         NewTemplate = (WCTemplate *) malloc(sizeof(WCTemplate));
491         NewTemplate->Data = NewStrBufPlain(NULL, statbuf.st_size);
492         NewTemplate->nTokensUsed = 0;
493         NewTemplate->TokenSpace = 0;
494         NewTemplate->Tokens = NULL;
495         if (StrBufReadBLOB(NewTemplate->Data, &fd, 1, statbuf.st_size, &Err) < 0) {
496                 close(fd);
497                 FreeWCTemplate(NewTemplate);
498                 lprintf(1, "ERROR: reading template '%s' - %s<br />\n",
499                         ChrPtr(filename), strerror(errno));
500                 return NULL;
501         }
502         close(fd);
503
504         pS = pch = ChrPtr(NewTemplate->Data);
505         pE = pS + StrLength(NewTemplate->Data);
506         while (pch < pE) {
507                 const char *pts, *pte;
508                 int InQuotes = 0;
509                 int InDoubleQuotes = 0;
510                 pos = (-1);
511                 for (; pch < pE; pch ++) {
512                         if ((*pch=='<')&&(*(pch + 1)=='?'))
513                                 break;
514                 }
515                 if (pch >= pE)
516                         continue;
517                 pts = pch;
518                 for (; pch < pE - 1; pch ++) {
519                         if (*pch == '"')
520                                 InDoubleQuotes = ! InDoubleQuotes;
521                         else if (*pch == '\'')
522                                 InQuotes = ! InQuotes;
523                         else if ((!InQuotes  && !InDoubleQuotes) &&
524                                  ((*pch!='\\')&&(*(pch + 1)=='>'))) {
525                                 pch ++;
526                                 break;
527                         }
528                 }
529                 if (pch + 1 >= pE)
530                         continue;
531                 pte = pch;
532                 PutNewToken(NewTemplate, NewTemlpateSubstitute(pS, pts, pte));
533                 pch ++;
534         }
535         Put(PutThere, ChrPtr(Key), StrLength(Key), NewTemplate, FreeWCTemplate);
536         return NewTemplate;
537 }
538
539 /**
540  * \brief Display a variable-substituted template
541  * \param templatename template file to load
542  */
543 void DoTemplate(const char *templatename, long len) 
544 {
545         HashList *Static;
546         HashList *StaticLocal;
547         void *vTmpl;
548
549         if (WC->is_mobile) {
550                 Static = WirelessTemplateCache;
551                 StaticLocal = WirelessLocalTemplateCache;
552         }
553         else {
554                 Static = TemplateCache;
555                 StaticLocal = LocalTemplateCache;
556         }
557
558         if (!GetHash(StaticLocal, templatename, len, &vTmpl) &&
559             !GetHash(Static, templatename, len, &vTmpl)) {
560                 printf ("didn't find %s\n", templatename);
561                 return;
562         }
563         if (vTmpl == NULL) 
564                 return;
565         ProcessTemplate(vTmpl, WC->WBuf);       
566 }
567
568 int LoadTemplateDir(const char *DirName, HashList *wireless, HashList *big)
569 {
570         StrBuf *FileName;
571         StrBuf *Tag;
572         StrBuf *Dir;
573         DIR *filedir = NULL;
574         struct dirent *filedir_entry;
575         int d_namelen;
576         int d_without_ext;
577         int IsMobile;
578         
579         Dir = NewStrBuf();
580         StrBufPrintf(Dir, "%s/t", DirName);
581         filedir = opendir (ChrPtr(Dir));
582         if (filedir == NULL) {
583                 return 0;
584         }
585
586         FileName = NewStrBuf();
587         Tag = NewStrBuf();
588         while ((filedir_entry = readdir(filedir)))
589         {
590                 char *MinorPtr;
591                 char *PStart;
592 #ifdef _DIRENT_HAVE_D_NAMELEN
593                 d_namelen = filedir_entry->d_namelen;
594 #else
595                 d_namelen = strlen(filedir_entry->d_name);
596 #endif
597                 d_without_ext = d_namelen;
598                 while ((d_without_ext > 0) && (filedir_entry->d_name[d_without_ext] != '.'))
599                         d_without_ext --;
600                 if ((d_without_ext == 0) || (d_namelen < 3))
601                         continue;
602
603                 IsMobile = (strstr(filedir_entry->d_name, ".m.html")!= NULL);
604                 PStart = filedir_entry->d_name;
605                 StrBufPrintf(FileName, "%s/%s", ChrPtr(Dir),  filedir_entry->d_name);
606                 MinorPtr = strchr(filedir_entry->d_name, '.');
607                 if (MinorPtr != NULL)
608                         *MinorPtr = '\0';
609                 StrBufPlain(Tag, filedir_entry->d_name, MinorPtr - filedir_entry->d_name);
610
611
612                 printf("%s %d %s\n",ChrPtr(FileName), IsMobile, ChrPtr(Tag));
613                 load_template(FileName, Tag, (IsMobile)?wireless:big);          
614         }
615         closedir(filedir);
616         FreeStrBuf(&FileName);
617         FreeStrBuf(&Tag);
618         FreeStrBuf(&Dir);
619         return 1;
620 }
621
622 void InitTemplateCache(void)
623 {
624         LoadTemplateDir(static_dirs[0],
625                         WirelessTemplateCache,
626                         TemplateCache);
627         LoadTemplateDir(static_dirs[1],
628                         WirelessLocalTemplateCache,
629                         LocalTemplateCache);
630 }
631
632 void tmplput_serv_ip(int nArgs, WCTemplateToken **Tokens)
633 {
634         wprintf("%d", WC->ctdl_pid);
635 }
636
637 void tmplput_serv_nodename(int nArgs, WCTemplateToken **Tokens)
638 {
639         escputs(serv_info.serv_nodename);
640 }
641
642 void tmplput_serv_humannode(int nArgs, WCTemplateToken **Tokens)
643 {
644         escputs(serv_info.serv_humannode);
645 }
646
647 void tmplput_serv_fqdn(int nArgs, WCTemplateToken **Tokens)
648 {
649         escputs(serv_info.serv_fqdn);
650 }
651
652 void tmmplput_serv_software(int nArgs, WCTemplateToken **Tokens)
653 {
654         escputs(serv_info.serv_software);
655 }
656
657 void tmplput_serv_rev_level(int nArgs, WCTemplateToken **Tokens)
658 {
659         wprintf("%d.%02d",
660                 serv_info.serv_rev_level / 100,
661                 serv_info.serv_rev_level % 100);
662 }
663
664 void tmmplput_serv_bbs_city(int nArgs, WCTemplateToken **Tokens)
665 {
666         escputs(serv_info.serv_bbs_city);
667 }
668
669 void tmplput_current_user(int nArgs, WCTemplateToken **Tokens)
670 {
671         escputs(WC->wc_fullname);
672 }
673
674 void tmplput_current_room(int nArgs, WCTemplateToken **Tokens)
675 {
676         escputs(WC->wc_roomname);
677 }
678
679 void 
680 InitModule_SUBST
681 (void)
682 {
683         RegisterNS(HKEY("SERV_PID"), 0, 0, tmplput_serv_ip);
684         RegisterNS(HKEY("SERV_NODENAME"), 0, 0, tmplput_serv_nodename);
685         RegisterNS(HKEY("SERV_HUMANNODE"), 0, 0, tmplput_serv_humannode);
686         RegisterNS(HKEY("SERV_FQDN"), 0, 0, tmplput_serv_fqdn);
687         RegisterNS(HKEY("SERV_SOFTWARE"), 0, 0, tmmplput_serv_software);
688         RegisterNS(HKEY("SERV_REV_LEVEL"), 0, 0, tmplput_serv_rev_level);
689         RegisterNS(HKEY("SERV_BBS_CITY"), 0, 0, tmmplput_serv_bbs_city);
690         RegisterNS(HKEY("CURRENT_USER"), 0, 0, tmplput_current_user);
691         RegisterNS(HKEY("CURRENT_ROOM"), 0, 0, tmplput_current_room);
692 }
693
694 /*@}*/