Giter Club home page Giter Club logo

imap's Introduction

IMAP

This is an IMAP library for Haskell that aims to be efficient, easy to use, transparent when it comes to underlying libraries and support results streaming. To this end it employs ListT, so you can use it with any concurrency management library of your choosing.

It tries to implement RFC3501 as faithfully as possible, diverging from it where we noticed that servers have different ideas. If you want to understand this library, it's highly recommended to skim through that RFC first.

Usage

For a description of types used in this tutorial or an in-depth description of functions presented, please check the documentation or the source code.

All of the commands will output their results in ListT and MonadIO. Results consist of a list of UntaggedResults followed by a single TaggedResult that describes the command state (if it succeeded or failed).

We provide a helper function that simplifies the output types for the cases when you don't care about the streaming and just want a list of UntaggedResults or an error message. Depending on your needs you will probably use it for all the commands that are not FETCH.

Also, remember that you probably have to keep the connection alive so that the server doesn't disconnect you. Send a noop from time to time to achieve that.

Simple, no streaming

You need a connection object first, so that you can execute commands on it. It's produced by connectServer, which accepts parameters from Network.Connection. Say you want to connect to gmail:

import Network.Connection
import Network.IMAP
import Network.IMAP.Types

let tls = TLSSettingsSimple False False False
let params = ConnectionParams "imap.gmail.com" 993 (Just tls) Nothing
conn <- connectServer params Nothing

From now on you can run commands on this connection. The second parameter to connectServer is Maybe IMAPSettings. If settings are not provided, sane defaults will be used. We will use the simpleFormat helper function to convert from ListT to IO. Let's log in:

> simpleFormat $ login conn "mylogin" "mypass"
Right [Capabilities [CIMAP4,CUnselect,CIdle,CNamespace,CQuota,CId,CExperimental "XLIST",CChildren,CExperimental "X-GM-EXT-1",CUIDPlus,CCompress "DEFLATE",CEnable,CMove,CCondstore,CEsearch,CUtf8 "ACCEPT",CListExtended,CListStatus,CAppendLimit 35882577]]

You can see that the server replied with a CAPABILITIES reply and the login was successful. Next, let's select an inbox:

> simpleFormat $ select conn "inbox2"
Left "[NONEXISTENT] Unknown Mailbox: inbox2 (Failure)"

Oh, let's fix that

> simpleFormat $ select conn "inbox"
Right [Flags [FAnswered,FFlagged,FDraft,FDeleted,FSeen,FOther "$NotPhishing",FOther "$Phishing",FOther "NonJunk"],PermanentFlags [FAnswered,FFlagged,FDraft,FDeleted,FSeen,FOther "$NotPhishing",FOther "$Phishing",FOther "NonJunk",FAny],UIDValidity 1,Exists 65,Recent 0,UIDNext 1050,HighestModSeq 251971]

Again you can use the metadata if you wish to. Let's see what messages we have (consult the RFC if you're unsure about the parameter to uidSearch):

> simpleFormat $ uidSearch conn "ALL"
Right [Search [105,219,411,424,425,748,763,770,774,819,824,825,..]]

Fetching a message is straigtforward as well:

> simpleFormat $ uidFetch conn "219"
Right [Fetch [MessageId 2,UID 219,Body "Delivered-To: [email protected]\r\nReceived: by...

If you need more control on the parameters of fetch, there is a more general function available:

> simpleFormat $ uidFetchG conn "219 ALL"
Right [Fetch [MessageId 2,UID 219,Envelope {eDate = Just "Tue, 10 Nov 2015 20:42:47 +0000", eSubject=...}, Flags [FAnswered,FSeen], Size 4880]]

Do you want multiple messages in one reply? That's easy with UID ranges!

> simpleFormat $ uidFetchG conn "219:9000 RFC822.SIZE"
Right [Fetch [MessageId 2,UID 219,Size 4880],Fetch [MessageId 3,UID 411,Size 7392],...]

That's where streaming comes in handy - if these were message bodies you would probably like to do something with them before all are downloaded.

Replies we didn't expect

IMAP protocol allows for messages pushed to the client at any time, even when they're not requested. This is used to notify the client that a new message had arrived, or as status of a message had changed as it was read by another client. These server messages wait for you in a bounded message queue and you can read them like:

import qualified Data.STM.RollingQueue as RQ
msgs <- atomically . RQ.read . untaggedQueue $ conn

Where conn is the connection from previous step.

Streaming

There's an excellent article by Gabriel Gonzalez you should read :)

ToDo

We would like to see more tests. Actual parsing of BODYSTRUCTURE replies would be nice, but the output format seems to be poorly documented and a bit insane, so PRs are appreciated.

imap's People

Contributors

cblp avatar jakzale avatar joehealy avatar mkawalec avatar yiding avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

imap's Issues

Capabilities can contain +

At least the LITERAL+ capabilty contains a + and that breaks your parsing, since you only accept things matching isAtomChar and + is not among those.

I am not sure if it makes sense to modify isAtomChar or add a special case for the parsing of capabilities.

email structure parser fails to parse address where addr-name is NIL

An address string like this would fail to parse: ((NIL NIL "foo" "example.com")).

This represents an email that has no display name field (i.e. it'll show up in email clients as a raw email address), and entirely valid per spec:

address         = "(" addr-name SP addr-adl SP addr-mailbox SP
                  addr-host ")"

addr-name       = nstring
                    ; If non-NIL, holds phrase from [RFC-2822]
                    ; mailbox after removing [RFC-2822] quoting

nstring         = string / nil

This comes up when fetching the ENVELOPE attribute of a message.

The existing code seems to be always matching the starting quote of the field (with my comments):

parseEmail :: Parser EmailAddress
parseEmail = do
  string "(\"" -- quote is always being matched here, nilOrValue only gets applied to the body.
  label <- nilOrValue $ AP.takeWhile1 (/= _quotedbl)
  string "\" NIL \"" -- this being forced to nil is also suspicious...

  emailUsername <- AP.takeWhile1 (/= _quotedbl)
  string "\" \""
  emailDomain <- AP.takeWhile1 (/= _quotedbl)
  string "\")"
  let fullAddr = decodeUtf8 $ BSC.concat [emailUsername, "@", emailDomain]

  return $ EmailAddress (liftM decodeUtf8 label) fullAddr

Mailbox names need to be quoted

I see that mailbox names aren't quoted or otherwise escaped while the command is being constructed; this fails for mailboxes whose names contain spaces or other characters which aren't valid in an IMAP atoms.

e.g.:

IMAP.select conn "[Gmail]/All Mail"
-> fails with a BAD "Could not parse command".

IMAP.select conn ""[Gmail]/All Mail""
-> works.

Problems with 32-bit Int

So this is a bit nitpicky, but...

The CONDSTORE extension defines that the modseq value is a 64-bit int. However, UntaggedResult uses HighestModSeq Int, and there are Haskell systems where Ints are 32-bits long, which would cause... actually, I'm not sure what would happen, but it would be bad.

I don't know if this affects any real-life systems, but you should be aware.

build an IMAPbackup tool

I need a tool to one-way sync an IMAP account to my home backup server. Every time the sync job runs it should update the local backup to reflect the state of the remote account. Local changes are not made.

I tried offlinesync and mbsync for this task but both programs fail when a folder gets deleted and recreated.

If you would write such a tool on top of your library I would be happy to test and use it and report back all issues I find.

Thanks for consideration.

Parse error that should never happen

Received an error that should not occur.

Executing a fetchG with a list of UIDs and FAST as the argument - the first line of the block below.

Any ideas how to proceed?

Working with commit: a80f9f1

"595320,595326,595332,595338,595344,595350,595356,595362,595368,595374 FAST"
Cannot dispatch a parse error without aLeft "Parse failed with error: 'Failed reading: takeWhile1' while reading an input chunk: '\"* 9503 FETCH (UID 595326 INTERNALDATE \\\"20-Feb-2017 17:24:18 +1100\\\" RFC822.SIZE 2576 FLAGS (\\\\Seen))\\r\\n* 9509 FETCH (UID 595332 INTERNALDATE \\\"20-Feb-2017 17:29:17 +1100\\\" RFC822.SIZE 2576 FLAGS (\\\\Seen))\\r\\n* 9515 FETCH (UID 595338 INTERNALDATE \\\"20-Feb-2017 17:34:20 +1100\\\" RFC822.SIZE 2576 FLAGS (\\\\Seen))\\r\\n* 9521 FETCH (UID 595344 INTERNALDATE \\\"20-Feb-2017 17:39:19 +1100\\\" RFC822.SIZE 2575 FLAGS (\\\\Seen))\\r\\n* 9527 FETCH (UID 595350 INTERNALDATE \\\"20-Feb-2017 17:44:19 +1100\\\" RFC822.SIZE 2576 FLAGS (\\\\Seen))\\r\\n* 9533 FETCH (UID 595356 INTERNALDATE \\\"20-Feb-2017 17:49:19 +1100\\\" RFC822.SIZE 3141 FLAGS (\\\\Seen))\\r\\n* 9539 FETCH (UID 595362 INTERNALDATE \\\"20-Feb-2017 17:54:17 +1100\\\" RFC822.SIZE 3138 FLAGS (\\\\Seen))\\r\\n* 9545 FETCH (UID 595368 INTERNALDATE \\\"20-Feb-2017 17:59:21 +1100\\\" RFC822.SIZE 3140 FLAGS (\\\\Seen))\\r\\n* 9551 FETCH (UID 595374 INTERNALDATE \\\"20-Feb-2017 18:04:21 +1100\\\" RFC822.SIZE 3140 FLAGS (\\\\Seen))\\r\\nfmnhksmna OK UID FETCH completed\\r\\n\"'.\n\nThis should never happen and is a library error. To open an issue please go to https://github.com/mkawalec/imap/issues"
n outstanding request

Parsing problem

I'm getting:

Parse failed with error: 'Failed reading: takeWhile1' while reading an input chunk: '"* SEARCH\r\ngvuyrkepn OK Search completed (0.001 + 0.000 secs).\r\n"'.

This should never happen and is a library error. To open an issue please go to https://github.com/mkawalec/imap/issues

After executing:

simpleFormat $ uidSearch conn ("NOT KEYWORD CALLBACK_CALLED")

when there's no such email, when there's, it works.

version constraints too tight

The usual procedure is to provide version ranges, or at least to only pin major and not minor versions, under the understanding that dependencies should be PVP-compliant. If you pin even the minor versions, people will quickly find this package nearly entirely unusable.

Package Bump

Would you be able to bump the package dependencies? It's based on a pretty old version of base, network and containers

FETCH response data items should not be UntaggedResult

Why are FETCH response data items (msg-att) simple UntaggedResult constructors instead of their own data type?

Given that msg-att can only appear in a FETCH response line, I think it makes sense for the fetch response to be something like Fetch Int [MessageAttr] where MessageAttr is something like:

data MessageAttr = MsgFlags ... | MsgEnvelope ... | MsgSize ... | MsgBody ...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.