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  }