* Fix a crash if `kDNSServiceErr_Invalid` is returned by `TXTRecordSetValue` which occured because the `%@` was being used for a C string
* Handle the situation which caused `kDNSServiceErr_Invalid` to be returned by `TXTRecordSetValue` if setting a long status message (≥ 250 characters) by truncating appropriately.
Fixes #11322
3 * File: AWEzvRendezvousData.h
6 * Author: Andrew Wellington <proton[at]wiretapped.net>
9 * Copyright (C) 2004-2005 Andrew Wellington.
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions are met:
15 * 1. Redistributions of source code must retain the above copyright notice,
16 * this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright notice,
18 * this list of conditions and the following disclaimer in the documentation
19 * and/or other materials provided with the distribution.
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
22 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
23 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
24 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
27 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
29 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
30 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 #import "AWEzvRendezvousData.h"
34 #import "AWEzvSupportRoutines.h"
35 #import <AIUtilities/AIStringAdditions.h>
37 @implementation AWEzvRendezvousData
39 /* subnegotiation that appears at start of rendezvous packet */
40 /* Reserved version? */
41 NSString *subn = @"subn\x00\x00\x00\x01";
43 /* end of subnegotation. significance of value unknown */
44 /* Reserved unknown */
45 NSString *endn = @"\x00\x00\x00\x00";
47 /* initialization, create our dictionary */
48 -(AWEzvRendezvousData *) init
50 if ((self = [super init])) {
51 keys = [[NSMutableDictionary dictionary] retain];
58 /* intialise given an NSData containing an announcement */
59 -(AWEzvRendezvousData *) initWithData:(NSData *)data {
60 UInt32 version; /* the version of the iChat announcement (?) */
61 UInt32 fieldCount; /* number of fields in the announcement */
62 UInt16 fieldLen; /* length of field being read */
63 UInt32 i; /* read index into data buffer */
64 NSString *fieldName; /* name of field */
65 NSString *fieldContent; /* contents of field */
66 NSData *tmpData; /* temporary data */
67 NSRange range; /* range for reading of data */
69 /* call the standard initialisation */
72 /* check that the length is ok */
73 if ([data length] < ([subn length] + 4 + [endn length])) {
74 AWEzvLog(@"Invalid rendezvous announcement: length %u", [data length]);
79 /* check version (?) of iChat announcement */
82 [data getBytes:&version range:range];
83 version = ntohl(version);
85 AWEzvLog(@"Invalid rendezvous announcement: incorrect version: %u", version);
90 /* get serial of announcement */
93 [data getBytes:&serial range:range];
94 serial = ntohl(serial);
96 /* get field count of data */
99 [data getBytes:&fieldCount range:range];
100 fieldCount = ntohl(fieldCount);
102 /* read fields from data */
103 for (i = [subn length] + 4 + [endn length] + 4; i < [data length];) {
106 /* read length of field name */
107 if ([data length] < i + 2) {
108 AWEzvLog(@"Invalid rendezvous announcement at field name length");
114 [data getBytes:&fieldLen range:range];
115 fieldLen = ntohs(fieldLen);
116 fieldLen = fieldLen & 0x7FFF;
119 /* read field data */
120 if ([data length] < i + fieldLen) {
121 AWEzvLog(@"Invalid rendezvous announcement at field name");
125 tmpData = [NSData dataWithBytes:[data bytes] + i length:fieldLen];
126 fieldName = [[[NSString alloc] initWithData:tmpData encoding:NSUTF8StringEncoding] autorelease];
129 /* read length of field data */
130 if ([data length] < i + 2) {
131 AWEzvLog(@"Invalid rendezvous announcement at field data length");
137 [data getBytes:&fieldLen range:range];
138 fieldLen = ntohs(fieldLen);
139 /* most significant bit in fieldLen is a binary data flag */
140 if ((fieldLen & 0x7FFF) != fieldLen)
142 fieldLen = fieldLen & 0x7FFF;
145 /* read field data */
146 if ([data length] < i + fieldLen) {
147 AWEzvLog(@"Invalid rendezvous announcement at field data");
152 tmpData = [NSData dataWithBytes:[data bytes] + i length:fieldLen];
153 fieldContent = [[[NSString alloc] initWithData:tmpData encoding:NSUTF8StringEncoding] autorelease];
155 fieldContent = [[[NSString alloc] initWithBytes:[data bytes] + i length:fieldLen encoding:NSUTF8StringEncoding] autorelease];
159 /* save field information */
160 [self setField:fieldName content:fieldContent];
163 /* return initialised object */
167 /* intialise rendezvous data with a plist of the data */
168 -(AWEzvRendezvousData *) initWithPlist:(NSString *)plist {
169 id extracted; /* extracted data from plist */
170 NSData *xmlData; /* XML data in NSData form */
171 NSString *error; /* error from conversion of plist */
172 NSPropertyListFormat format;/* something we can point at for the format pointer */
174 error = [NSString string];
176 /* create XML data */
177 xmlData = [NSData dataWithBytes:[plist UTF8String] length:[plist length]];
179 /* extract plist from XML data */
180 format = NSPropertyListXMLFormat_v1_0;
181 extracted = [NSPropertyListSerialization
182 propertyListFromData:xmlData
183 mutabilityOption:NSPropertyListImmutable
185 errorDescription:&error];
187 /* check if there was an error in extraction */
188 if (extracted == nil) {
189 AWEzvLog(@"Unable to extract XML into plist");
194 /* make sure it's an NSData, or reponds to getBytes:range: */
195 if (![extracted respondsToSelector:@selector(getBytes:range:)]) {
196 AWEzvLog(@"Extracted object from XML is not an NSData");
201 /* pass it to another initialiser */
202 return [self initWithData:extracted];
205 /* initialise object with a dictionary */
206 - (AWEzvRendezvousData *)initWithDictionary:(NSDictionary *)dictionary
208 if ((self = [super init])) {
209 keys = [dictionary retain];
216 /* initialise object with an AV TXT record */
217 - (AWEzvRendezvousData *)initWithAVTxt:(NSString *)txt {
223 attribs = [txt componentsSeparatedByString:@"\001"];
225 for (key in attribs) {
228 range = [key rangeOfString:@"="];
229 if (range.location != NSNotFound) {
230 [self setField:[key substringToIndex:range.location]
231 content:[key substringFromIndex:range.location+1]];
237 - (AWEzvRendezvousData *) initWithTXTRecordRef:(const unsigned char *) txtRecord length:(uint16_t)len{
241 DNSServiceErrorType txtRecordError;
244 numKeys = TXTRecordGetCount(len, txtRecord);
245 for (i=0; i<numKeys; i++) {
250 txtRecordError = TXTRecordGetItemAtIndex (
252 /* txtrecord */ txtRecord,
254 /* keybuffer length */ sizeof(key),
255 /* key buffer */ key,
256 /* valueLength */ &valLen,
257 /* value pointer */ &value );
258 if (txtRecordError == kDNSServiceErr_NoError) {
259 NSString *keyString = NULL;
260 NSString *data = NULL;
261 keyString = [NSString stringWithUTF8String: key];
264 data = [[[NSString alloc] initWithBytes: value length: valLen encoding: NSUTF8StringEncoding] autorelease];
267 if (data != NULL && keyString != NULL) {
268 [self setField:keyString content:data];
270 AWEzvLog(@"Creating TXTRecord: No data and No key");
273 /* AWEzvLog(@"key:%@ value=%@", keyString, data); */
275 AWEzvLog(@"Error reading txt keys");
282 // // kind of a hack: munge txtRecord so it's human-readable
284 // char *readableText = (char*) malloc( len);
285 // if ( readableText != nil) {
286 // ByteCount index, subStrLen;
287 // memcpy( readableText, txtRecord, len);
288 // for ( index=0; index < len - 1; index += subStrLen + 1) {
289 // subStrLen = readableText[ index];
290 // readableText[ index] = '\n';
292 // //NSLog(@"%@\n\n",[NSString stringWithCString:&readableText[1] length:len - 1]);
293 // free( readableText);
301 /* deallocate, destroy our dictionary */
308 /* sets a field in the rendezvous data structures */
309 -(void) setField:(NSString *)fieldName content:(NSObject *)content {
310 if (content == nil || fieldName == nil)
313 [keys setObject:content forKey:fieldName];
317 /* delete a field in the rendezvous data structure */
318 -(void) deleteField:(NSString *)fieldName {
319 if ([keys objectForKey:fieldName] != nil)
320 [keys removeObjectForKey:fieldName];
323 /* get a field from the rendezvous data structure */
324 -(NSString *) getField:(NSString *)fieldName {
325 return [[[keys objectForKey:fieldName] copy] autorelease];
328 /* return if a field exists */
329 -(BOOL) fieldExists:(NSString *)fieldName {
330 return [keys objectForKey:fieldName] != nil;
333 /* return the serial number of the data */
338 /* return the dictionary */
339 -(NSDictionary *)dictionary {
340 return [[keys copy] autorelease];
344 * Generate data to be placed in the protocolSpecificInformation (TXT record)
345 * of the rendezvous announcement. This is data shouldn't be used directly, but
346 * should be used indirectly via dataAsDNSTXT or dataAsPackedPString
349 NSMutableData *data; /* binary representation of rendezvous data */
350 NSData *xmlData; /* data converted to an XML plist */
351 NSMutableString *infoData; /* XML plist as a string */
352 NSString *key; /* strings used when manipulating data */
353 id value; /* value for data field */
354 NSString *error; /* error from creation of plist */
355 UInt32 keycount; /* a 32-bit integer, count of keys in data */
356 UInt16 fieldlen; /* a 16-bit integer, length of field being added to data */
357 UInt16 fieldlenBE; /* fieldlen as converted to network byte order */
358 UInt32 serialBE = htonl(serial); /* serial as converted to network byte order */
360 /* allocate NSData to create data in */
361 data = [[NSMutableData alloc] init];
363 /* add the subnegotiation string */
364 [data appendBytes:[subn UTF8String] length:[subn length]];
365 [data appendBytes:&serialBE length:4];
366 [data appendBytes:[endn UTF8String] length:[endn length]];
367 /* add a field containing the number of fields for the rest of the data */
368 keycount = [keys count] + 1; /* +1 for slumming field */
369 keycount = htonl(keycount);
370 [data appendBytes:&keycount length:4];
372 /* loop through fields to be added and add them to data */
374 /* add length of field name, then field name */
376 field = [key UTF8String];
377 fieldlen = strlen(field);
378 fieldlenBE = htons(fieldlen);
379 [data appendBytes:&fieldlenBE length:2];
380 [data appendBytes:field length:fieldlen];
382 /* add length of field data, then field data */
383 value = [keys objectForKey:key];
384 if ([value isKindOfClass: [NSData class]]) {
385 field = [value bytes];
386 fieldlen = [(NSData *)value length];
387 fieldlen = fieldlen | ~0x7FFF;
389 field = [value UTF8String];
390 fieldlen = strlen(field);
392 fieldlenBE = htons(fieldlen);
393 [data appendBytes:&fieldlenBE length:2];
394 if ([value isKindOfClass: [NSData class]]) {
395 fieldlen = [(NSData *)value length];
397 [data appendBytes:field length:fieldlen];
400 /* we're slumming it in iChat-land */
402 fieldlen = [key length];
403 fieldlenBE = htons(fieldlen);
404 [data appendBytes:&fieldlenBE length:2];
405 [data appendBytes:[key UTF8String] length:[key length]];
407 fieldlen = [(NSData *)value length];
408 fieldlenBE = htons(fieldlen);
409 [data appendBytes:&fieldlenBE length:2];
410 [data appendBytes:[value UTF8String] length:[(NSData *)value length]];
412 /* create XML plist of data and convert to string */
413 xmlData = [NSPropertyListSerialization dataFromPropertyList:data
414 format:NSPropertyListXMLFormat_v1_0
415 errorDescription:&error];
416 infoData = [[NSMutableString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding];
417 [infoData autorelease];
419 /* and now we have the rendezvous data to return to the caller, the copy
420 converts it to immutable */
421 return [[infoData copy] autorelease];
425 * Converts data: to a format appropriate for passing to service registration.
426 * We add an ASCII 1 character every 255 characters for pascal string separation
428 -(NSString *)dataAsDNSTXT {
429 NSMutableString *infoData = [[[self data] mutableCopy] autorelease]; /* data to be done */
430 unsigned long i; /* loop counter */
432 /* add the character \001 when we exceed 255 characters, required to allow announcement
433 to be longer than 255 characters */
434 for (i = 255; i < [infoData length]; i += 255)
436 [infoData insertString:@"\001" atIndex:i];
439 /* return a copy so it is immutable */
440 return [[infoData copy] autorelease];
443 /* ichat AV style TXT record */
444 -(NSString *)avDataAsDNSTXT {
445 NSMutableString *infoData = [NSMutableString string];
449 [infoData appendString:@"\001txtvers=1"];
450 [infoData appendString:@"\001version=1"];
452 /* enumerate through fields for announcement */
454 [infoData appendString:@"\001"];
455 [infoData appendString:key];
456 [infoData appendString:@"="];
457 value = [keys objectForKey:key];
459 if ([value isKindOfClass: [NSData class]]) {
460 /* convert binary to hex */
461 char *hexdata = (char *)malloc([(NSData *)value length] * 2 + 1);
464 for (i = 0; i < 20; i++) {
465 sprintf(hexdata + (i*2), "%.2x", ((unsigned char *)[(NSData *)value bytes])[i]);
467 hexdata[[(NSData *)value length] * 2] = '\0';
469 [infoData appendString:[NSString stringWithUTF8String:hexdata]];
471 [infoData appendString:value];
478 -(TXTRecordRef)dataAsTXTRecordRef {
479 //AWEzvLog(@"dataAsTXTRecordRef called");
480 TXTRecordRef txtRecord;
481 DNSServiceErrorType txtRecordError;
483 const char *valueToSet;
485 TXTRecordCreate(/* TXTRecordRef */ &txtRecord, /* buffer length */ 0, /* buffer */ NULL);
487 /* Enumerate through keys setting the txtrecordvalue */
488 for (NSString *key in [keys keyEnumerator]) {
489 value = [keys objectForKey:key];
490 //AWEzvLog(@"key=%@ value=%@", key, value);
492 if ([value isKindOfClass: [NSData class]]) {
493 /* convert binary to hex */
494 char *hexdata = (char *)malloc([(NSData *)value length] * 2 + 1);
497 for (i = 0; i < 20; i++) {
498 sprintf(hexdata + (i*2), "%.2x", ((unsigned char *)[(NSData *)value bytes])[i]);
500 hexdata[[(NSData *)value length] * 2] = '\0';
501 valueToSet = [[NSString stringWithUTF8String:hexdata] UTF8String];
502 valueSize = strlen(valueToSet);
505 valueToSet = [value UTF8String];
506 valueSize = strlen(valueToSet);
509 txtRecordError = TXTRecordSetValue (
510 /* TXTRecord */ &txtRecord,
511 /* key */ [key UTF8String],
512 /* size, may be zero */ valueSize,
513 /* value, may be null */ valueToSet);
515 if ((txtRecordError == kDNSServiceErr_Invalid) &&
516 [value isKindOfClass:[NSString class]]) {
517 /* kDNSServiceErr_Invalid may be returned if:
518 * 1. Invalid characters were included (per documentation)
519 * 2. The length of the value is >= 250 characters (at least for the msg key)
521 * So: First, try stripping out any non-ASCII characters, as I'm not sure what might consitute an
522 * "illegal character" in a field which requests UTF8.
524 * Then, if that still fails, truncate the string to 248 characters (248,249,250 are the ellipsis).
526 valueToSet = [[value dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES] bytes];
527 valueSize = strlen(valueToSet);
529 txtRecordError = TXTRecordSetValue (
530 /* TXTRecord */ &txtRecord,
531 /* key */ [key UTF8String],
532 /* size, may be zero */ valueSize,
533 /* value, may be null */ valueToSet);
535 if (txtRecordError == kDNSServiceErr_Invalid) {
536 valueToSet = [[value stringWithEllipsisByTruncatingToLength:248] UTF8String];
537 valueSize = strlen(valueToSet);
538 txtRecordError = TXTRecordSetValue (
539 /* TXTRecord */ &txtRecord,
540 /* key */ [key UTF8String],
541 /* size, may be zero */ valueSize,
542 /* value, may be null */ valueToSet);
546 if (txtRecordError != kDNSServiceErr_NoError) {
547 if (txtRecordError == kDNSServiceErr_Invalid) {
548 AWEzvLog(@"Error setting TXTRecord of key=%@ and value=%s: Value contains illegal characters", key, valueToSet);
550 } else if (txtRecordError == kDNSServiceErr_NoMemory) {
551 AWEzvLog(@"Error setting TXTRecord of key=%@ and value=%s: Exceeded available storage", key, valueToSet);
553 AWEzvLog(@"Error setting TXTRecord of key=%@ and value=%s: Error is %i", key, valueToSet, txtRecordError);
563 * Converts data: to packed PString format as required by the low level rendezvous
564 * functions when passing an opaque RData structure
566 -(NSData *) dataAsPackedPString {
567 NSString *origdata = [self data]; /* original data */
568 NSMutableData *data = [NSMutableData data]; /* modified data to return */
569 unsigned char pstring[256]; /* pascal string under construction */
570 unsigned long i; /* loop counter */
572 /* initialise pstring */
576 for (i = 0; i < [origdata length]; i++) {
578 pstring[pstring[0]] = [origdata characterAtIndex:i];
579 if (pstring[0] == 254 || i == [origdata length] - 1) {
580 [data appendBytes:(char *)&pstring length:pstring[0] + 1];
585 /* return copy so it is immutable */
586 return [[data copy] autorelease];
589 /* ichat AV style TXT record */
590 -(NSData *)avDataAsPackedPString {
591 NSMutableString *infoData = [NSMutableString string];
596 [infoData appendString:@"\x09txtvers=1"];
597 [infoData appendString:@"\x09version=1"];
599 /* enumerate through fields for announcement */
601 /* convert binary to hex */
605 value = [keys objectForKey:key];
607 if ([value isKindOfClass:[NSData class]]) {
608 hexdata = (char *)malloc([(NSData *)value length] * 2 + 1);
610 for (i = 0; i < 20; i++) {
611 sprintf(hexdata + (i*2), "%.2x", ((unsigned char *)[(NSData *)value bytes])[i]);
613 hexdata[[(NSData *)value length] * 2] = '\0';
615 [infoData appendFormat:@"%c", ([(NSData *)value length] * 2 + [key length] + 1)];
616 [infoData appendString:key];
617 [infoData appendString:@"="];
618 [infoData appendString:[NSString stringWithUTF8String:hexdata]];
621 const char *val = [(NSString *)value UTF8String];
622 int len = strlen(val);
623 [infoData appendFormat:@"%c", len + [key length] + 1];
624 [infoData appendString:key];
625 [infoData appendString:@"="];
626 [infoData appendString:value];
630 data = [infoData UTF8String];
632 return [NSData dataWithBytes:data length:strlen(data)];