Статьи

Взлом SWT: чтение GMail в приложении RCP

Разговаривая с Миком на Tasktop о том, что потребуется для того, чтобы перенести его революционный продукт на базе Mylyn на платформу Mac, Мик указал мне на проблему с тем, как основанный на Safari виджет браузера SWT обращается к GMail. Хотя Safari является поддерживаемым браузером для GMail, встроенный браузер Safari в Eclipse не распознается GMail. Этот пост освещает взлеты и падения моего исследования по этой проблеме .

Другие в Tasktop обнаружили, что браузер SWT работает, если доступ к GMail осуществляется через http://mail.google.com/mail/?nocheckbrowser URL, который был многообещающим. Это привело меня к мысли, что заголовок user-agent встроенного Safari отличается от заголовка, созданного Safari с рабочего стола. Быстрый тестовый просмотр на http://whatsmyuseragent.com подтвердил, что мои подозрения верны.

Вот заголовки агента пользователя:

Safari Inside Eclipse:

Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/523.12.2 (KHTML, like Gecko)

Автономное Сафари:

Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/523.12.2 (KHTML, like Gecko) Version/3.0.4 Safari/523.12.2

Следующим шагом было посмотреть, предоставляет ли Safari API для изменения заголовка агента пользователя. Уже установив XCode в моей системе, было легко искать документацию по API. Я нашел раздел под названием «Spoofing», который посвящен API для изменения заголовка «user-agent»:

  • setCustomUserAgent
  • setApplicationNameForUserAgent

Судя по разнице в заголовках пользовательских агентов, второй был именно тем, что я искал.

Зная, что SWT включает в себя нативные звонки в Какао, я немного опасался следующего шага. Глядя на Safari.javaэтот create()метод, я мог видеть другие вызовы веб-представления Safari для настройки различных свойств. Все они были выполнены с использованием собственных вызовов JNI, одного из различных Cocoa.objc_msgSendвариантов. К сожалению, не было той подписи, которая мне была нужна. Тот, который мне нужен был бы выглядеть так:

public static final native int objc_msgSend(int object, int
selector, String string);

Я добавил метод в Cocoa.java и добавил к нему вызов Safari.create()следующим образом:

// [webView
setApplicationNameForUserAgent:];Cocoa.objc_msgSend(webView,
Cocoa.S_setApplicationNameForUserAgent,"Safari/unknown");

Следующим шагом является предоставление собственной реализации метода.

Используя команду поиска eclipse, я увидел, что другие реализации objc_msgSend были в cocoa.c Никогда раньше не выполнял JNI, пришло время немного осмотреться. В FAQ по SWT было большинство того, что мне было нужно, с указанием того, как создавать нативные библиотеки и т. Д. Сложные части были следующими:

  • Как определить искаженное имя функции для нативного метода?
  • теперь, чтобы преобразовать аргумент jstring в NSString?

По первому вопросу — немного догадаться и посмотреть на спецификацию JNI, где говорится о калечении, и я понял, что это правильно. Потушив метод, построив его (согласно инструкциям в FAQ по SWT), я получил java.lang.UnsatisfiedLinkError. Запустив его снова в отладчике, я смог увидеть имя метода, который искал, и зафиксировал имя метода cocoa.c. Перестройте, перезапустите, и пока все хорошо! Мой заглушенный метод вызывался всякий раз, когда создается новый элемент управления SWT Browser. Это то, что у меня было до сих пор:

#ifndef NO_objc_1msgSend__IILjava_lang_String_2
JNIEXPORT jint
JNICALL Cocoa_NATIVE(objc_1msgSend__IILjava_lang_String_2)
(JNIEnv *env, jclass that,
jint arg0, jint arg1, jstring arg3)
{
jint rc = 0;
return rc;
}

#endif
 

Теперь перейдем к реализации метода: как вызвать правильный метод? Быстрый google на jstring к NSString привел к следующему:

#ifndef
NO_objc_1msgSend__IILjava_lang_String_2
JNIEXPORT jint JNICALL
Cocoa_NATIVE(objc_1msgSend__IILjava_lang_String_2)
(JNIEnv *env, jclass that, jint arg0,
jint arg1, jstring arg3)
{
jint rc = 0;

Cocoa_NATIVE_ENTER(env, that,<p>objc_1msgSend__IILjava_lang_String_2I_FUNC);
const char *str =
(*env)->GetStringUTFChars(env, arg3, 0);

  /* use NSString as follows: [NSString
stringWithCString: str] */

(*env)->ReleaseStringUTFChars(env, arg3, str);

Cocoa_NATIVE_EXIT(env, that, objc_1msgSend__IILjava_lang_String_2I_FUNC);
return rc;
}
#endif

Хорошо, теперь все, что мне нужно было сделать — это отправить объекту сообщение Objective-C! Копирование из других примеров в том же файле, вот что я закончил:

#ifndef NO_objc_1msgSend__IILjava_lang_String_2
JNIEXPORT jint JNICALL Cocoa_NATIVE(objc_1msgSend__IILjava_lang_String_2)
(JNIEnv *env,
jclass that, jint arg0, jint arg1, jstring arg3)
{
jint rc = 0;


Cocoa_NATIVE_ENTER(env, that, objc_1msgSend__IILjava_lang_String_2I_FUNC);
const char
*str = (*env)->GetStringUTFChars(env, arg3, 0);

rc = (jint)objc_msgSend((id)
arg0, (SEL)arg1, [NSString stringWithCString: str]);

(*env)->ReleaseStringUTFChars(env, arg3, str);
Cocoa_NATIVE_EXIT(env, that,
objc_1msgSend__IILjava_lang_String_2I_FUNC);
return rc;
}
#endif

Я восстановил нативные библиотеки, перезапустил затмение и вуаля! Это сработало! Теперь виджет браузера SWT Safari отправлял строку агента пользователя следующим образом: 

Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/523.12.2 (KHTML, like Gecko) Safari/unknown 

Я вернулся к GMail с этим новым патчем, и теперь GMail распознал Safari, встроенный в eclipse, как поддерживаемый браузер. Для моего первого попытки взлома SWT с использованием JNI и Objective-C это был большой успех … теперь, если только команда SWT примет мой патч;)