github.com/coincircle/mattermost-server@v4.8.1-0.20180321182714-9d701c704416+incompatible/app/post.go (about)

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