How to extract zipped file received from HttpWebResponse? - c#

I put url to browser's address bar and it downloads the zip file to HD. The size of zipped file is 386 bytes as written in its properties.
When I use UnZipFiles method to extract the file - it works.
But, I want to download programaticaly and extract it in memory. I use GetResultFromServer method to get zipped content. As shown in headers the size of the content is the same as the size of zipped file saved on HD:
content-disposition: attachment; filename=emaillog-xml.zip
Content-Length: 386
Cache-Control: private
Content-Type: application/zip
Date: Mon, 10 Sep 2012 08:28:28 GMT
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
My question is how to extract the content returned by GetResultFromServer?
I tried the following:
var ms = new MemoryStream(Encoding.Unicode.GetBytes(res))
var s = new ZipInputStream(ms);
but I get Unable to read from this stream.
UPDATED
I tried var zipStream = new System.IO.Compression.GZipStream(response.GetResponseStream(), CompressionMode.Decompress) but I get The magic number in GZip header is not correct error
Code
private string GetResultFromServer(ElasticLogParams elasticLogParams)
{
var webRequest = (HttpWebRequest)WebRequest.Create(url);
var response = webRequest.GetResponse();
using (var reader = new StreamReader(response.GetResponseStream()))
{
var res = reader.ReadToEnd();
var headers = response.Headers.ToString();
return res;
}
}
public static void UnZipFiles(string zippedFilePath, Stream stream = null)
{
var s = new ZipInputStream(stream ?? File.OpenRead(zippedFilePath));
ZipEntry theEntry;
while ((theEntry = s.GetNextEntry()) != null)
{
using (var streamWriter = File.Create(#"D:\extractedXML.xml"))
{
var size = 2048;
var data = new byte[size];
while (true)
{
size = s.Read(data, 0, size);
if (size > 0)
{
streamWriter.Write(data, 0, size);
}
else
{
break;
}
}
streamWriter.Close();
}
}
s.Close();
}

Give this a shot:
var response = webRequest.GetResponse() as HttpWebResponse;
var stream = response.GetResponseStream();
var s = new ZipInputStream(stream);
I believe you're very close and that you're using the right approach -- you can use this article to back that up -- their code is very similar.

I'm using http://dotnetzip.codeplex.com/
I've not used it to download stuff, but to extract stuff that people upload to my server. I assume it should work perfectly the other way round too.
I've tried the built-in zip from microsoft too, but also had issues. So I gave it up and switched.

Related

c# Web Server - How to send images

I'm creating a server using C# and I'm able to receive requests and return HTML, but I'm not sure what I have to do to send image files so they will display on the page.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;
namespace Ambar
{
class Program
{
static void Main(string[] args)
{
TcpListener listener = new TcpListener(80);
listener.Start();
while (true)
{
Console.WriteLine("waiting for a connection");
TcpClient client = listener.AcceptTcpClient();
StreamReader sr = new StreamReader(client.GetStream());
StreamWriter sw = new StreamWriter(client.GetStream());
Console.WriteLine(client.GetStream().ToString());
try
{
string request = sr.ReadLine();
Console.WriteLine(request);
string[] tokens = request.Split(' ');
string page = tokens[1];
if (page == "/")
{
page = "/default.htm";
}
StreamReader file = new StreamReader("../../web" + page);
sw.WriteLine("HTTP/1.0 200 OK\n");
string data = file.ReadLine();
while (data != null)
{
sw.WriteLine(data);
sw.Flush();
data = file.ReadLine();
}
}
catch (Exception e)
{
sw.WriteLine("HTTP/1.0 404 OK\n");
sw.WriteLine("<H1> Future Site of Ambar Remake </H!>");
sw.Flush();
}
client.Close();
}
}
}
I'm able to host whatever HTML I want, but if I try to display an image like
<img src="picture.gif" alt="a picture" height="42" width="42">
I'm not sure how to host that image and have it displayed there.
I assume you are using web sockets.
You'll need to return the image as a base 64 encoded string, then use the
<img src ="data:image/png;base64," + base64ImageHere
Format
HTTP request and response header have two line breaks(\n) to divide Header part and content part.
for example:
(Request that requesting an image(image.gif) from server)
GET /image.gif HTTP/1.1
User-Agent: WebBrowser
Accept-Encoding: deflate,gzip
Connection: keep-alive
... and more headers if present ...
(Response for image request)
HTTP/1.1 200 OK
Content-Length: <Length_Of_Content>
Content-Type: image/gif
... and more headers if present ...
<Data_Of_Image>
As you can see, there's two line breaks(\n) between response header and contents.
So, you have to read files as byte array(byte[]). In this situation, you can easily read files using System.IO.File.ReadAllBytes(string).
Now, one more left. I told you read files as byte array, but there's no way to join string and byte[]. So you have to encode string to byte[] and join header bytes and content bytes.
+ We can't send byte array via StreamWriter so, we'll use NetworkStream.
Here's explanation:
NetworkStream ns = client.GetStream();
...
string[] tokens = request.Split(' ');
string page = tokens[1];
if (page == "/")
{
page = "/default.htm";
}
//StreamReader file = new StreamReader("../../web" + page);
byte[] file = null;
try { file = File.ReadAllBytes(#"../../web" + page); }
// In this 'catch' block, you can't read requested file.
catch {
// do something (like printing error message)
}
// We are not going to use StreamWriter, we'll use StringBuilder
StringBuilder sbHeader = new StringBuilder();
// STATUS CODE
sbHeader.AppendLine("HTTP/1.1 200 OK");
// CONTENT-LENGTH
sbHeader.AppendLine("Content-Length: " + file.Length);
// Append one more line breaks to seperate header and content.
sbHeader.AppendLine();
// List for join two byte arrays.
List<byte> response = new List<byte>();
// First, add header.
response.AddRange(Encoding.ASCII.GetBytes(sbHeader.ToString()));
// Last, add content.
response.AddRange(file);
// Make byte array from List<byte>
byte[] responseByte = response.ToArray();
// Send entire response via NetworkStream
ns.Write(responseByte, 0, responseByte.Length);
This is it. I hope you can understand my bad english :O.
Hope this help to you!

Add trailer in HttpWebRequest using c#

I am trying to add trailer in HttpWebRequest header, but it is not appending that trailer after end of file data.
wreq.ContentType = "application/octet-stream";
wreq.AllowWriteStreamBuffering = false;
wreq.SendChunked = true;
//wreq.Headers.Add(HttpRequestHeader.Te, "trailers");
wreq.Headers.Add(HttpRequestHeader.Trailer, "Test");
wreq.Headers["Test"] = "the-value";
using (Stream POSTstream = wreq.GetRequestStream())
{
//dataByte is file-data in byte[]
POSTstream.Write(dataByte, 0, dataByte.Length);
POSTstream.Flush();
//hashValue is trailer in byte[]
POSTstream.Write(hashValue, 0, hashValue.Length);
POSTstream.Flush();
POSTstream.Close();
}
it should append this trailer "Test" # EOF after blank chunk, but it doesn't append it. when i tried to add trailer programatically it consider it as file data rather than trailer.
Expected request:
POST <URL> HTTP/1.1
Content-Type: application/octet-stream
Trailer: Test
Transfer-Encoding: chunked
5d
File-data
0
Test: the-value
Actual request:
POST <URL> HTTP/1.1
Content-Type: application/octet-stream
Trailer: Test
Transfer-Encoding: chunked
5d
File-data
5A
Test: the-value
0
Why this Test trailer is not getting after blank chunk. This trailer will be used on server to identify end of file.
Please Help.
After more than a week research I came to know that Dotnet does not allow to add trailer in httprequest. To achieve above expected request I have used Node.js. For that,
First I have create app.js file which contains code for creating request with trailer and get response from server:
var http = require('http');
var fs = require('fs');
var options = {hostname: 'ABC',port: 6688,path: 'XYZ/101/-3/test.file',method: 'POST',
headers: {'Content-type' : 'application/octet-stream','Trailer' : 'Test','Transfer-Encoding': 'chunked'}};
var fileContent = fs.readFileSync('C:\\test.file');
var req = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {fs.writeFile('C:\\response.xml', chunk);});});
req.on('error', function(e) {fs.writeFile('C:\\response.xml','Error: ' + e.message);});
var len = fileContent.length;
var bufSize = 4096;
for (var i = 0 ; i < len ; ) {
if (i+bufSize <len){req.write(fileContent.slice(i, i+bufSize));}
else{req.write(fileContent.slice(i,len));req.addTrailers({'Test': 'TESTTEST'});req.end();}i = i +bufSize;
}
And run this app.js file from dotnet as application and get the response:
//build app.js file content
var template = GetAppJSString();
var appjsfile = "C:\\app.js";
using (var sw = new StreamWriter(appjsfile))
{
sw.Write(template);
sw.Close();
}
var psi = new ProcessStartInfo
{
CreateNoWindow = true,
FileName = "C:\\Node.exe",
Arguments = "\"" + appjsfile + "\"",
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden
};
try
{
_nodeProcess.StartInfo = psi;
_nodeProcess.Start();
_nodeProcess.WaitForExit();
//read and process response
var responseText = string.Empty;
using (var sr = new StreamReader("C:\\response.xml"))
{
responseText = sr.ReadToEnd();
sr.Close();
}
File.Delete("C:\\response.xml");
// process response
}
catch (Exception ex) { }
Hope this will help others and save their time.

Downloading file from URL with no extension in C#

I'm trying to get the zip file out of this link with C#:
http://dl.opensubtitles.org/en/download/sub/4860863
I've tried:
string ResponseText;
HttpWebRequest m = (HttpWebRequest)WebRequest.Create(o.link);
m.Method = WebRequestMethods.Http.Get;
using (HttpWebResponse response = (HttpWebResponse)m.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
ResponseText = reader.ReadToEnd();
// ResponseText = HttpUtility.HtmlDecode(ResponseText);
XmlTextReader xmlr = new XmlTextReader(new StringReader(ResponseText));
}
}
and
WebRequest request = WebRequest.Create(o.link);
using (WebResponse response = request.GetResponse())
using (Stream stream = response.GetResponseStream())
{
string contentType = response.ContentType;
// TODO: examine the content type and decide how to name your file
string filename = "test.zip";
// Download the file
using (Stream file = File.OpenWrite(filename))
{
// Remark: if the file is very big read it in chunks
// to avoid loading it into memory
byte[] buffer = new byte[response.ContentLength];
stream.Read(buffer, 0, buffer.Length);
file.Write(buffer, 0, buffer.Length);
}
}
But they all return something weird, nothing that looks like the file I need...
I think the link is php generated, but I'm not sure...
The opensubtitles api is no option for me...
Many thanks
It seems the Content-Type response is ok for me for your link:
Request URL:http://dl.opensubtitles.org/en/download/sub/4860863
Request Method:GET
Status Code:200 OK
Request Headersview:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*//*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Cookie:PHPSESSID=gk86hdrce96pu06kuajtue45a6; ts=1372177758
Host:dl.opensubtitles.org
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36
Response Headersview:
Accept-Ranges:bytes
Age:0
Cache-Control:must-revalidate, post-check=0, pre-check=0
Connection:keep-alive
Content-Disposition:attachment; filename="the.dark.knight.(2008).dut.1cd.(4860863).zip"
Content-Length:48473
Content-Transfer-Encoding:Binary
Content-Type:application/zip
Date:Tue, 25 Jun 2013 16:29:45 GMT
Expires:Mon, 1 Apr 2006 01:23:45 GMT
Pragma:public
Set-Cookie:ts=1372177785; expires=Thu, 25-Jul-2013 16:29:45 GMT; path=/
X-Cache:MISS
X-Cache-Backend:web1
I have check your code and test it using the link and the manual download produced a 48473 bytes file, and using your code produced 48564 bytes with zero after 0xDC2 and when I compared it with Hex editor, it have many different part. We may need to put more request header before sending the request.
ok, now i can resolve it: put cookie and read at a smaller chunk
private void button1_Click(object sender, EventArgs e) {
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("http://dl.opensubtitles.org/en/download/sub/4860863"));
//request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36";
//request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*//*;q=0.8";
//request.Headers["Accept-Encoding"] = "gzip,deflate,sdch";
request.Headers["Cookie"] = "PHPSESSID=gk86hdrce96pu06kuajtue45a6; ts=1372177758";
using (WebResponse response = request.GetResponse())
using (Stream stream = response.GetResponseStream()) {
string contentType = response.ContentType;
// TODO: examine the content type and decide how to name your file
string filename = "test.zip";
// Download the file
using (Stream file = File.OpenWrite(filename)) {
byte[] buffer = ReadFully(stream, 256);
stream.Read(buffer, 0, buffer.Length);
file.Write(buffer, 0, buffer.Length);
}
}
}
/// <summary>
/// Reads data from a stream until the end is reached. The
/// data is returned as a byte array. An IOException is
/// thrown if any of the underlying IO calls fail.
/// </summary>
/// <param name="stream">The stream to read data from</param>
/// <param name="initialLength">The initial buffer length</param>
public static byte[] ReadFully(Stream stream, int initialLength) {
// If we've been passed an unhelpful initial length, just
// use 32K.
if (initialLength < 1) {
initialLength = 32768;
}
byte[] buffer = new byte[initialLength];
int read = 0;
int chunk;
while ((chunk = stream.Read(buffer, read, buffer.Length - read)) > 0) {
read += chunk;
// If we've reached the end of our buffer, check to see if there's
// any more information
if (read == buffer.Length) {
int nextByte = stream.ReadByte();
// End of stream? If so, we're done
if (nextByte == -1) {
return buffer;
}
// Nope. Resize the buffer, put in the byte we've just
// read, and continue
byte[] newBuffer = new byte[buffer.Length * 2];
Array.Copy(buffer, newBuffer, buffer.Length);
newBuffer[read] = (byte)nextByte;
buffer = newBuffer;
read++;
}
}
// Buffer is now too big. Shrink it.
byte[] ret = new byte[read];
Array.Copy(buffer, ret, read);
return ret;
}
EDIT: You don't need to set Cookie at all, you'll produce a different file but a valid one. I assume the server add extra info to the file when you revisit them.

Upload a file from ZipOutputStream to MVC 3 action

I am using ZipOutputStream from SharpZipLib and I wish to upload the zipped contents it creates directly to my MVC post action. I am successfully getting it to post however the parameter of my action method has null as the posted data when it gets to my MVC action.
Here is my Test code I'm using to test this out:
public void UploadController_CanUploadTest()
{
string xml = "<test>xml test</test>"
string url = "http://localhost:49316/Api/DataUpload/Upload/";
WebClient client = new WebClient();
var cc= new CredentialCache();
cc.Add(new Uri(url),
"Basic",
new NetworkCredential("Testuser", "user"));
client.Credentials = cc;
string _UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)";
client.Headers.Add(HttpRequestHeader.UserAgent, _UserAgent);
client.Headers["Content-type"] = "application/x-www-form-urlencoded";
using (var stream = client.OpenWrite(url, "POST"))
{
Zipped zip = new Zipped(stream, Encoding.UTF8, false);
FileContent content = new FileContent("Upload", xml);
var uploads = new List<FileContent>();
uploads.Add(content);
zip.Compress(uploads);
stream.Flush();
stream.Close();
}
}
This is my zipped class wrapper:
public class Zipped : ICompression, IDisposable
{
private Stream _stream = null;
private bool _closeStreamOnDispose = true;
private Encoding _encoding;
public Zipped()
: this(new MemoryStream())
{
}
public Zipped(Stream stream)
: this(stream, Encoding.UTF8, true)
{
}
public Zipped(Stream stream, Encoding encoding)
: this(stream, encoding, true)
{
}
public Zipped(Stream stream, Encoding encoding, bool closeStreamOnDispose)
{
_stream = stream;
_closeStreamOnDispose = closeStreamOnDispose;
_encoding = encoding;
}
public Stream Compress(IList<FileContent> dataList)
{
ZipOutputStream outputStream = new ZipOutputStream(_stream);
outputStream.SetLevel(9);
foreach (var data in dataList)
{
ZipEntry entry = new ZipEntry(data.Name);
entry.CompressionMethod = CompressionMethod.Deflated;
outputStream.PutNextEntry(entry);
byte[] dataAsByteArray = _encoding.GetBytes(data.Content);
outputStream.Write(dataAsByteArray, 0, dataAsByteArray.Length);
outputStream.CloseEntry();
}
outputStream.IsStreamOwner = false;
outputStream.Flush();
outputStream.Close();
return _stream;
}
public List<FileContent> DeCompress()
{
ZipInputStream inputStream = new ZipInputStream(_stream);
ZipEntry entry = inputStream.GetNextEntry();
List<FileContent> dataList = new List<FileContent>();
while(entry != null)
{
string entryFileName = entry.Name;
byte[] buffer = new byte[4096]; // 4K is optimum
// Unzip file in buffered chunks. This is just as fast as unpacking to a buffer the full size
// of the file, but does not waste memory.
// The "using" will close the stream even if an exception occurs.
using (MemoryStream tempMemoryStream = new MemoryStream())
{
StreamUtils.Copy(inputStream, tempMemoryStream, buffer);
string copied = _encoding.GetString(tempMemoryStream.ToArray());
dataList.Add(new FileContent(entry.Name, copied));
}
entry = inputStream.GetNextEntry();
}
return dataList;
}
public void Dispose()
{
if(_closeStreamOnDispose)
_stream.Dispose();
}
Here is my simple MVC action:
[HttpPost]
public ActionResult Upload(HttpPostedFileBase uploaded)
{
// uploaded is null at this point
}
If you want to use HttpPostedFileBase in your controller action you need to send a multipart/form-data request from the client and not application/x-www-form-urlencoded.
In fact you set the content type to application/x-www-form-urlencoded but you are not respecting this because you are directly writing the raw bytes to the request which is invalid. Well, in fact it's not respecting the HTTP protocol standard but it could still work if you read the raw request stream from the controller instead of using HttpPostedFileBase. I wouldn't recommend you going that route.
So the correct HTTP request that you are sending must look like this:
Content-Type: multipart/form-data; boundary=AaB03x
--AaB03x
Content-Disposition: form-data; name="uploaded"; filename="input.zip"
Content-Type: application/zip
... byte contents of the zip ...
--AaB03x--
The boundary must be chosen so that it doesn't appear anywhere in the contents of the file.
I have blogged about an example of how you could upload multiple files.

Downloading a file, and I get Content-Length header is Invalid

I'm downloading a file, from a webserver, using the code below, and am recieving this error:
The Error:
Error saving file from URL:The server committed a protocol violation.
Section=ResponseHeader Detail='Content-Length' header value is invalid
From running Fiddler while this is running, it says:
Content-Length response header is not a valid unsigned integer
Content-Length: 13312583
The Code:
public static bool SaveFileFromURL(string url, string destinationFileName, int timeoutInSeconds)
{
//SetAllowUnsafeHeaderParsing20();
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
SettingsSection section = (SettingsSection)config.GetSection("system.net/settings");
section.HttpWebRequest.UseUnsafeHeaderParsing = false;
config.Save();
// Create a web request to the URL
HttpWebRequest MyRequest = (HttpWebRequest)WebRequest.Create(url);
MyRequest.UseDefaultCredentials = true;
MyRequest.ContentLength = 0;
MyRequest.Timeout = timeoutInSeconds * 1000;
try
{
// Get the web response
HttpWebResponse MyResponse = (HttpWebResponse)MyRequest.GetResponse();
// Make sure the response is valid
if (HttpStatusCode.OK == MyResponse.StatusCode)
{
// Open the response stream
using (Stream MyResponseStream = MyResponse.GetResponseStream())
{
// Open the destination file
using (FileStream MyFileStream = new FileStream(destinationFileName, FileMode.OpenOrCreate, FileAccess.Write))
{
// Create a 4K buffer to chunk the file
byte[] MyBuffer = new byte[4096];
int BytesRead;
// Read the chunk of the web response into the buffer
while (0 < (BytesRead = MyResponseStream.Read(MyBuffer, 0, MyBuffer.Length)))
{
// Write the chunk from the buffer to the file
MyFileStream.Write(MyBuffer, 0, BytesRead);
}
}
}
}
}
catch (Exception err)
{
throw new Exception("Error saving file from URL:" + err.Message, err);
}
return true;
}
Update: If I pass the URL straight into a browser, the file is downloaded successfully, and the error is thrown on the GetResponse line.
Update 2: I get the same error with WebClient.Downloadfile:
public static bool DL_Webclient(string url, string destinationFileName)
{
WebClient myWebClient = new WebClient();
myWebClient.UseDefaultCredentials = true;
myWebClient.DownloadFile(url, destinationFileName);
return true;
}
Update 3: Having retrieved the other headers in the message (using Fiddler), they are:
HTTP/1.1 200 OK
Connection: close
Date: Wed, 03 Mar 2010 08:43:06 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
Content-Length: 13314320
Content-Type: application/x-evsaveset
Set-Cookie: ASPSESSIONIDQQCQSCRC=CFHHJHADOIBCFAFOHFJCDNEG; path=/
Cache-control: private
Are there other HTTP headers present?
It might have to do with the 'Transfer-Encoding' header or similar rules. For more specific information about these headers and their effect on the Content-Length header can be found at the W3C website and More W3C website
Hope this helps,
Could you not use WebClient.DownloadFile instead?

Categories