SecKeychain load item - objective-c

I want to store SMTP-Data from my Mac OSX application using the keychain. I read the Keychain Services Programming Guide of Apple and wrote this method to store the data:
- (BOOL)saveSMPTData
{
OSStatus err;
SecKeychainItemRef item = nil;
SecProtocolType protocol = kSecProtocolTypeSMTP;
const char *accessLabelUTF8 = [KEYCHAIN_NAME UTF8String];
const char *serverNameUTF8 = [self.serverName UTF8String];
const char *usernameUTF8 = [self.username UTF8String];
const char *passwordUTF8 = [self.password UTF8String];
SecAccessRef access = createAccess(KEYCHAIN_NAME);
SecKeychainAttribute attrs[] = {
{ kSecLabelItemAttr, (int)strlen(accessLabelUTF8), (char *)accessLabelUTF8 },
{ kSecAccountItemAttr, (int)strlen(usernameUTF8), (char *)usernameUTF8 },
{ kSecServerItemAttr, (int)strlen(serverNameUTF8), (char *)serverNameUTF8 },
{ kSecProtocolItemAttr, sizeof(SecProtocolType), (SecProtocolType *)&protocol }
};
SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs };
err = SecKeychainItemCreateFromContent(kSecInternetPasswordItemClass,
&attributes,
(int)strlen(passwordUTF8),
passwordUTF8,
NULL,
access,
&item);
if (access) CFRelease(access);
if (item) CFRelease(item);
return (err == noErr);
}
SecAccessRef createAccess(NSString *accessLabel)
{
OSStatus err;
SecAccessRef access = nil;
NSArray *trustedApplications = nil;
SecTrustedApplicationRef myself;
err = SecTrustedApplicationCreateFromPath(NULL, &myself);
trustedApplications = [NSArray arrayWithObjects:(__bridge id)myself, nil];
err = SecAccessCreate((__bridge CFStringRef)accessLabel,
(__bridge CFArrayRef)trustedApplications, &access);
if (err) return nil;
return access;
}
Of course I also want to load them. My first try looks like this:
- (BOOL)loadDataFromKeychain
{
uint32_t serverNameLength = 0;
const char *serverName = NULL;
uint32_t usernameLength = 0;
const char *username = NULL;
uint32_t passwordLength = 0;
void **password = NULL;
OSStatus err = SecKeychainFindInternetPassword(NULL,
serverNameLength, serverName,
0, NULL,
usernameLength, username,
0, NULL,
0, 0,
0,
&passwordLength, password,
NULL); // How do I get the ItemRef?
return (err == noErr);
}
But this does not work, and I think I know why not. I don’t know how to get the SecKeychainItemRef for the SecKeychainFindInternetPassword method.
Maybe anyone can help me?

Instead of declaring password a void **, declare it a void * and pass &password for the second-to-last parameter.
You probably don't need the SecKeychainItemRef for what you're trying to accomplish.
By the way, have you tried using Keychain Access to verify the items are getting into the keychain?

Related

Set and get kIsStationary bit using NSURL

I'm porting some old FSRef code to use NSURL, NSFileManager and friends. Everything works except setting and getting the Finder kIsStationery bit on the file.
Is there a way to do this without falling back on deprecated FSRef methods?
So after a bit of research here is the answer for setting and getting the stationery bit:
struct FileInfoBuf
{
u_int32_t info_length;
union
{
u_int32_t padding[8];
struct
{
u_int32_t type;
u_int32_t creator;
u_int16_t fdFlags;
u_int16_t location;
u_int32_t padding[4];
}
info;
}
data;
};
bool IsStationeryPad(const std::string& path)
{
attrlist attrList;
FileInfoBuf fileInfo;
attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
attrList.reserved = 0;
attrList.commonattr = ATTR_CMN_FNDRINFO;
attrList.volattr = 0;
attrList.dirattr = 0;
attrList.fileattr = 0;
attrList.forkattr = 0;
if (getattrlist(path.c_str(), &attrList, &fileInfo, sizeof(fileInfo), FSOPT_NOFOLLOW) == noErr)
{
return (CFSwapInt16BigToHost(fileInfo.data.info.fdFlags) & kIsStationery);
}
return false;
}
void SetStationeryPad(const std::string& path, bool isStationery)
{
OSErr err = noErr;
attrlist attrList;
FileInfoBuf fileInfo;
attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
attrList.reserved = 0;
attrList.commonattr = ATTR_CMN_FNDRINFO;
attrList.volattr = 0;
attrList.dirattr = 0;
attrList.fileattr = 0;
attrList.forkattr = 0;
err = getattrlist(path.c_str(), &attrList, &fileInfo, sizeof(fileInfo), FSOPT_NOFOLLOW);
if (err == noErr)
{
fileInfo.data.info.fdFlags |= CFSwapInt16HostToBig(kIsStationery);
setattrlist(path.c_str(), &attrList, &fileInfo.data, sizeof(fileInfo.data), FSOPT_NOFOLLOW);
}
}
Note that there is no real error checking in this code. Also, applications probably shouldn't be setting this bit, this is really a user decision and shuld be controlled through the Finder.

Resolving SRV records with iOS SDK

I want to resolve DNS SRV records using the iOS SDK.
I've already tried the high-level Bonjour APIs Apple is providing, but they're not what I need. Now I'm using DNS SD.
void *processQueryForSRVRecord(void *record) {
DNSServiceRef sdRef;
int context;
printf("Setting up query for record: %s\n", record);
DNSServiceQueryRecord(&sdRef, 0, 0, record, kDNSServiceType_SRV, kDNSServiceClass_IN, callback, &context);
printf("Processing query for record: %s\n", record);
DNSServiceProcessResult(sdRef);
printf("Deallocating query for record: %s\n", record);
DNSServiceRefDeallocate(sdRef);
return NULL;
}
This works as long as it gets only correct SRV records (for example: _xmpp-server._tcp.gmail.com), but when the record is typed wrong, DNSServiceProcessResult(sdRef) goes into an infinite loop.
Is there a way to stop DNSServiceProcessResult or must I cancel the thread calling it?
Use good old select(). This is what I have at the moment:
- (void)updateDnsRecords
{
if (self.dnsUpdatePending == YES)
{
return;
}
else
{
self.dnsUpdatePending = YES;
}
NSLog(#"DNS update");
DNSServiceRef sdRef;
DNSServiceErrorType err;
const char* host = [self.dnsHost UTF8String];
if (host != NULL)
{
NSTimeInterval remainingTime = self.dnsUpdateTimeout;
NSDate* startTime = [NSDate date];
err = DNSServiceQueryRecord(&sdRef, 0, 0,
host,
kDNSServiceType_SRV,
kDNSServiceClass_IN,
processDnsReply,
&remainingTime);
// This is necessary so we don't hang forever if there are no results
int dns_sd_fd = DNSServiceRefSockFD(sdRef);
int nfds = dns_sd_fd + 1;
fd_set readfds;
int result;
while (remainingTime > 0)
{
FD_ZERO(&readfds);
FD_SET(dns_sd_fd, &readfds);
struct timeval tv;
tv.tv_sec = (time_t)remainingTime;
tv.tv_usec = (remainingTime - tv.tv_sec) * 1000000;
result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
if (result == 1)
{
if (FD_ISSET(dns_sd_fd, &readfds))
{
err = DNSServiceProcessResult(sdRef);
if (err != kDNSServiceErr_NoError)
{
NSLog(#"There was an error reading the DNS SRV records.");
break;
}
}
}
else if (result == 0)
{
NBLog(#"DNS SRV select() timed out");
break;
}
else
{
if (errno == EINTR)
{
NBLog(#"DNS SRV select() interrupted, retry.");
}
else
{
NBLog(#"DNS SRV select() returned %d errno %d %s.", result, errno, strerror(errno));
break;
}
}
NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:startTime];
remainingTime -= elapsed;
}
DNSServiceRefDeallocate(sdRef);
}
}
static void processDnsReply(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char* fullname,
uint16_t rrtype,
uint16_t rrclass,
uint16_t rdlen,
const void* rdata,
uint32_t ttl,
void* context)
{
NSTimeInterval* remainingTime = (NSTimeInterval*)context;
// If a timeout occurs the value of the errorCode argument will be
// kDNSServiceErr_Timeout.
if (errorCode != kDNSServiceErr_NoError)
{
return;
}
// The flags argument will have the kDNSServiceFlagsAdd bit set if the
// callback is being invoked when a record is received in response to
// the query.
//
// If kDNSServiceFlagsAdd bit is clear then callback is being invoked
// because the record has expired, in which case the ttl argument will
// be 0.
if ((flags & kDNSServiceFlagsMoreComing) == 0)
{
*remainingTime = 0;
}
// Record parsing code below was copied from Apple SRVResolver sample.
NSMutableData * rrData = [NSMutableData data];
dns_resource_record_t * rr;
uint8_t u8;
uint16_t u16;
uint32_t u32;
u8 = 0;
[rrData appendBytes:&u8 length:sizeof(u8)];
u16 = htons(kDNSServiceType_SRV);
[rrData appendBytes:&u16 length:sizeof(u16)];
u16 = htons(kDNSServiceClass_IN);
[rrData appendBytes:&u16 length:sizeof(u16)];
u32 = htonl(666);
[rrData appendBytes:&u32 length:sizeof(u32)];
u16 = htons(rdlen);
[rrData appendBytes:&u16 length:sizeof(u16)];
[rrData appendBytes:rdata length:rdlen];
rr = dns_parse_resource_record([rrData bytes], (uint32_t) [rrData length]);
// If the parse is successful, add the results.
if (rr != NULL)
{
NSString *target;
target = [NSString stringWithCString:rr->data.SRV->target encoding:NSASCIIStringEncoding];
if (target != nil)
{
uint16_t priority = rr->data.SRV->priority;
uint16_t weight = rr->data.SRV->weight;
uint16_t port = rr->data.SRV->port;
[[FailoverWebInterface sharedInterface] addDnsServer:target priority:priority weight:weight port:port ttl:ttl]; // You'll have to do this in with your own method.
}
}
dns_free_resource_record(rr);
}
Here's the Apple SRVResolver sample from which I got the RR parsing.
This Apple sample mentions that it may block forever, but strange enough suggest to use NSTimer when trying to add a timeout yourself. But I think using select() is a much better way.
I have one to-do: Implement flushing cache with DNSServiceReconfirmRecord. But won't do that now.
Be aware, this code is working, but I'm still testing it.
You need to add libresolv.dylib to your Xcode project's 'Linked Frameworks and Libraries'.

How verify account's password

I am asking account's password(password for login in mac) in my application. How can I verify password which is entered user?
I think something like it, but it doesn't work:
-(BOOL)authenticatePassword:(char *)password adminName:(char *)userName
{
BOOL retValue = NO;
OSStatus status,status1;
AuthorizationFlags flag;
AuthorizationItem items[2];
items[0].name = kAuthorizationEnvironmentPassword;
items[0].value = password;
items[0].valueLength = strlen(password);
items[0].flags = 0;
items[1].name = kAuthorizationEnvironmentUsername;
items[1].value = userName;
items[1].valueLength = strlen(userName);
items[1].flags = 0;
AuthorizationItemSet itemSet = {2,items};
status = AuthorizationCreate(NULL, &itemSet, kAuthorizationFlagDefaults, &authorization_);
if(status == errAuthorizationSuccess) {
AuthorizationRights rights = {2,&items};
//AuthorizationEnvironment kEnviroment = {2, items};
AuthorizationFlags flag1 = kAuthorizationFlagDefaults;
status1 = AuthorizationCopyRights(authorization_, &rights,NULL, flag1, NULL);
if(status1 == errAuthorizationSuccess) {
retValue = YES;
}
}
return retValue;
}
In the AuthorizationCopyRightscall the user credentials for the validation should be in the environment parameter (your commented out line) and the rights parameter really should contain the rights you would like to gain using this user credentials.
The rights can contain built in rights or user created rights, it's simpler to use a built in one because creating a user defined right requires admin privilege.
This code bellow will do the trick for you, just call AuthenticateForRight with the username/password parameter and it will try to gain the allow right that is a built in one in the authorizationDB and requires a valid user credential.
To use with a custom right you should once call SetupAuthorizationForRight with admin rights for the right be created in the authenticationDB, after that you can check the user credentials anytime via AuthenticateForRight as a normal user just pass the rightName param also you passed for SetupAuthorizationForRight first time.
// original code: https://developer.apple.com/library/mac/#technotes/tn2095/_index.html
// https://developer.apple.com/library/mac/documentation/Security/Conceptual/authorization_concepts/03authtasks/authtasks.html#//apple_ref/doc/uid/TP30000995-CH206-BCIGEHDI
bool SetupAuthorizationForRight(const char* rightName)
// Called as the application starts up. Creates a connection
// to Authorization Services and then makes sure that our
// right is defined.
{
OSStatus err;
// Connect to Authorization Services.
AuthorizationRef authorization = NULL;
err = AuthorizationCreate(NULL, NULL, 0, &authorization);
// Set up our rights.
if (err == noErr) {
// Check whether our right is already defined.
err = AuthorizationRightGet(rightName, NULL);
if (err == noErr) {
// A right already exists, either set up in advance by
// the system administrator or because this is the second
// time we've run. Either way, there's nothing more for
// us to do.
} else if (err == errAuthorizationDenied) {
// The right is not already defined. Let's create a
// right definition based on the custom (not canned) rule defined
// in the dictionary below.
// The system administrator can modify this right as they
// see fit.
CFStringRef keys[2] = {CFSTR("class"), CFSTR("group")};
CFStringRef values[2] = {CFSTR("user"), CFSTR("everyone")};
// Allow access for every user - all of local and remote users are in the
// 'everyone' group, so this is a safe rule
CFDictionaryRef aDict = CFDictionaryCreate(NULL, (const void **)keys, (const void **)values, 2,
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
err = AuthorizationRightSet(
authorization, // authRef
rightName, // rightName
aDict, // rightDefinition
CFSTR("Authenticate to log in via YourAppName."), // descriptionKey
NULL, // bundle, NULL indicates main
NULL // localeTableName,
); // NULL indicates "Localizable.strings"
if (aDict) {
CFRelease(aDict);
}
if (err != noErr) {
NSLog(#"Cannot set up authorization entry. Error: %d", err);
}
}
} else {
NSLog(#"Cannot open authorization database. Error: %d", err);
}
return (err == noErr);
}
bool AuthenticateForRight(const char* username, const char* password, const char* rightName)
{
OSStatus status = noErr;
if (rightName) {
if ((status = SetupAuthorizationForRight(rightName)) != noErr)
return false;
}
else
rightName = "allow"; // Allow right rule always defined by default and only authenticated users has this right
AuthorizationRef authRef = 0;
AuthorizationItem environment[2] = {{NULL, 0, NULL, 0}, {NULL, 0, NULL, 0}};
int numItems = 0;
if (username) {
AuthorizationItem item = { kAuthorizationEnvironmentUsername, strlen(username), (char*)username, 0 };
environment[numItems++] = item;
if (password) {
AuthorizationItem passItem = { kAuthorizationEnvironmentPassword, strlen(password), (char*)password, 0 };
environment[numItems++] = passItem;
}
}
AuthorizationItem right = {NULL, 0, NULL, 0};
right.name = rightName;
right.valueLength = 0;
right.value = 0;
AuthorizationRights rightSet = { 1, &right };
AuthorizationRights environmentSet = { static_cast<unsigned int>(numItems), environment };
status = AuthorizationCreate(NULL, &environmentSet, kAuthorizationFlagDefaults, &authRef);
if (status != noErr) {
NSLog(#"Cannot create authorization reference. Error: %d", status);
return false;
}
AuthorizationFlags flags = kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize; // | kAuthorizationFlagInteractionAllowed; <- Just for debugging, will display the OS auth dialog if needed!!!
status = AuthorizationCopyRights(authRef, &rightSet, &environmentSet, flags, NULL );
AuthorizationFree(authRef,kAuthorizationFlagDestroyRights);
return (status == noErr);
}
authorization services API will verify and prompt again if password is wrong.
Here is my code for your reference.
char *password = "password";
char *userName = "account";
AuthorizationRef authorization = NULL;
AuthorizationItem items[2];
items[0].name = kAuthorizationEnvironmentPassword;
items[0].value = password;
items[0].valueLength = strlen(password);
items[0].flags = 0;
items[1].name = kAuthorizationEnvironmentUsername;
items[1].value = userName;
items[1].valueLength = strlen(userName);
items[1].flags = 0;
AuthorizationRights rights = {2, items};
AuthorizationEnvironment enviroment = {2, items};
// Creates a new authorization reference and provides an option to authorize or preauthorize rights.
AuthorizationCreate(NULL, &enviroment, kAuthorizationFlagDefaults, &authorization);
AuthorizationFlags flag = kAuthorizationFlagDefaults| kAuthorizationFlagExtendRights;
OSStatus status = AuthorizationCopyRights(authorization, &rights, &enviroment, flag, NULL);
if(status == errAuthorizationSuccess)
{
NSLog(#"Pass");
}
else
{
NSLog(#"Fail");
}

LDAP Authentication, ldap_sasl_bind_s not working but ldap_simple_bind_s works

I have a problem where in ldap_sasl_bind_s does not work, but ldap_simple_bind_s works.
The strange thing is, ldap_sasl_bind_s works even with wrong passwords and gives user the feeling that he has entered a correct password.
PFA code snippet of the problem and suggest me if anything is wrong with my approach.
{
int rc, aReturnVal = 0;
NSString *aUserDN = [NSString stringWithFormat:#"uid=%s,cn=users,dc=example,dc=com", username];
char* userDN = (char*)[aUserDN UTF8String];
rc = ldap_simple_bind_s (
ld,
userDN,
password
);
// TODO: ldap_simple_bind_s is a deprecated method and should not be used for long. ldap_sasl_bind_s is the right method, but is not working for now.
// Find the reason and get this code up and running.
// struct berval *servcred;
// struct berval cred;
// cred.bv_val = password; // my password
// cred.bv_len = strlen(password);
// rc = ldap_sasl_bind_s (
// ld,
// userDN,
// "DIGEST-MD5",
// &cred,
// NULL,
// NULL,
// &servcred
// );
if ( rc != LDAP_SUCCESS ) {
fprintf( stderr, "ldap_sasl_bind: %s\n", ldap_err2string( rc ) );
} else {
aReturnVal = 1;
}
return aReturnVal;
}
I have initialized the LDAP using following code SNIP:
rc = ldap_initialize(&ld, HOSTNAME);
version = LDAP_VERSION3;
ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
ldap_set_option( ld, LDAP_OPT_REFERRALS, 0 );
I need to be able to login with correct user name and when user tries to enter wrong user name, ldap should say so.
I have referred to following links and their related links to get to this conclusion:
LDAP - How to check a username/password combination?
How to do password authentication for a user using LDAP?
Digest-MD5 auth is more complicated than just sending a bind DN and password. You'll need to use ldap_sasl_interactive_bind_s and provide a callback so the SASL library can combine your credentials with the server-provided nonce.
This code (adapted from this blog post) works for me against an Active Directory server:
#include <stdio.h>
#include <stdlib.h>
#include <ldap.h>
#include <sasl/sasl.h>
typedef struct
{
char *username;
char *password;
} my_authdata;
int my_sasl_interact(LDAP *ld, unsigned flags, void *defaults, void *in)
{
my_authdata *auth = (my_authdata *)defaults;
sasl_interact_t *interact = (sasl_interact_t *)in;
if(ld == NULL) return LDAP_PARAM_ERROR;
while(interact->id != SASL_CB_LIST_END)
{
char *dflt = (char *)interact->defresult;
switch(interact->id)
{
case SASL_CB_GETREALM:
dflt = NULL;
break;
case SASL_CB_USER:
case SASL_CB_AUTHNAME:
dflt = auth->username;
break;
case SASL_CB_PASS:
dflt = auth->password;
break;
default:
printf("my_sasl_interact asked for unknown %ld\n",interact->id);
}
interact->result = (dflt && *dflt) ? dflt : (char *)"";
interact->len = strlen((char *)interact->result);
interact++;
}
return LDAP_SUCCESS;
}
int main(int argc, char *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: dmd5-bind [username] [password]\n");
return -1;
}
int rc;
LDAP *ld = NULL;
static my_authdata auth;
auth.username = argv[1];
auth.password = argv[2];
char *sasl_mech = ber_strdup("DIGEST-MD5");
char *ldapuri = ber_strdup("ldap://your.server.name.here");
int protocol = LDAP_VERSION3;
unsigned sasl_flags = LDAP_SASL_QUIET;
char *binddn = NULL;
rc = ldap_initialize(&ld, ldapuri);
if(rc != LDAP_SUCCESS)
{
fprintf(stderr, "ldap_initialize: %s\n", ldap_err2string(rc));
return rc;
}
if(ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol) != LDAP_OPT_SUCCESS)
{
fprintf(stderr, "Could not set LDAP_OPT_PROTOCOL_VERSION %d\n", protocol);
return -1;
}
rc = ldap_sasl_interactive_bind_s(ld,
binddn,
sasl_mech,
NULL,
NULL,
sasl_flags,
my_sasl_interact,
&auth);
if(rc != LDAP_SUCCESS)
{
ldap_perror(ld, "ldap_sasl_interactive_bind_s");
ldap_unbind_ext_s(ld, NULL, NULL);
return rc;
}
fprintf(stdout, "Authentication succeeded\n");
rc = ldap_unbind_ext_s(ld, NULL, NULL);
sasl_done();
sasl_client_init(NULL);
return rc;
}

CFURL does not give full data when download

Iam trying to use CFHTTP to write a small downloader. Unfortunately I cannot use NSURL which is very easy. I basically want an async way to download the data and store it in a file. I have not yet found how to do async way but I have some code with Sync approach which not working as well. The problem is the downloader does not download the complete bytes. I see some data missing causing the final file to be corrupt. Here is the code I have so far.
#include <stdio.h>
#include <string.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CFNetwork/CFNetwork.h>
#include <CFNetwork/CFHTTPStream.h>
int main(int argc, const char * argv[])
{
// insert code here...
const char *data;
FILE *fp;
CFShow(CFSTR("Hello, World!\n"));
CFURLRef cfUrl = CFURLCreateWithString(kCFAllocatorDefault, CFSTR("http://the.earth.li/~sgtatham/putty/0.62/x86/putty.zip"), NULL);
CFHTTPMessageRef cfHttpReq = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), cfUrl, kCFHTTPVersion1_1);
CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, cfHttpReq);
CFReadStreamOpen(readStream);
CFHTTPMessageRef cfHttpResp = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE);
CFIndex numBytesRead;
do {
const int nBuffSize = 1024;
UInt8 buff[nBuffSize];
numBytesRead = CFReadStreamRead(readStream, buff, nBuffSize);
if( numBytesRead > 0 )
{
CFHTTPMessageAppendBytes(cfHttpResp, buff, numBytesRead);
}
else if( numBytesRead < 0 )
{
CFStreamError error = CFReadStreamGetError(readStream);
printf ("Error %d", error.error);
}
} while ( numBytesRead > 0 );
CFStringRef myStatusLine = CFHTTPMessageCopyResponseStatusLine(cfHttpReq);
CFReadStreamClose(readStream);
CFDataRef cfResp = CFHTTPMessageCopyBody(cfHttpResp);
CFIndex length = CFDataGetLength(cfResp);
printf ("%lu\n", length);
CFShow(myStatusLine);
data = (const char*)CFDataGetBytePtr(cfResp);
fp = fopen("/var/tmp/Update.zip", "w");
if (fp == NULL) {
printf("ACnnot be opened\n");
} else {
fwrite(data, length, 1, fp);
fclose(fp);
}
printf ("Download done\n");
CFRelease(cfUrl);
CFRelease(cfHttpReq);
CFRelease(readStream);
CFRelease(cfHttpResp);
CFRelease(cfResp);
return 0;
}
The length I get is less than my actual file download. I dont understand whats is wrong with this code. Can someone please help me with this?
I still dont know what went wrong in this code. But I fixed my problem by replacing
//CFHTTPMessageRef cfHttpResp = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE);
CFMutableDataRef cfResp = CFDataCreateMutable(kCFAllocatorDefault, 0);
I now use MutableDataRef instead of CFHTTPMessageRef. I will upadte the new code here.
int main(int argc, const char * argv[])
{
// insert code here...
const char *data;
FILE *fp;
CFShow(CFSTR("Hello, World!\n"));
CFURLRef cfUrl = CFURLCreateWithString(kCFAllocatorDefault, CFSTR("http://the.earth.li/~sgtatham/putty/0.62/x86/putty.zip"), NULL);
CFHTTPMessageRef cfHttpReq = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), cfUrl, kCFHTTPVersion1_1);
CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, cfHttpReq);
CFReadStreamOpen(readStream);
//CFHTTPMessageRef cfHttpResp = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE);
CFMutableDataRef cfResp = CFDataCreateMutable(kCFAllocatorDefault, 0);
CFIndex numBytesRead;
do {
const int nBuffSize = 1024;
UInt8 buff[nBuffSize];
numBytesRead = CFReadStreamRead(readStream, buff, nBuffSize);
if( numBytesRead > 0 )
{
CFDataAppendBytes(cfResp, buff, numBytesRead);
//CFHTTPMessageAppendBytes(cfHttpResp, buff, numBytesRead);
}
else if( numBytesRead < 0 )
{
CFStreamError error = CFReadStreamGetError(readStream);
printf ("Error %d", error.error);
}
} while ( numBytesRead > 0 );
CFReadStreamClose(readStream);
//CFDataRef cfResp = CFHTTPMessageCopyBody(cfHttpResp);
CFIndex length = CFDataGetLength(cfResp);
printf ("%lu\n", length);
data = (const char*)CFDataGetBytePtr(cfResp);
fp = fopen("/var/tmp/Update.zip", "w");
if (fp == NULL) {
printf("ACnnot be opened\n");
} else {
fwrite(data, length, 1, fp);
fclose(fp);
}
printf ("Download done\n");
CFRelease(cfUrl);
CFRelease(cfHttpReq);
CFRelease(readStream);
CFRelease(cfResp);
return 0;
}
Hope this helps. It will still be useful if someone can point out what went wrong original code.
I know this late, but your code really helped me out (it is surprisingly hard to find straight forward, complete, examples of getting the response body bytes using CFNetworking API).
Anyway, I think the mistake was the last parameter should be FALSE (response message), and not TRUE (request message) in CFHTTPMessageCreateEmpty line.
CFHTTPMessageRef cfHttpResp = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, FALSE);