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