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