// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Repository contents API methods.
// GitHub API docs: https://docs.github.com/rest/repos/contents/

package github

import (
	
	
	
	
	
	
	
	
	
	
)

var ErrPathForbidden = errors.New("path must not contain '..' due to auth vulnerability issue")

// RepositoryContent represents a file or directory in a github repository.
type RepositoryContent struct {
	Type *string `json:"type,omitempty"`
	// Target is only set if the type is "symlink" and the target is not a normal file.
	// If Target is set, Path will be the symlink path.
	Target   *string `json:"target,omitempty"`
	Encoding *string `json:"encoding,omitempty"`
	Size     *int    `json:"size,omitempty"`
	Name     *string `json:"name,omitempty"`
	Path     *string `json:"path,omitempty"`
	// Content contains the actual file content, which may be encoded.
	// Callers should call GetContent which will decode the content if
	// necessary.
	Content         *string `json:"content,omitempty"`
	SHA             *string `json:"sha,omitempty"`
	URL             *string `json:"url,omitempty"`
	GitURL          *string `json:"git_url,omitempty"`
	HTMLURL         *string `json:"html_url,omitempty"`
	DownloadURL     *string `json:"download_url,omitempty"`
	SubmoduleGitURL *string `json:"submodule_git_url,omitempty"`
}

// RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile.
type RepositoryContentResponse struct {
	Content *RepositoryContent `json:"content,omitempty"`
	Commit  `json:"commit,omitempty"`
}

// RepositoryContentFileOptions specifies optional parameters for CreateFile, UpdateFile, and DeleteFile.
type RepositoryContentFileOptions struct {
	Message   *string       `json:"message,omitempty"`
	Content   []byte        `json:"content"` // unencoded
	SHA       *string       `json:"sha,omitempty"`
	Branch    *string       `json:"branch,omitempty"`
	Author    *CommitAuthor `json:"author,omitempty"`
	Committer *CommitAuthor `json:"committer,omitempty"`
}

// RepositoryContentGetOptions represents an optional ref parameter, which can be a SHA,
// branch, or tag
type RepositoryContentGetOptions struct {
	Ref string `url:"ref,omitempty"`
}

// String converts RepositoryContent to a string. It's primarily for testing.
func ( RepositoryContent) () string {
	return Stringify()
}

// GetContent returns the content of r, decoding it if necessary.
func ( *RepositoryContent) () (string, error) {
	var  string
	if .Encoding != nil {
		 = *.Encoding
	}

	switch  {
	case "base64":
		if .Content == nil {
			return "", errors.New("malformed response: base64 encoding of null content")
		}
		,  := base64.StdEncoding.DecodeString(*.Content)
		return string(), 
	case "":
		if .Content == nil {
			return "", nil
		}
		return *.Content, nil
	case "none":
		return "", errors.New("unsupported content encoding: none, this may occur when file size > 1 MB, if that is the case consider using DownloadContents")
	default:
		return "", fmt.Errorf("unsupported content encoding: %v", )
	}
}

// GetReadme gets the Readme file for the repository.
//
// GitHub API docs: https://docs.github.com/rest/repos/contents#get-a-repository-readme
//
//meta:operation GET /repos/{owner}/{repo}/readme
func ( *RepositoriesService) ( context.Context, ,  string,  *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) {
	 := fmt.Sprintf("repos/%v/%v/readme", , )
	,  := addOptions(, )
	if  != nil {
		return nil, nil, 
	}

	,  := .client.NewRequest("GET", , nil)
	if  != nil {
		return nil, nil, 
	}

	 := new(RepositoryContent)
	,  := .client.Do(, , )
	if  != nil {
		return nil, , 
	}

	return , , nil
}

// DownloadContents returns an io.ReadCloser that reads the contents of the
// specified file. This function will work with files of any size, as opposed
// to GetContents which is limited to 1 Mb files. It is the caller's
// responsibility to close the ReadCloser.
//
// It is possible for the download to result in a failed response when the
// returned error is nil. Callers should check the returned Response status
// code to verify the content is from a successful response.
//
// GitHub API docs: https://docs.github.com/rest/repos/contents#get-repository-content
//
//meta:operation GET /repos/{owner}/{repo}/contents/{path}
func ( *RepositoriesService) ( context.Context, , ,  string,  *RepositoryContentGetOptions) (io.ReadCloser, *Response, error) {
	 := path.Dir()
	 := path.Base()
	, , ,  := .GetContents(, , , , )
	if  != nil {
		return nil, , 
	}

	for ,  := range  {
		if *.Name ==  {
			if .DownloadURL == nil || *.DownloadURL == "" {
				return nil, , fmt.Errorf("no download link found for %s", )
			}

			,  := http.NewRequestWithContext(, http.MethodGet, *.DownloadURL, nil)
			if  != nil {
				return nil, , 
			}
			,  := .client.client.Do()
			if  != nil {
				return nil, &Response{Response: }, 
			}

			return .Body, &Response{Response: }, nil
		}
	}

	return nil, , fmt.Errorf("no file named %s found in %s", , )
}

// DownloadContentsWithMeta is identical to DownloadContents but additionally
// returns the RepositoryContent of the requested file. This additional data
// is useful for future operations involving the requested file. For merely
// reading the content of a file, DownloadContents is perfectly adequate.
//
// It is possible for the download to result in a failed response when the
// returned error is nil. Callers should check the returned Response status
// code to verify the content is from a successful response.
//
// GitHub API docs: https://docs.github.com/rest/repos/contents#get-repository-content
//
//meta:operation GET /repos/{owner}/{repo}/contents/{path}
func ( *RepositoriesService) ( context.Context, , ,  string,  *RepositoryContentGetOptions) (io.ReadCloser, *RepositoryContent, *Response, error) {
	 := path.Dir()
	 := path.Base()
	, , ,  := .GetContents(, , , , )
	if  != nil {
		return nil, nil, , 
	}

	for ,  := range  {
		if *.Name ==  {
			if .DownloadURL == nil || *.DownloadURL == "" {
				return nil, , , fmt.Errorf("no download link found for %s", )
			}

			,  := http.NewRequestWithContext(, http.MethodGet, *.DownloadURL, nil)
			if  != nil {
				return nil, , , 
			}
			,  := .client.client.Do()
			if  != nil {
				return nil, , &Response{Response: }, 
			}

			return .Body, , &Response{Response: }, nil
		}
	}

	return nil, nil, , fmt.Errorf("no file named %s found in %s", , )
}

// GetContents can return either the metadata and content of a single file
// (when path references a file) or the metadata of all the files and/or
// subdirectories of a directory (when path references a directory). To make it
// easy to distinguish between both result types and to mimic the API as much
// as possible, both result types will be returned but only one will contain a
// value and the other will be nil.
//
// Due to an auth vulnerability issue in the GitHub v3 API, ".." is not allowed
// to appear anywhere in the "path" or this method will return an error.
//
// GitHub API docs: https://docs.github.com/rest/repos/contents#get-repository-content
//
//meta:operation GET /repos/{owner}/{repo}/contents/{path}
func ( *RepositoriesService) ( context.Context, , ,  string,  *RepositoryContentGetOptions) ( *RepositoryContent,  []*RepositoryContent,  *Response,  error) {
	if strings.Contains(, "..") {
		return nil, nil, nil, ErrPathForbidden
	}

	 := (&url.URL{Path: strings.TrimSuffix(, "/")}).String()
	 := fmt.Sprintf("repos/%s/%s/contents/%s", , , )
	,  = addOptions(, )
	if  != nil {
		return nil, nil, nil, 
	}

	,  := .client.NewRequest("GET", , nil)
	if  != nil {
		return nil, nil, nil, 
	}

	var  json.RawMessage
	,  = .client.Do(, , &)
	if  != nil {
		return nil, nil, , 
	}

	 := json.Unmarshal(, &)
	if  == nil {
		return , nil, , nil
	}

	 := json.Unmarshal(, &)
	if  == nil {
		return nil, , , nil
	}

	return nil, nil, , fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s", , )
}

// CreateFile creates a new file in a repository at the given path and returns
// the commit and file metadata.
//
// GitHub API docs: https://docs.github.com/rest/repos/contents#create-or-update-file-contents
//
//meta:operation PUT /repos/{owner}/{repo}/contents/{path}
func ( *RepositoriesService) ( context.Context, , ,  string,  *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	 := fmt.Sprintf("repos/%s/%s/contents/%s", , , )
	,  := .client.NewRequest("PUT", , )
	if  != nil {
		return nil, nil, 
	}

	 := new(RepositoryContentResponse)
	,  := .client.Do(, , )
	if  != nil {
		return nil, , 
	}

	return , , nil
}

// UpdateFile updates a file in a repository at the given path and returns the
// commit and file metadata. Requires the blob SHA of the file being updated.
//
// GitHub API docs: https://docs.github.com/rest/repos/contents#create-or-update-file-contents
//
//meta:operation PUT /repos/{owner}/{repo}/contents/{path}
func ( *RepositoriesService) ( context.Context, , ,  string,  *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	 := fmt.Sprintf("repos/%s/%s/contents/%s", , , )
	,  := .client.NewRequest("PUT", , )
	if  != nil {
		return nil, nil, 
	}

	 := new(RepositoryContentResponse)
	,  := .client.Do(, , )
	if  != nil {
		return nil, , 
	}

	return , , nil
}

// DeleteFile deletes a file from a repository and returns the commit.
// Requires the blob SHA of the file to be deleted.
//
// GitHub API docs: https://docs.github.com/rest/repos/contents#delete-a-file
//
//meta:operation DELETE /repos/{owner}/{repo}/contents/{path}
func ( *RepositoriesService) ( context.Context, , ,  string,  *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	 := fmt.Sprintf("repos/%s/%s/contents/%s", , , )
	,  := .client.NewRequest("DELETE", , )
	if  != nil {
		return nil, nil, 
	}

	 := new(RepositoryContentResponse)
	,  := .client.Do(, , )
	if  != nil {
		return nil, , 
	}

	return , , nil
}

// ArchiveFormat is used to define the archive type when calling GetArchiveLink.
type ArchiveFormat string

const (
	// Tarball specifies an archive in gzipped tar format.
	Tarball ArchiveFormat = "tarball"

	// Zipball specifies an archive in zip format.
	Zipball ArchiveFormat = "zipball"
)

// GetArchiveLink returns an URL to download a tarball or zipball archive for a
// repository. The archiveFormat can be specified by either the github.Tarball
// or github.Zipball constant.
//
// GitHub API docs: https://docs.github.com/rest/repos/contents#download-a-repository-archive-tar
// GitHub API docs: https://docs.github.com/rest/repos/contents#download-a-repository-archive-zip
//
//meta:operation GET /repos/{owner}/{repo}/tarball/{ref}
//meta:operation GET /repos/{owner}/{repo}/zipball/{ref}
func ( *RepositoriesService) ( context.Context, ,  string,  ArchiveFormat,  *RepositoryContentGetOptions,  int) (*url.URL, *Response, error) {
	 := fmt.Sprintf("repos/%s/%s/%s", , , )
	if  != nil && .Ref != "" {
		 += fmt.Sprintf("/%s", .Ref)
	}
	,  := .client.roundTripWithOptionalFollowRedirect(, , )
	if  != nil {
		return nil, nil, 
	}
	defer .Body.Close()

	if .StatusCode != http.StatusOK && .StatusCode != http.StatusFound {
		return nil, newResponse(), fmt.Errorf("unexpected status code: %s", .Status)
	}

	,  := url.Parse(.Header.Get("Location"))
	if  != nil {
		return nil, newResponse(), 
	}

	return , newResponse(), nil
}