diff --git a/client.go b/client.go index f8805c6..b187cc9 100644 --- a/client.go +++ b/client.go @@ -11,6 +11,7 @@ package onlyoffice import ( "net/http" + "net/http/cookiejar" "os" "strings" ) @@ -35,8 +36,9 @@ type Client struct { // NewClient returns a new Client backed by http.DefaultClient. func NewClient(c Credentials) *Client { + jar, _ := cookiejar.New(nil) return &Client{ - client: http.DefaultClient, + client: &http.Client{Jar: jar}, credentials: &c, } } diff --git a/mails.go b/mails.go index 5689c6c..491f725 100644 --- a/mails.go +++ b/mails.go @@ -7,7 +7,9 @@ import ( "context" "encoding/json" "fmt" + "io" "net/mail" + "net/http" "net/url" "strconv" "strings" @@ -96,6 +98,38 @@ func (c *Client) GetMailMessage(ctx context.Context, messageID string) (map[stri return c.ResponseObject(ctx, "/api/2.0/mail/messages/"+url.PathEscape(id)) } +// DownloadMailAttachment fetches raw attachment bytes by mail attachment id via +// the mail addon's download.ashx handler. This path relies on the session +// cookie captured during authentication, so NewClient configures a cookie jar. +func (c *Client) DownloadMailAttachment(ctx context.Context, attachmentID string) ([]byte, error) { + id := strings.TrimSpace(attachmentID) + if id == "" { + return nil, fmt.Errorf("DownloadMailAttachment: attachment id is required") + } + auth, err := c.authHeader() + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL()+"/addons/mail/httphandlers/download.ashx?attachid="+url.QueryEscape(id), nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", auth) + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + raw, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("DownloadMailAttachment %s: %d %s", id, resp.StatusCode, truncate(string(raw), 400)) + } + return raw, nil +} + // RemoveMailMessages deletes messages by id (PUT /api/2.0/mail/messages/remove). // The API response "response" field may be a number or object; success is HTTP 2xx. func (c *Client) RemoveMailMessages(ctx context.Context, ids ...int) (map[string]any, error) { diff --git a/mails_test.go b/mails_test.go index be81255..86b2374 100644 --- a/mails_test.go +++ b/mails_test.go @@ -1,8 +1,13 @@ package onlyoffice import ( + "context" + "net/http" + "net/http/cookiejar" + "net/http/httptest" "strings" "testing" + "time" ) func TestResolveMailFolder(t *testing.T) { @@ -97,3 +102,60 @@ func TestInt64FromMap(t *testing.T) { t.Fatal("string") } } + +func TestNewClientSetsCookieJar(t *testing.T) { + c := NewClient(Credentials{Url: "https://example.test", User: "u", Password: "p"}) + if c.client == nil { + t.Fatal("client is nil") + } + if c.client.Jar == nil { + t.Fatal("cookie jar is nil") + } + if _, ok := c.client.Jar.(*cookiejar.Jar); !ok { + t.Fatalf("unexpected jar type %T", c.client.Jar) + } +} + +func TestDownloadMailAttachmentUsesAuthCookie(t *testing.T) { + var gotAuth, gotCookie, gotPath string + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/2.0/authentication.json": + http.SetCookie(w, &http.Cookie{Name: "sessionid", Value: "abc123", Path: "/"}) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"response":{"token":"tok","expires":"2099-01-01T00:00:00.0000000+00:00"}}`)) + case "/addons/mail/httphandlers/download.ashx": + gotAuth = r.Header.Get("Authorization") + gotCookie = r.Header.Get("Cookie") + gotPath = r.URL.RequestURI() + if gotCookie == "" { + http.Error(w, "missing cookie", http.StatusUnauthorized) + return + } + _, _ = w.Write([]byte("payload")) + default: + http.NotFound(w, r) + } + })) + defer srv.Close() + + c := NewClient(Credentials{Url: srv.URL, User: "u", Password: "p"}) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + body, err := c.DownloadMailAttachment(ctx, "42") + if err != nil { + t.Fatalf("DownloadMailAttachment: %v", err) + } + if string(body) != "payload" { + t.Fatalf("body = %q", body) + } + if gotAuth != "tok" { + t.Fatalf("auth header = %q", gotAuth) + } + if !strings.Contains(gotCookie, "sessionid=abc123") { + t.Fatalf("cookie header = %q", gotCookie) + } + if gotPath != "/addons/mail/httphandlers/download.ashx?attachid=42" { + t.Fatalf("path = %q", gotPath) + } +}