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