gitlabhq/gitlab-shell

View on GitHub
internal/command/githttp/push.go

Summary

Maintainability
A
0 mins
Test Coverage
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 service = "git-receive-pack"

var receivePackHttpPrefix = []byte("001f# service=git-receive-pack\n0000")

type PushCommand 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-receive-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-receive-pack request and send this data
// 6. Return the output to the user
func (c *PushCommand) 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.requestReceivePack(ctx, client)
}

func (c *PushCommand) requestInfoRefs(ctx context.Context, client *git.Client) error {
    response, err := client.InfoRefs(ctx, service)
    if err != nil {
        return err
    }
    defer response.Body.Close()

    // Read the first bytes that contain 001f# service=git-receive-pack\n0000 string
    // to convert HTTP(S) Git response to the one expected by SSH
    p := make([]byte, len(receivePackHttpPrefix))
    _, err = response.Body.Read(p)
    if err != nil || !bytes.Equal(p, receivePackHttpPrefix) {
        return fmt.Errorf("Unexpected git-receive-pack response")
    }

    _, err = io.Copy(c.ReadWriter.Out, response.Body)

    return err
}

func (c *PushCommand) requestReceivePack(ctx context.Context, client *git.Client) error {
    pipeReader, pipeWriter := io.Pipe()
    go c.readFromStdin(pipeWriter)

    response, err := client.ReceivePack(ctx, pipeReader)
    if err != nil {
        return err
    }
    defer response.Body.Close()

    _, err = io.Copy(c.ReadWriter.Out, response.Body)

    return err
}

func (c *PushCommand) readFromStdin(pw *io.PipeWriter) {
    var needsPackData bool

    scanner := pktline.NewScanner(c.ReadWriter.In)
    for scanner.Scan() {
        line := scanner.Bytes()
        pw.Write(line)

        if pktline.IsFlush(line) {
            break
        }

        if !needsPackData && !pktline.IsRefRemoval(line) {
            needsPackData = true
        }
    }

    if needsPackData {
        io.Copy(pw, c.ReadWriter.In)
    }

    pw.Close()
}