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