# HG changeset patch # User Rob Flynn # Date 1041052543 0 # Node ID 1bd663beada50f771a9ff1c7a99b3c1732ceba53 # Parent baf9d94e012898148621af9313a252d9fcaef254 [gaim-migrate @ 4369] potato chip file receive in msn committer: Tailor Script diff -r baf9d94e0128 -r 1bd663beada5 ChangeLog --- a/ChangeLog Sat Dec 28 05:12:03 2002 +0000 +++ b/ChangeLog Sat Dec 28 05:15:43 2002 +0000 @@ -106,6 +106,7 @@ (Thanks, Graham Booker) * Fixed crashbug on empty rvous requests, thanks Brandon Scott (Xeon) and Matt Pandina for pointing this out (Brandon) and for the patch (Matt) + * File receive support for MSN (Thanks, Christian Hammond) version 0.59 (06/24/2002): * Hungarian translation added (Thanks, Sutto Zoltan) diff -r baf9d94e0128 -r 1bd663beada5 src/ft.c --- a/src/ft.c Sat Dec 28 05:12:03 2002 +0000 +++ b/src/ft.c Sat Dec 28 05:15:43 2002 +0000 @@ -571,35 +571,50 @@ { struct file_transfer *xfer = (struct file_transfer *)data; int rt, i; - char buf[FT_BUFFER_SIZE]; + char *buf = NULL; if (condition & GAIM_INPUT_READ) { - rt = read(xfer->fd, buf, MIN(xfer->bytesleft, FT_BUFFER_SIZE)); + if (xfer->gc->prpl->file_transfer_read) + rt = xfer->gc->prpl->file_transfer_read(xfer->gc, xfer, + xfer->fd, &buf); + else { + buf = g_new0(char, MIN(xfer->bytesleft, FT_BUFFER_SIZE)); + rt = read(xfer->fd, buf, MIN(xfer->bytesleft, FT_BUFFER_SIZE)); + } + /* XXX What if the transfer is interrupted while we * are inside read()? How can this be handled safely? * -- wtm */ if (rt > 0) { xfer->bytesleft -= rt; - for (i = 0; i < rt; i++) { - fprintf(xfer->file, "%c", buf[i]); - } + fwrite(buf, 1, rt, xfer->file); } } else /* (condition & GAIM_INPUT_WRITE) */ { int remain = MIN(xfer->bytesleft, FT_BUFFER_SIZE); - for (i = 0; i < remain; i++) - fscanf(xfer->file, "%c", &buf[i]); + buf = g_new0(char, remain); + + fread(buf, 1, remain, xfer->file); - rt = write(xfer->fd, buf, remain); + if (xfer->gc->prpl->file_transfer_write) + rt = xfer->gc->prpl->file_transfer_write(xfer->gc, xfer, xfer->fd, + buf, remain); + else + rt = write(xfer->fd, buf, remain); + if (rt > 0) xfer->bytesleft -= rt; } - if (rt < 0) + if (rt < 0) { + if (buf != NULL) + g_free(buf); + return; + } xfer->bytessent += rt; @@ -614,6 +629,9 @@ xfer->file = 0; ft_nextfile(xfer); } + + if (buf != NULL) + g_free(buf); } static void ft_nextfile(struct file_transfer *xfer) diff -r baf9d94e0128 -r 1bd663beada5 src/protocols/msn/msn.c --- a/src/protocols/msn/msn.c Sat Dec 28 05:12:03 2002 +0000 +++ b/src/protocols/msn/msn.c Sat Dec 28 05:15:43 2002 +0000 @@ -8,6 +8,7 @@ #endif +#include #include #include #include @@ -58,7 +59,31 @@ #define MSN_TYPING_RECV_TIMEOUT 6 #define MSN_TYPING_SEND_TIMEOUT 4 - +struct msn_file_transfer { + enum { MFT_SENDFILE_IN, MFT_SENDFILE_OUT } type; + struct file_transfer *xfer; + struct gaim_connection *gc; + + int fd; + int inpa; + + char *filename; + + char *sn; + char ip[16]; + int port; + + unsigned long cookie; + unsigned long authcookie; + + int len; + + char *rxqueue; + int rxlen; + gboolean msg; + char *msguser; + int msglen; +}; struct msn_data { int fd; @@ -75,6 +100,7 @@ GSList *fl; GSList *permit; GSList *deny; + GSList *file_transfers; char *kv; char *sid; @@ -111,6 +137,10 @@ static void msn_login_callback(gpointer, gint, GaimInputCondition); static void msn_login_xfr_connect(gpointer, gint, GaimInputCondition); +static struct msn_file_transfer *find_mft_by_cookie(struct gaim_connection *gc, + unsigned long cookie); +static struct msn_file_transfer *find_mft_by_xfer(struct gaim_connection *gc, + struct file_transfer *xfer); #define GET_NEXT(tmp) while (*(tmp) && *(tmp) != ' ') \ (tmp)++; \ @@ -597,8 +627,243 @@ g_string_free(ret, TRUE); return cur; } - - + +static int msn_process_msnftp(struct msn_file_transfer *mft, char *buf) +{ + struct gaim_connection *gc = mft->gc; + char sendbuf[MSN_BUF_LEN]; + + if (!g_strncasecmp(buf, "VER MSNFTP", 10)) { + + /* Send the USR string. */ + g_snprintf(sendbuf, sizeof(sendbuf), "USR %s %ld\r\n", + gc->username, mft->authcookie); + + if (msn_write(mft->fd, sendbuf, strlen(sendbuf)) < 0) { + /* TODO: Clean up */ + return 0; + } + } + else if (!g_strncasecmp(buf, "FIL", 3)) { + + char *tmp = buf; + + GET_NEXT(tmp); + + mft->len = atoi(tmp); + + /* Send the TFR string, to request a start of transfer. */ + g_snprintf(sendbuf, sizeof(sendbuf), "TFR\r\n"); + + gaim_input_remove(mft->inpa); + mft->inpa = 0; + + if (msn_write(mft->fd, sendbuf, strlen(sendbuf)) < 0) { + /* TODO: Clean up */ + return 0; + } + + if (transfer_in_do(mft->xfer, mft->fd, mft->filename, mft->len)) { + debug_printf("MSN: transfer_in_do failed\n"); + } + } + + return 1; +} + +static void msn_msnftp_callback(gpointer data, gint source, + GaimInputCondition cond) +{ + struct msn_file_transfer *mft = (struct msn_file_transfer *)data; + char buf[MSN_BUF_LEN]; + int cont = 1; + int len; + + if (mft->fd != source) + mft->fd = source; + + len = read(mft->fd, buf, sizeof(buf)); + + if (len <= 0) { + /* TODO: Kill mft. */ + return; + } + + mft->rxqueue = g_realloc(mft->rxqueue, len + mft->rxlen); + memcpy(mft->rxqueue + mft->rxlen, buf, len); + mft->rxlen += len; + + while (cont) { + char *end = mft->rxqueue; + int cmdlen; + char *cmd; + int i = 0; + + if (!mft->rxlen) + return; + + while (i + 1 < mft->rxlen) { + if (*end == '\r' && end[1] == '\n') + break; + end++; i++; + } + if (i + 1 == mft->rxlen) + return; + + cmdlen = end - mft->rxqueue + 2; + cmd = mft->rxqueue; + mft->rxlen -= cmdlen; + if (mft->rxlen) { + mft->rxqueue = g_memdup(cmd + cmdlen, mft->rxlen); + } else { + mft->rxqueue = NULL; + cmd = g_realloc(cmd, cmdlen + 1); + } + cmd[cmdlen] = 0; + + g_strchomp(cmd); + cont = msn_process_msnftp(mft, cmd); + + g_free(cmd); + } +} + +static void msn_msnftp_connect(gpointer data, gint source, + GaimInputCondition cond) +{ + struct msn_file_transfer *mft = (struct msn_file_transfer *)data; + struct gaim_connection *gc = mft->gc; + char buf[MSN_BUF_LEN]; + + if (source == -1 || !g_slist_find(connections, gc)) { + debug_printf("Error establishing MSNFTP connection\n"); + close(source); + /* TODO: Clean up */ + return; + } + + if (mft->fd != source) + mft->fd = source; + + g_snprintf(buf, sizeof(buf), "VER MSNFTP\r\n"); + + if (msn_write(mft->fd, buf, strlen(buf)) < 0) { + /* TODO: Clean up */ + return; + } + + mft->inpa = gaim_input_add(mft->fd, GAIM_INPUT_READ, + msn_msnftp_callback, mft); +} + +static void msn_process_ft_msg(struct msn_switchboard *ms, char *msg) +{ + struct msn_file_transfer *mft; + struct msn_data *md = ms->gc->proto_data; + char *tmp = msg; + + if (strstr(msg, "Application-Name: File Transfer") && + strstr(msg, "Invitation-Command: INVITE")) { + + /* + * First invitation message, requesting an ACCEPT or CANCEL from + * the recipient. Used in incoming file transfers. + */ + + char *filename; + char *cookie_s, *filesize_s; + size_t filesize; + + tmp = strstr(msg, "Invitation-Cookie"); + GET_NEXT(tmp); + cookie_s = tmp; + GET_NEXT(tmp); + GET_NEXT(tmp); + filename = tmp; + + /* Needed for filenames with spaces */ + tmp = strchr(tmp, '\r'); + *tmp = '\0'; + tmp += 2; + + GET_NEXT(tmp); + filesize_s = tmp; + GET_NEXT(tmp); + + mft = g_new0(struct msn_file_transfer, 1); + mft->gc = ms->gc; + mft->type = MFT_SENDFILE_IN; + mft->sn = g_strdup(ms->msguser); + mft->cookie = atoi(cookie_s); + mft->filename = g_strdup(filename); + + filesize = atoi(filesize_s); + + md->file_transfers = g_slist_append(md->file_transfers, mft); + + mft->xfer = transfer_in_add(ms->gc, ms->msguser, + mft->filename, filesize, 1, NULL); + } + else if (strstr(msg, "Invitation-Command: ACCEPT")) { + + /* + * XXX I hope these checks don't return false positives, but they + * seem like they should work. The only issue is alternative + * protocols, *maybe*. + */ + + if (strstr(msg, "AuthCookie:")) { + + /* + * Second invitation request, sent after the recipient accepts + * the request. Used in incoming file transfers. + */ + + char *cookie_s, *ip, *port_s, *authcookie_s; + + tmp = strstr(msg, "Invitation-Cookie"); + GET_NEXT(tmp); + cookie_s = tmp; + GET_NEXT(tmp); + GET_NEXT(tmp); + ip = tmp; + GET_NEXT(tmp); + GET_NEXT(tmp); + port_s = tmp; + GET_NEXT(tmp); + GET_NEXT(tmp); + authcookie_s = tmp; + GET_NEXT(tmp); + + mft = find_mft_by_cookie(ms->gc, atoi(cookie_s)); + + if (!mft) + { + debug_printf("MSN: Cookie not found. File transfer aborted.\n"); + return; + } + + strncpy(mft->ip, ip, 16); + mft->port = atoi(port_s); + mft->authcookie = atoi(authcookie_s); + + mft->fd = proxy_connect(mft->ip, mft->port, msn_msnftp_connect, mft); + + if (ms->fd < 0) { + md->file_transfers = g_slist_remove(md->file_transfers, mft); + return; + } + } + else + { + /* + * An accept message from the recipient. Used in outgoing + * file transfers. + */ + } + } +} + static void msn_process_switch_msg(struct msn_switchboard *ms, char *msg) { char *content, *agent, *format; @@ -628,6 +893,19 @@ return; } + } else if (!g_strncasecmp(content, "Content-Type: text/x-msmsgsinvite;", + strlen("Content-Type: text/x-msmsgsinvite;"))) { + + /* + * NOTE: Other things, such as voice communication, would go in + * here too (since they send the same Content-Type). However, + * this is the best check for file transfer messages, so I'm + * calling msn_process_ft_invite_msg(). If anybody adds support + * for anything else that sends a text/x-msmsgsinvite, perhaps + * this should be changed. For now, it stays. + */ + msn_process_ft_msg(ms, content); + } else if (!g_strncasecmp(content, "Content-Type: text/plain", strlen("Content-Type: text/plain"))) { @@ -1775,6 +2053,206 @@ return MSN_TYPING_SEND_TIMEOUT; } +/* XXX Don't blame me. I stole this from the oscar module! */ +static struct msn_file_transfer *find_mft_by_xfer(struct gaim_connection *gc, + struct file_transfer *xfer) +{ + GSList *g = ((struct msn_data *)gc->proto_data)->file_transfers; + struct msn_file_transfer *f = NULL; + + while (g) { + f = (struct msn_file_transfer *)g->data; + if (f->xfer == xfer) + break; + + g = g->next; + f = NULL; + } + + return f; +} + +/* XXX Don't blame me. I stole this from the oscar module! */ +static struct msn_file_transfer *find_mft_by_cookie(struct gaim_connection *gc, + unsigned long cookie) +{ + GSList *g = ((struct msn_data *)gc->proto_data)->file_transfers; + struct msn_file_transfer *f = NULL; + + while (g) { + f = (struct msn_file_transfer *)g->data; + if (f->cookie == cookie) + break; + + g = g->next; + f = NULL; + } + + return f; +} + +static void msn_file_transfer_cancel(struct gaim_connection *gc, + struct file_transfer *xfer) +{ + struct msn_data *md = gc->proto_data; + struct msn_file_transfer *mft = find_mft_by_xfer(gc, xfer); + struct msn_switchboard *ms = msn_find_switch(gc, mft->sn); + char header[MSN_BUF_LEN]; + char buf[MSN_BUF_LEN]; + + if (!ms || !mft) + { + debug_printf("Eep! Returning from msn_file_transfer_cancel early"); + return; + } + + g_snprintf(header, sizeof(header), + "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n" + "Invitation-Command: CANCEL\r\n" + "Invitation-Cookie: %ld\r\n" + "Cancel-Code: REJECT\r\n", + mft->cookie); + + g_snprintf(buf, sizeof(buf), "MSG %d N %d\r\n%s\r\n\r\n", + ++ms->trId, strlen(header) + strlen("\r\n\r\n"), + header); + + md->file_transfers = g_slist_remove(md->file_transfers, mft); + + if (msn_write(ms->fd, buf, strlen(buf)) < 0) + { + debug_printf("Uh oh! Killing switch.\n"); + msn_kill_switch(ms); + } +} + +static void msn_file_transfer_in(struct gaim_connection *gc, + struct file_transfer *xfer, int offset) +{ + struct msn_data *md = gc->proto_data; + struct msn_file_transfer *mft = find_mft_by_xfer(gc, xfer); + struct msn_switchboard *ms = msn_find_switch(gc, mft->sn); + char header[MSN_BUF_LEN]; + char buf[MSN_BUF_LEN]; + + if (!ms || !mft) + { + debug_printf("Eep! Returning from msn_file_transfer_in early"); + return; + } + + g_snprintf(header, sizeof(header), + "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n" + "Invitation-Command: ACCEPT\r\n" + "Invitation-Cookie: %ld\r\n" + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n", + mft->cookie); + + g_snprintf(buf, sizeof(buf), "MSG %d N %d\r\n%s\r\n\r\n", + ++ms->trId, strlen(header) + strlen("\r\n\r\n"), + header); + + if (msn_write(ms->fd, buf, strlen(buf)) < 0) { + msn_kill_switch(ms); + return; + } + + mft->xfer = xfer; +} + +static void msn_file_transfer_out(struct gaim_connection *gc, + struct file_transfer *xfer, + const char *name, int totfiles, int totsize) +{ + struct msn_file_transfer *mft = find_mft_by_xfer(gc, xfer); + struct msn_switchboard *ms = msn_find_switch(gc, mft->sn); + char header[MSN_BUF_LEN]; + char buf[MSN_BUF_LEN]; + struct stat sb; + + if (!ms) + return; + + if (totfiles > 1) + return; + + if (stat(name, &sb) == -1) + return; + + mft->cookie = 1 + (int)(sizeof(unsigned long) * rand() / (RAND_MAX + 1.0)); + + g_snprintf(header, sizeof(header), + "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" + "Application-Name: File Transfer\r\n" + "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n" + "Invitation-Command: INVITE\r\n" + "Invitation-Cookie: %ld\r\n" + "Application-File: %s\r\n" + "Application-FileSize: %ld\r\n", + mft->cookie, name, sb.st_size); + + g_snprintf(buf, sizeof(buf), "MSG %d A %d\r\n%s\r\n\r\n", + ++ms->trId, + strlen(header) + strlen("\r\n\r\n"), + header); + + if (msn_write(ms->fd, buf, strlen(buf)) < 0) + msn_kill_switch(ms); + + debug_printf("\n"); +} + +static void msn_file_transfer_done(struct gaim_connection *gc, + struct file_transfer *xfer) +{ + struct msn_data *md = (struct msn_data *)gc->proto_data; + struct msn_file_transfer *mft = find_mft_by_xfer(gc, xfer); + struct msn_switchboard *ms = msn_find_switch(gc, mft->sn); + char buf[MSN_BUF_LEN]; + + g_snprintf(buf, sizeof(buf), "BYE 16777989\r\n"); + + msn_write(mft->fd, buf, strlen(buf)); + + md->file_transfers = g_slist_remove(md->file_transfers, mft); + + gaim_input_remove(mft->inpa); + + close(mft->fd); + + g_free(mft->filename); + g_free(mft->sn); + g_free(mft); +} + +static size_t msn_file_transfer_read(struct gaim_connection *gc, + struct file_transfer *xfer, int fd, + char **buf) +{ + unsigned char header[3]; + size_t len, size; + + if (read(fd, header, sizeof(header)) < 3) + return 0; + + if (header[0] != 0) { + debug_printf("Invalid header[0]: %d. Aborting.\n", header[0]); + return 0; + } + + size = header[1] | (header[2] << 8); + + *buf = g_new0(char, size); + + for (len = 0; len < size; len += read(fd, *buf + len, size - len)); + + return len; +} + static int msn_send_im(struct gaim_connection *gc, char *who, char *message, int len, int flags) { struct msn_data *md = gc->proto_data; @@ -1989,6 +2467,20 @@ handle_buddy_rename(b, b->name); } +static void msn_ask_send_file(struct gaim_connection *gc, char *destsn) +{ + struct msn_data *md = (struct msn_data *)gc->proto_data; + struct msn_file_transfer *mft = g_new0(struct msn_file_transfer, 1); + + mft->type = MFT_SENDFILE_OUT; + mft->sn = g_strdup(destsn); + mft->gc = gc; + + md->file_transfers = g_slist_append(md->file_transfers, mft); + + mft->xfer = transfer_out_add(gc, mft->sn); +} + static GList *msn_buddy_menu(struct gaim_connection *gc, char *who) { GList *m = NULL; @@ -2002,6 +2494,12 @@ pbm->gc = gc; m = g_list_append(m, pbm); + pbm = g_new0(struct proto_buddy_menu, 1); + pbm->label = _("Send File"); + pbm->callback = msn_ask_send_file; + pbm->gc = gc; + m = g_list_append(m, pbm); + if (!b || !(b->uc >> 1)) return m; @@ -2397,6 +2895,11 @@ ret->add_deny = msn_add_deny; ret->rem_deny = msn_rem_deny; ret->buddy_free = msn_buddy_free; + ret->file_transfer_cancel = msn_file_transfer_cancel; + ret->file_transfer_in = msn_file_transfer_in; + ret->file_transfer_out = msn_file_transfer_out; + ret->file_transfer_done = msn_file_transfer_done; + ret->file_transfer_read = msn_file_transfer_read; puo = g_new0(struct proto_user_opt, 1); puo->label = g_strdup(_("Server:")); diff -r baf9d94e0128 -r 1bd663beada5 src/prpl.h --- a/src/prpl.h Sat Dec 28 05:12:03 2002 +0000 +++ b/src/prpl.h Sat Dec 28 05:15:43 2002 +0000 @@ -185,6 +185,8 @@ void (* file_transfer_nextfile) (struct gaim_connection *, struct file_transfer *); void (* file_transfer_data_chunk)(struct gaim_connection *, struct file_transfer *, const char *, int); void (* file_transfer_done) (struct gaim_connection *, struct file_transfer *); + size_t (* file_transfer_read) (struct gaim_connection *, struct file_transfer *, int fd, char **buf); + size_t (* file_transfer_write) (struct gaim_connection *, struct file_transfer *, int fd, const char *buf, size_t size); }; extern GSList *protocols;