I’m trying to hack out some codes to fetch emails from IMAP server.
I’m able to connect to an IMAP server and send a message to the server
I changed the user name and the password in the code below, because I do not wan to put my user name and password on a public forum.
julia> using Sockets
julia> imap = connect("imap.139.com", 143)
TCPSocket(RawFD(0x00000049) open, 0 bytes waiting)
julia> print(readline(imap))
* OK RICHINFO System IMap Server Ready (caixuns[3764603d6f0ab88-00000])
julia> write(imap, "01 AUTH user_foo password_bar")
30
So I am able to communicate by using write(). However, I do not get the nice message back from the IMAP server. Julia gave me the output of 30, which I think is the length of the message 01 BAD errno:1404, imap command error [1].
The question is, how do I receive the entire message from the server, instead of 30?
[1] I got the message by using telnet to test the IMAP server, not from within Julia.
I believe 30 here is the number of bytes written. What I think you want to do after the write() is a readline(imap) when will read a single line back from the server.
EDIT: Thinking about it, since the interface seems to be text you probably want to do:
println(imap, "01 AUTH user_foo password_bar")
Instead of write so that a \n is sent to the server, it may be waiting for that to complete the line of text.
Two threads wouldn’t hurt, but technically you only need them if the server is sending you asynchronous events. If the server only sends you a message in response to you sending it a message then 1 thread is fine. (Also assuming you can tell how many lines the server is sending back in response.)
So you need to do a println() to send a request to the server and a readline() to get the server’s response. If the imap server sends back multiple lines in response then you would have to do multiple readlines to get the full response back.
With multiple threads you would send a response, then have to wait on your response thread for it to get the message back, which is basically what you are doing with println/readline pairs.
The challenge is that readline() does not give anything after println() or write(). Sorry I didn’t manage to convey this issue.
So after println() or write(), if I use readline(imap), I do not get any response.
Could you maybe try to run this piece of codes? The user name and password are made up, but they suffice for the purpose of testing, because the problem right now is not able getting any message back after first println() or write(). Of course the message will be an error message, if one manages to obtain it.
By the way, I found it challenging to obtain multi-line response from an IMAP server.
For example, the previous code only returns one line of response. If I run readline(imap) twice, in the second time, readline() tries to read a line until the connection is broken.
I tried to use this while() loop, however, the loop would still hang until the connection is broken.
while (! eof(imap))
print(readline(imap))
end
I just thought it would be a good opportunity to try to get your opinion on this here. But I can also open a new thread to ask this question!
A quick look at the example of the protocol here Internet Message Access Protocol - Wikipedia
It seems like each command is terminated with a “[id] OK [something] completed” so what I would probably do is write a method like:
function readresponse(imap, id, cmd)
full = Vector{String}()
done = false
while !done
line = readline(imap)
push!(full, line)
parsed = split(line)
if parsed[2] == "BAD"
done = true
elseif length(parsed) == 4 && parsed[1] == id &&
parsed[2] == "OK" && parsed[3] == uppercase(cmd) &&
parsed[5] == "completed"
done = true
end
end
return full
end
That should just read the response lines and stop if the response indicates an error or when it sees the completed line.
You might also want to update the if to detect the the “Ready” line you get when you connect to the server.
Another option, and I know this now goes threads and I tried to steer you away from that, but this is kind of clean, and I like clean…
struct Response
id::String
error::String
data::Vector{String}
Response(id, err::String) = new(id, err, Vector{String}())
Response(id, data) = new(id, "", data)
end
using Sockets
imap = connect("imap.139.com", 143)
readline(imap)
responses = Channel() do ch
current = Vector{String}()
while !eof(imap) do
line = readline(imap)
parse = split(line)
if length(parse) >= 2 && parse[2] == "BAD"
put!(ch, Response(parse[1], line)
elseif length(parse) > 2 && parse[2] == "OK" && parse[end] == "completed"
put!(ch, Response(parse[1], current)
current = Vector{String}()
else
push!(current, line)
end
end
end
function imapwrite(id, cmd, data)
write(imap, "$id $cmd $data\r\n");
response = take!(responses)
@assert response.id == id "Messages desynchronized, got $(response.id) expecting $id"
@assert response.error == "" response.error
return response.data
end
imapwrite("01", "auth", "someuser someepassword")
inbox = imapwrite("02", "select", "inbox")
dump(inbox)
I’m not 100% there are no errors in there, I just wrote it in discourse so I make no claims about correctness. It would also probably be better if the imapwrite() method generated the next ID but I’ll leave that to you.
Your codes were quite good. I only needed to modify one place.
For example, here is the response from the IMAP server for a login command:
01 OK login completed
This means that the server only responded the action word of the command, in this case, login. And it is not in uppercase. It is not certain whether an other server would return LOGIN instead. Therefore, it’d be safer to use lowercase on both sides when comparing the strings.
Another implication is that parsed[4] needs to be compared with completed.
Because of that, the elseif part of your function needs to be written as