Obexftp с поддержкой кириллицы

Пару дней назад встала задача передачи файлов по bluetooth в Ubuntu 10.10 с компьютера на различные устройства. Выполнять передачу файлов нужно было только фоном (без GUI), писать свой бинарник было нерентабельно, поэтому выбор пал на obexftp. Протестировав его пару минут, столкнулся с проблемой кодировки — имена файлов, содержащие символы кириллицы, приходили кракозябрами.

Погуглив, нашел патч, исправляющий проблемы с кодировкой с помощью iconv. Протестировав на паре файлов, все было замечательно, пока программа не стала выдавать segfault при начале приема файла или при окончании загрузки файла (только с кириллицей).

К примеру, при передачи файла с именем «Сосны.mp3» происходил segfault программы, хотя если написать «Сосны 1.mp3», то файл передавался успешно.

Пришлось погружаться в дебаггинг…
И спустя пару часов, выяснил причины:
  1. освобождалось переменная, которая имела неверный размер выделенной памяти под имя файла, что и приводило к краху при начале передачи файла
  2. при окончании передачи файла освобождался объект obex, что тоже приводило к краху (в чем была причина, так и не выяснил)
В итоге нашел баг в исходнике libopenobex, связанный с выделением памяти для переменной, содержащей имя файла. В функции OBEX_CharToUnicode в файле obex.c передавалась неправильная длина строки имени файла, вся проблема заключалась в функции strlen() (один кириллический символ в UTF8 она возвращает как два байта), вследствие чего происходит неправильное выделение памяти, длина возвращаемой строки не соответствовала действительности и переменная, содержащая имя файла, некорректно освобождалась, что и приводило к segfault'у.

Чтобы разобраться как перекодировать из UTF-8 в UCS-2BE написал небольшой скрипт на bash'е, который выводит символ и HEX-значение в обеих кодировках:

#!/bin/bash
OUT='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
OUT=${OUT}'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя';
for i in $(seq 0 $(( ${#OUT} - 1)) ); do
 echo ${OUT:$i:1}' '$(echo -n ${OUT:$i:1} | od -x | cut -f2 -d' ' | grep -v 0000002 | grep -v 0000001)' '$(echo -n ${OUT:$i:1} | iconv -f utf-8 -t UCS-2BE | od -x | cut -f2 -d' ' | grep -v 0000002 | grep -v 0000001)
done


После чего сделал новый патч. В функции OBEX_CharToUnicode теперь перераспределяется правильный размер для переменной, в которой храниться имя файла, и кириллические символы преобразуются из UTF-8 в UCS-2BE без использования iconv.

Листинг патча:
diff -Nuar orig/apps/obex_test_cable.c patched/apps/obex_test_cable.c
--- orig/apps/obex_test_cable.c 2009-02-08 21:30:22.000000000 +0300
+++ patched/apps/obex_test_cable.c 2011-02-01 16:52:40.630583001 +0300
@@ -191,34 +191,34 @@
 
 // Set up R320s phone in OBEX mode.
 if(cobex_do_at_cmd(gt, "ATZ\r", rspbuf, sizeof(rspbuf), 1) < 0) {
- printf("Comm-error sending ATZ\n");
+ fprintf(stderr, "Comm-error sending ATZ\n");
 goto err;
 }
 
 #ifdef TTBT
 /* Special BT-mode */
 if(cobex_do_at_cmd(gt, TTBT, rspbuf, sizeof(rspbuf), 10) < 0) {
- printf("Comm-error sending AT*TTBT\n");
+ fprintf(stderr, "Comm-error sending AT*TTBT\n");
 goto err;
 }
 
 if(strcasecmp("OK", rspbuf) != 0) {
- printf("Error doing AT*TTBT (%s)\n", rspbuf);
+ fprintf(stderr, "Error doing AT*TTBT (%s)\n", rspbuf);
 goto err;
 }
 #endif
 
 if(strcasecmp("OK", rspbuf) != 0) {
- printf("Error doing ATZ (%s)\n", rspbuf);
+ fprintf(stderr, "Error doing ATZ (%s)\n", rspbuf);
 goto err;
 }
 
 if(cobex_do_at_cmd(gt, "AT*EOBEX\r", rspbuf, sizeof(rspbuf), 1) < 0) {
- printf("Comm-error sending AT*EOBEX\n");
+ fprintf(stderr, "Comm-error sending AT*EOBEX\n");
 goto err;
 }
 if(strcasecmp("CONNECT", rspbuf) != 0) {
- printf("Error doing AT*EOBEX (%s)\n", rspbuf);
+ fprintf(stderr, "Error doing AT*EOBEX (%s)\n", rspbuf);
 goto err;
 }
 return 1;
@@ -243,7 +243,7 @@
 #else
 if(tcsendbreak(gt->ttyfd, 0) < 0) {
 #endif /* TCSBRKP */
- printf("Unable to send break!\n");
+ fprintf(stderr, "Unable to send break!\n");
 }
 }
 close(gt->ttyfd);
@@ -315,16 +315,16 @@
 if(gt->r320) {
 CDEBUG("R320!!!\n");
 if(cobex_do_at_cmd(gt, NULL, rspbuf, sizeof(rspbuf), 1) < 0)
- printf("Comm-error waiting for OK after disconnect\n");
+ fprintf(stderr, "Comm-error waiting for OK after disconnect\n");
 else if(strcasecmp(rspbuf, "OK") != 0)
- printf("Excpected OK after OBEX diconnect got %s\n", rspbuf);
+ fprintf(stderr, "Expected OK after OBEX diconnect got %s\n", rspbuf);
 
 #ifdef TTBT
 sleep(2);
 if(cobex_do_at_cmd(gt, "---", rspbuf, sizeof(rspbuf), 5) < 0)
- printf("Comm-error Sending ---\n");
+ fprintf(stderr, "Comm-error Sending ---\n");
 else if(strcasecmp(rspbuf, "DISCONNECT") != 0)
- printf("Error waiting for DISCONNECT (%s)\n", rspbuf);
+ fprintf(stderr, "Error waiting for DISCONNECT (%s)\n", rspbuf);
 sleep(2);
 #endif
 
@@ -366,7 +366,7 @@
 
 /* Return if no fd */
 if(gt->ttyfd < 0) {
- CDEBUG("No fd!");
+ CDEBUG("No fd!\n");
 return -1;
 }
 
@@ -380,8 +380,10 @@
 
 /* Check if this is a timeout (0) or error (-1) */
 if (ret < 1) {
+ if (ret == 0) 
+ CDEBUG("Timeout on select() call\n");
+ else CDEBUG("Error (%d) on select call - (%d) %s\n", ret, errno, strerror(errno)); 
 return ret;
- CDEBUG("Timeout or error (%d)\n", ret);
 }
 actual = read(gt->ttyfd, >->inputbuf, sizeof(gt->inputbuf));
 if(actual <= 0)
@@ -391,13 +393,13 @@
 {
 int i = 0;
 for(i=0;i<actual;i++) {
- printf("[%0X",gt->inputbuf[i]);
+ fprintf(stderr, "[%02X",(unsigned char)(gt->inputbuf[i]));
 if(gt->inputbuf[i] >= 32) {
- printf(",%c",gt->inputbuf[i]);
+ fprintf(stderr, ",%c",gt->inputbuf[i]);
 }
- printf("]");
+ fprintf(stderr, "]");
 }
- printf("\n");
+ fprintf(stderr, "\n");
 }
 #endif
 OBEX_CustomDataFeed(handle, (uint8_t *) gt->inputbuf, actual);
diff -Nuar orig/apps/obex_test_cable.h patched/apps/obex_test_cable.h
--- orig/apps/obex_test_cable.h 2009-02-08 21:30:22.000000000 +0300
+++ patched/apps/obex_test_cable.h 2011-02-01 16:53:25.930583001 +0300
@@ -34,7 +34,7 @@
 static void CDEBUG(char *format, ...) {}
 
 #elif defined(CABLE_DEBUG)
-#define CDEBUG(format, ...) printf("%s(): " format, __FUNCTION__ , ## __VA_ARGS__)
+#define CDEBUG(format, args...) fprintf(stderr, "%s(): " format, __FUNCTION__ , ##args)
 
 #else
 #define CDEBUG(format, ...)
diff -Nuar orig/apps/obex_test_client.c patched/apps/obex_test_client.c
--- orig/apps/obex_test_client.c 2009-02-08 21:30:22.000000000 +0300
+++ patched/apps/obex_test_client.c 2011-02-01 16:55:03.126583000 +0300
@@ -122,6 +122,7 @@
 {
 obex_object_t *object;
 obex_headerdata_t hd;
+ static unsigned char fsBrowser[]={ 0xf9, 0xec, 0x7b, 0xc4, 0x95, 0x3c, 0x11, 0xd2, 0x98, 0x4e, 0x52, 0x54, 0x00, 0xdc, 0x9e, 0x09, 0x00};
 
 if(! (object = OBEX_ObjectNew(handle, OBEX_CMD_CONNECT))) {
 printf("Error\n");
@@ -135,6 +136,15 @@
 OBEX_ObjectDelete(handle, object);
 return;
 }
+
+ hd.bs = (uint8_t *)fsBrowser;
+ if(OBEX_ObjectAddHeader(handle, object, OBEX_HDR_TARGET, hd, 16,
+ OBEX_FL_FIT_ONE_PACKET) < 0) {
+ printf("Error adding header 2\n");
+ OBEX_ObjectDelete(handle, object);
+ return;
+ }
+
 OBEX_Request(handle, object);
 syncwait(handle);
 }
@@ -412,7 +422,7 @@
 //
 void setpath_client(obex_t *handle)
 {
- uint8_t setpath_data[2] = { 0, 0 };
+ uint8_t setpath_data[2] = { 2, 0 };
 obex_object_t *object;
 char path[200];
 int num, path_size;
diff -Nuar orig/lib/obex.c patched/lib/obex.c
--- orig/lib/obex.c 2011-02-03 18:08:21.466204009 +0300
+++ patched/lib/obex.c 2011-02-04 12:16:32.729604002 +0300
@@ -929,25 +929,53 @@
 LIB_SYMBOL
 int CALLAPI OBEX_CharToUnicode(uint8_t *uc, const uint8_t *c, int size)
 {
- int len, n;
+ int i, len, n;
 
 DEBUG(4, "\n");
 
 obex_return_val_if_fail(uc != NULL, -1);
 obex_return_val_if_fail(c != NULL, -1);
-
- len = n = strlen((char *) c);
- obex_return_val_if_fail(n*2+2 <= size, -1);
-
- uc[n*2+1] = 0;
- uc[n*2] = 0;
-
- while (n--) {
- uc[n*2+1] = c[n];
- uc[n*2] = 0;
+ 
+ len = strlen((char *) c);
+ 
+ n = 0;
+ for (i = 0; i < len; i++) {
+ /* skip first byte cyrillic uft-8 */
+ if ( (unsigned char)c[i] == 0xd0 || 
+ (unsigned char)c[i] == 0xd1 )
+ continue;
+ n += 2;
+ }
+ n += 2;
+ 
+ do {
+ uc = realloc((uint8_t *)uc, n * sizeof(char));
+ } while (uc == NULL);
+ 
+ n = 0;
+ 
+ for(i = 0; i < len; i++) {
+ /* replace first byte cyrillic uft-8 */
+ if ( (unsigned char)c[i] == 0xd0) {
+ uc[n] = 0x04;
+ uc[n + 1] = c[i+1] - 0x80;
+ i++;
+ /* replace first byte cyrillic uft-8 */
+ } else if ( (unsigned char)c[i] == 0xd1) {
+ uc[n] = 0x04;
+ uc[n + 1] = c[i+1] - 0x40;
+ i++;
+ } else {
+ uc[n] = 0;
+ uc[n+1] = c[i];
+ }
+ n += 2;
 }
+ 
+ uc[n+1] = 0;
+ uc[n] = 0;
 
- return (len * 2) + 2;
+ return n;
 }
 
 /**


0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.