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) }