Replace HTTP status codes with IRC numeric replies in command handlers (closes #54)
All checks were successful
check / check (push) Successful in 2m17s
All checks were successful
check / check (push) Successful in 2m17s
IRC command handlers now return proper IRC numeric reply codes per
RFC 1459/2812 instead of HTTP status codes:
- 401 ERR_NOSUCHNICK for unknown DM targets
- 403 ERR_NOSUCHCHANNEL for invalid/missing channels
- 411 ERR_NORECIPIENT for missing message recipients
- 412 ERR_NOTEXTTOSEND for missing message body
- 421 ERR_UNKNOWNCOMMAND for unknown/empty commands
- 431 ERR_NONICKNAMEGIVEN for missing nick in NICK command
- 432 ERR_ERRONEUSNICKNAME for invalid nick format
- 433 ERR_NICKNAMEINUSE for taken nicks
- 442 ERR_NOTONCHANNEL for non-member channel actions
- 461 ERR_NEEDMOREPARAMS for missing required parameters
Error responses use the IRC numeric format:
{"command":"4xx","from":"server","to":"nick","body":["..."],"params":[...]}
HTTP status codes are now reserved for transport-level concerns:
- 400 for malformed HTTP requests (bad JSON)
- 401 for authentication failures
- 500 for internal server errors
Successful message sends changed from 201 to 200 since HTTP
status codes should not encode IRC-level semantics.
This commit is contained in:
@@ -28,6 +28,18 @@ const (
|
||||
defaultHistLimit = 50
|
||||
maxHistLimit = 500
|
||||
cmdPrivmsg = "PRIVMSG"
|
||||
|
||||
// IRC numeric reply codes per RFC 1459/2812.
|
||||
ircErrNoSuchNick = "401"
|
||||
ircErrNoSuchChannel = "403"
|
||||
ircErrNoRecipient = "411"
|
||||
ircErrNoTextToSend = "412"
|
||||
ircErrUnknownCommand = "421"
|
||||
ircErrNoNicknameGiven = "431"
|
||||
ircErrErroneusNickname = "432"
|
||||
ircErrNicknameInUse = "433"
|
||||
ircErrNotOnChannel = "442"
|
||||
ircErrNeedMoreParams = "461"
|
||||
)
|
||||
|
||||
func (hdlr *Handlers) maxBodySize() int64 {
|
||||
@@ -63,6 +75,43 @@ func (hdlr *Handlers) authSession(
|
||||
return sessionID, clientID, nick, nil
|
||||
}
|
||||
|
||||
// serverName returns the configured server name or the
|
||||
// default "neoirc".
|
||||
func (hdlr *Handlers) serverName() string {
|
||||
if hdlr.params.Config.ServerName != "" {
|
||||
return hdlr.params.Config.ServerName
|
||||
}
|
||||
|
||||
return "neoirc"
|
||||
}
|
||||
|
||||
// respondIRCError sends an IRC numeric error reply as a
|
||||
// JSON response with HTTP 200 OK. This is used for errors
|
||||
// in IRC command processing (as opposed to HTTP transport
|
||||
// errors). The params slice contains IRC-style positional
|
||||
// parameters (e.g. the target nick or channel).
|
||||
func (hdlr *Handlers) respondIRCError(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
nick, numeric, msg string,
|
||||
params ...string,
|
||||
) {
|
||||
resp := map[string]any{
|
||||
"command": numeric,
|
||||
"from": hdlr.serverName(),
|
||||
"to": nick,
|
||||
"body": []string{msg},
|
||||
}
|
||||
|
||||
if len(params) > 0 {
|
||||
resp["params"] = params
|
||||
}
|
||||
|
||||
hdlr.respondJSON(
|
||||
writer, request, resp, http.StatusOK,
|
||||
)
|
||||
}
|
||||
|
||||
func (hdlr *Handlers) requireAuth(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
@@ -226,11 +275,7 @@ func (hdlr *Handlers) deliverMOTD(
|
||||
clientID, sessionID int64,
|
||||
) {
|
||||
motd := hdlr.params.Config.MOTD
|
||||
serverName := hdlr.params.Config.ServerName
|
||||
|
||||
if serverName == "" {
|
||||
serverName = "neoirc"
|
||||
}
|
||||
serverName := hdlr.serverName()
|
||||
|
||||
if motd == "" {
|
||||
return
|
||||
@@ -557,10 +602,10 @@ func (hdlr *Handlers) HandleSendCommand() http.HandlerFunc {
|
||||
payload.To = strings.TrimSpace(payload.To)
|
||||
|
||||
if payload.Command == "" {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"command required",
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrUnknownCommand,
|
||||
"No command given",
|
||||
)
|
||||
|
||||
return
|
||||
@@ -631,10 +676,11 @@ func (hdlr *Handlers) dispatchCommand(
|
||||
},
|
||||
http.StatusOK)
|
||||
default:
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"unknown command: "+command,
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrUnknownCommand,
|
||||
command+" :Unknown command",
|
||||
command,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -648,10 +694,10 @@ func (hdlr *Handlers) handlePrivmsg(
|
||||
bodyLines func() []string,
|
||||
) {
|
||||
if target == "" {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"to field required",
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrNoRecipient,
|
||||
"No recipient given ("+command+")",
|
||||
)
|
||||
|
||||
return
|
||||
@@ -659,10 +705,10 @@ func (hdlr *Handlers) handlePrivmsg(
|
||||
|
||||
lines := bodyLines()
|
||||
if len(lines) == 0 {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"body required",
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrNoTextToSend,
|
||||
"No text to send",
|
||||
)
|
||||
|
||||
return
|
||||
@@ -694,10 +740,11 @@ func (hdlr *Handlers) handleChannelMsg(
|
||||
request.Context(), target,
|
||||
)
|
||||
if err != nil {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"channel not found",
|
||||
http.StatusNotFound,
|
||||
nick, ircErrNoSuchChannel,
|
||||
"No such channel",
|
||||
target,
|
||||
)
|
||||
|
||||
return
|
||||
@@ -720,10 +767,11 @@ func (hdlr *Handlers) handleChannelMsg(
|
||||
}
|
||||
|
||||
if !isMember {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"not a member of this channel",
|
||||
http.StatusForbidden,
|
||||
nick, ircErrNotOnChannel,
|
||||
"You're not on that channel",
|
||||
target,
|
||||
)
|
||||
|
||||
return
|
||||
@@ -761,7 +809,7 @@ func (hdlr *Handlers) handleChannelMsg(
|
||||
|
||||
hdlr.respondJSON(writer, request,
|
||||
map[string]string{"id": msgUUID, "status": "sent"},
|
||||
http.StatusCreated)
|
||||
http.StatusOK)
|
||||
}
|
||||
|
||||
func (hdlr *Handlers) handleDirectMsg(
|
||||
@@ -775,10 +823,11 @@ func (hdlr *Handlers) handleDirectMsg(
|
||||
request.Context(), target,
|
||||
)
|
||||
if err != nil {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"user not found",
|
||||
http.StatusNotFound,
|
||||
nick, ircErrNoSuchNick,
|
||||
"No such nick/channel",
|
||||
target,
|
||||
)
|
||||
|
||||
return
|
||||
@@ -805,7 +854,7 @@ func (hdlr *Handlers) handleDirectMsg(
|
||||
|
||||
hdlr.respondJSON(writer, request,
|
||||
map[string]string{"id": msgUUID, "status": "sent"},
|
||||
http.StatusCreated)
|
||||
http.StatusOK)
|
||||
}
|
||||
|
||||
func (hdlr *Handlers) handleJoin(
|
||||
@@ -815,10 +864,11 @@ func (hdlr *Handlers) handleJoin(
|
||||
nick, target string,
|
||||
) {
|
||||
if target == "" {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"to field required",
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrNeedMoreParams,
|
||||
"Not enough parameters",
|
||||
"JOIN",
|
||||
)
|
||||
|
||||
return
|
||||
@@ -830,10 +880,11 @@ func (hdlr *Handlers) handleJoin(
|
||||
}
|
||||
|
||||
if !validChannelRe.MatchString(channel) {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"invalid channel name",
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrNoSuchChannel,
|
||||
"No such channel",
|
||||
channel,
|
||||
)
|
||||
|
||||
return
|
||||
@@ -895,10 +946,11 @@ func (hdlr *Handlers) handlePart(
|
||||
body json.RawMessage,
|
||||
) {
|
||||
if target == "" {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"to field required",
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrNeedMoreParams,
|
||||
"Not enough parameters",
|
||||
"PART",
|
||||
)
|
||||
|
||||
return
|
||||
@@ -913,10 +965,11 @@ func (hdlr *Handlers) handlePart(
|
||||
request.Context(), channel,
|
||||
)
|
||||
if err != nil {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"channel not found",
|
||||
http.StatusNotFound,
|
||||
nick, ircErrNoSuchChannel,
|
||||
"No such channel",
|
||||
channel,
|
||||
)
|
||||
|
||||
return
|
||||
@@ -967,10 +1020,10 @@ func (hdlr *Handlers) handleNick(
|
||||
) {
|
||||
lines := bodyLines()
|
||||
if len(lines) == 0 {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"body required (new nick)",
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrNoNicknameGiven,
|
||||
"No nickname given",
|
||||
)
|
||||
|
||||
return
|
||||
@@ -979,10 +1032,11 @@ func (hdlr *Handlers) handleNick(
|
||||
newNick := strings.TrimSpace(lines[0])
|
||||
|
||||
if !validNickRe.MatchString(newNick) {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"invalid nick",
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrErroneusNickname,
|
||||
"Erroneous nickname",
|
||||
newNick,
|
||||
)
|
||||
|
||||
return
|
||||
@@ -1003,10 +1057,11 @@ func (hdlr *Handlers) handleNick(
|
||||
)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "UNIQUE") {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"nick already in use",
|
||||
http.StatusConflict,
|
||||
nick, ircErrNicknameInUse,
|
||||
"Nickname is already in use",
|
||||
newNick,
|
||||
)
|
||||
|
||||
return
|
||||
@@ -1093,10 +1148,11 @@ func (hdlr *Handlers) handleTopic(
|
||||
bodyLines func() []string,
|
||||
) {
|
||||
if target == "" {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"to field required",
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrNeedMoreParams,
|
||||
"Not enough parameters",
|
||||
"TOPIC",
|
||||
)
|
||||
|
||||
return
|
||||
@@ -1104,10 +1160,11 @@ func (hdlr *Handlers) handleTopic(
|
||||
|
||||
lines := bodyLines()
|
||||
if len(lines) == 0 {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"body required (topic text)",
|
||||
http.StatusBadRequest,
|
||||
nick, ircErrNeedMoreParams,
|
||||
"Not enough parameters",
|
||||
"TOPIC",
|
||||
)
|
||||
|
||||
return
|
||||
@@ -1140,10 +1197,11 @@ func (hdlr *Handlers) handleTopic(
|
||||
request.Context(), channel,
|
||||
)
|
||||
if err != nil {
|
||||
hdlr.respondError(
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
"channel not found",
|
||||
http.StatusNotFound,
|
||||
nick, ircErrNoSuchChannel,
|
||||
"No such channel",
|
||||
channel,
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user