view libnnchat.c @ 322:b9c15c57dc8f

Clean up message functions, add new printMsgQ() helper function for messages that should not go into the log file. Add skeleton help function, accessible via F1 key. And other cleanups.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 11 Jun 2011 09:48:26 +0300
parents a0c001672b44
children c086345d176b
line wrap: on
line source

/*
 * NNChat - Custom chat client for NewbieNudes.com chatrooms
 * Written by Matti 'ccr' Hämäläinen
 * (C) Copyright 2008-2011 Tecnic Software productions (TNSP)
 */
#include "libnnchat.h"


#ifdef __WIN32
const char *hstrerror(int err)
{
    static char buf[64];
    snprintf(buf, sizeof(buf), "Error #%d", err);
    return buf;
}

int nn_get_socket_errno(void)
{
    return WSAGetLastError();
}

const char *nn_get_socket_errstr(int err)
{
    static char buf[64];
    switch (err) {
        case WSAEADDRINUSE:      return "Address already in use";
        case WSAECONNABORTED:    return "Software caused connection abort";
        case WSAECONNREFUSED:    return "Connection refused";
        case WSAECONNRESET:      return "Connection reset by peer";
        case WSAEHOSTUNREACH:    return "No route to host";
        case WSAENETDOWN:        return "Network is down";
        case WSAETIMEDOUT:       return "Connection timed out";
        case WSAHOST_NOT_FOUND:  return "Host not found";
        case WSAVERNOTSUPPORTED: return "Wrong WinSock DLL version";
        default:
            snprintf(buf, sizeof(buf), "Error #%d", err);
            return buf;
            break;
    }
}
#else
int nn_get_socket_errno(void)
{
    return errno;
}

const char *nn_get_socket_errstr(int err)
{
    return strerror(err);
}
#endif


void nn_conn_err(nn_conn_t *conn, const char *fmt, ...)
{
    conn->err = TRUE;

    if (conn->errfunc) {
        va_list ap;
        va_start(ap, fmt);
        conn->errfunc(conn, fmt, ap);
        va_end(ap);
    }
}


static void nn_conn_msg(nn_conn_t *conn, const char *fmt, ...)
{
    if (conn->msgfunc) {
        va_list ap;
        va_start(ap, fmt);
        conn->msgfunc(conn, fmt, ap);
        va_end(ap);
    }
}


nn_conn_t *nn_conn_open(struct in_addr *addr, const int port)
{
    nn_conn_t *conn = th_calloc(1, sizeof(nn_conn_t));
    struct sockaddr_in dest;
    
    if (conn == NULL)
        return NULL;

    dest.sin_family = AF_INET;
    dest.sin_port = htons(port);
    dest.sin_addr = *addr;

    nn_conn_msg(conn, "Connecting to %s:%d ...\n",
        inet_ntoa(dest.sin_addr), port);

    if ((conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        nn_conn_err(conn, "Could not open socket: %s\n", strerror(errno));
        conn->status = NN_CONN_CLOSED;
        return conn;
    }
    
    nn_conn_msg(conn, "Using socket %d.\n", conn->socket);
    
    if (connect(conn->socket, (struct sockaddr *) &dest, sizeof(dest)) == -1) {
        nn_conn_err(conn, "Could not connect: %s\n", strerror(errno));
        conn->status = NN_CONN_CLOSED;
        return conn;
    }

    conn->port = port;
    conn->address = *addr;
    FD_ZERO(&(conn->sockfds));
    FD_SET(conn->socket, &(conn->sockfds));
    conn->status = NN_CONN_OPEN;
    
    return conn;
}


void nn_conn_close(nn_conn_t *conn)
{
    if (conn == NULL)
        return;
    
    if (conn->socket >= 0) {
#ifdef __WIN32
        closesocket(conn->socket);
#else
        close(conn->socket);
#endif
        conn->socket = -1;
    }
    
    conn->status = NN_CONN_CLOSED;
    
    th_free(conn);
}


BOOL nn_conn_send_buf(nn_conn_t *conn, const char *buf, const size_t len)
{
    size_t bufLeft = len;
    const char *bufPtr = buf;
    
    while (bufLeft > 0) {
        ssize_t bufSent;
        bufSent = send(conn->socket, bufPtr, bufLeft, 0);
        if (bufSent < 0) {
            nn_conn_err(conn, "nn_conn_send_buf() failed: %s", strerror(errno));
            return FALSE;
        }
        bufLeft -= bufSent;
        bufPtr += bufSent;
    }

    return TRUE;
}


BOOL nn_conn_pull(nn_conn_t *conn)
{
    int result;
    struct timeval socktv;
    fd_set tmpfds;

    if (conn == NULL)
        return -1;

    conn->ptr = conn->buf;

    /* Check for incoming data */
    socktv.tv_sec = 0;
    socktv.tv_usec = NN_DELAY_USEC;
    tmpfds = conn->sockfds;

    if ((result = select(conn->socket + 1, &tmpfds, NULL, NULL, &socktv)) == -1) {
        int err = nn_get_socket_errno();
        if (err != EINTR) {
            nn_conn_err(conn, "Error occured in select(%d, sockfds): %d, %s\n",
                socket, err, nn_get_socket_errstr(err));
            return -1;
        }
    } else
    if (FD_ISSET(conn->socket, &tmpfds)) {
        conn->got = recv(conn->socket, conn->ptr, NN_CONNBUF_SIZE, 0);

        if (conn->got < 0) {
            int res = nn_get_socket_errno();
            nn_conn_err(conn, "Error in recv: %d, %s\n", res, nn_get_socket_errstr(res));
            return -2;
        } else if (conn->got == 0) {
            nn_conn_err(conn, "Server closed connection.\n");
            conn->status = NN_CONN_CLOSED;
            return -3;
        } else {
            /* Handle protocol data */
            conn->buf[conn->got] = 0;
            return 0;
        }
    }
    
    return 1;
}

BOOL nn_conn_check(nn_conn_t *conn)
{
    if (conn == NULL)
        return FALSE;

    return conn->err == 0 && conn->status == NN_CONN_OPEN;
}



BOOL nn_network_init(void)
{
#ifdef __WIN32
    /* Initialize WinSock, if needed */
    WSADATA wsaData;
    int err = WSAStartup(0x0101, &wsaData);
    if (err != 0) {
        THERR("Could not initialize WinSock library (err=%d).\n", err);
        return FALSE;
    }
#endif
    return TRUE;
}


void nn_network_close(void)
{
#ifdef __WIN32
    WSACleanup();
#endif
}


#define PUSHCHAR(x) th_vputch(&result, &resSize, &resPos, x)
#define PUSHSTR(x) th_vputs(&result, &resSize, &resPos, x)

char *nn_encode_str1(const char *str)
{
    const char *s = str;
    char *result;
    size_t resSize, resPos = 0;
    
    if (str == NULL) return NULL;
    
    resSize = strlen(str) + NN_ALLOC_SIZE;
    if ((result = th_malloc(resSize)) == NULL)
        return NULL;
    
    while (*s) {
        switch (*s) {
        case 32:
            PUSHCHAR('+');
            break;
        
        default:
            if (th_isalnum(*s))
                PUSHCHAR(*s);
            else {
                char tmpStr[4];
                snprintf(tmpStr, sizeof(tmpStr), "%2X", (unsigned char) *s);
                PUSHCHAR('%');
                PUSHSTR(tmpStr);
            }
            break;
        }
        s++;
    }
    PUSHCHAR(0);
    
    return result;
}


static int getHexDigit(const int c, const int shift)
{
    int i;
    
    if (c >= 'A' && c <= 'F')
        i = c - 'A' + 10;
    else if (c >= 'a' && c <= 'f')
        i = c - 'a' + 10;
    else if (c >= '0' && c <= '9')
        i = c - '0';
    else
        return -1;
    
    return i << shift;
}


char *nn_decode_str1(const char *str)
{
    const char *s = str;
    char *result;
    size_t resSize, resPos = 0;
    int c;
    
    if (str == NULL) return NULL;
    
    resSize = strlen(str) + NN_ALLOC_SIZE;
    if ((result = th_malloc(resSize)) == NULL)
        return NULL;
    
    while (*s) {
        switch (*s) {
        case '+':
            PUSHCHAR(' ');
            s++;
            break;
        
        case '½':
            /* Escape these .. */
            PUSHCHAR('½');
            PUSHCHAR('½');
            s++;
            break;
            
        case '\r':
            PUSHCHAR(' ');
            s++;
            break;
            
        case '%':
            s++;
            if (*s == '%')
                PUSHCHAR('%');
            else if ((c = getHexDigit(*s, 4)) >= 0) {
                int i = getHexDigit(*(++s), 0);
                if (i >= 0) {
                    PUSHCHAR(c | i);
                } else {
                    PUSHCHAR('§');
                    PUSHCHAR(*s);
                }
            } else {
                PUSHCHAR('§');
                PUSHCHAR(*s);
            }
            s++;
            break;
        
        default:
            PUSHCHAR(*s);
            s++;
        }
    }
    PUSHCHAR(0);
    
    return result;
}


char *nn_strip_tags(const char *str)
{
    const char *s = str;
    char *result;
    size_t resSize, resPos = 0;
    
    if (str == NULL) return NULL;
    
    resSize = strlen(str) + NN_ALLOC_SIZE;
    if ((result = th_malloc(resSize)) == NULL)
        return NULL;
    
    while (*s) {
        if (*s == '<') {
            while (*s && *s != '>') s++;
            if (*s == '>') s++;
        } else
            PUSHCHAR(*s++);
    }
    PUSHCHAR(0);
    
    return result;
}


typedef struct {
    char c;
    char *ent;
} html_entity_t;


static const html_entity_t HTMLEntities[] = {
    { '<', "&lt;" },
    { '>', "&gt;" },
};

static const int numHTMLEntities = sizeof(HTMLEntities) / sizeof(HTMLEntities[0]);


char *nn_encode_str2(const char *str)
{
    const char *s = str;
    char *result;
    size_t resSize, resPos = 0;
    
    if (str == NULL) return NULL;
    
    resSize = strlen(str) + NN_ALLOC_SIZE;
    if ((result = th_malloc(resSize)) == NULL)
        return NULL;
    
    while (*s) {
        int i;
        BOOL found = FALSE;
        for (i = 0; i < numHTMLEntities; i++)
        if (HTMLEntities[i].c == *s) {
            PUSHSTR(HTMLEntities[i].ent);
            found = TRUE;
            break;
        }
        if (!found) PUSHCHAR(*s);
        
        s++;
    }
    PUSHCHAR(0);
    
    return result;
}


char *nn_decode_str2(const char *str)
{
    const char *s = str;
    char *result;
    size_t resSize, resPos = 0;
    
    if (str == NULL) return NULL;
    
    resSize = strlen(str);
    if ((result = th_malloc(resSize)) == NULL)
        return NULL;
    
    while (*s) {
        if (*s == '&') {
            int i;
            BOOL found = FALSE;
            for (i = 0; i < numHTMLEntities; i++) {
                const html_entity_t *ent = &HTMLEntities[i];
                int len = strlen(ent->ent);
                if (!strncmp(s, ent->ent, len)) {
                    PUSHCHAR(ent->c);
                    s += len;
                    found = TRUE;
                    break;
                }
            }
            if (!found) PUSHCHAR(*s++);
        } else
            PUSHCHAR(*s++);
    }
    PUSHCHAR(0);
    
    return result;
}


char *nn_dbldecode_str(const char *str)
{
    char *res, *tmp;
    
    if ((tmp = nn_decode_str1(str)) == NULL)
        return NULL;
    
    res = nn_decode_str2(tmp);
    th_free(tmp);
    
    return res;    
}


char *nn_dblencode_str(const char *str)
{
    char *res, *tmp;
    
    if ((tmp = nn_encode_str2(str)) == NULL)
        return NULL;
    
    res = nn_encode_str1(tmp);
    th_free(tmp);
    
    return res;    
}


BOOL nn_conn_send_msg(nn_conn_t *conn, const char *user, const char *fmt, ...)
{
    char *tmp, *msg;
    va_list ap;
    
    va_start(ap, fmt);
    tmp = th_strdup_vprintf(fmt, ap);
    va_end(ap);
    
    if (tmp == NULL)
        return FALSE;
    
    msg = th_strdup_printf("<USER>%s</USER><MESSAGE>%s</MESSAGE>", user, tmp);
    th_free(tmp);
    
    if (msg != NULL) {
        BOOL ret = nn_conn_send_buf(conn, msg, strlen(msg) + 1);
        th_free(msg);
        return ret;
    } else
        return FALSE;
}


int nn_editbuf_write(nn_editbuf_t *buf, ssize_t pos, int ch)
{
    if (buf->len+1 >= buf->size) return -3;
    
    if (pos < 0)
        return -1;
    else if (pos >= buf->len) {
        buf->data[buf->len++] = ch;
    } else {
        buf->data[pos] = ch;
    }
    return 0;
}


int nn_editbuf_insert(nn_editbuf_t *buf, ssize_t pos, int ch)
{
    if (buf->len+1 >= buf->size) return -3;
    
    if (pos < 0)
        return -1;
    else if (pos >= buf->len) {
        buf->data[buf->len] = ch;
    } else {
        memmove(&(buf->data[pos+1]), &(buf->data[pos]), buf->len - pos + 1);
        buf->data[pos] = ch;
    }
    buf->len++;
    return 0;
}


int nn_editbuf_delete(nn_editbuf_t *buf, ssize_t pos)
{
    if (pos < 0)
        return -1;
    else if (pos < buf->len) {
        memmove(&(buf->data[pos]), &(buf->data[pos+1]), buf->len - pos);
        buf->len--;
        return 0;
    } else
        return -2;
}


void nn_editbuf_clear(nn_editbuf_t *buf)
{
    buf->len = 0;
    buf->pos = 0;
}


nn_editbuf_t * nn_editbuf_new(ssize_t n)
{
    nn_editbuf_t *res = th_calloc(1, sizeof(nn_editbuf_t));
    
    res->data = (char *) th_malloc(n);
    res->size = n;
    
    return res;
}


void nn_editbuf_free(nn_editbuf_t *buf)
{
    if (buf != NULL) {
        th_free(buf->data);
        th_free(buf);
    }
}


nn_editbuf_t * nn_editbuf_copy(nn_editbuf_t *src)
{
    nn_editbuf_t *res;
    
    assert(src != NULL);
    
    if (src == NULL) return NULL;
    
    if ((res = nn_editbuf_new(src->size)) == NULL)
        return NULL;
    
    memcpy(res->data, src->data, src->size);
    res->pos = res->len = src->len;
    
    return res;
}


char * nn_editbuf_get_string(nn_editbuf_t *buf, ssize_t start, ssize_t end)
{
    char *str;
    ssize_t siz;
    
    if (buf == NULL)
        return NULL;

    if (start < 0 || end > buf->len || start >= buf->len)
        return NULL;

    if (end < 0) {
        siz = buf->len - start + 1;
    } else if (start <= end) {
        siz = end - start + 1;
    } else
        return NULL;

    if ((str = th_malloc(siz + 1)) == NULL)
        return NULL;

    memcpy(str, buf->data + start, siz);
    str[siz] = 0;

    return str;
}


void nn_editbuf_setpos(nn_editbuf_t *buf, ssize_t pos)
{
    assert(buf != NULL);

    if (pos < 0)
        buf->pos = 0;
    else if (pos >= buf->len)
        buf->pos = buf->len;
    else
        buf->pos = pos;
}


static uint8_t nn_hash_user(const char *name)
{
/*
    int n = 0;
    const uint8_t *c = (uint8_t *)name;
    uint8_t hash = 0xff;
    
    while (*c && n < 4) {
        hash = (hash << 1) ^ tolower(*c);
        c++; n++;
    }
    
    return (hash & 0xff);
*/
    return tolower(name[0]);
}


static void nn_user_insert(nn_user_t **list, nn_user_t *node)
{
    node->next = *list;
    *list = node;
}


nn_user_t *nn_userhash_foreach(const nn_userhash_t *list, int (*func)(const nn_user_t *))
{
    int i;

    if (list == NULL) return NULL;
    
    for (i = 0; i < NN_NUM_BUCKETS; i++)
    if (list->buckets[i] != NULL) {
        nn_user_t *curr = list->buckets[i];
        while (curr != NULL) {
            if (func(curr) != 0)
                return curr;
            curr = curr->next;
        }
    }
    
    return NULL;
}


nn_user_t *nn_user_find(const nn_userhash_t *list, const char *name)
{
    uint8_t hash;
    
    if (list == NULL) return NULL;
    
    hash = nn_hash_user(name);
    if (list->buckets[hash] != NULL) {
        nn_user_t *curr = list->buckets[hash];
        while (curr != NULL) {
            if (strcasecmp(curr->name, name) == 0)
                return curr;
            curr = curr->next;
        }
    }
    
    return NULL;
}


static nn_user_t *nn_user_match_do(nn_user_t *list, const char *pattern, size_t len)
{
    nn_user_t *curr = list;
    while (curr != NULL) {
        if (len <= strlen(curr->name) && strncasecmp(curr->name, pattern, len) == 0)
            return curr;
        curr = curr->next;
    }
    return NULL;
}


nn_user_t *nn_user_match(const nn_userhash_t *list, const char *pattern, const char *current, BOOL again)
{
    uint8_t hash;
    
    if (list == NULL || pattern == NULL) return NULL;

    hash = nn_hash_user(pattern);
    if (list->buckets[hash] != NULL) {
        nn_user_t *curr = list->buckets[hash];
        size_t len = strlen(pattern);

        if (current != NULL) {
            nn_user_t *found = NULL;
            while (curr != NULL) {
                if (strcasecmp(curr->name, current) == 0) {
                    if (again)
                        return curr;
                    found = curr->next;
                    break;
                }
                curr = curr->next;
            }
            
            if (found != NULL && (found = nn_user_match_do(found, pattern, len)) != NULL)
                return found;
        }

        if ((curr = nn_user_match_do(list->buckets[hash], pattern, len)) != NULL)
            return curr;
    }
    
    return NULL;
}


nn_userhash_t *nn_userhash_new(void)
{
    return th_calloc(1, sizeof(nn_userhash_t));
}


int nn_userhash_insert(nn_userhash_t *list, const char *name)
{
    uint8_t hash;
    nn_user_t *user;
    
    /* Check arguments */
    if (list == NULL || name == NULL)
        return -1;
    
    /* Check if username is already there */
    if (nn_user_find(list, name) != NULL)
        return 1;
    
    /* No, we'll add it */
    if ((user = th_calloc(1, sizeof(nn_user_t))) == NULL)
        return -3;
    
    user->name = th_strdup(name);
    if (user->name == NULL)
        return -4;
    
    hash = nn_hash_user(name);
    nn_user_insert(&(list->buckets[hash]), user);

    return 0;
}


int nn_userhash_delete(nn_userhash_t *list, const char *name)
{
    uint8_t hash;
    
    /* Check arguments */
    if (list == NULL || name == NULL)
        return -1;
    
    /* Check if username is already there */
    hash = nn_hash_user(name);
    if (list->buckets[hash] != NULL) {
        nn_user_t *curr, *prev;
        curr = list->buckets[hash];
        prev = NULL;
        while (curr != NULL) {
            if (strcasecmp(curr->name, name) == 0) {
                if (prev)
                    prev->next = curr->next;
                else
                    list->buckets[hash] = curr->next;
                
                nn_user_free(curr);
                
                return 0;
            } else {
                prev = curr;
                curr = curr->next;
            }
        }
    }

    return 1;
}


nn_user_t *nn_user_copy(const nn_user_t *src)
{
    nn_user_t *user;
    
    if (src == NULL) return NULL;
    
    if ((user = th_calloc(1, sizeof(nn_user_t))) == NULL)
        return NULL;
    
    /* Copy relevant data */
    user->name = th_strdup(src->name);
    user->lastspoke = src->lastspoke;
    user->joined = src->joined;
    
    return user;
}


void nn_user_free(nn_user_t *user)
{
   th_free(user->name);
   th_free(user);
}


void nn_user_free_list(nn_user_t *list)
{
    nn_user_t *next, *curr = list;
    while (curr != NULL) {
        next = curr->next;
        nn_user_free(curr);
        curr = next;
    }
}

void nn_userhash_free(nn_userhash_t *hash)
{
    int i;
    if (hash == NULL)
        return;
    
    for (i = 0; i < NN_NUM_BUCKETS; i++) {
        nn_user_free_list(hash->buckets[i]);
        hash->buckets[i] = NULL;
    }
    
    th_free(hash);
}


char *nn_username_encode(char *str)
{
    unsigned char *c = (unsigned char *) str;
    if (str == NULL) return NULL;
    for (; *c ; c++) 
        if (*c == ' ') *c = 255;
    return str;
}


char *nn_username_decode(char *str)
{
    unsigned char *c = (unsigned char *) str;
    if (str == NULL) return NULL;
    for (; *c ; c++) 
        if (*c == 255) *c = ' ';
    return str;
}