code.gitea.io/gitea@v1.21.7/routers/web/repo/wiki.go (about)

     1  // Copyright 2015 The Gogs Authors. All rights reserved.
     2  // Copyright 2018 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package repo
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"net/url"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	git_model "code.gitea.io/gitea/models/git"
    17  	repo_model "code.gitea.io/gitea/models/repo"
    18  	"code.gitea.io/gitea/models/unit"
    19  	"code.gitea.io/gitea/modules/base"
    20  	"code.gitea.io/gitea/modules/charset"
    21  	"code.gitea.io/gitea/modules/context"
    22  	"code.gitea.io/gitea/modules/git"
    23  	"code.gitea.io/gitea/modules/log"
    24  	"code.gitea.io/gitea/modules/markup"
    25  	"code.gitea.io/gitea/modules/markup/markdown"
    26  	"code.gitea.io/gitea/modules/setting"
    27  	"code.gitea.io/gitea/modules/timeutil"
    28  	"code.gitea.io/gitea/modules/util"
    29  	"code.gitea.io/gitea/modules/web"
    30  	"code.gitea.io/gitea/routers/common"
    31  	"code.gitea.io/gitea/services/forms"
    32  	notify_service "code.gitea.io/gitea/services/notify"
    33  	wiki_service "code.gitea.io/gitea/services/wiki"
    34  )
    35  
    36  const (
    37  	tplWikiStart    base.TplName = "repo/wiki/start"
    38  	tplWikiView     base.TplName = "repo/wiki/view"
    39  	tplWikiRevision base.TplName = "repo/wiki/revision"
    40  	tplWikiNew      base.TplName = "repo/wiki/new"
    41  	tplWikiPages    base.TplName = "repo/wiki/pages"
    42  )
    43  
    44  // MustEnableWiki check if wiki is enabled, if external then redirect
    45  func MustEnableWiki(ctx *context.Context) {
    46  	if !ctx.Repo.CanRead(unit.TypeWiki) &&
    47  		!ctx.Repo.CanRead(unit.TypeExternalWiki) {
    48  		if log.IsTrace() {
    49  			log.Trace("Permission Denied: User %-v cannot read %-v or %-v of repo %-v\n"+
    50  				"User in repo has Permissions: %-+v",
    51  				ctx.Doer,
    52  				unit.TypeWiki,
    53  				unit.TypeExternalWiki,
    54  				ctx.Repo.Repository,
    55  				ctx.Repo.Permission)
    56  		}
    57  		ctx.NotFound("MustEnableWiki", nil)
    58  		return
    59  	}
    60  
    61  	unit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalWiki)
    62  	if err == nil {
    63  		ctx.Redirect(unit.ExternalWikiConfig().ExternalWikiURL)
    64  		return
    65  	}
    66  }
    67  
    68  // PageMeta wiki page meta information
    69  type PageMeta struct {
    70  	Name         string
    71  	SubURL       string
    72  	GitEntryName string
    73  	UpdatedUnix  timeutil.TimeStamp
    74  }
    75  
    76  // findEntryForFile finds the tree entry for a target filepath.
    77  func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
    78  	entry, err := commit.GetTreeEntryByPath(target)
    79  	if err != nil && !git.IsErrNotExist(err) {
    80  		return nil, err
    81  	}
    82  	if entry != nil {
    83  		return entry, nil
    84  	}
    85  
    86  	// Then the unescaped, the shortest alternative
    87  	var unescapedTarget string
    88  	if unescapedTarget, err = url.QueryUnescape(target); err != nil {
    89  		return nil, err
    90  	}
    91  	return commit.GetTreeEntryByPath(unescapedTarget)
    92  }
    93  
    94  func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
    95  	wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
    96  	if err != nil {
    97  		ctx.ServerError("OpenRepository", err)
    98  		return nil, nil, err
    99  	}
   100  
   101  	commit, err := wikiRepo.GetBranchCommit(wiki_service.DefaultBranch)
   102  	if err != nil {
   103  		return wikiRepo, nil, err
   104  	}
   105  	return wikiRepo, commit, nil
   106  }
   107  
   108  // wikiContentsByEntry returns the contents of the wiki page referenced by the
   109  // given tree entry. Writes to ctx if an error occurs.
   110  func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
   111  	reader, err := entry.Blob().DataAsync()
   112  	if err != nil {
   113  		ctx.ServerError("Blob.Data", err)
   114  		return nil
   115  	}
   116  	defer reader.Close()
   117  	content, err := io.ReadAll(reader)
   118  	if err != nil {
   119  		ctx.ServerError("ReadAll", err)
   120  		return nil
   121  	}
   122  	return content
   123  }
   124  
   125  // wikiContentsByName returns the contents of a wiki page, along with a boolean
   126  // indicating whether the page exists. Writes to ctx if an error occurs.
   127  func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) {
   128  	gitFilename := wiki_service.WebPathToGitPath(wikiName)
   129  	entry, err := findEntryForFile(commit, gitFilename)
   130  	if err != nil && !git.IsErrNotExist(err) {
   131  		ctx.ServerError("findEntryForFile", err)
   132  		return nil, nil, "", false
   133  	} else if entry == nil {
   134  		return nil, nil, "", true
   135  	}
   136  	return wikiContentsByEntry(ctx, entry), entry, gitFilename, false
   137  }
   138  
   139  func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
   140  	wikiRepo, commit, err := findWikiRepoCommit(ctx)
   141  	if err != nil {
   142  		if wikiRepo != nil {
   143  			wikiRepo.Close()
   144  		}
   145  		if !git.IsErrNotExist(err) {
   146  			ctx.ServerError("GetBranchCommit", err)
   147  		}
   148  		return nil, nil
   149  	}
   150  
   151  	// Get page list.
   152  	entries, err := commit.ListEntries()
   153  	if err != nil {
   154  		if wikiRepo != nil {
   155  			wikiRepo.Close()
   156  		}
   157  		ctx.ServerError("ListEntries", err)
   158  		return nil, nil
   159  	}
   160  	pages := make([]PageMeta, 0, len(entries))
   161  	for _, entry := range entries {
   162  		if !entry.IsRegular() {
   163  			continue
   164  		}
   165  		wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
   166  		if err != nil {
   167  			if repo_model.IsErrWikiInvalidFileName(err) {
   168  				continue
   169  			}
   170  			if wikiRepo != nil {
   171  				wikiRepo.Close()
   172  			}
   173  			ctx.ServerError("WikiFilenameToName", err)
   174  			return nil, nil
   175  		} else if wikiName == "_Sidebar" || wikiName == "_Footer" {
   176  			continue
   177  		}
   178  		_, displayName := wiki_service.WebPathToUserTitle(wikiName)
   179  		pages = append(pages, PageMeta{
   180  			Name:         displayName,
   181  			SubURL:       wiki_service.WebPathToURLPath(wikiName),
   182  			GitEntryName: entry.Name(),
   183  		})
   184  	}
   185  	ctx.Data["Pages"] = pages
   186  
   187  	// get requested page name
   188  	pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   189  	if len(pageName) == 0 {
   190  		pageName = "Home"
   191  	}
   192  
   193  	_, displayName := wiki_service.WebPathToUserTitle(pageName)
   194  	ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
   195  	ctx.Data["old_title"] = displayName
   196  	ctx.Data["Title"] = displayName
   197  	ctx.Data["title"] = displayName
   198  
   199  	isSideBar := pageName == "_Sidebar"
   200  	isFooter := pageName == "_Footer"
   201  
   202  	// lookup filename in wiki - get filecontent, gitTree entry , real filename
   203  	data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
   204  	if noEntry {
   205  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
   206  	}
   207  	if entry == nil || ctx.Written() {
   208  		if wikiRepo != nil {
   209  			wikiRepo.Close()
   210  		}
   211  		return nil, nil
   212  	}
   213  
   214  	var sidebarContent []byte
   215  	if !isSideBar {
   216  		sidebarContent, _, _, _ = wikiContentsByName(ctx, commit, "_Sidebar")
   217  		if ctx.Written() {
   218  			if wikiRepo != nil {
   219  				wikiRepo.Close()
   220  			}
   221  			return nil, nil
   222  		}
   223  	} else {
   224  		sidebarContent = data
   225  	}
   226  
   227  	var footerContent []byte
   228  	if !isFooter {
   229  		footerContent, _, _, _ = wikiContentsByName(ctx, commit, "_Footer")
   230  		if ctx.Written() {
   231  			if wikiRepo != nil {
   232  				wikiRepo.Close()
   233  			}
   234  			return nil, nil
   235  		}
   236  	} else {
   237  		footerContent = data
   238  	}
   239  
   240  	rctx := &markup.RenderContext{
   241  		Ctx:   ctx,
   242  		Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
   243  		Links: markup.Links{
   244  			Base: ctx.Repo.RepoLink,
   245  		},
   246  		IsWiki: true,
   247  	}
   248  	buf := &strings.Builder{}
   249  
   250  	renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) {
   251  		markupRd, markupWr := io.Pipe()
   252  		defer markupWr.Close()
   253  		done := make(chan struct{})
   254  		go func() {
   255  			// We allow NBSP here this is rendered
   256  			escaped, _ = charset.EscapeControlReader(markupRd, buf, ctx.Locale, charset.RuneNBSP)
   257  			output = buf.String()
   258  			buf.Reset()
   259  			close(done)
   260  		}()
   261  
   262  		err = markdown.Render(rctx, bytes.NewReader(data), markupWr)
   263  		_ = markupWr.CloseWithError(err)
   264  		<-done
   265  		return escaped, output, err
   266  	}
   267  
   268  	ctx.Data["EscapeStatus"], ctx.Data["content"], err = renderFn(data)
   269  	if err != nil {
   270  		if wikiRepo != nil {
   271  			wikiRepo.Close()
   272  		}
   273  		ctx.ServerError("Render", err)
   274  		return nil, nil
   275  	}
   276  
   277  	if rctx.SidebarTocNode != nil {
   278  		sb := &strings.Builder{}
   279  		err = markdown.SpecializedMarkdown().Renderer().Render(sb, nil, rctx.SidebarTocNode)
   280  		if err != nil {
   281  			log.Error("Failed to render wiki sidebar TOC: %v", err)
   282  		} else {
   283  			ctx.Data["sidebarTocContent"] = sb.String()
   284  		}
   285  	}
   286  
   287  	if !isSideBar {
   288  		buf.Reset()
   289  		ctx.Data["sidebarEscapeStatus"], ctx.Data["sidebarContent"], err = renderFn(sidebarContent)
   290  		if err != nil {
   291  			if wikiRepo != nil {
   292  				wikiRepo.Close()
   293  			}
   294  			ctx.ServerError("Render", err)
   295  			return nil, nil
   296  		}
   297  		ctx.Data["sidebarPresent"] = sidebarContent != nil
   298  	} else {
   299  		ctx.Data["sidebarPresent"] = false
   300  	}
   301  
   302  	if !isFooter {
   303  		buf.Reset()
   304  		ctx.Data["footerEscapeStatus"], ctx.Data["footerContent"], err = renderFn(footerContent)
   305  		if err != nil {
   306  			if wikiRepo != nil {
   307  				wikiRepo.Close()
   308  			}
   309  			ctx.ServerError("Render", err)
   310  			return nil, nil
   311  		}
   312  		ctx.Data["footerPresent"] = footerContent != nil
   313  	} else {
   314  		ctx.Data["footerPresent"] = false
   315  	}
   316  
   317  	// get commit count - wiki revisions
   318  	commitsCount, _ := wikiRepo.FileCommitsCount(wiki_service.DefaultBranch, pageFilename)
   319  	ctx.Data["CommitCount"] = commitsCount
   320  
   321  	return wikiRepo, entry
   322  }
   323  
   324  func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
   325  	wikiRepo, commit, err := findWikiRepoCommit(ctx)
   326  	if err != nil {
   327  		if wikiRepo != nil {
   328  			wikiRepo.Close()
   329  		}
   330  		if !git.IsErrNotExist(err) {
   331  			ctx.ServerError("GetBranchCommit", err)
   332  		}
   333  		return nil, nil
   334  	}
   335  
   336  	// get requested pagename
   337  	pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   338  	if len(pageName) == 0 {
   339  		pageName = "Home"
   340  	}
   341  
   342  	_, displayName := wiki_service.WebPathToUserTitle(pageName)
   343  	ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
   344  	ctx.Data["old_title"] = displayName
   345  	ctx.Data["Title"] = displayName
   346  	ctx.Data["title"] = displayName
   347  
   348  	ctx.Data["Username"] = ctx.Repo.Owner.Name
   349  	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
   350  
   351  	// lookup filename in wiki - get filecontent, gitTree entry , real filename
   352  	data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
   353  	if noEntry {
   354  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
   355  	}
   356  	if entry == nil || ctx.Written() {
   357  		if wikiRepo != nil {
   358  			wikiRepo.Close()
   359  		}
   360  		return nil, nil
   361  	}
   362  
   363  	ctx.Data["content"] = string(data)
   364  	ctx.Data["sidebarPresent"] = false
   365  	ctx.Data["sidebarContent"] = ""
   366  	ctx.Data["footerPresent"] = false
   367  	ctx.Data["footerContent"] = ""
   368  
   369  	// get commit count - wiki revisions
   370  	commitsCount, _ := wikiRepo.FileCommitsCount(wiki_service.DefaultBranch, pageFilename)
   371  	ctx.Data["CommitCount"] = commitsCount
   372  
   373  	// get page
   374  	page := ctx.FormInt("page")
   375  	if page <= 1 {
   376  		page = 1
   377  	}
   378  
   379  	// get Commit Count
   380  	commitsHistory, err := wikiRepo.CommitsByFileAndRange(
   381  		git.CommitsByFileAndRangeOptions{
   382  			Revision: wiki_service.DefaultBranch,
   383  			File:     pageFilename,
   384  			Page:     page,
   385  		})
   386  	if err != nil {
   387  		if wikiRepo != nil {
   388  			wikiRepo.Close()
   389  		}
   390  		ctx.ServerError("CommitsByFileAndRange", err)
   391  		return nil, nil
   392  	}
   393  	ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commitsHistory, ctx.Repo.Repository)
   394  
   395  	pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5)
   396  	pager.SetDefaultParams(ctx)
   397  	ctx.Data["Page"] = pager
   398  
   399  	return wikiRepo, entry
   400  }
   401  
   402  func renderEditPage(ctx *context.Context) {
   403  	wikiRepo, commit, err := findWikiRepoCommit(ctx)
   404  	if err != nil {
   405  		if wikiRepo != nil {
   406  			wikiRepo.Close()
   407  		}
   408  		if !git.IsErrNotExist(err) {
   409  			ctx.ServerError("GetBranchCommit", err)
   410  		}
   411  		return
   412  	}
   413  	defer func() {
   414  		if wikiRepo != nil {
   415  			wikiRepo.Close()
   416  		}
   417  	}()
   418  
   419  	// get requested pagename
   420  	pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   421  	if len(pageName) == 0 {
   422  		pageName = "Home"
   423  	}
   424  
   425  	_, displayName := wiki_service.WebPathToUserTitle(pageName)
   426  	ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
   427  	ctx.Data["old_title"] = displayName
   428  	ctx.Data["Title"] = displayName
   429  	ctx.Data["title"] = displayName
   430  
   431  	// lookup filename in wiki - get filecontent, gitTree entry , real filename
   432  	data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName)
   433  	if noEntry {
   434  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
   435  	}
   436  	if entry == nil || ctx.Written() {
   437  		return
   438  	}
   439  
   440  	ctx.Data["content"] = string(data)
   441  	ctx.Data["sidebarPresent"] = false
   442  	ctx.Data["sidebarContent"] = ""
   443  	ctx.Data["footerPresent"] = false
   444  	ctx.Data["footerContent"] = ""
   445  }
   446  
   447  // WikiPost renders post of wiki page
   448  func WikiPost(ctx *context.Context) {
   449  	switch ctx.FormString("action") {
   450  	case "_new":
   451  		if !ctx.Repo.CanWrite(unit.TypeWiki) {
   452  			ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
   453  			return
   454  		}
   455  		NewWikiPost(ctx)
   456  		return
   457  	case "_delete":
   458  		if !ctx.Repo.CanWrite(unit.TypeWiki) {
   459  			ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
   460  			return
   461  		}
   462  		DeleteWikiPagePost(ctx)
   463  		return
   464  	}
   465  
   466  	if !ctx.Repo.CanWrite(unit.TypeWiki) {
   467  		ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
   468  		return
   469  	}
   470  	EditWikiPost(ctx)
   471  }
   472  
   473  // Wiki renders single wiki page
   474  func Wiki(ctx *context.Context) {
   475  	ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
   476  
   477  	switch ctx.FormString("action") {
   478  	case "_pages":
   479  		WikiPages(ctx)
   480  		return
   481  	case "_revision":
   482  		WikiRevision(ctx)
   483  		return
   484  	case "_edit":
   485  		if !ctx.Repo.CanWrite(unit.TypeWiki) {
   486  			ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
   487  			return
   488  		}
   489  		EditWiki(ctx)
   490  		return
   491  	case "_new":
   492  		if !ctx.Repo.CanWrite(unit.TypeWiki) {
   493  			ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
   494  			return
   495  		}
   496  		NewWiki(ctx)
   497  		return
   498  	}
   499  
   500  	if !ctx.Repo.Repository.HasWiki() {
   501  		ctx.Data["Title"] = ctx.Tr("repo.wiki")
   502  		ctx.HTML(http.StatusOK, tplWikiStart)
   503  		return
   504  	}
   505  
   506  	wikiRepo, entry := renderViewPage(ctx)
   507  	defer func() {
   508  		if wikiRepo != nil {
   509  			wikiRepo.Close()
   510  		}
   511  	}()
   512  	if ctx.Written() {
   513  		return
   514  	}
   515  	if entry == nil {
   516  		ctx.Data["Title"] = ctx.Tr("repo.wiki")
   517  		ctx.HTML(http.StatusOK, tplWikiStart)
   518  		return
   519  	}
   520  
   521  	wikiPath := entry.Name()
   522  	if markup.Type(wikiPath) != markdown.MarkupName {
   523  		ext := strings.ToUpper(filepath.Ext(wikiPath))
   524  		ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext)
   525  	}
   526  	// Get last change information.
   527  	lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
   528  	if err != nil {
   529  		ctx.ServerError("GetCommitByPath", err)
   530  		return
   531  	}
   532  	ctx.Data["Author"] = lastCommit.Author
   533  
   534  	ctx.HTML(http.StatusOK, tplWikiView)
   535  }
   536  
   537  // WikiRevision renders file revision list of wiki page
   538  func WikiRevision(ctx *context.Context) {
   539  	ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
   540  
   541  	if !ctx.Repo.Repository.HasWiki() {
   542  		ctx.Data["Title"] = ctx.Tr("repo.wiki")
   543  		ctx.HTML(http.StatusOK, tplWikiStart)
   544  		return
   545  	}
   546  
   547  	wikiRepo, entry := renderRevisionPage(ctx)
   548  	defer func() {
   549  		if wikiRepo != nil {
   550  			wikiRepo.Close()
   551  		}
   552  	}()
   553  
   554  	if ctx.Written() {
   555  		return
   556  	}
   557  	if entry == nil {
   558  		ctx.Data["Title"] = ctx.Tr("repo.wiki")
   559  		ctx.HTML(http.StatusOK, tplWikiStart)
   560  		return
   561  	}
   562  
   563  	// Get last change information.
   564  	wikiPath := entry.Name()
   565  	lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
   566  	if err != nil {
   567  		ctx.ServerError("GetCommitByPath", err)
   568  		return
   569  	}
   570  	ctx.Data["Author"] = lastCommit.Author
   571  
   572  	ctx.HTML(http.StatusOK, tplWikiRevision)
   573  }
   574  
   575  // WikiPages render wiki pages list page
   576  func WikiPages(ctx *context.Context) {
   577  	if !ctx.Repo.Repository.HasWiki() {
   578  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
   579  		return
   580  	}
   581  
   582  	ctx.Data["Title"] = ctx.Tr("repo.wiki.pages")
   583  	ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
   584  
   585  	wikiRepo, commit, err := findWikiRepoCommit(ctx)
   586  	if err != nil {
   587  		if wikiRepo != nil {
   588  			wikiRepo.Close()
   589  		}
   590  		return
   591  	}
   592  	defer func() {
   593  		if wikiRepo != nil {
   594  			wikiRepo.Close()
   595  		}
   596  	}()
   597  
   598  	entries, err := commit.ListEntries()
   599  	if err != nil {
   600  		ctx.ServerError("ListEntries", err)
   601  		return
   602  	}
   603  	pages := make([]PageMeta, 0, len(entries))
   604  	for _, entry := range entries {
   605  		if !entry.IsRegular() {
   606  			continue
   607  		}
   608  		c, err := wikiRepo.GetCommitByPath(entry.Name())
   609  		if err != nil {
   610  			ctx.ServerError("GetCommit", err)
   611  			return
   612  		}
   613  		wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
   614  		if err != nil {
   615  			if repo_model.IsErrWikiInvalidFileName(err) {
   616  				continue
   617  			}
   618  			ctx.ServerError("WikiFilenameToName", err)
   619  			return
   620  		}
   621  		_, displayName := wiki_service.WebPathToUserTitle(wikiName)
   622  		pages = append(pages, PageMeta{
   623  			Name:         displayName,
   624  			SubURL:       wiki_service.WebPathToURLPath(wikiName),
   625  			GitEntryName: entry.Name(),
   626  			UpdatedUnix:  timeutil.TimeStamp(c.Author.When.Unix()),
   627  		})
   628  	}
   629  	ctx.Data["Pages"] = pages
   630  
   631  	ctx.HTML(http.StatusOK, tplWikiPages)
   632  }
   633  
   634  // WikiRaw outputs raw blob requested by user (image for example)
   635  func WikiRaw(ctx *context.Context) {
   636  	wikiRepo, commit, err := findWikiRepoCommit(ctx)
   637  	defer func() {
   638  		if wikiRepo != nil {
   639  			wikiRepo.Close()
   640  		}
   641  	}()
   642  
   643  	if err != nil {
   644  		if git.IsErrNotExist(err) {
   645  			ctx.NotFound("findEntryForFile", nil)
   646  			return
   647  		}
   648  		ctx.ServerError("findEntryForfile", err)
   649  		return
   650  	}
   651  
   652  	providedWebPath := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   653  	providedGitPath := wiki_service.WebPathToGitPath(providedWebPath)
   654  	var entry *git.TreeEntry
   655  	if commit != nil {
   656  		// Try to find a file with that name
   657  		entry, err = findEntryForFile(commit, providedGitPath)
   658  		if err != nil && !git.IsErrNotExist(err) {
   659  			ctx.ServerError("findFile", err)
   660  			return
   661  		}
   662  
   663  		if entry == nil {
   664  			// Try to find a wiki page with that name
   665  			providedGitPath = strings.TrimSuffix(providedGitPath, ".md")
   666  			entry, err = findEntryForFile(commit, providedGitPath)
   667  			if err != nil && !git.IsErrNotExist(err) {
   668  				ctx.ServerError("findFile", err)
   669  				return
   670  			}
   671  		}
   672  	}
   673  
   674  	if entry != nil {
   675  		if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, entry.Blob(), nil); err != nil {
   676  			ctx.ServerError("ServeBlob", err)
   677  		}
   678  		return
   679  	}
   680  
   681  	ctx.NotFound("findEntryForFile", nil)
   682  }
   683  
   684  // NewWiki render wiki create page
   685  func NewWiki(ctx *context.Context) {
   686  	ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
   687  
   688  	if !ctx.Repo.Repository.HasWiki() {
   689  		ctx.Data["title"] = "Home"
   690  	}
   691  	if ctx.FormString("title") != "" {
   692  		ctx.Data["title"] = ctx.FormString("title")
   693  	}
   694  
   695  	ctx.HTML(http.StatusOK, tplWikiNew)
   696  }
   697  
   698  // NewWikiPost response for wiki create request
   699  func NewWikiPost(ctx *context.Context) {
   700  	form := web.GetForm(ctx).(*forms.NewWikiForm)
   701  	ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
   702  
   703  	if ctx.HasError() {
   704  		ctx.HTML(http.StatusOK, tplWikiNew)
   705  		return
   706  	}
   707  
   708  	if util.IsEmptyString(form.Title) {
   709  		ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplWikiNew, form)
   710  		return
   711  	}
   712  
   713  	wikiName := wiki_service.UserTitleToWebPath("", form.Title)
   714  
   715  	if len(form.Message) == 0 {
   716  		form.Message = ctx.Tr("repo.editor.add", form.Title)
   717  	}
   718  
   719  	if err := wiki_service.AddWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName, form.Content, form.Message); err != nil {
   720  		if repo_model.IsErrWikiReservedName(err) {
   721  			ctx.Data["Err_Title"] = true
   722  			ctx.RenderWithErr(ctx.Tr("repo.wiki.reserved_page", wikiName), tplWikiNew, &form)
   723  		} else if repo_model.IsErrWikiAlreadyExist(err) {
   724  			ctx.Data["Err_Title"] = true
   725  			ctx.RenderWithErr(ctx.Tr("repo.wiki.page_already_exists"), tplWikiNew, &form)
   726  		} else {
   727  			ctx.ServerError("AddWikiPage", err)
   728  		}
   729  		return
   730  	}
   731  
   732  	notify_service.NewWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName), form.Message)
   733  
   734  	ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(wikiName))
   735  }
   736  
   737  // EditWiki render wiki modify page
   738  func EditWiki(ctx *context.Context) {
   739  	ctx.Data["PageIsWikiEdit"] = true
   740  
   741  	if !ctx.Repo.Repository.HasWiki() {
   742  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
   743  		return
   744  	}
   745  
   746  	renderEditPage(ctx)
   747  	if ctx.Written() {
   748  		return
   749  	}
   750  
   751  	ctx.HTML(http.StatusOK, tplWikiNew)
   752  }
   753  
   754  // EditWikiPost response for wiki modify request
   755  func EditWikiPost(ctx *context.Context) {
   756  	form := web.GetForm(ctx).(*forms.NewWikiForm)
   757  	ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
   758  
   759  	if ctx.HasError() {
   760  		ctx.HTML(http.StatusOK, tplWikiNew)
   761  		return
   762  	}
   763  
   764  	oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   765  	newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
   766  
   767  	if len(form.Message) == 0 {
   768  		form.Message = ctx.Tr("repo.editor.update", form.Title)
   769  	}
   770  
   771  	if err := wiki_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, oldWikiName, newWikiName, form.Content, form.Message); err != nil {
   772  		ctx.ServerError("EditWikiPage", err)
   773  		return
   774  	}
   775  
   776  	notify_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(newWikiName), form.Message)
   777  
   778  	ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(newWikiName))
   779  }
   780  
   781  // DeleteWikiPagePost delete wiki page
   782  func DeleteWikiPagePost(ctx *context.Context) {
   783  	wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   784  	if len(wikiName) == 0 {
   785  		wikiName = "Home"
   786  	}
   787  
   788  	if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
   789  		ctx.ServerError("DeleteWikiPage", err)
   790  		return
   791  	}
   792  
   793  	notify_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName))
   794  
   795  	ctx.JSONRedirect(ctx.Repo.RepoLink + "/wiki/")
   796  }