github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+incompatible/app/post.go (about) 1 // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package app 5 6 import ( 7 "crypto/hmac" 8 "crypto/sha1" 9 "encoding/hex" 10 "encoding/json" 11 "fmt" 12 "io" 13 "net/http" 14 "net/url" 15 "path" 16 "strings" 17 18 "github.com/dyatlov/go-opengraph/opengraph" 19 "golang.org/x/net/html/charset" 20 21 "github.com/mattermost/mattermost-server/mlog" 22 "github.com/mattermost/mattermost-server/model" 23 "github.com/mattermost/mattermost-server/plugin" 24 "github.com/mattermost/mattermost-server/services/httpservice" 25 "github.com/mattermost/mattermost-server/store" 26 "github.com/mattermost/mattermost-server/utils" 27 ) 28 29 func (a *App) CreatePostAsUser(post *model.Post, clearPushNotifications bool) (*model.Post, *model.AppError) { 30 // Check that channel has not been deleted 31 var channel *model.Channel 32 if result := <-a.Srv.Store.Channel().Get(post.ChannelId, true); result.Err != nil { 33 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, result.Err.Error(), http.StatusBadRequest) 34 return nil, err 35 } else { 36 channel = result.Data.(*model.Channel) 37 } 38 39 if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { 40 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) 41 return nil, err 42 } 43 44 if channel.DeleteAt != 0 { 45 err := model.NewAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "", http.StatusBadRequest) 46 return nil, err 47 } 48 49 if rp, err := a.CreatePost(post, channel, true); err != nil { 50 if err.Id == "api.post.create_post.root_id.app_error" || 51 err.Id == "api.post.create_post.channel_root_id.app_error" || 52 err.Id == "api.post.create_post.parent_id.app_error" { 53 err.StatusCode = http.StatusBadRequest 54 } 55 56 if err.Id == "api.post.create_post.town_square_read_only" { 57 uchan := a.Srv.Store.User().Get(post.UserId) 58 var user *model.User 59 if result := <-uchan; result.Err != nil { 60 return nil, result.Err 61 } else { 62 user = result.Data.(*model.User) 63 } 64 65 T := utils.GetUserTranslations(user.Locale) 66 a.SendEphemeralPost( 67 post.UserId, 68 &model.Post{ 69 ChannelId: channel.Id, 70 ParentId: post.ParentId, 71 RootId: post.RootId, 72 UserId: post.UserId, 73 Message: T("api.post.create_post.town_square_read_only"), 74 CreateAt: model.GetMillis() + 1, 75 }, 76 ) 77 } 78 79 return nil, err 80 } else { 81 // Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app) 82 if _, ok := post.Props["from_webhook"]; !ok { 83 if _, err := a.MarkChannelsAsViewed([]string{post.ChannelId}, post.UserId, clearPushNotifications); err != nil { 84 mlog.Error(fmt.Sprintf("Encountered error updating last viewed, channel_id=%s, user_id=%s, err=%v", post.ChannelId, post.UserId, err)) 85 } 86 } 87 88 return rp, nil 89 } 90 91 } 92 93 func (a *App) CreatePostMissingChannel(post *model.Post, triggerWebhooks bool) (*model.Post, *model.AppError) { 94 var channel *model.Channel 95 cchan := a.Srv.Store.Channel().Get(post.ChannelId, true) 96 if result := <-cchan; result.Err != nil { 97 return nil, result.Err 98 } else { 99 channel = result.Data.(*model.Channel) 100 } 101 102 return a.CreatePost(post, channel, triggerWebhooks) 103 } 104 105 func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhooks bool) (*model.Post, *model.AppError) { 106 post.SanitizeProps() 107 108 var pchan store.StoreChannel 109 if len(post.RootId) > 0 { 110 pchan = a.Srv.Store.Post().Get(post.RootId) 111 } 112 113 uchan := a.Srv.Store.User().Get(post.UserId) 114 var user *model.User 115 if result := <-uchan; result.Err != nil { 116 return nil, result.Err 117 } else { 118 user = result.Data.(*model.User) 119 } 120 121 if a.License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly && 122 !post.IsSystemMessage() && 123 channel.Name == model.DEFAULT_CHANNEL && 124 !a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { 125 return nil, model.NewAppError("createPost", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) 126 } 127 128 // Verify the parent/child relationships are correct 129 var parentPostList *model.PostList 130 if pchan != nil { 131 if presult := <-pchan; presult.Err != nil { 132 return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) 133 } else { 134 parentPostList = presult.Data.(*model.PostList) 135 if len(parentPostList.Posts) == 0 || !parentPostList.IsChannelId(post.ChannelId) { 136 return nil, model.NewAppError("createPost", "api.post.create_post.channel_root_id.app_error", nil, "", http.StatusInternalServerError) 137 } 138 139 if post.ParentId == "" { 140 post.ParentId = post.RootId 141 } 142 143 if post.RootId != post.ParentId { 144 parent := parentPostList.Posts[post.ParentId] 145 if parent == nil { 146 return nil, model.NewAppError("createPost", "api.post.create_post.parent_id.app_error", nil, "", http.StatusInternalServerError) 147 } 148 } 149 } 150 } 151 152 post.Hashtags, _ = model.ParseHashtags(post.Message) 153 154 if err := a.FillInPostProps(post, channel); err != nil { 155 return nil, err 156 } 157 158 if a.PluginsReady() { 159 var rejectionError *model.AppError 160 pluginContext := &plugin.Context{} 161 a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 162 replacementPost, rejectionReason := hooks.MessageWillBePosted(pluginContext, post) 163 if rejectionReason != "" { 164 rejectionError = model.NewAppError("createPost", "Post rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest) 165 return false 166 } 167 if replacementPost != nil { 168 post = replacementPost 169 } 170 171 return true 172 }, plugin.MessageWillBePostedId) 173 if rejectionError != nil { 174 return nil, rejectionError 175 } 176 } 177 178 var rpost *model.Post 179 if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { 180 return nil, result.Err 181 } else { 182 rpost = result.Data.(*model.Post) 183 } 184 185 if a.PluginsReady() { 186 a.Go(func() { 187 pluginContext := &plugin.Context{} 188 a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 189 hooks.MessageHasBeenPosted(pluginContext, rpost) 190 return true 191 }, plugin.MessageHasBeenPostedId) 192 }) 193 } 194 195 esInterface := a.Elasticsearch 196 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 197 a.Go(func() { 198 esInterface.IndexPost(rpost, channel.TeamId) 199 }) 200 } 201 202 if a.Metrics != nil { 203 a.Metrics.IncrementPostCreate() 204 } 205 206 if len(post.FileIds) > 0 { 207 // There's a rare bug where the client sends up duplicate FileIds so protect against that 208 post.FileIds = utils.RemoveDuplicatesFromStringArray(post.FileIds) 209 210 for _, fileId := range post.FileIds { 211 if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil { 212 mlog.Error(fmt.Sprintf("Encountered error attaching files to post, post_id=%s, user_id=%s, file_ids=%v, err=%v", post.Id, post.FileIds, post.UserId, result.Err), mlog.String("post_id", post.Id)) 213 } 214 } 215 216 if a.Metrics != nil { 217 a.Metrics.IncrementPostFileAttachment(len(post.FileIds)) 218 } 219 } 220 221 if err := a.handlePostEvents(rpost, user, channel, triggerWebhooks, parentPostList); err != nil { 222 return nil, err 223 } 224 225 return rpost, nil 226 } 227 228 // FillInPostProps should be invoked before saving posts to fill in properties such as 229 // channel_mentions. 230 // 231 // If channel is nil, FillInPostProps will look up the channel corresponding to the post. 232 func (a *App) FillInPostProps(post *model.Post, channel *model.Channel) *model.AppError { 233 channelMentions := post.ChannelMentions() 234 channelMentionsProp := make(map[string]interface{}) 235 236 if len(channelMentions) > 0 { 237 if channel == nil { 238 result := <-a.Srv.Store.Channel().GetForPost(post.Id) 239 if result.Err != nil { 240 return model.NewAppError("FillInPostProps", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, result.Err.Error(), http.StatusBadRequest) 241 } 242 channel = result.Data.(*model.Channel) 243 } 244 245 mentionedChannels, err := a.GetChannelsByNames(channelMentions, channel.TeamId) 246 if err != nil { 247 return err 248 } 249 250 for _, mentioned := range mentionedChannels { 251 if mentioned.Type == model.CHANNEL_OPEN { 252 channelMentionsProp[mentioned.Name] = map[string]interface{}{ 253 "display_name": mentioned.DisplayName, 254 } 255 } 256 } 257 } 258 259 if len(channelMentionsProp) > 0 { 260 post.AddProp("channel_mentions", channelMentionsProp) 261 } else if post.Props != nil { 262 delete(post.Props, "channel_mentions") 263 } 264 265 return nil 266 } 267 268 func (a *App) handlePostEvents(post *model.Post, user *model.User, channel *model.Channel, triggerWebhooks bool, parentPostList *model.PostList) *model.AppError { 269 var tchan store.StoreChannel 270 if len(channel.TeamId) > 0 { 271 tchan = a.Srv.Store.Team().Get(channel.TeamId) 272 } 273 274 var team *model.Team 275 if tchan != nil { 276 if result := <-tchan; result.Err != nil { 277 return result.Err 278 } else { 279 team = result.Data.(*model.Team) 280 } 281 } else { 282 // Blank team for DMs 283 team = &model.Team{} 284 } 285 286 a.InvalidateCacheForChannel(channel) 287 a.InvalidateCacheForChannelPosts(channel.Id) 288 289 if _, err := a.SendNotifications(post, team, channel, user, parentPostList); err != nil { 290 return err 291 } 292 293 if triggerWebhooks { 294 a.Go(func() { 295 if err := a.handleWebhookEvents(post, team, channel, user); err != nil { 296 mlog.Error(err.Error()) 297 } 298 }) 299 } 300 301 return nil 302 } 303 304 func (a *App) SendEphemeralPost(userId string, post *model.Post) *model.Post { 305 post.Type = model.POST_EPHEMERAL 306 307 // fill in fields which haven't been specified which have sensible defaults 308 if post.Id == "" { 309 post.Id = model.NewId() 310 } 311 if post.CreateAt == 0 { 312 post.CreateAt = model.GetMillis() 313 } 314 if post.Props == nil { 315 post.Props = model.StringInterface{} 316 } 317 318 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userId, nil) 319 message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) 320 a.Publish(message) 321 322 return post 323 } 324 325 func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { 326 post.SanitizeProps() 327 328 var oldPost *model.Post 329 if result := <-a.Srv.Store.Post().Get(post.Id); result.Err != nil { 330 return nil, result.Err 331 } else { 332 oldPost = result.Data.(*model.PostList).Posts[post.Id] 333 334 if oldPost == nil { 335 err := model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest) 336 return nil, err 337 } 338 339 if oldPost.DeleteAt != 0 { 340 err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}, "", http.StatusBadRequest) 341 return nil, err 342 } 343 344 if oldPost.IsSystemMessage() { 345 err := model.NewAppError("UpdatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id, http.StatusBadRequest) 346 return nil, err 347 } 348 349 if a.License() != nil { 350 if *a.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message { 351 err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *a.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest) 352 return nil, err 353 } 354 } 355 } 356 357 newPost := &model.Post{} 358 *newPost = *oldPost 359 360 if newPost.Message != post.Message { 361 newPost.Message = post.Message 362 newPost.EditAt = model.GetMillis() 363 newPost.Hashtags, _ = model.ParseHashtags(post.Message) 364 } 365 366 if !safeUpdate { 367 newPost.IsPinned = post.IsPinned 368 newPost.HasReactions = post.HasReactions 369 newPost.FileIds = post.FileIds 370 newPost.Props = post.Props 371 } 372 373 if err := a.FillInPostProps(post, nil); err != nil { 374 return nil, err 375 } 376 377 if a.PluginsReady() { 378 var rejectionReason string 379 pluginContext := &plugin.Context{} 380 a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 381 newPost, rejectionReason = hooks.MessageWillBeUpdated(pluginContext, newPost, oldPost) 382 return post != nil 383 }, plugin.MessageWillBeUpdatedId) 384 if newPost == nil { 385 return nil, model.NewAppError("UpdatePost", "Post rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest) 386 } 387 } 388 389 if result := <-a.Srv.Store.Post().Update(newPost, oldPost); result.Err != nil { 390 return nil, result.Err 391 } else { 392 rpost := result.Data.(*model.Post) 393 394 if a.PluginsReady() { 395 a.Go(func() { 396 pluginContext := &plugin.Context{} 397 a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 398 hooks.MessageHasBeenUpdated(pluginContext, newPost, oldPost) 399 return true 400 }, plugin.MessageHasBeenUpdatedId) 401 }) 402 } 403 404 esInterface := a.Elasticsearch 405 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 406 a.Go(func() { 407 if rchannel := <-a.Srv.Store.Channel().GetForPost(rpost.Id); rchannel.Err != nil { 408 mlog.Error(fmt.Sprintf("Couldn't get channel %v for post %v for Elasticsearch indexing.", rpost.ChannelId, rpost.Id)) 409 } else { 410 esInterface.IndexPost(rpost, rchannel.Data.(*model.Channel).TeamId) 411 } 412 }) 413 } 414 415 a.sendUpdatedPostEvent(rpost) 416 417 a.InvalidateCacheForChannelPosts(rpost.ChannelId) 418 419 return rpost, nil 420 } 421 } 422 423 func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *model.AppError) { 424 post, err := a.GetSinglePost(postId) 425 if err != nil { 426 return nil, err 427 } 428 429 post.Patch(patch) 430 431 updatedPost, err := a.UpdatePost(post, false) 432 if err != nil { 433 return nil, err 434 } 435 436 return updatedPost, nil 437 } 438 439 func (a *App) sendUpdatedPostEvent(post *model.Post) { 440 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, "", nil) 441 message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) 442 a.Publish(message) 443 } 444 445 func (a *App) GetPostsPage(channelId string, page int, perPage int) (*model.PostList, *model.AppError) { 446 if result := <-a.Srv.Store.Post().GetPosts(channelId, page*perPage, perPage, true); result.Err != nil { 447 return nil, result.Err 448 } else { 449 return result.Data.(*model.PostList), nil 450 } 451 } 452 453 func (a *App) GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 454 if result := <-a.Srv.Store.Post().GetPosts(channelId, offset, limit, true); result.Err != nil { 455 return nil, result.Err 456 } else { 457 return result.Data.(*model.PostList), nil 458 } 459 } 460 461 func (a *App) GetPostsEtag(channelId string) string { 462 return (<-a.Srv.Store.Post().GetEtag(channelId, true)).Data.(string) 463 } 464 465 func (a *App) GetPostsSince(channelId string, time int64) (*model.PostList, *model.AppError) { 466 if result := <-a.Srv.Store.Post().GetPostsSince(channelId, time, true); result.Err != nil { 467 return nil, result.Err 468 } else { 469 return result.Data.(*model.PostList), nil 470 } 471 } 472 473 func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) { 474 if result := <-a.Srv.Store.Post().GetSingle(postId); result.Err != nil { 475 return nil, result.Err 476 } else { 477 return result.Data.(*model.Post), nil 478 } 479 } 480 481 func (a *App) GetPostThread(postId string) (*model.PostList, *model.AppError) { 482 if result := <-a.Srv.Store.Post().Get(postId); result.Err != nil { 483 return nil, result.Err 484 } else { 485 return result.Data.(*model.PostList), nil 486 } 487 } 488 489 func (a *App) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) { 490 if result := <-a.Srv.Store.Post().GetFlaggedPosts(userId, offset, limit); result.Err != nil { 491 return nil, result.Err 492 } else { 493 return result.Data.(*model.PostList), nil 494 } 495 } 496 497 func (a *App) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) (*model.PostList, *model.AppError) { 498 if result := <-a.Srv.Store.Post().GetFlaggedPostsForTeam(userId, teamId, offset, limit); result.Err != nil { 499 return nil, result.Err 500 } else { 501 return result.Data.(*model.PostList), nil 502 } 503 } 504 505 func (a *App) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 506 if result := <-a.Srv.Store.Post().GetFlaggedPostsForChannel(userId, channelId, offset, limit); result.Err != nil { 507 return nil, result.Err 508 } else { 509 return result.Data.(*model.PostList), nil 510 } 511 } 512 513 func (a *App) GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) { 514 if result := <-a.Srv.Store.Post().Get(postId); result.Err != nil { 515 return nil, result.Err 516 } else { 517 list := result.Data.(*model.PostList) 518 519 if len(list.Order) != 1 { 520 return nil, model.NewAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "", http.StatusNotFound) 521 } 522 post := list.Posts[list.Order[0]] 523 524 var channel *model.Channel 525 var err *model.AppError 526 if channel, err = a.GetChannel(post.ChannelId); err != nil { 527 return nil, err 528 } 529 530 if err = a.JoinChannel(channel, userId); err != nil { 531 return nil, err 532 } 533 534 return list, nil 535 } 536 } 537 538 func (a *App) GetPostsBeforePost(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { 539 if result := <-a.Srv.Store.Post().GetPostsBefore(channelId, postId, perPage, page*perPage); result.Err != nil { 540 return nil, result.Err 541 } else { 542 return result.Data.(*model.PostList), nil 543 } 544 } 545 546 func (a *App) GetPostsAfterPost(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { 547 if result := <-a.Srv.Store.Post().GetPostsAfter(channelId, postId, perPage, page*perPage); result.Err != nil { 548 return nil, result.Err 549 } else { 550 return result.Data.(*model.PostList), nil 551 } 552 } 553 554 func (a *App) GetPostsAroundPost(postId, channelId string, offset, limit int, before bool) (*model.PostList, *model.AppError) { 555 var pchan store.StoreChannel 556 if before { 557 pchan = a.Srv.Store.Post().GetPostsBefore(channelId, postId, limit, offset) 558 } else { 559 pchan = a.Srv.Store.Post().GetPostsAfter(channelId, postId, limit, offset) 560 } 561 562 if result := <-pchan; result.Err != nil { 563 return nil, result.Err 564 } else { 565 return result.Data.(*model.PostList), nil 566 } 567 } 568 569 func (a *App) DeletePost(postId, deleteByID string) (*model.Post, *model.AppError) { 570 if result := <-a.Srv.Store.Post().GetSingle(postId); result.Err != nil { 571 result.Err.StatusCode = http.StatusBadRequest 572 return nil, result.Err 573 } else { 574 post := result.Data.(*model.Post) 575 576 if result := <-a.Srv.Store.Post().Delete(postId, model.GetMillis(), deleteByID); result.Err != nil { 577 return nil, result.Err 578 } 579 580 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil) 581 message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) 582 a.Publish(message) 583 584 a.Go(func() { 585 a.DeletePostFiles(post) 586 }) 587 a.Go(func() { 588 a.DeleteFlaggedPosts(post.Id) 589 }) 590 591 esInterface := a.Elasticsearch 592 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 593 a.Go(func() { 594 esInterface.DeletePost(post) 595 }) 596 } 597 598 a.InvalidateCacheForChannelPosts(post.ChannelId) 599 600 return post, nil 601 } 602 } 603 604 func (a *App) DeleteFlaggedPosts(postId string) { 605 if result := <-a.Srv.Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); result.Err != nil { 606 mlog.Warn(fmt.Sprintf("Unable to delete flagged post preference when deleting post, err=%v", result.Err)) 607 return 608 } 609 } 610 611 func (a *App) DeletePostFiles(post *model.Post) { 612 if len(post.FileIds) != 0 { 613 return 614 } 615 616 if result := <-a.Srv.Store.FileInfo().DeleteForPost(post.Id); result.Err != nil { 617 mlog.Warn(fmt.Sprintf("Encountered error when deleting files for post, post_id=%v, err=%v", post.Id, result.Err), mlog.String("post_id", post.Id)) 618 } 619 } 620 621 func (a *App) parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId string, includeDeleted bool) (*model.Channel, error) { 622 if strings.HasPrefix(channelName, "@") && strings.Contains(channelName, ",") { 623 var userIds []string 624 users, err := a.GetUsersByUsernames(strings.Split(channelName[1:], ","), false) 625 if err != nil { 626 return nil, err 627 } 628 for _, user := range users { 629 userIds = append(userIds, user.Id) 630 } 631 632 channel, err := a.GetGroupChannel(userIds) 633 if err != nil { 634 return nil, err 635 } 636 return channel, nil 637 } 638 639 if strings.HasPrefix(channelName, "@") && !strings.Contains(channelName, ",") { 640 user, err := a.GetUserByUsername(channelName[1:]) 641 if err != nil { 642 return nil, err 643 } 644 channel, err := a.GetDirectChannel(userId, user.Id) 645 if err != nil { 646 return nil, err 647 } 648 return channel, nil 649 } 650 651 channel, err := a.GetChannelByName(channelName, teamId, includeDeleted) 652 if err != nil { 653 return nil, err 654 } 655 return channel, nil 656 } 657 658 func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError) { 659 paramsList := model.ParseSearchParams(terms, timeZoneOffset) 660 includeDeleted := includeDeletedChannels && *a.Config().TeamSettings.ExperimentalViewArchivedChannels 661 662 esInterface := a.Elasticsearch 663 if license := a.License(); esInterface != nil && *a.Config().ElasticsearchSettings.EnableSearching && license != nil && *license.Features.Elasticsearch { 664 finalParamsList := []*model.SearchParams{} 665 666 for _, params := range paramsList { 667 params.OrTerms = isOrSearch 668 // Don't allow users to search for "*" 669 if params.Terms != "*" { 670 // Convert channel names to channel IDs 671 for idx, channelName := range params.InChannels { 672 channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels) 673 if err != nil { 674 mlog.Error(fmt.Sprint(err)) 675 continue 676 } 677 params.InChannels[idx] = channel.Id 678 } 679 680 // Convert usernames to user IDs 681 for idx, username := range params.FromUsers { 682 if user, err := a.GetUserByUsername(username); err != nil { 683 mlog.Error(fmt.Sprint(err)) 684 } else { 685 params.FromUsers[idx] = user.Id 686 } 687 } 688 689 finalParamsList = append(finalParamsList, params) 690 } 691 } 692 693 // If the processed search params are empty, return empty search results. 694 if len(finalParamsList) == 0 { 695 return model.MakePostSearchResults(model.NewPostList(), nil), nil 696 } 697 698 // We only allow the user to search in channels they are a member of. 699 userChannels, err := a.GetChannelsForUser(teamId, userId, includeDeleted) 700 if err != nil { 701 mlog.Error(fmt.Sprint(err)) 702 return nil, err 703 } 704 705 postIds, matches, err := a.Elasticsearch.SearchPosts(userChannels, finalParamsList, page, perPage) 706 if err != nil { 707 return nil, err 708 } 709 710 // Get the posts 711 postList := model.NewPostList() 712 if len(postIds) > 0 { 713 if presult := <-a.Srv.Store.Post().GetPostsByIds(postIds); presult.Err != nil { 714 return nil, presult.Err 715 } else { 716 for _, p := range presult.Data.([]*model.Post) { 717 if p.DeleteAt == 0 { 718 postList.AddPost(p) 719 postList.AddOrder(p.Id) 720 } 721 } 722 } 723 } 724 725 return model.MakePostSearchResults(postList, matches), nil 726 } else { 727 if !*a.Config().ServiceSettings.EnablePostSearch { 728 return nil, model.NewAppError("SearchPostsInTeam", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented) 729 } 730 731 // Since we don't support paging we just return nothing for later pages 732 if page > 0 { 733 return model.MakePostSearchResults(model.NewPostList(), nil), nil 734 } 735 736 channels := []store.StoreChannel{} 737 738 for _, params := range paramsList { 739 params.IncludeDeletedChannels = includeDeleted 740 params.OrTerms = isOrSearch 741 // don't allow users to search for everything 742 if params.Terms != "*" { 743 for idx, channelName := range params.InChannels { 744 if strings.HasPrefix(channelName, "@") { 745 channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels) 746 if err != nil { 747 mlog.Error(fmt.Sprint(err)) 748 continue 749 } 750 params.InChannels[idx] = channel.Name 751 } 752 } 753 channels = append(channels, a.Srv.Store.Post().Search(teamId, userId, params)) 754 } 755 } 756 757 posts := model.NewPostList() 758 for _, channel := range channels { 759 if result := <-channel; result.Err != nil { 760 return nil, result.Err 761 } else { 762 data := result.Data.(*model.PostList) 763 posts.Extend(data) 764 } 765 } 766 767 posts.SortByCreateAt() 768 769 return model.MakePostSearchResults(posts, nil), nil 770 } 771 } 772 773 func (a *App) GetFileInfosForPost(postId string, readFromMaster bool) ([]*model.FileInfo, *model.AppError) { 774 pchan := a.Srv.Store.Post().GetSingle(postId) 775 fchan := a.Srv.Store.FileInfo().GetForPost(postId, readFromMaster, true) 776 777 var infos []*model.FileInfo 778 if result := <-fchan; result.Err != nil { 779 return nil, result.Err 780 } else { 781 infos = result.Data.([]*model.FileInfo) 782 } 783 784 if len(infos) == 0 { 785 // No FileInfos were returned so check if they need to be created for this post 786 var post *model.Post 787 if result := <-pchan; result.Err != nil { 788 return nil, result.Err 789 } else { 790 post = result.Data.(*model.Post) 791 } 792 793 if len(post.Filenames) > 0 { 794 a.Srv.Store.FileInfo().InvalidateFileInfosForPostCache(postId) 795 // The post has Filenames that need to be replaced with FileInfos 796 infos = a.MigrateFilenamesToFileInfos(post) 797 } 798 } 799 800 return infos, nil 801 } 802 803 func (a *App) GetOpenGraphMetadata(requestURL string) *opengraph.OpenGraph { 804 og := opengraph.NewOpenGraph() 805 806 res, err := a.HTTPService.MakeClient(false).Get(requestURL) 807 if err != nil { 808 mlog.Error(fmt.Sprintf("GetOpenGraphMetadata request failed for url=%v with err=%v", requestURL, err.Error())) 809 return og 810 } 811 defer consumeAndClose(res) 812 813 contentType := res.Header.Get("Content-Type") 814 body := forceHTMLEncodingToUTF8(res.Body, contentType) 815 816 if err := og.ProcessHTML(body); err != nil { 817 mlog.Error(fmt.Sprintf("GetOpenGraphMetadata processing failed for url=%v with err=%v", requestURL, err.Error())) 818 } 819 820 makeOpenGraphURLsAbsolute(og, requestURL) 821 822 // The URL should be the link the user provided in their message, not a redirected one. 823 if og.URL != "" { 824 og.URL = requestURL 825 } 826 827 return og 828 } 829 830 func forceHTMLEncodingToUTF8(body io.Reader, contentType string) io.Reader { 831 r, err := charset.NewReader(body, contentType) 832 if err != nil { 833 mlog.Error(fmt.Sprintf("forceHTMLEncodingToUTF8 failed to convert for contentType=%v with err=%v", contentType, err.Error())) 834 return body 835 } 836 return r 837 } 838 839 func makeOpenGraphURLsAbsolute(og *opengraph.OpenGraph, requestURL string) { 840 parsedRequestURL, err := url.Parse(requestURL) 841 if err != nil { 842 mlog.Warn(fmt.Sprintf("makeOpenGraphURLsAbsolute failed to parse url=%v", requestURL)) 843 return 844 } 845 846 makeURLAbsolute := func(resultURL string) string { 847 if resultURL == "" { 848 return resultURL 849 } 850 851 parsedResultURL, err := url.Parse(resultURL) 852 if err != nil { 853 mlog.Warn(fmt.Sprintf("makeOpenGraphURLsAbsolute failed to parse result url=%v", resultURL)) 854 return resultURL 855 } 856 857 if parsedResultURL.IsAbs() { 858 return resultURL 859 } 860 861 return parsedRequestURL.ResolveReference(parsedResultURL).String() 862 } 863 864 og.URL = makeURLAbsolute(og.URL) 865 866 for _, image := range og.Images { 867 image.URL = makeURLAbsolute(image.URL) 868 image.SecureURL = makeURLAbsolute(image.SecureURL) 869 } 870 871 for _, audio := range og.Audios { 872 audio.URL = makeURLAbsolute(audio.URL) 873 audio.SecureURL = makeURLAbsolute(audio.SecureURL) 874 } 875 876 for _, video := range og.Videos { 877 video.URL = makeURLAbsolute(video.URL) 878 video.SecureURL = makeURLAbsolute(video.SecureURL) 879 } 880 } 881 882 func (a *App) DoPostAction(postId, actionId, userId, selectedOption string) *model.AppError { 883 pchan := a.Srv.Store.Post().GetSingle(postId) 884 885 var post *model.Post 886 if result := <-pchan; result.Err != nil { 887 return result.Err 888 } else { 889 post = result.Data.(*model.Post) 890 } 891 892 cchan := a.Srv.Store.Channel().GetForPost(postId) 893 var channel *model.Channel 894 if result := <-cchan; result.Err != nil { 895 return result.Err 896 } else { 897 channel = result.Data.(*model.Channel) 898 } 899 900 action := post.GetAction(actionId) 901 if action == nil || action.Integration == nil { 902 return model.NewAppError("DoPostAction", "api.post.do_action.action_id.app_error", nil, fmt.Sprintf("action=%v", action), http.StatusNotFound) 903 } 904 905 request := &model.PostActionIntegrationRequest{ 906 UserId: userId, 907 ChannelId: post.ChannelId, 908 TeamId: channel.TeamId, 909 PostId: postId, 910 Type: action.Type, 911 Context: action.Integration.Context, 912 } 913 914 if action.Type == model.POST_ACTION_TYPE_SELECT { 915 request.DataSource = action.DataSource 916 request.Context["selected_option"] = selectedOption 917 } 918 919 req, _ := http.NewRequest("POST", action.Integration.URL, strings.NewReader(request.ToJson())) 920 req.Header.Set("Content-Type", "application/json") 921 req.Header.Set("Accept", "application/json") 922 923 // Allow access to plugin routes for action buttons 924 var httpClient *httpservice.Client 925 url, _ := url.Parse(action.Integration.URL) 926 siteURL, _ := url.Parse(*a.Config().ServiceSettings.SiteURL) 927 subpath, _ := utils.GetSubpathFromConfig(a.Config()) 928 if (url.Hostname() == "localhost" || url.Hostname() == "127.0.0.1" || url.Hostname() == siteURL.Hostname()) && strings.HasPrefix(url.Path, path.Join(subpath, "plugins")) { 929 httpClient = a.HTTPService.MakeClient(true) 930 } else { 931 httpClient = a.HTTPService.MakeClient(false) 932 } 933 934 resp, err := httpClient.Do(req) 935 if err != nil { 936 return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest) 937 } 938 defer consumeAndClose(resp) 939 940 if resp.StatusCode != http.StatusOK { 941 return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, fmt.Sprintf("status=%v", resp.StatusCode), http.StatusBadRequest) 942 } 943 944 var response model.PostActionIntegrationResponse 945 if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { 946 return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest) 947 } 948 949 retainedProps := []string{"override_username", "override_icon_url"} 950 951 if response.Update != nil { 952 response.Update.Id = postId 953 response.Update.AddProp("from_webhook", "true") 954 for _, prop := range retainedProps { 955 if value, ok := post.Props[prop]; ok { 956 response.Update.Props[prop] = value 957 } else { 958 delete(response.Update.Props, prop) 959 } 960 } 961 if _, err := a.UpdatePost(response.Update, false); err != nil { 962 return err 963 } 964 } 965 966 if response.EphemeralText != "" { 967 ephemeralPost := &model.Post{} 968 ephemeralPost.Message = model.ParseSlackLinksToMarkdown(response.EphemeralText) 969 ephemeralPost.ChannelId = post.ChannelId 970 ephemeralPost.RootId = post.RootId 971 if ephemeralPost.RootId == "" { 972 ephemeralPost.RootId = post.Id 973 } 974 ephemeralPost.UserId = post.UserId 975 ephemeralPost.AddProp("from_webhook", "true") 976 for _, prop := range retainedProps { 977 if value, ok := post.Props[prop]; ok { 978 ephemeralPost.Props[prop] = value 979 } else { 980 delete(ephemeralPost.Props, prop) 981 } 982 } 983 a.SendEphemeralPost(userId, ephemeralPost) 984 } 985 986 return nil 987 } 988 989 func (a *App) PostListWithProxyAddedToImageURLs(list *model.PostList) *model.PostList { 990 if f := a.ImageProxyAdder(); f != nil { 991 return list.WithRewrittenImageURLs(f) 992 } 993 return list 994 } 995 996 func (a *App) PostWithProxyAddedToImageURLs(post *model.Post) *model.Post { 997 if f := a.ImageProxyAdder(); f != nil { 998 return post.WithRewrittenImageURLs(f) 999 } 1000 return post 1001 } 1002 1003 func (a *App) PostWithProxyRemovedFromImageURLs(post *model.Post) *model.Post { 1004 if f := a.ImageProxyRemover(); f != nil { 1005 return post.WithRewrittenImageURLs(f) 1006 } 1007 return post 1008 } 1009 1010 func (a *App) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch { 1011 if f := a.ImageProxyRemover(); f != nil { 1012 return patch.WithRewrittenImageURLs(f) 1013 } 1014 return patch 1015 } 1016 1017 func (a *App) imageProxyConfig() (proxyType, proxyURL, options, siteURL string) { 1018 cfg := a.Config() 1019 1020 if cfg.ServiceSettings.ImageProxyURL == nil || cfg.ServiceSettings.ImageProxyType == nil || cfg.ServiceSettings.SiteURL == nil { 1021 return 1022 } 1023 1024 proxyURL = *cfg.ServiceSettings.ImageProxyURL 1025 proxyType = *cfg.ServiceSettings.ImageProxyType 1026 siteURL = *cfg.ServiceSettings.SiteURL 1027 1028 if proxyURL == "" || proxyType == "" { 1029 return "", "", "", "" 1030 } 1031 1032 if proxyURL[len(proxyURL)-1] != '/' { 1033 proxyURL += "/" 1034 } 1035 1036 if siteURL == "" || siteURL[len(siteURL)-1] != '/' { 1037 siteURL += "/" 1038 } 1039 1040 if cfg.ServiceSettings.ImageProxyOptions != nil { 1041 options = *cfg.ServiceSettings.ImageProxyOptions 1042 } 1043 1044 return 1045 } 1046 1047 func (a *App) ImageProxyAdder() func(string) string { 1048 proxyType, proxyURL, options, siteURL := a.imageProxyConfig() 1049 if proxyType == "" { 1050 return nil 1051 } 1052 1053 return func(url string) string { 1054 if url == "" || url[0] == '/' || strings.HasPrefix(url, siteURL) || strings.HasPrefix(url, proxyURL) { 1055 return url 1056 } 1057 1058 switch proxyType { 1059 case "atmos/camo": 1060 mac := hmac.New(sha1.New, []byte(options)) 1061 mac.Write([]byte(url)) 1062 digest := hex.EncodeToString(mac.Sum(nil)) 1063 return proxyURL + digest + "/" + hex.EncodeToString([]byte(url)) 1064 } 1065 1066 return url 1067 } 1068 } 1069 1070 func (a *App) ImageProxyRemover() (f func(string) string) { 1071 proxyType, proxyURL, _, _ := a.imageProxyConfig() 1072 if proxyType == "" { 1073 return nil 1074 } 1075 1076 return func(url string) string { 1077 switch proxyType { 1078 case "atmos/camo": 1079 if strings.HasPrefix(url, proxyURL) { 1080 if slash := strings.IndexByte(url[len(proxyURL):], '/'); slash >= 0 { 1081 if decoded, err := hex.DecodeString(url[len(proxyURL)+slash+1:]); err == nil { 1082 return string(decoded) 1083 } 1084 } 1085 } 1086 } 1087 1088 return url 1089 } 1090 } 1091 1092 func (a *App) MaxPostSize() int { 1093 maxPostSize := model.POST_MESSAGE_MAX_RUNES_V1 1094 if result := <-a.Srv.Store.Post().GetMaxPostSize(); result.Err != nil { 1095 mlog.Error(fmt.Sprint(result.Err)) 1096 } else { 1097 maxPostSize = result.Data.(int) 1098 } 1099 1100 return maxPostSize 1101 }