Pobieranie zawartości zewnętrznych stron w JavaScripcie

Wprowadzenie
Czasami mamy potrzebę pobrać zawartość jakiejś strony internetowej i wykorzystać te dane w naszej aplikacji. Prostym przykładem takiego działania jest pobranie wyników wyszukiwania jakiejś ogólnodostępnej przeglądarki internetowej. Dostęp do takich danych można zrealizować albo po stronie serwera, albo przeglądarki klienta. W przypadku prostych aplikacji, które nie komunikują się z naszym serwerem (i nawet nie muszą być umieszczone na żadnym serwerze HTTP - wystarczy lokalny plik HTML), wygodniej jest pobierać takie dane wykorzystując JavaScript. Takie podejście jest wygodne też dlatego, że praktycznie każdy użytkownik może sobie uruchomić taką aplikację u siebie lokalnie (otwierając plik HTML w jakiejś przeglądarce internetowej).

Bezpieczeństwo
Wiele lat temu na kwestie bezpieczeństwa przeglądania internetu nie patrzyło się tak dokładnie, jak teraz. To powodowało między innymi to, że była możliwość odczytania zawartości dowolnej strony www z poziomu strony innej domeny i przetworzenie jej zawartości w JavaScripcie wedle naszych potrzeb. Była to wygodna sprawa, ale z czasem osoby zajmujące się bezpieczeństwem przeglądarek internetowych zauważyły, że to może prowadzić do pewnych (mniejszych lub większych) nadużyć. Dlatego od jakiegoś czasu przeglądarki internetowe mają poblokowaną możliwość operowania na zawartości strony z innej domeny - fachowo to ograniczenie nazywa się same origin policy).

Dwa rozwiązania: XMLHttpRequest oraz IFRAME
W zasadzie istnieją dwie metody dostępu do zawartości innej strony:
1. Możemy ją pobrać wysyłając żądanie HTTP do serwera gdzie ona się znajduje, a on w odpowiedzi prześle nam kod HTML tej strony - to zachowanie jest analogiczne do metody pobierania strony przez przeglądarkę internetową - tę technikę zwie się potocznie AJAX albo żądaniami XMLHttpRequest;
2. Umieścić w kodzie naszej strony ramkę IFRAME (tzw. ramka pływająca - nie mylić z ramkami FRAME oraz FRAMESET dokumentu HTML), której adres źródłowy będzie wskazywać na daną stronę - przeglądarka wczyta nam automatycznie zawartość tej strony do takiej pływającej ramki.
Jest istotna (i wbrew pozorom bardzo ważna) różnica pomiędzy obiema powyższymi metodami - w przypadku pierwszej dostajemy zwykły kod "statyczny" strony (jako plik tekstowy lub XML), natomiast w drugim przypadku mamy dostęp do "żywej" zawartości takiej strony, między innymi z działającymi skryptami JS, które mogły zmodyfikować jej zawartość zaraz po wczytaniu strony (i cały czas mogą to robić, dopóki strona znajduje się w IFRAME), a dodatkowo może wykryć, że jest umieszczona w takiej ramce i się "wydostać" do głównego okna dokumentu. Taka metoda sprawdzi się jednak wtedy, kiedy dana strona jest tworzona/modyfikowana po swoim wczytaniu za pomocą JavaScriptu i dopiero wtedy zawiera interesujące nas dane.

Internet Explorer 8
Generalnie ta wersja IE (jak i starsze: IE 6 i 7), nie umożliwia dostępu do zawartości strony w innej domenie przy domyślnych ustawieniach (przy użyciu obiektu XMLHttpRequest), przy czym nie ma tu znaczenia fakt, czy robimy to z pliku lokalnego, czy z poziomu dowolnej domeny - dostęp jest zabroniony przy ustawieniach domyślnych.
Ale to można na szczęście zmienić - w tym celu należy wejść do menu Narzędzia -> Opcje internetowe i w zakładce Zabezpieczenia wybrać strefę Internet, a następnie kliknąć na przycisk Poziom niestandardowy, w którym należy włączyć pozycję "Dostęp do źródła danych poprzez domeny" jak na poniższym obrazku:


a następnie zamknąć okienko z ustawieniami, akceptując poniższy komunikat:

Po tych zmianach mamy już możliwość pobrania zawartości dowolnej strony internetowej przy użyciu AJAX (XMLHttpRequest).

Gorzej jest już jednak z dostępem poprzez ramkę IFRAME i mimo wielu moich prób nie udało mi się uzyskać dostępu do zawartości ramki IFRAME zewnętrznej strony internetowej - nawet z poziomu lokalnego pliku - to oczywiście plus dla bezpieczeństwa tych przeglądarek. Być może dostęp taki byłby możliwy w dokumencie typu HTA, ale tego jeszcze nie sprawdzałem.

Internet Explorer 9
W tej wersji IE nieco poprawiono kwestię bezpieczeństwa i choć nadal możliwy jest dostęp do zawartości dowolnej strony internetowej z pliku lokalnego, to już domyślnie zablokowany jest dostęp do strony między różnymi domenami. Ale i to można na szczęście zmienić - w tym celu należy wejść do menu Narzędzia -> Opcje internetowe i w zakładce Zabezpieczenia wybrać strefę Internet, a następnie kliknąć na przycisk Poziom niestandardowy, w którym należy włączyć pozycje "Dostęp do źródła danych poprzez domeny" oraz "Nawigowanie okien i ramek w różnych domenach" jak na poniższym obrazku:


a następnie zamknąć okienko z ustawieniami, akceptując poniższy komunikat:


Problem z ramką IFRAME jest analogiczny jak dla starszych wersji IE.

Internet Explorer 10
Przeglądarka jeszcze nie testowana przeze mnie.

Firefox - starsze wersje
Na forum Mozilli w lutym 2010 roku pojawił się wpis: Remove support for enablePrivilege. Wtedy "królowała" wersja 3.6 Firefoksa. Proponowane zmiany zostały wprowadzone dopiero w wersji 5.0 Firefoksa (czerwiec 2011 roku) i wtedy zablokowana została możliwość dostępu do zawartości stron w innej niż bieżąca domenie (XMLHttpRequest send function throws 0x80004005 exception when called from local html file as of Gecko 5 even with UniversalBrowserRead requested). Na szczęście wersja 4.0.1 Firefoksa i wcześniejsze wciąż oferują taką możliwość (w ograniczonym zakresie i przy wsparciu użytkownika) a starsze wersje tej przeglądarki (szczególnie wygodne są wersje portable) są wciąż dostępne do pobrania:
  Mozilla Firefox, Portable Edition 1.0.8 - angielska wersja językowa (by mieć polską wersję można pobrać i zainstalować dodatek językowy);
  Mozilla Firefox, Portable Edition 1.5.0.12 - angielska wersja językowa (by mieć polską wersję można pobrać i zainstalować dodatek językowy);
  Mozilla Firefox, Portable Edition 2.0.0.20 - kilka wersji językowych (by mieć polską wersję można pobrać i zainstalować dodatek językowy);
  Mozilla Firefox, Portable Edition 3.0.19 - różne wersje językowe (w tym polska wersja);
  Mozilla Firefox, Portable Edition 3.5.19 - różne wersje językowe (w tym polska wersja);
  Mozilla Firefox, Portable Edition 3.6.28 - różne wersje językowe (w tym polska wersja);
  Mozilla Firefox, Portable Edition 4.0.1 - różne wersje językowe (w tym polska wersja);
Aby zmienić język przeglądarek Firefox (wersje 1.0.x, 1.5.x, 2.0.x), prócz zainstalowania właściwego dodatku językowego (do każdej wersji jest inny), należy otworzyć stronę about:config (np. w nowej zakładce), tam w filtrze wpisać/wkleić general.useragent.locale a potem kliknąć na tę pozycję i zmienić wartość z "en-US" na "pl-PL". Zmiana języka nastąpi po zamknięciu i ponownym uruchomieniu przeglądarki.

Funkcja enablePrivilege()
Funkcja ta jest dostępna w przeglądarkach Mozilla Firefox (i innych opartych o silnik Gecko) i służy do nadania dostępu do zawartości strony z innej niż nasza domeny. Przez wywołaniem tej funkcji należy się upewnić, że ona jest zaimplementowana - można to zrobić np. tak:
  if (window.netscape && netscape.security && netscape.security.PrivilegeManager)
  {
    try { netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead'); }
    catch (e) { alert(e); }
  }

Takie wywołanie zabezpiecza nas zarówno przez brakiem tej funkcji w innych przeglądarkach, jak i przed brakiem uprawnień dostępu do danej strony poza naszą domeną (alert wyświetli stosowny komunikat). Zresztą nie tylko o dostęp do innej strony tu chodzi - za pomocą tej funkcji możemy nadać/sprawdzić dostęp do elementów przeglądarki takich jak schowek, strona startowa, pasek statusu oraz tytułowy strony. Niektóre z tych aspektów są krótko opisane w artykule Signed Scripts & Privileges: An Example. Mozilla zdefiniowała różne obszary dostępu do elementów przeglądarki - można o tym poczytać w artykule Expanded Privileges in Mozilla.

Google Chrome i jej pochodne
Przeglądarkę Google Chrome można uruchomić z przełącznikiem "--disable-web-security", który powoduje wyłączenie wielu zabezpieczeń i w ten sposób umożliwia dostęp do zawartości strony z innej domeny zarówno poprzez żądania XMLHttpRequest jak i ramkę IFRAME. Jest to o tyle ciekawe, że możemy wykorzystać tę nowoczesną i szybką przeglądarkę do własnych "niecnych" celów pobierania różnych stron i ich obróbki. Wygodna jest możliwość skorzystania z wersji portable tej przeglądarki, którą można pobrać z oficjalnego serwisu PortableApps. Sprawdziłem w działaniu wersje 27 i 28 Google Chrome i po uruchomieniu jej (a wcześniej po zainstalowaniu w jakimś folderze - instalator pobiera z internetu ok. 33 MB właściwego programu) ze wspomnianym przełącznikiem - przy starcie pokazuje się ostrzeżenie jak na poniższym obrazku:
Ostrzeżenie Gogole Chrome związane z użyciem przełącznika --disable-web-security
ale można je zignorować lub zwyczajnie zamknąć ten komunikat, a przeglądarka umożliwi swobodny dostęp do zawartości dowolnej strony internetowej (obiema metodami). Analogicznie zachowują się przeglądarki Chromium oraz SRWare Iron, które bazują na kodzie Chrome, więc nie jest to żadna niespodzianka.
Ciekawe aspekty związane z dostępem do innych stron przy użyciu żądań XMLHttpRequest jest opisany w artykule Cross-Origin XMLHttpRequest.

Inne przeglądarki
Ciąg dalszy wkrótce nastąpi...
Sprawdzić Safari oraz Operę (starszą i najnowszą wersję).

Znaleziony w internecie artykuł Cross-Domain JavaScript zawiera opis różnych rozwiązań pobrania strony www z innej domeny, ale za pośrednictwem proxy po stronie serwera (co nijak się ma do rozwiązania problemu przedstawionego przeze mnie). Podaję jednak informację o tym dla tych, dla których takie rozwiązanie przez proxy) jest do zaakceptowania.

Podsumowanie
Generalnie okazuje się, że najlepszą przeglądarką oferującą bezproblemowy dostęp do zawartości zewnętrznych stron internetowych obiema metodami jest Google Chrome i jej pochodne - najlepiej w wersji portable. Oczywiście przy użyciu przełącznika "--disable-web-security" podczas uruchamianiu programu.

Przygotowałem też kilka testowych stron, na których można sprawdzić zachowanie różnych przeglądarek w opisanej sprawie:
  przykładowa strona do przetestowania zapytań XMLHttpRequest;
  przykładowa strona do przetestowania działania IFRAME;
  strona do sprawdzenia zainstalowanych wersji Microsoft MSXML (tylko IE);
  inna wersja strony do testowania działania IFRAME;

Do testowania można użyć przygotowanych przeze mnie dwóch prostych plików HTML:
  empty.html - zwykła strona HTML, bez żadnych skryptów;
  empty2.html - strona zawierająca kod JavaScript, który po 3 sekundach od otwarcia/załadowania strony dodaje do jej treści dodatkowy tekst a następnie wyświetla stosowny komunikat na ekranie;

Działanie dostępu do zawartości strony zewnętrznego serwisu internetowego można przetestować podając inny adres powyższych plików HTML:
  http://programistrz.hekko24.pl/pub/cross-domain/empty.html
  http://programistrz.hekko24.pl/pub/cross-domain/empty2.html

Jak wykonać testy? Po otwarciu przykładowej testowej strony za pierwszym razem można sprawdzić jej działanie na domyślnej stronie (plik lokalny) i zaobserwować poprawne działanie. Następnie można wpisać adres jakiejś zewnętrznej strony - można użyć te powyższe (i zobaczyć różnicę), można też wpisać dowolny adres (ale należy na początku zawsze wpisać "http://" lub "https://", bo inaczej będzie błąd). W przypadku błędu wyświetli się stosowny komunikat. Wkrótce przygotuję dwa krótkie filmiki pokazujące opisaną procedurę testową w praktyce.


Valid HTML 4.01 TransitionalValid CSS