caddy-git-server

Provides a git_server caddy module for serving git repositories.

package gitserver

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"path"
	"strings"

	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/h2non/filetype"
)

type GitBrowserBlob struct {
	Blob     []byte
	BlobHash string
	FileName string
	FileType string
	MIMEType string
}

func (gb *GitBrowser) browseBlob() error {
	if gb.PageArgs == "" {
		return caddyhttp.Error(404, fmt.Errorf("no file specified in blob request"))
	}
	pageData := new(GitBrowserBlob)

	fileHash := plumbing.NewHash(gb.PageArgs)
	fileBlob, err := gb.Repo.BlobObject(fileHash)
	var fileName string
	if err != nil {
		// Try to see if file path
		refCommit, _ := gb.Repo.CommitObject(*gb.RefHash)
		tree, err := refCommit.Tree()
		if err != nil {
			return caddyhttp.Error(503, err)
		}
		file, err := tree.File(gb.PageArgs)
		if err != nil {
			return caddyhttp.Error(503, err)
		}
		fileHash = file.Hash
		fileBlob = &file.Blob
		fileName = file.Name
	} else {
		// Extract filename from blob
		refCommit, _ := gb.Repo.CommitObject(*gb.RefHash)
		files, err := refCommit.Files()
		if err != nil {
			return caddyhttp.Error(503, err)
		}
		for file, err := files.Next(); file != nil; {
			if err != nil {
				return caddyhttp.Error(503, err)
			}
			if file.Hash.String() == fileHash.String() {
				fileName = file.Name
				break
			}
		}
	}
	blobReader, err := fileBlob.Reader()
	bufBlobReader := bufio.NewReader(blobReader)
	if err != nil {
		return caddyhttp.Error(503, err)
	}
	// Read file header into buffer to determine type
	fileHead := make([]byte, 261)
	bufBlobReader.Read(fileHead)

	blobType, err := filetype.Match(fileHead)
	if err != nil {
		return caddyhttp.Error(503, err)
	}

	if blobType.MIME.Type == "image" {
		// Embed raw uri with image html tag
		pageData.FileType = "image"
		pageData.MIMEType = blobType.MIME.Value
		pageData.Blob = []byte("/" + gb.Root + "/raw/" + fileHash.String())
	} else if blobType.MIME.Type == "application" {
		pageData.FileType = "application"
		pageData.MIMEType = blobType.MIME.Value
		pageData.Blob = []byte("/" + gb.Root + "/raw/" + fileHash.String())

	} else if path.Ext(fileName) == ".md" {
		pageData.FileType = "markdown"

		inputMarkdown := make([]byte, fileBlob.Size)
		blobReader, _ = fileBlob.Reader()
		_, err := blobReader.Read(inputMarkdown)
		if err != nil {
			return caddyhttp.Error(503, err)
		}

		// marked := blackfriday.Run(inputMarkdown)

		pageData.Blob = inputMarkdown
	} else {
		pageData.FileType = "text"
		strBuilder := new(strings.Builder)
		// strBuilder.Write(fileHead)
		blobReader, _ = fileBlob.Reader()
		bufBlobReader.Reset(blobReader)
		_, err = io.Copy(strBuilder, bufBlobReader)
		if err != nil {
			return caddyhttp.Error(503, err)
		}
		pageData.Blob = []byte(strBuilder.String())
	}

	pageData.BlobHash = fileHash.String()
	pageData.FileName = fileName

	gb.PageData = pageData

	return nil
}

func (gb *GitBrowser) browseRaw() error {
	if gb.PageArgs == "" {
		return caddyhttp.Error(404, fmt.Errorf("no file specified in blob request"))
	}
	pageData := new(GitBrowserBlob)
	fileHash := plumbing.NewHash(gb.PageArgs)
	fileBlob, err := gb.Repo.BlobObject(fileHash)
	if err != nil {
		return caddyhttp.Error(404, err)
	}
	blobReader, err := fileBlob.Reader()
	bufBlobReader := bufio.NewReader(blobReader)
	if err != nil {
		return caddyhttp.Error(503, err)
	}
	// Read file header into buffer to determine type
	fileHead := make([]byte, 261)
	bufBlobReader.Read(fileHead)

	fileType, err := filetype.Match(fileHead)
	if err != nil {
		return caddyhttp.Error(503, err)
	}
	// Save MIMEtype
	pageData.FileType = fileType.MIME.Type

	// Create array as long as file
	pageData.Blob = make([]byte, fileBlob.Size)

	// Read file
	blobReader, _ = fileBlob.Reader()
	bufBlobReader.Reset(blobReader)

	bufBlobReader.Read(pageData.Blob)

	gb.PageData = pageData

	return nil
}

func (gb *GitBrowser) serveRaw(w http.ResponseWriter) error {
	pageData := gb.PageData.(*GitBrowserBlob)
	w.Header().Set("Content-Type", pageData.MIMEType)
	w.Write(pageData.Blob)
	return nil
}