// subscribeCmd handles subscribe command - clients send this when subscribe // on channel, if channel if private then we must validate provided sign here before // actually subscribe client on channel func (c *client) subscribeCmd(cmd *SubscribeClientCommand) (*response, error) { resp := newResponse("subscribe") channel := cmd.Channel if channel == "" { return nil, ErrInvalidMessage } c.app.RLock() secret := c.app.config.Secret maxChannelLength := c.app.config.MaxChannelLength insecure := c.app.config.Insecure c.app.RUnlock() if len(channel) > maxChannelLength { resp.Err(ErrLimitExceeded) return resp, nil } body := &SubscribeBody{ Channel: channel, } resp.Body = body if !c.app.userAllowed(channel, c.User) || !c.app.clientAllowed(channel, c.UID) { resp.Err(ErrPermissionDenied) return resp, nil } chOpts, err := c.app.channelOpts(channel) if err != nil { resp.Err(err) return resp, nil } if !chOpts.Anonymous && c.User == "" && !insecure { resp.Err(ErrPermissionDenied) return resp, nil } if c.app.privateChannel(channel) { // private channel - subscription must be properly signed if string(c.UID) != string(cmd.Client) { resp.Err(ErrPermissionDenied) return resp, nil } isValid := auth.CheckChannelSign(secret, string(cmd.Client), string(channel), cmd.Info, cmd.Sign) if !isValid { resp.Err(ErrPermissionDenied) return resp, nil } c.channelInfo[channel] = []byte(cmd.Info) } c.Channels[channel] = true info := c.info(channel) err = c.app.addSub(channel, c) if err != nil { logger.ERROR.Println(err) return resp, ErrInternalServerError } if chOpts.Presence { err = c.app.addPresence(channel, c.UID, info) if err != nil { logger.ERROR.Println(err) return nil, ErrInternalServerError } } if chOpts.JoinLeave { err = c.app.pubJoinLeave(channel, "join", info) if err != nil { logger.ERROR.Println(err) } } if c.app.mediator != nil { c.app.mediator.Subscribe(channel, c.UID, c.User) } body.Status = true return resp, nil }
// subscribeCmd handles subscribe command - clients send this when subscribe // on channel, if channel if private then we must validate provided sign here before // actually subscribe client on channel. Optionally we can send missed messages to // client if it provided last message id seen in channel. func (c *client) subscribeCmd(cmd *SubscribeClientCommand) (*clientResponse, error) { resp := newClientResponse("subscribe") channel := cmd.Channel if channel == "" { return nil, ErrInvalidMessage } c.app.RLock() secret := c.app.config.Secret maxChannelLength := c.app.config.MaxChannelLength channelLimit := c.app.config.ClientChannelLimit insecure := c.app.config.Insecure c.app.RUnlock() if len(channel) > maxChannelLength { logger.ERROR.Printf("channel too long: max %d, got %d", maxChannelLength, len(channel)) resp.Err(clientError{ErrLimitExceeded, errorAdviceFix}) return resp, nil } if len(c.Channels) >= channelLimit { logger.ERROR.Printf("maximimum limit of channels per client reached: %d", channelLimit) resp.Err(clientError{ErrLimitExceeded, errorAdviceFix}) return resp, nil } body := &SubscribeBody{ Channel: channel, } resp.Body = body if _, ok := c.Channels[channel]; ok { resp.Err(clientError{ErrAlreadySubscribed, errorAdviceFix}) return resp, nil } if !c.app.userAllowed(channel, c.User) || !c.app.clientAllowed(channel, c.UID) { resp.Err(clientError{ErrPermissionDenied, errorAdviceFix}) return resp, nil } chOpts, err := c.app.channelOpts(channel) if err != nil { resp.Err(clientError{err, errorAdviceFix}) return resp, nil } if !chOpts.Anonymous && c.User == "" && !insecure { resp.Err(clientError{ErrPermissionDenied, errorAdviceFix}) return resp, nil } if c.app.privateChannel(channel) { // private channel - subscription must be properly signed if string(c.UID) != string(cmd.Client) { resp.Err(clientError{ErrPermissionDenied, errorAdviceFix}) return resp, nil } isValid := auth.CheckChannelSign(secret, string(cmd.Client), string(channel), cmd.Info, cmd.Sign) if !isValid { resp.Err(clientError{ErrPermissionDenied, errorAdviceFix}) return resp, nil } c.channelInfo[channel] = []byte(cmd.Info) } c.Channels[channel] = true info := c.info(channel) err = c.app.addSub(channel, c) if err != nil { logger.ERROR.Println(err) return resp, ErrInternalServerError } if chOpts.Presence { err = c.app.addPresence(channel, c.UID, info) if err != nil { logger.ERROR.Println(err) return nil, ErrInternalServerError } } if chOpts.Recover { if cmd.Recover { // Client provided subscribe request with recover flag on. Try to recover missed messages // automatically from history (we suppose here that history configured wisely) based on // provided last message id value. messages, err := c.app.History(channel) if err != nil { logger.ERROR.Printf("can't recover messages for channel %s: %s", string(channel), err) body.Messages = []Message{} } else { recoveredMessages, recovered := recoverMessages(cmd.Last, messages) body.Messages = recoveredMessages body.Recovered = recovered } } else { // Client don't want to recover messages yet, we just return last message id to him here. lastMessageID, err := c.app.lastMessageID(channel) if err != nil { logger.ERROR.Println(err) } else { body.Last = lastMessageID } } } if chOpts.JoinLeave { go func() { err = c.app.pubJoinLeave(channel, "join", info) if err != nil { logger.ERROR.Println(err) } }() } if c.app.mediator != nil { c.app.mediator.Subscribe(channel, c.UID, c.User) } body.Status = true return resp, nil }