github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+incompatible/app/post.go (about)

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