2using System.Collections.Generic;
4using System.Threading.Tasks;
19 private const int BufferSize = 32768;
22 private readonly
bool requireEncryption;
33 this.fileUploadComponent = FileUploadComponent;
34 this.requireEncryption = RequireEncryption;
70 base.Validate(Request);
72 if (!this.fileUploadComponent.TryGetFile(Request.
SubPath, out
string FullPath, out
string _))
76 if (Method ==
"PUT" || Method ==
"PATCH")
82 if (this.requireEncryption &&
string.Compare(Request.
Header.
UriScheme,
"http",
true) == 0)
86 DateTimeOffset? Limit;
90 if (File.Exists(FullPath))
92 DateTime LastModified = File.GetLastWriteTime(FullPath);
93 LastModified = LastModified.ToUniversalTime();
101 private class ReadProgress : IDisposable
106 public string Boundary;
107 public string ContentType;
108 public long BytesLeft;
109 public long TotalLength;
110 public int BlockSize;
111 public byte[] Buffer;
113 public async Task BeginRead()
121 while (this.BytesLeft > 0)
123 NrRead = await this.f.TryReadAllAsync(this.Buffer, 0, (
int)Math.Min(
this.BlockSize,
this.BytesLeft));
127 await this.DisposeAsync();
132 await this.Response.
Write(this.Buffer, 0, NrRead);
133 this.BytesLeft -= NrRead;
137 if (!(this.Next is
null))
141 if (this.Next.
First.HasValue)
142 First = this.Next.
First.Value;
144 First = this.TotalLength - this.Next.
Last.Value;
146 this.f.Position = First;
150 await this.Response.
WriteLine(
"--" + this.Boundary);
151 await this.Response.
WriteLine(
"Content-Type: " + this.ContentType);
155 this.Next = this.Next.
Next;
158 while (this.BytesLeft > 0);
160 if (!
string.IsNullOrEmpty(this.Boundary))
163 await this.Response.
WriteLine(
"--" + this.Boundary +
"--");
166 await this.DisposeAsync();
175 await this.Response.
Flush();
178 this.Response =
null;
180 await this.DisposeAsync();
189 public async Task DisposeAsync()
191 if (!(this.Response is
null))
194 this.Response =
null;
197 if (!(this.f is
null))
199 await this.f.FlushAsync();
205 public void Dispose()
207 Task _ = this.DisposeAsync();
211 private readonly Dictionary<string, CacheRec> cacheInfo =
new Dictionary<string, CacheRec>();
213 private class CacheRec
215 public DateTime LastModified;
227 if (!this.fileUploadComponent.TryGetFile(Request.
SubPath, out
string FullPath, out
string ContentType))
230 if (!File.Exists(FullPath))
233 DateTime LastModified = File.GetLastWriteTime(FullPath).ToUniversalTime();
236 Rec = this.CheckCacheHeaders(FullPath, LastModified, Request);
244 Stream f = File.OpenRead(FullPath);
246 await SendResponse(f, FullPath, ContentType, Rec.ETag, LastModified, Response);
249 private static async Task SendResponse(Stream f,
string FullPath,
string ContentType,
string ETag, DateTime LastModified,
252 ReadProgress Progress =
new ReadProgress()
255 f = f ?? File.OpenRead(FullPath),
260 Progress.BytesLeft = Progress.TotalLength = Progress.f.Length;
261 Progress.BlockSize = (int)Math.Min(BufferSize, Progress.BytesLeft);
262 Progress.Buffer =
new byte[Progress.BlockSize];
264 Response.ContentType = ContentType;
265 Response.ContentLength = Progress.TotalLength;
270 if (Response.
OnlyHeader || Progress.TotalLength == 0)
273 await Progress.DisposeAsync();
277 Task _ = Progress.BeginRead();
281 private CacheRec CheckCacheHeaders(
string FullPath, DateTime LastModified,
HttpRequest Request)
283 string CacheKey = FullPath.ToLower();
286 DateTimeOffset? Limit;
288 lock (this.cacheInfo)
290 if (this.cacheInfo.TryGetValue(CacheKey, out Rec))
292 if (Rec.LastModified != LastModified)
294 this.cacheInfo.
Remove(CacheKey);
304 LastModified = LastModified,
307 using (FileStream fs = File.OpenRead(FullPath))
312 lock (this.cacheInfo)
314 this.cacheInfo[CacheKey] = Rec;
344 if (!this.fileUploadComponent.TryGetFile(Request.
SubPath, out
string FullPath, out
string ContentType))
347 if (!File.Exists(FullPath))
351 DateTime LastModified = File.GetLastWriteTime(FullPath).ToUniversalTime();
352 DateTimeOffset? Limit;
355 if (!(Header.
IfRange is
null) && (Limit = Header.
IfRange.Timestamp).HasValue &&
358 Response.StatusCode = 200;
359 await this.
GET(Request, Response);
363 Rec = this.CheckCacheHeaders(FullPath, LastModified, Request);
371 Stream f = File.OpenRead(FullPath);
373 ReadProgress Progress =
new ReadProgress()
376 f = f ?? File.OpenRead(FullPath)
380 Progress.TotalLength = Progress.f.Length;
386 if (FirstInterval.
First.HasValue)
387 First = FirstInterval.
First.Value;
389 First = Progress.TotalLength - FirstInterval.
Last.Value;
391 Progress.f.Position = First;
393 Progress.Next = Interval.
Next;
395 while (!(Interval is
null))
401 Interval = Interval.
Next;
404 Progress.BlockSize = (int)Math.Min(BufferSize, i);
405 Progress.Buffer =
new byte[Progress.BlockSize];
407 if (FirstInterval.
Next is
null)
409 Progress.Boundary =
null;
410 Progress.ContentType =
null;
412 Response.ContentType = ContentType;
418 Progress.Boundary = Guid.NewGuid().ToString().Replace(
"-",
string.Empty);
419 Progress.ContentType = ContentType;
421 Response.ContentType =
"multipart/byteranges; boundary=" + Progress.Boundary;
428 if (Response.
OnlyHeader || Progress.BytesLeft == 0)
431 await Progress.DisposeAsync();
435 if (!(FirstInterval.
Next is
null))
438 await Response.
WriteLine(
"--" + Progress.Boundary);
439 await Response.
WriteLine(
"Content-Type: " + Progress.ContentType);
444 Task _ = Progress.BeginRead();
457 return this.
PUT(Request, Response, Interval);
474 string Key = Request.
Header[
"X-Key"];
476 if (!await this.fileUploadComponent.DataUploaded(Request.
SubPath, Key, Request.
DataStream, Interval))
479 Response.StatusCode = 201;
480 Response.StatusMessage =
"Created";
Helps with parsing of commong data types.
static string EncodeRfc822(DateTime Timestamp)
Encodes a date and time, according to RFC 822 §5.
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.
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...
string ResourceName
Name of resource.
Represets a response of an HTTP client request.
async Task DisposeAsync()
Closes the connection and disposes of all resources.
Task Flush()
Clears all buffers for the current writer and causes any buffered data to be written to the underlyin...
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
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).
async Task Write(byte[] Data)
Returns binary data in the response.
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....
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.
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.
GET Interface for HTTP resources.
Ranged GET Interface for HTTP resources.
Ranged PATCH Interface for HTTP resources.
Ranged PUT Interface for HTTP resources.