Mercurial > pidgin
diff libpurple/protocols/novell/nmconn.c @ 15373:5fe8042783c1
Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sat, 20 Jan 2007 02:32:10 +0000 |
parents | |
children | 44b4e8bd759b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/novell/nmconn.c Sat Jan 20 02:32:10 2007 +0000 @@ -0,0 +1,701 @@ +/* + * nmconn.c + * + * Copyright (c) 2004 Novell, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <glib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <time.h> +#include "nmconn.h" + +#ifdef _WIN32 +#include <windows.h> +#endif + +#define NO_ESCAPE(ch) ((ch == 0x20) || (ch >= 0x30 && ch <= 0x39) || \ + (ch >= 0x41 && ch <= 0x5a) || (ch >= 0x61 && ch <= 0x7a)) + +/* Read data from conn until the end of a line */ +static NMERR_T +read_line(NMConn * conn, char *buff, int len) +{ + NMERR_T rc = NM_OK; + int total_bytes = 0; + + while ((rc == NM_OK) && (total_bytes < (len - 1))) { + rc = nm_read_all(conn, &buff[total_bytes], 1); + if (rc == NM_OK) { + total_bytes += 1; + if (buff[total_bytes - 1] == '\n') { + break; + } + } + } + buff[total_bytes] = '\0'; + + return rc; +} + +static char * +url_escape_string(char *src) +{ + guint32 escape = 0; + char *p; + char *q; + char *encoded = NULL; + int ch; + + static const char hex_table[16] = "0123456789abcdef"; + + if (src == NULL) { + return NULL; + } + + /* Find number of chars to escape */ + for (p = src; *p != '\0'; p++) { + ch = (guchar) *p; + if (!NO_ESCAPE(ch)) { + escape++; + } + } + + encoded = g_malloc((p - src) + (escape * 2) + 1); + + /* Escape the string */ + for (p = src, q = encoded; *p != '\0'; p++) { + ch = (guchar) * p; + if (NO_ESCAPE(ch)) { + if (ch != 0x20) { + *q = ch; + q++; + } else { + *q = '+'; + q++; + } + } else { + *q = '%'; + q++; + + *q = hex_table[ch >> 4]; + q++; + + *q = hex_table[ch & 15]; + q++; + } + } + *q = '\0'; + + return encoded; +} + +static char * +encode_method(guint8 method) +{ + char *str; + + switch (method) { + case NMFIELD_METHOD_EQUAL: + str = "G"; + break; + case NMFIELD_METHOD_UPDATE: + str = "F"; + break; + case NMFIELD_METHOD_GTE: + str = "E"; + break; + case NMFIELD_METHOD_LTE: + str = "D"; + break; + case NMFIELD_METHOD_NE: + str = "C"; + break; + case NMFIELD_METHOD_EXIST: + str = "B"; + break; + case NMFIELD_METHOD_NOTEXIST: + str = "A"; + break; + case NMFIELD_METHOD_SEARCH: + str = "9"; + break; + case NMFIELD_METHOD_MATCHBEGIN: + str = "8"; + break; + case NMFIELD_METHOD_MATCHEND: + str = "7"; + break; + case NMFIELD_METHOD_NOT_ARRAY: + str = "6"; + break; + case NMFIELD_METHOD_OR_ARRAY: + str = "5"; + break; + case NMFIELD_METHOD_AND_ARRAY: + str = "4"; + break; + case NMFIELD_METHOD_DELETE_ALL: + str = "3"; + break; + case NMFIELD_METHOD_DELETE: + str = "2"; + break; + case NMFIELD_METHOD_ADD: + str = "1"; + break; + default: /* NMFIELD_METHOD_VALID */ + str = "0"; + break; + } + + return str; +} + +NMConn * +nm_create_conn(const char *addr, int port) +{ + NMConn *conn = g_new0(NMConn, 1); + conn->addr = g_strdup(addr); + conn->port = port; + return conn; +} + +void nm_release_conn(NMConn *conn) +{ + if (conn) { + GSList *node; + for (node = conn->requests; node; node = node->next) { + if (node->data) + nm_release_request(node->data); + } + g_slist_free(conn->requests); + conn->requests = NULL; + if (conn->ssl_conn) { + g_free(conn->ssl_conn); + conn->ssl_conn = NULL; + } + g_free(conn->addr); + conn->addr = NULL; + g_free(conn); + } +} + +int +nm_tcp_write(NMConn * conn, const void *buff, int len) +{ + if (conn == NULL || buff == NULL) + return -1; + + if (!conn->use_ssl) + return (write(conn->fd, buff, len)); + else if (conn->ssl_conn && conn->ssl_conn->write) + return (conn->ssl_conn->write(conn->ssl_conn->data, buff, len)); + else + return -1; +} + +int +nm_tcp_read(NMConn * conn, void *buff, int len) +{ + if (conn == NULL || buff == NULL) + return -1; + + if (!conn->use_ssl) + return (read(conn->fd, buff, len)); + else if (conn->ssl_conn && conn->ssl_conn->read) + return ((conn->ssl_conn->read)(conn->ssl_conn->data, buff, len)); + else + return -1; +} + +NMERR_T +nm_read_all(NMConn * conn, char *buff, int len) +{ + NMERR_T rc = NM_OK; + int bytes_left = len; + int bytes_read; + int total_bytes = 0; + int retry = 1000; + + if (conn == NULL || buff == NULL) + return NMERR_BAD_PARM; + + /* Keep reading until buffer is full */ + while (bytes_left) { + bytes_read = nm_tcp_read(conn, &buff[total_bytes], bytes_left); + if (bytes_read > 0) { + bytes_left -= bytes_read; + total_bytes += bytes_read; + } else { + if (errno == EAGAIN) { + if (--retry == 0) { + rc = NMERR_TCP_READ; + break; + } +#ifdef _WIN32 + Sleep(1); +#else + usleep(1000); +#endif + } else { + rc = NMERR_TCP_READ; + break; + } + } + } + return rc; +} + +NMERR_T +nm_read_uint32(NMConn *conn, guint32 *val) +{ + NMERR_T rc = NM_OK; + + rc = nm_read_all(conn, (char *)val, sizeof(*val)); + if (rc == NM_OK) { + *val = GUINT32_FROM_LE(*val); + } + + return rc; +} + +NMERR_T +nm_read_uint16(NMConn *conn, guint16 *val) +{ + NMERR_T rc = NM_OK; + + rc = nm_read_all(conn, (char *)val, sizeof(*val)); + if (rc == NM_OK) { + *val = GUINT16_FROM_LE(*val); + } + + return rc; +} + +NMERR_T +nm_write_fields(NMConn * conn, NMField * fields) +{ + NMERR_T rc = NM_OK; + NMField *field; + char *value = NULL; + char *method = NULL; + char buffer[4096]; + int ret; + int bytes_to_send; + int val = 0; + + if (conn == NULL || fields == NULL) { + return NMERR_BAD_PARM; + } + + /* Format each field as valid "post" data and write it out */ + for (field = fields; (rc == NM_OK) && (field->tag); field++) { + + /* We don't currently handle binary types */ + if (field->method == NMFIELD_METHOD_IGNORE || + field->type == NMFIELD_TYPE_BINARY) { + continue; + } + + /* Write the field tag */ + bytes_to_send = g_snprintf(buffer, sizeof(buffer), "&tag=%s", field->tag); + ret = nm_tcp_write(conn, buffer, bytes_to_send); + if (ret < 0) { + rc = NMERR_TCP_WRITE; + } + + /* Write the field method */ + if (rc == NM_OK) { + method = encode_method(field->method); + bytes_to_send = g_snprintf(buffer, sizeof(buffer), "&cmd=%s", method); + ret = nm_tcp_write(conn, buffer, bytes_to_send); + if (ret < 0) { + rc = NMERR_TCP_WRITE; + } + } + + /* Write the field value */ + if (rc == NM_OK) { + switch (field->type) { + case NMFIELD_TYPE_UTF8: + case NMFIELD_TYPE_DN: + + value = url_escape_string((char *) field->ptr_value); + bytes_to_send = g_snprintf(buffer, sizeof(buffer), + "&val=%s", value); + if (bytes_to_send > (int)sizeof(buffer)) { + ret = nm_tcp_write(conn, buffer, sizeof(buffer)); + } else { + ret = nm_tcp_write(conn, buffer, bytes_to_send); + } + + if (ret < 0) { + rc = NMERR_TCP_WRITE; + } + + g_free(value); + + break; + + case NMFIELD_TYPE_ARRAY: + case NMFIELD_TYPE_MV: + + val = nm_count_fields((NMField *) field->ptr_value); + bytes_to_send = g_snprintf(buffer, sizeof(buffer), + "&val=%u", val); + ret = nm_tcp_write(conn, buffer, bytes_to_send); + if (ret < 0) { + rc = NMERR_TCP_WRITE; + } + + break; + + default: + + bytes_to_send = g_snprintf(buffer, sizeof(buffer), + "&val=%u", field->value); + ret = nm_tcp_write(conn, buffer, bytes_to_send); + if (ret < 0) { + rc = NMERR_TCP_WRITE; + } + + break; + } + } + + /* Write the field type */ + if (rc == NM_OK) { + bytes_to_send = g_snprintf(buffer, sizeof(buffer), + "&type=%u", field->type); + ret = nm_tcp_write(conn, buffer, bytes_to_send); + if (ret < 0) { + rc = NMERR_TCP_WRITE; + } + } + + /* If the field is a sub array then post its fields */ + if (rc == NM_OK && val > 0) { + if (field->type == NMFIELD_TYPE_ARRAY || + field->type == NMFIELD_TYPE_MV) { + + rc = nm_write_fields(conn, (NMField *) field->ptr_value); + + } + } + } + + return rc; +} + +NMERR_T +nm_send_request(NMConn *conn, char *cmd, NMField *fields, + nm_response_cb cb, gpointer data, NMRequest **request) +{ + NMERR_T rc = NM_OK; + char buffer[512]; + int bytes_to_send; + int ret; + NMField *request_fields = NULL; + char *str = NULL; + + if (conn == NULL || cmd == NULL) + return NMERR_BAD_PARM; + + /* Write the post */ + bytes_to_send = g_snprintf(buffer, sizeof(buffer), + "POST /%s HTTP/1.0\r\n", cmd); + ret = nm_tcp_write(conn, buffer, bytes_to_send); + if (ret < 0) { + rc = NMERR_TCP_WRITE; + } + + /* Write headers */ + if (rc == NM_OK) { + if (strcmp("login", cmd) == 0) { + bytes_to_send = g_snprintf(buffer, sizeof(buffer), + "Host: %s:%d\r\n\r\n", conn->addr, conn->port); + ret = nm_tcp_write(conn, buffer, bytes_to_send); + if (ret < 0) { + rc = NMERR_TCP_WRITE; + } + } else { + bytes_to_send = g_snprintf(buffer, sizeof(buffer), "\r\n"); + ret = nm_tcp_write(conn, buffer, bytes_to_send); + if (ret < 0) { + rc = NMERR_TCP_WRITE; + } + } + } + + /* Add the transaction id to the request fields */ + if (rc == NM_OK) { + if (fields) + request_fields = nm_copy_field_array(fields); + + str = g_strdup_printf("%d", ++(conn->trans_id)); + request_fields = nm_field_add_pointer(request_fields, NM_A_SZ_TRANSACTION_ID, 0, + NMFIELD_METHOD_VALID, 0, + str, NMFIELD_TYPE_UTF8); + } + + /* Send the request to the server */ + if (rc == NM_OK) { + rc = nm_write_fields(conn, request_fields); + } + + /* Write the CRLF to terminate the data */ + if (rc == NM_OK) { + ret = nm_tcp_write(conn, "\r\n", strlen("\r\n")); + if (ret < 0) { + rc = NMERR_TCP_WRITE; + } + } + + /* Create a request struct, add it to our queue, and return it */ + if (rc == NM_OK) { + NMRequest *new_request = nm_create_request(cmd, conn->trans_id, + time(0), cb, NULL, data); + nm_conn_add_request_item(conn, new_request); + + /* Set the out param if it was sent in, otherwise release the request */ + if (request) + *request = new_request; + else + nm_release_request(new_request); + } + + if (request_fields != NULL) + nm_free_fields(&request_fields); + + return rc; +} + +NMERR_T +nm_read_header(NMConn * conn) +{ + NMERR_T rc = NM_OK; + char buffer[512]; + char *ptr = NULL; + int i; + char rtn_buf[8]; + int rtn_code = 0; + + if (conn == NULL) + return NMERR_BAD_PARM; + + *buffer = '\0'; + rc = read_line(conn, buffer, sizeof(buffer)); + if (rc == NM_OK) { + + /* Find the return code */ + ptr = strchr(buffer, ' '); + if (ptr != NULL) { + ptr++; + + i = 0; + while (isdigit(*ptr) && (i < 3)) { + rtn_buf[i] = *ptr; + i++; + ptr++; + } + rtn_buf[i] = '\0'; + + if (i > 0) + rtn_code = atoi(rtn_buf); + } + } + + /* Finish reading header, in the future we might want to do more processing here */ + /* TODO: handle more general redirects in the future */ + while ((rc == NM_OK) && (strcmp(buffer, "\r\n") != 0)) { + rc = read_line(conn, buffer, sizeof(buffer)); + } + + if (rc == NM_OK && rtn_code == 301) + rc = NMERR_SERVER_REDIRECT; + + return rc; +} + +NMERR_T +nm_read_fields(NMConn * conn, int count, NMField ** fields) +{ + NMERR_T rc = NM_OK; + guint8 type; + guint8 method; + guint32 val; + char tag[64]; + NMField *sub_fields = NULL; + char *str = NULL; + + if (conn == NULL || fields == NULL) + return NMERR_BAD_PARM; + + do { + if (count > 0) { + count--; + } + + /* Read the field type, method, and tag */ + rc = nm_read_all(conn, (char *)&type, sizeof(type)); + if (rc != NM_OK || type == 0) + break; + + rc = nm_read_all(conn, (char *)&method, sizeof(method)); + if (rc != NM_OK) + break; + + rc = nm_read_uint32(conn, &val); + if (rc != NM_OK) + break; + + if (val > sizeof(tag)) { + rc = NMERR_PROTOCOL; + break; + } + + rc = nm_read_all(conn, tag, val); + if (rc != NM_OK) + break; + + if (type == NMFIELD_TYPE_MV || type == NMFIELD_TYPE_ARRAY) { + + /* Read the subarray (first read the number of items in the array) */ + rc = nm_read_uint32(conn, &val); + if (rc != NM_OK) + break; + + if (val > 0) { + rc = nm_read_fields(conn, val, &sub_fields); + if (rc != NM_OK) + break; + } + + *fields = nm_field_add_pointer(*fields, tag, 0, method, + 0, sub_fields, type); + + sub_fields = NULL; + + } else if (type == NMFIELD_TYPE_UTF8 || type == NMFIELD_TYPE_DN) { + + /* Read the string (first read the length) */ + rc = nm_read_uint32(conn, &val); + if (rc != NM_OK) + break; + + if (val >= NMFIELD_MAX_STR_LENGTH) { + rc = NMERR_PROTOCOL; + break; + } + + if (val > 0) { + str = g_new0(char, val + 1); + + rc = nm_read_all(conn, str, val); + if (rc != NM_OK) + break; + + *fields = nm_field_add_pointer(*fields, tag, 0, method, + 0, str, type); + str = NULL; + } + + } else { + + /* Read the numerical value */ + rc = nm_read_uint32(conn, &val); + if (rc != NM_OK) + break; + + *fields = nm_field_add_number(*fields, tag, 0, method, + 0, val, type); + } + + } while ((type != 0) && (count != 0)); + + + if (str != NULL) { + g_free(str); + } + + if (sub_fields != NULL) { + nm_free_fields(&sub_fields); + } + + return rc; +} + +void +nm_conn_add_request_item(NMConn * conn, NMRequest * request) +{ + if (conn == NULL || request == NULL) + return; + + nm_request_add_ref(request); + conn->requests = g_slist_append(conn->requests, request); +} + +void +nm_conn_remove_request_item(NMConn * conn, NMRequest * request) +{ + if (conn == NULL || request == NULL) + return; + + conn->requests = g_slist_remove(conn->requests, request); + nm_release_request(request); +} + +NMRequest * +nm_conn_find_request(NMConn * conn, int trans_id) +{ + NMRequest *req = NULL; + GSList *itr = NULL; + + if (conn == NULL) + return NULL; + + itr = conn->requests; + while (itr) { + req = (NMRequest *) itr->data; + if (req != NULL && nm_request_get_trans_id(req) == trans_id) { + return req; + } + itr = g_slist_next(itr); + } + return NULL; +} + +const char * +nm_conn_get_addr(NMConn * conn) +{ + if (conn == NULL) + return NULL; + else + return conn->addr; +} + +int +nm_conn_get_port(NMConn * conn) +{ + if (conn == NULL) + return -1; + else + return conn->port; +}