Files
mailgoserver/internal/jmap/session.go
T
2026-08-22 06:45:05 +01:00

95 lines
3.2 KiB
Go

package jmap
import (
"encoding/json"
"net/http"
"strconv"
)
// sessionCapability is one entry under the Session object's "capabilities" (account-
// independent) or an account's own "accountCapabilities" — both are just
// urn -> settings-object maps, an empty object being valid when a capability has no
// extra settings to advertise (RFC 8620 §2).
type sessionCapability = map[string]any
type sessionAccount struct {
Name string `json:"name"`
IsPersonal bool `json:"isPersonal"`
IsReadOnly bool `json:"isReadOnly"`
AccountCapabilities map[string]sessionCapability `json:"accountCapabilities"`
}
type sessionResponse struct {
Capabilities map[string]sessionCapability `json:"capabilities"`
Accounts map[string]sessionAccount `json:"accounts"`
PrimaryAccounts map[string]string `json:"primaryAccounts"`
Username string `json:"username"`
APIURL string `json:"apiUrl"`
DownloadURL string `json:"downloadUrl"`
UploadURL string `json:"uploadUrl"`
EventSourceURL string `json:"eventSourceUrl"`
State string `json:"state"`
}
const (
capCore = "urn:ietf:params:jmap:core"
capMail = "urn:ietf:params:jmap:mail"
capSubmission = "urn:ietf:params:jmap:submission"
)
// handleSession serves the RFC 8620 §2 Session resource — the entry point a JMAP
// client fetches once (and re-fetches on the Basic realm challenge/whenever the
// server's "state" changes) to discover every other endpoint URL and this account's
// capabilities.
func (b *Backend) handleSession(w http.ResponseWriter, r *http.Request) {
mbox := mailboxFromContext(r)
accountID := strconv.FormatInt(mbox.ID, 10)
maxSize := b.Cfg.Section("Mailstore").Key("max_message_bytes").MustInt64(25 * 1024 * 1024)
resp := sessionResponse{
Capabilities: map[string]sessionCapability{
capCore: {
"maxSizeUpload": maxSize,
"maxConcurrentUpload": 4,
"maxSizeRequest": maxSize,
"maxConcurrentRequests": 4,
"maxCallsInRequest": 32,
"maxObjectsInGet": 500,
"maxObjectsInSet": 500,
"collationAlgorithms": []string{},
},
capMail: {},
capSubmission: {},
},
Accounts: map[string]sessionAccount{
accountID: {
Name: mbox.Email,
IsPersonal: true,
IsReadOnly: false,
AccountCapabilities: map[string]sessionCapability{
capCore: {}, capMail: {}, capSubmission: {},
},
},
},
PrimaryAccounts: map[string]string{
capMail: accountID,
capSubmission: accountID,
},
Username: mbox.Email,
APIURL: "/jmap/api",
DownloadURL: "/jmap/download/{accountId}/{blobId}/{name}?type={type}",
UploadURL: "/jmap/upload/{accountId}",
EventSourceURL: "/jmap/eventsource?types={types}&closeafter={closeafter}&ping={ping}",
}
state, err := b.DB.MessagesState(mbox.ID)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
resp.State = state
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}