diff --git a/appservice/http.go b/appservice/http.go index 66c7bc5b..1ebe6e56 100644 --- a/appservice/http.go +++ b/appservice/http.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Tulir Asokan +// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -19,6 +19,7 @@ import ( "github.com/gorilla/mux" "github.com/rs/zerolog" + "go.mau.fi/util/exhttp" "go.mau.fi/util/exstrings" "maunium.net/go/mautrix" @@ -79,17 +80,9 @@ func (as *AppService) Stop() { func (as *AppService) CheckServerToken(w http.ResponseWriter, r *http.Request) (isValid bool) { authHeader := r.Header.Get("Authorization") if !strings.HasPrefix(authHeader, "Bearer ") { - Error{ - ErrorCode: ErrUnknownToken, - HTTPStatus: http.StatusForbidden, - Message: "Missing access token", - }.Write(w) + mautrix.MMissingToken.WithMessage("Missing access token").Write(w) } else if !exstrings.ConstantTimeEqual(authHeader[len("Bearer "):], as.Registration.ServerToken) { - Error{ - ErrorCode: ErrUnknownToken, - HTTPStatus: http.StatusForbidden, - Message: "Incorrect access token", - }.Write(w) + mautrix.MUnknownToken.WithMessage("Invalid access token").Write(w) } else { isValid = true } @@ -105,21 +98,13 @@ func (as *AppService) PutTransaction(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) txnID := vars["txnID"] if len(txnID) == 0 { - Error{ - ErrorCode: ErrNoTransactionID, - HTTPStatus: http.StatusBadRequest, - Message: "Missing transaction ID", - }.Write(w) + mautrix.MInvalidParam.WithMessage("Missing transaction ID").Write(w) return } defer r.Body.Close() body, err := io.ReadAll(r.Body) if err != nil || len(body) == 0 { - Error{ - ErrorCode: ErrNotJSON, - HTTPStatus: http.StatusBadRequest, - Message: "Missing request body", - }.Write(w) + mautrix.MNotJSON.WithMessage("Failed to read response body").Write(w) return } log := as.Log.With().Str("transaction_id", txnID).Logger() @@ -128,7 +113,7 @@ func (as *AppService) PutTransaction(w http.ResponseWriter, r *http.Request) { ctx = log.WithContext(ctx) if as.txnIDC.IsProcessed(txnID) { // Duplicate transaction ID: no-op - WriteBlankOK(w) + exhttp.WriteEmptyJSONResponse(w, http.StatusOK) log.Debug().Msg("Ignoring duplicate transaction") return } @@ -137,14 +122,10 @@ func (as *AppService) PutTransaction(w http.ResponseWriter, r *http.Request) { err = json.Unmarshal(body, &txn) if err != nil { log.Error().Err(err).Msg("Failed to parse transaction content") - Error{ - ErrorCode: ErrBadJSON, - HTTPStatus: http.StatusBadRequest, - Message: "Failed to parse body JSON", - }.Write(w) + mautrix.MBadJSON.WithMessage("Failed to parse transaction content").Write(w) } else { as.handleTransaction(ctx, txnID, &txn) - WriteBlankOK(w) + exhttp.WriteEmptyJSONResponse(w, http.StatusOK) } } @@ -263,12 +244,9 @@ func (as *AppService) GetRoom(w http.ResponseWriter, r *http.Request) { roomAlias := vars["roomAlias"] ok := as.QueryHandler.QueryAlias(roomAlias) if ok { - WriteBlankOK(w) + exhttp.WriteEmptyJSONResponse(w, http.StatusOK) } else { - Error{ - ErrorCode: ErrUnknown, - HTTPStatus: http.StatusNotFound, - }.Write(w) + mautrix.MNotFound.WithMessage("Alias not found").Write(w) } } @@ -282,12 +260,9 @@ func (as *AppService) GetUser(w http.ResponseWriter, r *http.Request) { userID := id.UserID(vars["userID"]) ok := as.QueryHandler.QueryUser(userID) if ok { - WriteBlankOK(w) + exhttp.WriteEmptyJSONResponse(w, http.StatusOK) } else { - Error{ - ErrorCode: ErrUnknown, - HTTPStatus: http.StatusNotFound, - }.Write(w) + mautrix.MNotFound.WithMessage("User not found").Write(w) } } @@ -297,11 +272,7 @@ func (as *AppService) PostPing(w http.ResponseWriter, r *http.Request) { } body, err := io.ReadAll(r.Body) if err != nil || len(body) == 0 || !json.Valid(body) { - Error{ - ErrorCode: ErrNotJSON, - HTTPStatus: http.StatusBadRequest, - Message: "Missing request body", - }.Write(w) + mautrix.MNotJSON.WithMessage("Invalid or missing request body").Write(w) return } @@ -309,27 +280,21 @@ func (as *AppService) PostPing(w http.ResponseWriter, r *http.Request) { _ = json.Unmarshal(body, &txn) as.Log.Debug().Str("txn_id", txn.TxnID).Msg("Received ping from homeserver") - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write([]byte("{}")) + exhttp.WriteEmptyJSONResponse(w, http.StatusOK) } func (as *AppService) GetLive(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Content-Type", "application/json") if as.Live { - w.WriteHeader(http.StatusOK) + exhttp.WriteEmptyJSONResponse(w, http.StatusOK) } else { - w.WriteHeader(http.StatusInternalServerError) + exhttp.WriteEmptyJSONResponse(w, http.StatusInternalServerError) } - w.Write([]byte("{}")) } func (as *AppService) GetReady(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Content-Type", "application/json") if as.Ready { - w.WriteHeader(http.StatusOK) + exhttp.WriteEmptyJSONResponse(w, http.StatusOK) } else { - w.WriteHeader(http.StatusInternalServerError) + exhttp.WriteEmptyJSONResponse(w, http.StatusInternalServerError) } - w.Write([]byte("{}")) } diff --git a/appservice/protocol.go b/appservice/protocol.go index 7a9891ef..7c493bcb 100644 --- a/appservice/protocol.go +++ b/appservice/protocol.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Tulir Asokan +// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -7,9 +7,7 @@ package appservice import ( - "encoding/json" "fmt" - "net/http" "strings" "github.com/rs/zerolog" @@ -103,50 +101,3 @@ func (txn *Transaction) ContentString() string { // EventListener is a function that receives events. type EventListener func(evt *event.Event) - -// WriteBlankOK writes a blank OK message as a reply to a HTTP request. -func WriteBlankOK(w http.ResponseWriter) { - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("{}")) -} - -// Respond responds to a HTTP request with a JSON object. -func Respond(w http.ResponseWriter, data interface{}) error { - w.Header().Add("Content-Type", "application/json") - dataStr, err := json.Marshal(data) - if err != nil { - return err - } - _, err = w.Write(dataStr) - return err -} - -// Error represents a Matrix protocol error. -type Error struct { - HTTPStatus int `json:"-"` - ErrorCode ErrorCode `json:"errcode"` - Message string `json:"error"` -} - -func (err Error) Write(w http.ResponseWriter) { - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(err.HTTPStatus) - _ = Respond(w, &err) -} - -// ErrorCode is the machine-readable code in an Error. -type ErrorCode string - -// Native ErrorCodes -const ( - ErrUnknownToken ErrorCode = "M_UNKNOWN_TOKEN" - ErrBadJSON ErrorCode = "M_BAD_JSON" - ErrNotJSON ErrorCode = "M_NOT_JSON" - ErrUnknown ErrorCode = "M_UNKNOWN" -) - -// Custom ErrorCodes -const ( - ErrNoTransactionID ErrorCode = "NET.MAUNIUM.NO_TRANSACTION_ID" -) diff --git a/appservice/websocket.go b/appservice/websocket.go index 598d70d1..3d5bd232 100644 --- a/appservice/websocket.go +++ b/appservice/websocket.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Tulir Asokan +// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -23,6 +23,8 @@ import ( "github.com/rs/zerolog" "github.com/tidwall/gjson" "github.com/tidwall/sjson" + + "maunium.net/go/mautrix" ) type WebsocketRequest struct { @@ -371,12 +373,12 @@ func (as *AppService) StartWebsocket(baseURL string, onConnect func()) error { "X-Mautrix-Websocket-Version": []string{"3"}, }) if resp != nil && resp.StatusCode >= 400 { - var errResp Error + var errResp mautrix.RespError err = json.NewDecoder(resp.Body).Decode(&errResp) if err != nil { return fmt.Errorf("websocket request returned HTTP %d with non-JSON body", resp.StatusCode) } else { - return fmt.Errorf("websocket request returned %s (HTTP %d): %s", errResp.ErrorCode, resp.StatusCode, errResp.Message) + return fmt.Errorf("websocket request returned %s (HTTP %d): %s", errResp.ErrCode, resp.StatusCode, errResp.Err) } } else if err != nil { return fmt.Errorf("failed to open websocket: %w", err)