114 lines
4.2 KiB
Diff
114 lines
4.2 KiB
Diff
--- 9.0.3 2024-12-12 08:06:13.000000000 +0100
|
|
+++ aneksajo 2024-12-16 08:23:15.000000000 +0100
|
|
@@ -10,6 +10,7 @@
|
|
gocontext "context"
|
|
"fmt"
|
|
"net/http"
|
|
+ "net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
@@ -78,7 +79,24 @@
|
|
strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") {
|
|
isPull = true
|
|
} else {
|
|
- isPull = ctx.Req.Method == "GET"
|
|
+ // In addition to GET requests, HEAD requests are also "pull"
|
|
+ // operations (reads), so they should also not require
|
|
+ // authentication. This is necessary for git-annex to operate
|
|
+ // properly, as it emits HEAD requests to check for the
|
|
+ // existence of keys, e.g. before dropping locally, and asking
|
|
+ // for authentication would break unauthenticated http usage in
|
|
+ // this situation.
|
|
+ // It should be safe to make all HEAD requests require no
|
|
+ // authentication, but as it is only necessary for the
|
|
+ // annex/objects endpoints to fix git-annex' drop operations it
|
|
+ // is limited to those for now.
|
|
+ r, err := regexp.Compile("^/?" + username + "/" + reponame + "(.git)?/annex/objects")
|
|
+ if err != nil {
|
|
+ ctx.ServerError("failed to create URL path regex", err)
|
|
+ return nil
|
|
+ }
|
|
+ isPull = ctx.Req.Method == "GET" ||
|
|
+ r.MatchString(ctx.Req.URL.Path) && ctx.Req.Method == "HEAD"
|
|
}
|
|
|
|
var accessMode perm.AccessMode
|
|
@@ -545,6 +563,42 @@
|
|
}
|
|
}
|
|
|
|
+// GetConfig implements fetching the git config of a repository
|
|
+func GetConfig(ctx *context.Context) {
|
|
+ h := httpBase(ctx)
|
|
+ if h != nil {
|
|
+ setHeaderNoCache(ctx)
|
|
+ config, err := os.ReadFile(filepath.Join(h.getRepoDir(), "config"))
|
|
+ if err != nil {
|
|
+ log.Error("Failed to read git config file: %v", err)
|
|
+ ctx.Resp.WriteHeader(http.StatusInternalServerError)
|
|
+ return
|
|
+ }
|
|
+ if !setting.Annex.DisableP2PHTTP {
|
|
+ appURL, err := url.Parse(setting.AppURL)
|
|
+ if err != nil {
|
|
+ log.Error("Could not parse 'setting.AppURL': %v", err)
|
|
+ ctx.Resp.WriteHeader(http.StatusInternalServerError)
|
|
+ return
|
|
+ }
|
|
+ if appURL.Port() == "" {
|
|
+ // If there is no port set then set the http(s) default ports.
|
|
+ // Without this, git-annex would try its own default port (9417) and fail.
|
|
+ if appURL.Scheme == "http" {
|
|
+ appURL.Host += ":80"
|
|
+ }
|
|
+ if appURL.Scheme == "https" {
|
|
+ appURL.Host += ":443"
|
|
+ }
|
|
+ }
|
|
+ config = append(config, []byte("[annex]\n\turl = annex+"+appURL.String()+"git-annex-p2phttp\n")...)
|
|
+ }
|
|
+ ctx.Resp.Header().Set("Content-Type", "text/plain")
|
|
+ ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(config)))
|
|
+ http.ServeContent(ctx.Resp, ctx.Req, "config", time.Now(), bytes.NewReader(config))
|
|
+ }
|
|
+}
|
|
+
|
|
// GetTextFile implements Git dumb HTTP
|
|
func GetTextFile(p string) func(*context.Context) {
|
|
return func(ctx *context.Context) {
|
|
@@ -597,3 +651,34 @@
|
|
h.sendFile(ctx, "application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx")
|
|
}
|
|
}
|
|
+
|
|
+// GetAnnexObject implements git-annex dumb HTTP
|
|
+func GetAnnexObject(ctx *context.Context) {
|
|
+ h := httpBase(ctx)
|
|
+ if h != nil {
|
|
+ // git-annex objects are stored in .git/annex/objects/{hash1}/{hash2}/{key}/{key}
|
|
+ // where key is a string containing the size and (usually SHA256) checksum of the file,
|
|
+ // and hash1+hash2 are the first few bits of the md5sum of key itself.
|
|
+ // ({hash1}/{hash2}/ is just there to avoid putting too many files in one directory)
|
|
+ // ref: https://git-annex.branchable.com/internals/hashing/
|
|
+
|
|
+ // keyDir should = key, but we don't enforce that
|
|
+ object := filepath.Join(ctx.Params("hash1"), ctx.Params("hash2"), ctx.Params("keyDir"), ctx.Params("key"))
|
|
+
|
|
+ // Sanitize the input against directory traversals.
|
|
+ //
|
|
+ // This works because at the filesystem root, "/.." = "/";
|
|
+ // So if a path starts rooted ("/"), path.Clean(), which
|
|
+ // path.Join() calls internally, removes all '..' prefixes.
|
|
+ // After, this unroots the path unconditionally ([1:]), which
|
|
+ // works because we know the input is never supposed to be rooted.
|
|
+ //
|
|
+ // The router code probably also disallows "..", so this
|
|
+ // should be redundant, but it's defensive to keep it
|
|
+ // whenever touching filesystem paths with user input.
|
|
+ object = filepath.Join(string(filepath.Separator), object)[1:]
|
|
+
|
|
+ setHeaderCacheForever(ctx)
|
|
+ h.sendFile(ctx, "application/octet-stream", "annex/objects/"+object)
|
|
+ }
|
|
+}
|