1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
|
package githttp
import (
"bytes"
"context"
"fmt"
"io"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/git"
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/pktline"
)
const pullService = "git-upload-pack"
var uploadPackHttpPrefix = []byte("001e# service=git-upload-pack\n0000")
type PullCommand struct {
Config *config.Config
ReadWriter *readwriter.ReadWriter
Response *accessverifier.Response
}
// See Uploading Data > HTTP(S) section at:
// https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols
//
// 1. Perform /info/refs?service=git-upload-pack request
// 2. Remove the header to make it consumable by SSH protocol
// 3. Send the result to the user via SSH (writeToStdout)
// 4. Read the send-pack data provided by user via SSH (stdinReader)
// 5. Perform /git-upload-pack request and send this data
// 6. Return the output to the user
func (c *PullCommand) Execute(ctx context.Context) error {
data := c.Response.Payload.Data
client := &git.Client{Url: data.PrimaryRepo, Headers: data.RequestHeaders}
if err := c.requestInfoRefs(ctx, client); err != nil {
return err
}
return c.requestUploadPack(ctx, client, data.GeoProxyFetchDirectToPrimaryWithOptions)
}
func (c *PullCommand) requestInfoRefs(ctx context.Context, client *git.Client) error {
response, err := client.InfoRefs(ctx, pullService)
if err != nil {
return err
}
defer response.Body.Close()
// Read the first bytes that contain 001e# service=git-upload-pack\n0000 string
// to convert HTTP(S) Git response to the one expected by SSH
p := make([]byte, len(uploadPackHttpPrefix))
_, err = response.Body.Read(p)
if err != nil || !bytes.Equal(p, uploadPackHttpPrefix) {
return fmt.Errorf("Unexpected git-upload-pack response")
}
_, err = io.Copy(c.ReadWriter.Out, response.Body)
return err
}
func (c *PullCommand) requestUploadPack(ctx context.Context, client *git.Client, geoProxyFetchDirectToPrimaryWithOptions bool) error {
pipeReader, pipeWriter := io.Pipe()
go c.readFromStdin(pipeWriter, geoProxyFetchDirectToPrimaryWithOptions)
response, err := client.UploadPack(ctx, pipeReader)
if err != nil {
return err
}
defer response.Body.Close()
_, err = io.Copy(c.ReadWriter.Out, response.Body)
return err
}
func (c *PullCommand) readFromStdin(pw *io.PipeWriter, geoProxyFetchDirectToPrimaryWithOptions bool) {
scanner := pktline.NewScanner(c.ReadWriter.In)
for scanner.Scan() {
line := scanner.Bytes()
pw.Write(line)
if pktline.IsDone(line) {
break
}
if pktline.IsFlush(line) && geoProxyFetchDirectToPrimaryWithOptions {
pw.Write(pktline.PktDone())
break
}
}
pw.Close()
}
|