Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
jmap
server.crt
server.key
TODO.md
/jmapd
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ RUN CGO_ENABLED=0 go build -o /jmap ./cmd/jmapd
FROM scratch

COPY --from=builder /jmap /jmap
COPY --from=builder /app/server.crt /server.crt
COPY --from=builder /app/server.key /server.key

EXPOSE 8080

Expand Down
15 changes: 13 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,24 @@ lint: fmt vet test
@echo "Code passed fmt, vet, and tests"

build: lint
go build .
go build ./cmd/jmapd/
#GOEXPERIMENT=jsonv2

dbuild:
docker build -t jmap .

drun: dbuild
docker run --rm --name jmap \
-p 8080:8080 \
-v "$(PWD)/server.crt:/certs/server.crt:ro" \
-v "$(PWD)/server.key:/certs/server.key:ro" \
jmap

linux:
GOOS=linux GOARCH=amd64 go build -o jmap

run: build
./jmap
./jmapd

clean:
rm -f jmap
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,16 @@ JMAP (JSON Meta Application Protocol) is a modern, efficient protocol for synchr

- cmd/jmapd: contains all code required for a jmap server.
- internal: contains reusable code that isn't jmap specific.


# Dev Setup

- Clone the repository using
```
git clone github.com/maneeshaxyz/jmap
```

- generate a .cert and .key for testing using
```
openssl req -x509 -newkey rsa:4096 -nodes -keyout server.key -out server.crt -days 365 -subj "/CN=localhost"
```
12 changes: 12 additions & 0 deletions cmd/jmapd/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

type Accounts map[string]Account

type Account struct {
Name string `json:"name"`
IsPersonal bool `json:"isPersonal"`
IsReadOnly bool `json:"isReadOnly"`
AccountCapabilities AccountCapabilities `json:"accountCapabilities"`
}

type AccountCapabilities map[string]any
41 changes: 41 additions & 0 deletions cmd/jmapd/handlers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"encoding/json"
"fmt"
"net/http"
)
Expand Down Expand Up @@ -44,5 +45,45 @@ func (app *application) healthCheck(w http.ResponseWriter, r *http.Request) {
app.serverError(w, err)
return
}
}

func (app *application) wellKnownHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode("/jmap/session"); err != nil {
app.serverError(w, err)
return
}
}

func (app *application) sessionHandler(w http.ResponseWriter, r *http.Request) {
session := Session{
Capabilities: Capabilities{
"urn:ietf:params:jmap:core": CoreCapability{
MaxSizeUpload: 50000000,
MaxConcurrentUpload: 4,
MaxSizeRequest: 10000000,
MaxConcurrentRequests: 4,
MaxCallsInRequest: 16,
MaxObjectsInGet: 500,
MaxObjectsInSet: 500,
CollationAlgorithms: []string{"i;ascii-numeric"},
},
},
PrimaryAccounts: map[string]string{
"urn:ietf:params:jmap:mail": "account1",
},
Username: "user@example.com",
APIURL: "https://api.example.com/jmap/",
DownloadURL: "https://api.example.com/jmap/download/{accountId}/{blobId}/{type}/{name}",
UploadURL: "https://api.example.com/jmap/upload/{accountId}",
EventSourceURL: "https://api.example.com/jmap/events?types={types}&closeafter={closeafter}&ping={ping}",
State: "some_state_string",
Accounts: make(Accounts), // Fill with actual accounts
}

w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(session); err != nil {
app.serverError(w, err)
return
}
}
13 changes: 11 additions & 2 deletions cmd/jmapd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (
)

type config struct {
port int
port int
certfile string
keyfile string
}

type application struct {
Expand All @@ -26,12 +28,19 @@ type application struct {

func main() {
var cfg config

flag.IntVar(&cfg.port, "port", 8080, "HTTP port")
flag.StringVar(&cfg.certfile, "certfile", "server.crt", "Cert File")
flag.StringVar(&cfg.keyfile, "keyfile", "server.key", "Key File")
flag.Parse()

infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)

if cfg.certfile == "" || cfg.keyfile == "" {
errorLog.Fatal("TLS enabled: certfile and keyfile must be provided")
}

app := &application{
config: cfg,
errorLog: errorLog,
Expand All @@ -49,6 +58,6 @@ func main() {

infoLog.Printf("Starting server on %s", srv.Addr)

err := srv.ListenAndServe()
err := srv.ListenAndServeTLS(cfg.certfile, cfg.keyfile)
errorLog.Fatal(err)
}
2 changes: 2 additions & 0 deletions cmd/jmapd/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ func (a *application) routes() *http.ServeMux {
mux.HandleFunc("/get/resource", a.getHandler)
mux.HandleFunc("/post/resource", a.postHandler)
mux.HandleFunc("/healthcheck", a.healthCheck)
mux.HandleFunc("/.well-known/jmap", a.wellKnownHandler)
mux.HandleFunc("/jmap/session", a.sessionHandler)

return mux
}
27 changes: 27 additions & 0 deletions cmd/jmapd/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

type Session struct {
Capabilities Capabilities `json:"capabilities"`
Accounts Accounts `json:"acounts"`
PrimaryAccounts map[string]string `json:"primaryAccounts,omitempty"`
Username string `json:"username"`
APIURL string `json:"apiUrl"`
DownloadURL string `json:"downloadUrl"`
UploadURL string `json:"uploadUrl"`
EventSourceURL string `json:"eventSourceUrl"`
State string `json:"state"`
}

// set to map[string]any so clients can ignore unknown Capabilities
type Capabilities map[string]any

type CoreCapability struct {
MaxSizeUpload uint64 `json:"maxSizeUpload"`
MaxConcurrentUpload uint64 `json:"maxConcurrentUpload"`
MaxSizeRequest uint64 `json:"maxSizeRequest"`
MaxConcurrentRequests uint64 `json:"maxConcurrentRequests"`
MaxCallsInRequest uint64 `json:"maxCallsInRequest"`
MaxObjectsInGet uint64 `json:"maxObjectsInGet"`
MaxObjectsInSet uint64 `json:"maxObjectsInSet"`
CollationAlgorithms []string `json:"collationAlgorithms"`
}
Loading