github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+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 "crypto/sha256" 10 "encoding/base64" 11 "encoding/hex" 12 "encoding/json" 13 "fmt" 14 "net/http" 15 "regexp" 16 "strings" 17 18 l4g "github.com/alecthomas/log4go" 19 "github.com/dyatlov/go-opengraph/opengraph" 20 "github.com/mattermost/mattermost-server/model" 21 "github.com/mattermost/mattermost-server/store" 22 "github.com/mattermost/mattermost-server/utils" 23 ) 24 25 var linkWithTextRegex = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) 26 27 func (a *App) CreatePostAsUser(post *model.Post) (*model.Post, *model.AppError) { 28 // Check that channel has not been deleted 29 var channel *model.Channel 30 if result := <-a.Srv.Store.Channel().Get(post.ChannelId, true); result.Err != nil { 31 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, result.Err.Error(), http.StatusBadRequest) 32 return nil, err 33 } else { 34 channel = result.Data.(*model.Channel) 35 } 36 37 if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { 38 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) 39 return nil, err 40 } 41 42 if channel.DeleteAt != 0 { 43 err := model.NewAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "", http.StatusBadRequest) 44 return nil, err 45 } 46 47 if rp, err := a.CreatePost(post, channel, true); err != nil { 48 if err.Id == "api.post.create_post.root_id.app_error" || 49 err.Id == "api.post.create_post.channel_root_id.app_error" || 50 err.Id == "api.post.create_post.parent_id.app_error" { 51 err.StatusCode = http.StatusBadRequest 52 } 53 54 if err.Id == "api.post.create_post.town_square_read_only" { 55 uchan := a.Srv.Store.User().Get(post.UserId) 56 var user *model.User 57 if result := <-uchan; result.Err != nil { 58 return nil, result.Err 59 } else { 60 user = result.Data.(*model.User) 61 } 62 63 T := utils.GetUserTranslations(user.Locale) 64 a.SendEphemeralPost( 65 post.UserId, 66 &model.Post{ 67 ChannelId: channel.Id, 68 ParentId: post.ParentId, 69 RootId: post.RootId, 70 UserId: post.UserId, 71 Message: T("api.post.create_post.town_square_read_only"), 72 CreateAt: model.GetMillis() + 1, 73 }, 74 ) 75 } 76 77 return nil, err 78 } else { 79 // Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app) 80 if _, ok := post.Props["from_webhook"]; !ok { 81 if result := <-a.Srv.Store.Channel().UpdateLastViewedAt([]string{post.ChannelId}, post.UserId); result.Err != nil { 82 l4g.Error(utils.T("api.post.create_post.last_viewed.error"), post.ChannelId, post.UserId, result.Err) 83 } 84 85 if *a.Config().ServiceSettings.EnableChannelViewedMessages { 86 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", post.UserId, nil) 87 message.Add("channel_id", post.ChannelId) 88 a.Go(func() { 89 a.Publish(message) 90 }) 91 } 92 } 93 94 return rp, nil 95 } 96 97 } 98 99 func (a *App) CreatePostMissingChannel(post *model.Post, triggerWebhooks bool) (*model.Post, *model.AppError) { 100 var channel *model.Channel 101 cchan := a.Srv.Store.Channel().Get(post.ChannelId, true) 102 if result := <-cchan; result.Err != nil { 103 return nil, result.Err 104 } else { 105 channel = result.Data.(*model.Channel) 106 } 107 108 return a.CreatePost(post, channel, triggerWebhooks) 109 } 110 111 func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhooks bool) (*model.Post, *model.AppError) { 112 post.SanitizeProps() 113 114 var pchan store.StoreChannel 115 if len(post.RootId) > 0 { 116 pchan = a.Srv.Store.Post().Get(post.RootId) 117 } 118 119 uchan := a.Srv.Store.User().Get(post.UserId) 120 var user *model.User 121 if result := <-uchan; result.Err != nil { 122 return nil, result.Err 123 } else { 124 user = result.Data.(*model.User) 125 } 126 127 if utils.IsLicensed() && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly && 128 !post.IsSystemMessage() && 129 channel.Name == model.DEFAULT_CHANNEL && 130 !a.CheckIfRolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { 131 return nil, model.NewAppError("createPost", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) 132 } 133 134 // Verify the parent/child relationships are correct 135 var parentPostList *model.PostList 136 if pchan != nil { 137 if presult := <-pchan; presult.Err != nil { 138 return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) 139 } else { 140 parentPostList = presult.Data.(*model.PostList) 141 if len(parentPostList.Posts) == 0 || !parentPostList.IsChannelId(post.ChannelId) { 142 return nil, model.NewAppError("createPost", "api.post.create_post.channel_root_id.app_error", nil, "", http.StatusInternalServerError) 143 } 144 145 if post.ParentId == "" { 146 post.ParentId = post.RootId 147 } 148 149 if post.RootId != post.ParentId { 150 parent := parentPostList.Posts[post.ParentId] 151 if parent == nil { 152 return nil, model.NewAppError("createPost", "api.post.create_post.parent_id.app_error", nil, "", http.StatusInternalServerError) 153 } 154 } 155 } 156 } 157 158 post.Hashtags, _ = model.ParseHashtags(post.Message) 159 160 if err := a.FillInPostProps(post, channel); err != nil { 161 return nil, err 162 } 163 164 var rpost *model.Post 165 if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { 166 return nil, result.Err 167 } else { 168 rpost = result.Data.(*model.Post) 169 } 170 171 esInterface := a.Elasticsearch 172 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 173 a.Go(func() { 174 esInterface.IndexPost(rpost, channel.TeamId) 175 }) 176 } 177 178 if a.Metrics != nil { 179 a.Metrics.IncrementPostCreate() 180 } 181 182 if len(post.FileIds) > 0 { 183 // There's a rare bug where the client sends up duplicate FileIds so protect against that 184 post.FileIds = utils.RemoveDuplicatesFromStringArray(post.FileIds) 185 186 for _, fileId := range post.FileIds { 187 if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil { 188 l4g.Error(utils.T("api.post.create_post.attach_files.error"), post.Id, post.FileIds, post.UserId, result.Err) 189 } 190 } 191 192 if a.Metrics != nil { 193 a.Metrics.IncrementPostFileAttachment(len(post.FileIds)) 194 } 195 } 196 197 if err := a.handlePostEvents(rpost, user, channel, triggerWebhooks, parentPostList); err != nil { 198 return nil, err 199 } 200 201 return rpost, nil 202 } 203 204 // FillInPostProps should be invoked before saving posts to fill in properties such as 205 // channel_mentions. 206 // 207 // If channel is nil, FillInPostProps will look up the channel corresponding to the post. 208 func (a *App) FillInPostProps(post *model.Post, channel *model.Channel) *model.AppError { 209 channelMentions := post.ChannelMentions() 210 channelMentionsProp := make(map[string]interface{}) 211 212 if len(channelMentions) > 0 { 213 if channel == nil { 214 result := <-a.Srv.Store.Channel().GetForPost(post.Id) 215 if result.Err != nil { 216 return model.NewAppError("FillInPostProps", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, result.Err.Error(), http.StatusBadRequest) 217 } 218 channel = result.Data.(*model.Channel) 219 } 220 221 mentionedChannels, err := a.GetChannelsByNames(channelMentions, channel.TeamId) 222 if err != nil { 223 return err 224 } 225 226 for _, mentioned := range mentionedChannels { 227 if mentioned.Type == model.CHANNEL_OPEN { 228 channelMentionsProp[mentioned.Name] = map[string]interface{}{ 229 "display_name": mentioned.DisplayName, 230 } 231 } 232 } 233 } 234 235 if len(channelMentionsProp) > 0 { 236 post.AddProp("channel_mentions", channelMentionsProp) 237 } else if post.Props != nil { 238 delete(post.Props, "channel_mentions") 239 } 240 241 return nil 242 } 243 244 func (a *App) handlePostEvents(post *model.Post, user *model.User, channel *model.Channel, triggerWebhooks bool, parentPostList *model.PostList) *model.AppError { 245 var tchan store.StoreChannel 246 if len(channel.TeamId) > 0 { 247 tchan = a.Srv.Store.Team().Get(channel.TeamId) 248 } 249 250 var team *model.Team 251 if tchan != nil { 252 if result := <-tchan; result.Err != nil { 253 return result.Err 254 } else { 255 team = result.Data.(*model.Team) 256 } 257 } else { 258 // Blank team for DMs 259 team = &model.Team{} 260 } 261 262 a.InvalidateCacheForChannel(channel) 263 a.InvalidateCacheForChannelPosts(channel.Id) 264 265 if _, err := a.SendNotifications(post, team, channel, user, parentPostList); err != nil { 266 return err 267 } 268 269 if triggerWebhooks { 270 a.Go(func() { 271 if err := a.handleWebhookEvents(post, team, channel, user); err != nil { 272 l4g.Error(err.Error()) 273 } 274 }) 275 } 276 277 return nil 278 } 279 280 // This method only parses and processes the attachments, 281 // all else should be set in the post which is passed 282 func parseSlackAttachment(post *model.Post, attachments []*model.SlackAttachment) { 283 post.Type = model.POST_SLACK_ATTACHMENT 284 285 for _, attachment := range attachments { 286 attachment.Text = parseSlackLinksToMarkdown(attachment.Text) 287 attachment.Pretext = parseSlackLinksToMarkdown(attachment.Pretext) 288 289 for _, field := range attachment.Fields { 290 if value, ok := field.Value.(string); ok { 291 field.Value = parseSlackLinksToMarkdown(value) 292 } 293 } 294 } 295 post.AddProp("attachments", attachments) 296 } 297 298 func parseSlackLinksToMarkdown(text string) string { 299 return linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})") 300 } 301 302 func (a *App) SendEphemeralPost(userId string, post *model.Post) *model.Post { 303 post.Type = model.POST_EPHEMERAL 304 305 // fill in fields which haven't been specified which have sensible defaults 306 if post.Id == "" { 307 post.Id = model.NewId() 308 } 309 if post.CreateAt == 0 { 310 post.CreateAt = model.GetMillis() 311 } 312 if post.Props == nil { 313 post.Props = model.StringInterface{} 314 } 315 316 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userId, nil) 317 message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) 318 319 a.Go(func() { 320 a.Publish(message) 321 }) 322 323 return post 324 } 325 326 func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { 327 post.SanitizeProps() 328 329 var oldPost *model.Post 330 if result := <-a.Srv.Store.Post().Get(post.Id); result.Err != nil { 331 return nil, result.Err 332 } else { 333 oldPost = result.Data.(*model.PostList).Posts[post.Id] 334 335 if utils.IsLicensed() { 336 if *a.Config().ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER && post.Message != oldPost.Message { 337 err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_denied.app_error", nil, "", http.StatusForbidden) 338 return nil, err 339 } 340 } 341 342 if oldPost == nil { 343 err := model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest) 344 return nil, err 345 } 346 347 if oldPost.DeleteAt != 0 { 348 err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}, "", http.StatusBadRequest) 349 return nil, err 350 } 351 352 if oldPost.IsSystemMessage() { 353 err := model.NewAppError("UpdatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id, http.StatusBadRequest) 354 return nil, err 355 } 356 357 if utils.IsLicensed() { 358 if *a.Config().ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message { 359 err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *a.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest) 360 return nil, err 361 } 362 } 363 } 364 365 newPost := &model.Post{} 366 *newPost = *oldPost 367 368 if newPost.Message != post.Message { 369 newPost.Message = post.Message 370 newPost.EditAt = model.GetMillis() 371 newPost.Hashtags, _ = model.ParseHashtags(post.Message) 372 } 373 374 if !safeUpdate { 375 newPost.IsPinned = post.IsPinned 376 newPost.HasReactions = post.HasReactions 377 newPost.FileIds = post.FileIds 378 newPost.Props = post.Props 379 } 380 381 if err := a.FillInPostProps(post, nil); err != nil { 382 return nil, err 383 } 384 385 if result := <-a.Srv.Store.Post().Update(newPost, oldPost); result.Err != nil { 386 return nil, result.Err 387 } else { 388 rpost := result.Data.(*model.Post) 389 390 esInterface := a.Elasticsearch 391 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 392 a.Go(func() { 393 if rchannel := <-a.Srv.Store.Channel().GetForPost(rpost.Id); rchannel.Err != nil { 394 l4g.Error("Couldn't get channel %v for post %v for Elasticsearch indexing.", rpost.ChannelId, rpost.Id) 395 } else { 396 esInterface.IndexPost(rpost, rchannel.Data.(*model.Channel).TeamId) 397 } 398 }) 399 } 400 401 a.sendUpdatedPostEvent(rpost) 402 403 a.InvalidateCacheForChannelPosts(rpost.ChannelId) 404 405 return rpost, nil 406 } 407 } 408 409 func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *model.AppError) { 410 post, err := a.GetSinglePost(postId) 411 if err != nil { 412 return nil, err 413 } 414 415 post.Patch(patch) 416 417 updatedPost, err := a.UpdatePost(post, false) 418 if err != nil { 419 return nil, err 420 } 421 422 return updatedPost, nil 423 } 424 425 func (a *App) sendUpdatedPostEvent(post *model.Post) { 426 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, "", nil) 427 message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) 428 429 a.Go(func() { 430 a.Publish(message) 431 }) 432 } 433 434 func (a *App) GetPostsPage(channelId string, page int, perPage int) (*model.PostList, *model.AppError) { 435 if result := <-a.Srv.Store.Post().GetPosts(channelId, page*perPage, perPage, true); result.Err != nil { 436 return nil, result.Err 437 } else { 438 return result.Data.(*model.PostList), nil 439 } 440 } 441 442 func (a *App) GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 443 if result := <-a.Srv.Store.Post().GetPosts(channelId, offset, limit, true); result.Err != nil { 444 return nil, result.Err 445 } else { 446 return result.Data.(*model.PostList), nil 447 } 448 } 449 450 func (a *App) GetPostsEtag(channelId string) string { 451 return (<-a.Srv.Store.Post().GetEtag(channelId, true)).Data.(string) 452 } 453 454 func (a *App) GetPostsSince(channelId string, time int64) (*model.PostList, *model.AppError) { 455 if result := <-a.Srv.Store.Post().GetPostsSince(channelId, time, true); result.Err != nil { 456 return nil, result.Err 457 } else { 458 return result.Data.(*model.PostList), nil 459 } 460 } 461 462 func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) { 463 if result := <-a.Srv.Store.Post().GetSingle(postId); result.Err != nil { 464 return nil, result.Err 465 } else { 466 return result.Data.(*model.Post), nil 467 } 468 } 469 470 func (a *App) GetPostThread(postId string) (*model.PostList, *model.AppError) { 471 if result := <-a.Srv.Store.Post().Get(postId); result.Err != nil { 472 return nil, result.Err 473 } else { 474 return result.Data.(*model.PostList), nil 475 } 476 } 477 478 func (a *App) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) { 479 if result := <-a.Srv.Store.Post().GetFlaggedPosts(userId, offset, limit); result.Err != nil { 480 return nil, result.Err 481 } else { 482 return result.Data.(*model.PostList), nil 483 } 484 } 485 486 func (a *App) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) (*model.PostList, *model.AppError) { 487 if result := <-a.Srv.Store.Post().GetFlaggedPostsForTeam(userId, teamId, offset, limit); result.Err != nil { 488 return nil, result.Err 489 } else { 490 return result.Data.(*model.PostList), nil 491 } 492 } 493 494 func (a *App) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 495 if result := <-a.Srv.Store.Post().GetFlaggedPostsForChannel(userId, channelId, offset, limit); result.Err != nil { 496 return nil, result.Err 497 } else { 498 return result.Data.(*model.PostList), nil 499 } 500 } 501 502 func (a *App) GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) { 503 if result := <-a.Srv.Store.Post().Get(postId); result.Err != nil { 504 return nil, result.Err 505 } else { 506 list := result.Data.(*model.PostList) 507 508 if len(list.Order) != 1 { 509 return nil, model.NewAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "", http.StatusNotFound) 510 } 511 post := list.Posts[list.Order[0]] 512 513 var channel *model.Channel 514 var err *model.AppError 515 if channel, err = a.GetChannel(post.ChannelId); err != nil { 516 return nil, err 517 } 518 519 if err = a.JoinChannel(channel, userId); err != nil { 520 return nil, err 521 } 522 523 return list, nil 524 } 525 } 526 527 func (a *App) GetPostsBeforePost(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { 528 if result := <-a.Srv.Store.Post().GetPostsBefore(channelId, postId, perPage, page*perPage); result.Err != nil { 529 return nil, result.Err 530 } else { 531 return result.Data.(*model.PostList), nil 532 } 533 } 534 535 func (a *App) GetPostsAfterPost(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { 536 if result := <-a.Srv.Store.Post().GetPostsAfter(channelId, postId, perPage, page*perPage); result.Err != nil { 537 return nil, result.Err 538 } else { 539 return result.Data.(*model.PostList), nil 540 } 541 } 542 543 func (a *App) GetPostsAroundPost(postId, channelId string, offset, limit int, before bool) (*model.PostList, *model.AppError) { 544 var pchan store.StoreChannel 545 if before { 546 pchan = a.Srv.Store.Post().GetPostsBefore(channelId, postId, limit, offset) 547 } else { 548 pchan = a.Srv.Store.Post().GetPostsAfter(channelId, postId, limit, offset) 549 } 550 551 if result := <-pchan; result.Err != nil { 552 return nil, result.Err 553 } else { 554 return result.Data.(*model.PostList), nil 555 } 556 } 557 558 func (a *App) DeletePost(postId string) (*model.Post, *model.AppError) { 559 if result := <-a.Srv.Store.Post().GetSingle(postId); result.Err != nil { 560 result.Err.StatusCode = http.StatusBadRequest 561 return nil, result.Err 562 } else { 563 post := result.Data.(*model.Post) 564 565 if result := <-a.Srv.Store.Post().Delete(postId, model.GetMillis()); result.Err != nil { 566 return nil, result.Err 567 } 568 569 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil) 570 message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) 571 572 a.Go(func() { 573 a.Publish(message) 574 }) 575 a.Go(func() { 576 a.DeletePostFiles(post) 577 }) 578 a.Go(func() { 579 a.DeleteFlaggedPosts(post.Id) 580 }) 581 582 esInterface := a.Elasticsearch 583 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 584 a.Go(func() { 585 esInterface.DeletePost(post) 586 }) 587 } 588 589 a.InvalidateCacheForChannelPosts(post.ChannelId) 590 591 return post, nil 592 } 593 } 594 595 func (a *App) DeleteFlaggedPosts(postId string) { 596 if result := <-a.Srv.Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); result.Err != nil { 597 l4g.Warn(utils.T("api.post.delete_flagged_post.app_error.warn"), result.Err) 598 return 599 } 600 } 601 602 func (a *App) DeletePostFiles(post *model.Post) { 603 if len(post.FileIds) != 0 { 604 return 605 } 606 607 if result := <-a.Srv.Store.FileInfo().DeleteForPost(post.Id); result.Err != nil { 608 l4g.Warn(utils.T("api.post.delete_post_files.app_error.warn"), post.Id, result.Err) 609 } 610 } 611 612 func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOrSearch bool) (*model.PostList, *model.AppError) { 613 paramsList := model.ParseSearchParams(terms) 614 615 esInterface := a.Elasticsearch 616 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableSearching && utils.IsLicensed() && *utils.License().Features.Elasticsearch { 617 finalParamsList := []*model.SearchParams{} 618 619 for _, params := range paramsList { 620 params.OrTerms = isOrSearch 621 // Don't allow users to search for "*" 622 if params.Terms != "*" { 623 // Convert channel names to channel IDs 624 for idx, channelName := range params.InChannels { 625 if channel, err := a.GetChannelByName(channelName, teamId); err != nil { 626 l4g.Error(err) 627 } else { 628 params.InChannels[idx] = channel.Id 629 } 630 } 631 632 // Convert usernames to user IDs 633 for idx, username := range params.FromUsers { 634 if user, err := a.GetUserByUsername(username); err != nil { 635 l4g.Error(err) 636 } else { 637 params.FromUsers[idx] = user.Id 638 } 639 } 640 641 finalParamsList = append(finalParamsList, params) 642 } 643 } 644 645 // If the processed search params are empty, return empty search results. 646 if len(finalParamsList) == 0 { 647 return model.NewPostList(), nil 648 } 649 650 // We only allow the user to search in channels they are a member of. 651 userChannels, err := a.GetChannelsForUser(teamId, userId) 652 if err != nil { 653 l4g.Error(err) 654 return nil, err 655 } 656 657 postIds, err := a.Elasticsearch.SearchPosts(userChannels, finalParamsList) 658 if err != nil { 659 return nil, err 660 } 661 662 // Get the posts 663 postList := model.NewPostList() 664 if len(postIds) > 0 { 665 if presult := <-a.Srv.Store.Post().GetPostsByIds(postIds); presult.Err != nil { 666 return nil, presult.Err 667 } else { 668 for _, p := range presult.Data.([]*model.Post) { 669 postList.AddPost(p) 670 postList.AddOrder(p.Id) 671 } 672 } 673 } 674 675 return postList, nil 676 } else { 677 if !*a.Config().ServiceSettings.EnablePostSearch { 678 return nil, model.NewAppError("SearchPostsInTeam", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented) 679 } 680 681 channels := []store.StoreChannel{} 682 683 for _, params := range paramsList { 684 params.OrTerms = isOrSearch 685 // don't allow users to search for everything 686 if params.Terms != "*" { 687 channels = append(channels, a.Srv.Store.Post().Search(teamId, userId, params)) 688 } 689 } 690 691 posts := model.NewPostList() 692 for _, channel := range channels { 693 if result := <-channel; result.Err != nil { 694 return nil, result.Err 695 } else { 696 data := result.Data.(*model.PostList) 697 posts.Extend(data) 698 } 699 } 700 701 posts.SortByCreateAt() 702 703 return posts, nil 704 } 705 } 706 707 func (a *App) GetFileInfosForPost(postId string, readFromMaster bool) ([]*model.FileInfo, *model.AppError) { 708 pchan := a.Srv.Store.Post().GetSingle(postId) 709 fchan := a.Srv.Store.FileInfo().GetForPost(postId, readFromMaster, true) 710 711 var infos []*model.FileInfo 712 if result := <-fchan; result.Err != nil { 713 return nil, result.Err 714 } else { 715 infos = result.Data.([]*model.FileInfo) 716 } 717 718 if len(infos) == 0 { 719 // No FileInfos were returned so check if they need to be created for this post 720 var post *model.Post 721 if result := <-pchan; result.Err != nil { 722 return nil, result.Err 723 } else { 724 post = result.Data.(*model.Post) 725 } 726 727 if len(post.Filenames) > 0 { 728 a.Srv.Store.FileInfo().InvalidateFileInfosForPostCache(postId) 729 // The post has Filenames that need to be replaced with FileInfos 730 infos = a.MigrateFilenamesToFileInfos(post) 731 } 732 } 733 734 return infos, nil 735 } 736 737 func (a *App) GetOpenGraphMetadata(url string) *opengraph.OpenGraph { 738 og := opengraph.NewOpenGraph() 739 740 res, err := a.HTTPClient(false).Get(url) 741 if err != nil { 742 l4g.Error("GetOpenGraphMetadata request failed for url=%v with err=%v", url, err.Error()) 743 return og 744 } 745 defer consumeAndClose(res) 746 747 if err := og.ProcessHTML(res.Body); err != nil { 748 l4g.Error("GetOpenGraphMetadata processing failed for url=%v with err=%v", url, err.Error()) 749 } 750 751 return og 752 } 753 754 func (a *App) DoPostAction(postId string, actionId string, userId string) *model.AppError { 755 pchan := a.Srv.Store.Post().GetSingle(postId) 756 757 var post *model.Post 758 if result := <-pchan; result.Err != nil { 759 return result.Err 760 } else { 761 post = result.Data.(*model.Post) 762 } 763 764 action := post.GetAction(actionId) 765 if action == nil || action.Integration == nil { 766 return model.NewAppError("DoPostAction", "api.post.do_action.action_id.app_error", nil, fmt.Sprintf("action=%v", action), http.StatusNotFound) 767 } 768 769 request := &model.PostActionIntegrationRequest{ 770 UserId: userId, 771 Context: action.Integration.Context, 772 } 773 774 req, _ := http.NewRequest("POST", action.Integration.URL, strings.NewReader(request.ToJson())) 775 req.Header.Set("Content-Type", "application/json") 776 req.Header.Set("Accept", "application/json") 777 resp, err := a.HTTPClient(false).Do(req) 778 if err != nil { 779 return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest) 780 } 781 defer consumeAndClose(resp) 782 783 if resp.StatusCode != http.StatusOK { 784 return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, fmt.Sprintf("status=%v", resp.StatusCode), http.StatusBadRequest) 785 } 786 787 var response model.PostActionIntegrationResponse 788 if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { 789 return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest) 790 } 791 792 retainedProps := []string{"override_username", "override_icon_url"} 793 794 if response.Update != nil { 795 response.Update.Id = postId 796 response.Update.AddProp("from_webhook", "true") 797 for _, prop := range retainedProps { 798 if value, ok := post.Props[prop]; ok { 799 response.Update.Props[prop] = value 800 } else { 801 delete(response.Update.Props, prop) 802 } 803 } 804 if _, err := a.UpdatePost(response.Update, false); err != nil { 805 return err 806 } 807 } 808 809 if response.EphemeralText != "" { 810 ephemeralPost := &model.Post{} 811 ephemeralPost.Message = parseSlackLinksToMarkdown(response.EphemeralText) 812 ephemeralPost.ChannelId = post.ChannelId 813 ephemeralPost.RootId = post.RootId 814 if ephemeralPost.RootId == "" { 815 ephemeralPost.RootId = post.Id 816 } 817 ephemeralPost.UserId = post.UserId 818 ephemeralPost.AddProp("from_webhook", "true") 819 for _, prop := range retainedProps { 820 if value, ok := post.Props[prop]; ok { 821 ephemeralPost.Props[prop] = value 822 } else { 823 delete(ephemeralPost.Props, prop) 824 } 825 } 826 a.SendEphemeralPost(userId, ephemeralPost) 827 } 828 829 return nil 830 } 831 832 func (a *App) PostListWithProxyAddedToImageURLs(list *model.PostList) *model.PostList { 833 if f := a.ImageProxyAdder(); f != nil { 834 return list.WithRewrittenImageURLs(f) 835 } 836 return list 837 } 838 839 func (a *App) PostWithProxyAddedToImageURLs(post *model.Post) *model.Post { 840 if f := a.ImageProxyAdder(); f != nil { 841 return post.WithRewrittenImageURLs(f) 842 } 843 return post 844 } 845 846 func (a *App) PostWithProxyRemovedFromImageURLs(post *model.Post) *model.Post { 847 if f := a.ImageProxyRemover(); f != nil { 848 return post.WithRewrittenImageURLs(f) 849 } 850 return post 851 } 852 853 func (a *App) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch { 854 if f := a.ImageProxyRemover(); f != nil { 855 return patch.WithRewrittenImageURLs(f) 856 } 857 return patch 858 } 859 860 func (a *App) imageProxyConfig() (proxyType, proxyURL, options, siteURL string) { 861 cfg := a.Config() 862 863 if cfg.ServiceSettings.ImageProxyURL == nil || cfg.ServiceSettings.ImageProxyType == nil || cfg.ServiceSettings.SiteURL == nil { 864 return 865 } 866 867 proxyURL = *cfg.ServiceSettings.ImageProxyURL 868 proxyType = *cfg.ServiceSettings.ImageProxyType 869 siteURL = *cfg.ServiceSettings.SiteURL 870 871 if proxyURL == "" || proxyType == "" { 872 return "", "", "", "" 873 } 874 875 if proxyURL[len(proxyURL)-1] != '/' { 876 proxyURL += "/" 877 } 878 879 if cfg.ServiceSettings.ImageProxyOptions != nil { 880 options = *cfg.ServiceSettings.ImageProxyOptions 881 } 882 883 return 884 } 885 886 func (a *App) ImageProxyAdder() func(string) string { 887 proxyType, proxyURL, options, siteURL := a.imageProxyConfig() 888 if proxyType == "" { 889 return nil 890 } 891 892 return func(url string) string { 893 if strings.HasPrefix(url, proxyURL) { 894 return url 895 } 896 897 if url[0] == '/' { 898 url = siteURL + url 899 } 900 901 switch proxyType { 902 case "atmos/camo": 903 mac := hmac.New(sha1.New, []byte(options)) 904 mac.Write([]byte(url)) 905 digest := hex.EncodeToString(mac.Sum(nil)) 906 return proxyURL + digest + "/" + hex.EncodeToString([]byte(url)) 907 case "willnorris/imageproxy": 908 options := strings.Split(options, "|") 909 if len(options) > 1 { 910 mac := hmac.New(sha256.New, []byte(options[1])) 911 mac.Write([]byte(url)) 912 digest := base64.URLEncoding.EncodeToString(mac.Sum(nil)) 913 if options[0] == "" { 914 return proxyURL + "s" + digest + "/" + url 915 } 916 return proxyURL + options[0] + ",s" + digest + "/" + url 917 } 918 return proxyURL + options[0] + "/" + url 919 } 920 921 return url 922 } 923 } 924 925 func (a *App) ImageProxyRemover() (f func(string) string) { 926 proxyType, proxyURL, _, _ := a.imageProxyConfig() 927 if proxyType == "" { 928 return nil 929 } 930 931 return func(url string) string { 932 switch proxyType { 933 case "atmos/camo": 934 if strings.HasPrefix(url, proxyURL) { 935 if slash := strings.IndexByte(url[len(proxyURL):], '/'); slash >= 0 { 936 if decoded, err := hex.DecodeString(url[len(proxyURL)+slash+1:]); err == nil { 937 return string(decoded) 938 } 939 } 940 } 941 case "willnorris/imageproxy": 942 if strings.HasPrefix(url, proxyURL) { 943 if slash := strings.IndexByte(url[len(proxyURL):], '/'); slash >= 0 { 944 return url[len(proxyURL)+slash+1:] 945 } 946 } 947 } 948 949 return url 950 } 951 }