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