Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
WebGetter.cs
1using System;
3using System.Net;
4using System.Net.Http;
5using System.Net.Http.Headers;
7using System.Reflection;
8using System.Security.Cryptography.X509Certificates;
9using System.Text;
10using System.Threading.Tasks;
12using Waher.Events;
17using Waher.Security;
18
20{
25 {
26 private static bool useProxy = true;
27 private static bool enforceHttps = false;
28
32 public WebGetter()
33 {
34 }
35
39 public string[] UriSchemes => new string[] { "http", "https" };
40
47 public bool CanGet(Uri Uri, out Grade Grade)
48 {
49 switch (Uri.Scheme.ToLower())
50 {
51 case "http":
52 case "https":
53 Grade = Grade.Ok;
54 return true;
55
56 default:
57 Grade = Grade.NotAtAll;
58 return false;
59 }
60 }
61
70 public Task<ContentResponse> GetAsync(Uri Uri, X509Certificate Certificate,
71 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator,
72 params KeyValuePair<string, string>[] Headers)
73 {
74 return this.GetAsync(Uri, Certificate, RemoteCertificateValidator, InternetContent.DefaultTimeout, Headers);
75 }
76
86 public async Task<ContentResponse> GetAsync(Uri Uri, X509Certificate Certificate,
87 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator,
88 int TimeoutMs, params KeyValuePair<string, string>[] Headers)
89 {
90 HttpClientHandler Handler = GetClientHandler(Certificate, RemoteCertificateValidator, Uri);
91 using (HttpClient HttpClient = new HttpClient(Handler, true)
92 {
93 Timeout = TimeSpan.FromMilliseconds(TimeoutMs)
94 })
95 {
96 using (HttpRequestMessage Request = new HttpRequestMessage()
97 {
98 Method = HttpMethod.Get
99 })
100 {
101 Request.RequestUri = CheckUri(Uri, Request);
102 PrepareHeaders(Request, Headers, Handler);
103
104 HttpResponseMessage Response = await HttpClient.SendAsync(Request);
105
106 return await ProcessResponse(Response, Uri);
107 }
108 }
109 }
110
116 public static bool TrustServer(Uri Uri)
117 {
118 if (Uri.Host != "localhost")
119 return false;
120
121 if (Uri.Scheme != "https")
122 return false;
123
124 if (!Types.TryGetModuleParameter("HTTP", out object Obj) || Obj is null)
125 return false;
126
127 int Port = Uri.Port;
128 if (Port <= 0)
129 Port = 443;
130
131 Type T = Obj.GetType();
132 PropertyInfo PI = T.GetProperty("OpenHttpsPorts");
133 if (PI is null || !PI.CanRead || !PI.GetMethod.IsPublic)
134 return false;
135
136 Obj = PI.GetValue(Obj);
137 if (!(Obj is int[] OpenHttpsPorts))
138 return false;
139
140 foreach (int OpenPort in OpenHttpsPorts)
141 {
142 if (OpenPort == Port)
143 return true;
144 }
145
146 return false;
147 }
148
153 public static HttpClientHandler GetClientHandler()
154 {
155 return GetClientHandler(null, null, false);
156 }
157
163 public static HttpClientHandler GetClientHandler(bool TrustServer)
164 {
165 return GetClientHandler(null, null, TrustServer);
166 }
167
174 public static HttpClientHandler GetClientHandler(X509Certificate Certificate,
175 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator)
176 {
177 return GetClientHandler(Certificate, RemoteCertificateValidator, false);
178 }
179
187 public static HttpClientHandler GetClientHandler(X509Certificate Certificate,
188 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator, Uri Uri)
189 {
190 return GetClientHandler(Certificate, RemoteCertificateValidator, TrustServer(Uri));
191 }
192
200 public static HttpClientHandler GetClientHandler(X509Certificate Certificate,
201 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator,
202 bool TrustServer)
203 {
204 RemoteCertificateValidator Validator = new RemoteCertificateValidator(RemoteCertificateValidator, TrustServer);
205
206 HttpClientHandler Handler = new HttpClientHandler()
207 {
208 AllowAutoRedirect = true,
209 CheckCertificateRevocationList = true,
210 ClientCertificateOptions = ClientCertificateOption.Automatic,
211 ServerCertificateCustomValidationCallback = Validator.RemoteCertificateValidationCallback,
212 AutomaticDecompression = (DecompressionMethods)(-1), // All
213 UseProxy = useProxy
214 };
215
216 try
217 {
218 Handler.SslProtocols = Crypto.TlsOnly;
219 }
220 catch (PlatformNotSupportedException)
221 {
222 // Ignore
223 }
224
225 if (!(Certificate is null))
226 {
227 Handler.ClientCertificateOptions = ClientCertificateOption.Manual;
228 Handler.ClientCertificates.Add(Certificate);
229 }
230
231 return Handler;
232 }
233
238 public static bool UseProxy
239 {
240 get => useProxy;
241 set => useProxy = value;
242 }
243
248 public static bool EnforceHttps
249 {
250 get => enforceHttps;
251 set => enforceHttps = value;
252 }
253
260 public static Uri CheckUri(Uri Uri, HttpRequestMessage Request)
261 {
262 EventHandler<HttpUriEventArgs> h = HttpUriEventHandler;
263
264 if (h is null && !enforceHttps)
265 return Uri;
266
267 if (Uri.Scheme.ToLower() != "http")
268 return Uri;
269
270 if (!(h is null))
271 {
272 HttpUriEventArgs e = new HttpUriEventArgs(Uri, Request);
273
274 h.Raise(null, e);
275
276 if (e.Uri != Uri)
277 return e.Uri;
278 else if (!enforceHttps)
279 return Uri;
280 }
281
282 if (Uri.Authority.IndexOf(':') >= 0)
283 return Uri;
284
285 if (Uri.TryCreate(Uri.OriginalString.Replace("http:", "https:"), UriKind.Absolute, out Uri Uri2))
286 return Uri2;
287 else
288 return Uri;
289 }
290
296 public static event EventHandler<HttpUriEventArgs> HttpUriEventHandler;
297
301 private class RemoteCertificateValidator
302 {
303 private readonly EventHandler<RemoteCertificateEventArgs> callback;
304 private readonly bool trustServer;
305
310 public RemoteCertificateValidator(EventHandler<RemoteCertificateEventArgs> Callback)
311 : this(Callback, false)
312 {
313 }
314
320 public RemoteCertificateValidator(EventHandler<RemoteCertificateEventArgs> Callback,
321 bool TrustServer)
322 {
323 this.callback = Callback;
324 this.trustServer = TrustServer;
325 }
326
327 public bool RemoteCertificateValidationCallback(object Sender,
328 X509Certificate Certificate, X509Chain Chain, SslPolicyErrors SslPolicyErrors)
329 {
330 if (!(this.callback is null))
331 {
332 RemoteCertificateEventArgs e = new RemoteCertificateEventArgs(Certificate, Chain, SslPolicyErrors);
333
334 this.callback.Raise(Sender, e);
335
336 if (e.IsValid.HasValue)
337 return e.IsValid.Value;
338 }
339
340 if (this.trustServer)
341 return true;
342 else if (SslPolicyErrors == SslPolicyErrors.None)
343 return true;
344 else
345 {
346 // Check for incomplete revocation check in the chain
347
348 if (SslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) && !(Chain is null))
349 {
350 foreach (X509ChainStatus Status in Chain.ChainStatus)
351 {
352 // Apple-specific error code for incomplete revocation check
353
354 if (Status.Status == X509ChainStatusFlags.RevocationStatusUnknown ||
355 Status.Status == X509ChainStatusFlags.OfflineRevocation)
356 {
357 continue; // Ignore this error
358 }
359
360 if (Status.Status != X509ChainStatusFlags.NoError)
361 {
362 if (Certificate is X509Certificate2 Certificate2)
363 return Certificate2.Verify();
364
365 Certificate2 = new X509Certificate2(Certificate.GetRawCertData());
366
367 return Certificate2.Verify(); // Check if certificate fails on other errors
368 }
369 }
370
371 return true; // Only revocation check failed, allow
372 }
373
374 return false;
375 }
376 }
377 }
378
386 public static async Task<ContentResponse> ProcessResponse(HttpResponseMessage Response, Uri Uri)
387 {
388 byte[] Bin = await Response.Content.ReadAsByteArrayAsync();
389 string ContentType;
390 ContentResponse Decoded;
391
392 if (Response.Content.Headers.ContentType is null)
393 {
394 if (Bin.Length == 0)
395 Decoded = new ContentResponse(string.Empty, null, Bin);
396 else
397 Decoded = new ContentResponse(BinaryCodec.DefaultContentType, Bin, Bin);
398 }
399 else
400 {
401 ContentType = Response.Content.Headers.ContentType.ToString();
402 Decoded = await InternetContent.DecodeAsync(ContentType, Bin, Uri);
403 if (Decoded.HasError)
404 return Decoded;
405 }
406
407 if (Decoded.Decoded is IWebServerMetaContent WebServerMetaContent)
408 await WebServerMetaContent.DecodeMetaInformation(Response);
409
410 if (!Response.IsSuccessStatusCode)
411 {
412 if (!(Decoded.Decoded is string Message))
413 {
414 if (Decoded.Decoded is null ||
415 Decoded.Decoded is Dictionary<string, object>)
416 {
417 Message = Response.ReasonPhrase;
418 }
419 else if (Decoded.Decoded is byte[] Bin2)
420 {
421 if (Bin2.Length == 0)
422 Message = Response.ReasonPhrase;
423 else
424 Message = Strings.GetString(Bin2, Encoding.UTF8);
425 }
426 else
427 {
428 Message = Decoded.ToString();
429 if (Message == Decoded.GetType().FullName)
430 Message = Response.ReasonPhrase;
431 }
432 }
433
434 Decoded = new ContentResponse(new WebException(Message, Response.StatusCode,
435 Decoded.ContentType, Bin, Decoded.Decoded, Response.Headers));
436 }
437
438 return Decoded;
439 }
440
447 public static void PrepareHeaders(HttpRequestMessage Request, KeyValuePair<string, string>[] Headers, HttpClientHandler Handler)
448 {
449 if (!(Headers is null))
450 {
451 foreach (KeyValuePair<string, string> Header in Headers)
452 {
453 switch (Header.Key)
454 {
455 case "Accept":
456 if (!Request.Headers.Accept.TryParseAdd(Header.Value))
457 throw new InvalidOperationException("Invalid Accept header value: " + Header.Value);
458 break;
459
460 case "Authorization":
461 int i = Header.Value.IndexOf(' ');
462 if (i < 0)
463 Request.Headers.Authorization = new AuthenticationHeaderValue(Header.Value);
464 else
465 Request.Headers.Authorization = new AuthenticationHeaderValue(Header.Value.Substring(0, i), Header.Value.Substring(i + 1).TrimStart());
466 break;
467
468 case "Cookie":
469 foreach (KeyValuePair<string, string> P in CommonTypes.ParseFieldValues(Header.Value))
470 Handler.CookieContainer.Add(Request.RequestUri, new Cookie(P.Key, P.Value));
471 break;
472
473 default:
474 Request.Headers.Add(Header.Key, Header.Value);
475 break;
476 }
477 }
478 }
479 }
480
489 public Task<ContentStreamResponse> GetTempStreamAsync(Uri Uri, X509Certificate Certificate,
490 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator, params KeyValuePair<string, string>[] Headers)
491 {
492 return this.GetTempStreamAsync(Uri, Certificate, RemoteCertificateValidator, InternetContent.DefaultTimeout, Headers);
493 }
494
504 public Task<ContentStreamResponse> GetTempStreamAsync(Uri Uri, X509Certificate Certificate,
505 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator, TemporaryStream Destination, params KeyValuePair<string, string>[] Headers)
506 {
507 return this.GetTempStreamAsync(Uri, Certificate, RemoteCertificateValidator, InternetContent.DefaultTimeout, Destination, Headers);
508 }
509
519 public Task<ContentStreamResponse> GetTempStreamAsync(Uri Uri, X509Certificate Certificate,
520 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator, int TimeoutMs, params KeyValuePair<string, string>[] Headers)
521 {
522 return this.GetTempStreamAsync(Uri, Certificate, RemoteCertificateValidator, TimeoutMs, null, Headers);
523 }
524
535 public async Task<ContentStreamResponse> GetTempStreamAsync(Uri Uri, X509Certificate Certificate,
536 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator, int TimeoutMs, TemporaryStream Destination,
537 params KeyValuePair<string, string>[] Headers)
538 {
539 HttpClientHandler Handler = GetClientHandler(Certificate, RemoteCertificateValidator, Uri);
540 using (HttpClient HttpClient = new HttpClient(Handler, true)
541 {
542 Timeout = TimeSpan.FromMilliseconds(10000)
543 })
544 {
545 using (HttpRequestMessage Request = new HttpRequestMessage()
546 {
547 Method = HttpMethod.Get
548 })
549 {
550 Request.RequestUri = CheckUri(Uri, Request);
551 PrepareHeaders(Request, Headers, Handler);
552
553 HttpResponseMessage Response = await HttpClient.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead);
554
555 if (!Response.IsSuccessStatusCode)
556 {
557 ContentResponse Temp = await ProcessResponse(Response, Uri);
558 return new ContentStreamResponse(Temp.Error);
559 }
560
561 string ContentType = Response.Content.Headers.ContentType.ToString();
562 bool TempStreamCreated = false;
563
564 if (Destination is null)
565 {
566 Destination = new TemporaryStream();
567 TempStreamCreated = true;
568 }
569
570 try
571 {
572 await Response.Content.CopyToAsync(Destination);
573 }
574 catch (Exception ex)
575 {
576 if (TempStreamCreated)
577 {
578 Destination.Dispose();
579 Destination = null;
580 }
581
582 return new ContentStreamResponse(ex);
583 }
584
585 return new ContentStreamResponse(ContentType, Destination);
586 }
587 }
588 }
589
596 public bool CanHead(Uri Uri, out Grade Grade)
597 {
598 return this.CanGet(Uri, out Grade);
599 }
600
609 public Task<ContentResponse> HeadAsync(Uri Uri, X509Certificate Certificate,
610 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator,
611 params KeyValuePair<string, string>[] Headers)
612 {
613 return this.HeadAsync(Uri, Certificate, RemoteCertificateValidator, InternetContent.DefaultTimeout, Headers);
614 }
615
625 public async Task<ContentResponse> HeadAsync(Uri Uri, X509Certificate Certificate,
626 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator,
627 int TimeoutMs, params KeyValuePair<string, string>[] Headers)
628 {
629 HttpClientHandler Handler = GetClientHandler(Certificate, RemoteCertificateValidator, Uri);
630 using (HttpClient HttpClient = new HttpClient(Handler, true)
631 {
632 Timeout = TimeSpan.FromMilliseconds(TimeoutMs)
633 })
634 {
635 using (HttpRequestMessage Request = new HttpRequestMessage()
636 {
637 Method = HttpMethod.Head
638 })
639 {
640 Request.RequestUri = CheckUri(Uri, Request);
641 PrepareHeaders(Request, Headers, Handler);
642
643 HttpResponseMessage Response = await HttpClient.SendAsync(Request);
644 Dictionary<string, object> Result = new Dictionary<string, object>()
645 {
646 { "Status", Response.StatusCode },
647 { "StatusCode", (int)Response.StatusCode },
648 { "Message", Response.ReasonPhrase },
649 { "IsSuccessStatusCode", Response.IsSuccessStatusCode },
650 { "Version", Response.Version.ToString() }
651 };
652
653 foreach (KeyValuePair<string, IEnumerable<string>> Header in Response.Headers)
654 {
655 string s = null;
656 ChunkedList<string> List = null;
657
658 foreach (string Value in Header.Value)
659 {
660 if (s is null)
661 s = Value;
662 else
663 {
664 if (List is null)
665 List = new ChunkedList<string>() { s };
666
667 List.Add(Value);
668 }
669 }
670
671 if (List is null)
672 Result[Header.Key] = s;
673 else
674 Result[Header.Key] = List.ToArray();
675 }
676
677 return new ContentResponse(Response.Content?.Headers.ContentType?.ToString() ?? string.Empty, Result, null);
678 }
679 }
680 }
681
682 }
683}
const string DefaultContentType
text/plain
Definition: BinaryCodec.cs:24
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static KeyValuePair< string, string >[] ParseFieldValues(string Value)
Parses a set of comma or semicolon-separated field values, optionaly delimited by ' or " characters.
Definition: CommonTypes.cs:474
Contains information about a response to a content request.
string ContentType
Internet Content-Type of encoded object.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Exception Error
Error response.
Contains information about a stream response to a content request.
Event arguments for the WebGetter.HttpUriEventHandler event.
Exception class for web exceptions.
Definition: WebException.cs:11
Gets resources from the Web (i.e. using HTTP or HTTPS).
Definition: WebGetter.cs:25
static async Task< ContentResponse > ProcessResponse(HttpResponseMessage Response, Uri Uri)
Decodes a response from the web. If the response is a success, the decoded response is returned....
Definition: WebGetter.cs:386
bool CanGet(Uri Uri, out Grade Grade)
If the getter is able to get a resource, given its URI.
Definition: WebGetter.cs:47
WebGetter()
Gets resources from the Web (i.e. using HTTP or HTTPS).
Definition: WebGetter.cs:32
async Task< ContentResponse > HeadAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, int TimeoutMs, params KeyValuePair< string, string >[] Headers)
Gets the headers of a resource, using a Uniform Resource Identifier (or Locator).
Definition: WebGetter.cs:625
static HttpClientHandler GetClientHandler(X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, Uri Uri)
Gets a HTTP Client handler
Definition: WebGetter.cs:187
static bool UseProxy
If HTTP proxies should be used when making requests, when available. Default is true.
Definition: WebGetter.cs:239
static HttpClientHandler GetClientHandler(bool TrustServer)
Gets a HTTP Client handler
Definition: WebGetter.cs:163
static void PrepareHeaders(HttpRequestMessage Request, KeyValuePair< string, string >[] Headers, HttpClientHandler Handler)
Prepares headers for a HTTP request.
Definition: WebGetter.cs:447
static EventHandler< HttpUriEventArgs > HttpUriEventHandler
Event raised when a HTTP only URI is being processed. Can be used to alter the URI,...
Definition: WebGetter.cs:296
static bool EnforceHttps
If HTTPS should be enforced, even when accessing unencrypted HTTP resources. Default is false.
Definition: WebGetter.cs:249
static HttpClientHandler GetClientHandler(X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, bool TrustServer)
Gets a HTTP Client handler
Definition: WebGetter.cs:200
static HttpClientHandler GetClientHandler()
Gets a HTTP Client handler
Definition: WebGetter.cs:153
string[] UriSchemes
Supported URI schemes.
Definition: WebGetter.cs:39
Task< ContentStreamResponse > GetTempStreamAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, int TimeoutMs, params KeyValuePair< string, string >[] Headers)
Gets a (possibly big) resource, using a Uniform Resource Identifier (or Locator).
Definition: WebGetter.cs:519
static bool TrustServer(Uri Uri)
If the server certificate can be trusted.
Definition: WebGetter.cs:116
Task< ContentStreamResponse > GetTempStreamAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, TemporaryStream Destination, params KeyValuePair< string, string >[] Headers)
Gets a (possibly big) resource, using a Uniform Resource Identifier (or Locator).
Definition: WebGetter.cs:504
async Task< ContentResponse > GetAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, int TimeoutMs, params KeyValuePair< string, string >[] Headers)
Gets a resource, using a Uniform Resource Identifier (or Locator).
Definition: WebGetter.cs:86
bool CanHead(Uri Uri, out Grade Grade)
If the getter is able to get headers of a resource, given its URI.
Definition: WebGetter.cs:596
static Uri CheckUri(Uri Uri, HttpRequestMessage Request)
Checks the URI, if it is suitable for processing, or if it needs to be modified.
Definition: WebGetter.cs:260
Task< ContentResponse > HeadAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, params KeyValuePair< string, string >[] Headers)
Gets the headers of a resource, using a Uniform Resource Identifier (or Locator).
Definition: WebGetter.cs:609
Task< ContentStreamResponse > GetTempStreamAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, params KeyValuePair< string, string >[] Headers)
Gets a (possibly big) resource, using a Uniform Resource Identifier (or Locator).
Definition: WebGetter.cs:489
static HttpClientHandler GetClientHandler(X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator)
Gets a HTTP Client handler
Definition: WebGetter.cs:174
async Task< ContentStreamResponse > GetTempStreamAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, int TimeoutMs, TemporaryStream Destination, params KeyValuePair< string, string >[] Headers)
Gets a (possibly big) resource, using a Uniform Resource Identifier (or Locator).
Definition: WebGetter.cs:535
Task< ContentResponse > GetAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, params KeyValuePair< string, string >[] Headers)
Gets a resource, using a Uniform Resource Identifier (or Locator).
Definition: WebGetter.cs:70
Static class managing encoding and decoding of internet content.
static int DefaultTimeout
Default timeout of internet access methods, in milliseconds.
static Task< ContentResponse > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
Remove certificate validation event arguments.
bool? IsValid
If remote certificate is considered valid or not. null means default validation rules will be applied...
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
Static class managing binary representations of strings.
Definition: Strings.cs:10
static string GetString(byte[] Data, int Offset, int Count, Encoding DefaultEncoding)
Gets a string from its binary representation, taking any Byte Order Mark (BOM) into account.
Definition: Strings.cs:148
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
Manages a temporary stream. Contents is kept in-memory, if below a memory threshold,...
override void Dispose(bool disposing)
Releases the unmanaged resources used by the System.IO.Stream and optionally releases the managed res...
Helper methods for encrypting and decrypting streams of data.
Definition: Crypto.cs:14
const SslProtocols TlsOnly
TLS 1.0, 1.1, 1.2 & 1.3
Definition: Crypto.cs:23
Interface for content classes, that process information available in HTTP headers in the response.
Basic interface for Internet Content getters. A class implementing this interface and having a defaul...
Basic interface for Internet Content headers. A class implementing this interface and having a defaul...
Grade
Grade enumeration
Definition: Grade.cs:7