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