HttpListener how to serve images - c#

I'm making a simple webserver to serve html, css, js & images (done in c#). I am using HttpListener and I can get the html, javascript and css files to work properly. I am just having trouble with the images. This is what I'm using currently:
if (request.RawUrl.ToLower().Contains(".png") || request.RawUrl.Contains(".ico") || request.RawUrl.ToLower().Contains(".jpg") || request.RawUrl.ToLower().Contains(".jpeg"))
{
string dir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string[] img = request.RawUrl.Split('/');
string path = dir + #"\public\imgs\" + img[img.Length - 1];
FileInfo fileInfo = new FileInfo(path);
long numBytes = fileInfo.Length;
FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
BinaryReader binaryReader = new BinaryReader(fileStream);
byte[] output = binaryReader.ReadBytes((int)numBytes);
binaryReader.Close();
fileStream.Close();
var temp = System.Text.Encoding.UTF8.GetString(output);
return temp;
}
I am converting the image into a string to return them (it's the way my boss suggested). This is the method where I am handling these requests.
private static string SendResponse(HttpListenerRequest request)
This is my WebServer classes Run() method. The call to SetContentType just goes through the request.RawUrl and determines the content type.
public void Run()
{
ThreadPool.QueueUserWorkItem((o) =>
{
Console.WriteLine("StackLight Web Server is running...");
try
{
while (_listener.IsListening)
{
ThreadPool.QueueUserWorkItem((c) =>
{
var ctx = c as HttpListenerContext;
try
{
// store html content in a byte array
string responderString = _responderMethod(ctx.Request);
// set the content type
ctx.Response.Headers[HttpResponseHeader.ContentType] = SetContentType(ctx.Request.RawUrl);
byte[] buffer = buffer = Encoding.UTF8.GetBytes(responderString);
// this writes the html out from the byte array
ctx.Response.ContentLength64 = buffer.Length;
using(Stream stream = ctx.Response.OutputStream)
{
stream.Write(buffer, 0, buffer.Length);
}
}
catch (Exception ex)
{
ConfigLogger.Instance.LogCritical(LogCategory, ex);
}
}, _listener.GetContext());
}
}
catch (Exception ex)
{
ConfigLogger.Instance.LogCritical(LogCategory, ex);
}
});
}
My html page needs to display an image to the screen, it displays a broken image so far. I know the images directory is correct, I tested that.
This is where I got my code for the webserver: here
I was thinking that maybe I have to change the SendResponse method to not return a string

I figured it out. I created a class to hold the data, content type and the request.RawUrl. Then, where I was passing a string, I changed it to pass the object I created.
So, for my WebServer class, my Run method looks like this:
public void Run()
{
ThreadPool.QueueUserWorkItem((o) =>
{
Console.WriteLine("StackLight Web Server is running...");
try
{
while (_listener.IsListening)
{
ThreadPool.QueueUserWorkItem((c) =>
{
var ctx = c as HttpListenerContext;
try
{
// set the content type
ctx.Response.Headers[HttpResponseHeader.ContentType] = SetContentType(ctx.Request.RawUrl);
WebServerRequestData data = new WebServerRequestData();
// store html content in a byte array
data = _responderMethod(ctx.Request);
string res = "";
if(data.ContentType.Contains("text"))
{
char[] chars = new char[data.Content.Length/sizeof(char)];
System.Buffer.BlockCopy(data.Content, 0, chars, 0, data.Content.Length);
res = new string(chars);
data.Content = Encoding.UTF8.GetBytes(res);
}
// this writes the html out from the byte array
ctx.Response.ContentLength64 = data.Content.Length;
ctx.Response.OutputStream.Write(data.Content, 0, data.Content.Length);
}
catch (Exception ex)
{
ConfigLogger.Instance.LogCritical(LogCategory, ex);
}
finally
{
ctx.Response.OutputStream.Close();
}
}, _listener.GetContext());
}
}
catch (Exception ex)
{
ConfigLogger.Instance.LogCritical(LogCategory, ex);
}
});
}
And my SendResponse method looks like this:
private static WebServerRequestData SendResponse(HttpListenerRequest request)
{
string dir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string[] fileUrl = request.RawUrl.Split('/');
// routes
if (request.RawUrl.Contains("/"))
{
// this is the main page ('/'), all other routes can be accessed from here (including css, js, & images)
if (request.RawUrl.ToLower().Contains(".png") || request.RawUrl.ToLower().Contains(".ico") || request.RawUrl.ToLower().Contains(".jpg") || request.RawUrl.ToLower().Contains(".jpeg"))
{
try
{
string path = dir + Properties.Settings.Default.ImagesPath + fileUrl[fileUrl.Length - 1];
FileInfo fileInfo = new FileInfo(path);
path = dir + #"\public\imgs\" + fileInfo.Name;
byte[] output = File.ReadAllBytes(path);
_data = new WebServerRequestData() {Content = output, ContentType = "image/png", RawUrl = request.RawUrl};
//var temp = System.Text.Encoding.UTF8.GetString(output);
//return Convert.ToBase64String(output);
return _data;
}
catch(Exception ex)
{
ConfigLogger.Instance.LogError(LogCategory, "File could not be read.");
ConfigLogger.Instance.LogCritical(LogCategory, ex);
_errorString = string.Format("<html><head><title>Test</title></head><body>There was an error processing your request:<br />{0}</body></html>", ex.Message);
_byteData = new byte[_errorString.Length * sizeof(char)];
System.Buffer.BlockCopy(_errorString.ToCharArray(), 0, _byteData, 0, _byteData.Length);
_data = new WebServerRequestData() { Content = _byteData, ContentType = "text/html", RawUrl = request.RawUrl };
return _data;
}
}
I'm still cleaning up the code a bit but it now serves the images!
Oh... And here is the object I'm using:
public class WebServerRequestData
{
public string RawUrl { get; set; }
public string ContentType { get; set; }
public byte[] Content { get; set; }
public string RawData { get; set; }
}

Some really bad stuff here:
Empty catch. You'll never find out about many bugs.
Stuffing binary data into a string. Why? There's no encoding that is able to roundtrip binary data.
You're not disposing of ctx. I don't see why you need a manual finally block. Use using.
Untrusted callers can inject arbitrary paths into path. I could request your web.config file by navigating to /img/..\..\web.config (something like that).
Consider factoring out some common expressions into variables. You've got a Copy&Paste error with ToLower. Don't do dirty stuff and you'll have less bugs.

Related

ASP.NET Core 5 web api duplicates file name on save

I have an API written in asp.net core 5. There I have a controller that receive a IFile object. Everything is fine till I need on the end of the process to save the file on the local hard drive. There my path named formatedPath is correct and it contains the full path (like: ‪D:/tanulmanyiversenyek.hu/server/profile/8226dfa5-2743aiE2-45cfpTjG-b186yENr-167280a987aa.png).
But when the save is called I god an exception with error message:
D:\tanulmanyiversenyek.hu\server\profile of the filename ‪D:/tanulmanyiversenyek.hu/server/profile/8226dfa5-2743aiE2-45cfpTjG-b186yENr-167280a987aa.png
The bolder part is added somehow. I can't figure out why.
[Route("api/v{version:apiVersion}/image/profile")]
[ApiVersion(ApplicationSettingsConstans.ActiveVersion)]
[HttpPost]
[AllowAnonymous]
[ProducesResponseType((int)HttpResponseType.OK, Type = typeof(string))]
[ProducesResponseType((int)HttpResponseType.BadRequest)]
[Produces("application/json")]
public async Task<string> UploadProfileImageAsync([FromForm] IFormFile file)
{
ServiceResponse<string> request = await _imageUploadService.UploadProfileImageAsync(file);
if (!request.IsSuccess)
{
throw new Exception(request.Message);
}
return request.ResultObject;
}
public async Task<ServiceResponse<string>> UploadProfileImageAsync(IFormFile file)
{
ServiceResponse<string> response = new ServiceResponse<string>();
try
{
response.ResultObject = await ImageUploadHandler.SaveProfileImageAsync(file);
}
catch (Exception ex)
{
response.IsSuccess = false;
response.Message = ex.Message;
response.ResultObject = string.Empty;
}
return response;
}
public static async Task<string> SaveProfileImageAsync(IFormFile file)
{
if (file.Length < 1)
{
return null;
}
string result = string.Empty;
string extension = file.FileName.Reverse().Split(".")[0].Reverse();
string fileName = $"{GenerateName()}.{extension}";
string subFolder = UploadedFileType.Profile.ToString().ToLower();
#if DEBUG
string path = $#"{ServerPathEnviorement.Base()}\{subFolder}\{fileName}";
result = $#"\{subFolder}\{fileName}";
#else
string path = $#"{ServerPathEnviorement.Base()}/{subFolder}/{fileName}";
result = $#"/{subFolder}/{fileName}";
#endif
await SaveOnDisk(path, file, 720, 405);
return result;
}
private static async Task SaveOnDisk(string path, IFormFile file, int width = 1920, int height = 1080)
{
string formatedPath = path.Replace(#"/", #"\");
Image image = null;
using (MemoryStream memoryStream = new MemoryStream())
{
await file.CopyToAsync(memoryStream);
image = Image.FromStream(memoryStream);
}
using (Bitmap bitmap = new Bitmap(image))
{
byte[] data = ImageCompressor.ScaleImage(bitmap, width, height, false);
using (MemoryStream memoryStream = new MemoryStream(data))
{
image = Image.FromStream(memoryStream);
image.Save(formatedPath, ImageFormat.Jpeg); *//error thrown here*
}
}
The same error appears form Postman or from my react app.
Interesting think is if I repleace the formatedPath variable with direct path to file like: d:/tanulmanyiversenyek.hu/server/profile/167280a987aa.png it is working !

Read large files - 2GB+ for Google Drive API Upload

I'm currently working on a small backup tool written in C# that is supposed to upload files contained within a specified folder to Google Drive via its API. The program largely functions as it's supposed to, the only problem that it is unable to handle files larger than 2GB.
The problem is caused by the upload function itself which is attached down below, it uses a byte array to read the file to subsequently create a Memory Stream. As far as I'm aware (I'm still a beginner when it comes to c#), a byte array can only contain 2GB of information before returning an overflow exception. To combat this I've tried to utilize FileStream.Read (second bit of code attached below) instead of System.IO.File.ReadAllBytes, though this again lead to an overflow exception of the byte Array. I know that at this point I'd have to split the file up, however, due to the rather limited documentation of the GDrive API for C# - at least from what I've seen - and my limited knowledge of C# I've got little to no clue on how to tackle this problem.
I'm sorry for the long read, all help on this matter is highly appreciated.
Upload Function V1 (System.IO.File.ReadAllBytes):
private static Google.Apis.Drive.v3.Data.File UploadFile(Boolean useFolder, String mime, DriveService _service, string _uploadFile, string _parent, string _descrp = "")
{
if (System.IO.File.Exists(_uploadFile))
{
Google.Apis.Drive.v3.Data.File body = new Google.Apis.Drive.v3.Data.File
{
Name = System.IO.Path.GetFileName(_uploadFile),
Description = _descrp,
MimeType = mime
};
if (useFolder)
{
body.Parents = new List<string> { _parent };
}
byte[] byteArray = System.IO.File.ReadAllBytes(_uploadFile);
MemoryStream stream = new System.IO.MemoryStream(byteArray);
try
{
FilesResource.CreateMediaUpload request = _service.Files.Create(body, stream, mime);
request.SupportsTeamDrives = true;
request.Upload();
return request.ResponseBody;
}
catch (Exception e)
{
Console.WriteLine("Error Occured: " + e);
return null;
}
}
else
{
Console.WriteLine("The file does not exist. 404");
return null;
}
}
Upload Method V2 (FileStream):
private static Google.Apis.Drive.v3.Data.File UploadFile(Boolean useFolder, String mime, DriveService _service, string _uploadFile, string _parent, string _descrp = "")
{
if (System.IO.File.Exists(_uploadFile))
{
Google.Apis.Drive.v3.Data.File body = new Google.Apis.Drive.v3.Data.File
{
Name = System.IO.Path.GetFileName(_uploadFile),
Description = _descrp,
MimeType = mime
};
if (useFolder)
{
body.Parents = new List<string> { _parent };
}
//byte[] byteArray = System.IO.File.ReadAllBytes(_uploadFile);
using (FileStream fileStream = new FileStream(_uploadFile, FileMode.Open, FileAccess.Read))
{
Console.WriteLine("ByteArrayStart");
byte[] byteArray = new byte[fileStream.Length];
int bytesToRead = (int)fileStream.Length;
int bytesRead = 0;
while (bytesRead > 0)
{
int n = fileStream.Read(byteArray, bytesRead, bytesToRead);
if (n == 0)
{
break;
}
bytesRead += n;
Console.WriteLine("Bytes Read: " + bytesRead);
bytesToRead -= n;
Console.WriteLine("Bytes to Read: " + bytesToRead);
}
bytesToRead = byteArray.Length;
MemoryStream stream = new System.IO.MemoryStream(byteArray);
try
{
FilesResource.CreateMediaUpload request = _service.Files.Create(body, stream, mime);
request.SupportsTeamDrives = true;
request.Upload();
return request.ResponseBody;
}
catch (Exception e)
{
Console.WriteLine("Error Occured: " + e);
return null;
}
}
}
else
{
Console.WriteLine("The file does not exist. 404");
return null;
}
}
MemoryStream's constructors only work with byte arrays that are limited to Int32.MaxValue bytes. Why not just use your FileStream object directly?
var fileMetadata = new Google.Apis.Drive.v3.Data.File()
{
Name = "flag.jpg"
};
FilesResource.CreateMediaUpload request;
using (var stream = new System.IO.FileStream(#"C:\temp\flag.jpg", System.IO.FileMode.Open))
{
request = service.Files.Create(fileMetadata, stream, "image/jpeg");
request.Fields = "id";
request.Upload();
}
var file = request.ResponseBody;
Really a file that big you should be using resumable upload but im going to have to dig around for some sample code for that.

Using FileStream to write to a file first time after the file is created gives an exception

I am trying to write some json text. But I get an Exception like
The process cannot access the file C:\blah blah\SystemInActivity.json because it is being used by an other process. But then second time when I run the app after json file is created and then when I write I dont get an exception. Please help.
class ApplicationSettingsViewModel
{
ApplicationSettingsModel model;
MemoryMappedFile mmf = null;
public string FullPath = string.Empty;
//This is not a singleton class but I guess it has to be one but its ok for demonstration.
public ApplicationSettingsViewModel()
{
model = new ApplicationSettingsModel();
CreateFileWithoutMemoryMap();
//MemoryMapped();
}
public string GetDriectory()
{
return Path.GetDirectoryName(FullPath);
}
private void CreateFileWithoutMemoryMap()
{
var info = Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "/" + model.Data.Settings.OrcaUISpecificSettings.TimeOutFolder);
string path = Path.Combine(info.FullName + #"\" + model.Data.Settings.OrcaUISpecificSettings.File);
//mmf = MemoryMappedFile.CreateFromFile(path, FileMode.CreateNew, "MyMemoryFile", 1024 * 1024, MemoryMappedFileAccess.ReadWrite);
FullPath = path;
if (!File.Exists(path))
{
File.Create(path);
}
}
public void WriteToFile(string json)
{
try
{
FileStream fileStream = File.Open(FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); //This line giving Exception
fileStream.SetLength(0);
fileStream.Close(); // This flushes the content, too.
using (StreamWriter sw = new StreamWriter(FullPath))
{
sw.Write(json);
}
}
catch (Exception ex)
{
}
}
In the constructor of the MainWindow I am calling the write method
private ApplicationSettingsViewModel AppViewModel;
public MainWindow()
{
InitializeComponent();
//MessageBox.Show("App Started");
AppViewModel = new ApplicationSettingsViewModel();
WriteToFile("Active");
}
public void WriteToFile(string status)
{
var root = new Root();
string jsonString = string.Empty;
root.AllApplications.Add(new DataToWrite() { AppName = "DevOrca", Status = status });
try
{
jsonString = JsonConvert.SerializeObject(root, Formatting.Indented);
}
catch (Exception ex)
{
MessageBox.Show(jsonString);
MessageBox.Show("Exception");
}
mutex.WaitOne();
//Serialize Contents and write
AppViewModel.WriteToFile(jsonString);
//var access = AppViewModel.GetAccessor();
//byte[] bytes = Encoding.ASCII.GetBytes(jsonString);
//access.Write(bytes, 0, bytes.Length);
mutex.ReleaseMutex();
}
File.Create() method opens FileStream to create a file and you need to close it, something like this:
File.Create(path).Close();

C# get text from .txt file that's added to the program

In my project i added a .txt file. I need to get the content inside of it, line after line, and slip the lines. I alreaddy have code to slipt the lines and overall handle the content of the .txt file like i want to, ill i need is to acess the content of the added file.
The code i have to handle the text form a txt file in the computer:
public static string[] loc_file = File.ReadAllLines(AppDomain.CurrentDomain.BaseDirectory + "\\loc.txt", Encoding.UTF8);
public static string loc_up = string.Join("|", loc_file);
public static string[] loc_p = loc_up.Split('|');
public static string[] loc = loc_p.Where((c, i) => i % 2 == 0).ToArray<string>();
public static string[] loc_txt = loc_p.Where((c, i) => i % 2 != 0).ToArray<string>();
now, how i think the code for what i need will be:
public string exePath = Application.StartupPath.ToString() + "\\loc.txt";
Stream stream = GetType().Assembly.GetManifestResourceStream(namexe);
string[] a = GetType().Assembly.GetManifestResourceNames();
byte[] bytes = new byte[(int)stream.Length];
stream.Read(bytes, 0, bytes.Length);
File.WriteAllBytes(exePath, bytes);
and then just read the text from the file.
thanks!
EDIT 1:
im making this code on my own, not sure if it will work but ill post annyways, if it ends out working i might be helping someone:
public bool get_file(string file)
{
string filePath = Application.StartupPath.ToString() + file;
if (File.Exists(filePath))
{
try
{
Stream stream = GetType().Assembly.GetManifestResourceStream(file);
string[] a = GetType().Assembly.GetManifestResourceNames();
byte[] bytes = new byte[(int)stream.Length];
stream.Read(bytes, 0, bytes.Length);
File.WriteAllBytes(filePath, bytes);
return true;
}
catch { return false; }
}
else { return false; }
}
EDIT 2:
I just realized that string filePath = Application.StartupPath.ToString() + file;
if (File.Exists(filePath))
will give me error because there is no file at the strat of the , lets call it cycle. So ill remove the part to see if file exists cause it makes no sence, leaving the code to be:
public bool get_file(string file)
{
string filePath = Application.StartupPath.ToString() + file;
try
{
Stream stream = GetType().Assembly.GetManifestResourceStream(file);
string[] a = GetType().Assembly.GetManifestResourceNames();
byte[] bytes = new byte[(int)stream.Length];
stream.Read(bytes, 0, bytes.Length);
File.WriteAllBytes(filePath, bytes);
return true;
}
catch { return false; }
}

Unzipping a file error

I am using the SharpZipLib open source .net library from www.icsharpcode.net
My goal is to unzip an xml file and read it into a dataset. However I get the following error reading the file into a dataset: "Data at the root level is invalid. Line 1, position 1."
I believe what is happening is the unzipping code is not releasing the file for the following reasons.
1.) If I unzip the file and exit the application. When I restart the app I CAN read the unzipped file into a dataset.
2.) If I read in the xml file right after writing it out (no zipping) then it works fine.
3.) If I write the dataset to xml, zip it up, unzip it, then attempt to read it back in I get the exception.
The code below is pretty straight forward. UnZipFile will return the name of the file just unzipped. Right below this call is the call to read it into a dataset. The variable fileToRead is the full path to the newly unzipped xml file.
string fileToRead = UnZipFile(filepath, DOViewerUploadStoreArea);
ds.ReadXml(fileToRead )
private string UnZipFile(string file, string dirToUnzipTo)
{
string unzippedfile = "";
try
{
ZipInputStream s = new ZipInputStream(File.OpenRead(file));
ZipEntry myEntry;
string tmpEntry = String.Empty;
while ((myEntry = s.GetNextEntry()) != null)
{
string directoryName = dirToUnzipTo;
string fileName = Path.GetFileName(myEntry.Name);
string fileWDir = directoryName + fileName;
unzippedfile = fileWDir;
FileStream streamWriter = File.Create(fileWDir);
int size = 4096;
byte[] data = new byte[4096];
while (true)
{
size = s.Read(data, 0, data.Length);
if (size > 0) { streamWriter.Write(data, 0, size); }
else { break; }
}
streamWriter.Close();
}
s.Close();
}
catch (Exception ex)
{
LogStatus.WriteErrorLog(ex, "ERROR", "DOViewer.UnZipFile");
}
return (unzippedfile);
}
Well, what does the final file look like? (compared to the original). You don't show the zipping code, which might be part of the puzzle, especially as you are partially swallowing the exception.
I would also try ensuring everything IDisposable is Dispose()d, ideally via using; also - in case the problem is with path construction, use Path.Combine. And note that if myEntry.Name contains sub-directories, you will need to create them manually.
Here's what I have - it works for unzipping ICSharpCode.SharpZipLib.dll:
private string UnZipFile(string file, string dirToUnzipTo)
{
string unzippedfile = "";
try
{
using(Stream inStream = File.OpenRead(file))
using (ZipInputStream s = new ZipInputStream(inStream))
{
ZipEntry myEntry;
byte[] data = new byte[4096];
while ((myEntry = s.GetNextEntry()) != null)
{
string fileWDir = Path.Combine(dirToUnzipTo, myEntry.Name);
string dir = Path.GetDirectoryName(fileWDir);
// note only supports a single level of sub-directories...
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
unzippedfile = fileWDir; // note; returns last file if multiple
using (FileStream outStream = File.Create(fileWDir))
{
int size;
while ((size = s.Read(data, 0, data.Length)) > 0)
{
outStream.Write(data, 0, size);
}
outStream.Close();
}
}
s.Close();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return (unzippedfile);
}
It could also be that the problem is either in the code that writes the zip, or the code that reads the generated file.
I compared the original with the final using TextPad and they are identical.
Also I rewrote the code to take advantage of the using. Here is the code.
My issue seems to be centered around file locking or something. If I unzip the file quit the application then start it up it will read find.
private string UnZipFile(string file, string dirToUnzipTo)
{
string unzippedfile = "";
try
{
using (ZipInputStream s = new ZipInputStream(File.OpenRead(file)))
{
ZipEntry theEntry;
while ((theEntry = s.GetNextEntry()) != null)
{
string directoryName = dirToUnzipTo;
string fileName = Path.GetFileName(theEntry.Name);
string fileWDir = directoryName + fileName;
unzippedfile = fileWDir;
if (fileName != String.Empty)
{
using (FileStream streamWriter = File.Create(fileWDir))
{
int size = 2048;
byte[] data = new byte[2048];
while (true)
{
size = s.Read(data, 0, data.Length);
if (size > 0)
{
streamWriter.Write(data, 0, size);
}
else
{
break;
}
}
}
}
}
}
}
catch (Exception ex)
{
LogStatus.WriteErrorLog(ex, "ERROR", "DOViewer.UnZipFile");
}
return (unzippedfile);
}
This is a lot simpler to do with DotNetZip.
using (ZipFile zip = ZipFile.Read(ExistingZipFile))
{
zip.ExtractAll(TargetDirectory);
}
If you want to decide on which files to extract ....
using (ZipFile zip = ZipFile.Read(ExistingZipFile))
{
foreach (ZipEntry e in zip)
{
if (wantThisFile(e.FileName)) e.Extract(TargetDirectory);
}
}
If you would like to overwrite existing files during extraction:
using (ZipFile zip = ZipFile.Read(ExistingZipFile))
{
zip.ExtractAll(TargetDirectory, ExtractExistingFileAction.OverwriteSilently);
}
Or, to extract password-protected entries:
using (ZipFile zip = ZipFile.Read(ExistingZipFile))
{
zip.Password = "Shhhh, Very Secret!";
zip.ExtractAll(TargetDirectory, ExtractExistingFileAction.OverwriteSilently);
}

Categories

Resources