/[LeafOK_CVS]/lbbs/src/article_post.c
ViewVC logotype

Contents of /lbbs/src/article_post.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.41 - (show annotations)
Fri Nov 7 06:41:43 2025 UTC (4 months, 1 week ago) by sysadm
Branch: MAIN
Changes since 1.40: +44 -2 lines
Content type: text/x-csrc
Apply BWF to article post / reply / modify

1 /* SPDX-License-Identifier: GPL-3.0-or-later */
2 /*
3 * article_post
4 * - user interactive feature to post / modify / reply article
5 *
6 * Copyright (C) 2004-2025 Leaflet <leaflet@leafok.com>
7 */
8
9 #include "article_cache.h"
10 #include "article_post.h"
11 #include "bbs.h"
12 #include "bwf.h"
13 #include "database.h"
14 #include "editor.h"
15 #include "io.h"
16 #include "log.h"
17 #include "lml.h"
18 #include "screen.h"
19 #include "user_priv.h"
20 #include <ctype.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include <time.h>
24
25 enum _article_post_constant_t
26 {
27 TITLE_INPUT_MAX_LEN = 72,
28 ARTICLE_QUOTE_MAX_LINES = 20,
29 MODIFY_DT_MAX_LEN = 50,
30 };
31
32 int article_post(const SECTION_LIST *p_section, ARTICLE *p_article_new)
33 {
34 MYSQL *db = NULL;
35 MYSQL_RES *rs = NULL;
36 MYSQL_ROW row;
37 char sql[SQL_BUFFER_LEN];
38 char *sql_content = NULL;
39 EDITOR_DATA *p_editor_data = NULL;
40 char title_input[BBS_article_title_max_len + 1];
41 char title_f[BBS_article_title_max_len * 2 + 1];
42 char *content = NULL;
43 char *content_f = NULL;
44 long len_content;
45 int content_display_length;
46 char nickname_f[BBS_nickname_max_len * 2 + 1];
47 int sign_id = 0;
48 int reply_note = 1;
49 long len;
50 int ch;
51 char *p, *q;
52 long ret = 0;
53
54 if (p_section == NULL || p_article_new == NULL)
55 {
56 log_error("NULL pointer error\n");
57 }
58
59 if (!checkpriv(&BBS_priv, p_section->sid, S_POST))
60 {
61 clearscr();
62 moveto(1, 1);
63 prints("您没有权限在本版块发表文章\n");
64 press_any_key();
65
66 return 0;
67 }
68
69 p_article_new->title[0] = '\0';
70 title_input[0] = '\0';
71 p_article_new->transship = 0;
72
73 p_editor_data = editor_data_load("");
74 if (p_editor_data == NULL)
75 {
76 log_error("editor_data_load() error\n");
77 ret = -1;
78 goto cleanup;
79 }
80
81 // Set title and sign
82 for (ch = 'T'; !SYS_server_exit;)
83 {
84 clearscr();
85 moveto(21, 1);
86 prints("发表文章于 %s[%s] 讨论区,类型: %s,回复通知:%s",
87 p_section->stitle, p_section->sname,
88 (p_article_new->transship ? "转载" : "原创"),
89 (reply_note ? "开启" : "关闭"));
90 moveto(22, 1);
91 prints("标题: %s", (p_article_new->title[0] == '\0' ? "[无]" : p_article_new->title));
92 moveto(23, 1);
93 prints("使用第 %d 个签名", sign_id);
94
95 if (toupper(ch) != 'T')
96 {
97 prints(" 按0~3选签名档(0表示不使用)");
98
99 moveto(24, 1);
100 prints("T改标题, C取消, Z设为%s, N%s, Enter继续: ",
101 (p_article_new->transship ? "原创" : "转载"),
102 (reply_note ? "关闭回复通知" : "开启回复通知"));
103 iflush();
104 ch = 0;
105 }
106
107 while (!SYS_server_exit)
108 {
109 switch (toupper(ch))
110 {
111 case KEY_NULL:
112 case KEY_TIMEOUT:
113 goto cleanup;
114 case CR:
115 break;
116 case 'T':
117 len = get_data(24, 1, "标题: ", title_input, sizeof(title_input), TITLE_INPUT_MAX_LEN);
118 for (p = title_input; *p == ' '; p++)
119 ;
120 for (q = title_input + len; q > p && *(q - 1) == ' '; q--)
121 ;
122 *q = '\0';
123 len = q - p;
124 if (*p != '\0')
125 {
126 if ((ret = check_badwords(p, '*')) < 0)
127 {
128 log_error("check_badwords(title) error\n");
129 }
130 else if (ret > 0)
131 {
132 memcpy(title_input, p, (size_t)len + 1);
133 continue;
134 }
135 memcpy(p_article_new->title, p, (size_t)len + 1);
136 memcpy(title_input, p_article_new->title, (size_t)len + 1);
137 }
138 ch = 0;
139 break;
140 case 'C':
141 clearscr();
142 moveto(1, 1);
143 prints("取消...");
144 press_any_key();
145 goto cleanup;
146 case 'Z':
147 p_article_new->transship = (p_article_new->transship ? 0 : 1);
148 break;
149 case 'N':
150 reply_note = (reply_note ? 0 : 1);
151 break;
152 case '0':
153 case '1':
154 case '2':
155 case '3':
156 sign_id = ch - '0';
157 break;
158 default: // Invalid selection
159 ch = igetch_t(BBS_max_user_idle_time);
160 continue;
161 }
162
163 break;
164 }
165
166 if (ch != CR || p_article_new->title[0] == '\0')
167 {
168 continue;
169 }
170
171 for (ch = 'E'; !SYS_server_exit && toupper(ch) == 'E';)
172 {
173 editor_display(p_editor_data);
174
175 clearscr();
176 moveto(1, 1);
177 prints("(S)发送, (C)取消, (T)更改标题 or (E)再编辑? [S]: ");
178 iflush();
179
180 for (ch = 0; !SYS_server_exit; ch = igetch_t(BBS_max_user_idle_time))
181 {
182 switch (toupper(ch))
183 {
184 case KEY_NULL:
185 case KEY_TIMEOUT:
186 goto cleanup;
187 case CR:
188 case 'S':
189 break;
190 case 'C':
191 clearscr();
192 moveto(1, 1);
193 prints("取消...");
194 press_any_key();
195 goto cleanup;
196 case 'T':
197 break;
198 case 'E':
199 break;
200 default: // Invalid selection
201 continue;
202 }
203
204 break;
205 }
206 }
207
208 if (toupper(ch) != 'T')
209 {
210 break;
211 }
212 }
213
214 if (SYS_server_exit) // Do not save data on shutdown
215 {
216 goto cleanup;
217 }
218
219 content = malloc(ARTICLE_CONTENT_MAX_LEN);
220 if (content == NULL)
221 {
222 log_error("malloc(content) error: OOM\n");
223 ret = -1;
224 goto cleanup;
225 }
226
227 len_content = editor_data_save(p_editor_data, content, ARTICLE_CONTENT_MAX_LEN);
228 if (len_content < 0)
229 {
230 log_error("editor_data_save() error\n");
231 ret = -1;
232 goto cleanup;
233 }
234
235 if (check_badwords(content, '*') < 0)
236 {
237 log_error("check_badwords(content) error\n");
238 ret = -1;
239 goto cleanup;
240 }
241
242 db = db_open();
243 if (db == NULL)
244 {
245 log_error("db_open() error: %s\n", mysql_error(db));
246 ret = -1;
247 goto cleanup;
248 }
249
250 if (sign_id > 0)
251 {
252 snprintf(sql, sizeof(sql),
253 "SELECT sign_%d AS sign FROM user_pubinfo WHERE UID = %d",
254 sign_id, BBS_priv.uid);
255
256 if (mysql_query(db, sql) != 0)
257 {
258 log_error("Query sign error: %s\n", mysql_error(db));
259 ret = -1;
260 goto cleanup;
261 }
262 if ((rs = mysql_use_result(db)) == NULL)
263 {
264 log_error("Get sign data failed\n");
265 ret = -1;
266 goto cleanup;
267 }
268
269 if ((row = mysql_fetch_row(rs)))
270 {
271 len_content += snprintf(content + len_content,
272 ARTICLE_CONTENT_MAX_LEN - (size_t)len_content,
273 "\n\n--\n%s\n", row[0]);
274 }
275 mysql_free_result(rs);
276 rs = NULL;
277 }
278
279 // Calculate display length of content
280 content_display_length = str_length(content, 1);
281
282 // Begin transaction
283 if (mysql_query(db, "SET autocommit=0") != 0)
284 {
285 log_error("SET autocommit=0 error: %s\n", mysql_error(db));
286 ret = -1;
287 goto cleanup;
288 }
289
290 if (mysql_query(db, "BEGIN") != 0)
291 {
292 log_error("Begin transaction error: %s\n", mysql_error(db));
293 ret = -1;
294 goto cleanup;
295 }
296
297 // Secure SQL parameters
298 content_f = malloc((size_t)len_content * 2 + 1);
299 if (content_f == NULL)
300 {
301 log_error("malloc(content_f) error: OOM\n");
302 ret = -1;
303 goto cleanup;
304 }
305
306 mysql_real_escape_string(db, nickname_f, BBS_nickname, (unsigned long)strnlen(BBS_nickname, sizeof(BBS_nickname)));
307 mysql_real_escape_string(db, title_f, p_article_new->title, strnlen(p_article_new->title, sizeof(p_article_new->title)));
308 mysql_real_escape_string(db, content_f, content, (unsigned long)len_content);
309
310 free(content);
311 content = NULL;
312
313 // Add content
314 sql_content = malloc(SQL_BUFFER_LEN + (size_t)len_content * 2 + 1);
315 if (sql_content == NULL)
316 {
317 log_error("malloc(sql_content) error: OOM\n");
318 ret = -1;
319 goto cleanup;
320 }
321
322 snprintf(sql_content, SQL_BUFFER_LEN + (size_t)len_content * 2 + 1,
323 "INSERT INTO bbs_content(AID, content) values(0, '%s')",
324 content_f);
325
326 free(content_f);
327 content_f = NULL;
328
329 if (mysql_query(db, sql_content) != 0)
330 {
331 log_error("Add article content error: %s\n", mysql_error(db));
332 ret = -1;
333 goto cleanup;
334 }
335
336 p_article_new->cid = (int32_t)mysql_insert_id(db);
337
338 free(sql_content);
339 sql_content = NULL;
340
341 // Add article
342 snprintf(sql, sizeof(sql),
343 "INSERT INTO bbs(SID, TID, UID, username, nickname, title, CID, transship, "
344 "sub_dt, sub_ip, reply_note, exp, last_reply_dt, icon, length) "
345 "VALUES(%d, 0, %d, '%s', '%s', '%s', %d, %d, NOW(), '%s', %d, %d, NOW(), 1, %d)",
346 p_section->sid, BBS_priv.uid, BBS_username, nickname_f, title_f,
347 p_article_new->cid, p_article_new->transship, hostaddr_client,
348 reply_note, BBS_user_exp, content_display_length);
349
350 if (mysql_query(db, sql) != 0)
351 {
352 log_error("Add article error: %s\n", mysql_error(db));
353 ret = -1;
354 goto cleanup;
355 }
356
357 p_article_new->aid = (int32_t)mysql_insert_id(db);
358
359 // Link content to article
360 snprintf(sql, sizeof(sql),
361 "UPDATE bbs_content SET AID = %d WHERE CID = %d",
362 p_article_new->aid, p_article_new->cid);
363
364 if (mysql_query(db, sql) != 0)
365 {
366 log_error("Update content error: %s\n", mysql_error(db));
367 ret = -1;
368 goto cleanup;
369 }
370
371 // Add exp
372 if (checkpriv(&BBS_priv, p_section->sid, S_GETEXP)) // Except in test section
373 {
374 snprintf(sql, sizeof(sql),
375 "UPDATE user_pubinfo SET exp = exp + %d WHERE UID = %d",
376 (p_article_new->transship ? 5 : 15), BBS_priv.uid);
377
378 if (mysql_query(db, sql) != 0)
379 {
380 log_error("Update exp error: %s\n", mysql_error(db));
381 ret = -1;
382 goto cleanup;
383 }
384 }
385
386 // Add log
387 snprintf(sql, sizeof(sql),
388 "INSERT INTO bbs_article_op(AID, UID, type, op_dt, op_ip)"
389 "VALUES(%d, %d, 'A', NOW(), '%s')",
390 p_article_new->aid, BBS_priv.uid, hostaddr_client);
391
392 if (mysql_query(db, sql) != 0)
393 {
394 log_error("Add log error: %s\n", mysql_error(db));
395 ret = -1;
396 goto cleanup;
397 }
398
399 // Commit transaction
400 if (mysql_query(db, "COMMIT") != 0)
401 {
402 log_error("Commit transaction error: %s\n", mysql_error(db));
403 ret = -1;
404 goto cleanup;
405 }
406
407 mysql_close(db);
408 db = NULL;
409
410 clearscr();
411 moveto(1, 1);
412 prints("发送完成,新文章通常会在%d秒后可见", BBS_section_list_load_interval);
413 press_any_key();
414 ret = 1; // Success
415
416 cleanup:
417 mysql_close(db);
418
419 // Cleanup buffers
420 editor_data_cleanup(p_editor_data);
421
422 free(sql_content);
423 free(content);
424 free(content_f);
425
426 return (int)ret;
427 }
428
429 int article_modify(const SECTION_LIST *p_section, const ARTICLE *p_article, ARTICLE *p_article_new)
430 {
431 MYSQL *db = NULL;
432 MYSQL_RES *rs = NULL;
433 MYSQL_ROW row;
434 char sql[SQL_BUFFER_LEN];
435 char *sql_content = NULL;
436 char *content = NULL;
437 char *content_f = NULL;
438 long len_content;
439 int content_display_length;
440 int reply_note = 1;
441 int ch;
442 long ret = 0;
443 time_t now;
444 struct tm tm_modify_dt;
445 char str_modify_dt[MODIFY_DT_MAX_LEN + 1];
446
447 EDITOR_DATA *p_editor_data = NULL;
448
449 if (p_section == NULL || p_article == NULL)
450 {
451 log_error("NULL pointer error\n");
452 }
453
454 if (p_article->excerption) // Modify is not allowed
455 {
456 clearscr();
457 moveto(1, 1);
458 prints("该文章无法被编辑,请联系版主。");
459 press_any_key();
460
461 return 0;
462 }
463
464 db = db_open();
465 if (db == NULL)
466 {
467 log_error("db_open() error: %s\n", mysql_error(db));
468 ret = -1;
469 goto cleanup;
470 }
471
472 snprintf(sql, sizeof(sql),
473 "SELECT bbs_content.CID, bbs_content.content, reply_note "
474 "FROM bbs INNER JOIN bbs_content ON bbs.CID = bbs_content.CID "
475 "WHERE bbs.AID = %d",
476 p_article->aid);
477
478 if (mysql_query(db, sql) != 0)
479 {
480 log_error("Query article content error: %s\n", mysql_error(db));
481 ret = -1;
482 goto cleanup;
483 }
484 if ((rs = mysql_use_result(db)) == NULL)
485 {
486 log_error("Get article content data failed\n");
487 ret = -1;
488 goto cleanup;
489 }
490
491 if ((row = mysql_fetch_row(rs)))
492 {
493 content = malloc(ARTICLE_CONTENT_MAX_LEN);
494 if (content == NULL)
495 {
496 log_error("malloc(content) error: OOM\n");
497 ret = -1;
498 goto cleanup;
499 }
500
501 strncpy(content, row[1], ARTICLE_CONTENT_MAX_LEN - 1);
502 content[ARTICLE_CONTENT_MAX_LEN - 1] = '\0';
503
504 // Remove control sequence
505 len_content = str_filter(content, 0);
506
507 p_editor_data = editor_data_load(content);
508 if (p_editor_data == NULL)
509 {
510 log_error("editor_data_load(aid=%d, cid=%d) error\n", p_article->aid, atoi(row[0]));
511 ret = -1;
512 goto cleanup;
513 }
514
515 free(content);
516 content = NULL;
517
518 reply_note = atoi(row[2]);
519 }
520 mysql_free_result(rs);
521 rs = NULL;
522
523 mysql_close(db);
524 db = NULL;
525
526 for (ch = 'E'; !SYS_server_exit && toupper(ch) == 'E';)
527 {
528 editor_display(p_editor_data);
529
530 while (!SYS_server_exit)
531 {
532 clearscr();
533 moveto(1, 1);
534 prints("(S)保存, (C)取消, (N)%s回复通知 or (E)再编辑? [S]: ",
535 (reply_note ? "关闭" : "开启"));
536 iflush();
537
538 ch = igetch_t(BBS_max_user_idle_time);
539 switch (toupper(ch))
540 {
541 case KEY_NULL:
542 case KEY_TIMEOUT:
543 goto cleanup;
544 case CR:
545 case 'S':
546 break;
547 case 'C':
548 clearscr();
549 moveto(1, 1);
550 prints("取消...");
551 press_any_key();
552 goto cleanup;
553 case 'N':
554 reply_note = (reply_note ? 0 : 1);
555 continue;
556 case 'E':
557 break;
558 default: // Invalid selection
559 continue;
560 }
561
562 break;
563 }
564 }
565
566 if (SYS_server_exit) // Do not save data on shutdown
567 {
568 goto cleanup;
569 }
570
571 // Allocate buffers in big size
572 content = malloc(ARTICLE_CONTENT_MAX_LEN);
573 if (content == NULL)
574 {
575 log_error("malloc(content) error: OOM\n");
576 ret = -1;
577 goto cleanup;
578 }
579
580 len_content = editor_data_save(p_editor_data, content, ARTICLE_CONTENT_MAX_LEN - LINE_BUFFER_LEN);
581 if (len_content < 0)
582 {
583 log_error("editor_data_save() error\n");
584 ret = -1;
585 goto cleanup;
586 }
587
588 if (check_badwords(content, '*') < 0)
589 {
590 log_error("check_badwords(content) error\n");
591 ret = -1;
592 goto cleanup;
593 }
594
595 time(&now);
596 localtime_r(&now, &tm_modify_dt);
597 strftime(str_modify_dt, sizeof(str_modify_dt), "%Y-%m-%d %H:%M:%S (UTC %z)", &tm_modify_dt);
598
599 len_content += snprintf(content + len_content, LINE_BUFFER_LEN,
600 "\n--\n※ 作者已于 %s 修改本文※\n",
601 str_modify_dt);
602
603 // Calculate display length of content
604 content_display_length = str_length(content, 1);
605
606 db = db_open();
607 if (db == NULL)
608 {
609 log_error("db_open() error: %s\n", mysql_error(db));
610 ret = -1;
611 goto cleanup;
612 }
613
614 // Begin transaction
615 if (mysql_query(db, "SET autocommit=0") != 0)
616 {
617 log_error("SET autocommit=0 error: %s\n", mysql_error(db));
618 ret = -1;
619 goto cleanup;
620 }
621
622 if (mysql_query(db, "BEGIN") != 0)
623 {
624 log_error("Begin transaction error: %s\n", mysql_error(db));
625 ret = -1;
626 goto cleanup;
627 }
628
629 // Secure SQL parameters
630 content_f = malloc((size_t)len_content * 2 + 1);
631 if (content_f == NULL)
632 {
633 log_error("malloc(content_f) error: OOM\n");
634 ret = -1;
635 goto cleanup;
636 }
637
638 mysql_real_escape_string(db, content_f, content, (unsigned long)len_content);
639
640 free(content);
641 content = NULL;
642
643 // Add content
644 sql_content = malloc(SQL_BUFFER_LEN + (size_t)len_content * 2 + 1);
645 if (sql_content == NULL)
646 {
647 log_error("malloc(sql_content) error: OOM\n");
648 ret = -1;
649 goto cleanup;
650 }
651
652 snprintf(sql_content, SQL_BUFFER_LEN + (size_t)len_content * 2 + 1,
653 "INSERT INTO bbs_content(AID, content) values(%d, '%s')",
654 p_article->aid, content_f);
655
656 free(content_f);
657 content_f = NULL;
658
659 if (mysql_query(db, sql_content) != 0)
660 {
661 log_error("Add article content error: %s\n", mysql_error(db));
662 ret = -1;
663 goto cleanup;
664 }
665
666 p_article_new->cid = (int32_t)mysql_insert_id(db);
667
668 free(sql_content);
669 sql_content = NULL;
670
671 // Update article
672 snprintf(sql, sizeof(sql),
673 "UPDATE bbs SET CID = %d, length = %d, reply_note = %d, excerption = 0 WHERE AID = %d", // Set excerption = 0 explictly in case of rare condition
674 p_article_new->cid, content_display_length, reply_note, p_article->aid);
675
676 if (mysql_query(db, sql) != 0)
677 {
678 log_error("Add article error: %s\n", mysql_error(db));
679 ret = -1;
680 goto cleanup;
681 }
682
683 if (mysql_query(db, sql) != 0)
684 {
685 log_error("Update content error: %s\n", mysql_error(db));
686 ret = -1;
687 goto cleanup;
688 }
689
690 // Add log
691 snprintf(sql, sizeof(sql),
692 "INSERT INTO bbs_article_op(AID, UID, type, op_dt, op_ip)"
693 "VALUES(%d, %d, 'M', NOW(), '%s')",
694 p_article->aid, BBS_priv.uid, hostaddr_client);
695
696 if (mysql_query(db, sql) != 0)
697 {
698 log_error("Add log error: %s\n", mysql_error(db));
699 ret = -1;
700 goto cleanup;
701 }
702
703 // Commit transaction
704 if (mysql_query(db, "COMMIT") != 0)
705 {
706 log_error("Commit transaction error: %s\n", mysql_error(db));
707 ret = -1;
708 goto cleanup;
709 }
710
711 mysql_close(db);
712 db = NULL;
713
714 clearscr();
715 moveto(1, 1);
716 prints("修改完成,新内容通常会在%d秒后可见", BBS_section_list_load_interval);
717 press_any_key();
718 ret = 1; // Success
719
720 cleanup:
721 mysql_free_result(rs);
722 mysql_close(db);
723
724 // Cleanup buffers
725 editor_data_cleanup(p_editor_data);
726
727 free(sql_content);
728 free(content);
729 free(content_f);
730
731 return (int)ret;
732 }
733
734 int article_reply(const SECTION_LIST *p_section, const ARTICLE *p_article, ARTICLE *p_article_new)
735 {
736 MYSQL *db = NULL;
737 MYSQL_RES *rs = NULL;
738 MYSQL_ROW row;
739 long line_offsets[ARTICLE_QUOTE_MAX_LINES + 1];
740 char sql[SQL_BUFFER_LEN];
741 char *sql_content = NULL;
742 EDITOR_DATA *p_editor_data = NULL;
743 char title_input[BBS_article_title_max_len + sizeof("Re: ")];
744 char title_f[BBS_article_title_max_len * 2 + 1];
745 char *content = NULL;
746 char *content_f = NULL;
747 long len_content;
748 int content_display_length;
749 char nickname_f[BBS_nickname_max_len * 2 + 1];
750 int sign_id = 0;
751 int reply_note = 0;
752 long len;
753 int ch;
754 char *p, *q;
755 int eol;
756 int display_len;
757 long quote_content_lines;
758 long i;
759 long ret = 0;
760 int topic_locked = 0;
761 char msg[BBS_msg_max_len];
762 char msg_f[BBS_msg_max_len * 2 + 1];
763 int len_msg;
764
765 if (p_section == NULL || p_article == NULL)
766 {
767 log_error("NULL pointer error\n");
768 }
769
770 if (!checkpriv(&BBS_priv, p_section->sid, S_POST))
771 {
772 clearscr();
773 moveto(1, 1);
774 prints("您没有权限在本版块发表文章\n");
775 press_any_key();
776
777 return 0;
778 }
779
780 p_article_new->title[0] = '\0';
781 snprintf(title_input, sizeof(title_input), "Re: %s", p_article->title);
782 len = split_line(title_input, TITLE_INPUT_MAX_LEN, &eol, &display_len, 0);
783 title_input[len] = '\0';
784
785 db = db_open();
786 if (db == NULL)
787 {
788 log_error("db_open() error: %s\n", mysql_error(db));
789 ret = -1;
790 goto cleanup;
791 }
792
793 snprintf(sql, sizeof(sql),
794 "SELECT `lock` FROM bbs WHERE AID = %d",
795 (p_article->tid == 0 ? p_article->aid : p_article->tid));
796
797 if (mysql_query(db, sql) != 0)
798 {
799 log_error("Query article status error: %s\n", mysql_error(db));
800 ret = -1;
801 goto cleanup;
802 }
803 if ((rs = mysql_store_result(db)) == NULL)
804 {
805 log_error("Get article status data failed\n");
806 ret = -1;
807 goto cleanup;
808 }
809
810 if ((row = mysql_fetch_row(rs)))
811 {
812 if (atoi(row[0]) != 0)
813 {
814 topic_locked = 1;
815 }
816 }
817 mysql_free_result(rs);
818 rs = NULL;
819
820 if (topic_locked) // Reply is not allowed
821 {
822 mysql_close(db);
823 db = NULL;
824
825 clearscr();
826 moveto(1, 1);
827 prints("该主题谢绝回复");
828 press_any_key();
829
830 goto cleanup;
831 }
832
833 snprintf(sql, sizeof(sql),
834 "SELECT bbs_content.CID, bbs_content.content "
835 "FROM bbs INNER JOIN bbs_content ON bbs.CID = bbs_content.CID "
836 "WHERE bbs.AID = %d",
837 p_article->aid);
838
839 if (mysql_query(db, sql) != 0)
840 {
841 log_error("Query article content error: %s\n", mysql_error(db));
842 ret = -1;
843 goto cleanup;
844 }
845 if ((rs = mysql_use_result(db)) == NULL)
846 {
847 log_error("Get article content data failed\n");
848 ret = -1;
849 goto cleanup;
850 }
851
852 if ((row = mysql_fetch_row(rs)))
853 {
854 content = malloc(ARTICLE_CONTENT_MAX_LEN);
855 if (content == NULL)
856 {
857 log_error("malloc(content) error: OOM\n");
858 ret = -1;
859 goto cleanup;
860 }
861
862 content_f = malloc(ARTICLE_CONTENT_MAX_LEN);
863 if (content_f == NULL)
864 {
865 log_error("malloc(content_f) error: OOM\n");
866 ret = -1;
867 goto cleanup;
868 }
869
870 // Apply LML render to content body
871 len = lml_render(row[1], content_f, ARTICLE_CONTENT_MAX_LEN, MAX_EDITOR_DATA_LINE_LENGTH - 3, 1);
872 content_f[len] = '\0';
873
874 // Remove control sequence
875 len = str_filter(content_f, 0);
876
877 len = snprintf(content, ARTICLE_CONTENT_MAX_LEN,
878 "\n\n【 在 %s (%s) 的大作中提到: 】\n",
879 p_article->username, p_article->nickname);
880
881 quote_content_lines = split_data_lines(content_f, MAX_EDITOR_DATA_LINE_LENGTH - 2, line_offsets, ARTICLE_QUOTE_MAX_LINES + 1, 0, NULL);
882 for (i = 0; i < quote_content_lines; i++)
883 {
884 memcpy(content + len, ": ", 2); // quote line prefix
885 len += 2;
886 memcpy(content + len, content_f + line_offsets[i], (size_t)(line_offsets[i + 1] - line_offsets[i]));
887 len += (line_offsets[i + 1] - line_offsets[i]);
888 if (content[len - 1] != '\n') // Appennd \n if not exist
889 {
890 content[len] = '\n';
891 len++;
892 }
893 }
894 if (content[len - 1] != '\n') // Appennd \n if not exist
895 {
896 content[len] = '\n';
897 len++;
898 }
899 content[len] = '\0';
900
901 free(content_f);
902 content_f = NULL;
903
904 p_editor_data = editor_data_load(content);
905 if (p_editor_data == NULL)
906 {
907 log_error("editor_data_load(aid=%d, cid=%d) error\n", p_article->aid, atoi(row[0]));
908 ret = -1;
909 goto cleanup;
910 }
911
912 free(content);
913 content = NULL;
914 }
915 mysql_free_result(rs);
916 rs = NULL;
917
918 mysql_close(db);
919 db = NULL;
920
921 // Set title and sign
922 for (ch = 'T'; !SYS_server_exit;)
923 {
924 clearscr();
925 moveto(21, 1);
926 prints("回复文章于 %s[%s] 讨论区,回复通知:%s", p_section->stitle, p_section->sname, (reply_note ? "开启" : "关闭"));
927 moveto(22, 1);
928 prints("标题: %s", (p_article_new->title[0] == '\0' ? "[无]" : p_article_new->title));
929 moveto(23, 1);
930 prints("使用第 %d 个签名", sign_id);
931
932 if (toupper(ch) != 'T')
933 {
934 prints(" 按0~3选签名档(0表示不使用)");
935
936 moveto(24, 1);
937 prints("T改标题, C取消, N%s, Enter继续: ",
938 (reply_note ? "关闭回复通知" : "开启回复通知"));
939 iflush();
940 ch = 0;
941 }
942
943 while (!SYS_server_exit)
944 {
945 switch (toupper(ch))
946 {
947 case KEY_NULL:
948 case KEY_TIMEOUT:
949 goto cleanup;
950 case CR:
951 break;
952 case 'T':
953 len = get_data(24, 1, "标题: ", title_input, sizeof(title_input), TITLE_INPUT_MAX_LEN);
954 for (p = title_input; *p == ' '; p++)
955 ;
956 for (q = title_input + len; q > p && *(q - 1) == ' '; q--)
957 ;
958 *q = '\0';
959 len = q - p;
960 if (*p != '\0')
961 {
962 if ((ret = check_badwords(p, '*')) < 0)
963 {
964 log_error("check_badwords(title) error\n");
965 }
966 else if (ret > 0)
967 {
968 memcpy(title_input, p, (size_t)len + 1);
969 continue;
970 }
971 memcpy(p_article_new->title, p, (size_t)len + 1);
972 memcpy(title_input, p_article_new->title, (size_t)len + 1);
973 }
974 ch = 0;
975 break;
976 case 'C':
977 clearscr();
978 moveto(1, 1);
979 prints("取消...");
980 press_any_key();
981 goto cleanup;
982 case 'N':
983 reply_note = (reply_note ? 0 : 1);
984 break;
985 case '0':
986 case '1':
987 case '2':
988 case '3':
989 sign_id = ch - '0';
990 break;
991 default: // Invalid selection
992 ch = igetch_t(BBS_max_user_idle_time);
993 continue;
994 }
995
996 break;
997 }
998
999 if (ch != CR || p_article_new->title[0] == '\0')
1000 {
1001 continue;
1002 }
1003
1004 for (ch = 'E'; !SYS_server_exit && toupper(ch) == 'E';)
1005 {
1006 editor_display(p_editor_data);
1007
1008 clearscr();
1009 moveto(1, 1);
1010 prints("(S)发送, (C)取消, (T)更改标题 or (E)再编辑? [S]: ");
1011 iflush();
1012
1013 for (ch = 0; !SYS_server_exit; ch = igetch_t(BBS_max_user_idle_time))
1014 {
1015 switch (toupper(ch))
1016 {
1017 case KEY_NULL:
1018 case KEY_TIMEOUT:
1019 goto cleanup;
1020 case CR:
1021 case 'S':
1022 break;
1023 case 'C':
1024 clearscr();
1025 moveto(1, 1);
1026 prints("取消...");
1027 press_any_key();
1028 goto cleanup;
1029 case 'T':
1030 break;
1031 case 'E':
1032 break;
1033 default: // Invalid selection
1034 continue;
1035 }
1036
1037 break;
1038 }
1039 }
1040
1041 if (toupper(ch) != 'T')
1042 {
1043 break;
1044 }
1045 }
1046
1047 if (SYS_server_exit) // Do not save data on shutdown
1048 {
1049 goto cleanup;
1050 }
1051
1052 content = malloc(ARTICLE_CONTENT_MAX_LEN);
1053 if (content == NULL)
1054 {
1055 log_error("malloc(content) error: OOM\n");
1056 ret = -1;
1057 goto cleanup;
1058 }
1059
1060 len_content = editor_data_save(p_editor_data, content, ARTICLE_CONTENT_MAX_LEN);
1061 if (len_content < 0)
1062 {
1063 log_error("editor_data_save() error\n");
1064 ret = -1;
1065 goto cleanup;
1066 }
1067
1068 if (check_badwords(content, '*') < 0)
1069 {
1070 log_error("check_badwords(content) error\n");
1071 ret = -1;
1072 goto cleanup;
1073 }
1074
1075 db = db_open();
1076 if (db == NULL)
1077 {
1078 log_error("db_open() error: %s\n", mysql_error(db));
1079 ret = -1;
1080 goto cleanup;
1081 }
1082
1083 if (sign_id > 0)
1084 {
1085 snprintf(sql, sizeof(sql),
1086 "SELECT sign_%d AS sign FROM user_pubinfo WHERE UID = %d",
1087 sign_id, BBS_priv.uid);
1088
1089 if (mysql_query(db, sql) != 0)
1090 {
1091 log_error("Query sign error: %s\n", mysql_error(db));
1092 ret = -1;
1093 goto cleanup;
1094 }
1095 if ((rs = mysql_use_result(db)) == NULL)
1096 {
1097 log_error("Get sign data failed\n");
1098 ret = -1;
1099 goto cleanup;
1100 }
1101
1102 if ((row = mysql_fetch_row(rs)))
1103 {
1104 len_content += snprintf(content + len_content,
1105 ARTICLE_CONTENT_MAX_LEN - (size_t)len_content,
1106 "\n\n--\n%s\n", row[0]);
1107 }
1108 mysql_free_result(rs);
1109 rs = NULL;
1110 }
1111
1112 // Calculate display length of content
1113 content_display_length = str_length(content, 1);
1114
1115 // Begin transaction
1116 if (mysql_query(db, "SET autocommit=0") != 0)
1117 {
1118 log_error("SET autocommit=0 error: %s\n", mysql_error(db));
1119 ret = -1;
1120 goto cleanup;
1121 }
1122
1123 if (mysql_query(db, "BEGIN") != 0)
1124 {
1125 log_error("Begin transaction error: %s\n", mysql_error(db));
1126 ret = -1;
1127 goto cleanup;
1128 }
1129
1130 // Secure SQL parameters
1131 content_f = malloc((size_t)len_content * 2 + 1);
1132 if (content_f == NULL)
1133 {
1134 log_error("malloc(content_f) error: OOM\n");
1135 ret = -1;
1136 goto cleanup;
1137 }
1138
1139 mysql_real_escape_string(db, nickname_f, BBS_nickname, (unsigned long)strnlen(BBS_nickname, sizeof(BBS_nickname)));
1140 mysql_real_escape_string(db, title_f, p_article_new->title, strnlen(p_article_new->title, sizeof(p_article_new->title)));
1141 mysql_real_escape_string(db, content_f, content, (unsigned long)len_content);
1142
1143 free(content);
1144 content = NULL;
1145
1146 // Add content
1147 sql_content = malloc(SQL_BUFFER_LEN + (size_t)len_content * 2 + 1);
1148 if (sql_content == NULL)
1149 {
1150 log_error("malloc(sql_content) error: OOM\n");
1151 ret = -1;
1152 goto cleanup;
1153 }
1154
1155 snprintf(sql_content, SQL_BUFFER_LEN + (size_t)len_content * 2 + 1,
1156 "INSERT INTO bbs_content(AID, content) values(0, '%s')",
1157 content_f);
1158
1159 free(content_f);
1160 content_f = NULL;
1161
1162 if (mysql_query(db, sql_content) != 0)
1163 {
1164 log_error("Add article content error: %s\n", mysql_error(db));
1165 ret = -1;
1166 goto cleanup;
1167 }
1168
1169 p_article_new->cid = (int32_t)mysql_insert_id(db);
1170
1171 free(sql_content);
1172 sql_content = NULL;
1173
1174 // Add article
1175 snprintf(sql, sizeof(sql),
1176 "INSERT INTO bbs(SID, TID, UID, username, nickname, title, CID, transship, "
1177 "sub_dt, sub_ip, reply_note, exp, last_reply_dt, icon, length) "
1178 "VALUES(%d, %d, %d, '%s', '%s', '%s', %d, 0, NOW(), '%s', %d, %d, NOW(), 1, %d)",
1179 p_section->sid, (p_article->tid == 0 ? p_article->aid : p_article->tid),
1180 BBS_priv.uid, BBS_username, nickname_f, title_f,
1181 p_article_new->cid, hostaddr_client,
1182 reply_note, BBS_user_exp, content_display_length);
1183
1184 if (mysql_query(db, sql) != 0)
1185 {
1186 log_error("Add article error: %s\n", mysql_error(db));
1187 ret = -1;
1188 goto cleanup;
1189 }
1190
1191 p_article_new->aid = (int32_t)mysql_insert_id(db);
1192
1193 // Update topic article
1194 snprintf(sql, sizeof(sql),
1195 "UPDATE bbs SET reply_count = reply_count + 1, "
1196 "last_reply_dt = NOW(), last_reply_UID=%d, last_reply_username = '%s', "
1197 "last_reply_nickname = '%s' WHERE AID = %d",
1198 BBS_priv.uid, BBS_username, nickname_f,
1199 (p_article->tid == 0 ? p_article->aid : p_article->tid));
1200
1201 if (mysql_query(db, sql) != 0)
1202 {
1203 log_error("Update topic article error: %s\n", mysql_error(db));
1204 ret = -1;
1205 goto cleanup;
1206 }
1207
1208 // Link content to article
1209 snprintf(sql, sizeof(sql),
1210 "UPDATE bbs_content SET AID = %d WHERE CID = %d",
1211 p_article_new->aid, p_article_new->cid);
1212
1213 if (mysql_query(db, sql) != 0)
1214 {
1215 log_error("Update content error: %s\n", mysql_error(db));
1216 ret = -1;
1217 goto cleanup;
1218 }
1219
1220 // Notify the authors of the topic / article which is replyed.
1221 snprintf(sql, sizeof(sql),
1222 "SELECT DISTINCT UID FROM bbs WHERE (AID = %d OR AID = %d) "
1223 "AND visible AND reply_note AND UID <> %d",
1224 p_article->tid, p_article->aid, BBS_priv.uid);
1225
1226 if (mysql_query(db, sql) != 0)
1227 {
1228 log_error("Read reply info error: %s\n", mysql_error(db));
1229 ret = -1;
1230 goto cleanup;
1231 }
1232 if ((rs = mysql_store_result(db)) == NULL)
1233 {
1234 log_error("Get reply info failed\n");
1235 ret = -1;
1236 goto cleanup;
1237 }
1238
1239 while ((row = mysql_fetch_row(rs)))
1240 {
1241 // Send notification message
1242 len_msg = snprintf(msg, BBS_msg_max_len,
1243 "有人回复了您所发表/回复的文章,快来"
1244 "[article %d]看看[/article]《%s》吧!\n",
1245 p_article_new->aid, title_f);
1246
1247 mysql_real_escape_string(db, msg_f, msg, (unsigned long)len_msg);
1248
1249 snprintf(sql, sizeof(sql),
1250 "INSERT INTO bbs_msg(fromUID, toUID, content, send_dt, send_ip) "
1251 "VALUES(%d, %d, '%s', NOW(), '%s')",
1252 BBS_sys_id, atoi(row[0]), msg_f, hostaddr_client);
1253
1254 if (mysql_query(db, sql) != 0)
1255 {
1256 log_error("Insert msg error: %s\n", mysql_error(db));
1257 ret = -1;
1258 goto cleanup;
1259 }
1260 }
1261 mysql_free_result(rs);
1262 rs = NULL;
1263
1264 // Add exp
1265 if (checkpriv(&BBS_priv, p_section->sid, S_GETEXP)) // Except in test section
1266 {
1267 snprintf(sql, sizeof(sql),
1268 "UPDATE user_pubinfo SET exp = exp + %d WHERE UID = %d",
1269 3, BBS_priv.uid);
1270
1271 if (mysql_query(db, sql) != 0)
1272 {
1273 log_error("Update exp error: %s\n", mysql_error(db));
1274 ret = -1;
1275 goto cleanup;
1276 }
1277 }
1278
1279 // Add log
1280 snprintf(sql, sizeof(sql),
1281 "INSERT INTO bbs_article_op(AID, UID, type, op_dt, op_ip)"
1282 "VALUES(%d, %d, 'A', NOW(), '%s')",
1283 p_article_new->aid, BBS_priv.uid, hostaddr_client);
1284
1285 if (mysql_query(db, sql) != 0)
1286 {
1287 log_error("Add log error: %s\n", mysql_error(db));
1288 ret = -1;
1289 goto cleanup;
1290 }
1291
1292 // Commit transaction
1293 if (mysql_query(db, "COMMIT") != 0)
1294 {
1295 log_error("Commit transaction error: %s\n", mysql_error(db));
1296 ret = -1;
1297 goto cleanup;
1298 }
1299
1300 mysql_close(db);
1301 db = NULL;
1302
1303 clearscr();
1304 moveto(1, 1);
1305 prints("发送完成,新文章通常会在%d秒后可见", BBS_section_list_load_interval);
1306 press_any_key();
1307 ret = 1; // Success
1308
1309 cleanup:
1310 mysql_free_result(rs);
1311 mysql_close(db);
1312
1313 // Cleanup buffers
1314 editor_data_cleanup(p_editor_data);
1315
1316 free(sql_content);
1317 free(content);
1318 free(content_f);
1319
1320 return (int)ret;
1321 }

webmaster@leafok.com
ViewVC Help
Powered by ViewVC 1.3.0-beta1