4using System.Threading.Tasks;
20 private const int BufferSize = 32768;
23 private readonly
bool requireEncryption;
34 this.fileUploadComponent = FileUploadComponent;
35 this.requireEncryption = RequireEncryption;
71 base.Validate(Request);
73 if (!this.fileUploadComponent.TryGetFile(Request.
SubPath, out
string FullPath, out
_, out
_))
77 if (Method ==
"PUT" || Method ==
"PATCH")
83 if (this.requireEncryption &&
string.Compare(Request.
Header.
UriScheme,
"http",
true) == 0)
87 DateTimeOffset? Limit;
89 if (Header.IfMatch is
null && !(Header.IfUnmodifiedSince is
null) && (Limit = Header.IfUnmodifiedSince.Timestamp).HasValue)
91 if (File.Exists(FullPath))
93 DateTime LastModified = File.GetLastWriteTimeUtc(FullPath);
106 public string Boundary;
107 public string ContentType;
108 public string FullPath;
109 public long BytesLeft;
110 public long TotalLength;
111 public int BlockSize;
112 public byte[] Buffer;
113 public bool DeleteFileOnCompletion;
117 if (this.DeleteFileOnCompletion)
121 if (File.Exists(
this.FullPath))
122 File.Delete(this.FullPath);
126 Log.
Error(
"Unable to delete temporary file for internal transfer: " +
127 ex.Message,
this.FullPath);
132 public async Task BeginRead()
140 while (this.BytesLeft > 0)
142 NrRead = await this.f.TryReadAllAsync(this.Buffer, 0, (
int)Math.Min(
this.BlockSize,
this.BytesLeft));
146 await this.DisposeAsync();
151 await this.Response.
Write(
false, this.Buffer, 0, NrRead);
152 this.BytesLeft -= NrRead;
156 if (!(this.Next is
null))
160 if (this.Next.
First.HasValue)
161 First = this.Next.
First.Value;
163 First = this.TotalLength - this.Next.
Last.Value;
165 this.f.Position = First;
169 await this.Response.
WriteLine(
"--" + this.Boundary);
170 await this.Response.
WriteLine(
"Content-Type: " + this.ContentType);
174 this.Next = this.Next.
Next;
177 while (this.BytesLeft > 0);
179 if (!
string.IsNullOrEmpty(this.Boundary))
182 await this.Response.
WriteLine(
"--" + this.Boundary +
"--");
185 await this.DisposeAsync();
196 await this.Response.
Flush(
true);
199 this.Response =
null;
201 await this.DisposeAsync();
210 public async Task DisposeAsync()
212 if (!(this.Response is
null))
215 this.Response =
null;
218 if (!(this.f is
null))
220 await this.f.FlushAsync();
226 public void Dispose()
228 Task
_ = this.DisposeAsync();
232 private readonly Dictionary<string, CacheRec> cacheInfo =
new Dictionary<string, CacheRec>();
234 private class CacheRec
236 public DateTime LastModified;
248 if (!this.fileUploadComponent.TryGetFile(Request.
SubPath, out
string FullPath,
262 if (!File.Exists(FullPath))
268 DateTime LastModified = File.GetLastWriteTimeUtc(FullPath);
271 Rec = this.CheckCacheHeaders(FullPath, LastModified, Request);
287 Stream f = File.OpenRead(FullPath);
289 await SendResponse(f, FullPath, ContentType, Rec.ETag, LastModified, Response, Purpose);
292 private static async Task SendResponse(Stream f,
string FullPath,
string ContentType,
295 ReadProgress Progress =
new ReadProgress()
298 f = f ?? File.OpenRead(FullPath),
303 DeleteFileOnCompletion = Purpose == FilePurpose.InternalTransfer
305 Progress.BytesLeft = Progress.TotalLength = Progress.f.Length;
306 Progress.BlockSize = (int)Math.Min(BufferSize, Progress.BytesLeft);
307 Progress.Buffer =
new byte[Progress.BlockSize];
309 Response.ContentType = ContentType;
310 Response.ContentLength = Progress.TotalLength;
315 if (Response.
OnlyHeader || Progress.TotalLength == 0)
318 await Progress.DisposeAsync();
320 if (Progress.TotalLength == 0)
325 Task
_ = Progress.BeginRead();
329 private CacheRec CheckCacheHeaders(
string FullPath, DateTime LastModified,
HttpRequest Request)
331 string CacheKey = FullPath.ToLower();
334 DateTimeOffset? Limit;
336 lock (this.cacheInfo)
338 if (this.cacheInfo.TryGetValue(CacheKey, out Rec))
340 if (Rec.LastModified != LastModified)
342 this.cacheInfo.
Remove(CacheKey);
352 LastModified = LastModified,
355 using (FileStream fs = File.OpenRead(FullPath))
360 lock (this.cacheInfo)
362 this.cacheInfo[CacheKey] = Rec;
366 if (!(
Header.IfNoneMatch is
null))
368 if (
Header.IfNoneMatch.Value == Rec.ETag)
371 else if (!(
Header.IfModifiedSince is
null))
373 if ((Limit =
Header.IfModifiedSince.Timestamp).HasValue &&
392 if (!this.fileUploadComponent.TryGetFile(Request.
SubPath, out
string FullPath,
406 if (!File.Exists(FullPath))
413 DateTime LastModified = File.GetLastWriteTimeUtc(FullPath);
414 DateTimeOffset? Limit;
417 if (!(Header.IfRange is
null) && (Limit = Header.IfRange.Timestamp).HasValue &&
420 Response.StatusCode = 200;
421 Response.StatusMessage =
"OK";
422 await this.
GET(Request, Response);
426 Rec = this.CheckCacheHeaders(FullPath, LastModified, Request);
442 Stream f = File.OpenRead(FullPath);
444 ReadProgress Progress =
new ReadProgress()
447 DeleteFileOnCompletion =
false,
448 f = f ?? File.OpenRead(FullPath)
452 Progress.TotalLength = Progress.f.Length;
458 if (FirstInterval.
First.HasValue)
459 First = FirstInterval.
First.Value;
461 First = Progress.TotalLength - FirstInterval.
Last.Value;
463 Progress.f.Position = First;
465 Progress.Next = Interval.
Next;
467 while (!(Interval is
null))
473 Interval = Interval.
Next;
476 Progress.BlockSize = (int)Math.Min(BufferSize, i);
477 Progress.Buffer =
new byte[Progress.BlockSize];
479 if (FirstInterval.
Next is
null)
481 Progress.Boundary =
null;
482 Progress.ContentType =
null;
484 Response.ContentType = ContentType;
490 Progress.Boundary = Guid.NewGuid().ToString().Replace(
"-",
string.Empty);
491 Progress.ContentType = ContentType;
493 Response.ContentType =
"multipart/byteranges; boundary=" + Progress.Boundary;
500 if (Response.
OnlyHeader || Progress.BytesLeft == 0)
503 await Progress.DisposeAsync();
507 if (!(FirstInterval.
Next is
null))
510 await Response.
WriteLine(
"--" + Progress.Boundary);
511 await Response.
WriteLine(
"Content-Type: " + Progress.ContentType);
516 Task
_ = Progress.BeginRead();
529 return this.
PUT(Request, Response, Interval);
546 string Key = Request.
Header[
"X-Key"];
548 if (!await this.fileUploadComponent.DataUploaded(Request.
SubPath, Key,
555 Response.StatusCode = 201;
556 Response.StatusMessage =
"Created";
576 if (this.fileUploadComponent.TryGetFile(SubPath, out
string FullPath, out
_, out
_))
578 if (!MustExist || File.Exists(FullPath))
Helps with parsing of commong data types.
static string EncodeRfc822(DateTime Timestamp)
Encodes a date and time, according to RFC 822 §5.
Static class managing the application event log. Applications and services log events on this static ...
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Represents a range in a ranged HTTP request or response.
long? Last
Last byte of interval, inclusive, if provided. If not provided, the interval ends at the end of the r...
ByteRangeInterval Next
Next segment.
long GetIntervalLength(long TotalLength)
Calculates the number of bytes spanned by the interval.
long? First
First byte of interval, if provided. If not provided, the interval represents the last Last number of...
Represents a content range in a ranged HTTP request or response.
static string ContentRangeToString(long First, long Last, long Total)
Converts the content range to an HTTP header field value string.
The server understood the request, but is refusing to fulfill it. Authorization will not help and the...
Base class for all asynchronous HTTP resources. An asynchronous resource responds outside of the meth...
Publishes a folder with all its files and subfolders through HTTP GET, with optional support for PUT,...
static bool GreaterOrEqual(DateTime LastModified, DateTimeOffset Limit)
Computes LastModified >=Limit . The normal >= operator behaved strangely, and did not get the equalit...
static bool LessOrEqual(DateTime LastModified, DateTimeOffset Limit)
Computes LastModified <=Limit . The normal <= operator behaved strangely, and did not get the equalit...
Represents an HTTP request.
Stream DataStream
Data stream, if data is available, or null if data is not available.
HttpRequestHeader Header
Request header.
string RemoteEndPoint
Remote end-point.
bool HasData
If the request has data.
string SubPath
Sub-path. If a resource is found handling the request, this property contains the trailing sub-path o...
IUser User
Authenticated user, if available, or null if not available.
string ResourceName
Name of resource.
Represets a response of an HTTP client request.
Task Flush(bool EndOfData)
Clears all buffers for the current writer and causes any buffered data to be written to the underlyin...
async Task DisposeAsync()
Closes the connection and disposes of all resources.
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
Task Write(byte[] Data)
Returns binary data in the response.
void SetHeader(string FieldName, string Value)
Sets a custom header field value.
bool HeaderSent
If the header has been sent.
bool OnlyHeader
If only the header is of interest.
Task WriteLine()
Writes a new line character sequence (CRLF).
The resource identified by the request is only capable of generating response entities which have con...
The server has not found anything matching the Request-URI. No indication is given of whether the con...
If the client has performed a conditional GET request and access is allowed, but the document has not...
Implements HTTP File Upload support as an XMPP component: https://xmpp.org/extensions/xep-0363....
Access scheme used for internal transfer of files between client and server.
static bool IsInternal(HttpRequest Request)
Checks if a request is an internal transfer request, meaning that both the local and remote end point...
HTTP Resource managing HTTP Uploads, and access to uploaded files.
async Task PUT(HttpRequest Request, HttpResponse Response, ContentByteRangeInterval Interval)
Executes the ranged PUT method on the resource.
XmppFileUploadResource(string ResourceName, HttpFileUploadComponent FileUploadComponent, bool RequireEncryption)
HTTP Resource managing HTTP Uploads, and access to uploaded files.
bool AllowsPATCH
If the PATCH method is allowed.
override bool UserSessions
If the resource uses user sessions.
bool AllowsGET
If the GET method is allowed.
bool TryGetFileName(string SubPath, string Host, bool MustExist, out string FileName)
Tries to get the full path of a file-based resource.
override void Validate(HttpRequest Request)
Validates the request itself. This method is called prior to processing the request,...
override bool HandlesSubPaths
If the resource handles sub-paths.
Task PATCH(HttpRequest Request, HttpResponse Response, ContentByteRangeInterval Interval)
Executes the ranged PATCH method on the resource.
bool AllowsPUT
If the PUT method is allowed.
async Task GET(HttpRequest Request, HttpResponse Response, ByteRangeInterval FirstInterval)
Executes the ranged GET method on the resource.
async Task GET(HttpRequest Request, HttpResponse Response)
Executes the GET method on the resource.
Contains methods for simple hash calculations.
static string ComputeSHA1HashString(byte[] Data)
Computes the SHA-1 hash of a block of binary data.
Interface for asynchronously disposable objects.
GET Interface for HTTP resources.
Ranged GET Interface for HTTP resources.
Ranged PATCH Interface for HTTP resources.
Ranged PUT Interface for HTTP resources.
Interface for resources hosting files in a folder.
class Header(ISimulationNode Parent, Model Model)
Represents an identity property.
FilePurpose
Purpose of file uploaded