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