code.gitea.io/gitea@v1.22.3/modules/issue/template/unmarshal.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package template
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"path"
    10  	"strconv"
    11  
    12  	"code.gitea.io/gitea/modules/git"
    13  	"code.gitea.io/gitea/modules/markup/markdown"
    14  	"code.gitea.io/gitea/modules/setting"
    15  	api "code.gitea.io/gitea/modules/structs"
    16  	"code.gitea.io/gitea/modules/util"
    17  
    18  	"gopkg.in/yaml.v3"
    19  )
    20  
    21  // CouldBe indicates a file with the filename could be a template,
    22  // it is a low cost check before further processing.
    23  func CouldBe(filename string) bool {
    24  	it := &api.IssueTemplate{
    25  		FileName: filename,
    26  	}
    27  	return it.Type() != ""
    28  }
    29  
    30  // Unmarshal parses out a valid template from the content
    31  func Unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
    32  	it, err := unmarshal(filename, content)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	if err := Validate(it); err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	return it, nil
    42  }
    43  
    44  // UnmarshalFromEntry parses out a valid template from the blob in entry
    45  func UnmarshalFromEntry(entry *git.TreeEntry, dir string) (*api.IssueTemplate, error) {
    46  	return unmarshalFromEntry(entry, path.Join(dir, entry.Name())) // Filepaths in Git are ALWAYS '/' separated do not use filepath here
    47  }
    48  
    49  // UnmarshalFromCommit parses out a valid template from the commit
    50  func UnmarshalFromCommit(commit *git.Commit, filename string) (*api.IssueTemplate, error) {
    51  	entry, err := commit.GetTreeEntryByPath(filename)
    52  	if err != nil {
    53  		return nil, fmt.Errorf("get entry for %q: %w", filename, err)
    54  	}
    55  	return unmarshalFromEntry(entry, filename)
    56  }
    57  
    58  // UnmarshalFromRepo parses out a valid template from the head commit of the branch
    59  func UnmarshalFromRepo(repo *git.Repository, branch, filename string) (*api.IssueTemplate, error) {
    60  	commit, err := repo.GetBranchCommit(branch)
    61  	if err != nil {
    62  		return nil, fmt.Errorf("get commit on branch %q: %w", branch, err)
    63  	}
    64  
    65  	return UnmarshalFromCommit(commit, filename)
    66  }
    67  
    68  func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTemplate, error) {
    69  	if size := entry.Blob().Size(); size > setting.UI.MaxDisplayFileSize {
    70  		return nil, fmt.Errorf("too large: %v > MaxDisplayFileSize", size)
    71  	}
    72  
    73  	r, err := entry.Blob().DataAsync()
    74  	if err != nil {
    75  		return nil, fmt.Errorf("data async: %w", err)
    76  	}
    77  	defer r.Close()
    78  
    79  	content, err := io.ReadAll(r)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("read all: %w", err)
    82  	}
    83  
    84  	return Unmarshal(filename, content)
    85  }
    86  
    87  func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
    88  	it := &api.IssueTemplate{
    89  		FileName: filename,
    90  	}
    91  
    92  	// Compatible with treating description as about
    93  	compatibleTemplate := &struct {
    94  		About string `yaml:"description"`
    95  	}{}
    96  
    97  	if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown {
    98  		if templateBody, err := markdown.ExtractMetadata(string(content), it); err != nil {
    99  			// The only thing we know here is that we can't extract metadata from the content,
   100  			// it's hard to tell if metadata doesn't exist or metadata isn't valid.
   101  			// There's an example template:
   102  			//
   103  			//    ---
   104  			//    # Title
   105  			//    ---
   106  			//    Content
   107  			//
   108  			// It could be a valid markdown with two horizontal lines, or an invalid markdown with wrong metadata.
   109  
   110  			it.Content = string(content)
   111  			it.Name = path.Base(it.FileName) // paths in Git are always '/' separated - do not use filepath!
   112  			it.About, _ = util.SplitStringAtByteN(it.Content, 80)
   113  		} else {
   114  			it.Content = templateBody
   115  			if it.About == "" {
   116  				if _, err := markdown.ExtractMetadata(string(content), compatibleTemplate); err == nil && compatibleTemplate.About != "" {
   117  					it.About = compatibleTemplate.About
   118  				}
   119  			}
   120  		}
   121  	} else if typ == api.IssueTemplateTypeYaml {
   122  		if err := yaml.Unmarshal(content, it); err != nil {
   123  			return nil, fmt.Errorf("yaml unmarshal: %w", err)
   124  		}
   125  		if it.About == "" {
   126  			if err := yaml.Unmarshal(content, compatibleTemplate); err == nil && compatibleTemplate.About != "" {
   127  				it.About = compatibleTemplate.About
   128  			}
   129  		}
   130  		for i, v := range it.Fields {
   131  			// set default id value
   132  			if v.ID == "" {
   133  				v.ID = strconv.Itoa(i)
   134  			}
   135  			// set default visibility
   136  			if v.Visible == nil {
   137  				v.Visible = []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm}
   138  				// markdown is not submitted by default
   139  				if v.Type != api.IssueFormFieldTypeMarkdown {
   140  					v.Visible = append(v.Visible, api.IssueFormFieldVisibleContent)
   141  				}
   142  			}
   143  		}
   144  	}
   145  
   146  	return it, nil
   147  }