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