Alters

Aventuras de DoHITB: parte II (c)

Buenas!

Hoy por fin acabamos la parte II de esta pequeña aventura... os comento de paso que tengo otra paranoia escéptica lista para mostraros.

Así, veremos bastante código útil en VB.net.

Para recordar un poco (y hacer algo de preámbulo), veamos qué haremos:

  • Conexión FTP
  • Conexión HTTP
    • Con GET
    • Con POST

Empecemos con ello:

Primero debemos conocer (obviamente) nuestros datos de login para nuestro FTP. Aquí usaré los siguientes:

host = mihost.com
user = miUser
pass = David

Ahora, empezando con VB.net, crearemos una clase para facilitar todo un poco.

Para empezar, nuestra clase tendrá tres atributos:

Dim host, user, pass As String

También debemos hacer unos imports (muy importante)

Imports System.Net.FtpWebRequest
Imports System.Net
Imports System.IO

Bien, ahora vayamos a ver el constructor de nuestra clase:

Public Sub New(ByVal host As String, ByVal user As String, ByVal pass As String)
    Me.host = host
    Me.user = user
    Me.pass = pass
End Sub

Simplemente seteamos los valores pasados. Sigamos a cosas más importantes:

Vamos a crear una función que baja un archivo dado:

Public Function bajarArchivo(ByVal fichero As String, ByVal destino As String) As Boolean
    Dim peticionFTP As FtpWebRequest
    Dim localfile As String = destino
    Dim b As Boolean = False

    ' Creamos una peticion FTP con la dirección del fichero que vamos a subir
    peticionFTP = CType(FtpWebRequest.Create(New Uri(fichero)), FtpWebRequest)
    ' Fijamos el usuario y la contraseña de la petición
    peticionFTP.Credentials = New NetworkCredential(user, pass)

    peticionFTP.KeepAlive = False
    peticionFTP.UsePassive = False

    peticionFTP.Method = WebRequestMethods.Ftp.DownloadFile

    Try
        Using response As System.Net.FtpWebResponse = CType(peticionFTP.GetResponse, System.Net.FtpWebResponse)
            Using responseStream As IO.Stream = response.GetResponseStream
                Using fs As New IO.FileStream(localfile, IO.FileMode.Create)
                    Dim buffer(2047) As Byte
                    Dim read As Integer = 0
                    Do
                        read = responseStream.Read(buffer, 0, buffer.Length)
                        fs.Write(buffer, 0, read)
                    Loop Until read = 0

                    responseStream.Close()
                    fs.Flush()
                    fs.Close()
                End Using

                responseStream.Close()
            End Using

            response.Close()
        End Using

         b = True
    Catch ex As Exception
        MsgBox(ex.Message)
    End Try

    Return b
End Function

Esta función recibe dos parámetros: el fichero a bajar (tipo ruta completa, como ftp://host/directorio/archivo.ext), y el lugar donde se guardará (tipo ruta completa, como "C:\Temporal\Archivo.ext").

Primero, creamos una petición FTP vacía, la cual rellenamos con algunos datos, como la ruta, las credenciales (user y pass) y el método (download, en este caso).

El siguiente paso es obtener una respuesta FTP (response) usando nuestra petición (peticionFTP). Si todo va bien, creamos un Stream a partir de esta respuesta.

Si esto ha ido bien, abriremos un archivo mediante un stream. Este archivo será el destino, mientras que el anterior será el origen.

Ahora, haremos un pequeño buffer, en el cual iremos guardando información del origen y escribiéndola en el destino, mientras se pueda.

Cuando tenemos hecho el traspaso de información, vaciamos (flush) los streams y los cerramos.

Si todo ha salido bien, "b" valdrá true; en caso contrario, "b" valdrá false.

Ahora, ya podemos descargar nuestro archivo generado con PHP. Solo tendríamos que pasar los datos necesarios a esta función, y... voliá!

Bien, ahora deberíamos desmenuzar el archivo descargado, para cargar una lista en VB.net. Para ello, supondremos que tenemos un elemento llamado listBox1 y otro llamado listBox2. También doy por supuesto (en este y todos los ejemplos) que todas las funciones están en un módulo aparte. Así, deberíamos hacer algo así:

Bajar archivo
Abrir archivo
Leer archivo
Desmenuzar archivo
Colocarlo en listBox1

Bien, ahora se tendría que hacer lo siguiente (por razones de espacio no lo haré aquí): tomamos todas las funciones anteriores de PHP y las adaptamos a VB.net; de esta manera, ya tendremos la función que nos destripe el archivo.

Así, podemos hacer así:

Dim ftp as new Ftp("mihost.com", "miUser", "David")

ftp.bajarArchivo("BIG_INDEX.dat", "cat.dat")
Form1.ListBox1.Items.AddRange(Module1.getIndexList("cat.dat", vbLf))

Y ya está, tenemos la lista cargada.

Si no sabes de dónde vienen estas funciones, lee este artículo de mi blog.

Ahora viene el siguiente paso: añadir unos handler a nuestras listas para poner o quitar ítems a la lista definitiva. Esto lo haremos así:

Private Sub ListBox1_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox1.DoubleClick
    Module1.addItemFromList(Me.ListBox1, Me.ListBox2)
End Sub

Private Sub ListBox2_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox2.DoubleClick
    Module1.addItemFromList(Me.ListBox2, Me.ListBox1)
End Sub

Ahora, la función addItemFromList():

Public Function addItemFromList(ByVal fromL As ListBox, ByVal toL As ListBox) As Boolean
    Try
        toL.Items.Add(fromL.SelectedItem)
        fromL.Items.Remove(fromL.SelectedItem)
    Catch ex As Exception
        Return False
    End Try

    Return True
End Function

Como veis el manejo es simple; ponemos el ítem en la lista de destino y lo quitamos de la lista origen. Por si acaso, hemos añadido un control de errores (try/catch), así como un retorno, por si queremos controlar la ejecución.

Ahora nos falta generar el archivo definitivo donde buscar, obtener las publicaciones adecuadas y responder vía email.

Para ello, debemos investigar un poco más...

Nos dirigimos a paginaweb.es/buscar/X/Y/1.


Allí vemos las publicaciones de la sección "X", subsección "Y", página 1.

Bajo cada publicación, hay una serie de opciones, una de la cual es "responder". Pulso, a ver qué pasa. Me sale otra ventana (pop-up).

Siendo un poco off-topic, comentar que aquella ventana muestra un montón de información privada a USUARIOS NO REGISTRADOS. Entre ellos, destaco el número de teléfono...

Más abajo, veo que hay una opción de "enviar correo". Voy a esa opción... el contenido de la ventana cambia, y ahora me pide que introduzca datos, a saber:
  • Nombre
  • Email
  • Repetir Email
  • Asunto
  • Texto
Pinta bien... así que decido hacer un pequeño test; voy a paginaweb.es y busco cómo hacer una publicación.

Una vez encontrada, hago una en la sección X, subsección Y, y accedo a paginaweb.es/X/Y/1. Ahí está mi publicación.

Vuelvo a andar el camino a enviar el correo, relleno los datos y le doy a enviar...

Al momento me dice que se ha enviado el correo satisfactoriamente, y me llega un email.

Es de paginaweb.es... lo abro, y para mi sorpresa, no es el típico email diciendo "La persona X se ha puesto en contacto contigo a través de paginaweb.es, ve a este enlace..." sino que directamente han enviado un correo a mi dirección con toda la información que he puesto antes...

Perfecto, ¿no?

Vuelvo a la página para enviar correo, y (desde chrome) hago "inspeccionar elemento" (F12). Allí busco el forumlario (form) de envío de correo, y veo que pasa los siguientes inputs:
  • Nombre
  • Email
  • Repetir Email
  • Asunto
  • Texto
  • ID
¿Y ese ID?... es el ID de la publicación. Esto parece que podría detener mi tarea, pero vamos a investigar un poco más.

Vuelvo a paginaweb.es/X/Y/1, y inspecciono el enlace que lleva a los datos personales (que éste lleva a enviar el correo, recordemos). Allí el enlace es una llamada JavaScript, que incluye el ID.

Bien, tenemos un punto de apoyo, de momento.

Abro la ventana de contacto, y busco el enlace de enviar correo... allí reza el siguiente enlace:

paginaweb.es/correo.php?id=XXXXX

Ok... sigamos. El formulario de envío de correo va hacia (action) paginaweb.es/envio.php

Entonces, no tengo más que tomar el ID, añadirlo a los datos POST, y llamar a envio.php... y tendré el correo enviado, de parte de paginaweb.es

El problema es el ID... pero me fijo en una cosa: el ID aparece al lado de cada publicación. ¿Qué? Me han alegrado el día...

Entonces, solo falta establecer un sistema mediante el cual obtener los códigos. Yo establecí la siguiente ruta:

  • Genero el archivo final (con los ítems de listBox2)
  • Obtengo "X" e "Y" del fichero final generado con VB.net
  • Abro un fichero de códigos
  • Por cada ítem del fichero final, obtengo los códigos de las 10 primeras páginas de "X" e "Y"
  • Guardo dichos códigos en el fichero de códigos
  • Voy al siguiente ítem
  • Cuando acabo con los items, voy al fichero de códigos
  • Por cada código, hago una petición HTTP a paginaweb.es/envio.php con los datos necesarios
  • Termina la tarea

Bien, vamos a ver cómo hacer esto:

Para generar el archivo final, la cosa es tan sencilla como escribir los ítems de listbox2:

Public Sub prepareSend(ByVal code As Integer)
    Dim f As String = "BIG_INDEX"

    If code <> 0 Then
        Dim sw As New StreamWriter("BIG_INDEX_AUX.dat", False)

        For i = 0 To Form1.ListBox2.Items.Count - 1
            sw.Write("[" + i.ToString + "] = " + Form1.ListBox2.Items(i).ToString + "\n")
        Next

        sw.Close()
        f = "BIG_INDEX_AUX"
    End If

    Module1.send(f)
End Sub

Esta función recibe un parámetro (code) de entrada: en nuestra llamada, será el número de ítems de listBox2. Así, si es 0, enviaremos a todo BIG_INDEX; en caso contrario, escribiremos en el fichero auxiliar los ítems necesarios.

Finalmente, enviamos el nombre del archivo a "send".

Vamos a ver ahora la función "send":

Public Sub send(ByVal path As String)
    Dim f As New StreamWriter("GLOBAL_CODES.dat", False)

    Module1.getURL(Module1.getIndexList(path), f)
    f.Close()
    Module1.sendMessages()
End Sub

Creamos el archivo de códigos (GLOBAL_CODES), obtenemos las URL (getURL()), cerramos el archivo y enviamos los mensajes (sendMessages()).

Ahora, explicaré cómo funciona getURL, ya que getIndexList ya está visto:

Public Sub getURL(ByVal list() As String, ByVal file As StreamWriter)
    For i = 0 To list.Length - 1
        Module1.setURLList(list(i), file)
    Next
End Sub

Por cada elemento de la lista (retornada por getIndexList()), añadimos en "file" una lista de URL, mediante la función setURLList(), que a continuación vemos:

Public Sub setURLList(ByVal item As String, ByVal file As StreamWriter)
    For i = 0 To 10
        Module1.getItemList("div", Module1.GetHTML("http://www.paginaweb.es/" + item + "/?pagina=" + (i + 1).ToString), file)
    Next
End Sub

Bien, ésta también delega en otras funciones (la lista parece interminable, pero tened paciencia... el código está muy estructurado), pero por contra la explicación es sencilla:

Obtiene el HTML de la página mandada ("http://www.paginaweb.es/item/?pagina=...), el cual será parseado mediante "div", y será guardado en el archivo. Esto lo hace para las 10 primeras páginas del ítem pasado.

La función getHTML la explicaré al final; vayamos ahora a getItemList:

Public Sub getItemList(ByVal item As String, ByVal src As String, ByVal file As StreamWriter)
    Dim list() As String = Module1.reGenere(Split(src, "<"), "<")
    Dim ret(0) As String
    Dim rc As Integer = 0

    For i = 0 To list.Length - 1
        If Module1.isItem(item, list(i)) Then
            ReDim Preserve ret(rc)
            ret(rc) = list(i)
            rc += 1
        End If
    Next

    list = Nothing

    Dim clD() As String = Module1.getContent(Module1.getValue(Module1.getAttribute("class", ret)), "X>")

    For i = 0 To clD.Length - 1
        file.Write(clD(i).Substring(1) + "\n")
    Next
End Sub

Vamos a explicar: primero parte el texto por el carácter "<", y le añade al principio de cada ítem de nuevo el carácter "<" (esta es la función reGenere: añade al principio el carácter que se le pasa).

Seguidamente iteramos sobre nuestra regenerada lista, y por cada elemento comprobamos si es del tipo especificado ("div", en nuestro caso) mediante la función isItem(toCheck, item).

Si es del tipo, lo añadimos a nuestra lista final.

Tras tratar la lista, la borramos (para ahorrar memoria), y nos quedamos con ret (la lista purgada).

Acto seguido, creamos un nuevo array cuyo contenido es el resultado de obtener el texto que hay tras los "div" de "class" "X", mediante las funciones getContent y getAttribute.

Con esta nueva lista, escribimos los códigos, y estamos listos para otro ítem.

No he querido poner la definición de todas las funciones para no cargar demasiado la entrada y para que os estrujéis el cerebro para encontrar la solución :-P

El siguiente paso es el tema de hacer peticiones HTTP. Para ello, haremos una función sobrecargada:


Public Function GetHTML(ByVal strUrl As String) As String

    Dim WR As System.Net.WebRequest
    Dim Rsp As System.Net.WebResponse
    Try
        WR = System.Net.WebRequest.Create(strUrl)
        Rsp = WR.GetResponse()
        Return New StreamReader(Rsp.GetResponseStream()).ReadToEnd()
    Catch ex As System.Net.WebException
        MsgBox(ex.Message)
        Throw ex
    End Try
End Function

Public Function GetHTML(ByVal strUrl As String, ByVal post() As String) As String

    Dim HttpWRequest As System.Net.WebRequest
    Dim data As StringBuilder = New StringBuilder()
    Dim byteData() As Byte
    HttpWRequest = System.Net.WebRequest.Create(strUrl)
    HttpWRequest.Method = "POST"
    HttpWRequest.ContentType = "application/x-www-form-urlencoded"
    HttpWRequest.Credentials = CredentialCache.DefaultCredentials
    data.Append(String.Join("&", post))
    byteData = UTF8Encoding.UTF8.GetBytes(data.ToString())
    HttpWRequest.ContentLength = byteData.Length
    HttpWRequest.GetRequestStream().Write(byteData, 0, byteData.Length)

    Return New StreamReader(HttpWRequest.GetResponse().GetResponseStream()).ReadToEnd()
End Function
 
Podemos llamarla como GetHTML(url) o bien como GetHTML(url, post). Vamos a explicarlas por separado:

La primera crea una petición web (WebRequest), y una respuesta (WebResponse). Acto seguido crea la petición con la URL pasada, e intenta obtener una respuesta.

Si todo ha salido bien, retornaremos todo el HTML leído en forma de String. En el caso que algo falle, nos avisará.

La segunda función crea una petición (HttpWRequest), un StringBuilder, y un array de Byte.

El siguiente paso es crear la petición con la URL pasada, y además, le añadimos algo de información de cabecera, como el método (POST), el tipo de contenido, las credenciales...

Ahora vamos a formatear los datos POST: juntamos los parámetros del array post() (tienen que estar en formato v=p) con el carácter "&". Seguidamente lo pasamos todo a UTF8.

Ahora, lo añadimos a HttpWRequest, y devolvemos, como antes, un String con todo el HTML resultante de la petición.

Bien, ahora solo falta una cosa: leer los códigos y emular el mensaje... vamos allá:


Public Sub sendMessages(Optional ByVal dir As String = "")

        Dim d As String = "http://www.paginaweb.es/enviado.php"

        If dir <> "" Then
            d = dir
        End If

        Dim f As New StreamWriter("GLOBAL_LOG.dat", False)
        fwrite(f, "EMPIEZA MAILS" + vbLf + vbLf)

        Dim data() As String = Module1.getData()
        Dim codes() As String = Split(Module1.getFContent("GLOBAL_CODES"), "\n")
        Dim max As Integer = codes.Length
        Dim id As Integer = 5

        data(0) = "nombre=" + System.Web.HttpUtility.UrlEncode(data(0).Trim)
        data(4) = "repemail=" + System.Web.HttpUtility.UrlEncode(data(1).Trim)
        data(1) = "email=" + System.Web.HttpUtility.UrlEncode(data(1).Trim)
        data(2) = "asunto=" + System.Web.HttpUtility.UrlEncode(data(2).Trim)
        data(3) = "mensaje=" + System.Web.HttpUtility.UrlEncode(data(3).Trim)

        fwrite(f, "DATOS OBTENIDOS:" + vbLf)
        fwrite(f, "Nombre: " + data(0) + vbLf)
        fwrite(f, "Email: " + data(1) + ", (rep: " + data(2) + ")" + vbLf)
        fwrite(f, "Asunto: " + data(3) + vbLf)
        fwrite(f, "Mensaje [inicio]:" + data(4) + vbLf)
        fwrite(f, ":[fin]" + vbLf + vbLf + vbLf)
        fwrite(f, "____________________" + vbLf + vbLf + vbLf)
        fwrite(f, "OBTENIENDO CÓDIGOS DE FICHERO GLOBAL_CODES.DAT:" + vbLf + vbLf)
        fwrite(f, "ENCONTRADOS " + max.ToString + " CÓDIGOS:" + vbLf + vbLf)

        For i = 0 To max - 1
            Dim h As New StreamWriter("codes(i) + "_" + i.ToString + ".html", False)
            data(id) = "id=" + System.Web.HttpUtility.UrlEncode(codes(i))
            fwrite(f, "Mensaje " + i.ToString + " de " + max.ToString + ":" + vbLf)
            fwrite(f, "Código: " + codes(i) + vbLf)

            h.Close()
        Next

        fwrite(f, "FINALIZA ENVÍO DE MENSAJES" + vbLf)
        f.Close()
        MsgBox("Tarea Terminada")
        Form1.ListBox2.Items.Clear()
    End Sub


Vamos a ver qué hace esto:

Primero definimos la ruta; si "dir" contiene texto, ésta será la URL que usaremos, sino, usamos la de "por defecto".

Seguidamente, creamos un pequeño log, y escribimos en él (con una función definida fwrite) una cabecera.

Usando el fichero de datos de email (con formato conocido), usamos el método getData para obtener los datos (el método getData accede al fichero de datos de email y lo "parsea" siguiendo el formato establecido [clave] = valor).

El siguiente paso es obtener un array con los códigos de los cuales responderemos. Para ello usamos getFContent (función definida, similar a file_get_contents() de PHP), y sobre este String partimos a partir de cada "\n". Obtenemos también la longitud del array resultante.

Creamos, además, un índice auxiliar con valor 5, que usaremos después para los datos POST. El siguiente paso es ordenar los datos POST dentro de "data".

Escribimos algo más en el LOG, y empezamos a iterar el array de códigos.

Para cada elemento, abrimos un StreamWriter. Usamos el índice 5 para poner el último dato POST (el id del elemento).

Escribimos algo más de info. en el LOG, y en el fichero abierto volcamos el HTML obtenido de la llamada (para saber si ha salido bien).

Finalmente, escribimos que hemos terminado, y mostramos un mensaje por pantalla.

Y esto es todo respecto el encargo; así es como aproveché la pequeña brecha de la página web...

En la próxima entrega, un poco más de investigación (¡parte III ya!), en la que descubrí que podía usar este método en combinación con otro para agrandar la brecha de seguridad...

La tercera parte sera en JAVA, y usaremos (mucho) el tema de Thread...

Como siempre, os espero...

¡Hasta la próxima!