I would like to create Email client working with IMAP protocol. I'm using Apple's Network framework to create TCP connection with imap.gmail.com.
Spoiler! I used the official tutorial (Implementing netcat with Network Framework).
I have very simple interface in my app:
All methods wrote in ViewController.m
When I press Login button:
- (IBAction)loginButtonPressed:(id)sender {
const char *hostname = "imap.gmail.com";
const char *port = "imaps";
NSString *tempFileTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"myapptempfile.XXXXXX"];
const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
strcpy(tempFileNameCString, tempFileTemplateCString);
dispatch_fd_t fd = mkstemp(tempFileNameCString); // Creating temp file descriptor
free(tempFileNameCString);
NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:NO]; // Creating tempFile to use this file descriptor
nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DEFAULT_CONFIGURATION, NW_PARAMETERS_DEFAULT_CONFIGURATION);
nw_endpoint_t endpoint = nw_endpoint_create_host(hostname, port);
nw_connection_t connection = nw_connection_create(endpoint, parameters);
[self start_connection:connection: tempFileHandle: fd]; // Start connection
}
Someone will think that it's it not necessary to pass "fd" variable to my connection method. And I guess you are be absolutely right, but I had some problems with file descriptors and it was my last try to understand what's going on.
Then I try to start connection and all is right there.
- (void)start_connection:(nw_connection_t)connection :(NSFileHandle *)file :(dispatch_fd_t)fd {
nw_connection_set_queue(connection, dispatch_get_main_queue());
nw_connection_set_state_changed_handler(connection, ^(nw_connection_state_t state, nw_error_t error) {
switch (state) {
case nw_connection_state_waiting:
NSLog(@"waiting");
break;
case nw_connection_state_failed:
NSLog(@"failed");
break;
case nw_connection_state_ready:
NSLog(@"connection is ready");
[self receive_loop:connection: file :fd];
break;
case nw_connection_state_cancelled:
NSLog(@"connection is cancelled");
break;
default:
break;
}
});
nw_connection_start(connection);
}
My console in Xcode just displays: "connection is ready".
As you can see, we call "receive_loop method with 3 arguments", let's take a look at this method:
- (void)receive_loop:(nw_connection_t)connection :(NSFileHandle *)file :(dispatch_fd_t)fd {
nw_connection_receive(connection, 1, UINT32_MAX, ^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t receive_error) {
dispatch_block_t schedule_next_receive = ^{
if (is_complete &&
context != NULL && nw_content_context_get_is_final(context)) {
exit(0);
}
if (receive_error == NULL) {
[self receive_loop:connection: file :fd];
} else {
NSLog(@"Error");
}
};
if (content != NULL) {
dispatch_write(fd, content, dispatch_get_main_queue(), ^(__unused dispatch_data_t _Nullable data, int stdout_error) {
if (stdout_error != 0) {
NSLog(@"Error in receive loop");
} else {
NSString* str = @"00000001 LOGIN zurgsrushmber@gmail.com mypasswordhere";
NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding];
[file writeData:data];
[self send_loop:connection: file :fd];
schedule_next_receive();
}
});
} else {
// No content, so directly schedule the next receive
schedule_next_receive();
}
});
}
And now the most interesting thing: At first, I used STDOUT_FILENO instead of "fd" or (very important) "file.fileDescriptor" in my dispath_write() function. Well, when I was using STDOUT_FILENO my console just printing something like that "* OK Gimap ready for requests from 109.252.29.37 m5mb106667441ljg" As you can see, I'm trying to write it in my "fd" or in "file" variables (both doesn't work, I have "NULL" after trying to read fd or file).
So, that's how I try to send info, but it doesn't matter because I can't write to my fd, so I can't send the data to the imap.gmail.com...
My send_loop method:
- (void)send_loop:(nw_connection_t)connection :(NSFileHandle *)file :(dispatch_fd_t)fd {
dispatch_read(fd, 8192, dispatch_get_main_queue(), ^(dispatch_data_t _Nonnull read_data, int stdin_error) {
if (stdin_error != 0) {
// … error logging …
} else if (read_data == NULL) {
// … handle end of file …
} else {
nw_connection_send(connection, read_data, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, ^(nw_error_t _Nullable error) {
if (error != NULL) {
// … error logging …
} else {
NSLog(@"send loop %@", (NSData *)read_data);
[self send_loop:connection: file :fd];
}
});
}
});
}
Someone help me please!
UPDATE: receive_loop method works properly, I understood it, when opened tmp folder and saw my file with content: "* OK Gimap ready for requests from 109.252.29.37 m5mb106667441ljg", but I can't read this file with send_loop method...
EDIT I created a package to help with TLS sockets. You can easily connect to a NodeJS TLS Server, and it takes into account all of the recent iOS 13 restrictions for completing a TLS handshake. Lots of references to helpful articles as well - https://github.com/eamonwhiter73/IOSObjCWebSockets - if you are a developer, feel free to pull and change, I am not an expert on this subject, but I wanted to create this because it doesn't seem there is much like this for an iOS
/ Objective-C
/ NodeJS
combination on the web.
I think you might be mixing up how to form the send loop
, this part in your code:
} else if (read_data == NULL) {
//… handle end of file …
} else {
The above part of your code is very important for function, I believe your send_loop
should look something like this:
- (void)send_loop:(nw_connection_t)connection {
dispatch_read(STDIN_FILENO, 8192, dispatch_get_main_queue(), ^(dispatch_data_t _Nonnull read_data, int stdin_error) {
if (stdin_error != 0) {
//… error logging …
NSLog(@"error int: %i", stdin_error);
} else if (read_data == NULL) {
nw_connection_send(connection, read_data, NW_CONNECTION_FINAL_MESSAGE_CONTEXT, true, ^(nw_error_t _Nullable error) {
if (error != NULL) {
NSLog(@"error: %@", error);
}
NSLog(@"read_data: %@", [read_data description]);
});
} else {
nw_connection_send(connection, read_data, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, ^(nw_error_t _Nullable error) {
if (error != NULL) {
NSLog(@"error: %@", error);
} else {
[self send_loop:connection];
}
});
}
});
}
The part of code I am mentioning is important because of NW_CONNECTION_FINAL_MESSAGE_CONTEXT
- when there is nothing left to read, it sends the signal that it is the final message.
I am still working on the package I am creating to make WebSockets easy with IOS and the basic Network
package - I will update if I need to when I am done.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.