ObjXMPP  Check-in [abf66b5c9b]

Overview
Comment:Use dot syntax
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: abf66b5c9bef6eb979cc63a8036e6c02869606970a4d26ee478ea3d8faf7bb52
User & Date: js on 2019-03-16 20:58:13
Other Links: manifest | tags
Context
2019-10-03
23:42
Adjust to ObjFW changes check-in: eafae30990 user: js tags: trunk
2019-03-16
20:58
Use dot syntax check-in: abf66b5c9b user: js tags: trunk
2019-01-27
15:29
Fix connecting to servers with SRV records check-in: d5b95a3e8c user: js tags: trunk
Changes

Modified src/XMPPConnection.h from [8e9ce8e74b] to [3eae11716b].

143
144
145
146
147
148
149
150

151
152
153
154
155
156
157
143
144
145
146
147
148
149

150
151
152
153
154
155
156
157







-
+







@end

/*!
 * @brief A class which abstracts a connection to an XMPP service.
 */
@interface XMPPConnection: OFObject
{
	OF_KINDOF(OFTCPSocket *) _socket;
	OFTCPSocket *_socket;
	char _buffer[XMPP_CONNECTION_BUFFER_LENGTH];
	OFXMLParser *_parser, *_oldParser;
	OFXMLElementBuilder *_elementBuilder, *_oldElementBuilder;
	OFString *_Nullable _username, *_Nullable _password, *_Nullable _server;
	OFString *_Nullable _resource;
	bool _usesAnonymousAuthentication;
	OFString *_Nullable _privateKeyFile, *_Nullable _certificateFile;
231
232
233
234
235
236
237
238

239
240
241
242
243
244
245
231
232
233
234
235
236
237

238
239
240
241
242
243
244
245







-
+







 * @brief An object for data storage, conforming to the XMPPStorage protocol.
 */
@property OF_NULLABLE_PROPERTY (nonatomic, assign) id <XMPPStorage> dataStorage;

/*!
 * @brief The socket used for the connection.
 */
@property (readonly, nonatomic) OF_KINDOF(OFTCPSocket *) socket;
@property (readonly, nonatomic) OFTCPSocket *socket;

/*!
 * @brief Whether encryption is required.
 */
@property (nonatomic) bool encryptionRequired;

/*!

Modified src/XMPPConnection.m from [c7bf092d14] to [c9e6b14c30].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







/*
 * Copyright (c) 2010, 2011, 2012, 2013, 2015, 2016, 2017, 2018
 * Copyright (c) 2010, 2011, 2012, 2013, 2015, 2016, 2017, 2018, 2019
 *   Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2011, 2012, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
139
140
141
142
143
144
145
146

147
148
149
150
151
152
153
139
140
141
142
143
144
145

146
147
148
149
150
151
152
153







-
+







{
	OFString *old = _username;

	if (username != nil) {
		char *node;
		Stringprep_rc rc;

		if ((rc = stringprep_profile([username UTF8String], &node,
		if ((rc = stringprep_profile(username.UTF8String, &node,
		    "SASLprep", 0)) != STRINGPREP_OK)
			@throw [XMPPStringPrepFailedException
			    exceptionWithConnection: self
					    profile: @"SASLprep"
					     string: username];

		@try {
165
166
167
168
169
170
171
172

173
174
175
176
177
178
179
165
166
167
168
169
170
171

172
173
174
175
176
177
178
179







-
+







{
	OFString *old = _resource;

	if (resource != nil) {
		char *res;
		Stringprep_rc rc;

		if ((rc = stringprep_profile([resource UTF8String], &res,
		if ((rc = stringprep_profile(resource.UTF8String, &res,
		    "Resourceprep", 0)) != STRINGPREP_OK)
			@throw [XMPPStringPrepFailedException
			    exceptionWithConnection: self
					    profile: @"Resourceprep"
					     string: resource];

		@try {
204
205
206
207
208
209
210
211

212
213
214
215
216
217
218
204
205
206
207
208
209
210

211
212
213
214
215
216
217
218







-
+







	OFString *oldDomain = _domain;
	OFString *oldDomainToASCII = _domainToASCII;

	if (domain != nil) {
		char *srv;
		Stringprep_rc rc;

		if ((rc = stringprep_profile([domain UTF8String], &srv,
		if ((rc = stringprep_profile(domain.UTF8String, &srv,
		    "Nameprep", 0)) != STRINGPREP_OK)
			@throw [XMPPStringPrepFailedException
			    exceptionWithConnection: self
					    profile: @"Nameprep"
					     string: domain];

		@try {
235
236
237
238
239
240
241
242

243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260

261
262
263
264
265
266

267
268
269
270
271
272
273
235
236
237
238
239
240
241

242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259

260
261
262
263
264
265

266
267
268
269
270
271
272
273







-
+

















-
+





-
+







{
	OFString *old = _password;

	if (password != nil) {
		char *pass;
		Stringprep_rc rc;

		if ((rc = stringprep_profile([password UTF8String], &pass,
		if ((rc = stringprep_profile(password.UTF8String, &pass,
		    "SASLprep", 0)) != STRINGPREP_OK)
			@throw [XMPPStringPrepFailedException
			    exceptionWithConnection: self
					    profile: @"SASLprep"
					     string: password];

		@try {
			_password = [[OFString alloc] initWithUTF8String: pass];
		} @finally {
			free(pass);
		}
	} else
		_password = nil;

	[old release];
}

-     (void)socket: (OF_KINDOF(OFTCPSocket *))sock
-     (void)socket: (OFTCPSocket *)sock
  didConnectToHost: (OFString *)host
	      port: (uint16_t)port
	 exception: (id)exception
{
	if (exception != nil) {
		if ([_nextSRVRecords count] > 0) {
		if (_nextSRVRecords.count > 0) {
			[self xmpp_tryNextSRVRecord];
			return;
		}

		[_delegates broadcastSelector: @selector(connection:
						   didThrowException:)
				   withObject: self
282
283
284
285
286
287
288
289

290
291
292
293
294
295


296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315

316
317
318
319
320
321
322
323

324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344

345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365

366
367
368
369
370
371
372
282
283
284
285
286
287
288

289
290
291
292
293


294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314

315
316
317
318
319
320
321
322

323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343

344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364

365
366
367
368
369
370
371
372







-
+




-
-
+
+



















-
+







-
+




















-
+




















-
+







}

- (void)xmpp_tryNextSRVRecord
{
	OFSRVDNSResourceRecord *record =
	    [[[_nextSRVRecords objectAtIndex: 0] copy] autorelease];

	if ([_nextSRVRecords count] == 0) {
	if (_nextSRVRecords.count == 0) {
		[_nextSRVRecords release];
		_nextSRVRecords = nil;
	}

	[_socket asyncConnectToHost: [record target]
			       port: [record port]];
	[_socket asyncConnectToHost: record.target
			       port: record.port];
}

-	(void)resolver: (OFDNSResolver *)resolver
  didResolveDomainName: (OFString *)domainName
	 answerRecords: (OFDictionary *)answerRecords
      authorityRecords: (OFDictionary *)authorityRecords
     additionalRecords: (OFDictionary *)additionalRecords
	     exception: (id)exception
{
	OFMutableArray *records = [OFMutableArray array];

	if (exception != nil) {
		[_delegates
		    broadcastSelector: @selector(connection:didThrowException:)
			   withObject: self
			   withObject: exception];
		return;
	}

	for (OF_KINDOF(OFDNSResourceRecord *) record in
	for (OFDNSResourceRecord *record in
	    [answerRecords objectForKey: domainName])
		if ([record isKindOfClass: [OFSRVDNSResourceRecord class]])
		       [records addObject: record];

	/* TODO: Sort records */
	[records makeImmutable];

	if ([records count] == 0) {
	if (records.count == 0) {
		/* Fall back to A / AAAA record. */
		[_socket asyncConnectToHost: _domainToASCII
				       port: _port];
		return;
	}

	[_nextSRVRecords release];
	_nextSRVRecords = nil;
	_nextSRVRecords = [records mutableCopy];
	[self xmpp_tryNextSRVRecord];
}

- (void)asyncConnect
{
	void *pool = objc_autoreleasePoolPush();

	if (_socket != nil)
		@throw [OFAlreadyConnectedException exception];

	_socket = [[OFTCPSocket alloc] init];
	[(OFTCPSocket *)_socket setDelegate: self];
	[_socket setDelegate: self];

	if (_server != nil)
		[_socket asyncConnectToHost: _server
				       port: _port];
	else {
		OFString *SRVDomain = [_domainToASCII
		    stringByPrependingString: @"_xmpp-client._tcp."];
		[[OFThread DNSResolver]
		    asyncResolveHost: SRVDomain
			 recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN
			  recordType: OF_DNS_RESOURCE_RECORD_TYPE_SRV
			    delegate: self];
	}

	objc_autoreleasePoolPop(pool);
}

-  (bool)xmpp_parseBuffer: (const void *)buffer
		   length: (size_t)length
{
	if ([_socket isAtEndOfStream]) {
	if (_socket.atEndOfStream) {
		[_delegates broadcastSelector: @selector(connectionWasClosed:
						   error:)
				   withObject: self
				   withObject: nil];
		return false;
	}

444
445
446
447
448
449
450

451
452
453

454
455
456

457
458
459
460
461
462


463
464
465
466
467



468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488

489
490
491
492
493
494
495
496
497
498
499

500
501

502
503
504
505


506
507
508
509
510
511
512
444
445
446
447
448
449
450
451
452
453

454
455
456

457
458
459
460
461


462
463
464
465



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488

489
490
491
492
493
494
495
496
497
498
499

500
501

502
503
504


505
506
507
508
509
510
511
512
513







+


-
+


-
+




-
-
+
+


-
-
-
+
+
+




















-
+










-
+

-
+


-
-
+
+







}

- (bool)checkCertificateAndGetReason: (OFString **)reason
{
	X509Certificate *cert;
	OFDictionary *SANs;
	bool serviceSpecific = false;
	SSLSocket *socket = (SSLSocket *)_socket;

	@try {
		[_socket verifyPeerCertificate];
		[socket verifyPeerCertificate];
	} @catch (SSLInvalidCertificateException *e) {
		if (reason != NULL)
			*reason = [[[e reason] copy] autorelease];
			*reason = e.reason;

		return false;
	}

	cert = [_socket peerCertificate];
	SANs = [cert subjectAlternativeName];
	cert = socket.peerCertificate;
	SANs = cert.subjectAlternativeName;

	if ([[SANs objectForKey: @"otherName"]
		objectForKey: OID_SRVName] != nil ||
	     [SANs objectForKey: @"dNSName"] != nil ||
	     [SANs objectForKey: @"uniformResourceIdentifier"] != nil)
	    objectForKey: OID_SRVName] != nil ||
	    [SANs objectForKey: @"dNSName"] != nil ||
	    [SANs objectForKey: @"uniformResourceIdentifier"] != nil)
		serviceSpecific = true;

	if ([cert hasSRVNameMatchingDomain: _domainToASCII
				   service: @"xmpp-client"] ||
	    [cert hasDNSNameMatchingDomain: _domainToASCII])
		return true;

	if (!serviceSpecific &&
	    [cert hasCommonNameMatchingDomain: _domainToASCII])
		return true;

	return false;
}

- (void)sendStanza: (OFXMLElement *)element
{
	[_delegates broadcastSelector: @selector(connection:didSendElement:)
			   withObject: self
			   withObject: element];

	[_socket writeString: [element XMLString]];
	[_socket writeString: element.XMLString];
}

-   (void)sendIQ: (XMPPIQ *)IQ
  callbackTarget: (id)target
	selector: (SEL)selector
{
	void *pool = objc_autoreleasePoolPush();
	XMPPCallback *callback;
	OFString *ID, *key;

	if ((ID = [IQ ID]) == nil) {
	if ((ID = IQ.ID) == nil) {
		ID = [self generateStanzaID];
		[IQ setID: ID];
		IQ.ID = ID;
	}

	if ((key = [[IQ to] fullJID]) == nil)
		key = [_JID bareJID];
	if ((key = IQ.to.fullJID) == nil)
		key = _JID.bareJID;
	if (key == nil) // Only happens for resource bind
		key = @"bind";
	key = [key stringByAppendingString: ID];

	callback = [XMPPCallback callbackWithTarget: target
					   selector: selector];
	[_callbacks setObject: callback
521
522
523
524
525
526
527
528

529
530

531
532
533
534


535
536
537
538
539
540
541
522
523
524
525
526
527
528

529
530

531
532
533


534
535
536
537
538
539
540
541
542







-
+

-
+


-
-
+
+







-  (void)sendIQ: (XMPPIQ *)IQ
  callbackBlock: (xmpp_callback_block_t)block
{
	void *pool = objc_autoreleasePoolPush();
	XMPPCallback *callback;
	OFString *ID, *key;

	if ((ID = [IQ ID]) == nil) {
	if ((ID = IQ.ID) == nil) {
		ID = [self generateStanzaID];
		[IQ setID: ID];
		IQ.ID = ID;
	}

	if ((key = [[IQ to] fullJID]) == nil)
		key = [_JID bareJID];
	if ((key = IQ.to.fullJID) == nil)
		key = _JID.bareJID;
	if (key == nil) // Connection not yet bound, can't send stanzas
		@throw [OFInvalidArgumentException exception];
	key = [key stringByAppendingString: ID];

	callback = [XMPPCallback callbackWithBlock: block];
	[_callbacks setObject: callback
		       forKey: key];
573
574
575
576
577
578
579
580
581


582
583
584
585
586
587


588
589
590
591
592
593
594

595
596
597
598
599
600
601

602
603
604

605
606
607
608
609
610
611
612

613
614
615

616
617
618

619
620
621

622
623
624
625
626
627
628
629
630
631
632
633

634
635
636
637
638
639
640
641
642
643
644


645
646
647
648
649
650
651
652
653
654

655
656
657

658
659
660
661
662
663
664
574
575
576
577
578
579
580


581
582
583
584
585
586


587
588
589
590
591
592
593
594

595
596
597
598
599
600
601

602
603
604

605
606
607
608
609
610
611
612

613
614
615

616
617
618

619
620
621

622
623
624
625
626
627
628
629
630
631
632
633

634
635

636
637
638
639
640
641
642


643
644
645
646
647
648
649
650
651
652
653

654
655
656

657
658
659
660
661
662
663
664







-
-
+
+




-
-
+
+






-
+






-
+


-
+







-
+


-
+


-
+


-
+











-
+

-







-
-
+
+









-
+


-
+







	if (![namespace isEqual: XMPP_NS_STREAM]) {
		[self xmpp_sendStreamError: @"invalid-namespace"
				      text: nil];
		return;
	}

	for (OFXMLAttribute *attribute in attributes) {
		if ([[attribute name] isEqual: @"from"] &&
		    ![[attribute stringValue] isEqual: _domain]) {
		if ([attribute.name isEqual: @"from"] &&
		    ![attribute.stringValue isEqual: _domain]) {
			[self xmpp_sendStreamError: @"invalid-from"
					      text: nil];
			return;
		}
		if ([[attribute name] isEqual: @"version"] &&
		    ![[attribute stringValue] isEqual: @"1.0"]) {
		if ([attribute.name isEqual: @"version"] &&
		    ![attribute.stringValue isEqual: @"1.0"]) {
			[self xmpp_sendStreamError: @"unsupported-version"
					      text: nil];
			return;
		}
	}

	[parser setDelegate: _elementBuilder];
	parser.delegate = _elementBuilder;
}

- (void)elementBuilder: (OFXMLElementBuilder *)builder
       didBuildElement: (OFXMLElement *)element
{
	/* Ignore whitespace elements */
	if ([element name] == nil)
	if (element.name == nil)
		return;

	[element setDefaultNamespace: XMPP_NS_CLIENT];
	element.defaultNamespace = XMPP_NS_CLIENT;
	[element setPrefix: @"stream"
	      forNamespace: XMPP_NS_STREAM];

	[_delegates broadcastSelector: @selector(connection:didReceiveElement:)
			   withObject: self
			   withObject: element];

	if ([[element namespace] isEqual: XMPP_NS_CLIENT])
	if ([element.namespace isEqual: XMPP_NS_CLIENT])
		[self xmpp_handleStanza: element];

	if ([[element namespace] isEqual: XMPP_NS_STREAM])
	if ([element.namespace isEqual: XMPP_NS_STREAM])
		[self xmpp_handleStream: element];

	if ([[element namespace] isEqual: XMPP_NS_STARTTLS])
	if ([element.namespace isEqual: XMPP_NS_STARTTLS])
		[self xmpp_handleTLS: element];

	if ([[element namespace] isEqual: XMPP_NS_SASL])
	if ([element.namespace isEqual: XMPP_NS_SASL])
		[self xmpp_handleSASL: element];
}

- (void)elementBuilder: (OFXMLElementBuilder *)builder
  didNotExpectCloseTag: (OFString *)name
		prefix: (OFString *)prefix
	     namespace: (OFString *)ns
{
	if (![name isEqual: @"stream"] || ![prefix isEqual: @"stream"] ||
	    ![ns isEqual: XMPP_NS_STREAM])
		@throw [OFMalformedXMLException exception];
	else {
	else
		[self close];
	}
}

- (void)xmpp_startStream
{
	OFString *langString = @"";

	/* Make sure we don't get any old events */
	[_parser setDelegate: nil];
	[_elementBuilder setDelegate: nil];
	_parser.delegate = nil;
	_elementBuilder.delegate = nil;

	/*
	 * We can't release them now, as we are currently inside them. Release
	 * them the next time the parser returns.
	 */
	_oldParser = _parser;
	_oldElementBuilder = _elementBuilder;

	_parser = [[OFXMLParser alloc] init];
	[_parser setDelegate: self];
	_parser.delegate = self;

	_elementBuilder = [[XMPPXMLElementBuilder alloc] init];
	[_elementBuilder setDelegate: self];
	_elementBuilder.delegate = self;

	if (_language != nil)
		langString = [OFString stringWithFormat: @"xml:lang='%@' ",
							 _language];

	[_socket writeFormat: @"<?xml version='1.0'?>\n"
			      @"<stream:stream to='%@' "
687
688
689
690
691
692
693
694

695
696
697
698
699

700
701
702
703
704
705

706
707
708
709
710
711
712
713
714
715
716
717
718

719
720
721
722
723

724
725
726
727
728
729
730
731
732


733
734
735
736
737

738
739

740
741
742
743
744
745
746
747
748
749
750
751
752
753

754
755
756
757
758
759
760
761
762

763
764
765
766
767
768
769
770
771
772

773
774
775
776
777
778
779
780
781
782
783
784
785
786

787
788
789
790
791
792
793
794
795

796
797
798

799
800
801
802
803
804
805


806
807
808


809
810
811
812
813
814
815

816
817

818
819
820
821
822
823
824
825
826
827
828
829

830
831
832
833

834
835
836
837
838
839
840
841
842
843
844
845
846


847
848
849

850
851
852
853
854
855
856
857
858
859
860
861
862
863
864


865
866
867
868
869
870
871
687
688
689
690
691
692
693

694
695
696
697
698

699
700
701
702
703
704

705
706
707
708
709
710
711
712
713
714
715
716
717

718
719
720
721
722

723
724
725
726
727
728
729
730


731
732
733
734
735
736

737
738

739
740
741
742
743
744
745
746
747
748
749
750
751
752

753
754
755
756
757
758
759
760
761

762
763
764
765
766
767
768
769
770
771

772
773
774
775
776
777
778
779
780
781
782
783
784
785

786
787
788
789
790
791
792
793
794

795
796
797

798
799
800
801
802
803


804
805
806


807
808
809
810
811
812
813
814

815
816

817
818
819
820
821
822
823
824
825
826
827
828

829
830
831
832

833
834
835
836
837
838
839
840
841
842
843
844


845
846
847
848

849
850
851
852
853
854
855
856
857
858
859
860
861
862


863
864
865
866
867
868
869
870
871







-
+




-
+





-
+












-
+




-
+







-
-
+
+




-
+

-
+













-
+








-
+









-
+













-
+








-
+


-
+





-
-
+
+

-
-
+
+






-
+

-
+











-
+



-
+











-
-
+
+


-
+













-
-
+
+







	_streamOpen = _needsSession = _encrypted = false;
	_supportsRosterVersioning = _supportsStreamManagement = false;
	_lastID = 0;
}

- (void)xmpp_handleStanza: (OFXMLElement *)element
{
	if ([[element name] isEqual: @"iq"]) {
	if ([element.name isEqual: @"iq"]) {
		[self xmpp_handleIQ: [XMPPIQ stanzaWithElement: element]];
		return;
	}

	if ([[element name] isEqual: @"message"]) {
	if ([element.name isEqual: @"message"]) {
		[self xmpp_handleMessage:
		    [XMPPMessage stanzaWithElement: element]];
		return;
	}

	if ([[element name] isEqual: @"presence"]) {
	if ([element.name isEqual: @"presence"]) {
		[self xmpp_handlePresence:
		    [XMPPPresence stanzaWithElement: element]];
		return;
	}

	[self xmpp_sendStreamError: @"unsupported-stanza-type"
			      text: nil];
}


- (void)xmpp_handleStream: (OFXMLElement *)element
{
	if ([[element name] isEqual: @"features"]) {
	if ([element.name isEqual: @"features"]) {
		[self xmpp_handleFeatures: element];
		return;
	}

	if ([[element name] isEqual: @"error"]) {
	if ([element.name isEqual: @"error"]) {
		OFString *condition, *reason;
		[self close];

		[_delegates broadcastSelector: @selector(connectionWasClosed:)
				   withObject: self
				   withObject: element];

		condition = [[[element elementsForNamespace:
		    XMPP_NS_XMPP_STREAM] firstObject] name];
		condition = [[element elementsForNamespace:
		    XMPP_NS_XMPP_STREAM].firstObject name];

		if (condition == nil)
			condition = @"undefined";

		reason = [[element
		reason = [element
		    elementForName: @"text"
			 namespace: XMPP_NS_XMPP_STREAM] stringValue];
			 namespace: XMPP_NS_XMPP_STREAM].stringValue;

		@throw [XMPPStreamErrorException
		    exceptionWithConnection: self
				  condition: condition
				     reason: reason];
		return;
	}

	assert(0);
}

- (void)xmpp_handleTLS: (OFXMLElement *)element
{
	if ([[element name] isEqual: @"proceed"]) {
	if ([element.name isEqual: @"proceed"]) {
		/* FIXME: Catch errors here */
		SSLSocket *newSock;

		[_delegates broadcastSelector: @selector(
						   connectionWillUpgradeToTLS:)
				   withObject: self];

		newSock = [[SSLSocket alloc] initWithSocket: _socket];
		[newSock setCertificateVerificationEnabled: false];
		newSock.certificateVerificationEnabled = false;
#if 0
		/* FIXME: Not yet implemented by ObjOpenSSL */
		[newSock setCertificateFile: _certificateFile];
		[newSock setPrivateKeyFile: _privateKeyFile];
		[newSock setPrivateKeyPassphrase: _privateKeyPassphrase];
#endif
		[newSock startTLSWithExpectedHost: nil];
		[_socket release];
		_socket = newSock;
		[(OFTCPSocket *)_socket setDelegate: self];
		[_socket setDelegate: self];

		_encrypted = true;

		[_delegates broadcastSelector: @selector(
						   connectionDidUpgradeToTLS:)
				   withObject: self];

		/* Stream restart */
		[self xmpp_startStream];

		return;
	}

	if ([[element name] isEqual: @"failure"])
	if ([element.name isEqual: @"failure"])
		/* TODO: Find/create an exception to throw here */
		@throw [OFException exception];

	assert(0);
}

- (void)xmpp_handleSASL: (OFXMLElement *)element
{
	if ([[element name] isEqual: @"challenge"]) {
	if ([element.name isEqual: @"challenge"]) {
		OFXMLElement *responseTag;
		OFData *challenge =
		    [OFData dataWithBase64EncodedString: [element stringValue]];
		    [OFData dataWithBase64EncodedString: element.stringValue];
		OFData *response = [_authModule continueWithData: challenge];

		responseTag = [OFXMLElement elementWithName: @"response"
						  namespace: XMPP_NS_SASL];
		if (response) {
			if ([response count] == 0)
				[responseTag setStringValue: @"="];
			if (response.count == 0)
				responseTag.stringValue = @"=";
			else
				[responseTag setStringValue:
				    [response stringByBase64Encoding]];
				responseTag.stringValue =
				    response.stringByBase64Encoding;
		}

		[self sendStanza: responseTag];
		return;
	}

	if ([[element name] isEqual: @"success"]) {
	if ([element.name isEqual: @"success"]) {
		[_authModule continueWithData: [OFData
		    dataWithBase64EncodedString: [element stringValue]]];
		    dataWithBase64EncodedString: element.stringValue]];

		[_delegates broadcastSelector: @selector(
						   connectionWasAuthenticated:)
				   withObject: self];

		/* Stream restart */
		[self xmpp_startStream];

		return;
	}

	if ([[element name] isEqual: @"failure"]) {
	if ([element.name isEqual: @"failure"]) {
		/* FIXME: Do more parsing/handling */
		@throw [XMPPAuthFailedException
		    exceptionWithConnection: self
				     reason: [element XMLString]];
				     reason: element.XMLString];
	}

	assert(0);
}

- (void)xmpp_handleIQ: (XMPPIQ *)IQ
{
	bool handled = false;
	XMPPCallback *callback;
	OFString *key;

	if ((key = [[IQ from] fullJID]) == nil)
		key = [_JID bareJID];
	if ((key = IQ.from.fullJID) == nil)
		key = _JID.bareJID;
	if (key == nil) // Only happens for resource bind
		key = @"bind";
	key = [key stringByAppendingString: [IQ ID]];
	key = [key stringByAppendingString: IQ.ID];

	if ((callback = [_callbacks objectForKey: key])) {
		[callback runWithIQ: IQ
			 connection: self];
		[_callbacks removeObjectForKey: key];
		return;
	}

	handled = [_delegates broadcastSelector: @selector(
						     connection:didReceiveIQ:)
				     withObject: self
				     withObject: IQ];

	if (!handled && ![[IQ type] isEqual: @"error"] &&
	    ![[IQ type] isEqual: @"result"]) {
	if (!handled && ![IQ.type isEqual: @"error"] &&
	    ![IQ.type isEqual: @"result"]) {
		[self sendStanza: [IQ errorIQWithType: @"cancel"
					    condition: @"service-unavailable"]];
	}
}

- (void)xmpp_handleMessage: (XMPPMessage *)message
{
909
910
911
912
913
914
915
916
917


918
919
920
921
922
923
924
909
910
911
912
913
914
915


916
917
918
919
920
921
922
923
924







-
-
+
+







		_supportsRosterVersioning = true;

	if ([element elementForName: @"sm"
			  namespace: XMPP_NS_SM] != nil)
		_supportsStreamManagement = true;

	if (mechs != nil) {
		for (OFXMLElement *mech in [mechs children])
			[mechanisms addObject: [mech stringValue]];
		for (OFXMLElement *mech in mechs.children)
			[mechanisms addObject: mech.stringValue];

		if (_usesAnonymousAuthentication) {
			if (![mechanisms containsObject: @"ANONYMOUS"])
				@throw [XMPPAuthFailedException
				    exceptionWithConnection: self
						     reason: @"No supported "
							     @"auth mechanism"];
989
990
991
992
993
994
995
996
997


998
999
1000


1001
1002
1003
1004
1005
1006
1007
989
990
991
992
993
994
995


996
997
998


999
1000
1001
1002
1003
1004
1005
1006
1007







-
-
+
+

-
-
+
+







	OFData *initialMessage = [_authModule initialMessage];

	authTag = [OFXMLElement elementWithName: @"auth"
				      namespace: XMPP_NS_SASL];
	[authTag addAttributeWithName: @"mechanism"
			  stringValue: authName];
	if (initialMessage != nil) {
		if ([initialMessage count] == 0)
			[authTag setStringValue: @"="];
		if (initialMessage.count == 0)
			authTag.stringValue = @"=";
		else
			[authTag setStringValue:
			    [initialMessage stringByBase64Encoding]];
			authTag.stringValue =
			    initialMessage.stringByBase64Encoding;
	}

	[self sendStanza: authTag];
}

- (void)xmpp_sendResourceBind
{
1034
1035
1036
1037
1038
1039
1040
1041

1042
1043
1044
1045

1046
1047
1048
1049
1050
1051
1052
1053
1054
1055

1056
1057
1058
1059
1060
1061
1062
1063
1064

1065
1066
1067
1068
1069
1070
1071
1034
1035
1036
1037
1038
1039
1040

1041
1042
1043
1044

1045
1046
1047
1048
1049
1050
1051
1052
1053
1054

1055
1056
1057
1058
1059
1060
1061
1062
1063

1064
1065
1066
1067
1068
1069
1070
1071







-
+



-
+









-
+








-
+







	    elementWithName: @"error"
		  namespace: XMPP_NS_STREAM];
	[error setPrefix: @"stream"
	    forNamespace: XMPP_NS_STREAM];
	[error addChild: [OFXMLElement elementWithName: condition
					     namespace: XMPP_NS_XMPP_STREAM]];
	if (text)
		[error addChild: [OFXMLElement
		[error	   addChild: [OFXMLElement
		    elementWithName: @"text"
			  namespace: XMPP_NS_XMPP_STREAM
			stringValue: text]];
	[_parser setDelegate: nil];
	_parser.delegate = nil;
	[self sendStanza: error];
	[self close];
}

- (void)xmpp_handleResourceBindForConnection: (XMPPConnection *)connection
					  IQ: (XMPPIQ *)IQ
{
	OFXMLElement *bindElement, *JIDElement;

	assert([[IQ type] isEqual: @"result"]);
	assert([IQ.type isEqual: @"result"]);

	bindElement = [IQ elementForName: @"bind"
			       namespace: XMPP_NS_BIND];

	assert(bindElement != nil);

	JIDElement = [bindElement elementForName: @"jid"
				       namespace: XMPP_NS_BIND];
	_JID = [[XMPPJID alloc] initWithString: [JIDElement stringValue]];
	_JID = [[XMPPJID alloc] initWithString: JIDElement.stringValue];

	if (_needsSession) {
		[self xmpp_sendSession];
		return;
	}

	[_delegates broadcastSelector: @selector(connection:wasBoundToJID:)
1085
1086
1087
1088
1089
1090
1091
1092

1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106

1107
1108
1109
1110
1111
1112
1113
1085
1086
1087
1088
1089
1090
1091

1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105

1106
1107
1108
1109
1110
1111
1112
1113







-
+













-
+







	    callbackTarget: self
		  selector: @selector(xmpp_handleSessionForConnection:IQ:)];
}

- (void)xmpp_handleSessionForConnection: (XMPPConnection *)connection
				     IQ: (XMPPIQ *)IQ
{
	if (![[IQ type] isEqual: @"result"])
	if (![IQ.type isEqual: @"result"])
		OF_ENSURE(0);

	[_delegates broadcastSelector: @selector(connection:wasBoundToJID:)
			   withObject: self
			   withObject: _JID];
}

- (OFString *)xmpp_IDNAToASCII: (OFString *)domain
{
	OFString *ret;
	char *cDomain;
	Idna_rc rc;

	if ((rc = idna_to_ascii_8z([domain UTF8String],
	if ((rc = idna_to_ascii_8z(domain.UTF8String,
	    &cDomain, IDNA_USE_STD3_ASCII_RULES)) != IDNA_SUCCESS)
		@throw [XMPPIDNATranslationFailedException
		    exceptionWithConnection: self
				  operation: @"ToASCII"
				     string: domain];

	@try {

Modified src/XMPPContact+Private.h from [bbdffdaaee] to [235329a86f].

1
2
3
4
5

6



7
8
9
10
11
12
13
1
2
3
4
5
6

7
8
9
10
11
12

13
14
15





+
-
+
+
+



-



#import "XMPPContact.h"

OF_ASSUME_NONNULL_BEGIN

@interface XMPPContact ()
@property (readwrite, nonatomic, setter=xmpp_setRosterItem:)
- (void)xmpp_setRosterItem: (XMPPRosterItem *)rosterItem;
    XMPPRosterItem *rosterItem;
@property OF_NULLABLE_PROPERTY (retain, nonatomic) XMPPJID *xmpp_lockedOnJID;

- (void)xmpp_setPresence: (XMPPPresence *)presence
		resource: (OFString *)resource;
- (void)xmpp_removePresenceForResource: (OFString *)resource;
- (void)xmpp_setLockedOnJID: (nullable XMPPJID *)JID;
@end

OF_ASSUME_NONNULL_END

Modified src/XMPPContact.m from [b77733fbe8] to [7b7906a48d].

1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







/*
 * Copyright (c) 2013, Florian Zeitz <florob@babelmonkeys.de>
 * Copyright (c) 2013, 2016, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2013, 2016, 2019, Jonathan Schleifer <js@heap.zone>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
53
54
55
56
57
58
59
60

61
62

63
64
65
66
67
68
69
53
54
55
56
57
58
59

60
61

62
63
64
65
66
67
68
69







-
+

-
+







	[super dealloc];
}

- (void)sendMessage: (XMPPMessage *)message
	 connection: (XMPPConnection *)connection
{
	if (_lockedOnJID == nil)
		[message setTo: [_rosterItem JID]];
		message.to = _rosterItem.JID;
	else
		[message setTo: _lockedOnJID];
		message.to = _lockedOnJID;

	[connection sendStanza: message];
}

- (void)xmpp_setRosterItem: (XMPPRosterItem *)rosterItem
{
	XMPPRosterItem *old = _rosterItem;
77
78
79
80
81
82
83
84

85
86
87
88
89
90
91
92
93
94
95
96

97
98
99
100
101
102
103
104
105
77
78
79
80
81
82
83

84
85
86
87
88
89
90
91
92
93
94
95

96
97
98
99
100
101
102
103
104
105







-
+











-
+









	if (resource != nil)
		[_presences setObject: presence
			       forKey: resource];
	else
		[_presences setObject: presence
			       forKey: @""];

	[self xmpp_setLockedOnJID: nil];
	self.xmpp_lockedOnJID = nil;
}

- (void)xmpp_removePresenceForResource: (OFString *)resource
{
	if (resource != nil) {
		[_presences removeObjectForKey: resource];
	} else {
		[_presences release];
		_presences = [[OFMutableDictionary alloc] init];
	}

	[self xmpp_setLockedOnJID: nil];
	self.xmpp_lockedOnJID = nil;
}

- (void)xmpp_setLockedOnJID: (XMPPJID *)JID;
{
	XMPPJID *old = _lockedOnJID;
	_lockedOnJID = [JID retain];
	[old release];
}
@end

Modified src/XMPPContactManager.m from [44efae0635] to [f3f3c997c4].

1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







/*
 * Copyright (c) 2013, Florian Zeitz <florob@babelmonkeys.de>
 * Copyright (c) 2013, 2016, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2013, 2016, 2019, Jonathan Schleifer <js@heap.zone>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
63
64
65
66
67
68
69
70

71
72
73
74
75
76
77
78

79
80
81
82
83
84
85
63
64
65
66
67
68
69

70
71
72
73
74
75
76
77

78
79
80
81
82
83
84
85







-
+







-
+







	[super dealloc];
}


- (void)sendSubscribedToJID: (XMPPJID *)subscriber
{
	XMPPPresence *presence = [XMPPPresence presenceWithType: @"subscribed"];
	[presence setTo: subscriber];
	presence.to = subscriber;
	[_connection sendStanza: presence];
}

- (void)sendUnsubscribedToJID: (XMPPJID *)subscriber
{
	XMPPPresence *presence =
	    [XMPPPresence presenceWithType: @"unsubscribed"];
	[presence setTo: subscriber];
	presence.to = subscriber;
	[_connection sendStanza: presence];
}

- (void)addDelegate: (id <XMPPContactManagerDelegate>)delegate
{
	[_delegates addDelegate: delegate];
}
99
100
101
102
103
104
105
106

107
108
109
110

111
112
113
114
115
116
117
118
119
120
121
122
123
124

125
126
127
128

129
130
131
132
133
134
135
136
137
138
139
140

141
142
143
144
145
146
147
148
149
150
151
152

153
154
155
156
157
158
159
160
161


162
163
164
165
166
167
168
169
170
171
172
173

174
175
176
177
178
179
180

181
182
183
184
185
186
187
188
189
190

191
192
193
194
195
196
197
198
199
200
201
202
203


204
205
206
207
208

209
210
211
212
213
214
99
100
101
102
103
104
105

106
107
108


109
110
111
112
113
114
115
116
117
118
119
120
121
122

123
124
125
126

127
128
129
130
131
132
133
134
135
136
137
138

139
140
141
142
143
144
145
146
147
148
149
150

151
152
153
154
155
156
157
158


159
160
161
162
163
164
165
166
167
168
169
170
171

172
173
174
175
176
177
178

179
180
181
182
183
184
185
186
187
188

189
190
191
192
193
194
195
196
197
198
199
200


201
202
203
204
205
206

207
208
209
210
211
212
213







-
+


-
-
+













-
+



-
+











-
+











-
+







-
-
+
+











-
+






-
+









-
+











-
-
+
+




-
+






				   withObject: self
				   withObject: contact];
	[_contacts release];
	_contacts = nil;

	_contacts = [[OFMutableDictionary alloc] init];

	rosterItems = [roster rosterItems];
	rosterItems = roster.rosterItems;
	for (OFString *bareJID in rosterItems) {
		XMPPContact *contact = [[[XMPPContact alloc] init] autorelease];
		[contact xmpp_setRosterItem:
		    [rosterItems objectForKey: bareJID]];
		contact.rosterItem = [rosterItems objectForKey: bareJID];
		[_contacts setObject: contact
			      forKey: bareJID];
		[_delegates broadcastSelector: @selector(contactManager:
						   didAddContact:)
				   withObject: self
				   withObject: contact];
	}
}

-         (void)roster: (XMPPRoster *)roster
  didReceiveRosterItem: (XMPPRosterItem *)rosterItem
{
	XMPPContact *contact;
	OFString *bareJID = [[rosterItem JID] bareJID];
	OFString *bareJID = rosterItem.JID.bareJID;

	contact = [_contacts objectForKey: bareJID];

	if ([[rosterItem subscription] isEqual: @"remove"]) {
	if ([rosterItem.subscription isEqual: @"remove"]) {
		if (contact != nil)
			[_delegates broadcastSelector: @selector(contactManager:
							   didRemoveContact:)
					   withObject: self
					   withObject: contact];
		[_contacts removeObjectForKey: bareJID];
		return;
	}

	if (contact == nil) {
		contact = [[[XMPPContact alloc] init] autorelease];
		[contact xmpp_setRosterItem: rosterItem];
		contact.rosterItem = rosterItem;
		[_contacts setObject: contact
			     forKey: bareJID];
		[_delegates broadcastSelector: @selector(contactManager:
						   didAddContact:)
				   withObject: self
				   withObject: contact];
	} else {
		[_delegates broadcastSelector: @selector(contact:
						   willUpdateWithRosterItem:)
				   withObject: contact
				   withObject: rosterItem];
		[contact xmpp_setRosterItem: rosterItem];
		contact.rosterItem = rosterItem;
	}
}

-   (void)connection: (XMPPConnection *)connection
  didReceivePresence: (XMPPPresence *)presence
{
	XMPPContact *contact;
	XMPPJID *JID = [presence from];
	OFString *type = [presence type];
	XMPPJID *JID = presence.from;
	OFString *type = presence.type;

	/* Subscription request */
	if ([type isEqual: @"subscribe"]) {
		of_log(@"ObjXMPP: received subscription request");
		[_delegates broadcastSelector: @selector(contactManager:
						 didReceiveSubscriptionRequest:)
				   withObject: self
				   withObject: presence];
		return;
	}

	contact = [_contacts objectForKey: [JID bareJID]];
	contact = [_contacts objectForKey: JID.bareJID];
	if (contact == nil)
		return;

	/* Available presence */
	if ([type isEqual: @"available"]) {
		[contact xmpp_setPresence: presence
				 resource: [JID resource]];
				 resource: JID.resource];
		[_delegates broadcastSelector: @selector(contact:
						   didSendPresence:)
				   withObject: contact
				   withObject: presence];
		return;
	}

	/* Unavailable presence */
	if ([type isEqual: @"unavailable"]) {
		[contact xmpp_removePresenceForResource: [JID resource]];
		[contact xmpp_removePresenceForResource: JID.resource];
		[_delegates broadcastSelector: @selector(contact:
						   didSendPresence:)
				   withObject: contact
				   withObject: presence];
		return;
	}
}

-  (void)connection: (XMPPConnection *)connection
  didReceiveMessage: (XMPPMessage *)message
{
	XMPPJID *JID = [message from];
	XMPPContact *contact = [_contacts objectForKey: [JID bareJID]];
	XMPPJID *JID = message.from;
	XMPPContact *contact = [_contacts objectForKey: JID.bareJID];

	if (contact == nil)
		return;

	[contact xmpp_setLockedOnJID: JID];
	contact.xmpp_lockedOnJID = JID;

	[_delegates broadcastSelector: @selector(contact:didSendMessage:)
			   withObject: contact
			   withObject: message];
}
@end

Modified src/XMPPDiscoEntity.m from [d5ee52dfea] to [d3e937014c].

1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







/*
 * Copyright (c) 2013, Florian Zeitz <florob@babelmonkeys.de>
 * Copyright (c) 2013, 2016, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2013, 2016, 2019, Jonathan Schleifer <js@heap.zone>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
104
105
106
107
108
109
110
111

112
113
114
115
116
117
118
119
120
121
122


123
124
125
126
127
128


129
130
131


132
133

134
135
136
137
138
139
140
141
142
143
144
145

146
147
148
149
150
151
152

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170

171
172
173
174
175
176
177
178


179
180
181
182
183
184
185
104
105
106
107
108
109
110

111
112
113
114
115
116
117
118
119
120


121
122
123
124
125
126


127
128
129


130
131
132

133
134
135
136
137
138
139
140
141
142
143
144

145
146
147
148
149
150
151

152

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168

169

170
171
172
173
174


175
176
177
178
179
180
181
182
183







-
+









-
-
+
+




-
-
+
+

-
-
+
+

-
+











-
+






-
+
-
















-
+
-





-
-
+
+








	[super dealloc];
}

- (void)addDiscoNode: (XMPPDiscoNode *)node
{
	[_discoNodes setObject: node
			forKey: [node node]];
			forKey: node.node];
}

- (OFString *)capsHash
{
	OFMutableString *caps = [OFMutableString string];
	OFSHA1Hash *hash = [OFSHA1Hash cryptoHash];
	OFData *digest;

	for (XMPPDiscoIdentity *identity in _identities)
		[caps appendFormat: @"%@/%@//%@<", [identity category],
		    [identity type], [identity name]];
		[caps appendFormat: @"%@/%@//%@<",
		    identity.category, identity.type, identity.name];

	for (OFString *feature in _features)
		[caps appendFormat: @"%@<", feature];

	[hash updateWithBuffer: [caps UTF8String]
			length: [caps UTF8StringLength]];
	[hash updateWithBuffer: caps.UTF8String
			length: caps.UTF8StringLength];

	digest = [OFData dataWithItems: [hash digest]
				 count: [[hash class] digestSize]];
	digest = [OFData dataWithItems: hash.digest
				 count: hash.digestSize];

	return [digest stringByBase64Encoding];
	return digest.stringByBase64Encoding;
}

- (void)connection: (XMPPConnection *)connection
     wasBoundToJID: (XMPPJID *)JID
{
	_JID = [JID copy];
}

- (bool)connection: (XMPPConnection *)connection
      didReceiveIQ: (XMPPIQ *)IQ
{
	if (![[IQ to] isEqual: _JID])
	if (![IQ.to isEqual: _JID])
		return false;

	OFXMLElement *query = [IQ elementForName: @"query"
				       namespace: XMPP_NS_DISCO_ITEMS];

	if (query != nil) {
		OFString *node =
		OFString *node = [query attributeForName: @"node"].stringValue;
		    [[query attributeForName: @"node"] stringValue];
		if (node == nil)
			return [self xmpp_handleItemsIQ: IQ
					     connection: connection];

		XMPPDiscoNode *responder = [_discoNodes objectForKey: node];
		if (responder != nil)
			return [responder xmpp_handleItemsIQ: IQ
						  connection: connection];

		return false;
	}

	query = [IQ elementForName: @"query"
			 namespace: XMPP_NS_DISCO_INFO];

	if (query != nil) {
		OFString *node =
		OFString *node = [query attributeForName: @"node"].stringValue;
		    [[query attributeForName: @"node"] stringValue];

		if (node == nil)
			return [self xmpp_handleInfoIQ: IQ
					    connection: connection];

		OFString *capsNode = [_capsNode stringByAppendingFormat: @"#%@",
					 [self capsHash]];
		OFString *capsNode =
		    [_capsNode stringByAppendingFormat: @"#%@", self.capsHash];
		if ([capsNode isEqual: node])
			return [self xmpp_handleInfoIQ: IQ
					    connection: connection];

		XMPPDiscoNode *responder = [_discoNodes objectForKey: node];
		if (responder != nil)
			return [responder xmpp_handleInfoIQ: IQ

Modified src/XMPPDiscoIdentity.m from [e06950dbbc] to [45b58d387c].

1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







/*
 * Copyright (c) 2013, Florian Zeitz <florob@babelmonkeys.de>
 * Copyright (c) 2013, 2016, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2013, 2016, 2019, Jonathan Schleifer <js@heap.zone>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
48
49
50
51
52
53
54
55
56
57



58
59
60
61
62
63
64
48
49
50
51
52
53
54



55
56
57
58
59
60
61
62
63
64







-
-
-
+
+
+







{
	self = [super init];

	@try {
		if (category == nil || type == nil)
			@throw [OFInvalidArgumentException exception];

		_category = [category copy];
		_name = [name copy];
		_type = [type copy];
		_category = category.copy;
		_name = name.copy;
		_type = type.copy;
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
107
108
109
110
111
112
113
114
115
116



117
118
119
120
121
122
123
107
108
109
110
111
112
113



114
115
116
117
118
119
120
121
122
123







-
-
-
+
+
+








- (uint32_t)hash
{
	uint32_t hash;

	OF_HASH_INIT(hash);

	OF_HASH_ADD_HASH(hash, [_category hash]);
	OF_HASH_ADD_HASH(hash, [_type hash]);
	OF_HASH_ADD_HASH(hash, [_name hash]);
	OF_HASH_ADD_HASH(hash, _category.hash);
	OF_HASH_ADD_HASH(hash, _type.hash);
	OF_HASH_ADD_HASH(hash, _name.hash);

	OF_HASH_FINALIZE(hash);

	return hash;
}

- (of_comparison_result_t)compare: (id <OFComparing>)object

Modified src/XMPPDiscoNode.m from [fb7f062274] to [270f41c89f].

1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







/*
 * Copyright (c) 2013, Florian Zeitz <florob@babelmonkeys.de>
 * Copyright (c) 2013, 2016, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2013, 2016, 2019, Jonathan Schleifer <js@heap.zone>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
113
114
115
116
117
118
119
120

121
122
123
124
125
126
127
113
114
115
116
117
118
119

120
121
122
123
124
125
126
127







-
+







{
	[_features insertObject: feature];
}

- (void)addChildNode: (XMPPDiscoNode *)node
{
	[_childNodes setObject: node
			forKey: [node node]];
			forKey: node.node];
}

- (bool)xmpp_handleItemsIQ: (XMPPIQ *)IQ
		connection: (XMPPConnection *)connection
{
	XMPPIQ *resultIQ;
	OFXMLElement *response;
139
140
141
142
143
144
145
146
147


148
149
150


151
152

153
154
155
156
157
158
159
139
140
141
142
143
144
145


146
147
148


149
150
151

152
153
154
155
156
157
158
159







-
-
+
+

-
-
+
+

-
+








	for (XMPPDiscoNode *child in _childNodes) {
		OFXMLElement *item =
		    [OFXMLElement elementWithName: @"item"
					namespace: XMPP_NS_DISCO_ITEMS];

		[item addAttributeWithName: @"jid"
			       stringValue: [[child JID] fullJID]];
		if ([child node] != nil)
			       stringValue: child.JID.fullJID];
		if (child.node != nil)
			[item addAttributeWithName: @"node"
				       stringValue: [child node]];
		if ([child name] != nil)
				       stringValue: child.node];
		if (child.name != nil)
			[item addAttributeWithName: @"name"
				       stringValue: [child name]];
				       stringValue: child.name];

		[response addChild: item];
	}

	[connection sendStanza: resultIQ];

	return true;
172
173
174
175
176
177
178
179

180
181
182


183
184

185
186
187
188
189
190
191
172
173
174
175
176
177
178

179
180


181
182
183

184
185
186
187
188
189
190
191







-
+

-
-
+
+

-
+








	for (XMPPDiscoIdentity *identity in _identities) {
		OFXMLElement *identityElement =
		    [OFXMLElement elementWithName: @"identity"
					namespace: XMPP_NS_DISCO_INFO];

		[identityElement addAttributeWithName: @"category"
					  stringValue: [identity category]];
					  stringValue: identity.category];
		[identityElement addAttributeWithName: @"type"
					  stringValue: [identity type]];
		if ([identity name] != nil)
					  stringValue: identity.type];
		if (identity.name != nil)
			[identityElement addAttributeWithName: @"name"
						  stringValue: [identity name]];
						  stringValue: identity.name];

		[response addChild: identityElement];
	}

	for (OFString *feature in _features) {
		OFXMLElement *featureElement =
		    [OFXMLElement elementWithName: @"feature"

Modified src/XMPPEXTERNALAuth.m from [09b7294848] to [e1c935f11f].

1
2

3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
10


+







/*
 * Copyright (c) 2012, Florian Zeitz <florob@babelmonkeys.de>
 * Copyright (c) 2019, Jonathan Schleifer <js@heap.zone>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
40
41
42
43
44
45
46
47
48


49
50
51
52
53
54
41
42
43
44
45
46
47


48
49
50
51
52
53
54
55







-
-
+
+







- (OFData *)initialMessage
{
	OFMutableData *message = [OFMutableData data];

	/* authzid */
	if (_authzid != nil)
		[message addItems: [_authzid UTF8String]
			    count: [_authzid UTF8StringLength]];
		[message addItems: _authzid.UTF8String
			    count: _authzid.UTF8StringLength];

	[message makeImmutable];

	return message;
}
@end

Modified src/XMPPFileStorage.m from [f704cad7e3] to [51fa277e00].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







/*
 * Copyright (c) 2012, Jonathan Schleifer <js@webkeks.org>
 * Copyright (c) 2012, 2019, Jonathan Schleifer <js@webkeks.org>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
46
47
48
49
50
51
52
53
54


55
56
57
58
59
60
61
46
47
48
49
50
51
52


53
54
55
56
57
58
59
60
61







-
-
+
+







	self = [super init];

	@try {
		OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];

		_file = [file copy];
		@try {
			_data = [[[OFData dataWithContentsOfFile: file]
			    messagePackValue] retain];
			_data = [[OFData dataWithContentsOfFile: file]
			    .messagePackValue copy];
		} @catch (id e) {
			_data = [[OFMutableDictionary alloc] init];
		}

		[pool release];
	} @catch (id e) {
		[self release];
71
72
73
74
75
76
77
78

79
80
81
82
83
84
85
86

87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

108
109
110
111
112
113
114
71
72
73
74
75
76
77

78
79
80
81
82
83
84
85

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

107
108
109
110
111
112
113
114







-
+







-
+




















-
+







	[_data release];

	[super dealloc];
}

- (void)save
{
	[[_data messagePackRepresentation] writeToFile: _file];
	[_data.messagePackRepresentation writeToFile: _file];
}

- (void)xmpp_setObject: (id)object
	       forPath: (OFString *)path
{
	OFArray *pathComponents = [path componentsSeparatedByString: @"."];
	OFMutableDictionary *iter = _data;
	size_t i = 0, components = [pathComponents count];
	size_t i = 0, components = pathComponents.count;

	for (OFString *component in pathComponents) {
		if (i++ == components - 1)
			continue;

		OFMutableDictionary *iter2 = [iter objectForKey: component];

		if (iter2 == nil) {
			iter2 = [OFMutableDictionary dictionary];
			[iter setObject: iter2
				 forKey: component];
		}

		iter = iter2;
	}

	if (object != nil)
		[iter setObject: object
			 forKey: [pathComponents lastObject]];
	else
		[iter removeObjectForKey: [pathComponents lastObject]];
		[iter removeObjectForKey: pathComponents.lastObject];
}

- (id)xmpp_objectForPath: (OFString *)path
{
	id object = _data;

	for (OFString *component in [path componentsSeparatedByString: @"."])

Modified src/XMPPIQ.m from [beaa481153] to [a98a2208b8].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







/*
 * Copyright (c) 2011, Jonathan Schleifer <js@webkeks.org>
 * Copyright (c) 2011, 2019, Jonathan Schleifer <js@webkeks.org>
 * Copyright (c) 2011, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
52
53
54
55
56
57
58
59
60
61



62
63
64
65
66
67
68
69
70

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85


86
87
88
89
90
91
92
52
53
54
55
56
57
58



59
60
61
62
63
64
65
66
67
68
69

70
71
72
73
74
75
76
77
78
79
80
81
82
83


84
85
86
87
88
89
90
91
92







-
-
-
+
+
+








-
+













-
-
+
+








	return self;
}

- (XMPPIQ *)resultIQ
{
	XMPPIQ *ret = [XMPPIQ IQWithType: @"result"
				      ID: [self ID]];
	[ret setTo: [self from]];
	[ret setFrom: nil];
				      ID: self.ID];
	ret.to = self.from;
	ret.from = nil;
	return ret;
}

- (XMPPIQ *)errorIQWithType: (OFString *)type
		  condition: (OFString *)condition
		       text: (OFString *)text
{
	XMPPIQ *ret = [XMPPIQ IQWithType: @"error"
				      ID: [self ID]];
				      ID: self.ID];
	void *pool = objc_autoreleasePoolPush();
	OFXMLElement *error = [OFXMLElement elementWithName: @"error"
						  namespace: XMPP_NS_CLIENT];

	[error addAttributeWithName: @"type"
			stringValue: type];
	[error addChild: [OFXMLElement elementWithName: condition
					     namespace: XMPP_NS_STANZAS]];
	if (text)
		[error addChild: [OFXMLElement elementWithName: @"text"
						     namespace: XMPP_NS_STANZAS
						   stringValue: text]];
	[ret addChild: error];
	[ret setTo: [self from]];
	[ret setFrom: nil];
	ret.to = self.from;
	ret.from = nil;

	objc_autoreleasePoolPop(pool);

	return ret;
}

- (XMPPIQ *)errorIQWithType: (OFString *)type

Modified src/XMPPJID.m from [02575a8c15] to [25d212cac9].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







/*
 * Copyright (c) 2011, 2012, 2013, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2011, 2012, 2013, 2019, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2011, 2012, 2013, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
41
42
43
44
45
46
47
48
49
50
51
52
53


54
55



56
57
58


59
60
61
62
63
64





65
66
67
68
69
70
71
72
73








74
75
76






77
78
79
80
81
82
83
41
42
43
44
45
46
47


48
49


50
51


52
53
54
55


56
57
58





59
60
61
62
63
64








65
66
67
68
69
70
71
72
73


74
75
76
77
78
79
80
81
82
83
84
85
86







-
-


-
-
+
+
-
-
+
+
+

-
-
+
+

-
-
-
-
-
+
+
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
+
+
+
+
+
+







+ (instancetype)JIDWithString: (OFString *)string
{
	return [[[self alloc] initWithString: string] autorelease];
}

- (instancetype)initWithString: (OFString *)string
{
	size_t nodesep, resourcesep;

	self = [super init];

	if (string == nil) {
		[self release];
	@try {
		size_t nodesep, resourcesep;
		return nil;
	}

		if (string == nil)
			@throw [OFInvalidArgumentException exception];

	nodesep = [string rangeOfString: @"@"].location;
	resourcesep = [string rangeOfString: @"/"].location;
		nodesep = [string rangeOfString: @"@"].location;
		resourcesep = [string rangeOfString: @"/"].location;

	if (nodesep == SIZE_MAX)
		[self setNode: nil];
	else
		[self setNode:
		    [string substringWithRange: of_range(0, nodesep)]];
		if (nodesep == SIZE_MAX)
			self.node = nil;
		else
			self.node =
			    [string substringWithRange: of_range(0, nodesep)];

	if (resourcesep == SIZE_MAX) {
		[self setResource: nil];
		resourcesep = [string length];
	} else {
		of_range_t range = of_range(resourcesep + 1,
		    [string length] - resourcesep - 1);
		[self setResource: [string substringWithRange: range]];
	}
		if (resourcesep == SIZE_MAX) {
			self.resource = nil;
			resourcesep = string.length;
		} else {
			of_range_t range = of_range(resourcesep + 1,
			    string.length - resourcesep - 1);
			self.resource = [string substringWithRange: range];
		}

	[self setDomain: [string substringWithRange:
	    of_range(nodesep + 1, resourcesep - nodesep - 1)]];
		self.domain = [string substringWithRange:
		    of_range(nodesep + 1, resourcesep - nodesep - 1)];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	[_node release];
111
112
113
114
115
116
117
118

119
120
121
122
123
124
125
114
115
116
117
118
119
120

121
122
123
124
125
126
127
128







-
+








	if (node == nil) {
		[old release];
		_node = nil;
		return;
	}

	if (((rc = stringprep_profile([node UTF8String], &nodepart,
	if (((rc = stringprep_profile(node.UTF8String, &nodepart,
	    "Nodeprep", 0)) != STRINGPREP_OK) || (nodepart[0] == '\0') ||
	    (strlen(nodepart) > 1023))
		@throw [XMPPStringPrepFailedException
		    exceptionWithConnection: nil
				    profile: @"Nodeprep"
				     string: node];

134
135
136
137
138
139
140
141

142
143
144
145
146
147
148
137
138
139
140
141
142
143

144
145
146
147
148
149
150
151







-
+








- (void)setDomain: (OFString *)domain
{
	OFString *old = _domain;
	char *srv;
	Stringprep_rc rc;

	if (((rc = stringprep_profile([domain UTF8String], &srv,
	if (((rc = stringprep_profile(domain.UTF8String, &srv,
	    "Nameprep", 0)) != STRINGPREP_OK) || (srv[0] == '\0') ||
	    (strlen(srv) > 1023))
		@throw [XMPPStringPrepFailedException
		    exceptionWithConnection: nil
				    profile: @"Nameprep"
				     string: domain];

163
164
165
166
167
168
169
170

171
172
173
174
175
176
177
166
167
168
169
170
171
172

173
174
175
176
177
178
179
180







-
+








	if (resource == nil) {
		[old release];
		_resource = nil;
		return;
	}

	if (((rc = stringprep_profile([resource UTF8String], &res,
	if (((rc = stringprep_profile(resource.UTF8String, &res,
	    "Resourceprep", 0)) != STRINGPREP_OK) || (res[0] == '\0') ||
	    (strlen(res) > 1023))
		@throw [XMPPStringPrepFailedException
		    exceptionWithConnection: nil
				    profile: @"Resourceprep"
				     string: resource];

192
193
194
195
196
197
198
199

200
201
202
203

204
205
206

207
208
209
210
211
212
213
195
196
197
198
199
200
201

202
203
204
205

206
207
208

209
210
211
212
213
214
215
216







-
+



-
+


-
+







		return [OFString stringWithFormat: @"%@", _domain];
}

- (OFString *)fullJID
{
	/* If we don't have a resource, the full JID is equal to the bare JID */
	if (_resource == nil)
		return [self bareJID];
		return self.bareJID;

	if (_node != nil)
		return [OFString stringWithFormat: @"%@@%@/%@",
		       _node, _domain, _resource];
		    _node, _domain, _resource];
	else
		return [OFString stringWithFormat: @"%@/%@",
		       _domain, _resource];
		    _domain, _resource];
}

- (OFString *)description
{
	return [self fullJID];
}

234
235
236
237
238
239
240
241
242
243



244
245
246
247
248
249
237
238
239
240
241
242
243



244
245
246
247
248
249
250
251
252







-
-
-
+
+
+







- (uint32_t)hash
{
	uint32_t hash;

	OF_HASH_INIT(hash);

	OF_HASH_ADD_HASH(hash, [_node hash]);
	OF_HASH_ADD_HASH(hash, [_domain hash]);
	OF_HASH_ADD_HASH(hash, [_resource hash]);
	OF_HASH_ADD_HASH(hash, _node.hash);
	OF_HASH_ADD_HASH(hash, _domain.hash);
	OF_HASH_ADD_HASH(hash, _resource.hash);

	OF_HASH_FINALIZE(hash);

	return hash;
}
@end

Modified src/XMPPMessage.m from [49f1d7eed0] to [5fedee525e].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







/*
 * Copyright (c) 2011, 2012, 2013, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2011, 2012, 2013, 2019, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2011, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
87
88
89
90
91
92
93
94
95


96
97
87
88
89
90
91
92
93


94
95
96
97







-
-
+
+


		[self addChild: [OFXMLElement elementWithName: @"body"
						    namespace: XMPP_NS_CLIENT
						  stringValue: body]];
}

- (OFString *)body
{
	return [[self elementForName: @"body"
			   namespace: XMPP_NS_CLIENT] stringValue];
	return [self elementForName: @"body"
			  namespace: XMPP_NS_CLIENT].stringValue;
}
@end

Modified src/XMPPMulticastDelegate.m from [6b55dc1bbf] to [7d0f01d54a].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







/*
 * Copyright (c) 2012, Jonathan Schleifer <js@webkeks.org>
 * Copyright (c) 2012, 2019, Jonathan Schleifer <js@webkeks.org>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
53
54
55
56
57
58
59
60
61


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78


79
80
81
82
83
84
85
53
54
55
56
57
58
59


60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76


77
78
79
80
81
82
83
84
85







-
-
+
+















-
-
+
+







- (void)addDelegate: (id)delegate
{
	[_delegates addItem: &delegate];
}

- (void)removeDelegate: (id)delegate
{
	id *items = [_delegates items];
	size_t i, count = [_delegates count];
	id const *items = _delegates.items;
	size_t i, count = _delegates.count;

	for (i = 0; i < count; i++) {
		if (items[i] != delegate)
			continue;

		[_delegates removeItemAtIndex: i];
		return;
	}
}

- (bool)broadcastSelector: (SEL)selector
	       withObject: (id)object
{
	void *pool = objc_autoreleasePoolPush();
	OFMutableData *currentDelegates = [[_delegates copy] autorelease];
	id *items = [currentDelegates items];
	size_t i, count = [currentDelegates count];
	id const *items = currentDelegates.items;
	size_t i, count = currentDelegates.count;
	bool handled = false;

	for (i = 0; i < count; i++) {
		id responder = items[i];

		if (![responder respondsToSelector: selector])
			continue;
97
98
99
100
101
102
103
104
105


106
107
108
109
110
111
112
97
98
99
100
101
102
103


104
105
106
107
108
109
110
111
112







-
-
+
+








- (bool)broadcastSelector: (SEL)selector
	       withObject: (id)object1
	       withObject: (id)object2
{
	void *pool = objc_autoreleasePoolPush();
	OFMutableData *currentDelegates = [[_delegates copy] autorelease];
	id *items = [currentDelegates items];
	size_t i, count = [currentDelegates count];
	id const *items = currentDelegates.items;
	size_t i, count = currentDelegates.count;
	bool handled = false;

	for (i = 0; i < count; i++) {
		id responder = items[i];

		if (![responder respondsToSelector: selector])
			continue;

Modified src/XMPPPLAINAuth.m from [4ce23edb9d] to [aab4070d6d].

1
2

3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
10


+







/*
 * Copyright (c) 2011, Florian Zeitz <florob@babelmonkeys.de>
 * Copyright (c) 2019, Jonathan Schleifer <js@webkeks.org>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
44
45
46
47
48
49
50
51
52


53
54
55
56
57
58
59


60
61
62
63
64
65
66


67
68
69
70
71
72
45
46
47
48
49
50
51


52
53
54
55
56
57
58


59
60
61
62
63
64
65


66
67
68
69
70
71
72
73







-
-
+
+





-
-
+
+





-
-
+
+







- (OFData *)initialMessage
{
	OFMutableData *message = [OFMutableData data];

	/* authzid */
	if (_authzid != nil)
		[message addItems: [_authzid UTF8String]
			    count: [_authzid UTF8StringLength]];
		[message addItems: _authzid.UTF8String
			    count: _authzid.UTF8StringLength];

	/* separator */
	[message addItem: ""];

	/* authcid */
	[message addItems: [_authcid UTF8String]
		    count: [_authcid UTF8StringLength]];
	[message addItems: _authcid.UTF8String
		    count: _authcid.UTF8StringLength];

	/* separator */
	[message addItem: ""];

	/* passwd */
	[message addItems: [_password UTF8String]
		    count: [_password UTF8StringLength]];
	[message addItems: _password.UTF8String
		    count: _password.UTF8StringLength];

	[message makeImmutable];

	return message;
}
@end

Modified src/XMPPPresence.m from [d9aa17b963] to [47e770589e].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







/*
 * Copyright (c) 2011, 2012, 2013, 2016, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2011, 2012, 2013, 2016, 2019, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2011, 2012, 2013, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
26
27
28
29
30
31
32
33

34
35
36
37
38
39
40
26
27
28
29
30
31
32

33
34
35
36
37
38
39
40







-
+







#include <inttypes.h>

#import "XMPPPresence.h"
#import "namespaces.h"

/* This provides us with sortable values for show values */
static int
show_to_int(OFString *show)
showToInt(OFString *show)
{
	if ([show isEqual: @"chat"])
		return 0;
	if (show == nil)
		return 1;	/* available */
	if ([show isEqual: @"away"])
		return 2;
102
103
104
105
106
107
108
109

110
111
112
113

114
115
116
117
118


119
120
121
122
123
124
125
126
102
103
104
105
106
107
108

109
110
111
112

113
114
115
116


117
118

119
120
121
122
123
124
125







-
+



-
+



-
-
+
+
-







	self = [super initWithElement: element];

	@try {
		OFXMLElement *subElement;

		if ((subElement = [element elementForName: @"show"
						namespace: XMPP_NS_CLIENT]))
			[self setShow: [subElement stringValue]];
			self.show = subElement.stringValue;

		if ((subElement = [element elementForName: @"status"
						namespace: XMPP_NS_CLIENT]))
			[self setStatus: [subElement stringValue]];
			self.status = subElement.stringValue;

		if ((subElement = [element elementForName: @"priority"
						namespace: XMPP_NS_CLIENT]))
			[self setPriority:
			    [OFNumber numberWithIntMax:
			self.priority = [OFNumber
			    numberWithIntMax: subElement.decimalValue];
				[[subElement stringValue] decimalValue]]];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
170
171
172
173
174
175
176
177

178
179
180
181
182
183
184
185
186
187
188
189
190

191
192
193
194
195
196
197
169
170
171
172
173
174
175

176
177
178
179
180
181
182
183
184
185
186
187
188

189
190
191
192
193
194
195
196







-
+












-
+







	old = _status;
	_status = [status copy];
	[old release];
}

- (void)setPriority: (OFNumber *)priority
{
	intmax_t prio = [priority intMaxValue];
	intmax_t prio = priority.intMaxValue;
	OFNumber *old;

	if ((prio < -128) || (prio > 127))
		@throw [OFInvalidArgumentException exception];

	OFXMLElement *oldPriority = [self elementForName: @"priority"
					       namespace: XMPP_NS_CLIENT];

	if (oldPriority != nil)
		[self removeChild: oldPriority];

	OFString *priority_s =
	    [OFString stringWithFormat: @"%" @PRId8, [priority int8Value]];
	    [OFString stringWithFormat: @"%" @PRId8, priority.int8Value];
	[self addChild: [OFXMLElement elementWithName: @"priority"
					    namespace: XMPP_NS_CLIENT
					  stringValue: priority_s]];

	old = _priority;
	_priority = [priority copy];
	[old release];
207
208
209
210
211
212
213
214

215
216
217
218
219
220
221
222
223
224
225
226
227

228
229
230
231

232
233
234
235
236
206
207
208
209
210
211
212

213
214
215
216
217
218
219
220
221
222
223
224
225

226
227
228
229

230
231
232
233
234
235







-
+












-
+



-
+





	if (object == self)
		return OF_ORDERED_SAME;

	if (![(id)object isKindOfClass: [XMPPPresence class]])
		@throw [OFInvalidArgumentException exception];

	otherPresence = (XMPPPresence *)object;
	otherPriority = [otherPresence priority];
	otherPriority = otherPresence.priority;
	if (otherPriority == nil)
		otherPriority = [OFNumber numberWithInt8: 0];

	if (_priority != nil)
		priorityOrder = [_priority compare: otherPriority];
	else
		priorityOrder =
		    [[OFNumber numberWithInt8: 0] compare: otherPriority];

	if (priorityOrder != OF_ORDERED_SAME)
		return priorityOrder;

	otherShow = [otherPresence show];
	otherShow = otherPresence.show;
	if ([_show isEqual: otherShow])
		return OF_ORDERED_SAME;

	if (show_to_int(_show) < show_to_int(otherShow))
	if (showToInt(_show) < showToInt(otherShow))
		return OF_ORDERED_ASCENDING;

	return OF_ORDERED_DESCENDING;
}
@end

Modified src/XMPPRoster.m from [10ea13abbb] to [7c662ebfbc].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







/*
 * Copyright (c) 2011, 2012, 2013, 2016, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2011, 2012, 2013, 2016, 2019, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2012, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
62
63
64
65
66
67
68
69

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

90
91
92
93
94

95
96
97
98
99
100

101
102
103
104
105
106
107
108
109
110
111

112
113

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

134
135
136
137
138


139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

156
157

158
159
160
161
162
163
164
62
63
64
65
66
67
68

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

89
90
91
92
93

94
95
96
97
98
99

100
101
102
103
104
105
106
107
108
109
110

111
112

113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132

133
134
135
136


137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

155
156

157
158
159
160
161
162
163
164







-
+



















-
+




-
+





-
+










-
+

-
+



















-
+



-
-
+
+
















-
+

-
+







	self = [super init];

	@try {
		_rosterItems = [[OFMutableDictionary alloc] init];
		_connection = connection;
		[_connection addDelegate: self];
		_delegates = [[XMPPMulticastDelegate alloc] init];
		_dataStorage = [_connection dataStorage];
		_dataStorage = _connection.dataStorage;
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	[_connection removeDelegate: self];
	[_delegates release];
	[_rosterItems release];

	[super dealloc];
}

- (void)requestRoster
{
	XMPPIQ *iq;
	XMPPIQ *IQ;
	OFXMLElement *query;

	_rosterRequested = true;

	iq = [XMPPIQ IQWithType: @"get"
	IQ = [XMPPIQ IQWithType: @"get"
			     ID: [_connection generateStanzaID]];

	query = [OFXMLElement elementWithName: @"query"
				    namespace: XMPP_NS_ROSTER];

	if ([_connection supportsRosterVersioning]) {
	if (_connection.supportsRosterVersioning) {
		OFString *ver =
		    [_dataStorage stringValueForPath: @"roster.ver"];

		if (ver == nil)
			ver = @"";

		[query addAttributeWithName: @"ver"
				stringValue: ver];
	}

	[iq addChild: query];
	[IQ addChild: query];

	[_connection sendIQ: iq
	[_connection sendIQ: IQ
	     callbackTarget: self
		   selector: @selector(xmpp_handleInitialRosterForConnection:
				IQ:)];
}

- (bool)connection: (XMPPConnection *)connection
      didReceiveIQ: (XMPPIQ *)IQ
{
	OFXMLElement *rosterElement;
	OFXMLElement *element;
	XMPPRosterItem *rosterItem;
	OFString *origin;

	rosterElement = [IQ elementForName: @"query"
				 namespace: XMPP_NS_ROSTER];

	if (rosterElement == nil)
		return false;

	if (![[IQ type] isEqual: @"set"])
	if (![IQ.type isEqual: @"set"])
		return false;

	// Ensure the roster push has been sent by the server
	origin = [[IQ from] fullJID];
	if (origin != nil && ![origin isEqual: [[connection JID] bareJID]])
	origin = IQ.from.fullJID;
	if (origin != nil && ![origin isEqual: connection.JID.bareJID])
		return false;

	element = [rosterElement elementForName: @"item"
				      namespace: XMPP_NS_ROSTER];

	if (element != nil) {
		rosterItem = [self xmpp_rosterItemWithXMLElement: element];

		[_delegates broadcastSelector: @selector(
						   roster:didReceiveRosterItem:)
				   withObject: self
				   withObject: rosterItem];

		[self xmpp_updateRosterItem: rosterItem];
	}

	if ([_connection supportsRosterVersioning]) {
	if (_connection.supportsRosterVersioning) {
		OFString *ver =
		    [[rosterElement attributeForName: @"ver"] stringValue];
		    [rosterElement attributeForName: @"ver"].stringValue;
		[_dataStorage setStringValue: ver
				     forPath: @"roster.ver"];
		[_dataStorage save];
	}

	[connection sendStanza: [IQ resultIQ]];

176
177
178
179
180
181
182
183
184


185
186

187
188

189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209

210
211
212
213
214
215
216
176
177
178
179
180
181
182


183
184
185

186
187

188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208

209
210
211
212
213
214
215
216







-
-
+
+

-
+

-
+




















-
+







				     ID: [_connection generateStanzaID]];
	OFXMLElement *query = [OFXMLElement elementWithName: @"query"
						  namespace: XMPP_NS_ROSTER];
	OFXMLElement *item = [OFXMLElement elementWithName: @"item"
						 namespace: XMPP_NS_ROSTER];

	[item addAttributeWithName: @"jid"
		       stringValue: [[rosterItem JID] bareJID]];
	if ([rosterItem name] != nil)
		       stringValue: rosterItem.JID.bareJID];
	if (rosterItem.name != nil)
		[item addAttributeWithName: @"name"
			       stringValue: [rosterItem name]];
			       stringValue: rosterItem.name];

	for (OFString *group in [rosterItem groups])
	for (OFString *group in rosterItem.groups)
		[item addChild: [OFXMLElement elementWithName: @"group"
						    namespace: XMPP_NS_ROSTER
						  stringValue: group]];

	[query addChild: item];
	[IQ addChild: query];

	[_connection sendStanza: IQ];
}

- (void)deleteRosterItem: (XMPPRosterItem *)rosterItem
{
	XMPPIQ *IQ = [XMPPIQ IQWithType: @"set"
				     ID: [_connection generateStanzaID]];
	OFXMLElement *query = [OFXMLElement elementWithName: @"query"
						  namespace: XMPP_NS_ROSTER];
	OFXMLElement *item = [OFXMLElement elementWithName: @"item"
						 namespace: XMPP_NS_ROSTER];

	[item addAttributeWithName: @"jid"
		       stringValue: [[rosterItem JID] bareJID]];
		       stringValue: rosterItem.JID.bareJID];
	[item addAttributeWithName: @"subscription"
		       stringValue: @"remove"];

	[query addChild: item];
	[IQ addChild: query];

	[_connection sendStanza: IQ];
233
234
235
236
237
238
239
240

241
242
243
244
245
246
247

248
249
250
251


252
253
254
255


256
257
258
259

260
261
262
263

264
265

266
267
268
269
270
271

272
273

274
275

276
277
278
279
280
281
282
283
284


285
286

287
288

289
290
291
292
293
294
295
296
297
298

299
300
301
302
303

304
305
306


307
308
309
310
311
312
313
314
315
316
317

318
319
320
321
322
323
324
325
326

327
328
329
330
331
332
333






334
335
336

337
338
339
340
341
342
343

344
345
346
347
348


349
350
351
352
353
354
355
356
357
358

359
360

361
362
363
364
365
366
367
368
369
233
234
235
236
237
238
239

240
241
242
243
244
245
246

247
248
249


250
251
252
253


254
255
256
257
258

259
260
261
262

263
264

265
266
267
268
269
270

271
272

273
274

275
276
277
278
279
280
281
282


283
284


285
286

287

288
289
290
291
292
293
294
295

296
297
298
299
300

301
302


303
304
305
306
307
308
309
310
311
312
313
314

315
316
317
318
319
320
321
322
323

324







325
326
327
328
329
330
331
332

333
334
335
336
337
338
339

340
341
342
343


344
345
346
347
348
349
350
351
352
353
354

355
356

357
358
359
360
361
362
363
364
365
366







-
+






-
+


-
-
+
+


-
-
+
+



-
+



-
+

-
+





-
+

-
+

-
+







-
-
+
+
-
-
+

-
+
-








-
+




-
+

-
-
+
+










-
+








-
+
-
-
-
-
-
-
-
+
+
+
+
+
+


-
+






-
+



-
-
+
+









-
+

-
+









		@throw [OFInvalidArgumentException exception];

	_dataStorage = dataStorage;
}

- (void)xmpp_updateRosterItem: (XMPPRosterItem *)rosterItem
{
	if ([_connection supportsRosterVersioning]) {
	if (_connection.supportsRosterVersioning) {
		OFMutableDictionary *items = [[[_dataStorage dictionaryForPath:
		    @"roster.items"] mutableCopy] autorelease];

		if (items == nil)
			items = [OFMutableDictionary dictionary];

		if (![[rosterItem subscription] isEqual: @"remove"]) {
		if (![rosterItem.subscription isEqual: @"remove"]) {
			OFMutableDictionary *item = [OFMutableDictionary
			    dictionaryWithKeysAndObjects:
			    @"JID", [[rosterItem JID] bareJID],
			    @"subscription", [rosterItem subscription],
			    @"JID", rosterItem.JID.bareJID,
			    @"subscription", rosterItem.subscription,
			    nil];

			if ([rosterItem name] != nil)
				[item setObject: [rosterItem name]
			if (rosterItem.name != nil)
				[item setObject: rosterItem.name
					 forKey: @"name"];

			if ([rosterItem groups] != nil)
				[item setObject: [rosterItem groups]
				[item setObject: rosterItem.groups
					 forKey: @"groups"];

			[items setObject: item
				  forKey: [[rosterItem JID] bareJID]];
				  forKey: rosterItem.JID.bareJID];
		} else
			[items removeObjectForKey: [[rosterItem JID] bareJID]];
			[items removeObjectForKey: rosterItem.JID.bareJID];

		[_dataStorage setDictionary: items
				    forPath: @"roster.items"];
	}

	if (![[rosterItem subscription] isEqual: @"remove"])
	if (![rosterItem.subscription isEqual: @"remove"])
		[_rosterItems setObject: rosterItem
				 forKey: [[rosterItem JID] bareJID]];
				 forKey: rosterItem.JID.bareJID];
	else
		[_rosterItems removeObjectForKey: [[rosterItem JID] bareJID]];
		[_rosterItems removeObjectForKey: rosterItem.JID.bareJID];
}

- (XMPPRosterItem *)xmpp_rosterItemWithXMLElement: (OFXMLElement *)element
{
	OFString *subscription;
	OFMutableArray *groups = [OFMutableArray array];
	XMPPRosterItem *rosterItem = [XMPPRosterItem rosterItem];
	[rosterItem setJID: [XMPPJID JIDWithString:
	    [[element attributeForName: @"jid"] stringValue]]];
	rosterItem.JID = [XMPPJID JIDWithString:
	    [element attributeForName: @"jid"].stringValue];
	[rosterItem setName:
	    [[element attributeForName: @"name"] stringValue]];
	rosterItem.name = [element attributeForName: @"name"].stringValue;

	subscription = [[element attributeForName:
	subscription = [element attributeForName: @"subscription"].stringValue;
	    @"subscription"] stringValue];

	if (![subscription isEqual: @"none"] &&
	    ![subscription isEqual: @"to"] &&
	    ![subscription isEqual: @"from"] &&
	    ![subscription isEqual: @"both"] &&
	    ![subscription isEqual: @"remove"])
		subscription = @"none";

	[rosterItem setSubscription: subscription];
	rosterItem.subscription = subscription;

	for (OFXMLElement *groupElement in
	    [element elementsForName: @"group"
			   namespace: XMPP_NS_ROSTER])
		[groups addObject: [groupElement stringValue]];
		[groups addObject: groupElement.stringValue];

	if ([groups count] > 0)
		[rosterItem setGroups: groups];
	if (groups.count > 0)
		rosterItem.groups = groups;

	return rosterItem;
}

- (void)xmpp_handleInitialRosterForConnection: (XMPPConnection *)connection
					   IQ: (XMPPIQ *)IQ
{
	OFXMLElement *rosterElement = [IQ elementForName: @"query"
					       namespace: XMPP_NS_ROSTER];

	if ([connection supportsRosterVersioning]) {
	if (connection.supportsRosterVersioning) {
		if (rosterElement == nil) {
			for (OFDictionary *item in
			    [_dataStorage dictionaryForPath: @"roster.items"]) {
				XMPPRosterItem *rosterItem;
				XMPPJID *JID;

				rosterItem = [XMPPRosterItem rosterItem];
				JID = [XMPPJID JIDWithString:
					  [item objectForKey: @"JID"]];
				    [item objectForKey: @"JID"]];
				[rosterItem setJID: JID];
				[rosterItem setName:
				    [item objectForKey: @"name"]];
				[rosterItem setSubscription:
				    [item objectForKey: @"subscription"]];
				[rosterItem setGroups:
				    [item objectForKey: @"groups"]];
				rosterItem.JID = JID;
				rosterItem.name = [item objectForKey: @"name"];
				rosterItem.subscription =
				    [item objectForKey: @"subscription"];
				rosterItem.groups =
				    [item objectForKey: @"groups"];

				[_rosterItems setObject: rosterItem
						 forKey: [JID bareJID]];
						 forKey: JID.bareJID];
			}
		} else
			[_dataStorage setDictionary: nil
					    forPath: @"roster.items"];
	}

	for (OFXMLElement *element in [rosterElement children]) {
	for (OFXMLElement *element in rosterElement.children) {
		void *pool = objc_autoreleasePoolPush();
		XMPPRosterItem *rosterItem;

		if (![[element name] isEqual: @"item"] ||
		    ![[element namespace] isEqual: XMPP_NS_ROSTER])
		if (![element.name isEqual: @"item"] ||
		    ![element.namespace isEqual: XMPP_NS_ROSTER])
			continue;

		rosterItem = [self xmpp_rosterItemWithXMLElement: element];

		[self xmpp_updateRosterItem: rosterItem];

		objc_autoreleasePoolPop(pool);
	}

	if ([connection supportsRosterVersioning] && rosterElement != nil) {
	if (connection.supportsRosterVersioning && rosterElement != nil) {
		OFString *ver =
		    [[rosterElement attributeForName: @"ver"] stringValue];
		    [rosterElement attributeForName: @"ver"].stringValue;
		[_dataStorage setStringValue: ver
				     forPath: @"roster.ver"];
		[_dataStorage save];
	}

	[_delegates broadcastSelector: @selector(rosterWasReceived:)
			   withObject: self];
}
@end

Modified src/XMPPSCRAMAuth.m from [bdb2995bf2] to [0d02b6cf21].

1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







/*
 * Copyright (c) 2011, Florian Zeitz <florob@babelmonkeys.de>
 * Copyright (c) 2011, Jonathan Schleifer <js@webkeks.org>
 * Copyright (c) 2011, 2019, Jonathan Schleifer <js@webkeks.org>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
137
138
139
140
141
142
143

144

145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

161

162
163
164
165
166
167
168
137
138
139
140
141
142
143
144

145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162

163
164
165
166
167
168
169
170







+
-
+
















+
-
+








	if (authzid) {
		OFMutableString *new = [[authzid mutableCopy] autorelease];
		[new replaceOccurrencesOfString: @"="
				     withString: @"=3D"];
		[new replaceOccurrencesOfString: @","
				     withString: @"=2C"];
		[new makeImmutable];
		_authzid = [new retain];
		_authzid = [new copy];
	} else
		_authzid = nil;

	[old release];
}

- (void)setAuthcid: (OFString *)authcid
{
	OFString *old = _authcid;

	if (authcid) {
		OFMutableString *new = [[authcid mutableCopy] autorelease];
		[new replaceOccurrencesOfString: @"="
				     withString: @"=3D"];
		[new replaceOccurrencesOfString: @","
				     withString: @"=2C"];
		[new makeImmutable];
		_authcid = [new retain];
		_authcid = [new copy];
	} else
		_authcid = nil;

	[old release];
}

- (OFData *)initialMessage
189
190
191
192
193
194
195
196
197


198
199
200


201
202
203
204
205
206
207
191
192
193
194
195
196
197


198
199
200


201
202
203
204
205
206
207
208
209







-
-
+
+

-
-
+
+







	_cNonce = [[self xmpp_genNonce] retain];

	[_clientFirstMessageBare release];
	_clientFirstMessageBare = nil;
	_clientFirstMessageBare = [[OFString alloc]
	    initWithFormat: @"n=%@,r=%@", _authcid, _cNonce];

	[ret addItems: [_GS2Header UTF8String]
		count: [_GS2Header UTF8StringLength]];
	[ret addItems: _GS2Header.UTF8String
		count: _GS2Header.UTF8StringLength];

	[ret addItems: [_clientFirstMessageBare UTF8String]
		count: [_clientFirstMessageBare UTF8StringLength]];
	[ret addItems: _clientFirstMessageBare.UTF8String
		count: _clientFirstMessageBare.UTF8StringLength];

	[ret makeImmutable];

	return ret;
}

- (OFData *)continueWithData: (OFData *)data
235
236
237
238
239
240
241
242
243
244



245
246
247
248
249

250
251
252
253
254
255
256
257
258
259
260
261
262
263
264

265
266
267
268
269
270
271
272
273
274
275



276
277
278
279


280
281

282
283
284
285


286
287
288
289
290
291
292


293
294
295
296
297
298
299


300
301
302
303
304
305
306
307
308
309
310
311


312
313
314


315
316
317


318
319
320
321
322
323
324
237
238
239
240
241
242
243



244
245
246
247
248
249
250

251
252
253
254
255
256
257
258
259
260
261
262
263
264
265

266
267
268
269
270
271
272
273
274



275
276
277
278
279


280
281
282

283
284
285


286
287
288
289
290
291
292


293
294
295
296
297
298
299


300
301
302
303
304
305
306
307
308
309
310
311


312
313
314


315
316
317


318
319
320
321
322
323
324
325
326







-
-
-
+
+
+




-
+














-
+








-
-
-
+
+
+


-
-
+
+

-
+


-
-
+
+





-
-
+
+





-
-
+
+










-
-
+
+

-
-
+
+

-
-
+
+







		GOT_ITERCOUNT = 0x04
	} got = 0;

	hash = [[[_hashType alloc] init] autorelease];
	ret = [OFMutableData data];
	authMessage = [OFMutableData data];

	OFString *challenge = [OFString stringWithUTF8String: [data items]
						      length: [data count] *
							      [data itemSize]];
	OFString *challenge = [OFString stringWithUTF8String: data.items
						      length: data.count *
							      data.itemSize];

	for (OFString *component in
	    [challenge componentsSeparatedByString: @","]) {
		OFString *entry = [component substringWithRange:
		    of_range(2, [component length] - 2)];
		    of_range(2, component.length - 2)];

		if ([component hasPrefix: @"r="]) {
			if (![entry hasPrefix: _cNonce])
				@throw [XMPPAuthFailedException
				    exceptionWithConnection: nil
						     reason: @"Received wrong "
							     @"nonce"];

			sNonce = entry;
			got |= GOT_SNONCE;
		} else if ([component hasPrefix: @"s="]) {
			salt = [OFData dataWithBase64EncodedString: entry];
			got |= GOT_SALT;
		} else if ([component hasPrefix: @"i="]) {
			iterCount = [entry decimalValue];
			iterCount = entry.decimalValue;
			got |= GOT_ITERCOUNT;
		}
	}

	if (got != (GOT_SNONCE | GOT_SALT | GOT_ITERCOUNT))
		@throw [OFInvalidServerReplyException exception];

	// Add c=<base64(GS2Header+channelBindingData)>
	tmpArray = [OFMutableData dataWithItems: [_GS2Header UTF8String]
					  count: [_GS2Header UTF8StringLength]];
	if (_plusAvailable && [_connection encrypted]) {
	tmpArray = [OFMutableData dataWithItems: _GS2Header.UTF8String
					  count: _GS2Header.UTF8StringLength];
	if (_plusAvailable && _connection.encrypted) {
		OFData *channelBinding = [((SSLSocket *)[_connection socket])
		    channelBindingDataWithType: @"tls-unique"];
		[tmpArray addItems: [channelBinding items]
			     count: [channelBinding count]];
		[tmpArray addItems: channelBinding.items
			     count: channelBinding.count];
	}
	tmpString = [tmpArray stringByBase64Encoding];
	tmpString = tmpArray.stringByBase64Encoding;
	[ret addItems: "c="
		count: 2];
	[ret addItems: [tmpString UTF8String]
		count: [tmpString UTF8StringLength]];
	[ret addItems: tmpString.UTF8String
		count: tmpString.UTF8StringLength];

	// Add r=<nonce>
	[ret addItem: ","];
	[ret addItems: "r="
		count: 2];
	[ret addItems: [sNonce UTF8String]
		count: [sNonce UTF8StringLength]];
	[ret addItems: sNonce.UTF8String
		count: sNonce.UTF8StringLength];

	/*
	 * IETF RFC 5802:
	 * SaltedPassword := Hi(Normalize(password), salt, i)
	 */
	tmpArray = [OFMutableData dataWithItems: [_password UTF8String]
					  count: [_password UTF8StringLength]];
	tmpArray = [OFMutableData dataWithItems: _password.UTF8String
					  count: _password.UTF8StringLength];
	saltedPassword = [self xmpp_hiWithData: tmpArray
					  salt: salt
				iterationCount: iterCount];

	/*
	 * IETF RFC 5802:
	 * AuthMessage := client-first-message-bare + "," +
	 *		  server-first-message + "," +
	 *		  client-final-message-without-proof
	 */
	[authMessage addItems: [_clientFirstMessageBare UTF8String]
			count: [_clientFirstMessageBare UTF8StringLength]];
	[authMessage addItems: _clientFirstMessageBare.UTF8String
			count: _clientFirstMessageBare.UTF8StringLength];
	[authMessage addItem: ","];
	[authMessage addItems: [data items]
			count: [data count] * [data itemSize]];
	[authMessage addItems: data.items
			count: data.count * data.itemSize];
	[authMessage addItem: ","];
	[authMessage addItems: [ret items]
			count: [ret count]];
	[authMessage addItems: ret.items
			count: ret.count];

	/*
	 * IETF RFC 5802:
	 * ClientKey := HMAC(SaltedPassword, "Client Key")
	 */
	clientKey = [self xmpp_HMACWithKey: saltedPassword
				      data: [OFData dataWithItems: "Client Key"
332
333
334
335
336
337
338
339
340


341
342
343
344
345
346
347
334
335
336
337
338
339
340


341
342
343
344
345
346
347
348
349







-
-
+
+







			length: [_hashType digestSize]];

	/*
	 * IETF RFC 5802:
	 * ClientSignature := HMAC(StoredKey, AuthMessage)
	 */
	clientSignature = [self
	    xmpp_HMACWithKey: [OFData dataWithItems: [hash digest]
					      count: [_hashType digestSize]]
	    xmpp_HMACWithKey: [OFData dataWithItems: hash.digest
					      count: hash.digestSize]
			data: authMessage];

	/*
	 * IETF RFC 5802:
	 * ServerKey := HMAC(SaltedPassword, "Server Key")
	 */
	serverKey = [self xmpp_HMACWithKey: saltedPassword
371
372
373
374
375
376
377
378
379
380



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398



399
400
401

402
403
404
405
406
407
408
373
374
375
376
377
378
379



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397



398
399
400
401
402

403
404
405
406
407
408
409
410







-
-
-
+
+
+















-
-
-
+
+
+


-
+







		[tmpArray addItem: &c];
	}

	// Add p=<base64(ClientProof)>
	[ret addItem: ","];
	[ret addItems: "p="
		count: 2];
	tmpString = [tmpArray stringByBase64Encoding];
	[ret addItems: [tmpString UTF8String]
		count: [tmpString UTF8StringLength]];
	tmpString = tmpArray.stringByBase64Encoding;
	[ret addItems: tmpString.UTF8String
		count: tmpString.UTF8StringLength];

	return ret;
}

- (OFData *)xmpp_parseServerFinalMessage: (OFData *)data
{
	OFString *mess, *value;

	/*
	 * server-final-message already received,
	 * we were just waiting for the last word from the server
	 */
	if (_authenticated)
		return nil;

	mess = [OFString stringWithUTF8String: [data items]
				       length: [data count] * [data itemSize]];
	value = [mess substringWithRange: of_range(2, [mess length] - 2)];
	mess = [OFString stringWithUTF8String: data.items
				       length: data.count * data.itemSize];
	value = [mess substringWithRange: of_range(2, mess.length - 2)];

	if ([mess hasPrefix: @"v="]) {
		if (![value isEqual: [_serverSignature stringByBase64Encoding]])
		if (![value isEqual: _serverSignature.stringByBase64Encoding])
			@throw [XMPPAuthFailedException
			    exceptionWithConnection: nil
					     reason: @"Received wrong "
						     @"ServerSignature"];
		_authenticated = true;
	} else
		@throw [XMPPAuthFailedException exceptionWithConnection: nil
437
438
439
440
441
442
443
444

445
446
447
448
449




450
451
452


453
454
455
456
457
458
459


460
461
462
463
464
465
466
467
468
469

470
471
472


473
474
475

476
477
478


479
480
481
482
483
484
485
439
440
441
442
443
444
445

446
447




448
449
450
451
452


453
454
455
456
457
458
459


460
461
462
463
464
465
466
467
468
469
470

471
472


473
474
475
476

477
478


479
480
481
482
483
484
485
486
487







-
+

-
-
-
-
+
+
+
+

-
-
+
+





-
-
+
+









-
+

-
-
+
+


-
+

-
-
+
+







{
	void *pool = objc_autoreleasePoolPush();
	OFMutableData *k = [OFMutableData data];
	size_t i, kSize, blockSize = [_hashType blockSize];
	uint8_t *kI = NULL, *kO = NULL;
	id <OFCryptoHash> hashI, hashO;

	if ([key itemSize] * [key count] > blockSize) {
	if (key.itemSize * key.count > blockSize) {
		hashI = [[[_hashType alloc] init] autorelease];
		[hashI updateWithBuffer: [key items]
				length: [key itemSize] * [key count]];
		[k addItems: [hashI digest]
		      count: [_hashType digestSize]];
		[hashI updateWithBuffer: key.items
				 length: key.itemSize * key.count];
		[k addItems: hashI.digest
		      count: hashI.digestSize];
	} else
		[k addItems: [key items]
		      count: [key itemSize] * [key count]];
		[k addItems: key.items
		      count: key.itemSize * key.count];

	@try {
		kI = [self allocMemoryWithSize: blockSize];
		kO = [self allocMemoryWithSize: blockSize];

		kSize = [k count];
		memcpy(kI, [k items], kSize);
		kSize = k.count;
		memcpy(kI, k.items, kSize);
		memset(kI + kSize, 0, blockSize - kSize);
		memcpy(kO, kI, blockSize);

		for (i = 0; i < blockSize; i++) {
			kI[i] ^= HMAC_IPAD;
			kO[i] ^= HMAC_OPAD;
		}

		hashI = [[[_hashType alloc] init] autorelease];
		[hashI updateWithBuffer: (char *)kI
		[hashI updateWithBuffer: kI
				 length: blockSize];
		[hashI updateWithBuffer: [data items]
				 length: [data itemSize] * [data count]];
		[hashI updateWithBuffer: data.items
				 length: data.itemSize * data.count];

		hashO = [[[_hashType alloc] init] autorelease];
		[hashO updateWithBuffer: (char *)kO
		[hashO updateWithBuffer: kO
				 length: blockSize];
		[hashO updateWithBuffer: (char *)[hashI digest]
				 length: [_hashType digestSize]];
		[hashO updateWithBuffer: hashI.digest
				 length: hashI.digestSize];
	} @finally {
		[self freeMemory: kI];
		[self freeMemory: kO];
	}

	[hashO retain];

Modified src/XMPPStanza.m from [b2b145f65c] to [127836dc56].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







/*
 * Copyright (c) 2011, 2012, 2013, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2011, 2012, 2013, 2019, Jonathan Schleifer <js@heap.zone>
 * Copyright (c) 2011, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
125
126
127
128
129
130
131
132

133
134
135
136
137

138
139
140

141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158


159
160
161
162


163
164
165

166
167
168

169
170
171
172
173
174
175
125
126
127
128
129
130
131

132
133
134
135
136

137
138
139

140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156


157
158
159
160


161
162
163
164

165
166
167

168
169
170
171
172
173
174
175







-
+




-
+


-
+
















-
-
+
+


-
-
+
+


-
+


-
+







		       stringValue: nil];

	@try {
		if (![name isEqual: @"iq"] && ![name isEqual: @"message"] &&
		    ![name isEqual: @"presence"])
			@throw [OFInvalidArgumentException exception];

		[self setDefaultNamespace: XMPP_NS_CLIENT];
		self.defaultNamespace = XMPP_NS_CLIENT;
		[self setPrefix: @"stream"
		   forNamespace: XMPP_NS_STREAM];

		if (type != nil)
			[self setType: type];
			self.type = type;

		if (ID != nil)
			[self setID: ID];
			self.ID = ID;
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (instancetype)initWithElement: (OFXMLElement *)element
{
	self = [super initWithElement: element];

	@try {
		OFXMLAttribute *attribute;

		if ((attribute = [element attributeForName: @"from"]))
			[self setFrom:
			    [XMPPJID JIDWithString: [attribute stringValue]]];
			self.from =
			    [XMPPJID JIDWithString: attribute.stringValue];

		if ((attribute = [element attributeForName: @"to"]))
			[self setTo:
			    [XMPPJID JIDWithString: [attribute stringValue]]];
			self.to =
			    [XMPPJID JIDWithString: attribute.stringValue];

		if ((attribute = [element attributeForName: @"type"]))
			[self setType: [attribute stringValue]];
			self.type = attribute.stringValue;

		if ((attribute = [element attributeForName: @"id"]))
			[self setID: [attribute stringValue]];
			self.ID = attribute.stringValue;
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
190
191
192
193
194
195
196
197

198
199
200
201
202
203
204
205
206
207
208
209
210

211
212
213
214
215
216
217
190
191
192
193
194
195
196

197
198
199
200
201
202
203
204
205
206
207
208
209

210
211
212
213
214
215
216
217







-
+












-
+







	_from = [from copy];
	[old release];

	[self removeAttributeForName: @"from"];

	if (from != nil)
		[self addAttributeWithName: @"from"
			       stringValue: [from fullJID]];
			       stringValue: from.fullJID];
}

- (void)setTo: (XMPPJID *)to
{
	XMPPJID *old = _to;
	_to = [to copy];
	[old release];

	[self removeAttributeForName: @"to"];

	if (to != nil)
		[self addAttributeWithName: @"to"
			       stringValue: [to fullJID]];
			       stringValue: to.fullJID];
}

- (void)setType: (OFString *)type
{
	OFString *old = _type;
	_type = [type copy];
	[old release];

Modified src/XMPPStreamManagement.m from [42b7f06f4f] to [72de3d76fb].

1
2

3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
10


+







/*
 * Copyright (c) 2012, Florian Zeitz <florob@babelmonkeys.de>
 * Copyright (c) 2019, Jonathan Schleifer <js@heap.zone>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
52
53
54
55
56
57
58
59
60


61
62
63
64
65
66
67
53
54
55
56
57
58
59


60
61
62
63
64
65
66
67
68







-
-
+
+








	[super dealloc];
}

- (void)connection: (XMPPConnection *)connection
 didReceiveElement: (OFXMLElement *)element
{
	OFString *elementName = [element name];
	OFString *elementNS = [element namespace];
	OFString *elementName = element.name;
	OFString *elementNS = element.namespace;

	if ([elementNS isEqual: XMPP_NS_SM]) {
		if ([elementName isEqual: @"enabled"]) {
			_receivedCount = 0;
			return;
		}

95
96
97
98
99
100
101
102

103
104
105
106
107
96
97
98
99
100
101
102

103
104
105
106
107
108







-
+





{
}
*/

- (void)connection: (XMPPConnection *)connection
     wasBoundToJID: (XMPPJID *)JID
{
	if ([connection supportsStreamManagement])
	if (connection.supportsStreamManagement)
		[connection sendStanza:
		    [OFXMLElement elementWithName: @"enable"
					namespace: XMPP_NS_SM]];
}
@end

Modified tests/test.m from [d958b131ca] to [6b93900f41].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







/*
 * Copyright (c) 2010, 2011, Jonathan Schleifer <js@webkeks.org>
 * Copyright (c) 2010, 2011, 2019, Jonathan Schleifer <js@webkeks.org>
 * Copyright (c) 2011, 2012, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://heap.zone/objxmpp/
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
49
50
51
52
53
54
55
56
57
58
59
60
61






62
63
64
65
66
67
68
69
70
71





72
73
74
75
76
77
78
79




80
81
82
83
84
85
86
87





88
89
90
91
92
93
94
95
96
97
98
99
100
101

102
103
104


105
106
107
108
109
110
111
112

113
114
115
116
117
118
119

120
121
122
123
124
125
126
127




128
129
130
131
132
133
134
49
50
51
52
53
54
55






56
57
58
59
60
61
62
63
64
65
66





67
68
69
70
71
72
73
74
75




76
77
78
79
80
81
82
83




84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

102
103


104
105
106
107
108
109
110
111
112

113
114
115
116
117
118
119

120
121
122
123
124




125
126
127
128
129
130
131
132
133
134
135







-
-
-
-
-
-
+
+
+
+
+
+





-
-
-
-
-
+
+
+
+
+




-
-
-
-
+
+
+
+




-
-
-
-
+
+
+
+
+













-
+

-
-
+
+







-
+






-
+




-
-
-
-
+
+
+
+








@implementation AppDelegate
- (void)applicationDidFinishLaunching
{
	OFArray *arguments = [OFApplication arguments];

	XMPPPresence *pres = [XMPPPresence presence];
	[pres setShow: @"xa"];
	[pres setStatus: @"Bored"];
	[pres setPriority: [OFNumber numberWithInt8: 20]];
	[pres setTo: [XMPPJID JIDWithString: @"alice@example.com"]];
	[pres setFrom: [XMPPJID JIDWithString: @"bob@example.org"]];
	assert([[pres XMLString] isEqual: @"<presence to='alice@example.com' "
	pres.show = @"xa";
	pres.status = @"Bored";
	pres.priority = [OFNumber numberWithInt8: 20];
	pres.to = [XMPPJID JIDWithString: @"alice@example.com"];
	pres.from = [XMPPJID JIDWithString: @"bob@example.org"];
	assert([pres.XMLString isEqual: @"<presence to='alice@example.com' "
	    @"from='bob@example.org'><show>xa</show>"
	    @"<status>Bored</status><priority>20</priority>"
	    @"</presence>"]);

	XMPPPresence *pres2 = [XMPPPresence presence];
	[pres2 setShow: @"away"];
	[pres2 setStatus: @"Bored"];
	[pres2 setPriority: [OFNumber numberWithInt8: 23]];
	[pres2 setTo: [XMPPJID JIDWithString: @"alice@example.com"]];
	[pres2 setFrom: [XMPPJID JIDWithString: @"bob@example.org"]];
	pres2.show = @"away";
	pres2.status = @"Bored";
	pres2.priority = [OFNumber numberWithInt8: 23];
	pres2.to = [XMPPJID JIDWithString: @"alice@example.com"];
	pres2.from = [XMPPJID JIDWithString: @"bob@example.org"];

	assert([pres compare: pres2] == OF_ORDERED_ASCENDING);

	XMPPMessage *msg = [XMPPMessage messageWithType: @"chat"];
	[msg setBody: @"Hello everyone"];
	[msg setTo: [XMPPJID JIDWithString: @"jdev@conference.jabber.org"]];
	[msg setFrom: [XMPPJID JIDWithString: @"alice@example.com"]];
	assert([[msg XMLString] isEqual: @"<message type='chat' "
	msg.body = @"Hello everyone";
	msg.to = [XMPPJID JIDWithString: @"jdev@conference.jabber.org"];
	msg.from = [XMPPJID JIDWithString: @"alice@example.com"];
	assert([msg.XMLString isEqual: @"<message type='chat' "
	    @"to='jdev@conference.jabber.org' "
	    @"from='alice@example.com'><body>Hello everyone</body>"
	    @"</message>"]);

	XMPPIQ *iq = [XMPPIQ IQWithType: @"set" ID: @"128"];
	[iq setTo: [XMPPJID JIDWithString: @"juliet@capulet.lit"]];
	[iq setFrom: [XMPPJID JIDWithString: @"romeo@montague.lit"]];
	assert([[iq XMLString] isEqual: @"<iq type='set' id='128' "
	XMPPIQ *IQ = [XMPPIQ IQWithType: @"set"
				     ID: @"128"];
	IQ.to = [XMPPJID JIDWithString: @"juliet@capulet.lit"];
	IQ.from = [XMPPJID JIDWithString: @"romeo@montague.lit"];
	assert([IQ.XMLString isEqual: @"<iq type='set' id='128' "
	    @"to='juliet@capulet.lit' "
	    @"from='romeo@montague.lit'/>"]);

	OFXMLElement *elem = [OFXMLElement elementWithName: @"iq"];
	[elem addAttributeWithName: @"from"
		       stringValue: @"bob@localhost"];
	[elem addAttributeWithName: @"to"
		       stringValue: @"alice@localhost"];
	[elem addAttributeWithName: @"type"
		       stringValue: @"get"];
	[elem addAttributeWithName: @"id"
		       stringValue: @"42"];
	XMPPStanza *stanza = [XMPPStanza stanzaWithElement: elem];
	assert([[elem XMLString] isEqual: [stanza XMLString]]);
	assert([elem.XMLString isEqual: [stanza XMLString]]);
	assert(([[OFString stringWithFormat: @"%@, %@, %@, %@",
	    [[stanza from] fullJID], [[stanza to] fullJID], [stanza type],
	    [stanza ID]] isEqual: @"bob@localhost, alice@localhost, get, 42"]));
	    stanza.from.fullJID, stanza.to.fullJID, stanza.type, stanza.ID]
	    isEqual: @"bob@localhost, alice@localhost, get, 42"]));


	conn = [[XMPPConnection alloc] init];
	[conn addDelegate: self];

	XMPPFileStorage *storage =
	    [[XMPPFileStorage alloc] initWithFile: @"storage.binarypack"];
	[conn setDataStorage: storage];
	conn.dataStorage = storage;

	roster = [[XMPPRoster alloc] initWithConnection: conn];
	[roster addDelegate: self];

	[[XMPPStreamManagement alloc] initWithConnection: conn];

	if ([arguments count] != 3) {
	if (arguments.count != 3) {
		of_log(@"Invalid count of command line arguments!");
		[OFApplication terminateWithStatus: 1];
	}

	[conn setDomain: [arguments objectAtIndex: 0]];
	[conn setUsername: [arguments objectAtIndex: 1]];
	[conn setPassword: [arguments objectAtIndex: 2]];
	[conn setResource: @"ObjXMPP"];
	conn.domain = [arguments objectAtIndex: 0];
	conn.username = [arguments objectAtIndex: 1];
	conn.password = [arguments objectAtIndex: 2];
	conn.resource = @"ObjXMPP";

	[conn asyncConnect];
}

-  (void)connection: (XMPPConnection *)conn
  didReceiveElement: (OFXMLElement *)element
{
143
144
145
146
147
148
149
150

151
152

153
154

155
156
157
158
159
160
161
162
163
164
165

166
167
168
169
170
171

172
173
174
175
176
177

178
179
180
181
182

183
184
185
186
187

188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205

206
207
208
209


210
211
212
213
214

215
216

217
218

219
220
221
222
223
224
225
144
145
146
147
148
149
150

151
152

153
154

155
156
157
158
159
160
161
162
163
164
165

166
167
168
169
170
171

172
173
174
175
176
177

178
179
180
181
182

183
184
185
186
187

188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205

206
207
208


209
210
211
212
213
214

215
216

217
218

219
220
221
222
223
224
225
226







-
+

-
+

-
+










-
+





-
+





-
+




-
+




-
+

















-
+


-
-
+
+




-
+

-
+

-
+








- (void)connectionWasAuthenticated: (XMPPConnection *)conn
{
	of_log(@"Auth successful");
}

- (void)connection: (XMPPConnection *)conn_
     wasBoundToJID: (XMPPJID *)jid
     wasBoundToJID: (XMPPJID *)JID
{
	of_log(@"Bound to JID: %@", [jid fullJID]);
	of_log(@"Bound to JID: %@", JID.fullJID);
	of_log(@"Supports SM: %@",
	    [conn_ supportsStreamManagement] ? @"true" : @"false");
	    conn_.supportsStreamManagement ? @"true" : @"false");

	XMPPDiscoEntity *discoEntity =
	    [[XMPPDiscoEntity alloc] initWithConnection: conn];

	[discoEntity addIdentity:
	    [XMPPDiscoIdentity identityWithCategory: @"client"
					       type: @"pc"
					       name: @"ObjXMPP"]];

	XMPPDiscoNode *nodeMusic =
	    [XMPPDiscoNode discoNodeWithJID: jid
	    [XMPPDiscoNode discoNodeWithJID: JID
				       node: @"music"
				       name: @"My music"];
	[discoEntity addChildNode: nodeMusic];

	XMPPDiscoNode *nodeRHCP =
	    [XMPPDiscoNode discoNodeWithJID: jid
	    [XMPPDiscoNode discoNodeWithJID: JID
				       node: @"fa3b6"
				       name: @"Red Hot Chili Peppers"];
	[nodeMusic addChildNode: nodeRHCP];

	XMPPDiscoNode *nodeStop =
	    [XMPPDiscoNode discoNodeWithJID: jid
	    [XMPPDiscoNode discoNodeWithJID: JID
				       node: @"qwe87"
				       name: @"Can't Stop"];
	[nodeRHCP addChildNode: nodeStop];

	XMPPDiscoNode *nodeClueso = [XMPPDiscoNode discoNodeWithJID: jid
	XMPPDiscoNode *nodeClueso = [XMPPDiscoNode discoNodeWithJID: JID
							       node: @"ea386"
							       name: @"Clueso"];
	[nodeMusic addChildNode: nodeClueso];

	XMPPDiscoNode *nodeChicago = [XMPPDiscoNode discoNodeWithJID: jid
	XMPPDiscoNode *nodeChicago = [XMPPDiscoNode discoNodeWithJID: JID
							      node: @"qwr87"
							      name: @"Chicago"];
	[nodeClueso addChildNode: nodeChicago];

	[discoEntity addDiscoNode: nodeMusic];
	[discoEntity addDiscoNode: nodeRHCP];
	[discoEntity addDiscoNode: nodeClueso];
	[discoEntity addDiscoNode: nodeStop];
	[discoEntity addDiscoNode: nodeChicago];

	[roster requestRoster];
}

- (void)rosterWasReceived: (XMPPRoster *)roster_
{
	XMPPPresence *pres;

	of_log(@"Got roster: %@", [roster_ rosterItems]);
	of_log(@"Got roster: %@", roster_.rosterItems);

	pres = [XMPPPresence presence];
	[pres setPriority: [OFNumber numberWithInt8: 10]];
	[pres setStatus: @"ObjXMPP test is working!"];
	pres.priority = [OFNumber numberWithInt8: 10];
	pres.status = @"ObjXMPP test is working!";

	[conn sendStanza: pres];

#ifdef OF_HAVE_BLOCKS
	XMPPIQ *iq = [XMPPIQ IQWithType: @"get"
	XMPPIQ *IQ = [XMPPIQ IQWithType: @"get"
				     ID: [conn generateStanzaID]];
	[iq addChild: [OFXMLElement elementWithName: @"ping"
	[IQ addChild: [OFXMLElement elementWithName: @"ping"
					  namespace: @"urn:xmpp:ping"]];
	[conn	   sendIQ: iq
	[conn	   sendIQ: IQ
	    callbackBlock: ^ (XMPPConnection *c, XMPPIQ *resp) {
		of_log(@"Ping response: %@", resp);
	}];
#endif
}

- (void)connectionDidUpgradeToTLS: (XMPPConnection *)conn_