PDA

Ver la Versión Completa : subir determinado archivo


Migue Rguez
09-05-2006, 09:34:09
Que tal foro. En Delphi ando suelto, pero en html no se me pueden pedir grandes cosas.

Seguro que cualquiera que visite este post puede ayudarme: el caso es que necesito enviar un archivo al servidor pero el típico caso del formulario con el botón Examinar y Submit pues no me sirve. El archivo a enviar debe estar especificado en el mismo HTML. Es decir, al usuario no se le da la opción de escoger el archivo "file.txt" y pulsar submit sino que directamente es subido.

Tal vez no sea posible por el riesgo de seguridad que entrañaría esto, visitar una pagina web y empezar a subirse archivos de nuestro pc, pero nunca se sabe.

¿De qué manera puede hacerse?

Saludos y gracias de antemano.

kayetano
09-05-2006, 13:57:37
Hola

En primer lugar necesitas alguna lenguaje tipo PHP, ASP o JSP.
Sobre el tema de no permitir el indicar una ruta al usuario creo que factible, el problema reside en si ese archivo esta en otra ruta.

Migue Rguez
10-05-2006, 14:09:07
Kayetano gracias,

el archivo lo subo perfectamente al servidor mediante el formulario HTML pero es un poco tedioso y poco funcional el hecho de que los usuarios tengan que usar el dichoso formulario web.

Me explico mejor:
El servidor me recoge el archivo mediante un HTML parecido a esto...
<FORM action=http://server.com/process
enctype="multipart/form-data"
method="post">
<P>
Cuál es tu nombre? <INPUT type="text" name="nameperson"><BR>
File to send? <INPUT type="file" name="file_name"><BR>
<INPUT type="submit" value="send"> <INPUT type="reset">
</FORM>
desde mi aplicación en Delphi conozco el archivo, tamaño, path incluso el contenido del archivo puesto que el usuario asi se lo ha indicado a la aplicación.

La única manera que conozco es incrustar un TWebBrowser abriendo ese triste form y haciendo que el usuario seleccione el archivo.

Un poco enrevesado para un usuario de a pie no? Y más aún si queremos dotar a la aplicacion la propiedad de hacer drag&drop de uno o varios archivos desde el explorador de windows a la aplicación.

También intenté "capturar" el resultado del form donde me devolvía algo parecido a esto...
--AaB03x
Content-Disposition: form-data; name="nameperson"

Migue
--AaB03x
Content-Disposition: form-data; name="file_name"; filename="file1.txt"
Content-Type: text/plain

... contenido del archivo file1.txt ...
bla, bla, bla...
--AaB03x--

y pasárselo con el componente Indy IdHTTP1.Post(). Evidentemente no funciona y está claro que no debe ser la forma, pero como programador Delphi y aventurero tenía que probar.

Quizás alguien tenga la forma y pueda darme las directivas para hacerlo.

Gracias.

seoane
10-05-2006, 15:05:57
Entiendo que lo que quieres es mandar un archivo a un servidor web desde una aplicacion de delphi. Pero me surge la pregunta de quien sera el encargado de recibirlo, me explico, es muy sencillo mandar el contendio del archivo utilizando el metodo post, pero tal cual sin utilizar el formato que usan los navegadores para hacerlo ("multipart/form-data"), pero si del lado del servidor el encargado de recibir el archivo es un cgi creado por nosotros no deberia de haber problema para que entendiera lo que estamos mandando.

Pongamos un poco de codigo:

uses WinInet;

function SubirArchivo(Servidor, Cgi, Archivo: string; Puerto: Word): Boolean;
var
hNet: HINTERNET;
hCon: HINTERNET;
hReq: HINTERNET;
Context: DWORD;
Mem: TMemoryStream;
begin
Context:= 0;
Result := FALSE;
hNet := InternetOpen('Agente', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if (hNet <> nil) then
begin
hCon:= InternetConnect(hNet,PChar(Servidor),Puerto,nil,nil,INTERNET_SERVICE_HTTP,0,Context);
if (hCon <> nil) then
begin
hReq:= HttpOpenRequest(hCon,'POST',PChar(Cgi),nil,nil,nil,
INTERNET_FLAG_RELOAD,Context);
if (hReq <> nil) then
begin
Mem:= TMemoryStream.Create;
try
try
Mem.LoadFromFile(Archivo);
HttpSendRequest(hReq,nil,0,Mem.Memory,Mem.Size);
except end;
finally
Mem.Free;
end;
InternetCloseHandle(hReq);
end;
InternetCloseHandle(hCon);
end;
InternetCloseHandle(hNet);
end;
end;


El metodo que describo aqui arriba mandaria los datos tal cual, sin codificarlos ni tratarlos de ninguna manera. Asi que del lado del servidor deberiamos de tener un cgi que supiera como le estamos mandando los datos y que supìera interpretarlo.

Aqui te propongo una solucion, habria que modificarla un poco segun tus necesidades, pero te puede dar una idea. Es un cgi, lo he probado en un servidor apache y me funciona bien:


program Upload;

uses
Windows,
Sysutils;

function Decode(s: string): string;
var
i: integer;
Ch: integer;
begin
result := '';
i := 1;
while i <= Length(s) do
begin
if copy(s, i, 1) = '%' then
begin
Ch := StrToIntDef('$' + copy(s, i + 1, 2), -1);
if (Ch > 0) and (Ch < 256) then
result := result + Char(Ch);
inc(i, 2);
end
else
result := result + copy(s, i, 1);
inc(i);
end;
end;

function StdWrite(Output: THandle; Str: string): boolean;
var
Escritos: Cardinal;
begin
if WriteFile(Output,PChar(Str)^,Length(Str),Escritos,nil) then
Result:= Escritos <> Cardinal(Length(Str))
else Result:= FALSE;
end;

function StdWriteln(Output: THandle; Str: string): boolean;
begin
Result:= StdWrite(Output,Str+#13#10);
end;

function GetEnvVar(Nombre: string): string;
var
Str: PChar;
Len: Integer;
begin
Len:= GetEnvironmentVariable(PChar(Nombre),nil,0);
if Len > 0 then
begin
GetMem(Str,Len+1);
try
GetEnvironmentVariable(PChar(Nombre),Str,Len);
Result:= String(Str);
finally
FreeMem(Str);
end;
end else Result:= '';
end;

function Guardar(Input: THandle; Archivo: string): boolean;
var
hFile: THandle;
Leidos, Escritos, Total: Cardinal;
Buffer: array[0..1024] of Byte;
Root: string;
begin
Total:= StrToIntDef(GetEnvVar('CONTENT_LENGTH'),0);
if Total > 0 then
begin
// Aqui colocamos el directorio donde se colocaran los archivos recibidos
Root:= 'C:\';
Archivo:= ExpandFileName(Root+Archivo);
if StrLIComp(PChar(Root),PChar(Archivo),Length(Root))=0 then
begin
hFile:= CreateFile(PChar(Archivo),GENERIC_WRITE,0,nil,CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,0);
if hFile <> INVALID_HANDLE_VALUE then
begin
repeat
if not ReadFile(Input, Buffer, sizeof(Buffer), Leidos, nil) then
break;
if not WriteFile(hFile,Buffer,Leidos,Escritos,nil) then
break;
Dec(Total,Leidos);
until (Leidos = 0) or (Total = 0);
CloseHandle(hFile);
end;
end;
Result:= Total = 0;
end else Result:= TRUE;
end;

procedure Vamos;
var
Output: THandle;
Input: THandle;
QueryString: string;
begin
Output:= GetStdHandle(STD_OUTPUT_HANDLE);
Input:= GetStdHandle(STD_INPUT_HANDLE);
if (Output <> INVALID_HANDLE_VALUE) and (Input <> INVALID_HANDLE_VALUE) then
begin
QueryString:= Decode(GetEnvVar('QUERY_STRING'));
StdWriteln(Output,'Content-type: text/html');
StdWriteln(Output,'');
if Guardar(Input,QueryString) then
StdWriteln(Output,'OK')
else
StdWriteln(Output,'ERROR');
end;
end;

begin
Vamos;
end.


Y por ultimo solo nos queda utilizar la primera funcion para mandar el archivo, de esta manera:

SubirArchivo('www.tuservidor.com','/cgi-bin/upload.exe?archivo.txt','archivo.txt',80);


Espero que te sirva, aunque si lo que necesitas es subirlo con formato, pues viene siendo lo mismo pero dandole formato a los datos antes de mandarlos.

Migue Rguez
13-05-2006, 11:10:56
Gracias seoane!

Sencillamente magistral tu respuesta, es para apuntarla y no olvidarla. Ahora bien, no solucionó mi problema pero consiguió abrirme la mente y obtuve la forma de darle salida a este caso.

Os lo explico para compartirlo con todos vosotros. Prefiero dejar la idea antes que el código ya que cada caso puede ser particular.

La idea principal es hacer un POST conteniendo la respuesta que envía nuestro navegador del formulario HTML. Es decir, qué cosa recibe el servidor para que termine recibiendo perfectamente lo que le enviamos. Para esto puedes usar un programa gratuito de http://www.softx.org/internet-logger.html que logea todo el tráfico web cliente-servidor-servidor-cliente.

Analizamos el log resultante y extraemos toda la respuesta que devuelve el navegador.

Con esta respuesta la dividimos en dos partes: la primera que va desde el principio del log extraido hasta la primera posicion donde comienza el envio del contenido del archivo a enviar por el formulario web. Y la segunda parte que va desde la ultima posicion del archivo a enviar hasta el final. Con lo que el resultante de esta operación son dos archivos txt (si asi queremos guardarlo) sin el contenido del archivo a enviar.

Ahora sólo queda sustituir en ambas partes la información que nos interese tales como el nombre de archivo, el tamaño, la ubicación, y otros datos que se incluyen en el formulario web.

A continuación se carga en un TStreamMemory (SM1) la primera parte, en otro TStreamMemory (SM2) el contenido del archivo (SM2.LoadFromFile(archivo);) y en el ultimo (SM3) la última parte del log.

Se concatenan los tres TStreamMemory.

Se prepara la cabecera del TidHTTP en base al log que registramos (idhttp1.Request.Connection, IdHTTP1.Request.ContentType, etc...) y finalmente se envían con TidHTTP.Post()

El resultado es satisfactorio al menos en mi caso. Es un procedimiento un poco enrevesado pero al menos puede ayudarnos a sacarnos del atasco cuando no sabemos que script posee el servidor ni sabemos de que manera procesa la información que le enviamos.

Saludos y espero que esto saque a más de uno de un aprieto.