summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libbbs/Makefile20
-rw-r--r--src/libbbs/log.c0
-rw-r--r--src/libbbs/money.c37
-rw-r--r--src/libbbs/string.c1
-rw-r--r--src/libbbsutil/Makefile20
-rw-r--r--src/libbbsutil/file.c276
-rw-r--r--src/libbbsutil/lock.c23
-rw-r--r--src/libbbsutil/log.c43
-rw-r--r--src/libbbsutil/net.c114
-rw-r--r--src/libbbsutil/sort.c10
-rw-r--r--src/libbbsutil/string.c284
-rw-r--r--src/libbbsutil/time.c116
12 files changed, 944 insertions, 0 deletions
diff --git a/src/libbbs/Makefile b/src/libbbs/Makefile
new file mode 100644
index 00000000..efbf9534
--- /dev/null
+++ b/src/libbbs/Makefile
@@ -0,0 +1,20 @@
+
+SRCROOT= ../..
+.include "$(SRCROOT)/pttbbs.mk"
+
+CFLAGS+= -I$(SRCROOT)/include
+
+OBJS= log.o string.o money.o
+TARGET= libbbs.a
+
+
+.SUFFIXES: .c .o
+.c.o:
+ $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $*.c
+
+$(TARGET): $(OBJS)
+ $(AR) cru $@ $(OBJS)
+ ranlib $@
+
+clean:
+ rm -f $(OBJS) $(TARGET)
diff --git a/src/libbbs/log.c b/src/libbbs/log.c
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/libbbs/log.c
diff --git a/src/libbbs/money.c b/src/libbbs/money.c
new file mode 100644
index 00000000..a6d54127
--- /dev/null
+++ b/src/libbbs/money.c
@@ -0,0 +1,37 @@
+#include <stdio.h>
+
+#include <libbbs.h>
+
+/* 計算贈與稅 */
+int
+give_tax(int money)
+{
+ int i, tax = 0;
+ int tax_bound[] = {1000000, 100000, 10000, 1000, 0};
+ double tax_rate[] = {0.4, 0.3, 0.2, 0.1, 0.08};
+ for (i = 0; i <= 4; i++)
+ if (money > tax_bound[i]) {
+ tax += (money - tax_bound[i]) * tax_rate[i];
+ money -= (money - tax_bound[i]);
+ }
+ return (tax <= 0) ? 1 : tax;
+}
+
+const char*
+money_level(int money)
+{
+ int i = 0;
+
+ static const char *money_msg[] =
+ {
+ "債台高築", "赤貧", "清寒", "普通", "小康",
+ "小富", "中富", "大富翁", "富可敵國", "比爾蓋\天", NULL
+ };
+ while (money_msg[i] && money > 10)
+ i++, money /= 10;
+
+ if(!money_msg[i])
+ i--;
+ return money_msg[i];
+}
+
diff --git a/src/libbbs/string.c b/src/libbbs/string.c
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/src/libbbs/string.c
@@ -0,0 +1 @@
+
diff --git a/src/libbbsutil/Makefile b/src/libbbsutil/Makefile
new file mode 100644
index 00000000..eafe2ab5
--- /dev/null
+++ b/src/libbbsutil/Makefile
@@ -0,0 +1,20 @@
+
+SRCROOT= ../..
+.include "$(SRCROOT)/pttbbs.mk"
+
+CFLAGS+= -I$(SRCROOT)/include
+
+OBJS= file.o lock.o log.o net.o sort.o string.o time.o
+TARGET= libbbsutil.a
+
+
+.SUFFIXES: .c .o
+.c.o:
+ $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $*.c
+
+$(TARGET): $(OBJS)
+ $(AR) cru $@ $(OBJS)
+ ranlib $@
+
+clean:
+ rm -f $(OBJS) $(TARGET)
diff --git a/src/libbbsutil/file.c b/src/libbbsutil/file.c
new file mode 100644
index 00000000..c508e2de
--- /dev/null
+++ b/src/libbbsutil/file.c
@@ -0,0 +1,276 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <strings.h>
+#include <dirent.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#include "libbbsutil.h"
+
+
+/* ----------------------------------------------------- */
+/* 檔案檢查函數:檔案、目錄、屬於 */
+/* ----------------------------------------------------- */
+
+/**
+ * 傳回 fname 的檔案大小
+ * @param fname
+ */
+off_t
+dashs(const char *fname)
+{
+ struct stat st;
+
+ if (!stat(fname, &st))
+ return st.st_size;
+ else
+ return -1;
+}
+
+/**
+ * 傳回 fname 的 mtime
+ * @param fname
+ */
+time4_t
+dasht(const char *fname)
+{
+ struct stat st;
+
+ if (!stat(fname, &st))
+ return st.st_mtime;
+ else
+ return -1;
+}
+
+/**
+ * 傳回 fname 是否為 symbolic link
+ * @param fname
+ */
+int
+dashl(const char *fname)
+{
+ struct stat st;
+
+ return (lstat(fname, &st) == 0 && S_ISLNK(st.st_mode));
+}
+
+/**
+ * 傳回 fname 是否為一般的檔案
+ * @param fname
+ */
+int
+dashf(const char *fname)
+{
+ struct stat st;
+
+ return (stat(fname, &st) == 0 && S_ISREG(st.st_mode));
+}
+
+/**
+ * 傳回 fname 是否為目錄
+ * @param fname
+ */
+int
+dashd(const char *fname)
+{
+ struct stat st;
+
+ return (stat(fname, &st) == 0 && S_ISDIR(st.st_mode));
+}
+
+#define BUFFER_SIZE 8192
+int copy_file_to_file(const char *src, const char *dst)
+{
+ char buf[BUFFER_SIZE];
+ int fdr, fdw, len;
+
+ if ((fdr = open(src, O_RDONLY)) < 0)
+ return -1;
+
+ if ((fdw = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0) {
+ close(fdr);
+ return -1;
+ }
+
+ while (1) {
+ len = read(fdr, buf, sizeof(buf));
+ if (len <= 0)
+ break;
+ write(fdw, buf, len);
+ if (len < BUFFER_SIZE)
+ break;
+ }
+
+ close(fdr);
+ close(fdw);
+ return 0;
+}
+#undef BUFFER_SIZE
+
+int copy_file_to_dir(const char *src, const char *dst)
+{
+ char buf[PATH_MAX];
+ char *slash;
+ if ((slash = rindex(src, '/')) == NULL)
+ snprintf(buf, sizeof(buf), "%s/%s", dst, src);
+ else
+ snprintf(buf, sizeof(buf), "%s/%s", dst, slash);
+ return copy_file_to_file(src, buf);
+}
+
+int copy_dir_to_dir(const char *src, const char *dst)
+{
+ DIR *dir;
+ struct dirent *entry;
+ struct stat st;
+ char buf[PATH_MAX], buf2[PATH_MAX];
+
+ if (stat(dst, &st) < 0)
+ if (mkdir(dst, 0700) < 0)
+ return -1;
+
+ if ((dir = opendir(src)) == NULL)
+ return -1;
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0 ||
+ strcmp(entry->d_name, "..") == 0)
+ continue;
+ snprintf(buf, sizeof(buf), "%s/%s", src, entry->d_name);
+ snprintf(buf2, sizeof(buf2), "%s/%s", dst, entry->d_name);
+ if (stat(buf, &st) < 0)
+ continue;
+ if (S_ISDIR(st.st_mode))
+ mkdir(buf2, 0700);
+ copy_file(buf, buf2);
+ }
+
+ closedir(dir);
+ return 0;
+}
+
+/**
+ * copy src to dst (recursively)
+ * @param src and dst are file or dir
+ * @return -1 if failed
+ */
+int copy_file(const char *src, const char *dst)
+{
+ struct stat st;
+
+ if (stat(dst, &st) == 0 && S_ISDIR(st.st_mode)) {
+ if (stat(src, &st) < 0)
+ return -1;
+
+ if (S_ISDIR(st.st_mode))
+ return copy_dir_to_dir(src, dst);
+ else if (S_ISREG(st.st_mode))
+ return copy_file_to_dir(src, dst);
+ return -1;
+ }
+ else if (stat(src, &st) == 0 && S_ISDIR(st.st_mode))
+ return copy_dir_to_dir(src, dst);
+ return copy_file_to_file(src, dst);
+}
+
+int
+Rename(const char *src, const char *dst)
+{
+ if (rename(src, dst) == 0)
+ return 0;
+ if (!strchr(src, ';') && !strchr(dst, ';'))
+ {
+ pid_t pid = fork();
+ if (pid == 0)
+ execl("/bin/mv", "mv", "-f", src, dst, (char *)NULL);
+ else if (pid > 0)
+ waitpid(pid, NULL, 0);
+ else
+ return -1;
+ }
+ return -1;
+}
+
+int
+Copy(const char *src, const char *dst)
+{
+ int fi, fo, bytes;
+ char buf[8192];
+ fi=open(src, O_RDONLY);
+ if(fi<0) return -1;
+ fo=open(dst, O_WRONLY | O_TRUNC | O_CREAT, 0600);
+ if(fo<0) {close(fi); return -1;}
+ while((bytes=read(fi, buf, sizeof(buf)))>0)
+ write(fo, buf, bytes);
+ close(fo);
+ close(fi);
+ return 0;
+}
+
+int
+CopyN(const char *src, const char *dst, int n)
+{
+ int fi, fo, bytes;
+ char buf[8192];
+
+ fi=open(src, O_RDONLY);
+ if(fi<0) return -1;
+
+ fo=open(dst, O_WRONLY | O_TRUNC | O_CREAT, 0600);
+ if(fo<0) {close(fi); return -1;}
+
+ while(n > 0 && (bytes=read(fi, buf, sizeof(buf)))>0)
+ {
+ n -= bytes;
+ if (n < 0)
+ bytes += n;
+ write(fo, buf, bytes);
+ }
+ close(fo);
+ close(fi);
+ return 0;
+}
+
+/* append data from tail of src (starting point=off) to dst */
+int
+AppendTail(const char *src, const char *dst, int off)
+{
+ int fi, fo, bytes;
+ char buf[8192];
+
+ fi=open(src, O_RDONLY);
+ if(fi<0) return -1;
+
+ fo=open(dst, O_WRONLY | O_APPEND | O_CREAT, 0600);
+ if(fo<0) {close(fi); return -1;}
+
+ if(off > 0)
+ lseek(fi, (off_t)off, SEEK_SET);
+
+ while((bytes=read(fi, buf, sizeof(buf)))>0)
+ {
+ write(fo, buf, bytes);
+ }
+ close(fo);
+ close(fi);
+ return 0;
+}
+
+/**
+ * @param src file
+ * @param dst file
+ * @return 0 if success
+ */
+int
+Link(const char *src, const char *dst)
+{
+ if (symlink(src, dst) == 0)
+ return 0;
+
+ return Copy(src, dst);
+}
+
diff --git a/src/libbbsutil/lock.c b/src/libbbsutil/lock.c
new file mode 100644
index 00000000..4b28bab1
--- /dev/null
+++ b/src/libbbsutil/lock.c
@@ -0,0 +1,23 @@
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+/**
+ * lock fd
+ * @param mode F_WRLCK, F_UNLCK
+ */
+void
+PttLock(int fd, int start, int size, int mode)
+{
+ static struct flock lock_it;
+ int ret;
+
+ lock_it.l_whence = SEEK_CUR;/* from current point */
+ lock_it.l_start = start; /* -"- */
+ lock_it.l_len = size; /* length of data */
+ lock_it.l_type = mode; /* set exclusive/write lock */
+ lock_it.l_pid = 0; /* pid not actually interesting */
+ while ((ret = fcntl(fd, F_SETLKW, &lock_it)) < 0 && errno == EINTR)
+ sleep(1);
+}
+
diff --git a/src/libbbsutil/log.c b/src/libbbsutil/log.c
new file mode 100644
index 00000000..9fe3d514
--- /dev/null
+++ b/src/libbbsutil/log.c
@@ -0,0 +1,43 @@
+#include <stdarg.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "libbbsutil.h"
+
+int
+log_filef(const char *fn, int log_flag, const char *fmt,...)
+{
+ char msg[256];
+
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(msg, sizeof(msg), fmt, ap);
+ va_end(ap);
+
+ return log_file(fn, log_flag, msg);
+}
+
+int
+log_file(const char *fn, int log_flag, const char *msg)
+{
+ int fd;
+ int flag = O_APPEND | O_WRONLY;
+ int mode = 0664;
+
+ if (log_flag & LOG_CREAT)
+ flag |= O_CREAT;
+
+ fd = open(fn, flag, mode);
+ if( fd < 0 )
+ return -1;
+
+ if( write(fd, msg, strlen(msg)) < 0 ){
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
diff --git a/src/libbbsutil/net.c b/src/libbbsutil/net.c
new file mode 100644
index 00000000..60208a65
--- /dev/null
+++ b/src/libbbsutil/net.c
@@ -0,0 +1,114 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#include "libbbsutil.h"
+
+unsigned int
+ipstr2int(const char *ip)
+{
+ unsigned int i, val = 0;
+ char buf[32];
+ char *nil, *p;
+
+ strlcpy(buf, ip, sizeof(buf));
+ p = buf;
+ for (i = 0; i < 4; i++) {
+ nil = strchr(p, '.');
+ if (nil != NULL)
+ *nil = 0;
+ val *= 256;
+ val += atoi(p);
+ if (nil != NULL)
+ p = nil + 1;
+ }
+ return val;
+}
+
+int tobind(const char * host, int port)
+{
+ int sockfd, val = 1;
+ struct sockaddr_in servaddr;
+
+ if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
+ perror("socket()");
+ exit(1);
+ }
+ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&val, sizeof(val));
+ bzero(&servaddr, sizeof(servaddr));
+ servaddr.sin_family = AF_INET;
+ if (host == NULL || host[0] == 0)
+ servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ else if (inet_aton(host, &servaddr.sin_addr) == 0) {
+ perror("inet_aton()");
+ exit(1);
+ }
+ servaddr.sin_port = htons(port);
+ if( bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0 ) {
+ perror("bind()");
+ exit(1);
+ }
+ if( listen(sockfd, 5) < 0 ) {
+ perror("listen()");
+ exit(1);
+ }
+
+ return sockfd;
+}
+
+int toconnect(const char *host, int port)
+{
+ int sock;
+ struct sockaddr_in serv_name;
+ if( (sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ){
+ perror("socket");
+ return -1;
+ }
+
+ serv_name.sin_family = AF_INET;
+ serv_name.sin_addr.s_addr = inet_addr(host);
+ serv_name.sin_port = htons(port);
+ if( connect(sock, (struct sockaddr*)&serv_name, sizeof(serv_name)) < 0 ){
+ close(sock);
+ return -1;
+ }
+ return sock;
+}
+
+/**
+ * same as read(2), but read until exactly size len
+ */
+int toread(int fd, void *buf, int len)
+{
+ int l;
+ for( l = 0 ; len > 0 ; )
+ if( (l = read(fd, buf, len)) <= 0 )
+ return -1;
+ else{
+ buf += l;
+ len -= l;
+ }
+ return l;
+}
+
+/**
+ * same as write(2), but write until exactly size len
+ */
+int towrite(int fd, const void *buf, int len)
+{
+ int l;
+ for( l = 0 ; len > 0 ; )
+ if( (l = write(fd, buf, len)) <= 0 )
+ return -1;
+ else{
+ buf += l;
+ len -= l;
+ }
+ return l;
+}
diff --git a/src/libbbsutil/sort.c b/src/libbbsutil/sort.c
new file mode 100644
index 00000000..edecc0a8
--- /dev/null
+++ b/src/libbbsutil/sort.c
@@ -0,0 +1,10 @@
+
+int cmp_int(const void *a, const void *b)
+{
+ return *(int*)a - *(int*)b;
+}
+
+int cmp_int_desc(const void * a, const void * b)
+{
+ return cmp_int(b, a);
+}
diff --git a/src/libbbsutil/string.c b/src/libbbsutil/string.c
new file mode 100644
index 00000000..695d65ab
--- /dev/null
+++ b/src/libbbsutil/string.c
@@ -0,0 +1,284 @@
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include "fnv_hash.h"
+
+#include "ansi.h"
+#include "libbbsutil.h"
+
+#define CHAR_LOWER(c) ((c >= 'A' && c <= 'Z') ? c|32 : c)
+/* ----------------------------------------------------- */
+/* 字串轉換檢查函數 */
+/* ----------------------------------------------------- */
+/**
+ * 將字串 s 轉為小寫存回 t
+ * @param t allocated char array
+ * @param s
+ */
+void
+str_lower(char *t, const char *s)
+{
+ register unsigned char ch;
+
+ do {
+ ch = *s++;
+ *t++ = CHAR_LOWER(ch);
+ } while (ch);
+}
+
+/**
+ * 移除字串 buf 後端多餘的空白。
+ * @param buf
+ */
+void
+trim(char *buf)
+{ /* remove trailing space */
+ char *p = buf;
+
+ while (*p)
+ p++;
+ while (--p >= buf) {
+ if (*p == ' ')
+ *p = '\0';
+ else
+ break;
+ }
+}
+
+/**
+ * 移除 src 的 '\n' 並改成 '\0'
+ * @param src
+ */
+void chomp(char *src)
+{
+ while(*src){
+ if (*src == '\n')
+ *src = 0;
+ else
+ src++;
+ }
+}
+
+int
+strip_blank(char *cbuf, char *buf)
+{
+ for (; *buf; buf++)
+ if (*buf != ' ')
+ *cbuf++ = *buf;
+ *cbuf = 0;
+ return 0;
+}
+
+static const char EscapeFlag[] = {
+ /* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0, 0, 0, 0, 0,
+ /* 20 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, /* 0~9 ;= */
+ /* 40 */ 0, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, /* ABCDHIJK */
+ /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 60 */ 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 2, 2, 0, 0, /* fhlm */
+ /* 70 */ 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* su */
+ /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* A0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* B0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* C0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* D0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* E0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* F0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+/**
+ * 根據 mode 來 strip 字串 src,並把結果存到 dst
+ * @param dst
+ * @param src
+ * @param mode enum {STRIP_ALL = 0, ONLY_COLOR, NO_RELOAD};
+ * STRIP_ALL: 全部吃掉
+ * ONLY_COLOR: 吃掉所有跟顏色無關的 (ESC[*m)
+ * NO_RELOAD: 不 strip (?)
+ * @return strip 後的長度
+ */
+int
+strip_ansi(char *dst, const char *src, enum STRIP_FLAG mode)
+{
+ register int count = 0;
+#define isEscapeParam(X) (EscapeFlag[(int)(X)] & 1)
+#define isEscapeCommand(X) (EscapeFlag[(int)(X)] & 2)
+
+ for(; *src; ++src)
+ if( *src != ESC_CHR ){
+ if( dst )
+ *dst++ = *src;
+ ++count;
+ }else{
+ const char* p = src + 1;
+ if( *p != '[' ){
+ ++src;
+ if(*src=='\0') break;
+ continue;
+ }
+ while(isEscapeParam(*++p));
+ if( (mode == NO_RELOAD && isEscapeCommand(*p)) ||
+ (mode == ONLY_COLOR && *p == 'm' )){
+ register int len = p - src + 1;
+ if( dst ){
+ strncpy(dst, src, len);
+ dst += len;
+ }
+ count += len;
+ }
+ src = p;
+ if(*src=='\0') break;
+ }
+ if( dst )
+ *dst = 0;
+ return count;
+}
+
+int
+strlen_noansi(const char *s)
+{
+ // XXX this is almost identical to
+ // strip_ansi(NULL, s, STRIP_ALL)
+ register int count = 0, mode = 0;
+
+ if (!s || !*s)
+ return 0;
+
+ for (; *s; ++s)
+ {
+ // 0 - no ansi, 1 - [, 2 - param+cmd
+ switch (mode)
+ {
+ case 0:
+ if (*s == ESC_CHR)
+ mode = 1;
+ else
+ count ++;
+ break;
+
+ case 1:
+ if (*s == '[')
+ mode = 2;
+ else
+ mode = 0; // unknown command
+ break;
+
+ case 2:
+ if (isEscapeParam(*s))
+ continue;
+ else if (isEscapeCommand(*s))
+ mode = 0;
+ else
+ mode = 0;
+ break;
+ }
+ }
+ return count;
+}
+
+void
+strip_nonebig5(unsigned char *str, int maxlen)
+{
+ int i;
+ int len=0;
+ for(i=0;i<maxlen && str[i];i++) {
+ if(32<=str[i] && str[i]<128)
+ str[len++]=str[i];
+ else if(str[i]==255) {
+ if(i+1<maxlen)
+ if(251<=str[i+1] && str[i+1]<=254) {
+ i++;
+ if(i+1<maxlen && str[i+1])
+ i++;
+ }
+ continue;
+ } else if(str[i]&0x80) {
+ if(i+1<maxlen)
+ if((0x40<=str[i+1] && str[i+1]<=0x7e) ||
+ (0xa1<=str[i+1] && str[i+1]<=0xfe)) {
+ str[len++]=str[i];
+ str[len++]=str[i+1];
+ i++;
+ }
+ }
+ }
+ if(len<maxlen)
+ str[len]='\0';
+}
+
+/* ----------------------------------------------------- */
+/* 字串檢查函數:英文、數字、檔名、E-mail address */
+/* ----------------------------------------------------- */
+
+int
+invalid_pname(const char *str)
+{
+ const char *p1, *p2, *p3;
+
+ p1 = str;
+ while (*p1) {
+ if (!(p2 = strchr(p1, '/')))
+ p2 = str + strlen(str);
+ if (p1 + 1 > p2 || p1 + strspn(p1, ".") == p2) /* 不允許用 / 開頭, 或是 // 之間只有 . */
+ return 1;
+ for (p3 = p1; p3 < p2; p3++)
+ if (!isalnum(*p3) && !strchr("@[]-._", *p3)) /* 只允許 alnum 或這些符號 */
+ return 1;
+ p1 = p2 + (*p2 ? 1 : 0);
+ }
+ return 0;
+}
+
+/*
+ * return 1 if /^[0-9]+$/
+ * 0 else, 含空字串
+ */
+int is_number(const char *p)
+{
+ if (*p == '\0')
+ return 0;
+
+ for(; *p; p++) {
+ if (*p < '0' || '9' < *p)
+ return 0;
+ }
+ return 1;
+}
+
+unsigned
+StringHash(const char *s)
+{
+ return fnv1a_32_strcase(s, FNV1_32_INIT);
+}
+
+/* qp_encode() modified from mutt-1.5.7/rfc2047.c q_encoder() */
+const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
+char * qp_encode (char *s, size_t slen, const char *d, const char *tocode)
+{
+ char hex[] = "0123456789ABCDEF";
+ char *s0 = s;
+
+ memcpy (s, "=?", 2), s += 2;
+ memcpy (s, tocode, strlen (tocode)), s += strlen (tocode);
+ memcpy (s, "?Q?", 3), s += 3;
+ assert(s-s0+3<slen);
+
+ while (*d != '\0' && s-s0+6<slen)
+ {
+ unsigned char c = *d++;
+ if (c == ' ')
+ *s++ = '_';
+ else if (c >= 0x7f || c < 0x20 || c == '_' || strchr (MimeSpecials, c))
+ {
+ *s++ = '=';
+ *s++ = hex[(c & 0xf0) >> 4];
+ *s++ = hex[c & 0x0f];
+ }
+ else
+ *s++ = c;
+ }
+ memcpy (s, "?=", 2), s += 2;
+ *s='\0';
+ return s0;
+}
+
diff --git a/src/libbbsutil/time.c b/src/libbbsutil/time.c
new file mode 100644
index 00000000..2e0dbdd1
--- /dev/null
+++ b/src/libbbsutil/time.c
@@ -0,0 +1,116 @@
+#include <time.h>
+#include <stdio.h>
+#include "libbbsutil.h"
+
+static char cdate_buffer[32];
+
+/**
+ * 閏年
+ */
+int is_leap_year(int year)
+{
+ return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);
+}
+
+/**
+ * 給日期求星座
+ *
+ * @return 1..12
+ */
+int getHoroscope(int m, int d)
+{
+ if (m > 12 || m < 1)
+ return 1;
+
+ // 摩羯 水瓶 雙魚 牡羊 金牛 雙子 巨蟹 獅子 處女 天秤 天蠍 射手
+ const int firstday[12] = {
+ /* Jan. */ 20, 19, 21, 20, 21, 21, 23, 23, 23, 23, 22, 22
+ };
+ if (d >= firstday[m - 1]) {
+ if (m == 12)
+ return 1;
+ else
+ return m + 1;
+ }
+ else
+ return m;
+}
+
+/**
+ * 23+1 bytes, "12/31/2007 00:00:00 Mon\0"
+ */
+char *
+Cdate(const time4_t *clock)
+{
+ time_t temp = (time_t)*clock;
+ struct tm *mytm = localtime(&temp);
+
+ strftime(cdate_buffer, sizeof(cdate_buffer), "%m/%d/%Y %T %a", mytm);
+ return cdate_buffer;
+}
+
+/**
+ * 19+1 bytes, "12/31/2007 00:00:00\0"
+ */
+char *
+Cdatelite(const time4_t *clock)
+{
+ time_t temp = (time_t)*clock;
+ struct tm *mytm = localtime(&temp);
+
+ strftime(cdate_buffer, sizeof(cdate_buffer), "%m/%d/%Y %T", mytm);
+ return cdate_buffer;
+}
+
+/**
+ * 10+1 bytes, "12/31/2007\0"
+ */
+char *
+Cdatedate(const time4_t * clock)
+{
+ time_t temp = (time_t)*clock;
+ struct tm *mytm = localtime(&temp);
+
+ strftime(cdate_buffer, sizeof(cdate_buffer), "%m/%d/%Y", mytm);
+ return cdate_buffer;
+}
+
+#ifdef TIMET64
+char *
+ctime4(const time4_t *clock)
+{
+ time_t temp = (time_t)*clock;
+
+ return ctime(&temp);
+}
+
+struct tm *localtime4(const time4_t *t)
+{
+ if( t == NULL )
+ return localtime(NULL);
+ else {
+ time_t temp = (time_t)*t;
+ return localtime(&temp);
+ }
+}
+
+time4_t time4(time4_t *ptr)
+{
+ if( ptr == NULL )
+ return time(NULL);
+ else
+ return *ptr = (time4_t)time(NULL);
+}
+#endif
+
+char *
+my_ctime(const time4_t * t, char *ans, int len)
+{
+ struct tm *tp;
+
+ tp = localtime4((time4_t*)t);
+ snprintf(ans, len,
+ "%02d/%02d/%02d %02d:%02d:%02d", (tp->tm_year % 100),
+ tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);
+ return ans;
+}