Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
HttpxClient.cs
1#define LOG_SOCKS5_EVENTS
2
3using System;
5using System.Globalization;
6using System.IO;
7using System.Runtime.ExceptionServices;
8using System.Text;
9using System.Threading.Tasks;
10using System.Xml;
11using Waher.Content;
13using Waher.Events;
19using Waher.Security;
20
22{
27 {
31 public const string ExtensionId = "XEP-0332-c";
32
36 public const string Namespace = "urn:xmpp:http";
37
41 public const string NamespaceJwt = "urn:xmpp:jwt:0";
42
46 public const string NamespaceHeaders = "http://jabber.org/protocol/shim";
47
48 private InBandBytestreams.IbbClient ibbClient = null;
49 private P2P.SOCKS5.Socks5Proxy socks5Proxy = null;
50 private IEndToEndEncryption e2e;
51 private IPostResource postResource;
52 private readonly int maxChunkSize;
53
59 public HttpxClient(XmppClient Client, int MaxChunkSize)
60 : this(Client, null, MaxChunkSize)
61 {
62 }
63
71 : base(Client)
72 {
73 this.e2e = E2e;
74 this.maxChunkSize = MaxChunkSize;
75
76 HttpxChunks.RegisterChunkReceiver(this.client);
77 }
78
82 public override string[] Extensions => new string[] { ExtensionId };
83
88 {
89 get => this.e2e;
90 set => this.e2e = value;
91 }
92
97 {
98 get => this.ibbClient;
99 set
100 {
101 if (!(this.ibbClient is null))
102 this.ibbClient.OnOpen -= this.IbbClient_OnOpen;
103
104 this.ibbClient = value;
105 this.ibbClient.OnOpen += this.IbbClient_OnOpen;
106 }
107 }
108
113 {
114 get => this.socks5Proxy;
115 set
116 {
117 if (!(this.socks5Proxy is null))
118 this.socks5Proxy.OnOpen -= this.Socks5Proxy_OnOpen;
119
120 this.socks5Proxy = value;
121 this.socks5Proxy.OnOpen += this.Socks5Proxy_OnOpen;
122 }
123 }
124
129 {
130 get => this.postResource;
131 set => this.postResource = value;
132 }
133
135 public override void Dispose()
136 {
137 HttpxChunks.UnregisterChunkReceiver(this.client);
138 base.Dispose();
139 }
140
150 public Task GET(string To, string Resource, EventHandlerAsync<HttpxResponseEventArgs> Callback,
151 EventHandlerAsync<HttpxResponseDataEventArgs> DataCallback, object State, params HttpField[] Headers)
152 {
153 return this.Request(To, "GET", Resource, Callback, DataCallback, State, Headers);
154 }
155
166 public async Task POST(string To, string Resource, object Data,
167 EventHandlerAsync<HttpxResponseEventArgs> Callback, EventHandlerAsync<HttpxResponseDataEventArgs> DataCallback,
168 object State, params HttpField[] Headers)
169 {
170 ContentResponse P = await InternetContent.EncodeAsync(Data, Encoding.UTF8);
171 P.AssertOk();
172
173 await this.POST(To, Resource, P.Encoded, P.ContentType, Callback, DataCallback, State, Headers);
174 }
175
187 public async Task POST(string To, string Resource, byte[] Data, string ContentType,
188 EventHandlerAsync<HttpxResponseEventArgs> Callback, EventHandlerAsync<HttpxResponseDataEventArgs> DataCallback,
189 object State, params HttpField[] Headers)
190 {
191 MemoryStream DataStream = new MemoryStream(Data);
192
193 try
194 {
195 Task ResponseReceived(object Sender, HttpxResponseEventArgs e)
196 {
197 DataStream?.Dispose();
198 DataStream = null;
199
200 return Callback.Raise(Sender, e);
201 }
202 ;
203
204 await this.POST(To, Resource, DataStream, ContentType, ResponseReceived, DataCallback, State, Headers);
205 }
206 catch (Exception ex)
207 {
208 DataStream?.Dispose();
209 ExceptionDispatchInfo.Capture(ex).Throw();
210 }
211 }
212
224 public Task POST(string To, string Resource, Stream DataStream, string ContentType,
225 EventHandlerAsync<HttpxResponseEventArgs> Callback, EventHandlerAsync<HttpxResponseDataEventArgs> DataCallback,
226 object State, params HttpField[] Headers)
227 {
228 List<HttpField> Headers2 = new List<HttpField>()
229 {
230 new HttpField("Content-Type", ContentType)
231 };
232
233 if (!(Headers is null))
234 {
235 foreach (HttpField Field in Headers)
236 {
237 if (Field.Key != "Content-Type")
238 Headers2.Add(Field);
239 }
240 }
241
242 return this.Request(To, "POST", Resource, 1.1, Headers2, DataStream, Callback, DataCallback, State);
243 }
244
255 public Task Request(string To, string Method, string LocalResource, EventHandlerAsync<HttpxResponseEventArgs> Callback,
256 EventHandlerAsync<HttpxResponseDataEventArgs> DataCallback, object State, params HttpField[] Headers)
257 {
258 return this.Request(To, Method, LocalResource, 1.1, Headers, null, Callback, DataCallback, State);
259 }
260
273 public async Task Request(string To, string Method, string LocalResource, double HttpVersion, IEnumerable<HttpField> Headers,
274 Stream DataStream, EventHandlerAsync<HttpxResponseEventArgs> Callback, EventHandlerAsync<HttpxResponseDataEventArgs> DataCallback, object State)
275 {
276 StringBuilder Xml = new StringBuilder();
277 ResponseState ResponseState = new ResponseState()
278 {
279 Callback = Callback,
280 DataCallback = DataCallback,
281 State = State
282 };
283
284 Xml.Append("<req xmlns='");
285 Xml.Append(Namespace);
286 Xml.Append("' method='");
287 Xml.Append(Method);
288 Xml.Append("' resource='");
289 Xml.Append(XML.Encode(LocalResource));
290 Xml.Append("' version='");
291 Xml.Append(HttpVersion.ToString("F1", CultureInfo.InvariantCulture));
292 Xml.Append("' maxChunkSize='");
293 Xml.Append(this.maxChunkSize.ToString());
294
295 if (!(this.postResource is null))
296 {
297 string Resource = await this.postResource.GetUrl(this.ResponsePostbackHandler, ResponseState);
298
299 Xml.Append("' post='");
300 Xml.Append(XML.Encode(Resource));
301
302 ResponseState.PreparePostBackCall(this.e2e, Resource, this.client);
303 }
304
305 Xml.Append("' sipub='false' ibb='");
306 Xml.Append(CommonTypes.Encode(!(this.ibbClient is null)));
307 Xml.Append("' s5='");
308 Xml.Append(CommonTypes.Encode(!(this.socks5Proxy is null)));
309 Xml.Append("' jingle='false'>");
310
311 Xml.Append("<headers xmlns='");
312 Xml.Append(NamespaceHeaders);
313 Xml.Append("'>");
314
315 foreach (HttpField HeaderField in Headers)
316 {
317 Xml.Append("<header name='");
318 Xml.Append(XML.Encode(HeaderField.Key));
319 Xml.Append("'>");
320 Xml.Append(XML.Encode(HeaderField.Value));
321 Xml.Append("</header>");
322 }
323 Xml.Append("</headers>");
324
325 string StreamId = null;
326
327 if (!(DataStream is null))
328 {
329 if (DataStream.Length < this.maxChunkSize)
330 {
331 DataStream.Position = 0;
332 byte[] Data = await DataStream.ReadAllAsync();
333
334 Xml.Append("<data><base64>");
335 Xml.Append(Convert.ToBase64String(Data));
336 Xml.Append("</base64></data>");
337 }
338 else
339 {
340 StreamId = Guid.NewGuid().ToString().Replace("-", string.Empty);
341
342 Xml.Append("<data><chunkedBase64 streamId='");
343 Xml.Append(StreamId);
344 Xml.Append("'/></data>");
345 }
346 }
347
348 Xml.Append("</req>");
349
350 await this.SendIqSet(To, Xml.ToString(), ResponseState);
351
352 if (!string.IsNullOrEmpty(StreamId))
353 {
354 byte[] Data = new byte[this.maxChunkSize];
355 long Pos = 0;
356 long Len = DataStream.Length;
357 int Nr = 0;
358 int i;
359
360 DataStream.Position = 0;
361
362 while (Pos < Len)
363 {
364 if (Pos + this.maxChunkSize <= Len)
365 i = this.maxChunkSize;
366 else
367 i = (int)(Len - Pos);
368
369 await DataStream.ReadAllAsync(Data, 0, i);
370
371 Pos += i;
372
373 Xml.Clear();
374 Xml.Append("<chunk xmlns='");
375 Xml.Append(Namespace);
376 Xml.Append("' streamId='");
377 Xml.Append(StreamId);
378 Xml.Append("' nr='");
379 Xml.Append(Nr.ToString());
380
381 if (Pos >= Len)
382 Xml.Append("' last='true");
383
384 Xml.Append("'>");
385 Xml.Append(Convert.ToBase64String(Data, 0, i));
386 Xml.Append("</chunk>");
387 Nr++;
388
389 await this.SendChunk(To, Xml.ToString(), ResponseState);
390 }
391 }
392 }
393
394 private async Task SendIqSet(string To, string Xml, object ResponseState)
395 {
396 TaskCompletionSource<bool> StanzaSent = new TaskCompletionSource<bool>();
397 Task FlagStanzaAsSent(object Sender, EventArgs e)
398 {
399 StanzaSent.TrySetResult(true);
400 return Task.CompletedTask;
401 }
402 ;
403
404 if (!(this.e2e is null))
405 {
406 await this.e2e.SendIqSet(this.client, E2ETransmission.NormalIfNotE2E, To, Xml,
407 this.ResponseHandler, ResponseState, InternetContent.DefaultTimeout, 0, FlagStanzaAsSent);
408 }
409 else
410 {
411 await this.client.SendIqSet(To, Xml, this.ResponseHandler, ResponseState,
412 InternetContent.DefaultTimeout, 0, FlagStanzaAsSent);
413 }
414
415 Task _ = Task.Delay(10000).ContinueWith((_2) =>
416 StanzaSent.TrySetException(new GenericException(new TimeoutException("Unable to send HTTPX request."), null, To)));
417
418 await StanzaSent.Task; // By waiting for request to have been sent, E2E synchronization has already been performed, if necessary.
419 }
420
421 private async Task SendChunk(string To, string Xml, object ResponseState)
422 {
423 TaskCompletionSource<bool> StanzaSent = new TaskCompletionSource<bool>();
424 Task FlagStanzaAsSent(object Sender, EventArgs e)
425 {
426 StanzaSent.TrySetResult(true);
427 return Task.CompletedTask;
428 };
429
430 if (!(this.e2e is null))
431 {
432 await this.e2e.SendMessage(this.client, E2ETransmission.NormalIfNotE2E, QoSLevel.Unacknowledged,
433 MessageType.Normal, string.Empty, To, Xml.ToString(), string.Empty, string.Empty,
434 string.Empty, string.Empty, string.Empty, FlagStanzaAsSent, ResponseState);
435 }
436 else
437 {
438 await this.client.SendMessage(QoSLevel.Unacknowledged, MessageType.Normal, string.Empty, To, Xml.ToString(),
439 string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, FlagStanzaAsSent, ResponseState);
440 }
441
442 Task _ = Task.Delay(10000).ContinueWith((_2) =>
443 StanzaSent.TrySetException(new GenericException(new TimeoutException("Unable to send HTTPX data chunk."), null, To)));
444
445 await StanzaSent.Task; // By waiting for chunk to have been sent, transmission is throttled to the network bandwidth.
446 }
447
448 internal class ResponseState : IDisposable
449 {
450 public EventHandlerAsync<HttpxResponseEventArgs> Callback;
451 public EventHandlerAsync<HttpxResponseDataEventArgs> DataCallback;
452 public HttpxResponseEventArgs HttpxResponse = null;
453 public object State;
454
455 private string sha256 = null;
456 private string id = null;
457 private string from = null;
458 private string to = null;
459 private string endpointReference = null;
460 private string symmetricCipherReference = null;
461 private Stream data = null;
462 private XmppClient client;
463 private bool e2e = false;
464 private bool disposeData = false;
465 private bool disposed = false;
466 private IEndToEndEncryption endpointSecurity;
467 private MultiReadSingleWriteObject synchObj = null;
468
469 public void PreparePostBackCall(IEndToEndEncryption EndpointSecurity, string Id, XmppClient Client)
470 {
471 this.synchObj = new MultiReadSingleWriteObject(this);
472 this.endpointSecurity = EndpointSecurity;
473 this.id = Id;
474 this.client = Client;
475 }
476
477 public async Task PostDataReceived(object Sender, Stream Data, string From, string To, string EndpointReference, string SymmetricCipherReference)
478 {
479 if (this.disposed)
480 return;
481
482 if (!await this.synchObj.TryBeginWrite(InternetContent.DefaultTimeout))
483 {
484 this.client.Error("Unable to get access to HTTPX client. Dropping posted response.");
485 return;
486 }
487
488 try
489 {
490 this.from = From;
491 this.to = To;
492 this.endpointReference = EndpointReference;
493 this.symmetricCipherReference = SymmetricCipherReference;
494
495 if (this.sha256 is null)
496 {
497 this.data = new TemporaryStream();
498 await Data.CopyToAsync(this.data);
499 this.disposeData = true;
500
501 this.client.Information("HTTP(S) POST received. Waiting for HTTPX response.");
502 }
503 else
504 {
505 this.client.Information("HTTP(S) POST received.");
506 string Msg = await this.CheckPostedData(Sender, Data);
507 if (!string.IsNullOrEmpty(Msg))
508 throw new BadRequestException(Msg);
509 }
510 }
511 finally
512 {
513 if (!(this.synchObj is null))
514 await this.synchObj.EndWrite();
515 }
516 }
517
518 public async Task Sha256Received(object Sender, string Sha256, bool E2e)
519 {
520 if (this.disposed)
521 return;
522
523 if (!await this.synchObj.TryBeginWrite(InternetContent.DefaultTimeout))
524 {
525 this.client.Error("Unable to get access to HTTPX client. Dropping posted response.");
526 return;
527 }
528
529 try
530 {
531 this.sha256 = Sha256;
532 this.e2e = E2e;
533
534 if (!(this.data is null))
535 await this.CheckPostedData(Sender, this.data);
536 }
537 finally
538 {
539 if (!(this.synchObj is null))
540 await this.synchObj.EndWrite();
541 }
542 }
543
544 private async Task<string> CheckPostedData(object Sender, Stream Data)
545 {
546 try
547 {
548 string CipherLocalName;
549 string CipherNamespace;
550 string Msg;
551
552 Data.Position = 0;
553
554 if (this.e2e)
555 {
556 int i = this.symmetricCipherReference.IndexOf('#');
557
558 if (i < 0)
559 {
560 CipherLocalName = this.symmetricCipherReference;
561 CipherNamespace = string.Empty;
562 }
563 else
564 {
565 CipherLocalName = this.symmetricCipherReference[(i + 1)..];
566 CipherNamespace = this.symmetricCipherReference[..i];
567 }
568
569 if (!this.endpointSecurity.TryGetSymmetricCipher(CipherLocalName, CipherNamespace, out IE2eSymmetricCipher SymmetricCipher))
570 {
571 this.client.Error(Msg = "Symmetric cipher not understood: " + this.symmetricCipherReference);
572 return Msg;
573 }
574
575 Stream Decrypted = await this.endpointSecurity.Decrypt(this.endpointReference, this.id, "POST", this.from, this.to, Data, SymmetricCipher);
576 if (Decrypted is null)
577 {
578 StringBuilder sb = new StringBuilder();
579
580 sb.Append("Unable to decrypt POSTed payload. Endpoint: ");
581 sb.Append(this.endpointReference);
582 sb.Append(", Id: ");
583 sb.Append(this.id);
584 sb.Append(", Type: POST, From: ");
585 sb.Append(this.from);
586 sb.Append(", To: ");
587 sb.Append(this.to);
588 sb.Append(", Cipher: ");
589 sb.Append(this.symmetricCipherReference);
590 sb.Append(", Bytes: ");
591 sb.Append(Data.Length.ToString());
592
593 this.client.Error(Msg = sb.ToString());
594 return Msg;
595 }
596
597 if (this.disposeData)
598 this.data?.Dispose();
599
600 this.data = Data = Decrypted;
601 this.disposeData = true;
602 }
603
604 Data.Position = 0;
605 byte[] Digest = Hashes.ComputeSHA256Hash(Data);
606 string DigestBase64 = Convert.ToBase64String(Digest);
607
608 if (DigestBase64 == this.sha256)
609 {
610 this.client.Information("POSTed response validated and accepted.");
611
612 long Count = Data.Length;
613 int BufSize = (int)Math.Min(65536, Count);
614 byte[] Buf = new byte[BufSize];
615
616 Data.Position = 0;
617
618 while (Count > 0)
619 {
620 if (Count < BufSize)
621 {
622 Array.Resize(ref Buf, (int)Count);
623 BufSize = (int)Count;
624 }
625
626 await Data.ReadAllAsync(Buf, 0, BufSize);
627
628 Count -= BufSize;
629
630 HttpxResponseDataEventArgs e = new HttpxResponseDataEventArgs(this.HttpxResponse,
631 true, Buf, string.Empty, Count <= 0, this.State);
632
633 await this.DataCallback.Raise(Sender, e, false);
634 }
635 }
636 else
637 {
638 this.client.Error(Msg = "Dropping POSTed response, as SHA-256 digest did not match reported digest in response.");
639 return Msg;
640 }
641 }
642 finally
643 {
644 this.Dispose();
645 }
646
647 return null;
648 }
649
650 public void Dispose()
651 {
652 if (!this.disposed)
653 {
654 this.disposed = true;
655 this.synchObj?.Dispose();
656 this.synchObj = null;
657
658 if (this.disposeData)
659 {
660 this.data?.Dispose();
661 this.data = null;
662 }
663 }
664 }
665 }
666
667 private Task ResponsePostbackHandler(object Sender, PostBackEventArgs e)
668 {
669 ResponseState ResponseState = (ResponseState)e.State;
670 return ResponseState.PostDataReceived(this, e.Data, e.From, e.To, e.EndpointReference, e.SymmetricCipherReference);
671 }
672
673 private async Task ResponseHandler(object Sender, IqResultEventArgs e)
674 {
675 XmlElement E = e.FirstElement;
676 HttpResponse Response;
677 string StatusMessage;
678 double Version;
679 int StatusCode;
680 ResponseState ResponseState = (ResponseState)e.State;
681 byte[] Data = null;
682 bool HasData = false;
683 bool DisposeResponse = true;
684 ClientChunkRecord Record = null;
685 PendingChunkRecord PendingRecord = null;
686
687 if (e.Ok && !(E is null) && E.LocalName == "resp" && E.NamespaceURI == Namespace)
688 {
689 Version = XML.Attribute(E, "version", 0.0);
690 StatusCode = XML.Attribute(E, "statusCode", 0);
691 StatusMessage = XML.Attribute(E, "statusMessage");
692 Response = new HttpResponse();
693
694
695 foreach (XmlNode N in E.ChildNodes)
696 {
697 switch (N.LocalName)
698 {
699 case "headers":
700 foreach (XmlNode N2 in N.ChildNodes)
701 {
702 switch (N2.LocalName)
703 {
704 case "header":
705 string Key = XML.Attribute((XmlElement)N2, "name");
706 string Value = N2.InnerText;
707
708 Response.SetHeader(Key, Value);
709 break;
710 }
711 }
712 break;
713
714 case "data":
715 foreach (XmlNode N2 in N.ChildNodes)
716 {
717 switch (N2.LocalName)
718 {
719 case "text":
720 MemoryStream ms = new MemoryStream();
721 Response.SetResponseStream(ms);
722 Data = Response.Encoding.GetBytes(N2.InnerText);
723 ms.Write(Data, 0, Data.Length);
724 ms.Position = 0;
725 HasData = true;
726 break;
727
728 case "xml":
729 ms = new MemoryStream();
730 Response.SetResponseStream(ms);
731 Data = Response.Encoding.GetBytes(N2.InnerText);
732 ms.Write(Data, 0, Data.Length);
733 ms.Position = 0;
734 HasData = true;
735 break;
736
737 case "base64":
738 ms = new MemoryStream();
739 Response.SetResponseStream(ms);
740 Data = Convert.FromBase64String(N2.InnerText);
741 ms.Write(Data, 0, Data.Length);
742 ms.Position = 0;
743 HasData = true;
744 break;
745
746 case "chunkedBase64":
747 string StreamId = XML.Attribute((XmlElement)N2, "streamId");
748
749 ResponseState.HttpxResponse = new HttpxResponseEventArgs(e, Response, ResponseState.State, Version, StatusCode, StatusMessage, true, true, null);
750
751 Record = new ClientChunkRecord(this, ResponseState.HttpxResponse,
752 Response, ResponseState.DataCallback, ResponseState.State, StreamId, e.From,
753 e.To, false, null, null);
754
755 PendingRecord = await HttpxChunks.Add(e.From + " " + StreamId, Record);
756
757 DisposeResponse = false;
758 HasData = true;
759 break;
760
761 case "ibb":
762 StreamId = XML.Attribute((XmlElement)N2, "sid");
763
764 ResponseState.HttpxResponse = new HttpxResponseEventArgs(e, Response, ResponseState.State, Version, StatusCode, StatusMessage, true, true, null);
765
766 Record = new ClientChunkRecord(this, ResponseState.HttpxResponse,
767 Response, ResponseState.DataCallback, ResponseState.State, StreamId, e.From,
768 e.To, false, null, null);
769
770 PendingRecord = await HttpxChunks.Add(e.From + " " + StreamId, Record);
771
772 DisposeResponse = false;
773 HasData = true;
774 break;
775
776 case "s5":
777 StreamId = XML.Attribute((XmlElement)N2, "sid");
778 bool E2e = XML.Attribute((XmlElement)N2, "e2e", false);
779
780 ResponseState.HttpxResponse = new HttpxResponseEventArgs(e, Response, ResponseState.State, Version, StatusCode, StatusMessage, true, true, null);
781
782 Record = new ClientChunkRecord(this, ResponseState.HttpxResponse,
783 Response, ResponseState.DataCallback, ResponseState.State, StreamId, e.From,
785
786 PendingRecord = await HttpxChunks.Add(e.From + " " + StreamId, Record);
787
788 DisposeResponse = false;
789 HasData = true;
790 break;
791
792 case "sha256":
793 E2e = XML.Attribute((XmlElement)N2, "e2e", false);
794 string DigestBase64 = N2.InnerText;
795
796 ResponseState.HttpxResponse = new HttpxResponseEventArgs(e, Response, ResponseState.State, Version, StatusCode, StatusMessage, true, true, null);
797
798 Task _ = Task.Run(() => ResponseState.Sha256Received(this, DigestBase64, E2e));
799
800 DisposeResponse = false;
801 HasData = true;
802 break;
803
804 case "sipub":
805 // TODO: Implement File Transfer support.
806 break;
807
808 case "jingle":
809 // TODO: Implement Jingle support.
810 break;
811 }
812 }
813 break;
814 }
815 }
816 }
817 else
818 {
819 Version = 0.0;
820 StatusCode = 503;
821 StatusMessage = "Service Unavailable";
822 Response = new HttpResponse();
823 }
824
825 HttpxResponseEventArgs e2 = ResponseState.HttpxResponse ??
826 new HttpxResponseEventArgs(e, Response, ResponseState.State, Version, StatusCode, StatusMessage, HasData, true, Data);
827
828 try
829 {
830 await ResponseState.Callback.Raise(this, e2, false);
831
832 if (!(PendingRecord is null) && !(Record is null))
833 await PendingRecord.Replay(Record);
834 }
835 finally
836 {
837 if (DisposeResponse)
838 {
839 await Response.DisposeAsync();
840 ResponseState.Dispose();
841 }
842 }
843 }
844
850 public async Task CancelTransfer(string To, string StreamId)
851 {
852 await HttpxChunks.Cancel(To + " " + StreamId);
853
854 StringBuilder Xml = new StringBuilder();
855
856 Xml.Append("<cancel xmlns='");
857 Xml.Append(Namespace);
858 Xml.Append("' streamId='");
859 Xml.Append(StreamId);
860 Xml.Append("'/>");
861
862 if (!(this.e2e is null))
863 {
864 await this.e2e.SendMessage(this.client, E2ETransmission.NormalIfNotE2E, QoSLevel.Unacknowledged,
865 MessageType.Normal, string.Empty, To, Xml.ToString(), string.Empty, string.Empty, string.Empty,
866 string.Empty, string.Empty, null, null);
867 }
868 else
869 await this.client.SendMessage(MessageType.Normal, To, Xml.ToString(), string.Empty, string.Empty, string.Empty, string.Empty, string.Empty);
870 }
871
872 private async Task IbbClient_OnOpen(object Sender, InBandBytestreams.ValidateStreamEventArgs e)
873 {
874 string Key = e.From + " " + e.StreamId;
875
876 if (await HttpxChunks.Contains(Key))
877 e.AcceptStream(this.IbbDataReceived, this.IbbStreamClosed, new object[] { Key, -1, true, null });
878 }
879
880 private async Task IbbDataReceived(object Sender, InBandBytestreams.DataReceivedEventArgs e)
881 {
882 object[] P = (object[])e.State;
883 string Key = (string)P[0];
884 int Nr = (int)P[1];
885 bool ConstantBuffer = (bool)P[2];
886 byte[] PrevData = (byte[])P[3];
887
888 if (await HttpxChunks.Received(Key, Nr, false, ConstantBuffer, PrevData))
889 {
890 Nr++;
891 P[1] = Nr;
892 P[2] = e.ConstantBuffer;
893 P[3] = e.Data;
894 }
895 }
896
897 private async Task IbbStreamClosed(object Sender, InBandBytestreams.StreamClosedEventArgs e)
898 {
899 object[] P = (object[])e.State;
900 string Key = (string)P[0];
901 int Nr = (int)P[1];
902 bool ConstantBuffer = (bool)P[2];
903 byte[] PrevData = (byte[])P[3];
904
905 if (e.Reason == InBandBytestreams.CloseReason.Done)
906 {
907 await HttpxChunks.Received(Key, Nr, true, ConstantBuffer, PrevData);
908 P[2] = null;
909 }
910 else
911 await HttpxChunks.Cancel(Key);
912 }
913
914 private async Task Socks5Proxy_OnOpen(object Sender, P2P.SOCKS5.ValidateStreamEventArgs e)
915 {
916 string Key = e.From + " " + e.StreamId;
917
918 if (await HttpxChunks.TryGetRecord(Key, false) is ClientChunkRecord ClientRec)
919 {
920#if LOG_SOCKS5_EVENTS
921 this.client.Information("Accepting SOCKS5 stream from " + e.From);
922#endif
923 e.AcceptStream(this.Socks5DataReceived, this.Socks5StreamClosed, new Socks5Receiver(Key, e.StreamId,
924 ClientRec.From, ClientRec.To, ClientRec.E2e, ClientRec.EndpointReference, ClientRec.SymmetricCipher));
925 }
926 }
927
928 private class Socks5Receiver
929 {
930 public string Key;
931 public string StreamId;
932 public string From;
933 public string To;
934 public string EndpointReference;
935 public IE2eSymmetricCipher SymmetricCipher;
936 public int State = 0;
937 public int BlockSize;
938 public int BlockPos;
939 public int Nr = 0;
940 public byte[] Block;
941 public bool E2e;
942
943 public Socks5Receiver(string Key, string StreamId, string From, string To, bool E2e, string EndpointReference,
944 IE2eSymmetricCipher SymmetricCipher)
945 {
946 this.Key = Key;
947 this.StreamId = StreamId;
948 this.From = From;
949 this.To = To;
950 this.E2e = E2e;
951 this.EndpointReference = EndpointReference;
952 this.SymmetricCipher = SymmetricCipher;
953 }
954 }
955
956 private async Task Socks5DataReceived(object Sender, P2P.SOCKS5.DataReceivedEventArgs e)
957 {
958 Socks5Receiver Rx = (Socks5Receiver)e.State;
959 ChunkRecord Rec = await HttpxChunks.TryGetRecord(Rx.Key, false);
960
961 if (!(Rec is null))
962 {
963#if LOG_SOCKS5_EVENTS
964 this.client.Information(e.Count.ToString() + " bytes received over SOCKS5 stream " + Rx.Key + ".");
965#endif
966 byte[] Buffer = e.Buffer;
967 int Offset = e.Offset;
968 int Count = e.Count;
969 int d;
970
971 while (Count > 0)
972 {
973 switch (Rx.State)
974 {
975 case 0:
976 Rx.BlockSize = Buffer[Offset++];
977 Count--;
978 Rx.State++;
979 break;
980
981 case 1:
982 Rx.BlockSize <<= 8;
983 Rx.BlockSize |= Buffer[Offset++];
984 Count--;
985
986 if (Rx.BlockSize == 0)
987 {
988 await HttpxChunks.Cancel(Rx.Key);
989 await Rec.ChunkReceived(Rx.Nr++, true, true, Array.Empty<byte>());
990 await e.Stream.DisposeAsync();
991 return;
992 }
993
994 Rx.BlockPos = 0;
995
996 if (Rx.Block is null || Rx.Block.Length != Rx.BlockSize)
997 Rx.Block = new byte[Rx.BlockSize];
998
999 Rx.State++;
1000 break;
1001
1002 case 2:
1003 d = Math.Min(Count, Rx.BlockSize - Rx.BlockPos);
1004
1005 System.Buffer.BlockCopy(Buffer, Offset, Rx.Block, Rx.BlockPos, d);
1006 Offset += d;
1007 Rx.BlockPos += d;
1008 Count -= d;
1009
1010 if (Rx.BlockPos >= Rx.BlockSize)
1011 {
1012 if (Rx.E2e)
1013 {
1014 string Id = Rec.NextId().ToString();
1015 Rx.Block = await this.e2e.Decrypt(Rx.EndpointReference, Id, Rx.StreamId, Rx.From, Rx.To, Rx.Block, Rx.SymmetricCipher);
1016 if (Rx.Block is null)
1017 {
1018 string Message = "Decryption of chunk " + Rx.Nr.ToString() + " failed.";
1019#if LOG_SOCKS5_EVENTS
1020 this.client.Error(Message);
1021#endif
1022 await Rec.Fail(Message);
1023 await e.Stream.DisposeAsync();
1024 return;
1025 }
1026 }
1027
1028#if LOG_SOCKS5_EVENTS
1029 this.client.Information("Chunk " + Rx.Nr.ToString() + " received and forwarded.");
1030#endif
1031 await Rec.ChunkReceived(Rx.Nr++, false, false, Rx.Block);
1032 Rx.State = 0;
1033 }
1034 break;
1035 }
1036 }
1037 }
1038 else
1039 {
1040#if LOG_SOCKS5_EVENTS
1041 this.client.Warning(e.Count.ToString() + " bytes received over SOCKS5 stream " + Rx.Key + " and discarded.");
1042#endif
1043 await e.Stream.DisposeAsync();
1044 }
1045 }
1046
1047 private async Task Socks5StreamClosed(object Sender, P2P.SOCKS5.StreamEventArgs e)
1048 {
1049#if LOG_SOCKS5_EVENTS
1050 this.client.Information("SOCKS5 stream closed.");
1051#endif
1052 Socks5Receiver Rx = (Socks5Receiver)e.State;
1053 ChunkRecord Rec = await HttpxChunks.TryGetRecord(Rx.Key, true);
1054
1055 if (!(Rec is null))
1056 await Rec.ChunkReceived(Rx.Nr++, true, true, Array.Empty<byte>());
1057 }
1058
1067 public Task GetJwtToken(int Seconds, EventHandlerAsync<TokenResponseEventArgs> Callback, object State)
1068 {
1069 return this.GetJwtToken(this.client.Domain, Seconds, Callback, State);
1070 }
1071
1079 public Task GetJwtToken(string Address, int Seconds, EventHandlerAsync<TokenResponseEventArgs> Callback, object State)
1080 {
1081 StringBuilder Xml = new StringBuilder();
1082
1083 Xml.Append("<jwt xmlns='");
1084 Xml.Append(NamespaceJwt);
1085 Xml.Append("' seconds='");
1086 Xml.Append(Seconds.ToString());
1087 Xml.Append("'/>");
1088
1089 return this.client.SendIqGet(Address, Xml.ToString(), async (Sender, e) =>
1090 {
1091 string Token = null;
1092
1093 if (e.Ok && !(e.FirstElement is null) && e.FirstElement.LocalName == "token" && e.FirstElement.NamespaceURI == NamespaceJwt)
1094 Token = e.FirstElement.InnerText;
1095 else
1096 e.Ok = false;
1097
1098 await Callback.Raise(this, new TokenResponseEventArgs(e, Token));
1099 }, State);
1100 }
1101
1110 public Task<string> GetJwtTokenAsync(int Seconds)
1111 {
1112 return this.GetJwtTokenAsync(this.client.Domain, Seconds);
1113 }
1114
1122 public async Task<string> GetJwtTokenAsync(string Address, int Seconds)
1123 {
1124 TaskCompletionSource<string> Result = new TaskCompletionSource<string>();
1125
1126 await this.GetJwtToken(Address, Seconds, (Sender, e) =>
1127 {
1128 if (e.Ok)
1129 Result.TrySetResult(e.Token);
1130 else
1131 Result.TrySetException(e.StanzaError ?? new Exception("Unable to get token."));
1132
1133 return Task.CompletedTask;
1134 }, null);
1135
1136 return await Result.Task;
1137 }
1138 }
1139}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:596
Contains information about a response to a content request.
byte[] Encoded
Encoded object.
string ContentType
Internet Content-Type of encoded object.
void AssertOk()
Asserts response is OK.
Static class managing encoding and decoding of internet content.
static int DefaultTimeout
Default timeout of internet access methods, in milliseconds.
static Task< ContentResponse > EncodeAsync(object Object, Encoding Encoding, params string[] AcceptedContentTypes)
Encodes an object.
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
Generic exception, with meta-data for logging.
void Warning(string Warning)
Called to inform the viewer of a warning state.
void Information(string Comment)
Called to inform the viewer of something.
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Base class for all HTTP fields.
Definition: HttpField.cs:7
string Key
HTTP Field Name
Definition: HttpField.cs:25
string Value
HTTP Field Value
Definition: HttpField.cs:31
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
void SetResponseStream(Stream ResponseStream)
Sets the response stream of the response. Can only be set, if not set before.
async Task DisposeAsync()
Closes the connection and disposes of all resources.
void SetHeader(string FieldName, string Value)
Sets a custom header field value.
Encoding Encoding
Gets the System.Text.Encoding in which the output is written.
Event arguments for responses to IQ queries.
string E2eReference
Reference to End-to-end encryption endpoint used.
bool Ok
If the response is an OK result response (true), or an error response (false).
object State
State object passed to the original request.
XmppException StanzaError
Any stanza error returned.
IE2eSymmetricCipher E2eSymmetricCipher
Type of symmetric cipher used in E2E encryption.
XmlElement FirstElement
First child element of the Response element.
HttpxClient(XmppClient Client, int MaxChunkSize)
HTTPX client.
Definition: HttpxClient.cs:59
InBandBytestreams.IbbClient IbbClient
In-band bytestream client, if supported.
Definition: HttpxClient.cs:97
Task POST(string To, string Resource, Stream DataStream, string ContentType, EventHandlerAsync< HttpxResponseEventArgs > Callback, EventHandlerAsync< HttpxResponseDataEventArgs > DataCallback, object State, params HttpField[] Headers)
Performs a HTTP POST request.
Definition: HttpxClient.cs:224
Task GetJwtToken(string Address, int Seconds, EventHandlerAsync< TokenResponseEventArgs > Callback, object State)
Gets a JWT token from a token factory addressed by Address .
HttpxClient(XmppClient Client, IEndToEndEncryption E2e, int MaxChunkSize)
HTTPX client.
Definition: HttpxClient.cs:70
const string NamespaceHeaders
http://jabber.org/protocol/shim
Definition: HttpxClient.cs:46
Task GetJwtToken(int Seconds, EventHandlerAsync< TokenResponseEventArgs > Callback, object State)
Gets a JWT token from the server to which the client is connceted. The JWT token encodes the current ...
async Task POST(string To, string Resource, object Data, EventHandlerAsync< HttpxResponseEventArgs > Callback, EventHandlerAsync< HttpxResponseDataEventArgs > DataCallback, object State, params HttpField[] Headers)
Performs a HTTP POST request.
Definition: HttpxClient.cs:166
IPostResource PostResource
If responses can be posted to a specific resource.
Definition: HttpxClient.cs:129
const string Namespace
urn:xmpp:http
Definition: HttpxClient.cs:36
override void Dispose()
Disposes of the extension.
Definition: HttpxClient.cs:135
const string NamespaceJwt
urn:xmpp:http
Definition: HttpxClient.cs:41
const string ExtensionId
String identifying the extension on the client.
Definition: HttpxClient.cs:31
async Task CancelTransfer(string To, string StreamId)
Requests the transfer of a stream to be cancelled.
Definition: HttpxClient.cs:850
Task GET(string To, string Resource, EventHandlerAsync< HttpxResponseEventArgs > Callback, EventHandlerAsync< HttpxResponseDataEventArgs > DataCallback, object State, params HttpField[] Headers)
Performs an HTTP GET request.
Definition: HttpxClient.cs:150
IEndToEndEncryption E2e
Optional end-to-end encryption interface to use in requests.
Definition: HttpxClient.cs:88
async Task< string > GetJwtTokenAsync(string Address, int Seconds)
Gets a JWT token from a token factory addressed by Address .
Task< string > GetJwtTokenAsync(int Seconds)
Gets a JWT token from the server to which the client is connceted. The JWT token encodes the current ...
override string[] Extensions
Implemented extensions.
Definition: HttpxClient.cs:82
Task Request(string To, string Method, string LocalResource, EventHandlerAsync< HttpxResponseEventArgs > Callback, EventHandlerAsync< HttpxResponseDataEventArgs > DataCallback, object State, params HttpField[] Headers)
Performs an HTTP request.
Definition: HttpxClient.cs:255
async Task Request(string To, string Method, string LocalResource, double HttpVersion, IEnumerable< HttpField > Headers, Stream DataStream, EventHandlerAsync< HttpxResponseEventArgs > Callback, EventHandlerAsync< HttpxResponseDataEventArgs > DataCallback, object State)
Performs an HTTP request.
Definition: HttpxClient.cs:273
P2P.SOCKS5.Socks5Proxy Socks5Proxy
SOCKS5 proxy, if supported.
Definition: HttpxClient.cs:113
async Task POST(string To, string Resource, byte[] Data, string ContentType, EventHandlerAsync< HttpxResponseEventArgs > Callback, EventHandlerAsync< HttpxResponseDataEventArgs > DataCallback, object State, params HttpField[] Headers)
Performs a HTTP POST request.
Definition: HttpxClient.cs:187
Class sending and receiving binary streams over XMPP using XEP-0047: In-band Bytestreams: https://xmp...
Definition: IbbClient.cs:20
Class managing a SOCKS5 proxy associated with the current XMPP server.
Definition: Socks5Proxy.cs:19
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:58
Task SendMessage(MessageType Type, string To, string CustomXml, string Body, string Subject, string Language, string ThreadId, string ParentThreadId)
Sends a simple chat message
Definition: XmppClient.cs:5447
string Domain
Current Domain.
Definition: XmppClient.cs:3492
Task< uint > SendIqSet(string To, string Xml, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends an IQ Set request.
Definition: XmppClient.cs:3646
Task< uint > SendIqGet(string To, string Xml, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends an IQ Get request.
Definition: XmppClient.cs:3598
Base class for XMPP Extensions.
XmppClient client
XMPP Client used by the extension.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
XmppClient Client
XMPP Client.
Manages a temporary stream. Contents is kept in-memory, if below a memory threshold,...
Represents an object that allows single concurrent writers but multiple concurrent readers....
virtual Task EndWrite()
Ends a writing session of the object. Must be called once for each call to BeginWrite or successful c...
virtual async Task< bool > TryBeginWrite(int Timeout)
Waits, at most Timeout milliseconds, until object ready for writing. Each successful call to TryBegi...
Contains methods for simple hash calculations.
Definition: Hashes.cs:57
static byte[] ComputeSHA256Hash(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:469
Interface for HTTP(S) Post-back resources. These can be used to allow HTTPX servers to HTTP POST back...
Task< string > GetUrl(EventHandlerAsync< PostBackEventArgs > Callback, object State)
Gets a Post-back URL
Interface for symmetric ciphers.
End-to-end encryption interface.
Task< uint > SendIqSet(XmppClient Client, E2ETransmission E2ETransmission, string To, string Xml, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends an IQ Set request.
bool TryGetSymmetricCipher(string LocalName, string Namespace, out IE2eSymmetricCipher Cipher)
Tries to get a symmetric cipher from a reference.
Task< byte[]> Decrypt(string EndpointReference, string Id, string Type, string From, string To, byte[] Data, IE2eSymmetricCipher SymmetricCipher)
Decrypts binary data received from an XMPP client out of band.
Task SendMessage(XmppClient Client, E2ETransmission E2ETransmission, QoSLevel QoS, MessageType Type, string Id, string To, string CustomXml, string Body, string Subject, string Language, string ThreadId, string ParentThreadId, EventHandlerAsync< DeliveryEventArgs > DeliveryCallback, object State)
Sends an end-to-end encrypted message, if possible. If recipient does not support end-to-end encrypti...
Definition: ImplTypes.g.cs:58
QoSLevel
Quality of Service Level for asynchronous messages. Support for QoS Levels must be supported by the r...
Definition: QoSLevel.cs:8
MessageType
Type of message received.
Definition: MessageType.cs:7
E2ETransmission
End-to-end encryption mode.