1 /*************************************************************************************************
2  * The Web interface of Tokyo Promenade
3  *                                                      Copyright (C) 2008-2012 Mikio Hirabayashi
4  * This file is part of Tokyo Promenade.
5  * This program is free software: you can redistribute it and/or modify it under the terms of
6  * the GNU General Public License as published by the Free Software Foundation, either version
7  * 3 of the License, or any later version.
8  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10  * See the GNU General Public License for more details.
11  * You should have received a copy of the GNU General Public License along with this program.
12  * If not, see <http://www.gnu.org/licenses/>.
13  *************************************************************************************************/
14 
15 
16 #include "common.h"
17 #include "scrext.h"
18 #if defined(MYFCGI)
19 #include <fcgi_stdio.h>
20 #endif
21 
22 #define SALTNAME       "[salt]"          // dummy user name of the salt
23 #define RIDDLENAME     "[riddle]"        // dummy user name of the riddle
24 #define ADMINNAME      "admin"           // user name of the administrator
25 
26 typedef struct {                         // type of structure for a record
27   int64_t id;                            // ID of the article
28   int64_t date;                          // date
29   const char *owner;                     // owner
30   const char *text;                      // text
31 } COMMENT;
32 
33 
34 /* global variables */
35 time_t g_starttime = 0;                  // start time of the process
36 TCMPOOL *g_mpool = NULL;                 // global memory pool
37 TCTMPL *g_tmpl = NULL;                   // template serializer
38 TCMAP *g_users = NULL;                   // user list
39 void *g_scrextproc = NULL;               // processor of the script extension
40 unsigned long g_eventcount = 0;          // event counter
41 const char *g_scriptname;                // script name
42 const char *g_scriptprefix;              // script prefix
43 const char *g_scriptpath;                // script path
44 const char *g_docroot;                   // document root
45 const char *g_database;                  // path of the database file
46 const char *g_password;                  // path of the password file
47 const char *g_upload;                    // path of the upload directory
48 const char *g_uploadpub;                 // public path of the upload directory
49 const char *g_scrext;                    // path of the script extension file
50 int64_t g_recvmax;                       // maximum size of received data
51 const char *g_mimerule;                  // mime dicision rule
52 const char *g_title;                     // site title
53 int g_searchnum;                         // number of articles in a search page
54 int g_listnum;                           // number of articles in a list page
55 int g_listabslen;                        // maximum length of an abstract text
56 int g_listabsobjnum;                     // maximum number of objects in an abstract text
57 int g_feedlistnum;                       // number of articles in a RSS feed
58 int g_feedlistabslen;                    // maximum length of an RSS abstract text
59 int g_feedlistabsobjnum;                 // maximum number of objects in an abstract text
60 int g_filenum;                           // number of files in a file list page
61 int g_sidebarnum;                        // number of items in the side bar
62 const char *g_commentmode;               // comment mode
63 const char *g_updatecmd;                 // path of the update command
64 int g_sessionlife;                       // lifetime of each session
65 const char *g_frontpage;                 // name of the front page
66 
67 
68 /* function prototypes */
69 int main(int argc, char **argv);
70 static int realmain(int argc, char **argv);
71 static void showerror(int code, const char *msg);
72 static void showcache(void);
73 static void readpasswd(void);
74 static bool writepasswd(void);
75 static void dosession(TCMPOOL *mpool);
76 static void setdberrmsg(TCLIST *emsgs, TCTDB *tdb, const char *msg);
77 static void setarthtml(TCMPOOL *mpool, TCMAP *cols, int64_t id, int bhl,
78                        int abslen, int absobjnum, bool tiny);
79 static TCLIST *searcharts(TCMPOOL *mpool, TCTDB *tdb, const char *cond, const char *expr,
80                           const char *order, int max, int skip, bool ls);
81 static void getdaterange(const char *expr, int64_t *lowerp, int64_t *upper);
82 static bool putfile(TCMPOOL *mpool, const char *path, const char *name,
83                     const char *ptr, int size);
84 static bool outfile(TCMPOOL *mpool, const char *path);
85 static TCLIST *searchfiles(TCMPOOL *mpool, const char *expr, const char *order,
86                            int max, int skip, bool thum);
87 static int comparecomments(const TCLISTDATUM *a, const TCLISTDATUM *b);
88 static bool doupdatecmd(TCMPOOL *mpool, const char *mode, const char *baseurl, const char *user,
89                         double now, int64_t id, TCMAP *ncols, TCMAP *ocols);
90 
91 
92 /* main routine */
main(int argc,char ** argv)93 int main(int argc, char **argv){
94 #if defined(MYFCGI)
95   g_starttime = time(NULL);
96   g_mpool = tcmpoolnew();
97   int rv = 0;
98   while(FCGI_Accept() >= 0){
99     g_eventcount++;
100     if(realmain(argc, argv) != 0) rv = 1;
101   }
102   tcmpooldel(g_mpool);
103   return rv;
104 #else
105   g_starttime = time(NULL);
106   g_mpool = tcmpoolnew();
107   int rv = realmain(argc, argv);
108   tcmpooldel(g_mpool);
109   return rv;
110 #endif
111 }
112 
113 
114 /* real main routine */
realmain(int argc,char ** argv)115 static int realmain(int argc, char **argv){
116   TCMPOOL *mpool = tcmpoolnew();
117   g_scriptname = getenv("SCRIPT_NAME");
118   if(!g_scriptname) g_scriptname = argv[0];
119   char *prefix = tcmpoolpushptr(mpool, tcregexreplace(g_scriptname, "\\.[a-zA-Z0-9]*$", ""));
120   g_scriptprefix = prefix;
121   g_scriptpath = getenv("SCRIPT_FILENAME");
122   if(!g_scriptpath) g_scriptpath = argv[0];
123   g_docroot = getenv("DOCUMENT_ROOT");
124   if(!g_docroot){
125     g_docroot = "/";
126     if(*g_scriptpath == '/'){
127       int diff = strlen(g_scriptpath) - strlen(g_scriptname);
128       if(diff > 0 && !strcmp(g_scriptpath + diff, g_scriptname))
129         g_docroot = tcmpoolpushptr(mpool, tcmemdup(g_scriptpath, diff));
130     }
131   }
132   char *barepath = tcmpoolpushptr(mpool, tcregexreplace(g_scriptpath, "\\.[a-zA-Z0-9]*$", ""));
133   char *tmplpath = tcmpoolpushptr(mpool, tcsprintf("%s.tmpl", barepath));
134   const char *rp = getenv("REQUEST_URI");
135   if(rp && *rp == '/'){
136     char *buf = tcmpoolpushptr(mpool, strdup(rp));
137     char *pv = strchr(buf, '#');
138     if(pv) *pv = '\0';
139     pv = strchr(buf, '?');
140     if(pv) *pv = '\0';
141     if(*buf != '\0') g_scriptname = buf;
142   }
143   if(g_tmpl){
144     if(g_users){
145       int64_t mtime;
146       if(tcstatfile(g_password, NULL, NULL, &mtime) && mtime >= g_starttime){
147         tcmapclear(g_users);
148         readpasswd();
149       }
150     }
151     dosession(mpool);
152   } else {
153     g_tmpl = tcmpoolpush(g_mpool, tctmplnew(), (void (*)(void *))tctmpldel);
154     if(tctmplload2(g_tmpl, tmplpath)){
155       g_database = tctmplconf(g_tmpl, "database");
156       if(!g_database) g_database = "promenade.tct";
157       g_password = tctmplconf(g_tmpl, "password");
158       if(g_password){
159         g_users = tcmpoolpushmap(g_mpool, tcmapnew2(TINYBNUM));
160         readpasswd();
161       }
162       g_upload = tctmplconf(g_tmpl, "upload");
163       g_uploadpub = NULL;
164       if(g_upload && *g_upload != '\0'){
165         if(!strchr(g_upload, '/')){
166           g_uploadpub = g_upload;
167         } else {
168           char *rpath = tcmpoolpushptr(g_mpool, tcrealpath(g_upload));
169           if(rpath){
170             if(tcstrfwm(rpath, g_docroot)){
171               int plen = strlen(g_docroot);
172               if(rpath[plen] == '/') g_uploadpub = rpath + plen;
173             }
174           }
175         }
176       }
177       g_scrext = tctmplconf(g_tmpl, "scrext");
178       if(g_scrext && *g_scrext != '\0')
179         g_scrextproc = tcmpoolpush(g_mpool, scrextnew(), scrextdel);
180       const char *rp = tctmplconf(g_tmpl, "recvmax");
181       g_recvmax = rp ? tcatoix(rp) : INT_MAX;
182       g_mimerule = tctmplconf(g_tmpl, "mimerule");
183       if(!g_mimerule) g_mimerule = "auto";
184       g_title = tctmplconf(g_tmpl, "title");
185       if(!g_title) g_title = "Tokyo Promenade";
186       rp = tctmplconf(g_tmpl, "searchnum");
187       g_searchnum = tclmax(rp ? tcatoi(rp) : 10, 1);
188       rp = tctmplconf(g_tmpl, "listnum");
189       g_listnum = tclmax(rp ? tcatoi(rp) : 10, 1);
190       rp = tctmplconf(g_tmpl, "listabslen");
191       g_listabslen = rp ? tcatoi(rp) : 256;
192       rp = tctmplconf(g_tmpl, "listabsobjnum");
193       g_listabsobjnum = rp ? tcatoi(rp) : -1;
194       rp = tctmplconf(g_tmpl, "feedlistnum");
195       g_feedlistnum = tclmax(rp ? tcatoi(rp) : 10, 1);
196       rp = tctmplconf(g_tmpl, "feedlistabslen");
197       g_feedlistabslen = rp ? tcatoi(rp) : 256;
198       rp = tctmplconf(g_tmpl, "feedlistabsobjnum");
199       g_feedlistabsobjnum = rp ? tcatoi(rp) : -1;
200       rp = tctmplconf(g_tmpl, "filenum");
201       g_filenum = tclmax(rp ? tcatoi(rp) : 10, 1);
202       rp = tctmplconf(g_tmpl, "sidebarnum");
203       g_sidebarnum = tclmax(rp ? tcatoi(rp) : 0, 0);
204       g_commentmode = tctmplconf(g_tmpl, "commentmode");
205       if(!g_commentmode) g_commentmode = "";
206       g_updatecmd = tctmplconf(g_tmpl, "updatecmd");
207       if(!g_updatecmd) g_updatecmd = "";
208       rp = tctmplconf(g_tmpl, "sessionlife");
209       g_sessionlife = tclmax(rp ? tcatoi(rp) : 0, 0);
210       g_frontpage = tctmplconf(g_tmpl, "frontpage");
211       if(!g_frontpage) g_frontpage = "";
212       TCMAP *conf = g_tmpl->conf;
213       tcmapiterinit(conf);
214       while((rp = tcmapiternext2(conf)) != NULL){
215         const char *pv = tcmapiterval2(rp);
216         char *name = tcmpoolpushptr(g_mpool, tcsprintf("TP_%s", rp));
217         tcstrtoupper(name);
218         setenv(name, pv, 1);
219       }
220       if(g_scrextproc){
221         scrextsetmapvar(g_scrextproc, "_conf", conf);
222         scrextload(g_scrextproc, g_scrext);
223       }
224       dosession(mpool);
225     } else {
226       showerror(500, "The template file is missing.");
227     }
228   }
229   tcmpooldel(mpool);
230   return 0;
231 }
232 
233 
234 /* show the error page */
showerror(int code,const char * msg)235 static void showerror(int code, const char *msg){
236   switch(code){
237     case 400:
238       printf("Status: %d Bad Request\r\n", code);
239       break;
240     case 404:
241       printf("Status: %d File Not Found\r\n", code);
242       break;
243     case 413:
244       printf("Status: %d Request Entity Too Large\r\n", code);
245       break;
246     case 500:
247       printf("Status: %d Internal Server Error\r\n", code);
248       break;
249     default:
250       printf("Status: %d Error\r\n", code);
251       break;
252   }
253   printf("Content-Type: text/plain; charset=UTF-8\r\n");
254   printf("\r\n");
255   printf("%s\n", msg);
256 }
257 
258 
259 /* show the not-modified page */
showcache(void)260 static void showcache(void){
261   printf("Status: 304 Not Modified\r\n");
262   printf("\r\n");
263 }
264 
265 
266 /* read the password file */
readpasswd(void)267 static void readpasswd(void){
268   if(!g_password) return;
269   TCLIST *lines = tcreadfilelines(g_password);
270   if(!lines) return;
271   int lnum = tclistnum(lines);
272   for(int i = 0; i < lnum; i++){
273     const char *line = tclistval2(lines, i);
274     const char *pv = strchr(line, ':');
275     if(!pv) continue;
276     tcmapputkeep(g_users, line, pv - line, pv + 1, strlen(pv + 1));
277   }
278   tclistdel(lines);
279 }
280 
281 
282 /* write the password file */
writepasswd(void)283 static bool writepasswd(void){
284   if(!g_password) return false;
285   bool err = false;
286   TCXSTR *xstr = tcxstrnew();
287   tcmapiterinit(g_users);
288   const char *name;
289   while((name = tcmapiternext2(g_users)) != NULL){
290     const char *value = tcmapiterval2(name);
291     tcxstrprintf(xstr, "%s:%s\n", name, value);
292   }
293   if(!tcwritefile(g_password, tcxstrptr(xstr), tcxstrsize(xstr))) err = true;
294   tcxstrdel(xstr);
295   return !err;
296 }
297 
298 
299 /* process each session */
dosession(TCMPOOL * mpool)300 static void dosession(TCMPOOL *mpool){
301   // download a file
302   const char *rp = getenv("HTTP_IF_MODIFIED_SINCE");
303   int64_t p_ifmod = rp ? tcstrmktime(rp) : 0;
304   rp = getenv("PATH_INFO");
305   if(rp && *rp == '/'){
306     rp++;
307     if(!g_upload){
308       showerror(404, "The upload directory is missing.");
309       return;
310     }
311     if(*rp == '\0' || strchr(rp, '/')){
312       showerror(404, "The request path is invalid.");
313       return;
314     }
315     const char *path = tcmpoolpushptr(mpool, tcsprintf("%s/%s", g_upload, rp));
316     int64_t mtime;
317     if(!tcstatfile(path, NULL, NULL, &mtime)){
318       showerror(404, "The requested file is missing.");
319       return;
320     }
321     if(mtime <= p_ifmod){
322       showcache();
323       return;
324     }
325     FILE *ifp = tcmpoolpush(mpool, fopen(path, "rb"), (void (*)(void *))fclose);
326     if(!ifp){
327       showerror(404, "The requested file is missing.");
328       return;
329     }
330     printf("Content-Type: %s\r\n", mimetype(rp));
331     printf("Cache-Control: no-cache\r\n");
332     char numbuf[NUMBUFSIZ];
333     tcdatestrhttp(mtime, 0, numbuf);
334     printf("Last-Modified: %s\r\n", numbuf);
335     printf("\r\n");
336     int c;
337     while((c = fgetc(ifp)) != EOF){
338       putchar(c);
339     }
340     return;
341   }
342   // prepare session-scope variables
343   TCMAP *vars = tcmpoolpushmap(mpool, tcmapnew2(TINYBNUM));
344   TCLIST *emsgs = tcmpoollistnew(mpool);
345   TCMAP *params = tcmpoolpushmap(mpool, tcmapnew2(TINYBNUM));
346   double now = tctime();
347   // read query parameters
348   rp = getenv("CONTENT_LENGTH");
349   bool post = false;
350   if(rp && *rp != '\0'){
351     int clen = tcatoi(rp);
352     if(clen > g_recvmax){
353       showerror(413, "The entity body was too long.");
354       return;
355     }
356     char *cbuf = tcmpoolmalloc(mpool, clen + 1);
357     if(fread(cbuf, 1, clen, stdin) != clen){
358       showerror(500, "Reading the entity body was failed.");
359       return;
360     }
361     tcwwwformdecode2(cbuf, clen, getenv("CONTENT_TYPE"), params);
362     post = true;
363   }
364   rp = getenv("QUERY_STRING");
365   if(rp) tcwwwformdecode(rp, params);
366   rp = getenv("HTTP_COOKIE");
367   if(rp) tcwwwformdecode(rp, params);
368   const char *p_user = tcstrskipspc(tcmapget4(params, "user", ""));
369   const char *p_pass = tcstrskipspc(tcmapget4(params, "pass", ""));
370   const char *p_format = tcstrskipspc(tcmapget4(params, "format", ""));
371   const char *p_act = tcstrskipspc(tcmapget4(params, "act", ""));
372   int64_t p_id = tcatoi(tcmapget4(params, "id", ""));
373   const char *p_name = tcstrskipspc(tcmapget4(params, "name", ""));
374   const char *p_order = tcstrskipspc(tcmapget4(params, "order", ""));
375   const char *p_adjust = tcstrskipspc(tcmapget4(params, "adjust", ""));
376   const char *p_expr = tcstrskipspc(tcmapget4(params, "expr", ""));
377   const char *p_cond = tcstrskipspc(tcmapget4(params, "cond", ""));
378   int p_page = tclmax(tcatoi(tcmapget4(params, "page", "")), 1);
379   const char *p_wiki = tcstrskipspc(tcmapget4(params, "wiki", ""));
380   bool p_mts = *tcmapget4(params, "mts", "") != '\0';
381   const char *p_hash = tcstrskipspc(tcmapget4(params, "hash", ""));
382   uint32_t p_seskey = tcatoi(tcmapget4(params, "seskey", ""));
383   const char *p_comowner = tcstrskipspc(tcmapget4(params, "comowner", ""));
384   const char *p_comtext = tcstrskipspc(tcmapget4(params, "comtext", ""));
385   const char *p_ummode = tcstrskipspc(tcmapget4(params, "ummode", ""));
386   const char *p_umname = tcstrskipspc(tcmapget4(params, "umname", ""));
387   const char *p_uminfo = tcstrskipspc(tcmapget4(params, "uminfo", ""));
388   const char *p_umpassone = tcstrskipspc(tcmapget4(params, "umpassone", ""));
389   const char *p_umpasstwo = tcstrskipspc(tcmapget4(params, "umpasstwo", ""));
390   const char *p_umridque = tcstrskipspc(tcmapget4(params, "umridque", ""));
391   const char *p_umridans = tcstrskipspc(tcmapget4(params, "umridans", ""));
392   const char *p_fmmode = tcstrskipspc(tcmapget4(params, "fmmode", ""));
393   const char *p_fmpath = tcstrskipspc(tcmapget4(params, "fmpath", ""));
394   const char *p_fmname = tcstrskipspc(tcmapget4(params, "fmname", ""));
395   int p_fmfilesiz;
396   const char *p_fmfilebuf = tcmapget(params, "fmfile", 6, &p_fmfilesiz);
397   const char *p_fmfilename = tcstrskipspc(tcmapget4(params, "fmfile_filename", ""));
398   bool p_fmthum = *tcmapget4(params, "fmthum", "") != '\0';
399   bool p_confirm = *tcmapget4(params, "confirm", "") != '\0';
400   rp = getenv("REMOTE_HOST");
401   const char *p_remotehost = rp ? rp : "";
402   rp = getenv("HTTP_HOST");
403   const char *p_hostname = rp ? rp : "";
404   if(*p_hostname == '\0'){
405     rp = getenv("SERVER_NAME");
406     p_hostname = rp ? rp : "";
407   }
408   rp = getenv("SSL_PROTOCOL_VERSION");
409   const char *p_scheme = (rp && *rp != '\0') ? "https" : "http";
410   const char *p_scripturl = tcmpoolpushptr(mpool, tcsprintf("%s://%s%s",
411                                                             p_scheme, p_hostname, g_scriptname));
412   rp = getenv("HTTP_REFERER");
413   const char *p_referrer = rp ? rp : "";
414   rp = getenv("HTTP_USER_AGENT");
415   const char *p_useragent = rp ? rp : "";
416   const char *p_userlang = "";
417   rp = getenv("HTTP_ACCEPT_LANGUAGE");
418   if(rp){
419     char *lang = tcmpoolpushptr(mpool, tcstrdup(rp));
420     char *pv = strchr(lang, ',');
421     if(pv) *pv = '\0';
422     pv = strchr(lang, ';');
423     if(pv) *pv = '\0';
424     pv = strchr(lang, '-');
425     if(pv) *pv = '\0';
426     tcstrtrim(lang);
427     p_userlang = lang;
428   }
429   if(*p_format == '\0'){
430     if(!strcmp(g_mimerule, "xhtml")) {
431       p_format = "xhtml";
432     } else if(!strcmp(g_mimerule, "html")) {
433       p_format = "html";
434     } else {
435       rp = getenv("HTTP_ACCEPT");
436       if(rp && strstr(rp, "application/xhtml+xml")) p_format = "xhtml";
437     }
438   }
439   // perform authentication
440   bool auth = true;
441   const char *userinfo = NULL;
442   uint32_t seskey = 0;
443   const char *authcookie = NULL;
444   const char *ridque = "";
445   const char *ridans = "";
446   const char *ridcookie = NULL;
447   rp = getenv("AUTH_TYPE");
448   if(rp && (!tcstricmp(rp, "Basic") || !tcstricmp(rp, "Digest")) &&
449      (rp = getenv("REMOTE_USER")) != NULL){
450     p_user = rp;
451     userinfo = "";
452     tcmapput2(vars, "basicauth", "true");
453   } else if(g_users){
454     auth = false;
455     const char *salt = tcmapget4(g_users, SALTNAME, "");
456     int saltsiz = strlen(salt);
457     bool cont = false;
458     if(*p_user == '\0'){
459       int authsiz;
460       const char *authbuf = tcmapget(params, "auth", 4, &authsiz);
461       if(authbuf && authsiz > 0){
462         char *token = tcmpoolmalloc(mpool, authsiz + 1);
463         tcarccipher(authbuf, authsiz, salt, saltsiz, token);
464         token[authsiz] = '\0';
465         TCLIST *elems = tcmpoolpushlist(mpool, tcstrsplit(token, ":"));
466         if(tclistnum(elems) >= 4 && !strcmp(tclistval2(elems, 0), salt)){
467           seskey = tcatoi(tclistval2(elems, 3));
468           if(seskey > 0){
469             p_user = tclistval2(elems, 1);
470             p_pass = tclistval2(elems, 2);
471             cont = true;
472           }
473         }
474       }
475     }
476     if(*p_user != '\0'){
477       rp = tcmapget2(g_users, p_user);
478       if(rp){
479         char *hash = tcmpoolpushptr(mpool, tcstrdup(rp));
480         char *pv = strchr(hash, ':');
481         if(pv) *(pv++) = '\0';
482         char numbuf[NUMBUFSIZ];
483         passwordhash(p_pass, salt, numbuf);
484         if(!strcmp(hash, numbuf)){
485           auth = true;
486           userinfo = pv ? pv : "";
487           if(seskey < 1){
488             uint32_t seed = 19780211;
489             for(rp = p_pass; *rp != '\0'; rp++){
490               seed = seed * 31 + *(unsigned char *)rp;
491             }
492             double integ;
493             double fract = modf(now, &integ) * (1ULL << 31);
494             seskey = (((uint32_t)integ + (uint32_t)fract) ^ (seed << 8)) & INT32_MAX;
495             if(seskey < 1) seskey = INT32_MAX;
496           }
497           int tsiz = strlen(p_user) + strlen(p_pass) + saltsiz + NUMBUFSIZ * 2;
498           char token[tsiz];
499           tsiz = sprintf(token, "%s:%s:%s:%u:%lld",
500                          salt, p_user, p_pass, (unsigned int)seskey, (long long)now);
501           tcmd5hash(token, tsiz, numbuf);
502           sprintf(token + tsiz, ":%s", numbuf);
503           tcarccipher(token, tsiz, salt, saltsiz, token);
504           if(!cont){
505             authcookie = tcmpoolpushptr(mpool, tcurlencode(token, tsiz));
506             p_seskey = seskey;
507           }
508         }
509       }
510     }
511     rp = tcmapget2(g_users, RIDDLENAME);
512     if(rp){
513       const char *pv = strstr(rp, ":");
514       if(pv){
515         ridque = tcmpoolpushptr(mpool, tcstrdup(pv + 1));
516         ridans = tcmpoolpushptr(mpool, tcmemdup(rp, pv - rp));
517       }
518     }
519     if(*ridans != '\0'){
520       const char *ridbuf = tcmapget2(params, "riddle");
521       if((ridbuf && !tcstricmp(ridbuf, ridans)) || !tcstricmp(p_umridans, ridans))
522         ridcookie = ridans;
523     }
524   }
525   if(!strcmp(p_act, "logout")){
526     p_user = "";
527     p_pass = "";
528     auth = false;
529     seskey = 0;
530     authcookie = "";
531   }
532   bool admin = auth && !strcmp(p_user, ADMINNAME);
533   bool cancom = false;
534   if(!strcmp(g_commentmode, "all") || (!strcmp(g_commentmode, "login") && auth) ||
535      (!strcmp(g_commentmode, "riddle") && (ridcookie || auth))) cancom = true;
536   // execute the beginning script
537   if(g_scrextproc){
538     const char *emsg = screxterrmsg(g_scrextproc);
539     if(emsg) tclistprintf(emsgs, "Loading scripting extension was failed (%s).", emsg);
540     scrextsetmapvar(g_scrextproc, "_params", params);
541     if(*p_user != '\0'){
542       TCMAP *user = tcmpoolpushmap(mpool, tcmapnew2(TINYBNUM));
543       tcmapput2(user, "name", p_user);
544       tcmapput2(user, "pass", p_pass);
545       if(userinfo) tcmapput2(user, "info", userinfo);
546       scrextsetmapvar(g_scrextproc, "_user", user);
547     }
548   }
549   if(g_scrextproc && scrextcheckfunc(g_scrextproc, "_begin")){
550     char *obuf = tcmpoolpushptr(mpool, scrextcallfunc(g_scrextproc, "_begin", ""));
551     if(obuf){
552       tcmapput2(vars, "beginmsg", obuf);
553     } else {
554       const char *emsg = screxterrmsg(g_scrextproc);
555       tclistprintf(emsgs, "Scripting extension was failed (%s).", emsg ? emsg : "(unknown)");
556     }
557   }
558   // open the database
559   TCTDB *tdb = tcmpoolpush(mpool, tctdbnew(), (void (*)(void *))tctdbdel);
560   int omode = TDBOREADER;
561   if(!strcmp(p_act, "update") && auth && post) omode = TDBOWRITER;
562   if(!strcmp(p_act, "comment") && cancom && post) omode = TDBOWRITER;
563   if(!strcmp(p_act, "files") && auth && post) omode = TDBOWRITER;
564   if(post && auth && *p_referrer != '\0'){
565     char *src = tcmpoolpushptr(mpool, tcstrdup(p_referrer));
566     char *wp = strchr(src, '?');
567     if(wp) *wp = '\0';
568     if(strcmp(src, p_scripturl)){
569       tclistprintf(emsgs, "Referrer is invalid (%s).", src);
570       admin = false;
571       post = false;
572       omode = TDBOREADER;
573     }
574   }
575   if(!tctdbopen(tdb, g_database, omode))
576     setdberrmsg(emsgs, tdb, "Opening the database was failed.");
577   int64_t mtime = tctdbmtime(tdb);
578   if(mtime < 1) mtime = now;
579   // prepare the common query
580   TCXSTR *comquery = tcmpoolxstrnew(mpool);
581   if(*p_act != '\0') tcxstrprintf(comquery, "&act=%?", p_act);
582   if(p_id > 0) tcxstrprintf(comquery, "&id=%lld", (long long)p_id);
583   if(*p_name != '\0') tcxstrprintf(comquery, "&name=%?", p_name);
584   if(*p_order != '\0') tcxstrprintf(comquery, "&order=%?", p_order);
585   if(*p_expr != '\0') tcxstrprintf(comquery, "&expr=%?", p_expr);
586   if(*p_cond != '\0') tcxstrprintf(comquery, "&cond=%?", p_cond);
587   if(p_fmthum) tcxstrprintf(comquery, "&fmthum=on");
588   // save a comment
589   if(!strcmp(p_act, "comment") && p_id > 0 && *p_comowner != '\0' && *p_comtext != '\0'){
590     char *owner = tcmpoolpushptr(mpool, tcstrdup(p_comowner));
591     tcstrsqzspc(owner);
592     char *text = tcmpoolpushptr(mpool, tcstrdup(p_comtext));
593     tcstrsqzspc(text);
594     if(*owner != '\0' && *text != '\0'){
595       if(checkusername(p_comowner)){
596         TCMAP *cols = tcmpoolpushmap(mpool, dbgetart(tdb, p_id));
597         if(cols){
598           if(checkfrozen(cols) && !admin){
599             tclistprintf(emsgs, "Frozen articles are not editable by normal users.");
600           } else {
601             TCMAP *ocols = *g_updatecmd != '\0' ? tcmpoolpushmap(mpool, tcmapdup(cols)) : NULL;
602             TCXSTR *wiki = tcmpoolxstrnew(mpool);
603             wikidump(wiki, cols);
604             TCXSTR *line = tcmpoolxstrnew(mpool);
605             tcxstrprintf(line, "%lld|%s|%s\n", (long long)now, owner, text);
606             tcmapputcat(cols, "comments", 8, tcxstrptr(line), tcxstrsize(line));
607             if(dbputart(tdb, p_id, cols)){
608               if(*g_updatecmd != '\0' &&
609                  !doupdatecmd(mpool, "comment", p_scripturl, p_user, now, p_id, cols, ocols))
610                 tclistprintf(emsgs, "The update command was failed.");
611             } else {
612               setdberrmsg(emsgs, tdb, "Storing the article was failed.");
613             }
614           }
615         }
616       } else {
617         tclistprintf(emsgs, "An invalid user name was specified.");
618       }
619     }
620   }
621   // perform each view
622   if(!strcmp(p_act, "login")){
623     // login view
624     tcmapprintf(vars, "titletip", "[login]");
625     tcmapput2(vars, "view", "login");
626   } else if(!strcmp(p_act, "logincheck") && !auth){
627     // login view
628     tcmapprintf(vars, "titletip", "[login]");
629     tcmapput2(vars, "view", "login");
630   } else if(!strcmp(p_act, "edit")){
631     // edit view
632     if(p_id > 0){
633       TCMAP *cols = tcmpoolpushmap(mpool, dbgetart(tdb, p_id));
634       if(cols){
635         if(checkfrozen(cols) && !admin){
636           tclistprintf(emsgs, "Frozen articles are not editable by normal users.");
637         } else {
638           TCXSTR *wiki = tcmpoolxstrnew(mpool);
639           wikidump(wiki, cols);
640           tcmapprintf(cols, "id", "%lld", (long long)p_id);
641           char numbuf[NUMBUFSIZ];
642           tcmd5hash(tcxstrptr(wiki), tcxstrsize(wiki), numbuf);
643           tcmapput2(cols, "hash", numbuf);
644           tcmapput2(vars, "view", "edit");
645           tcmapputmap(vars, "art", cols);
646           tcmapput(vars, "wiki", 4, tcxstrptr(wiki), tcxstrsize(wiki));
647         }
648       } else {
649         tcmapput2(vars, "view", "empty");
650       }
651       tcmapprintf(vars, "cond", "id:%lld", (long long)p_id);
652     } else {
653       tcmapprintf(vars, "titletip", "[edit]");
654       tcmapput2(vars, "view", "edit");
655       if(*p_name != '\0') tcmapput2(vars, "name", p_name);
656       if(*p_user != '\0') tcmapput2(vars, "user", p_user);
657       if(!strcmp(p_adjust, "front")) tcmapput2(vars, "tags", "*,?");
658     }
659   } else if(!strcmp(p_act, "preview")){
660     // preview view
661     if(p_id > 0){
662       TCMAP *cols = tcmpoolpushmap(mpool, dbgetart(tdb, p_id));
663       if(cols){
664         if(checkfrozen(cols) && !admin){
665           tclistprintf(emsgs, "Frozen articles are not editable by normal users.");
666         } else {
667           TCXSTR *wiki = tcmpoolxstrnew(mpool);
668           wikidump(wiki, cols);
669           char numbuf[NUMBUFSIZ];
670           tcmd5hash(tcxstrptr(wiki), tcxstrsize(wiki), numbuf);
671           if(!strcmp(numbuf, p_hash)){
672             if(*p_wiki != '\0'){
673               tcmapclear(cols);
674               wikiload(cols, p_wiki);
675               if(p_mts) tcmapprintf(cols, "mdate", "%lld", (long long)now);
676               const char *name = tcmapget2(cols, "name");
677               if(!name || *name == '\0'){
678                 tclistprintf(emsgs, "The name can not be empty.");
679               } else if(checkfrozen(cols) && !admin){
680                 tclistprintf(emsgs, "The frozen tag is not available by normal users.");
681               } else {
682                 setarthtml(mpool, cols, p_id, 0, -1, -1, false);
683                 tcmapprintf(vars, "titletip", "[preview]");
684                 tcmapput2(vars, "view", "preview");
685                 tcmapputmap(vars, "art", cols);
686                 tcmapput2(vars, "wiki", p_wiki);
687                 if(p_mts) tcmapput2(vars, "mts", "on");
688                 tcmapprintf(vars, "id", "%lld", (long long)p_id);
689                 tcmapput2(vars, "hash", p_hash);
690               }
691             } else {
692               tcmapput2(vars, "view", "removecheck");
693               tcmapputmap(vars, "art", cols);
694               tcmapprintf(vars, "id", "%lld", (long long)p_id);
695               tcmapput2(vars, "hash", p_hash);
696             }
697           } else {
698             tcmapput2(vars, "view", "collision");
699             tcmapput(vars, "wiki", 4, tcxstrptr(wiki), tcxstrsize(wiki));
700             tcmapput2(vars, "yourwiki", p_wiki);
701           }
702         }
703       } else {
704         tcmapput2(vars, "view", "empty");
705       }
706       tcmapprintf(vars, "cond", "id:%lld", (long long)p_id);
707     } else {
708       TCMAP *cols = tcmpoolpushmap(mpool, tcmapnew2(TINYBNUM));
709       wikiload(cols, p_wiki);
710       if(p_mts){
711         tcmapprintf(cols, "cdate", "%lld", (long long)now);
712         tcmapprintf(cols, "mdate", "%lld", (long long)now);
713       }
714       const char *name = tcmapget2(cols, "name");
715       if(!name || *name == '\0'){
716         tclistprintf(emsgs, "The name can not be empty.");
717         tcmapput2(vars, "view", "edit");
718         tcmapput2(vars, "wiki", p_wiki);
719       } else if(checkfrozen(cols)  && !admin){
720         tclistprintf(emsgs, "The frozen tag is not available by normal users.");
721         tcmapput2(vars, "view", "edit");
722         tcmapput2(vars, "wiki", p_wiki);
723       } else {
724         setarthtml(mpool, cols, 0, 0, -1, -1, false);
725         tcmapprintf(vars, "titletip", "[preview]");
726         tcmapput2(vars, "view", "preview");
727         tcmapputmap(vars, "art", cols);
728         tcmapput2(vars, "wiki", p_wiki);
729         if(p_mts) tcmapput2(vars, "mts", "on");
730       }
731     }
732   } else if(!strcmp(p_act, "update")){
733     // update view
734     if(seskey > 0 && p_seskey != seskey){
735       tclistprintf(emsgs, "The session key is invalid (%u).", (unsigned int)p_seskey);
736     } else if(p_id > 0){
737       TCMAP *cols = tcmpoolpushmap(mpool, dbgetart(tdb, p_id));
738       if(cols){
739         if(checkfrozen(cols) && !admin){
740           tclistprintf(emsgs, "Frozen articles are not editable by normal users.");
741         } else {
742           TCXSTR *wiki = tcmpoolxstrnew(mpool);
743           wikidump(wiki, cols);
744           char numbuf[NUMBUFSIZ];
745           tcmd5hash(tcxstrptr(wiki), tcxstrsize(wiki), numbuf);
746           if(!strcmp(numbuf, p_hash)){
747             TCMAP *ocols = *g_updatecmd != '\0' ? tcmpoolpushmap(mpool, tcmapdup(cols)) : NULL;
748             if(*p_wiki != '\0'){
749               tcmapclear(cols);
750               wikiload(cols, p_wiki);
751               if(p_mts) tcmapprintf(cols, "mdate", "%lld", (long long)now);
752               const char *name = tcmapget2(cols, "name");
753               if(!name || *name == '\0'){
754                 tclistprintf(emsgs, "The name can not be empty.");
755               } else if(checkfrozen(cols) && !admin){
756                 tclistprintf(emsgs, "The frozen tag is not available by normal users.");
757               } else if(dbputart(tdb, p_id, cols)){
758                 if(*g_updatecmd != '\0' &&
759                    !doupdatecmd(mpool, "update", p_scripturl, p_user, now, p_id, cols, ocols))
760                   tclistprintf(emsgs, "The update command was failed.");
761                 tcmapput2(vars, "view", "store");
762                 tcmapputmap(vars, "art", cols);
763               } else {
764                 setdberrmsg(emsgs, tdb, "Storing the article was failed.");
765               }
766             } else {
767               if(dboutart(tdb, p_id)){
768                 if(*g_updatecmd != '\0' &&
769                    !doupdatecmd(mpool, "remove", p_scripturl, p_user, now, p_id, NULL, ocols))
770                   tclistprintf(emsgs, "The update command was failed.");
771                 tcmapprintf(cols, "id", "%lld", (long long)p_id);
772                 tcmapput2(vars, "view", "remove");
773                 tcmapputmap(vars, "art", cols);
774               } else {
775                 setdberrmsg(emsgs, tdb, "Removing the article was failed.");
776               }
777             }
778           } else {
779             tcmapput2(vars, "view", "collision");
780             tcmapput(vars, "wiki", 4, tcxstrptr(wiki), tcxstrsize(wiki));
781             tcmapput2(vars, "yourwiki", p_wiki);
782           }
783         }
784       } else {
785         tcmapput2(vars, "view", "empty");
786       }
787       tcmapprintf(vars, "cond", "id:%lld", (long long)p_id);
788     } else {
789       TCMAP *cols = tcmpoolpushmap(mpool, tcmapnew2(TINYBNUM));
790       wikiload(cols, p_wiki);
791       if(p_mts){
792         tcmapprintf(cols, "cdate", "%lld", (long long)now);
793         tcmapprintf(cols, "mdate", "%lld", (long long)now);
794       }
795       const char *name = tcmapget2(cols, "name");
796       if(!name || *name == '\0'){
797         tclistprintf(emsgs, "The name can not be empty.");
798         tcmapput2(vars, "view", "edit");
799         tcmapput2(vars, "wiki", p_wiki);
800       } else if(dbputart(tdb, 0, cols)){
801         rp = tcmapget2(cols, "id");
802         int64_t nid = rp ? tcatoi(rp) : 0;
803         if(*g_updatecmd != '\0' &&
804            !doupdatecmd(mpool, "new", p_scripturl, p_user, now, nid, cols, NULL))
805           tclistprintf(emsgs, "The update command was failed.");
806         tcmapput2(vars, "view", "store");
807         tcmapputmap(vars, "art", cols);
808       } else {
809         setdberrmsg(emsgs, tdb, "Storing the article was failed.");
810       }
811     }
812   } else if(!strcmp(p_act, "users")){
813     // users view
814     if(g_users){
815       if(admin){
816         if(post && p_umname != '\0'){
817           if(seskey > 0 && p_seskey != seskey){
818             tclistprintf(emsgs, "The session key is invalid (%u).", (unsigned int)p_seskey);
819           } else if(!strcmp(p_ummode, "new")){
820             if(tcmapget2(g_users, p_umname)){
821               tclistprintf(emsgs, "The user already exists.");
822             } else if(!checkusername(p_umname)){
823               tclistprintf(emsgs, "The user name is invalid.");
824             } else if(strcmp(p_umpassone, p_umpasstwo)){
825               tclistprintf(emsgs, "The two passwords are different.");
826             } else {
827               const char *salt = tcmapget4(g_users, SALTNAME, "");
828               char numbuf[NUMBUFSIZ];
829               passwordhash(p_umpassone, salt, numbuf);
830               tcmapprintf(g_users, p_umname, "%s:%s", numbuf, p_uminfo);
831               if(writepasswd()){
832                 tcmapput2(vars, "newuser", p_umname);
833               } else {
834                 tclistprintf(emsgs, "Storing the password file was failed.");
835               }
836             }
837           } else if(!strcmp(p_ummode, "chpw")){
838             const char *pass = tcmapget2(g_users, p_umname);
839             if(!pass){
840               tclistprintf(emsgs, "The user does not exist.");
841             } else if(strcmp(p_umpassone, p_umpasstwo)){
842               tclistprintf(emsgs, "The two passwords are different.");
843             } else {
844               char *str = tcmpoolpushptr(mpool, tcstrdup(pass));
845               char *pv = strchr(str, ':');
846               if(pv){
847                 *(pv++) = '\0';
848               } else {
849                 pv = "";
850               }
851               const char *salt = tcmapget4(g_users, SALTNAME, "");
852               char numbuf[NUMBUFSIZ];
853               passwordhash(p_umpassone, salt, numbuf);
854               tcmapprintf(g_users, p_umname, "%s:%s", numbuf, pv);
855               if(writepasswd()){
856                 tcmapput2(vars, "chpwuser", p_umname);
857                 if(!strcmp(p_umname, p_user)){
858                   p_user = "";
859                   p_pass = "";
860                   auth = false;
861                   authcookie = "";
862                   tcmapput2(vars, "tologin", p_umname);
863                 }
864               } else {
865                 tclistprintf(emsgs, "Storing the password file was failed.");
866               }
867             }
868           } else if(!strcmp(p_ummode, "del") && p_confirm){
869             if(!tcmapget2(g_users, p_umname)){
870               tclistprintf(emsgs, "The user does not exist.");
871             } else {
872               tcmapout2(g_users, p_umname);
873               if(writepasswd()){
874                 tcmapput2(vars, "deluser", p_umname);
875                 if(!strcmp(p_umname, p_user)){
876                   p_user = "";
877                   p_pass = "";
878                   auth = false;
879                   authcookie = "";
880                   tcmapput2(vars, "tologin", p_umname);
881                 }
882               } else {
883                 tclistprintf(emsgs, "Storing the password file was failed.");
884               }
885             }
886           } else if(!strcmp(p_ummode, "rid")){
887             if(!checkusername(p_umridans)){
888               tclistprintf(emsgs, "The answer is invalid.");
889             } else {
890               tcmapprintf(g_users, RIDDLENAME, "%s:%s", p_umridans, p_umridque);
891               if(writepasswd()){
892                 tcmapput2(vars, "chrid", p_umname);
893                 ridque = p_umridque;
894                 ridans = p_umridans;
895               } else {
896                 tclistprintf(emsgs, "Storing the password file was failed.");
897               }
898             }
899           }
900         }
901         TCLIST *ulist = tcmpoollistnew(mpool);
902         tcmapiterinit(g_users);
903         const char *salt = NULL;
904         const char *name;
905         while((name = tcmapiternext2(g_users)) != NULL){
906           const char *pass = tcmapiterval2(name);
907           if(!strcmp(name, SALTNAME)){
908             salt = pass;
909           } else if(*name != SALTNAME[0]){
910             TCMAP *user = tcmpoolpushmap(mpool, tcmapnew2(TINYBNUM));
911             char *str = tcmpoolpushptr(mpool, tcstrdup(pass));
912             char *pv = strchr(str, ':');
913             if(pv){
914               *(pv++) = '\0';
915             } else {
916               pv = "";
917             }
918             tcmapput2(user, "name", name);
919             tcmapput2(user, "pass", str);
920             tcmapput2(user, "info", pv);
921             if(!strcmp(name, ADMINNAME)) tcmapput2(user, "admin", "true");
922             tclistpushmap(ulist, user);
923           }
924         }
925         tcmapprintf(vars, "titletip", "[user management]");
926         tcmapput2(vars, "view", "users");
927         if(tclistnum(ulist) > 0) tcmapputlist(vars, "userlist", ulist);
928         if(salt) tcmapput2(vars, "salt", salt);
929         tcmapput2(vars, "ridque", ridque);
930         tcmapput2(vars, "ridans", ridans);
931       } else {
932         tclistprintf(emsgs, "The user management function is not available by normal users.");
933       }
934     } else {
935       tclistprintf(emsgs, "The password file is missing.");
936     }
937   } else if(!strcmp(p_act, "files")){
938     // files view
939     bool isdir;
940     if(g_upload && tcstatfile(g_upload, &isdir, NULL, &mtime) && isdir){
941       if(auth){
942         if(post && (p_fmfilebuf || *p_fmpath != '\0')){
943           if(seskey > 0 && p_seskey != seskey){
944             tclistprintf(emsgs, "The session key is invalid (%u).", (unsigned int)p_seskey);
945           } else if(!strcmp(p_fmmode, "new")){
946             if(p_fmfilebuf && p_fmfilesiz > 0){
947               const char *name = p_fmname;
948               if(*name == '\0') name = p_fmfilename;
949               if(*name == '\0') name = "_noname_";
950               const char *ext = strrchr(name, '.');
951               if(!ext && (ext = strrchr(p_fmfilename, '.')) != NULL)
952                 name = tcmpoolpushptr(mpool, tcsprintf("%s%s", name, ext));
953               if(putfile(mpool, p_fmpath, name, p_fmfilebuf, p_fmfilesiz)){
954                 tcmapput2(vars, "newfile", name);
955               } else {
956                 tclistprintf(emsgs, "Storing the file was failed.");
957               }
958             } else {
959               tclistprintf(emsgs, "There is no data.");
960             }
961           } else if(!strcmp(p_fmmode, "repl")){
962             if(p_fmfilebuf && p_fmfilesiz > 0){
963               if(putfile(mpool, p_fmpath, "", p_fmfilebuf, p_fmfilesiz)){
964                 tcmapput2(vars, "replfile", p_fmname);
965               } else {
966                 tclistprintf(emsgs, "Storing the file was failed.");
967               }
968             } else {
969               tclistprintf(emsgs, "There is no data.");
970             }
971           } else if(!strcmp(p_fmmode, "del") && p_confirm){
972             if(outfile(mpool, p_fmpath)){
973               tcmapput2(vars, "delfile", p_fmname);
974             } else {
975               tclistprintf(emsgs, "Removing the file was failed.");
976             }
977           }
978         } else {
979           if(mtime <= p_ifmod){
980             showcache();
981             return;
982           }
983           char numbuf[NUMBUFSIZ];
984           tcdatestrhttp(mtime, 0, numbuf);
985           tcmapput2(vars, "lastmod", numbuf);
986         }
987         int max = g_filenum;
988         int skip = max * (p_page - 1);
989         TCLIST *files = searchfiles(mpool, p_expr, p_order, max + 1, skip, p_fmthum);
990         bool over = false;
991         if(tclistnum(files) > max){
992           tcfree(tclistpop2(files));
993           over = true;
994         }
995         tcmapprintf(vars, "titletip", "[file management]");
996         tcmapput2(vars, "view", "files");
997         if(p_page > 1) tcmapprintf(vars, "prev", "%d", p_page - 1);
998         if(over) tcmapprintf(vars, "next", "%d", p_page + 1);
999         if(tclistnum(files) > 0) tcmapputlist(vars, "files", files);
1000       } else {
1001         tclistprintf(emsgs, "The file management function is not available by outer users.");
1002       }
1003     } else {
1004       tclistprintf(emsgs, "The upload directory is missing.");
1005     }
1006   } else if(p_id > 0){
1007     // single view
1008     if(!auth && !ridcookie){
1009       if(mtime <= p_ifmod){
1010         showcache();
1011         return;
1012       }
1013       char numbuf[NUMBUFSIZ];
1014       tcdatestrhttp(mtime, 0, numbuf);
1015       tcmapput2(vars, "lastmod", numbuf);
1016     }
1017     TCMAP *cols = tcmpoolpushmap(mpool, dbgetart(tdb, p_id));
1018     if(cols){
1019       setarthtml(mpool, cols, p_id, 0, -1, -1, false);
1020       if(checkfrozen(cols) && !admin){
1021         tcmapput2(cols, "frozen", "true");
1022       } else if(cancom){
1023         tcmapputkeep2(cols, "comnum", "0");
1024         tcmapputkeep2(cols, "cancom", "true");
1025       } else if(!strcmp(g_commentmode, "riddle")){
1026         tcmapputkeep2(cols, "comnum", "0");
1027         tcmapput2(vars, "ridque", ridque);
1028         tcmapput2(vars, "ridans", ridans);
1029       }
1030       const char *name = tcmapget2(cols, "name");
1031       if(name) tcmapput2(vars, "titletip", name);
1032       if(!strcmp(p_adjust, "front")){
1033         tcmapput2(vars, "view", "front");
1034       } else {
1035         tcmapput2(vars, "view", "single");
1036       }
1037       tcmapput2(vars, "robots", "index,follow");
1038       tcmapputmap(vars, "art", cols);
1039     } else {
1040       tcmapput2(vars, "view", "empty");
1041     }
1042     tcmapprintf(vars, "cond", "id:%lld", (long long)p_id);
1043   } else if(*p_name != '\0'){
1044     // single view or search view
1045     if(!auth){
1046       if(mtime <= p_ifmod){
1047         showcache();
1048         return;
1049       }
1050       char numbuf[NUMBUFSIZ];
1051       tcdatestrhttp(mtime, 0, numbuf);
1052       tcmapput2(vars, "lastmod", numbuf);
1053     }
1054     int max = g_searchnum;
1055     int skip = max * (p_page - 1);
1056     const char *order = (*p_order == '\0') ? "_cdate" : p_order;
1057     TCLIST *res = searcharts(mpool, tdb, "name", p_name, order, max + 1, skip, false);
1058     int rnum = tclistnum(res);
1059     if(rnum < 1){
1060       tcmapput2(vars, "view", "empty");
1061       if(auth) tcmapput2(vars, "missname", p_name);
1062     } else if(rnum < 2 || p_confirm){
1063       int64_t id = tcatoi(tclistval2(res, 0));
1064       TCMAP *cols = tcmpoolpushmap(mpool, id > 0 ? dbgetart(tdb, id) : NULL);
1065       if(cols){
1066         setarthtml(mpool, cols, id, 0, -1, -1, false);
1067         if(checkfrozen(cols) && !admin){
1068           tcmapput2(cols, "frozen", "true");
1069         } else if(cancom){
1070           tcmapputkeep2(cols, "comnum", "0");
1071           tcmapputkeep2(cols, "cancom", "true");
1072         }
1073         const char *name = tcmapget2(cols, "name");
1074         if(name) tcmapput2(vars, "titletip", name);
1075         if(!strcmp(p_adjust, "front")){
1076           tcmapput2(vars, "view", "front");
1077         } else {
1078           tcmapput2(vars, "view", "single");
1079         }
1080         tcmapput2(vars, "robots", "index,follow");
1081         tcmapputmap(vars, "art", cols);
1082       } else {
1083         tcmapput2(vars, "view", "empty");
1084       }
1085     } else {
1086       tcmapprintf(vars, "hitnum", "%d", rnum);
1087       TCLIST *arts = tcmpoollistnew(mpool);
1088       for(int i = 0; i < rnum && i < max; i++){
1089         int64_t id = tcatoi(tclistval2(res, i));
1090         TCMAP *cols = tcmpoolpushmap(mpool, id > 0 ? dbgetart(tdb, id) : NULL);
1091         if(cols){
1092           setarthtml(mpool, cols, id, 1, -1, -1, true);
1093           tclistpushmap(arts, cols);
1094         }
1095       }
1096       if(tclistnum(arts) > 0){
1097         tcmapprintf(vars, "titletip", "[name:%s]", p_name);
1098         tcmapput2(vars, "view", "search");
1099         tcmapput2(vars, "robots", "noindex,follow");
1100         if(p_page > 1) tcmapprintf(vars, "prev", "%d", p_page - 1);
1101         if(rnum > max) tcmapprintf(vars, "next", "%d", p_page + 1);
1102         tcmapputlist(vars, "arts", arts);
1103       } else {
1104         tcmapput2(vars, "view", "empty");
1105       }
1106     }
1107     tcmapprintf(vars, "cond", "name:%s", p_name);
1108   } else if(!strcmp(p_act, "search")){
1109     // search view
1110     if(!auth){
1111       if(mtime <= p_ifmod){
1112         showcache();
1113         return;
1114       }
1115       char numbuf[NUMBUFSIZ];
1116       tcdatestrhttp(mtime, 0, numbuf);
1117       tcmapput2(vars, "lastmod", numbuf);
1118     }
1119     tcmapprintf(vars, "titletip", "[search]");
1120     tcmapput2(vars, "view", "search");
1121     tcmapput2(vars, "robots", "noindex,follow");
1122     int max = g_searchnum;
1123     int skip = max * (p_page - 1);
1124     TCLIST *res = searcharts(mpool, tdb, p_cond, p_expr, p_order, max + 1, skip, true);
1125     int rnum = tclistnum(res);
1126     TCLIST *arts = tcmpoollistnew(mpool);
1127     for(int i = 0; i < rnum && i < max; i++){
1128       int64_t id = tcatoi(tclistval2(res, i));
1129       TCMAP *cols = tcmpoolpushmap(mpool, id > 0 ? dbgetart(tdb, id) : NULL);
1130       if(cols){
1131         setarthtml(mpool, cols, id, 1, -1, -1, true);
1132         tclistpushmap(arts, cols);
1133       }
1134     }
1135     if(tclistnum(arts) > 0){
1136       if(*p_expr != '\0') tcmapprintf(vars, "titletip", "[search:%s]", p_expr);
1137       if(p_page > 1) tcmapprintf(vars, "prev", "%d", p_page - 1);
1138       if(rnum > max) tcmapprintf(vars, "next", "%d", p_page + 1);
1139       if(tcmapget2(vars, "prev") || tcmapget2(vars, "next")) tcmapput2(vars, "page", "true");
1140       tcmapputlist(vars, "arts", arts);
1141     }
1142     if(*p_cond != '\0'){
1143       tcmapprintf(vars, "hitnum", "%d", rnum);
1144     } else {
1145       res = tcmpoollistnew(mpool);
1146       char numbuf[NUMBUFSIZ];
1147       tcdatestrwww(now, INT_MAX, numbuf);
1148       int year = tcatoi(numbuf);
1149       int minyear = year;
1150       res = searcharts(mpool, tdb, "cdate", "x", "_cdate", 1, 0, true);
1151       if(tclistnum(res) > 0){
1152         int64_t id = tcatoi(tclistval2(res, 0));
1153         TCMAP *cols = tcmpoolpushmap(mpool, id > 0 ? dbgetart(tdb, id) : NULL);
1154         const char *value = cols ? tcmapget2(cols, "cdate") : NULL;
1155         if(value){
1156           int64_t cdate = tcstrmktime(value);
1157           tcdatestrwww(cdate, INT_MAX, numbuf);
1158           minyear = tcatoi(numbuf);
1159         }
1160       }
1161       TCLIST *arcyears = tcmpoollistnew(mpool);
1162       for(int i = 0; i < 100 && year >= minyear; i++){
1163         sprintf(numbuf, "%04d", year);
1164         res = searcharts(mpool, tdb, "cdate", numbuf, "cdate", 1, 0, true);
1165         if(tclistnum(res) > 0){
1166           TCMAP *arcmonths = tcmpoolpushmap(mpool, tcmapnew2(TINYBNUM));
1167           for(int month = 0; month <= 12; month++){
1168             sprintf(numbuf, "%04d-%02d", year, month);
1169             res = searcharts(mpool, tdb, "cdate", numbuf, "cdate", 1, 0, true);
1170             rnum = tclistnum(res);
1171             sprintf(numbuf, "%02d", month);
1172             if(rnum > 0) tcmapprintf(arcmonths, numbuf, "%d", rnum);
1173           }
1174           if(tcmaprnum(arcmonths) > 0){
1175             tcmapprintf(arcmonths, "year", "%d", year);
1176             tclistpushmap(arcyears, arcmonths);
1177           }
1178         }
1179         year--;
1180       }
1181       if(tclistnum(arcyears) > 0) tcmapputlist(vars, "arcyears", arcyears);
1182     }
1183   } else if(*g_frontpage != '\0' && strcmp(p_act, "timeline")){
1184 
1185 
1186 
1187     // front view
1188     if(!auth){
1189       if(mtime <= p_ifmod){
1190         showcache();
1191         return;
1192       }
1193       char numbuf[NUMBUFSIZ];
1194       tcdatestrhttp(mtime, 0, numbuf);
1195       tcmapput2(vars, "lastmod", numbuf);
1196     }
1197     int64_t id = 0;
1198     const char *name = "";
1199     if(tcstrfwm(g_frontpage, "id:")){
1200       id = tcatoi(strchr(g_frontpage, ':') + 1);
1201     } else if(tcstrfwm(g_frontpage, "name:")){
1202       name = strchr(g_frontpage, ':') + 1;
1203     } else {
1204       name = g_frontpage;
1205     }
1206     if(id < 1 && *name != '\0'){
1207       TCLIST *res = searcharts(mpool, tdb, "name", name, "_cdate", 1, 0, false);
1208       if(tclistnum(res) > 0) id = tcatoi(tclistval2(res, 0));
1209     }
1210     tcmapput2(vars, "view", "front");
1211     tcmapput2(vars, "robots", "index,follow");
1212     if(id > 0){
1213       TCMAP *cols = tcmpoolpushmap(mpool, dbgetart(tdb, id));
1214       if(cols){
1215         setarthtml(mpool, cols, id, 0, -1, -1, false);
1216         if(checkfrozen(cols) && !admin) tcmapput2(cols, "frozen", "true");
1217         tcmapputmap(vars, "art", cols);
1218       }
1219     }
1220   } else {
1221     // timeline view
1222     if(!auth){
1223       if(mtime <= p_ifmod){
1224         showcache();
1225         return;
1226       }
1227       char numbuf[NUMBUFSIZ];
1228       tcdatestrhttp(mtime, 0, numbuf);
1229       tcmapput2(vars, "lastmod", numbuf);
1230     }
1231     int max = g_listnum;
1232     int abslen = g_listabslen;
1233     int absobjnum = g_listabsobjnum;
1234     if(!strcmp(p_format, "atom")) {
1235       max = g_feedlistnum;
1236       abslen = g_feedlistabslen;
1237       absobjnum = g_feedlistabsobjnum;
1238     }
1239     int skip = max * (p_page - 1);
1240     TCLIST *res = searcharts(mpool, tdb, NULL, NULL, p_order, max + 1, skip, true);
1241     int rnum = tclistnum(res);
1242     if(rnum < 1){
1243       tcmapput2(vars, "view", "empty");
1244     } else {
1245       TCLIST *arts = tcmpoollistnew(mpool);
1246       for(int i = 0; i < rnum && i < max ; i++){
1247         int64_t id = tcatoi(tclistval2(res, i));
1248         TCMAP *cols = tcmpoolpushmap(mpool, id > 0 ? dbgetart(tdb, id) : NULL);
1249         if(cols){
1250           setarthtml(mpool, cols, id, 1, abslen, absobjnum, false);
1251           tclistpushmap(arts, cols);
1252         }
1253       }
1254       if(tclistnum(arts) > 0){
1255         if(*p_act != '\0'){
1256           if(!strcmp(p_order, "_cdate")){
1257             tcmapput2(vars, "titletip", "[old creation]");
1258           } else if(!strcmp(p_order, "mdate")){
1259             tcmapput2(vars, "titletip", "[recent modification]");
1260           } else if(!strcmp(p_order, "_mdate")){
1261             tcmapput2(vars, "titletip", "[old modification]");
1262           } else if(!strcmp(p_order, "xdate")){
1263             tcmapput2(vars, "titletip", "[recent comment]");
1264           } else if(!strcmp(p_order, "_xdate")){
1265             tcmapput2(vars, "titletip", "[old comment]");
1266           } else {
1267             tcmapput2(vars, "titletip", "[newcome articles]");
1268           }
1269         }
1270         tcmapput2(vars, "view", "timeline");
1271         tcmapput2(vars, "robots", "index,follow");
1272         if(p_page > 1) tcmapprintf(vars, "prev", "%d", p_page - 1);
1273         if(rnum > max) tcmapprintf(vars, "next", "%d", p_page + 1);
1274         if(tcmapget2(vars, "prev") || tcmapget2(vars, "next")) tcmapput2(vars, "page", "true");
1275         tcmapputlist(vars, "arts", arts);
1276       } else {
1277         tcmapput2(vars, "view", "empty");
1278       }
1279     }
1280   }
1281   if(g_sidebarnum > 0 && strcmp(p_format, "atom")){
1282     // side bar
1283     TCLIST *res = searcharts(mpool, tdb, NULL, NULL, "cdate", g_sidebarnum + 1, 0, true);
1284     int rnum = tclistnum(res);
1285     if(rnum > g_sidebarnum) {
1286       rnum = g_sidebarnum;
1287       tcmapput2(vars, "sideartsmore", "");
1288     }
1289     TCLIST *arts = tcmpoollistnew(mpool);
1290     for(int i = 0; i < rnum; i++){
1291       int64_t id = tcatoi(tclistval2(res, i));
1292       TCMAP *cols = tcmpoolpushmap(mpool, id > 0 ? dbgetart(tdb, id) : NULL);
1293       if(cols){
1294         setarthtml(mpool, cols, id, 1, -1, -1, true);
1295         tclistpushmap(arts, cols);
1296       }
1297     }
1298     if(tclistnum(arts) > 0) tcmapputlist(vars, "sidearts", arts);
1299     res = searcharts(mpool, tdb, NULL, NULL, "xdate", g_sidebarnum, 0, true);
1300     rnum = tclistnum(res);
1301     TCLIST *coms = tcmpoollistnew(mpool);
1302     for(int i = 0; i < rnum; i++){
1303       int64_t id = tcatoi(tclistval2(res, i));
1304       TCMAP *cols = tcmpoolpushmap(mpool, id > 0 ? dbgetart(tdb, id) : NULL);
1305       if(cols){
1306         rp = tcmapget2(cols, "comments");
1307         if(rp && *rp != '\0'){
1308           TCLIST *lines = tcmpoolpushlist(mpool, tcstrsplit(rp, "\n"));
1309           int left = g_sidebarnum;
1310           for(int i = tclistnum(lines) - 1; i >= 0 && left > 0; i--, left--){
1311             rp = tclistval2(lines, i);
1312             char *co = strchr(rp, '|');
1313             if(co){
1314               *(co++) = '\0';
1315               char *ct = strchr(co, '|');
1316               if(ct){
1317                 *(ct++) = '\0';
1318                 COMMENT com;
1319                 memset(&com, 0, sizeof(com));
1320                 com.id = id;
1321                 com.date = tcatoi(rp);
1322                 com.owner = co;
1323                 com.text = ct;
1324                 tclistpush(coms, &com, sizeof(com));
1325               }
1326             }
1327           }
1328         }
1329       }
1330     }
1331     int cnum = tclistnum(coms);
1332     if(cnum > 0){
1333       tclistsortex(coms, comparecomments);
1334       TCLIST *comments = tcmpoolpushlist(mpool, tclistnew2(cnum));
1335       for(int i = 0; i < cnum && i < g_sidebarnum; i++){
1336         COMMENT *com = (COMMENT *)tclistval2(coms, i);
1337         TCMAP *comment = tcmpoolpushmap(mpool, tcmapnew2(TINYBNUM));
1338         tcmapprintf(comment, "id", "%lld", (long long)com->id);
1339         char numbuf[NUMBUFSIZ];
1340         tcdatestrwww(com->date, INT_MAX, numbuf);
1341         tcmapput2(comment, "date", numbuf);
1342         tcmapput2(comment, "datesimple", datestrsimple(numbuf));
1343         tcmapput2(comment, "owner", com->owner);
1344         tcmapput2(comment, "text", com->text);
1345         TCXSTR *xstr = tcmpoolxstrnew(mpool);
1346         wikitohtmlinline(xstr, com->text, g_scriptname, g_uploadpub);
1347         tcmapput(comment, "texthtml", 8, tcxstrptr(xstr), tcxstrsize(xstr));
1348         tclistpushmap(comments, comment);
1349       }
1350       tcmapputlist(vars, "sidecoms", comments);
1351     }
1352     tcmapput2(vars, "sidebar", "true");
1353   }
1354   // close the database
1355   if(!tctdbclose(tdb)) setdberrmsg(emsgs, tdb, "Closing the database was failed.");
1356   // execute the ending script
1357   if(g_scrextproc && scrextcheckfunc(g_scrextproc, "_end")){
1358     char *obuf = tcmpoolpushptr(mpool, scrextcallfunc(g_scrextproc, "_end", ""));
1359     if(obuf){
1360       tcmapput2(vars, "endmsg", obuf);
1361     } else {
1362       const char *emsg = screxterrmsg(g_scrextproc);
1363       tclistprintf(emsgs, "Scripting extension was failed (%s).", emsg ? emsg : "(unknown)");
1364     }
1365   }
1366   // generete the output
1367   if(tclistnum(emsgs) > 0) tcmapputlist(vars, "emsgs", emsgs);
1368   tcmapputmap(vars, "params", params);
1369   if(tcxstrsize(comquery) > 0) tcmapput2(vars, "comquery", tcxstrptr(comquery));
1370   if(!strcmp(p_act, "logout")) tcmapout2(vars, "lastmod");
1371   tcmapput2(vars, "scriptname", g_scriptname);
1372   tcmapput2(vars, "scriptprefix", g_scriptprefix);
1373   tcmapput2(vars, "scriptpath", g_scriptpath);
1374   tcmapput2(vars, "scripturl", p_scripturl);
1375   tcmapput2(vars, "documentroot", g_docroot);
1376   if(g_users) tcmapputmap(vars, "users", g_users);
1377   if(auth && p_user != '\0'){
1378     tcmapput2(vars, "username", p_user);
1379     if(userinfo && *userinfo != '\0') tcmapput2(vars, "userinfo", userinfo);
1380     if(seskey > 0) tcmapprintf(vars, "seskey", "%llu", (unsigned long long)seskey);
1381   }
1382   if(auth) tcmapput2(vars, "auth", "true");
1383   if(admin) tcmapput2(vars, "admin", "true");
1384   if(cancom) tcmapput2(vars, "cancom", "true");
1385   if(g_uploadpub) tcmapput2(vars, "uploadpub", g_uploadpub);
1386   tcmapput2(vars, "tpversion", TPVERSION);
1387   char numbuf[NUMBUFSIZ];
1388   tcdatestrwww(now, INT_MAX, numbuf);
1389   tcmapput2(vars, "now", numbuf);
1390   tcdatestrwww(mtime, INT_MAX, numbuf);
1391   tcmapput2(vars, "mtime", numbuf);
1392   tcmapput2(vars, "mtimesimple", datestrsimple(numbuf));
1393   if(tcmapget2(vars, "prev") || tcmapget2(vars, "next")) tcmapput2(vars, "page", "true");
1394   tcmapputkeep2(vars, "view", "error");
1395   tcmapput2(vars, "format", !strcmp(p_format, "atom") ? "atom" : "html");
1396   tcmapput2(vars, "scheme", p_scheme);
1397   tcmapput2(vars, "remotehost", p_remotehost);
1398   tcmapput2(vars, "hostname", p_hostname);
1399   tcmapput2(vars, "referrer", p_referrer);
1400   tcmapput2(vars, "useragent", p_useragent);
1401   tcmapput2(vars, "userlang", p_userlang);
1402   const char *mtype = "text/html; charset=UTF-8";
1403   if(!strcmp(p_format, "atom")){
1404     mtype = "application/atom+xml";
1405   } else if(!strcmp(p_format, "xhtml")){
1406     mtype = "application/xhtml+xml";
1407   } else if(!strcmp(p_format, "xml")){
1408     mtype = "application/xml";
1409   }
1410   tcmapput2(vars, "mimetype", mtype);
1411   const char *tmplstr = trimxmlchars(tcmpoolpushptr(mpool, tctmpldump(g_tmpl, vars)));
1412   while(*tmplstr > '\0' && *tmplstr <= ' '){
1413     tmplstr++;
1414   }
1415   if(g_scrextproc && scrextcheckfunc(g_scrextproc, "_procpage")){
1416     char *obuf = tcmpoolpushptr(mpool, scrextcallfunc(g_scrextproc, "_procpage", tmplstr));
1417     if(obuf) tmplstr = obuf;
1418   }
1419   printf("Content-Type: %s\r\n", mtype);
1420   rp = tcmapget2(vars, "lastmod");
1421   if(rp) printf("Last-Modified: %s\r\n", rp);
1422   printf("Cache-Control: no-cache\r\n");
1423   if(authcookie){
1424     printf("Set-Cookie: auth=%s;", authcookie);
1425     if(g_sessionlife > 0) printf(" max-age=%d;", g_sessionlife);
1426     printf("\r\n");
1427   }
1428   if(ridcookie){
1429     printf("Set-Cookie: riddle=%s;", ridcookie);
1430     if(g_sessionlife > 0) printf(" max-age=%d;", g_sessionlife);
1431     printf("\r\n");
1432   }
1433   if(*p_comowner != '\0' && checkusername(p_comowner)){
1434     printf("Set-Cookie: comowner=%s;", p_comowner);
1435     if(g_sessionlife > 0) printf(" max-age=%d;", g_sessionlife);
1436     printf("\r\n");
1437   }
1438   if(g_eventcount > 0) printf("X-Event-Count: %lu\r\n", g_eventcount);
1439   printf("\r\n");
1440   fwrite(tmplstr, 1, strlen(tmplstr), stdout);
1441   fflush(stdout);
1442 }
1443 
1444 
1445 /* set a database error message */
setdberrmsg(TCLIST * emsgs,TCTDB * tdb,const char * msg)1446 static void setdberrmsg(TCLIST *emsgs, TCTDB *tdb, const char *msg){
1447   tclistprintf(emsgs, "[database error: %s] %s", tctdberrmsg(tctdbecode(tdb)), msg);
1448 }
1449 
1450 
1451 /* set the HTML data of an article */
setarthtml(TCMPOOL * mpool,TCMAP * cols,int64_t id,int bhl,int abslen,int absobjnum,bool tiny)1452 static void setarthtml(TCMPOOL *mpool, TCMAP *cols, int64_t id, int bhl,
1453                        int abslen, int absobjnum, bool tiny){
1454   if(g_scrextproc && scrextcheckfunc(g_scrextproc, "_procart")){
1455     TCXSTR *wiki = tcmpoolxstrnew(mpool);
1456     wikidump(wiki, cols);
1457     char *obuf = tcmpoolpushptr(mpool,
1458                                 scrextcallfunc(g_scrextproc, "_procart", tcxstrptr(wiki)));
1459     if(obuf){
1460       tcmapclear(cols);
1461       wikiload(cols, obuf);
1462     }
1463   }
1464   tcmapprintf(cols, "id", "%lld", (long long)id);
1465   char idbuf[NUMBUFSIZ];
1466   sprintf(idbuf, "article%lld", (long long)id);
1467   char numbuf[NUMBUFSIZ];
1468   const char *rp = tcmapget2(cols, "cdate");
1469   if(rp){
1470     tcdatestrwww(tcstrmktime(rp), INT_MAX, numbuf);
1471     tcmapput2(cols, "cdate", numbuf);
1472     tcmapput2(cols, "cdatesimple", datestrsimple(numbuf));
1473   }
1474   rp = tcmapget2(cols, "mdate");
1475   if(rp){
1476     tcdatestrwww(tcstrmktime(rp), INT_MAX, numbuf);
1477     tcmapput2(cols, "mdate", numbuf);
1478     tcmapput2(cols, "mdatesimple", datestrsimple(numbuf));
1479   }
1480   rp = tcmapget2(cols, "xdate");
1481   if(rp){
1482     tcdatestrwww(tcstrmktime(rp), INT_MAX, numbuf);
1483     tcmapput2(cols, "xdate", numbuf);
1484     tcmapput2(cols, "xdatesimple", datestrsimple(numbuf));
1485   }
1486   rp = tcmapget2(cols, "tags");
1487   if(rp){
1488     TCLIST *tags = tcmpoolpushlist(mpool, tcstrsplit(rp, " ,"));
1489     int idx = 0;
1490     while(idx < tclistnum(tags)){
1491       rp = tclistval2(tags, idx);
1492       if(*rp != '\0'){
1493         idx++;
1494       } else {
1495         tcfree(tclistremove2(tags, idx));
1496       }
1497     }
1498     tcmapputlist(cols, "taglist", tags);
1499   }
1500   rp = tcmapget2(cols, "text");
1501   if(rp && *rp != '\0'){
1502     if(tiny){
1503       TCXSTR *xstr = tcmpoolxstrnew(mpool);
1504       wikitotext(xstr, rp);
1505       char *str = tcmpoolpushptr(mpool, tcmemdup(tcxstrptr(xstr), tcxstrsize(xstr)));
1506       tcstrutfnorm(str, TCUNSPACE);
1507       tcstrcututf(str, 256);
1508       tcmapput2(cols, "texttiny", str);
1509     } else {
1510       TCXSTR *xstr = tcmpoolxstrnew(mpool);
1511       int txtlen = wikitohtml(xstr, rp, idbuf, g_scriptname, bhl + 1, g_uploadpub,
1512                               abslen, absobjnum);
1513       if(tcxstrsize(xstr) > 0) {
1514         tcmapput(cols, "texthtml", 8, tcxstrptr(xstr), tcxstrsize(xstr));
1515         if(abslen > 0 && txtlen > abslen) tcmapput2(cols, "omitted", "true");
1516       }
1517     }
1518   }
1519   rp = tcmapget2(cols, "comments");
1520   if(rp && *rp != '\0'){
1521     TCLIST *lines = tcmpoolpushlist(mpool, tcstrsplit(rp, "\n"));
1522     int cnum = tclistnum(lines);
1523     tcmapprintf(cols, "comnum", "%d", cnum);
1524     TCLIST *comments = tcmpoolpushlist(mpool, tclistnew2(cnum));
1525     int cnt = 0;
1526     for(int i = 0; i < cnum; i++){
1527       const char *rp = tclistval2(lines, i);
1528       char *co = strchr(rp, '|');
1529       if(co){
1530         *(co++) = '\0';
1531         char *ct = strchr(co, '|');
1532         if(ct){
1533           *(ct++) = '\0';
1534           TCMAP *comment = tcmpoolpushmap(mpool, tcmapnew2(TINYBNUM));
1535           char numbuf[NUMBUFSIZ];
1536           tcdatestrwww(tcatoi(rp), INT_MAX, numbuf);
1537           cnt++;
1538           tcmapprintf(comment, "cnt", "%d", cnt);
1539           tcmapput2(comment, "date", numbuf);
1540           tcmapput2(comment, "datesimple", datestrsimple(numbuf));
1541           tcmapput2(comment, "owner", co);
1542           tcmapput2(comment, "text", ct);
1543           TCXSTR *xstr = tcmpoolxstrnew(mpool);
1544           wikitohtmlinline(xstr, ct, g_scriptname, g_uploadpub);
1545           tcmapput(comment, "texthtml", 8, tcxstrptr(xstr), tcxstrsize(xstr));
1546           tclistpushmap(comments, comment);
1547         }
1548       }
1549     }
1550     if(tclistnum(comments) > 0) tcmapputlist(cols, "comments", comments);
1551     tcmapprintf(cols, "comnum", "%d", tclistnum(comments));
1552   }
1553 }
1554 
1555 
1556 /* search for articles */
searcharts(TCMPOOL * mpool,TCTDB * tdb,const char * cond,const char * expr,const char * order,int max,int skip,bool ls)1557 static TCLIST *searcharts(TCMPOOL *mpool, TCTDB *tdb, const char *cond, const char *expr,
1558                           const char *order, int max, int skip, bool ls){
1559   TDBQRY *qrys[8];
1560   int qnum = 0;
1561   if(!cond) cond = "";
1562   if(!expr) expr = "";
1563   if(*expr == '\0'){
1564     qrys[qnum++] = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1565   } else if(!strcmp(cond, "main")){
1566     const char *names[] = { "name", "text", NULL };
1567     for(int i = 0; names[i] != NULL; i++){
1568       TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1569       tctdbqryaddcond(qry, names[i], TDBQCFTSEX, expr);
1570       qrys[qnum++] = qry;
1571     }
1572   } else if(!strcmp(cond, "name")){
1573     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1574     tctdbqryaddcond(qry, "name", TDBQCSTREQ, expr);
1575     qrys[qnum++] = qry;
1576   } else if(!strcmp(cond, "namebw")){
1577     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1578     tctdbqryaddcond(qry, "name", TDBQCSTRBW, expr);
1579     qrys[qnum++] = qry;
1580   } else if(!strcmp(cond, "namefts")){
1581     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1582     tctdbqryaddcond(qry, "name", TDBQCFTSEX, expr);
1583     qrys[qnum++] = qry;
1584   } else if(!strcmp(cond, "cdate")){
1585     int64_t lower, upper;
1586     getdaterange(expr, &lower, &upper);
1587     char numbuf[NUMBUFSIZ*2];
1588     sprintf(numbuf, "%lld,%lld", (long long)lower, (long long)upper);
1589     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1590     tctdbqryaddcond(qry, "cdate", TDBQCNUMBT, numbuf);
1591     qrys[qnum++] = qry;
1592   } else if(!strcmp(cond, "mdate")){
1593     int64_t lower, upper;
1594     getdaterange(expr, &lower, &upper);
1595     char numbuf[NUMBUFSIZ*2];
1596     sprintf(numbuf, "%lld,%lld", (long long)lower, (long long)upper);
1597     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1598     tctdbqryaddcond(qry, "mdate", TDBQCNUMBT, numbuf);
1599     qrys[qnum++] = qry;
1600   } else if(!strcmp(cond, "xdate")){
1601     int64_t lower, upper;
1602     getdaterange(expr, &lower, &upper);
1603     char numbuf[NUMBUFSIZ*2];
1604     sprintf(numbuf, "%lld,%lld", (long long)lower, (long long)upper);
1605     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1606     tctdbqryaddcond(qry, "xdate", TDBQCNUMBT, numbuf);
1607     qrys[qnum++] = qry;
1608   } else if(!strcmp(cond, "owner")){
1609     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1610     tctdbqryaddcond(qry, "owner", TDBQCSTREQ, expr);
1611     qrys[qnum++] = qry;
1612   } else if(!strcmp(cond, "ownerbw")){
1613     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1614     tctdbqryaddcond(qry, "", TDBQCSTRBW, expr);
1615     qrys[qnum++] = qry;
1616   } else if(!strcmp(cond, "ownerfts")){
1617     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1618     tctdbqryaddcond(qry, "", TDBQCFTSEX, expr);
1619     qrys[qnum++] = qry;
1620   } else if(!strcmp(cond, "tags")){
1621     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1622     tctdbqryaddcond(qry, "tags", TDBQCSTRAND, expr);
1623     qrys[qnum++] = qry;
1624   } else if(!strcmp(cond, "tagsor")){
1625     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1626     tctdbqryaddcond(qry, "tags", TDBQCSTROR, expr);
1627     qrys[qnum++] = qry;
1628   } else if(!strcmp(cond, "tagsfts")){
1629     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1630     tctdbqryaddcond(qry, "tags", TDBQCFTSEX, expr);
1631     qrys[qnum++] = qry;
1632   } else if(!strcmp(cond, "text")){
1633     TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1634     tctdbqryaddcond(qry, "text", TDBQCFTSEX, expr);
1635     qrys[qnum++] = qry;
1636   } else if(!strcmp(cond, "any")){
1637     const char *names[] = { "name", "owner", "tags", "text", NULL };
1638     for(int i = 0; names[i] != NULL; i++){
1639       TDBQRY *qry = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1640       tctdbqryaddcond(qry, names[i], TDBQCFTSEX, expr);
1641       qrys[qnum++] = qry;
1642     }
1643   } else {
1644     qrys[qnum++] = tcmpoolpush(mpool, tctdbqrynew(tdb), (void (*)(void *))tctdbqrydel);
1645   }
1646   if(!order) order = "";
1647   const char *oname = "cdate";
1648   int otype = TDBQONUMDESC;
1649   if(!strcmp(order, "_cdate")){
1650     oname = "cdate";
1651     otype = TDBQONUMASC;
1652   } else if(!strcmp(order, "mdate")){
1653     oname = "mdate";
1654     otype = TDBQONUMDESC;
1655   } else if(!strcmp(order, "_mdate")){
1656     oname = "mdate";
1657     otype = TDBQONUMASC;
1658   } else if(!strcmp(order, "xdate")){
1659     oname = "xdate";
1660     otype = TDBQONUMDESC;
1661   } else if(!strcmp(order, "_xdate")){
1662     oname = "xdate";
1663     otype = TDBQONUMASC;
1664   }
1665   for(int i = 0; i < qnum; i++){
1666     if(ls) tctdbqryaddcond(qrys[i], "tags", TDBQCSTROR | TDBQCNEGATE, "?");
1667     tctdbqrysetorder(qrys[i], oname, otype);
1668   }
1669   for(int i = 0; i < qnum; i++){
1670     tctdbqrysetlimit(qrys[i], max, skip);
1671   }
1672   TCLIST *res = tcmpoolpushlist(mpool, tctdbmetasearch(qrys, qnum, TDBMSUNION));
1673   return res;
1674 }
1675 
1676 
1677 /* get the range of a date expression */
getdaterange(const char * expr,int64_t * lowerp,int64_t * upperp)1678 static void getdaterange(const char *expr, int64_t *lowerp, int64_t *upperp){
1679   while(*expr == ' '){
1680     expr++;
1681   }
1682   unsigned int year = 0;
1683   for(int i = 0; i < 4 && *expr >= '0' && *expr <= '9'; i++){
1684     year = year * 10 + *expr - '0';
1685     expr++;
1686   }
1687   if(*expr == '-' || *expr == '/') expr++;
1688   unsigned int month = 0;
1689   for(int i = 0; i < 2 && *expr >= '0' && *expr <= '9'; i++){
1690     month = month * 10 + *expr - '0';
1691     expr++;
1692   }
1693   if(*expr == '-' || *expr == '/') expr++;
1694   unsigned int day = 0;
1695   for(int i = 0; i < 2 && *expr >= '0' && *expr <= '9'; i++){
1696     day = day * 10 + *expr - '0';
1697     expr++;
1698   }
1699   int lag = tcjetlag() / 3600;
1700   int64_t lower, upper;
1701   char numbuf[NUMBUFSIZ*2];
1702   if(day > 0){
1703     sprintf(numbuf, "%04u-%02u-%02uT00:00:00%+03d:00", year, month, day, lag);
1704     lower = tcstrmktime(numbuf);
1705     upper = lower + 60 * 60 * 24 - 1;
1706   } else if(month > 0){
1707     sprintf(numbuf, "%04u-%02u-01T00:00:00%+03d:00", year, month, lag);
1708     lower = tcstrmktime(numbuf);
1709     month++;
1710     if(month > 12){
1711       year++;
1712       month = 1;
1713     }
1714     sprintf(numbuf, "%04u-%02u-01T00:00:00%+03d:00", year, month, lag);
1715     upper = tcstrmktime(numbuf) - 1;
1716   } else if(year > 0){
1717     sprintf(numbuf, "%04u-01-01T00:00:00%+03d:00", year, lag);
1718     lower = tcstrmktime(numbuf);
1719     year++;
1720     sprintf(numbuf, "%04u-01-01T00:00:00%+03d:00", year, lag);
1721     upper = tcstrmktime(numbuf) - 1;
1722   } else {
1723     lower = INT64_MIN / 2;
1724     upper = INT64_MAX / 2;
1725   }
1726   *lowerp = lower;
1727   *upperp = upper;
1728 }
1729 
1730 
1731 /* store a file */
putfile(TCMPOOL * mpool,const char * path,const char * name,const char * ptr,int size)1732 static bool putfile(TCMPOOL *mpool, const char *path, const char *name,
1733                     const char *ptr, int size){
1734   if(*path != '\0'){
1735     if(strchr(path, '/')) return false;
1736   } else {
1737     char *enc = pathencode(name);
1738     path = tcmpoolpushptr(mpool, tcsprintf("%lld-%s", (long long)tctime(), enc));
1739     tcfree(enc);
1740   }
1741   path = tcmpoolpushptr(mpool, tcsprintf("%s/%s", g_upload, path));
1742   return tcwritefile(path, ptr, size);
1743 }
1744 
1745 
1746 /* remove a file */
outfile(TCMPOOL * mpool,const char * path)1747 static bool outfile(TCMPOOL *mpool, const char *path){
1748   if(*path == '\0' || strchr(path, '/')) return false;
1749   path = tcmpoolpushptr(mpool, tcsprintf("%s/%s", g_upload, path));
1750   return remove(path) == 0;
1751 }
1752 
1753 
1754 /* search for files */
searchfiles(TCMPOOL * mpool,const char * expr,const char * order,int max,int skip,bool thum)1755 static TCLIST *searchfiles(TCMPOOL *mpool, const char *expr, const char *order,
1756                            int max, int skip, bool thum){
1757   TCLIST *paths = tcmpoolpushlist(mpool, tcreaddir(g_upload));
1758   if(!paths) return tcmpoollistnew(mpool);
1759   tclistsort(paths);
1760   if(strcmp(order, "_cdate")) tclistinvert(paths);
1761   int pnum = tclistnum(paths);
1762   TCLIST *files = tcmpoolpushlist(mpool, tclistnew2(pnum));
1763   for(int i = 0; i < pnum && tclistnum(files) < max; i++){
1764     const char *path = tclistval2(paths, i);
1765     int64_t date = tcatoi(path);
1766     const char *pv = strchr(path, '-');
1767     if(date < 1 || !pv) continue;
1768     pv++;
1769     int nsiz;
1770     char *name = tcmpoolpushptr(mpool, tcurldecode(pv, &nsiz));
1771     if(*expr != '\0' && !strstr(name, expr)){
1772       tcmpoolpop(mpool, true);
1773       continue;
1774     }
1775     if(--skip >= 0){
1776       tcmpoolpop(mpool, true);
1777       continue;
1778     }
1779     char lpath[8192];
1780     snprintf(lpath, sizeof(lpath) - 1, "%s/%s", g_upload, path);
1781     lpath[sizeof(lpath)-1] = '\0';
1782     int64_t size;
1783     if(!tcstatfile(lpath, NULL, &size, NULL)) size = 0;
1784     char numbuf[NUMBUFSIZ];
1785     tcdatestrwww(date, INT_MAX, numbuf);
1786     TCMAP *file = tcmpoolpushmap(mpool, tcmapnew2(TINYBNUM));
1787     tcmapput2(file, "path", path);
1788     tcmapput2(file, "name", name);
1789     tcmapput2(file, "date", datestrsimple(numbuf));
1790     tcmapprintf(file, "size", "%lld", (long long)size);
1791     const char *type = mimetype(name);
1792     if(!type) type = "application/octet-stream";
1793     tcmapput2(file, "type", type);
1794     tcmapput2(file, "typename", mimetypename(type));
1795     if(thum && tcstrfwm(type, "image/") && !strchr(type, '+'))
1796       tcmapput2(file, "thumnail", "true");
1797     tclistpushmap(files, file);
1798   }
1799   return files;
1800 }
1801 
1802 
1803 /* compare two comments by date */
comparecomments(const TCLISTDATUM * a,const TCLISTDATUM * b)1804 static int comparecomments(const TCLISTDATUM *a, const TCLISTDATUM *b){
1805   COMMENT *coma = (COMMENT *)a->ptr;
1806   COMMENT *comb = (COMMENT *)b->ptr;
1807   if(coma->date > comb->date) return -1;
1808   if(coma->date < comb->date) return 1;
1809   if(coma->id > comb->id) return -1;
1810   if(coma->id < comb->id) return 1;
1811   return strcmp(coma->owner, comb->owner);
1812 }
1813 
1814 
1815 /* process the update command */
doupdatecmd(TCMPOOL * mpool,const char * mode,const char * baseurl,const char * user,double now,int64_t id,TCMAP * ncols,TCMAP * ocols)1816 static bool doupdatecmd(TCMPOOL *mpool, const char *mode, const char *baseurl, const char *user,
1817                         double now, int64_t id, TCMAP *ncols, TCMAP *ocols){
1818   const char *base = g_upload;
1819   if(!base || *base == '\0') base = P_tmpdir;
1820   if(!base || *base == '\0') base = "/tmp";
1821   int64_t ts = now * 1000000;
1822   bool err = false;
1823   TCXSTR *nbuf = tcmpoolxstrnew(mpool);
1824   if(ncols){
1825     tcmapprintf(ncols, "id", "%lld", (long long)id);
1826     wikidump(nbuf, ncols);
1827   }
1828   char *npath = tcmpoolpushptr(mpool, tcsprintf("%s/tmp-%lld-%lld-%s-new.tpw", base,
1829                                                 (long long)id, (long long)ts, mode));
1830   if(!tcwritefile(npath, tcxstrptr(nbuf), tcxstrsize(nbuf))) err = true;
1831   TCXSTR *obuf = tcmpoolxstrnew(mpool);
1832   if(ocols){
1833     tcmapprintf(ocols, "id", "%lld", (long long)id);
1834     wikidump(obuf, ocols);
1835   }
1836   char *opath = tcmpoolpushptr(mpool, tcsprintf("%s/tmp-%lld-%lld-%s-old.tpw", base,
1837                                                 (long long)id, (long long)ts, mode));
1838   if(!tcwritefile(opath, tcxstrptr(obuf), tcxstrsize(obuf))) err = true;
1839   char idbuf[NUMBUFSIZ];
1840   sprintf(idbuf, "%lld", (long long)id);
1841   char tsbuf[NUMBUFSIZ];
1842   sprintf(tsbuf, "%lld", (long long)ts);
1843   const char *args[16];
1844   int anum = 0;
1845   args[anum++] = g_updatecmd;
1846   args[anum++] = mode;
1847   args[anum++] = idbuf;
1848   args[anum++] = npath;
1849   args[anum++] = opath;
1850   args[anum++] = tsbuf;
1851   args[anum++] = user;
1852   args[anum++] = baseurl;
1853   if(tcsystem(args, anum) != 0) err = true;
1854   remove(opath);
1855   remove(npath);
1856   return !err;
1857 }
1858 
1859 
1860 
1861 // END OF FILE
1862