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

Annotation of /lbbs/src/article_post.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.36 - (hide annotations)
Thu Oct 30 11:15:12 2025 UTC (4 months, 2 weeks ago) by sysadm
Branch: MAIN
Changes since 1.35: +2 -2 lines
Content type: text/x-csrc
Refine handling of unsupported / invalid ANSI control sequence

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

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