mirror of
https://git.checksum.fail/alec/erythros
synced 2025-12-10 13:09:55 +02:00
System/Libraries/Imap: Add initial support for IMAP protocol
This commit is contained in:
517
System/Libraries/Imap.HC
Normal file
517
System/Libraries/Imap.HC
Normal file
@@ -0,0 +1,517 @@
|
||||
class ImapBuffer {
|
||||
U8* rx;
|
||||
U8* tx;
|
||||
};
|
||||
|
||||
class ImapClient {
|
||||
CTask* mem_task;
|
||||
TlsSocket* s;
|
||||
JsonObject* o;
|
||||
ImapBuffer buf;
|
||||
U8 response_text[1024];
|
||||
U8 selected[256];
|
||||
I64 state;
|
||||
I64 tag;
|
||||
U0 (*close)();
|
||||
U0 (*connect)(U8* host, I64 port);
|
||||
U0 (*fetch)(I64 uid);
|
||||
JsonArray* (*fetch_array)(U8* sequence_set, U8* data_items);
|
||||
U0 (*select)(U8* mailbox);
|
||||
U0 (*login)(U8* userid, U8* password);
|
||||
U0 (*logout)();
|
||||
};
|
||||
|
||||
#define IMAP_BUFFER_SIZE 65536
|
||||
|
||||
#define IMAP_LINE_EQ 1
|
||||
#define IMAP_LINE_IQ 2
|
||||
|
||||
#define IMAP_STATE_NOT_CONNECTED 0
|
||||
#define IMAP_STATE_NOT_AUTHENTICATED 1
|
||||
#define IMAP_STATE_AUTHENTICATED 2
|
||||
#define IMAP_STATE_SELECTED 3
|
||||
#define IMAP_STATE_LOGOUT 128
|
||||
|
||||
#define IMAP_STATE_ERROR 255
|
||||
|
||||
#define IMAP_WRAPPER_MAGIC_NUMBER 0xCAFEFACADEDEFACE
|
||||
|
||||
JsonArray* @imap_json_array_from_param_list(ImapClient* c, U8* line)
|
||||
{
|
||||
I64 i = 0;
|
||||
I64 state = 0;
|
||||
JsonArray* a = NULL;
|
||||
U8* out = CAlloc(StrLen(line) * 2, c->mem_task);
|
||||
while (line[i]) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (line[i] < ' ') {
|
||||
++i;
|
||||
goto @imap_convert_process_next_ch;
|
||||
}
|
||||
if (line[i] == ' ') {
|
||||
String.Append(out, ",");
|
||||
++i;
|
||||
goto @imap_convert_process_next_ch;
|
||||
}
|
||||
if (line[i] == '(') {
|
||||
String.Append(out, "[");
|
||||
++i;
|
||||
goto @imap_convert_process_next_ch;
|
||||
}
|
||||
if (line[i] == ')') {
|
||||
String.Append(out, "]");
|
||||
++i;
|
||||
goto @imap_convert_process_next_ch;
|
||||
}
|
||||
if (line[i] == '"') {
|
||||
state = IMAP_LINE_EQ;
|
||||
String.Append(out, "%c", line[i]);
|
||||
++i;
|
||||
goto @imap_convert_process_next_ch;
|
||||
}
|
||||
state = IMAP_LINE_IQ;
|
||||
String.Append(out, "\"");
|
||||
String.Append(out, "%c", line[i]);
|
||||
++i;
|
||||
break;
|
||||
case IMAP_LINE_EQ:
|
||||
if (line[i] == '"') {
|
||||
state = 0;
|
||||
}
|
||||
String.Append(out, "%c", line[i]);
|
||||
++i;
|
||||
break;
|
||||
case IMAP_LINE_IQ:
|
||||
if (line[i] == ')' || line[i] == ' ') {
|
||||
state = 0;
|
||||
String.Append(out, "\"");
|
||||
goto @imap_convert_process_next_ch;
|
||||
}
|
||||
String.Append(out, "%c", line[i]);
|
||||
++i;
|
||||
break;
|
||||
default:
|
||||
// We shouldn't be here! Return empty Array
|
||||
StrCpy(out, "[]");
|
||||
i = StrLen(line);
|
||||
break;
|
||||
}
|
||||
@imap_convert_process_next_ch:
|
||||
}
|
||||
a = Json.Parse(out, c->mem_task);
|
||||
Free(out);
|
||||
return a;
|
||||
}
|
||||
|
||||
U8** @imap_lineate_buffer(ImapClient* c, U8* line_cnt_out)
|
||||
{
|
||||
if (!c || !line_cnt_out)
|
||||
return NULL;
|
||||
I64 line_cnt = StrOcc(c->buf.rx, '\n');
|
||||
*line_cnt_out = line_cnt;
|
||||
if (!line_cnt)
|
||||
return NULL;
|
||||
I64 i = 0;
|
||||
U8* p = c->buf.rx;
|
||||
U8** lines = CAlloc(sizeof(U8*) * line_cnt, c->mem_task);
|
||||
lines[i++] = p++;
|
||||
while (*p) {
|
||||
switch (*p) {
|
||||
case '\r':
|
||||
*p = NULL;
|
||||
p += 2;
|
||||
lines[i++] = p++;
|
||||
break;
|
||||
default:
|
||||
++p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
U0 @imap_init_buffers(ImapClient* c)
|
||||
{
|
||||
c->buf.rx = CAlloc(IMAP_BUFFER_SIZE, c->mem_task);
|
||||
c->buf.tx = CAlloc(IMAP_BUFFER_SIZE, c->mem_task);
|
||||
}
|
||||
|
||||
U0 @imap_free_buffers(ImapClient* c)
|
||||
{
|
||||
if (c->buf.rx)
|
||||
Free(c->buf.rx);
|
||||
if (c->buf.tx)
|
||||
Free(c->buf.tx);
|
||||
}
|
||||
|
||||
U0 @imap_send(ImapClient* c)
|
||||
{
|
||||
c->s->send(c->buf.tx, StrLen(c->buf.tx));
|
||||
}
|
||||
|
||||
U0 @imap_set_response_text(ImapClient* c, U8* s)
|
||||
{
|
||||
// Trim whitespace at both ends
|
||||
Bool trim = TRUE;
|
||||
while (trim) {
|
||||
switch (*s) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
++s;
|
||||
break;
|
||||
default:
|
||||
trim = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
StrCpy(c->response_text, s);
|
||||
s = c->response_text + StrLen(c->response_text) - 1;
|
||||
trim = TRUE;
|
||||
while (trim) {
|
||||
switch (*s) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
*s = NULL;
|
||||
--s;
|
||||
break;
|
||||
default:
|
||||
trim = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
U0 @imap_error(ImapClient* c, U8* s)
|
||||
{
|
||||
c->state = IMAP_STATE_ERROR;
|
||||
@imap_set_response_text(c, s);
|
||||
}
|
||||
|
||||
U0 @imap_receive(ImapClient* c)
|
||||
{
|
||||
U8 buf[16];
|
||||
I64 cnt = 0;
|
||||
|
||||
U8* msg;
|
||||
|
||||
while (c->s->state != TCP_SOCKET_STATE_CLOSED) {
|
||||
cnt += c->s->receive(c->buf.rx + cnt, IMAP_BUFFER_SIZE);
|
||||
|
||||
if (StrFind("* OK", c->buf.rx)) {
|
||||
@imap_set_response_text(c, StrFind("* OK", c->buf.rx) + 4);
|
||||
return;
|
||||
}
|
||||
StrPrint(buf, "A%03d OK", c->tag);
|
||||
if (StrFind(buf, c->buf.rx)) {
|
||||
@imap_set_response_text(c, StrFind(buf, c->buf.rx) + 7);
|
||||
return;
|
||||
}
|
||||
|
||||
if (StrFind("* NO", c->buf.rx)) {
|
||||
@imap_error(c, StrFind("* NO", c->buf.rx) + 4);
|
||||
return;
|
||||
}
|
||||
StrPrint(buf, "A%03d NO", c->tag);
|
||||
if (StrFind(buf, c->buf.rx)) {
|
||||
@imap_error(c, StrFind(buf, c->buf.rx) + 7);
|
||||
return;
|
||||
}
|
||||
|
||||
if (StrFind("* BAD", c->buf.rx)) {
|
||||
@imap_error(c, StrFind("* BAD", c->buf.rx) + 5);
|
||||
return;
|
||||
}
|
||||
StrPrint(buf, "A%03d BAD", c->tag);
|
||||
if (StrFind(buf, c->buf.rx)) {
|
||||
@imap_error(c, StrFind(buf, c->buf.rx) + 8);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
U0 @imap_logout(ImapClient* c)
|
||||
{
|
||||
StrPrint(c->buf.tx, "A%03d LOGOUT\r\n", ++c->tag);
|
||||
@imap_send(c);
|
||||
|
||||
// Receive data
|
||||
@imap_receive(c);
|
||||
|
||||
c->state = IMAP_STATE_LOGOUT;
|
||||
}
|
||||
|
||||
U0 @imap_close(ImapClient* c)
|
||||
{
|
||||
c->s->close();
|
||||
@imap_free_buffers(c);
|
||||
Free(c->s);
|
||||
|
||||
Free(c->close);
|
||||
Free(c->connect);
|
||||
Free(c->fetch);
|
||||
Free(c->fetch_array);
|
||||
Free(c->select);
|
||||
Free(c->login);
|
||||
Free(c->logout);
|
||||
Free(c);
|
||||
}
|
||||
|
||||
U0 @imap_login(ImapClient* c, U8* userid, U8* password)
|
||||
{
|
||||
StrPrint(c->buf.tx, "A%03d LOGIN %s %s\r\n", ++c->tag, userid, password);
|
||||
@imap_send(c);
|
||||
|
||||
// Receive data
|
||||
@imap_receive(c);
|
||||
if (c->state == IMAP_STATE_ERROR)
|
||||
return;
|
||||
|
||||
c->state = IMAP_STATE_AUTHENTICATED;
|
||||
}
|
||||
|
||||
JsonArray* @imap_fetch_array(ImapClient* c, U8* sequence_set, U8* data_items)
|
||||
{
|
||||
StrPrint(c->buf.tx, "A%03d FETCH %s %s\r\n", ++c->tag, sequence_set, data_items);
|
||||
@imap_send(c);
|
||||
|
||||
// Receive data
|
||||
@imap_receive(c);
|
||||
if (c->state == IMAP_STATE_ERROR)
|
||||
return Json.Parse("[]", c->mem_task);
|
||||
|
||||
// Split buffer into lines
|
||||
I64 i = 0;
|
||||
I64 lines_cnt = 0;
|
||||
U8** lines = @imap_lineate_buffer(c, &lines_cnt);
|
||||
U8* param_list = NULL;
|
||||
|
||||
if (!lines_cnt) {
|
||||
Free(lines);
|
||||
return Json.Parse("[]", c->mem_task);
|
||||
}
|
||||
|
||||
// Convert each param_list to a JSON array
|
||||
JsonArray* a = Json.CreateArray(c->mem_task);
|
||||
for (i = 0; i < lines_cnt; i++) {
|
||||
param_list = StrFind(" FETCH (", lines[i]);
|
||||
if (param_list) {
|
||||
a->append(@imap_json_array_from_param_list(c, param_list + 7), JSON_ARRAY);
|
||||
}
|
||||
}
|
||||
|
||||
// Return array of arrays
|
||||
return a;
|
||||
}
|
||||
|
||||
U0 @imap_fetch(ImapClient* c, I64 uid)
|
||||
{
|
||||
StrPrint(c->buf.tx, "A%03d UID FETCH %d BODY.PEEK[]\r\n", ++c->tag, uid);
|
||||
@imap_send(c);
|
||||
|
||||
@imap_receive(c);
|
||||
}
|
||||
|
||||
U0 @imap_select(ImapClient* c, U8* mailbox)
|
||||
{
|
||||
StrPrint(c->buf.tx, "A%03d SELECT %s\r\n", ++c->tag, mailbox);
|
||||
@imap_send(c);
|
||||
|
||||
@imap_receive(c);
|
||||
if (c->state == IMAP_STATE_ERROR)
|
||||
return;
|
||||
|
||||
StrCpy(&c->selected, mailbox);
|
||||
c->state = IMAP_STATE_SELECTED;
|
||||
}
|
||||
|
||||
U0 @imap_connect(ImapClient* c, U8* host, I64 port)
|
||||
{
|
||||
if (!host || !port || !StrLen(host))
|
||||
return;
|
||||
|
||||
U32 addr = @dns_query(host);
|
||||
if (addr == U32_MAX)
|
||||
return;
|
||||
|
||||
TlsSocket* s = NULL;
|
||||
|
||||
switch (port) {
|
||||
case 143:
|
||||
s = @tcp_socket_create(host, port);
|
||||
break;
|
||||
case 993:
|
||||
s = @tls_socket_create(host, port);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
c->s = s;
|
||||
|
||||
// Receive data
|
||||
@imap_receive(c);
|
||||
if (c->state == IMAP_STATE_ERROR)
|
||||
return;
|
||||
|
||||
c->state = IMAP_STATE_NOT_AUTHENTICATED;
|
||||
}
|
||||
|
||||
U0 @imap_close_wrapper_function()
|
||||
{
|
||||
@imap_close(IMAP_WRAPPER_MAGIC_NUMBER);
|
||||
}
|
||||
|
||||
U0 @imap_connect_wrapper_function(U8* host, I64 port)
|
||||
{
|
||||
@imap_connect(IMAP_WRAPPER_MAGIC_NUMBER, host, port);
|
||||
}
|
||||
|
||||
U0 @imap_select_wrapper_function(U8* mailbox)
|
||||
{
|
||||
@imap_select(IMAP_WRAPPER_MAGIC_NUMBER, mailbox);
|
||||
}
|
||||
|
||||
U0 @imap_login_wrapper_function(U8* userid, U8* password)
|
||||
{
|
||||
@imap_login(IMAP_WRAPPER_MAGIC_NUMBER, userid, password);
|
||||
}
|
||||
|
||||
U0 @imap_logout_wrapper_function()
|
||||
{
|
||||
@imap_logout(IMAP_WRAPPER_MAGIC_NUMBER);
|
||||
}
|
||||
|
||||
U0 @imap_fetch_wrapper_function(I64 uid)
|
||||
{
|
||||
@imap_fetch(IMAP_WRAPPER_MAGIC_NUMBER, uid);
|
||||
}
|
||||
|
||||
U0 @imap_fetch_array_wrapper_function(U8* sequence_set, U8* data_items)
|
||||
{
|
||||
@imap_fetch_array(IMAP_WRAPPER_MAGIC_NUMBER, sequence_set, data_items);
|
||||
}
|
||||
|
||||
ImapClient* @imap_new(CTask* mem_task)
|
||||
{
|
||||
if (!mem_task) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ImapClient* c = CAlloc(sizeof(ImapClient), mem_task);
|
||||
c->mem_task = mem_task;
|
||||
c->o = Json.CreateObject(mem_task);
|
||||
c->state = 0;
|
||||
StrCpy(&c->selected, "");
|
||||
@imap_init_buffers(c);
|
||||
c->tag = 1;
|
||||
|
||||
U64 a;
|
||||
I64 buffer_size = (MSize(&@imap_close_wrapper_function) + MSize(&@imap_connect_wrapper_function) + MSize(&@imap_fetch_wrapper_function) + MSize(&@imap_fetch_array_wrapper_function) + MSize(&@imap_select_wrapper_function) + MSize(&@imap_login_wrapper_function) + MSize(&@imap_logout_wrapper_function));
|
||||
buffer_size += buffer_size % 16;
|
||||
|
||||
U64 code_ptr = CAlloc(buffer_size, c->mem_task->code_heap);
|
||||
I64 code_size = 0;
|
||||
|
||||
// Create a copy of function and patch close
|
||||
code_size = MSize(&@imap_close_wrapper_function);
|
||||
c->close = code_ptr;
|
||||
MemCpy(c->close, &@imap_close_wrapper_function, code_size);
|
||||
code_ptr += code_size;
|
||||
|
||||
a = c->close;
|
||||
while (a(U64*)[0] != IMAP_WRAPPER_MAGIC_NUMBER)
|
||||
++a;
|
||||
MemSetI64(a, c, 1);
|
||||
a += 9;
|
||||
@patch_call_rel32(a, &@imap_close);
|
||||
|
||||
// Create a copy of function and patch connect
|
||||
code_size = MSize(&@imap_connect_wrapper_function);
|
||||
c->connect = code_ptr;
|
||||
MemCpy(c->connect, &@imap_connect_wrapper_function, code_size);
|
||||
code_ptr += code_size;
|
||||
|
||||
a = c->connect;
|
||||
while (a(U64*)[0] != IMAP_WRAPPER_MAGIC_NUMBER)
|
||||
++a;
|
||||
MemSetI64(a, c, 1);
|
||||
a += 9;
|
||||
@patch_call_rel32(a, &@imap_connect);
|
||||
|
||||
// Create a copy of function and patch select
|
||||
code_size = MSize(&@imap_select_wrapper_function);
|
||||
c->select = code_ptr;
|
||||
MemCpy(c->select, &@imap_select_wrapper_function, code_size);
|
||||
code_ptr += code_size;
|
||||
|
||||
a = c->select;
|
||||
while (a(U64*)[0] != IMAP_WRAPPER_MAGIC_NUMBER)
|
||||
++a;
|
||||
MemSetI64(a, c, 1);
|
||||
a += 9;
|
||||
@patch_call_rel32(a, &@imap_select);
|
||||
|
||||
// Create a copy of function and patch login
|
||||
code_size = MSize(&@imap_login_wrapper_function);
|
||||
c->login = code_ptr;
|
||||
MemCpy(c->login, &@imap_login_wrapper_function, code_size);
|
||||
code_ptr += code_size;
|
||||
|
||||
a = c->login;
|
||||
while (a(U64*)[0] != IMAP_WRAPPER_MAGIC_NUMBER)
|
||||
++a;
|
||||
MemSetI64(a, c, 1);
|
||||
a += 9;
|
||||
@patch_call_rel32(a, &@imap_login);
|
||||
|
||||
// Create a copy of function and patch logout
|
||||
code_size = MSize(&@imap_logout_wrapper_function);
|
||||
c->logout = code_ptr;
|
||||
MemCpy(c->logout, &@imap_logout_wrapper_function, code_size);
|
||||
code_ptr += code_size;
|
||||
|
||||
a = c->logout;
|
||||
while (a(U64*)[0] != IMAP_WRAPPER_MAGIC_NUMBER)
|
||||
++a;
|
||||
MemSetI64(a, c, 1);
|
||||
a += 9;
|
||||
@patch_call_rel32(a, &@imap_logout);
|
||||
|
||||
// Create a copy of function and patch fetch
|
||||
code_size = MSize(&@imap_fetch_wrapper_function);
|
||||
c->fetch = code_ptr;
|
||||
MemCpy(c->fetch, &@imap_fetch_wrapper_function, code_size);
|
||||
code_ptr += code_size;
|
||||
|
||||
a = c->fetch;
|
||||
while (a(U64*)[0] != IMAP_WRAPPER_MAGIC_NUMBER)
|
||||
++a;
|
||||
MemSetI64(a, c, 1);
|
||||
a += 9;
|
||||
@patch_call_rel32(a, &@imap_fetch);
|
||||
|
||||
// Create a copy of function and patch fetch_array
|
||||
code_size = MSize(&@imap_fetch_array_wrapper_function);
|
||||
c->fetch_array = code_ptr;
|
||||
MemCpy(c->fetch_array, &@imap_fetch_array_wrapper_function, code_size);
|
||||
code_ptr += code_size;
|
||||
|
||||
a = c->fetch_array;
|
||||
while (a(U64*)[0] != IMAP_WRAPPER_MAGIC_NUMBER)
|
||||
++a;
|
||||
MemSetI64(a, c, 1);
|
||||
a += 9;
|
||||
@patch_call_rel32(a, &@imap_fetch_array);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
"imap ";
|
||||
Reference in New Issue
Block a user