Goit

Simple and lightweight Git web server
git clone http://git.omkov.net/Goit
Log | Tree | Refs | README | Download

Goit/src/repo/download.go (150 lines, 3.6 KiB) -rw-r--r-- blame download

0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
// Copyright (C) 2023, Jakob Wakeling
// All rights reserved.

package repo

import (
	"archive/zip"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/Jamozed/Goit/src/goit"
	"github.com/Jamozed/Goit/src/util"
	"github.com/go-chi/chi/v5"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/object"
)

func HandleDownload(w http.ResponseWriter, r *http.Request) {
	auth, user, err := goit.Auth(w, r, true)
	if err != nil {
		log.Println("[repo/download]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	tpath := chi.URLParam(r, "*")

	repo, err := goit.GetRepoByName(chi.URLParam(r, "repo"))
	if err != nil {
		goit.HttpError(w, http.StatusInternalServerError)
		return
	} else if repo == nil || !goit.IsVisible(repo, auth, user) {
		goit.HttpError(w, http.StatusNotFound)
		return
	}

	gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
	if err != nil {
		log.Println("[/repo/download]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	ref, err := gr.Head()
	if errors.Is(err, plumbing.ErrReferenceNotFound) {
		goit.HttpError(w, http.StatusNotFound)
		return
	} else if err != nil {
		log.Println("[/repo/download]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	commit, err := gr.CommitObject(ref.Hash())
	if err != nil {
		log.Println("[/repo/download]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	file, err := commit.File(tpath)
	if errors.Is(err, object.ErrFileNotFound) {
		/* Possibly a directory, search file tree for prefix */
		var files []string
		var zSize uint64

		iter, err := commit.Files()
		if err != nil {
			log.Println("[/repo/download]", err.Error())
			goit.HttpError(w, http.StatusInternalServerError)
			return
		}

		iter.ForEach(func(f *object.File) error {
			if tpath == "" || strings.HasPrefix(f.Name, tpath+"/") {
				files = append(files, f.Name)
				zSize += uint64(f.Size)
			}

			return nil
		})

		if len(files) == 0 {
			goit.HttpError(w, http.StatusNotFound)
			return
		}

		/* Build and write ZIP of directory */
		w.Header().Set(
			"Content-Disposition", "attachment; filename="+util.If(tpath == "", repo.Name, filepath.Base(tpath))+".zip",
		)
		// w.Header().Set("Content-Length", fmt.Sprint(zSize))

		z := zip.NewWriter(w)
		for _, f := range files {
			zh := zip.FileHeader{Name: f, Method: zip.Store}

			zf, err := z.CreateHeader(&zh)
			if err != nil {
				log.Println("[/repo/download]", err.Error())
				goit.HttpError(w, http.StatusInternalServerError)
			}

			if file, err := commit.File(f); err != nil {
				log.Println("[/repo/download]", err.Error())
				goit.HttpError(w, http.StatusInternalServerError)
				return
			} else if rc, err := file.Blob.Reader(); err != nil {
				log.Println("[/repo/download]", err.Error())
				goit.HttpError(w, http.StatusInternalServerError)
				return
			} else {
				if _, err := io.Copy(zf, rc); err != nil {
					log.Println("[/repo/download]", err.Error())
				}

				rc.Close()
			}
		}

		z.Close()
		return
	} else if err != nil {
		log.Println("[/repo/download]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	if rc, err := file.Blob.Reader(); err != nil {
		log.Println("[/repo/download]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	} else {
		w.Header().Set("Content-Disposition", "attachement; filename="+filepath.Base(tpath))
		w.Header().Set("Content-Length", fmt.Sprint(file.Size))

		if _, err := io.Copy(w, rc); err != nil {
			log.Println("[/repo/download]", err.Error())
		}

		rc.Close()
	}
}