From 464fdc1bbda34a37d889c2273cb6317a00e8179c Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Fri, 24 Jan 2025 11:26:48 +0700 Subject: [PATCH] First Commit - [+] feat: add initial implementation of IMAP client package with support for single and multiple user management - [+] chore: add .gitignore file to exclude binaries, IDE files, and environment configurations - [+] docs: add README file with project description, features, installation, usage examples, and license information - [+] docs: add BSD 3-Clause License file - [+] feat(client): implement configuration and connection handling for IMAP clients - [+] feat(client): implement message listing functionality for single and multiple users - [+] feat(client): add support for secure TLS connections in IMAP client - [+] feat(client): create helper functions for initializing single and multi-user IMAP clients - [+] chore: initialize Go module and add dependencies for IMAP and byte buffer pooling --- .gitignore | 34 +++++++++++ LICENSE | 30 ++++++++++ README.md | 120 +++++++++++++++++++++++++++++++++++++++ client/config.go | 30 ++++++++++ client/connection.go | 84 +++++++++++++++++++++++++++ client/list_messages.go | 102 +++++++++++++++++++++++++++++++++ client/new.go | 17 ++++++ client/new_multi_user.go | 21 +++++++ go.mod | 13 +++++ go.sum | 12 ++++ 10 files changed, 463 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 client/config.go create mode 100644 client/connection.go create mode 100644 client/list_messages.go create mode 100644 client/new.go create mode 100644 client/new_multi_user.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40858a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with "go test -c" +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +tmp/ + +# IDE specific files +.vscode +.idea +# https://idx.google.com it's free +.idx + +# .env file +.env + +# Project build +bin + +emails.csv +run.go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..63ddb01 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +BSD 3-Clause License +----------- + +Copyright (c) 2024, H0llyW00dzZ / H0llyW00dz +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fb82a8 --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# IMAP Client Package + +This package provides a simple interface to manage IMAP connections for single or multiple users. It allows you to connect to an IMAP server, list messages, and manage multiple user accounts. + +## Features + +- Connect and disconnect from an IMAP server +- List messages in a specified mailbox +- Manage multiple users with separate IMAP clients + +## Installation + +To install the package, use `go get`: + +```bash +go get git.b0zal.io/H0llyW00dzZ/imap +``` + +## Usage + +### Single User + +```go +package main + +import ( + "fmt" + "log" + + "git.b0zal.io/H0llyW00dzZ/imap/client" +) + +func main() { + config := &client.Config{ + Username: "user@example.com", + Password: "password", + Server: "imap.example.com:993", + InsecureSkipVerify: true, + } + + imapClient := client.NewIMAP(config) + + err := imapClient.Connect() + if err != nil { + log.Fatalf("Failed to connect: %v", err) + } + defer imapClient.Disconnect() + + messageConfig := client.MessageConfig{ + GrabID: true, + GrabFrom: true, + GrabSubject: true, + GrabBody: true, + } + + messages, err := imapClient.ListMessages("INBOX", 10, messageConfig) + if err != nil { + log.Fatalf("Failed to list messages: %v", err) + } + + for _, msg := range messages { + fmt.Printf("ID: %s, From: %v, Subject: %s, Body: %s\n", msg.ID, msg.From, msg.Subject, msg.Body) + } +} +``` + +### Multiple Users + +```go +package main + +import ( + "fmt" + "log" + + "git.b0zal.io/H0llyW00dzZ/imap/client" +) + +func main() { + multiUserIMAP := client.NewMultiUserIMAP() + + multiUserIMAP.AddUser("user1@example.com", "password1", "imap.example.com:993", true) + multiUserIMAP.AddUser("user2@example.com", "password2", "imap.example.com:993", true) + + err := multiUserIMAP.ConnectUser("user1@example.com") + if err != nil { + log.Fatalf("Failed to connect user1: %v", err) + } + defer multiUserIMAP.DisconnectUser("user1@example.com") + + messageConfig := client.MessageConfig{ + GrabID: true, + GrabFrom: true, + GrabSubject: true, + GrabBody: true, + } + + messages, err := multiUserIMAP.ListUserMessages("user1@example.com", "INBOX", 10, messageConfig) + if err != nil { + log.Fatalf("Failed to list messages for user1: %v", err) + } + + for _, msg := range messages { + fmt.Printf("ID: %s, From: %v, Subject: %s, Body: %s\n", msg.ID, msg.From, msg.Subject, msg.Body) + } +} +``` + +## TODO + +- Implement functionality to send emails. +- Add support for folder management (create, delete, rename). +- Enhance error handling and logging. +- Support for message search and filtering. +- Implement message deletion and flagging. +- Add more tests for edge cases and concurrent access. + +## License + +This project is licensed under the BSD 3-Clause License. See the [LICENSE](LICENSE) file for details. diff --git a/client/config.go b/client/config.go new file mode 100644 index 0000000..3bd52cd --- /dev/null +++ b/client/config.go @@ -0,0 +1,30 @@ +// Copyright (c) 2025 H0llyW00dzZ All rights reserved. +// +// By accessing or using this software, you agree to be bound by the terms +// of the License Agreement, which you can find at LICENSE files. + +package client + +import "github.com/emersion/go-imap/client" + +// Config holds the configuration for the IMAP client +type Config struct { + Username string + Password string + Server string + InsecureSkipVerify bool +} + +// MessageConfig defines what parts of the message to retrieve +type MessageConfig struct { + GrabID bool + GrabFrom bool + GrabSubject bool + GrabBody bool +} + +// IMAPClient represents an IMAP client +type IMAPClient struct { + config *Config + client *client.Client +} diff --git a/client/connection.go b/client/connection.go new file mode 100644 index 0000000..231e2ed --- /dev/null +++ b/client/connection.go @@ -0,0 +1,84 @@ +// Copyright (c) 2025 H0llyW00dzZ All rights reserved. +// +// By accessing or using this software, you agree to be bound by the terms +// of the License Agreement, which you can find at LICENSE files. + +package client + +import ( + "crypto/tls" + "fmt" + + "github.com/emersion/go-imap/client" +) + +// Connect establishes a connection to the IMAP server +func (c *IMAPClient) Connect() error { + tlsConfig := &tls.Config{ + InsecureSkipVerify: c.config.InsecureSkipVerify, + } + + cl, err := client.DialTLS(c.config.Server, tlsConfig) + if err != nil { + return fmt.Errorf("failed to connect to server: %v", err) + } + + if err := cl.Login(c.config.Username, c.config.Password); err != nil { + return fmt.Errorf("failed to login: %v", err) + } + + c.client = cl + return nil +} + +// Disconnect logs out and closes the connection +func (c *IMAPClient) Disconnect() error { + if c.client != nil { + if err := c.client.Logout(); err != nil { + return fmt.Errorf("failed to logout: %v", err) + } + } + return nil +} + +// AddUser adds a new user to the MultiUserIMAP +func (m *MultiUserIMAP) AddUser(username, password, server string, insecureSkipVerify bool) { + m.mu.Lock() + defer m.mu.Unlock() + + config := &Config{ + Username: username, + Password: password, + Server: server, + InsecureSkipVerify: insecureSkipVerify, + } + + client := NewIMAP(config) + m.clients[username] = client +} + +// ConnectUser connects a specific user +func (m *MultiUserIMAP) ConnectUser(username string) error { + m.mu.Lock() + defer m.mu.Unlock() + + client, exists := m.clients[username] + if !exists { + return fmt.Errorf("user not found: %s", username) + } + + return client.Connect() +} + +// DisconnectUser disconnects a specific user +func (m *MultiUserIMAP) DisconnectUser(username string) error { + m.mu.Lock() + defer m.mu.Unlock() + + client, exists := m.clients[username] + if !exists { + return fmt.Errorf("user not found: %s", username) + } + + return client.Disconnect() +} diff --git a/client/list_messages.go b/client/list_messages.go new file mode 100644 index 0000000..8058f5b --- /dev/null +++ b/client/list_messages.go @@ -0,0 +1,102 @@ +// Copyright (c) 2025 H0llyW00dzZ All rights reserved. +// +// By accessing or using this software, you agree to be bound by the terms +// of the License Agreement, which you can find at LICENSE files. + +package client + +import ( + "fmt" + + "github.com/emersion/go-imap" + "github.com/valyala/bytebufferpool" +) + +// MessageDetails holds the details of an email message. +// This struct allows the caller to access and format the message's +// ID, sender information, subject, and body as needed. +type MessageDetails struct { + ID string + From []string + Subject string + Body string +} + +// ListMessages lists the messages in the specified mailbox based on the MessageConfig +func (c *IMAPClient) ListMessages(mailbox string, numMessages uint32, config MessageConfig) ([]MessageDetails, error) { + if c.client == nil { + return nil, fmt.Errorf("client is not connected") + } + + mbox, err := c.client.Select(mailbox, false) + if err != nil { + return nil, fmt.Errorf("failed to select %s: %v", mailbox, err) + } + + from := uint32(1) + to := mbox.Messages + if mbox.Messages > numMessages { + from = mbox.Messages - numMessages + 1 + } + seqset := new(imap.SeqSet) + seqset.AddRange(from, to) + + items := []imap.FetchItem{imap.FetchEnvelope} + if config.GrabBody { + items = append(items, imap.FetchItem("BODY.PEEK[]")) + } + + messages := make(chan *imap.Message, numMessages) + done := make(chan error, 1) + go func() { + done <- c.client.Fetch(seqset, items, messages) + }() + + var results []MessageDetails + for msg := range messages { + details := MessageDetails{} + + if config.GrabID { + details.ID = msg.Envelope.MessageId + } + if config.GrabFrom { + for _, addr := range msg.Envelope.From { + details.From = append(details.From, addr.Address()) + } + } + if config.GrabSubject { + details.Subject = msg.Envelope.Subject + } + if config.GrabBody { + for _, literal := range msg.Body { + buf := bytebufferpool.Get() + defer bytebufferpool.Put(buf) + + _, err := buf.ReadFrom(literal) + if err == nil { + details.Body = buf.String() + } + } + } + results = append(results, details) + } + + if err := <-done; err != nil { + return nil, fmt.Errorf("failed to fetch messages: %v", err) + } + + return results, nil +} + +// ListUserMessages lists messages for a specific user based on the MessageConfig +func (m *MultiUserIMAP) ListUserMessages(username, mailbox string, numMessages uint32, config MessageConfig) ([]MessageDetails, error) { + m.mu.Lock() + defer m.mu.Unlock() + + client, exists := m.clients[username] + if !exists { + return nil, fmt.Errorf("user not found: %s", username) + } + + return client.ListMessages(mailbox, numMessages, config) +} diff --git a/client/new.go b/client/new.go new file mode 100644 index 0000000..dcdbb55 --- /dev/null +++ b/client/new.go @@ -0,0 +1,17 @@ +// Copyright (c) 2025 H0llyW00dzZ All rights reserved. +// +// By accessing or using this software, you agree to be bound by the terms +// of the License Agreement, which you can find at LICENSE files. + +package client + +// NewIMAP creates a new IMAP client with the given configuration +func NewIMAP(config *Config) *IMAPClient { + if config == nil { + config = &Config{} + } + + return &IMAPClient{ + config: config, + } +} diff --git a/client/new_multi_user.go b/client/new_multi_user.go new file mode 100644 index 0000000..468cef3 --- /dev/null +++ b/client/new_multi_user.go @@ -0,0 +1,21 @@ +// Copyright (c) 2025 H0llyW00dzZ All rights reserved. +// +// By accessing or using this software, you agree to be bound by the terms +// of the License Agreement, which you can find at LICENSE files. + +package client + +import "sync" + +// MultiUserIMAP manages multiple IMAP clients for different users +type MultiUserIMAP struct { + clients map[string]*IMAPClient + mu sync.Mutex +} + +// NewMultiUserIMAP initializes a new MultiUserIMAP +func NewMultiUserIMAP() *MultiUserIMAP { + return &MultiUserIMAP{ + clients: make(map[string]*IMAPClient), + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a5b4ec8 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module git.b0zal.io/H0llyW00dzZ/imap + +go 1.23.5 + +require ( + github.com/emersion/go-imap v1.2.1 + github.com/valyala/bytebufferpool v1.0.0 +) + +require ( + github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect + golang.org/x/text v0.3.7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0a0f352 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA= +github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= +github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= +github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= +github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= +github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=