view libnnchat.c @ 312:a0c001672b44

Cosmetics.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 11 Jun 2011 06:00:31 +0300
parents 61884ce9db41
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;
}