342 lines
8.3 KiB
C#
342 lines
8.3 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Security;
|
|
using System.Web;
|
|
using System.Web.UI;
|
|
|
|
namespace FileSharePortal
|
|
{
|
|
public partial class Download : Page
|
|
{
|
|
private const string ShareRoot =
|
|
@"\\LAN-DC1\Fileshare";
|
|
|
|
|
|
protected void Page_Load(
|
|
object sender,
|
|
EventArgs e)
|
|
{
|
|
/*
|
|
* Kein Caching.
|
|
*/
|
|
Response.Cache.SetCacheability(
|
|
HttpCacheability.NoCache);
|
|
|
|
Response.Cache.SetNoStore();
|
|
|
|
|
|
string relativePath =
|
|
Request.QueryString["path"];
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(relativePath))
|
|
{
|
|
Response.StatusCode = 400;
|
|
CompleteResponse();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
string physicalPath;
|
|
|
|
|
|
try
|
|
{
|
|
physicalPath =
|
|
GetSafePhysicalFilePath(
|
|
relativePath);
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
Response.StatusCode = 403;
|
|
CompleteResponse();
|
|
|
|
return;
|
|
}
|
|
catch (SecurityException)
|
|
{
|
|
Response.StatusCode = 403;
|
|
CompleteResponse();
|
|
|
|
return;
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
Response.StatusCode = 404;
|
|
CompleteResponse();
|
|
|
|
return;
|
|
}
|
|
catch (DirectoryNotFoundException)
|
|
{
|
|
Response.StatusCode = 404;
|
|
CompleteResponse();
|
|
|
|
return;
|
|
}
|
|
catch
|
|
{
|
|
Response.StatusCode = 400;
|
|
CompleteResponse();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* Die Datei muss existieren.
|
|
*/
|
|
if (!File.Exists(physicalPath))
|
|
{
|
|
Response.StatusCode = 404;
|
|
CompleteResponse();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* ====================================================
|
|
* ZUGRIFF DES AKTUELL ANGEMELDETEN BENUTZERS PRÜFEN
|
|
* ====================================================
|
|
*
|
|
* Wegen
|
|
*
|
|
* <identity impersonate="true" />
|
|
*
|
|
* läuft dieser Zugriff unter dem angemeldeten
|
|
* AD-Benutzer.
|
|
*/
|
|
try
|
|
{
|
|
using (
|
|
FileStream stream =
|
|
new FileStream(
|
|
physicalPath,
|
|
FileMode.Open,
|
|
FileAccess.Read,
|
|
FileShare.ReadWrite |
|
|
FileShare.Delete))
|
|
{
|
|
/*
|
|
* Datei erfolgreich geöffnet.
|
|
*
|
|
* Es wird hier absichtlich nichts gelesen.
|
|
* Das Öffnen reicht als Berechtigungsprüfung.
|
|
*/
|
|
}
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
Response.StatusCode = 403;
|
|
CompleteResponse();
|
|
|
|
return;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
Response.StatusCode = 403;
|
|
CompleteResponse();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* ====================================================
|
|
* DOWNLOAD AUSLIEFERN
|
|
* ====================================================
|
|
*/
|
|
|
|
string fileName =
|
|
Path.GetFileName(
|
|
physicalPath);
|
|
|
|
|
|
string encodedFileName =
|
|
Uri.EscapeDataString(
|
|
fileName);
|
|
|
|
|
|
string contentType =
|
|
MimeMapping.GetMimeMapping(
|
|
physicalPath);
|
|
|
|
|
|
Response.Clear();
|
|
|
|
Response.Buffer = true;
|
|
|
|
Response.ContentType =
|
|
contentType;
|
|
|
|
|
|
/*
|
|
* UTF-8-Dateiname.
|
|
*
|
|
* Funktioniert auch für:
|
|
*
|
|
* Müller.pdf
|
|
* Änderungen.docx
|
|
* Köln.xlsx
|
|
* Rechnung März 2026.pdf
|
|
*/
|
|
Response.AddHeader(
|
|
"Content-Disposition",
|
|
"attachment; filename*=UTF-8''" +
|
|
encodedFileName
|
|
);
|
|
|
|
|
|
/*
|
|
* Dateigröße setzen.
|
|
*/
|
|
FileInfo fileInfo =
|
|
new FileInfo(
|
|
physicalPath);
|
|
|
|
|
|
Response.AddHeader(
|
|
"Content-Length",
|
|
fileInfo.Length.ToString()
|
|
);
|
|
|
|
|
|
/*
|
|
* Datei übertragen.
|
|
*/
|
|
Response.TransmitFile(
|
|
physicalPath);
|
|
|
|
|
|
CompleteResponse();
|
|
}
|
|
|
|
|
|
/*
|
|
* ========================================================
|
|
* SICHEREN DATEIPFAD ERMITTELN
|
|
* ========================================================
|
|
*/
|
|
|
|
private string GetSafePhysicalFilePath(
|
|
string relativePath)
|
|
{
|
|
/*
|
|
* URL-Decoding.
|
|
*/
|
|
string decoded =
|
|
HttpUtility.UrlDecode(
|
|
relativePath);
|
|
|
|
|
|
/*
|
|
* URL-Pfade in Windows-Pfade umwandeln.
|
|
*/
|
|
decoded =
|
|
decoded.Replace(
|
|
'/',
|
|
'\\');
|
|
|
|
|
|
/*
|
|
* Keine absoluten Pfade erlauben.
|
|
*
|
|
* Verboten wären z.B.:
|
|
*
|
|
* \Windows\System32
|
|
* C:\...
|
|
* \\Server\Share
|
|
*/
|
|
if (
|
|
decoded.StartsWith("\\") ||
|
|
decoded.Contains(":"))
|
|
{
|
|
throw new SecurityException();
|
|
}
|
|
|
|
|
|
/*
|
|
* Path Traversal verhindern.
|
|
*/
|
|
string[] parts =
|
|
decoded.Split(
|
|
new[] { '\\' },
|
|
StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
|
foreach (
|
|
string part
|
|
in parts)
|
|
{
|
|
if (
|
|
part == "." ||
|
|
part == "..")
|
|
{
|
|
throw new SecurityException();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Vollständigen Dateipfad erzeugen.
|
|
*/
|
|
string fullPath =
|
|
Path.GetFullPath(
|
|
Path.Combine(
|
|
ShareRoot,
|
|
decoded));
|
|
|
|
|
|
/*
|
|
* Root normalisieren.
|
|
*/
|
|
string normalizedRoot =
|
|
Path.GetFullPath(
|
|
ShareRoot)
|
|
.TrimEnd('\\') +
|
|
"\\";
|
|
|
|
|
|
/*
|
|
* Der Pfad MUSS unterhalb von
|
|
* \\LAN-DC1\Fileshare bleiben.
|
|
*/
|
|
if (
|
|
!fullPath.StartsWith(
|
|
normalizedRoot,
|
|
StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new SecurityException();
|
|
}
|
|
|
|
|
|
/*
|
|
* Datei muss existieren.
|
|
*/
|
|
if (!File.Exists(fullPath))
|
|
{
|
|
throw new FileNotFoundException();
|
|
}
|
|
|
|
|
|
return fullPath;
|
|
}
|
|
|
|
|
|
/*
|
|
* ========================================================
|
|
* RESPONSE BEENDEN
|
|
* ========================================================
|
|
*
|
|
* Kleine Hilfsmethode, damit Response.End()
|
|
* nicht unnötig in mehreren Stellen steht.
|
|
*/
|
|
private void CompleteResponse()
|
|
{
|
|
HttpContext.Current.ApplicationInstance
|
|
.CompleteRequest();
|
|
}
|
|
}
|
|
} |