Enviar archivo con HTTP POST a servidor PHP.

Iniciado por Kaxperday, 11 Marzo 2016, 13:27 PM

0 Miembros y 1 Visitante están viendo este tema.

Kaxperday

Hola, a todos llevo muchos días sin estar al tanto del foro, pero he vuelto a programar un poco, y he tenido un problema para enviar un archivo con HTTP POST, os dejo el código del cliente C++ y del servidor PHP.

Hay otros métodos como PUT pero no me interesan. Quiero enviarlo por POST que funciona para todo tipo de archivos. La idea es que al subirlo envio 2 variables, una con el nombre del archivo y su extensión y otra con el contenido del archivo en binario. Quizás pueda tener que ver con el fallo.

Ojo, solo quiero subir todos los bytes del archivo y su nombre en 2 variables (nombre y contenido), y con el script PHP guardarlo. La idea sería poner cifrado a los bytes del archivo y desencriptarlo en el PHP, pero eso ya más adelante. Pero por eso necesito enviar el archivo en una variable como un array de bytes.

Código (cpp) [Seleccionar]

//_path C:\\Users\\User\\Desktop\\bicieta.jpg
bool tracker::send_file(std::string _path)
{
WSADATA wsa;
SOCKET sock;
sockaddr_in addr;
std::string data;
std::string peticion;

WSAStartup(MAKEWORD(2, 0), &wsa);

if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR){
WSACleanup();
return CODIGO::ERR_RED;
}

memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(IPPORT_HTTP);
addr.sin_addr.s_addr = inet_addr("71.60.170.21");//ip
domain = "web.com";//quitar
std::ifstream filer(_path, std::ifstream::ate | std::ifstream::binary);
int size = filer.tellg();
char* buffer = new char[size];
filer.seekg(0, std::ios::beg);
filer.read(buffer, size);
filer.close();

char aux[8];
int len;
data = "n=" + _path.substr(_path.find_last_of('\\') + 1) + "&c=";
len = data.size();
data += std::string(buffer, size);
len = len + size;
_itoa(len, aux, 10);

peticion += "POST /catcher.php HTTP/1.1\r\n";
peticion += "Host: " + domain + "\r\n";
//peticion += "Cookie: " + cookie + "\r\n";
peticion += "Content-type: application/octet-stream\r\n";//multipart/form-data también he probado..
peticion += "Content-length: " + std::string(aux);// data.length();
peticion += "\r\n\r\n";
peticion += data;

if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR){
closesocket(sock);
WSACleanup();
return CODIGO::ERR_RED;
}

if (send(sock, peticion.c_str(), peticion.length(), 0) == SOCKET_ERROR){
closesocket(sock);
WSACleanup();
return CODIGO::ERR_RED;
}

char respuesta[1024];
memset(respuesta, 0, sizeof(respuesta));
if (recv(sock, respuesta, 1024, 0) == SOCKET_ERROR){
closesocket(sock);
WSACleanup();
return CODIGO::ERR_RED;
}

return CODIGO::CORRECTO;
}


Código (php) [Seleccionar]

<?
$name = htmlspecialchars($_POST["n"]);
$content = htmlspecialchars($_POST["c"]);
file_put_contents($name, $content);
?>


Bueno el cliente funciona sin problemas, sin embargo el php me dice que la variable "c" esta vacía. "empty". Luego la función salta error, pero no entiendo porque cuando le estoy enviando el contenido a la variable "c".

Edito: Y sí el código no es de lo mejorcito estoy de pruebas xd, hay chapuzillas y faltan cosas para que quede la función como tal.

Un saludo y gracias.

Edito: Bueno lo acabo de enviar como application/octet-stream y me daba error de que el filename estaba vacio que no se porque, de todas maneras lo elimine y dije que el server pusiera el nombre y solo mandar el contenido de la imagen, y se ha subido con éxito solo que al ver la imagen me dice que hay error y no me deja verla :""D.. bueno quizas no lo habra porque tiene 0 bytes.. por lo mismo que me daba error de que el nombre estaba vacio, la variable del contenido también esta vacia, quizás sea por el htmlgetspecialchars.

Lo suyo sería hacerlo con el boundary.. pero no entiendo porque de esta manera no deja. Solo quiero mandar los bytes en una variable cifrados y que al llegar los desencripte y los meta en un archivo.

Bueno continuando un poco la mejor manera creo que es enviandolo con el application/x-www-form-urlencoded, recibe la variable del nombre correctamente y el contenido a medias 1,900kb cuando el archivo es de 80kb, así que luego al abrir la foto me dice que no la puede mostrar. Creo que es el htmlgetspecialchars que lo corta al encontrar un caracter o algo porque los 80kb se mandan por wireshark. Sin el specialshars se corta en el 1,6kb XD.

Habrá un caracter nulo que corte el array o algo :).

Seguramente haya que usar octet-stream pero en el PHP me aparecen nulas cuando las mando con ese formato.
Cuando el poder económico parasita al político ningún partido ni dictador podrá liberarnos de él. Se reserva el 99% ese poder.

kub0x

La propia API de Win32 ofrece funciones para enviar datos a un servidor HTTP bajo POST.

Primero llama a HttpOpenRequest -> https://msdn.microsoft.com/en-us/library/windows/desktop/aa384233%28v=vs.85%29.aspx

Y después llama a HttpSendRequest -> https://msdn.microsoft.com/en-us/library/windows/desktop/aa384247%28v=vs.85%29.aspx

(Así te dejas de sockets y tanta manualidad ;) )

Para enviar los datos cifrados emplea HTTPS (TLS) o cifrálos bajo AES con una clave que este alojada en ambos extremos.

Saludos.
Viejos siempre viejos,
Ellos tienen el poder,
Y la juventud,
¡En el ataúd! Criaturas Al poder.

Visita mi perfil en ResearchGate


Kaxperday

#2
Hola socio, gracias por la respuesta. Como recomendastes he dejado de usar sockets y he pasado a usar las APIs.

El código sin embargo no funciona, os comento más abajo, primero lo muestro:

Código (cpp) [Seleccionar]

bool tracker::send_file(std::string _path)
{
HINTERNET hi;
DWORD dwBytes;

if ((hi = InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0)) == NO_ERROR){
return false;
}

if ((hi = InternetConnect(hi, L"web.com", INTERNET_DEFAULT_HTTP_PORT,
NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0)) == NO_ERROR){
std::cout << "error en connect";
InternetCloseHandle(hi);
return false;
}

DWORD dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS |
INTERNET_FLAG_KEEP_CONNECTION |
INTERNET_FLAG_NO_AUTO_REDIRECT |
INTERNET_FLAG_NO_COOKIES |
INTERNET_FLAG_NO_CACHE_WRITE |
INTERNET_FLAG_NO_UI |
INTERNET_FLAG_RELOAD;

if ((hi = HttpOpenRequest(hi,
L"POST",
L"/catcher.php",
L"HTTP/1.1",
NULL, NULL,
dwOpenRequestFlags, 0)) == NO_ERROR){
std::cout << "error en openrequest";
InternetCloseHandle(hi);
return false;
}

std::string boundary = "-----------------------------autoupdater1234";
std::string headers;
headers += "Host: web.com\r\n";
headers += "Content-Type: multipart/form-data; boundary=" + boundary;

HttpAddRequestHeadersA(hi, headers.c_str(), headers.length(), HTTP_ADDREQ_FLAG_ADD);

std::string request;
std::string body;

std::ifstream filer("C:\\Users\\User\\Desktop\\ciclista.jpg", std::ifstream::ate | std::ifstream::binary);
int size = filer.tellg();
char* buffer = new char[size];
filer.seekg(0, std::ios::beg);
filer.read(buffer, size);
filer.close();
std::string content = std::string(buffer, size);

/*Body*/
body += boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"archivo\"; filename=\"ciclista.jpg\"\r\n";
body += "Content-Type: image/jpeg\r\n\r\n";//application/octet-stream\r\n\r\n multipart/form-data\r\n\r\n
body += content;
body += "\r\n";
body += boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"boton\"\r\n";//"\r\n\r\n"
body += "Enviar archivo\r\n";
body += boundary + "--\r\n";

int a = body.length();
char aux[10];
_itoa(a, aux, 10);

/*Headers*/
//request += "Host: web.com\r\n";
request += "Content-Type: multipart/form-data; boundary=" + boundary + "\r\n";
request += "Content-Length: " + std::string(aux) + "\r\n\r\n";//"\r\n";

//HttpAddRequestHeadersA(hi, request.c_str(), request.length(), HTTP_ADDREQ_FLAG_ADD);

/*Body*/
request += body;
std::cout << request.substr(0, 600) << request.substr(request.length() - 200, 200);
//InternetSetOption(hi, INTERNET_OPTION_USERNAME, "username\0", 9);
//InternetSetOption(hi, INTERNET_OPTION_PASSWORD, "password\0", 9);
std::cout << body.length() << "  " << request.length() << std::endl;
if (HttpSendRequest(hi, NULL, 0, (LPVOID)request.c_str(), request.length())== NO_ERROR){
std::cout << "error send req";
InternetCloseHandle(hi);
return false;
}

std::string res;
char ch;
while (InternetReadFile(hi, &ch, 1, &dwBytes))
{
if (dwBytes != 1)break;
res += ch;
}

InternetCloseHandle(hi);
std::cout << res;
}


Y ahora os dejo lo que tengo en el PHP:

Código (php) [Seleccionar]

<form action="" method="post" enctype="multipart/form-data">
   Subir archivo:
   <input type="file" name="archivo" id="Seleccionar archivo" />
   <input type="submit" name="boton" value="Enviar archivo" />
</form>

<?php 
if($_FILES["archivo"]["name"]){
      if (
$_FILES["archivo"]["error"] > 0) { 
        echo 
$_FILES["archivo"]["error"] . "<br/>"
      } else { 
          
// Si no hubo ningun error, hacemos otra condicion para asegurarnos que el archivo no sea repetido 
          
if (file_exists("" $_FILES["archivo"]["name"])) { 
            echo 
$_FILES["archivo"]["name"] . " ya existe. "
          } else { 
           
// Si no es un archivo repetido y no hubo ningun error, procedemos a subir a la carpeta /archivos, seguido de eso mostramos la imagen subida 
            
move_uploaded_file($_FILES["archivo"]["tmp_name"], 
            
"" $_FILES["archivo"]["name"]); 
            echo 
"Archivo Subido <br />"
          } 
      } 

?>



Bueno, con el navegador funciona perfectamente y sube el archivo, y con mozilla firefox usando http live headers he tratado de copiar prácticamente los campos sin éxito, he estado buscando bastantes códigos (se que no es una buena solución, es lo peor que se puede hacer) pero de nada han servido.

Básicamente hay que delimitar el archivo por el boundary, la variable "content-length" pongo en ella todo lo que mando en el body es decir el contenido del archivo, el los boundary, Content-Disposition etc.

Citar
POST /catcher.php HTTP/1.1
Host: web.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://web.com/catcher.php
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------283331266631290
Content-Length: 80890
-----------------------------283331266631290
Content-Disposition: form-data; name="archivo"; filename="ciclista.jpg"
Content-Type: image/jpeg
ÿØÿá824tn017ct09134n9t7n190347tv09q0924nct09DATOS ARCHIVO0QWECTQWTCQWT
Q48Y1V 384YNC09834NUTC
-----------------------------283331266631290
Content-Disposition: form-data; name="boton"

Enviar archivo
-----------------------------283331266631290--


Bien, ahora la petición que envío yo capturada con wireshark:

Citar
POST /catcher.php HTTP/1.1
Host: web.com
Content-Length: 81003
Connection: keep-alive
Cache-Control: no-cache

Content-Type: multipart/form-data; boundary=-----------------------------autoupdater1234
Content-Length: 80888
-----------------------------autoupdater1234
Content-Disposition: form-data; name="archivo"; filename="ciclista.jpg"
Content-Type: image/jpeg
q4w98n8934qynqv893y4ntARCHIVOQ0WY4CTN184
QWETCQWETQWTVQWETV
-----------------------------autoupdater1234
Content-Disposition: form-data; name="boton"

Enviar archivo
-----------------------------autoupdater1234--

Me salen 2 content-length pero aun corrigiendo eso falla, ahí ando. La cosa es que el content-length que "se supone estaría bien" es el que calculo yo que es el tamaño del body, pero la funcion automaticamente me pone el otro (el de toda la peticion incluyendo la cabecera), ¿como puedo cambiar eso?.

Un saludo y gracias. :)
Cuando el poder económico parasita al político ningún partido ni dictador podrá liberarnos de él. Se reserva el 99% ese poder.