diff options
author | piaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204> | 2010-10-23 11:23:31 +0800 |
---|---|---|
committer | piaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204> | 2010-10-23 11:23:31 +0800 |
commit | 5ff0600fafe917a9b692283f18d6b666af214538 (patch) | |
tree | 254d63fd599481f2266a9eff103633d2db0395d6 | |
parent | f85382c5c198704453473598273a5f451eacc20e (diff) | |
download | pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar.gz pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar.bz2 pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar.lz pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar.xz pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar.zst pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.zip |
New UI subsystem: Panty & Stocking Browser (psb).
First two clients: view_edit_history and admin_edit were also included.
git-svn-id: http://opensvn.csie.org/pttbbs/trunk@5143 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
-rw-r--r-- | pttbbs/include/proto.h | 4 | ||||
-rw-r--r-- | pttbbs/include/pttstruct.h | 2 | ||||
-rw-r--r-- | pttbbs/mbbsd/Makefile | 10 | ||||
-rw-r--r-- | pttbbs/mbbsd/admin.c | 144 | ||||
-rw-r--r-- | pttbbs/mbbsd/bbs.c | 51 | ||||
-rw-r--r-- | pttbbs/mbbsd/mail.c | 4 | ||||
-rw-r--r-- | pttbbs/mbbsd/psb.c | 460 |
7 files changed, 526 insertions, 149 deletions
diff --git a/pttbbs/include/proto.h b/pttbbs/include/proto.h index 7ed488c9..8bca64ee 100644 --- a/pttbbs/include/proto.h +++ b/pttbbs/include/proto.h @@ -150,6 +150,10 @@ int card_99(void); int ccw_talk(int fd, int destuid); // common chat window: private talk int ccw_chat(int fd); // common chat window: chatroom +/* psb (panty and stocking browser) */ +int psb_view_edit_history(const char *base, const char *subject, int max_hist); +int psb_admin_edit(); + /* chc */ void chc(int s, ChessGameMode mode); int chc_main(void); diff --git a/pttbbs/include/pttstruct.h b/pttbbs/include/pttstruct.h index 3e8d7310..0f73f262 100644 --- a/pttbbs/include/pttstruct.h +++ b/pttbbs/include/pttstruct.h @@ -450,7 +450,7 @@ typedef struct menu_t { /* Used to pass commands to the readmenu. * direct mapping, indexed by ascii code. */ -#define onekey_size ((int) 'z') +#define onekey_size ((int) '~') /* keymap, 若 needitem = 0 表示不需要 item, func 的 type 應為 int (*)(void). * 否則應為 int (*)(int ent, const fileheader_t *fhdr, const char *direct) */ typedef struct { diff --git a/pttbbs/mbbsd/Makefile b/pttbbs/mbbsd/Makefile index 6b7b0beb..a77dee71 100644 --- a/pttbbs/mbbsd/Makefile +++ b/pttbbs/mbbsd/Makefile @@ -8,19 +8,19 @@ SRCROOT= .. ####################################################################### PROG= mbbsd -COREOBJS = bbs.o announce.o read.o board.o cache.o cal.o brc.o mail.o record.o fav.o +COREOBJS = bbs.o announce.o read.o board.o brc.o mail.o record.o fav.o ACCOBJS = user.o register.o passwd.o emaildb.o NETOBJS = mbbsd.o io.o term.o telnet.o TALKOBJS = friend.o talk.o ccw.o -UTILOBJS = stuff.o kaede.o convert.o name.o syspost.o +UTILOBJS = stuff.o kaede.o convert.o name.o syspost.o cache.o cal.o +UIOBJS = menu.o vtuikit.o psb.o PAGEROBJS= more.o pmore.o PLUGOBJS = calendar.o ordersong.o gamble.o angel.o CHESSOBJS= chess.o chc.o chc_tab.o ch_go.o ch_gomo.o ch_dark.o ch_reversi.o GAMEOBJS = card.o chicken.o -OBJS:= admin.o assess.o edit.o menu.o xyz.o var.o vtuikit.o \ - vote.o voteboard.o \ +OBJS:= admin.o assess.o edit.o xyz.o var.o vote.o voteboard.o \ $(COREOBJS) $(ACCOBJS) $(NETOBJS) $(TALKOBJS) $(UTILOBJS) \ - $(PAGEROBJS) $(PLUGOBJS) \ + $(UIOBJS) $(PAGEROBJS) $(PLUGOBJS) \ $(CHESSOBJS) $(GAMEOBJS) ####################################################################### diff --git a/pttbbs/mbbsd/admin.c b/pttbbs/mbbsd/admin.c index 854477b6..f3511a17 100644 --- a/pttbbs/mbbsd/admin.c +++ b/pttbbs/mbbsd/admin.c @@ -746,153 +746,11 @@ m_board(void) return 0; } -static void -str_unify_blank(char *s) -{ - while(*s) - { - if (*s == '\t') *s = ' '; - s++; - } -} - -// 偷懶一下,寫死最大上限。 -#define MAX_ENTRIES (100) -#define min(a,b) ((a)<(b) ? (a) : (b)) - -// TODO 哪天把這種 UI 寫的更 general 一點... - /* 設定系統檔案 */ int x_file(void) { - char *entries[MAX_ENTRIES] = {NULL}; - int centries = 0, i = 0; - char *fn, *v; - int sel = 0, page = 0; - char buf[PATHLEN]; - FILE *fp = NULL; - - fp = fopen(FN_CONF_EDITABLE, "rt"); - if (!fp) - { - // you can find a sample in sample/etc/editable - vmsgf("未設定可編輯檔案列表[%s],請洽系統站長。", FN_CONF_EDITABLE); - return 0; - } - - // load the editable file. - // format: filename [ \t]* description - while (centries < MAX_ENTRIES && fgets(buf, sizeof(buf), fp)) - { - if (!buf[0] || buf[0] == '#' || buf[0] == '.' - || buf[0] == '/' || buf[0] == ' ') - continue; - str_unify_blank(buf); // replace all \t to ' ' in buf. - v = strchr(buf, ' '); // find if description exists. - if (v == NULL) continue; - fn = strstr(buf, ".."); // see if someone trying to crack - if (fn && fn < v) continue; - // reject anything outside etc/ folder. - if (strncmp(buf, "etc/", strlen("etc/")) != 0) - continue; - chomp(buf); - entries[centries++] = strdup(buf); - } - fclose(fp); - if (centries == 0) - { - vmsg("無可編輯檔案,請洽系統站長。"); - return 0; - } - - // edit the files! - while (sel >= 0) - { - const int rows = t_lines-2; - - // display. - clear(); showtitle("系統檔案", "編輯系統檔案"); - for (i = page*rows; i < min(centries, (page+1)*rows); i++) - { - // parse entry - strlcpy(buf, entries[i], sizeof(buf)); - fn = buf; - v = strchr(fn, ' '); - *v++ = 0; - while (*v == ' ') v++; - if (strlen(fn) > 30) - strcpy(fn+30-2, ".."); - prints(" %3d. %s" - "%-36.36s " - ANSI_COLOR(0;1) "%-30.30s" - ANSI_RESET "\n", - i+1, - dashf(fn) ? ANSI_COLOR(1;32) : ANSI_COLOR(1;30;40), - v, fn); - } - vs_footer(" 編輯系統檔案 ", - " (jk/↑↓/0-9)移動 (Enter/→)編輯 (d)刪除 \t(q/←)跳出"); - cursor_show(1+sel-page*rows, 0); - switch((i = vkey())) - { - case 'q': case KEY_LEFT: - sel = -1; continue; - case KEY_HOME: sel = 0; break; - case KEY_END: sel = centries-1; break; - case KEY_PGDN: sel += rows; - if (sel >= centries) sel = centries-1; - break; - case KEY_PGUP: sel -= rows; - if (sel < 0) sel = 0; - break; - case 'k': case KEY_UP: - if (sel > 0) sel--; break; - case 'j': case KEY_DOWN: - if (sel < centries-1) sel++; break; - - case 'd': - strlcpy(buf, entries[sel], sizeof(buf)); - v = strchr(buf, ' '); *v++ = 0; - i = vansf("確定要刪除 %s 嗎? (y/N) ", v); - if (i == 'y') - unlink(buf); - vmsgf("系統檔案[%s]: %s", buf, !dashf(buf) ? - "刪除成功\ " : "未刪除"); - break; - - case KEY_ENTER: case KEY_RIGHT: - strlcpy(buf, entries[sel], sizeof(buf)); - v = strchr(buf, ' '); *v++ = 0; - i = veditfile(buf); - // log file change - if (i != EDIT_ABORTED) - { - log_filef("log/etc_edit.log", LOG_CREAT, - "%s %s %s # %s\n", Cdate(&now), - cuser.userid, buf, v); - } - vmsgf("系統檔案[%s]: %s", buf, (i == -1) ? - "未改變" : "更新完畢"); - break; - - default: - if (i >= '0' && i <= '9') - { - sel = search_num(i, centries-1); - if (sel < 0) sel = 0; - } - break; - } - // change page if required. - page = sel / rows; - } - - // free the entries - for (i = 0; i < centries; i++) - free(entries[i]); - - return FULLUPDATE; + return psb_admin_edit(); } static int add_board_record(const boardheader_t *board) diff --git a/pttbbs/mbbsd/bbs.c b/pttbbs/mbbsd/bbs.c index 68245c04..1b112a23 100644 --- a/pttbbs/mbbsd/bbs.c +++ b/pttbbs/mbbsd/bbs.c @@ -3541,6 +3541,53 @@ view_postinfo(int ent, const fileheader_t * fhdr, const char *direct, int crs_ln return FULLUPDATE; } +static int +view_post_history(int ent, const fileheader_t * fhdr, const char *direct) +{ + const char *err_no_history = "抱歉,此篇文章暫無編輯歷史記錄可供檢視"; + char hist_file[PATHLEN]; + int fd, maxhist = 0; + + // TODO allow author? + if (!(currmode & MODE_BOARD)) + return DONOTHING; + + if ((!fhdr) || + ((fhdr->filename[0] == '.' || !fhdr->filename[0]) && + (strcmp(fhdr->filename, FN_SAFEDEL) != 0))) { + vmsg(err_no_history); + return FULLUPDATE; + } + + // build history index file name + setdirpath(hist_file, direct, FN_EDITHISTORY "/"); + if (strcmp(FN_SAFEDEL, fhdr->filename) == 0) { + assert(strlen(FN_SAFEDEL) == strlen("M.")); + // M.1 is a dirty hack, anyway.. + strlcat(hist_file, "M.1", sizeof(hist_file)); + strlcat(hist_file, fhdr->filename + strlen(FN_SAFEDEL) + 1, sizeof(hist_file)); + } else { + strlcat(hist_file, fhdr->filename, sizeof(hist_file)); + } + + // TODO check if hist_file is a valid file (directoy gives fake result) + + fd = open(hist_file, O_RDONLY); + if (fd < 0) { + vmsg(err_no_history); + return FULLUPDATE; + } + read(fd, &maxhist, sizeof(maxhist)); + close(fd); + if (maxhist < 1) { + vmsg(err_no_history); + return FULLUPDATE; + } + + psb_view_edit_history(hist_file, fhdr->title, maxhist+1); + return FULLUPDATE; +} + #ifdef OUTJOBSPOOL /* 看板備份 */ static int @@ -4049,6 +4096,10 @@ const onekey_t read_comms[] = { { 1, old_cross_post }, // 'x' { 1, reply_post }, // 'y' { 0, b_man }, // 'z' 122 + { 0, NULL }, // '{' 123 + { 0, NULL }, // '|' 124 + { 0, NULL }, // '}' 125 + { 1, view_post_history }, // '~' 126 }; int diff --git a/pttbbs/mbbsd/mail.c b/pttbbs/mbbsd/mail.c index 0ddc60fc..84c4d69c 100644 --- a/pttbbs/mbbsd/mail.c +++ b/pttbbs/mbbsd/mail.c @@ -1934,6 +1934,10 @@ static const onekey_t mail_comms[] = { { 1, m_forward }, // 'x' { 1, multi_reply }, // 'y' { 0, mail_man }, // 'z' 122 + { 0, NULL }, // '{' 123 + { 0, NULL }, // '|' 124 + { 0, NULL }, // '}' 125 + { 0, NULL }, // '~' 126 }; int diff --git a/pttbbs/mbbsd/psb.c b/pttbbs/mbbsd/psb.c new file mode 100644 index 00000000..7c16c53b --- /dev/null +++ b/pttbbs/mbbsd/psb.c @@ -0,0 +1,460 @@ +/* $Id$ */ +#include "bbs.h" + +// Panty & Stocking Browser +// +// A generic framework for displaying pre-generated data by a simplified +// page-view user interface. +// +// Author: Hung-Te Lin (piaip) +// -------------------------------------------------------------------------- +// Copyright (c) 2010 Hung-Te Lin <piaip@csie.ntu.edu.tw> +// All rights reserved. +// Distributed under BSD license (GPL compatible). +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// -------------------------------------------------------------------------- + +/////////////////////////////////////////////////////////////////////////// +// Constant +#define PSB_EOF (-1) +#define PSB_NA (-2) +#define PSB_NOP (-3) + +/////////////////////////////////////////////////////////////////////////// +// Data Structure +typedef struct { + int curr, total, header_lines, footer_lines; + int key; + void *ctx; + int (*header)(void *ctx); + int (*footer)(void *ctx); + int (*renderer)(int i, int curr, int total, int rows, void *ctx); + int (*cursor)(int y, int curr, void *ctx); + int (*input_processor)(int key, int curr, int total, int rows, void *ctx); +} PSB_CTX; + +static int +psb_default_header(void *ctx) { + vs_hdr2bar("Panty & Stocking Browser", BBSNAME); + return 0; +} + +static int +psb_default_footer(void *ctx) { + vs_footer(" PSB 1.0 ", + " (↑/↓/PgUp/PgDn/0-9)Move (Enter/→)Select \t(q/←)Quit"); + return 0; +} + +static int +psb_default_renderer(int i, int curr, int total, int rows, void *ctx) { + prints(" %s(Demo) %5d / %5d Item\n", (i == curr) ? "*" : " ", i, total); + return 0; +} + +static int +psb_default_cursor(int y, int curr, void * ctx) { + outs("=>"); + // cursor_show(y, 0); + return 0; +} + +static int +psb_default_input_processor(int key, int curr, int total, int rows, void *ctx) { + switch(key) { + case 'q': + case KEY_LEFT: + return PSB_EOF; + + case KEY_HOME: + case '0': + return 0; + + case KEY_END: + case '$': + return total-1; + + case KEY_PGUP: + case Ctrl('B'): + case 'N': + if (curr / rows > 0) + return curr - rows; + return 0; + + case KEY_PGDN: + case Ctrl('F'): + case 'P': + if (curr + rows < total) + return curr + rows; + return total - 1; + + case KEY_UP: + case Ctrl('P'): + case 'p': + case 'k': + return (curr > 0) ? curr-1 : curr; + + case KEY_DOWN: + case Ctrl('N'): + case 'n': + case 'j': + return (curr + 1 < total) ? curr + 1 : curr; + + default: + if (key >= '0' && key <= '9') { + int newval = search_num(key, total); + if (newval >= 0 && newval < total) + return newval; + return curr; + } + break; + } + return PSB_NA; +} + +static void +psb_init_defaults(PSB_CTX *psbctx) { + // pre-setup + assert(psbctx); + if (!psbctx->header) + psbctx->header = psb_default_header; + if (!psbctx->footer) + psbctx->footer = psb_default_footer; + if (!psbctx->renderer) + psbctx->renderer = psb_default_renderer; + if (!psbctx->cursor) + psbctx->cursor = psb_default_cursor; + if (!psbctx->footer) + psbctx->footer = psb_default_footer; + + assert(psbctx->curr >= 0 && + psbctx->total >= 0 && + psbctx->curr < psbctx->total); + assert(psbctx->header_lines > 0 && + psbctx->footer_lines); +} + +int +psb_main(PSB_CTX *psbctx) +{ + psb_init_defaults(psbctx); + + while (1) { + int i; + int rows = t_lines - psbctx->header_lines - psbctx->footer_lines; + int base; + + assert(rows > 0); + base = psbctx->curr / rows * rows; + clear(); + SOLVE_ANSI_CACHE(); + psbctx->header(psbctx->ctx); + for (i = 0; i < rows; i++) { + move(psbctx->header_lines + i, 0); + SOLVE_ANSI_CACHE(); + if (base + i < psbctx->total) + psbctx->renderer(base + i, psbctx->curr, psbctx->total, + rows, psbctx->ctx); + } + move(t_lines - psbctx->footer_lines, 0); + SOLVE_ANSI_CACHE(); + psbctx->footer(psbctx->ctx); + i = psbctx->header_lines + psbctx->curr - base; + move(i, 0); + psbctx->cursor(i, psbctx->curr, psbctx->ctx); + psbctx->key = vkey(); + + i = PSB_NA; + if (psbctx->input_processor) + i = psbctx->input_processor(psbctx->key, psbctx->curr, + psbctx->total, rows, psbctx->ctx); + if (i == PSB_NA) + i = psb_default_input_processor(psbctx->key, psbctx->curr, + psbctx->total, rows, psbctx->ctx); + if (i == PSB_EOF) + break; + if (i == PSB_NOP) + continue; + + if (i >=0 && i < psbctx->total) + psbctx->curr = i; + } + return 0; +} + +/////////////////////////////////////////////////////////////////////////// +// View Edit History + +typedef struct { + const char *subject; + const char *filebase; +} pveh_ctx; + +static int +pveh_header(void *ctx) { + pveh_ctx *cx = (pveh_ctx*) ctx; + vs_hdr2barf(" 【檢視文章編輯歷史】 \t %s", cx->subject); + move(1, 0); + outs("請注意本系統不會永久保留所有的編輯歷史。"); + outs("\n"); + return 0; +} + +static int +pveh_footer(void *ctx) { + vs_footer(" 編輯歷史 ", + " (↑/↓/PgUp/PgDn/0-9)移動 (Enter/r/→)選擇 \t(q/←)跳出"); + return 0; +} + +static int +pveh_cursor(int y, int curr, void *ctx) { + // (y, 0) before drawing +#ifdef USE_PFTERM + outs("●\b"); +#else + cursor_show(y, 0); +#endif + return 0; +} + +static int +pveh_renderer(int i, int curr, int total, int rows, void *ctx) { + const char *subject = ""; + char fname[PATHLEN]; + time4_t ftime = 0; + pveh_ctx *cx = (pveh_ctx*) ctx; + + snprintf(fname, sizeof(fname), "%s.%03d", cx->filebase, i); + ftime = dasht(fname); + if (ftime != -1) + subject = Cdate(&ftime); + else + subject = "(記錄已過保留期限/已清除)"; + + prints(" %s%s 版本: #%08d ", + (i == curr) ? ANSI_COLOR(1;41;37) : "", + (ftime == -1) ? ANSI_COLOR(1;30) : "", + i + 1); + prints(" 時間: %-47s" ANSI_RESET "\n", subject); + return 0; +} + +static int +pveh_input_processor(int key, int curr, int total, int rows, void *ctx) { + char fname[PATHLEN]; + pveh_ctx *cx = (pveh_ctx*) ctx; + + switch (key) { + case KEY_ENTER: + case KEY_RIGHT: + case 'r': + snprintf(fname, sizeof(fname), "%s.%03d", cx->filebase, curr); + more(fname, YEA); + return PSB_NOP; + } + return PSB_NA; +} + +int +psb_view_edit_history(const char *base, const char *subject, int max_hist) { + pveh_ctx pvehctx = { + .subject = subject, + .filebase = base, + }; + PSB_CTX ctx = { + .curr = 0, + .total = max_hist, + .header_lines = 3, + .footer_lines = 2, + .ctx = (void*)&pvehctx, + .header = pveh_header, + .footer = pveh_footer, + .renderer = pveh_renderer, + .cursor = pveh_cursor, + .input_processor = pveh_input_processor, + }; + + // warning screen! + static char is_first_enter_pveh = 1; + if (is_first_enter_pveh) { + is_first_enter_pveh = 0; + clear(); + move(5, 0); + outs( +" 歡迎使用文章編輯歷史瀏覽系統!\n\n" +" 提醒您: (1) 此系統尚在實驗性開放中,站方未來會決定是否繼續提供此功\能。\n\n" +" (2) 所有的資料僅供參考,站方不保證此處為完整的電磁記錄。\n\n" +" (3) 所有的資料都可能不定期由系統清除掉。\n" +" 無編輯歷史不能代表沒有編輯過,也可能是被清除了\n\n" + ); + pressanykey(); + } + + + psb_main(&ctx); + return 0; +} + +/////////////////////////////////////////////////////////////////////////// +// Admin Edit + +// still 偷懶... +#define MAX_PAE_ENTRIES (256) + +typedef struct { + char *descs[MAX_PAE_ENTRIES]; + char *files[MAX_PAE_ENTRIES]; +} pae_ctx; + +static int +pae_header(void *ctx) { + vs_hdr2bar(" 【系統檔案】 ", " 編輯系統檔案"); + outs("請選取要編輯的檔案後按 Enter 開始修改\n"); + vbarf(ANSI_REVERSE + "%5s %-36s%-30s", "編號", "名 稱", "檔 名"); + return 0; +} + +static int +pae_footer(void *ctx) { + vs_footer(" 編輯系統檔案 ", + " (↑↓/0-9)移動 (Enter/e/r/→)編輯 (DEL/d)刪除 \t(q/←)跳出"); + return 0; +} + +static int +pae_renderer(int i, int curr, int total, int rows, void *ctx) { + pae_ctx *cx = (pae_ctx*) ctx; + prints(" %3d %s%s%-36.36s " ANSI_COLOR(1;37) "%-30.30s" ANSI_RESET "\n", + i+1, + (i == curr) ? ANSI_COLOR(41) : "", + dashf(cx->files[i]) ? ANSI_COLOR(1;36) : ANSI_COLOR(1;30), + cx->descs[i], cx->files[i]); + return 0; +} + +static int +pae_cursor(int y, int curr, void *ctx) { + cursor_show(y, 0); + return 0; +} + +static int +pae_input_processor(int key, int curr, int total, int rows, void *ctx) { + int result; + pae_ctx *cx = (pae_ctx*) ctx; + + switch(key) { + case KEY_DEL: + case 'd': + if (vansf("確定要刪除 %s 嗎? (y/N) ", cx->descs[curr]) == 'y') + unlink(cx->files[curr]); + vmsgf("系統檔案[%s]: %s", cx->files[curr], + !dashf(cx->files[curr]) ? "刪除成功\ " : "未刪除"); + return PSB_NOP; + + case KEY_ENTER: + case KEY_RIGHT: + case 'r': + case 'e': + case 'E': + result = veditfile(cx->files[curr]); + // log file change + if (result == EDIT_ABORTED) + { + log_filef("log/etc_edit.log", + LOG_CREAT, + "%s %s %s # %s\n", + Cdate(&now), + cuser.userid, + cx->files[curr], + cx->descs[curr]); + } + vmsgf("系統檔案[%s]: %s", + cx->files[curr], + (result == EDIT_ABORTED) ? "未改變" : "更新完畢"); + return PSB_NOP; + } + return PSB_NA; +} + +int +psb_admin_edit() { + int i; + char buf[PATHLEN*2]; + FILE *fp; + pae_ctx paectx = { }; + PSB_CTX ctx = { + .curr = 0, + .total = 0, + .header_lines = 4, + .footer_lines = 2, + .ctx = (void*)&paectx, + .header = pae_header, + .footer = pae_footer, + .renderer = pae_renderer, + .cursor = pae_cursor, + .input_processor = pae_input_processor, + }; + + fp = fopen(FN_CONF_EDITABLE, "rt"); + if (!fp) { + // you can find a sample in sample/etc/editable + vmsgf("未設定可編輯檔案列表[%s],請洽系統站長。", FN_CONF_EDITABLE); + return 0; + } + + // load the editable file. + // format: filename [ \t]* description + while (ctx.total < MAX_PAE_ENTRIES && + fgets(buf, sizeof(buf), fp)) { + char *k = buf, *v = buf; + if (!*buf || strchr("#./ \t\n\r", *buf)) + continue; + + // change \t to ' '. + while (*v) if (*v++ == '\t') *(v-1) = ' '; + v = strchr(buf, ' '); + if (v == NULL) + continue; + + // see if someone is trying to crack + k = strstr(buf, ".."); + if (k && k < v) + continue; + + // reject anything outside etc/ folder. + if (strncmp(buf, "etc/", strlen("etc/")) != 0) + continue; + + // adjust spaces + chomp(buf); + k = buf; *v++ = 0; + while (*v == ' ') v++; + trim(k); + trim(v); + + // add into context + paectx.files[ctx.total] = strdup(k); + paectx.descs[ctx.total] = strdup(v); + ctx.total++; + } + + psb_main(&ctx); + + for (i = 0; i < ctx.total; i++) { + free(paectx.files[i]); + free(paectx.descs[i]); + } + return 0; +} + |