Hej, jestem event15 - nowicjusz na tym forum :)
Mam nadzieję, że znajdzie się jedna dobra dusza, która będzie chciała pomóc.
Otóż już mówię o co mi chodzi.
- Na początku wspomnę, że x lat programowałem w PHP, trochę mniej w C++ i troszeczkę w Javie. Teraz zaczynam przygodę z C#
- Chciałem napisać aplikację sieciową, która jest swoistym Hubem pomiędzy usługami na jednym serwerze. Te usługi to poczta e-mail, platformy edukacyjne i inne o których możliwe, że w przyszłości pomyślę.
- Oprogramowanie połączenia z pocztą imap nie było problemem. Odczytanie skrzynki odbiorczej również. Pobranie tylko nieprzeczytanych wiadomości i wyświetlenie ich ilości też nie było problemem.
Problem jednak pojawił się w chwili, kiedy moja aplikacja pisana w (Microsoft Visual Studio 2015 Pro) C# jako projekt WPF miała za zadanie obsłużyć logowanie do strony internetowej.
To jest formularz na stronie:
<form action="/login" method="post">
<div id="content-box">
<div id="login">
<div class="greycornr_content">
<div class="box" >
<div class="row">
<label for="username">Login</label>
<input id="username" name="username" class="required" tabindex="1" accesskey="i" type="text" value="nazwa_uzytkownika" size="25" autocomplete="off"/>
</div>
<div class="row">
<label for="password">Hasło:</label>
<input id="password" name="password" class="required" tabindex="2" accesskey="h" type="password" value="" size="25" autocomplete="off"/>
</div>
<div class="row btn-row">
<input type="hidden" name="lt" value="LT-3319433-SpTmc47FfhI6WKiMPddXLRL3C7jD09" />
<input type="hidden" name="execution" value="e1s2" />
<input type="hidden" name="_eventId" value="submit" />
<input class="btn-submit disabled" id="submit_button" name="submit" accesskey="l" value="Zaloguj" tabindex="4" type="submit" disabled="true" />
<input class="btn-reset" name="reset" accesskey="c" value="Wyczyść" tabindex="5" type="reset" />
</div>
</div>
</div>
</div>
</div>
</form>
Strona, w momencie kiedy na nią wchodzę generuje coś takiego (Odczyt z Firebuga):
Nagłówki żądania:
GET /login HTTP/1.1
Host: logowanie.pl
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0 FirePHP/0.7.4
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: pl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
x-insight: activate
Connection: keep-alive
Nagłówki odpowiedzi:
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 09 Dec 2014 20:58:54 GMT
Content-Type: text/html;charset=UTF-8
Connection: keep-alive
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache, no-store
Set-Cookie: JSESSIONID=843D2E8E0A509F30E370BF93FD486720.n1; Path=/; Secure; HttpOnly
Content-Length: 6327
Oraz, co w pierwszej chwili prawdopodobnie nie jest zauważalne, generowany jest kod ticketu. Jest to pole input typu hidden o nazwie lt
<input type="hidden" name="lt" value="LT-3319433-SpTmc47FfhI6WKiMPddXLRL3C7jD09" />
Ja próbując się połączyć ze stroną korzystam z paczki HtmlAgilityPack, a robię to w ten sposób:
private void Button_Click(object sender, RoutedEventArgs e)
{
var webGet = new HtmlWeb();
var document = webGet.Load("https://logowanie.pl/login");
var metaTags = document.DocumentNode.SelectNodes("//input");
if (metaTags != null)
{
foreach (var tag in metaTags)
{
if (tag.Attributes["name"].Value == "lt")
{
Cookie_LT = tag.Attributes["value"].Value;
}
}
}
loginik = login_field.Text;
haslo = pass_field.Password;
Box_Text.Text = Cookie_LT;
// Pobieranie ciasteczek
CookieCollection cookies = new CookieCollection();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://logowanie.pl/login");
request.CookieContainer = new CookieContainer();
request.CookieContainer.Add(cookies);
request.GetRequestStream();
// Pobierz odpowiedź z serwera i zapisz ciastka z pierwszego zapytania
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
cookies = response.Cookies;
string getUrl = "https://logowanie.pl/login";
MessageBox.Show(cookies[0].ToString().Split('=')[1]);
string postData = String.Format("username={0}&password={1}<={2}&execution=e1s1&_eventId=submit&submit=Zaloguj", loginik, haslo, Cookie_LT);
HttpWebRequest getRequest = (HttpWebRequest)WebRequest.Create(getUrl);
getRequest.CookieContainer = new CookieContainer();
getRequest.Referer = getUrl;
getRequest.CookieContainer.Add(cookies); //recover cookies First request
getRequest.Method = WebRequestMethods.Http.Post;
getRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.121 Safari/535.2";
getRequest.AllowWriteStreamBuffering = true;
getRequest.ProtocolVersion = HttpVersion.Version11;
getRequest.AllowAutoRedirect = true;
getRequest.ContentType = "application/x-www-form-urlencoded";
byte[] byteArray = Encoding.ASCII.GetBytes(postData);
getRequest.ContentLength = byteArray.Length;
Stream newStream = getRequest.GetRequestStream(); //open connection
newStream.Write(byteArray, 0, byteArray.Length); // Send the data.
newStream.Close();
HttpWebResponse getResponse = (HttpWebResponse)getRequest.GetResponse();
using (StreamReader sr = new StreamReader(getResponse.GetResponseStream()))
{
string sourceCode = sr.ReadToEnd();
box.AppendText(sourceCode);
}
MessageBox.Show(getRequest.Headers.ToString());
MessageBox.Show(byteArray.Length.ToString());
MessageBox.Show(getResponse.Headers.ToString() + "" + cookies.Count);
}
Na formularzu WPF mam następujące kontrolki:
- TextBox o nazwie login_field
- PasswordBox o nazwie pass_field
- TextBlock o nazwie Box_Text
- RichTextBox o nazwie box
Połączenie działa prawie doskonale. Pobierana jest strona do obiektu webGet, następnie z niej odczytuję wartość parametru LT (z kodu źródłowego).
Następnie wykonywane jest zapytanie do strony internetowej i zapisanie cookies w pamięci. Odebranie odpowiedzi. Pobranie cookies.
Teraz zapytanie razem z danymi POST - czyli użytkownik, hasło, ticket i inne dane wraz z ciastkiem są pakowane w jedną zmienną. To wszystko za pomocą Stream wysyłane na serwer. Otrzymuję od niego odpowiedź. Do kontrolki box ładuje się strona.
I tu zaczęły się schody.
Mianowicie program romansuje z siecią w taki sposób:
Dane zapytania:
Referer: https://logowanie.pl/login
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.121 Safari/535.2
Content-Type: application/x-www-form-urlencoded
Host: logowanie.pl
Cookie: JSESSIONID=E01F260159E958F36EAA4C3965FE9B1E.n1
Content-Length: 142
Expect: 100-continue
Dane odpowiedzi:
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache,no-store
Content-Type: text/html;charset=UTF-8
Date: Tue, 09 Dec 2014 21:19:28 GMT
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Server: nginx
Content-Length: 7124
Ilość ciastek w kolekcji: 1
A strona, która jest przechwytywana do zmiennej, zwraca coś takiego:
nieznana postać biletu 'LT-3319431-QAkDZufekpbY5kjXAgmhbMXkvU5eLZ'
Co temu wini?
Przeanalizowałem kod.
Zobaczyłem, że:
- łączę się ze stroną i pobieram ją do zmiennej webGet, z tego co tam weszło wybieram sobie wartość LT
- następnie, ponownie się łączę ze stroną za pomocą innej biblioteki (HtmlWebRequest) wywołując metodę Create().
- operuję w zapytaniu na tej właśnie wersji pobranej strony. Wcześniej załadowana stronka tylko zajmuje miejsce w pamięci i w kodzie. Można to usunąć
I tak metodą analizy doszedłem, że w praktyce nie potrzebuję:
var webGet = new HtmlWeb();
var document = webGet.Load("https://logowanie.pl/login");
var metaTags = document.DocumentNode.SelectNodes("//input");
if (metaTags != null)
{
foreach (var tag in metaTags)
{
if (tag.Attributes["name"].Value == "lt")
{
Cookie_LT = tag.Attributes["value"].Value;
}
}
}
Jednak nie wiem w jaki sposób pobrać tę samą wartość z zapytania, które jest "aktywne".
Czy ktoś mógłby mi podpowiedzieć, co należy zrobić?
Wiem, że można pobrać stronkę po wygenerowaniu odpowiedzi, tak właśnie robię. Ale nie wiem w sumie jak pobrać stronkę po wykonaniu zapytania. Możliwe, że zbyt duża ilość przesiadywania nad jednym kodem wyłączyła mi myślenie i rozwiązanie jest banalne - jednak w tej chwili tego nie dostrzegam.
Pozdrawiam, event15 :)