Exemple #1
0
def _split_proto_line(line, allowed):
    """Split a line read from the wire.

    :param line: The line read from the wire.
    :param allowed: An iterable of command names that should be allowed.
        Command names not listed below as possible return values will be
        ignored.  If None, any commands from the possible return values are
        allowed.
    :return: a tuple having one of the following forms:
        ('want', obj_id)
        ('have', obj_id)
        ('done', None)
        (None, None)  (for a flush-pkt)

    :raise UnexpectedCommandError: if the line cannot be parsed into one of the
        allowed return values.
    """
    if not line:
        fields = [None]
    else:
        fields = line.rstrip(b"\n").split(b" ", 1)
    command = fields[0]
    if allowed is not None and command not in allowed:
        raise UnexpectedCommandError(command)
    if len(fields) == 1 and command in (COMMAND_DONE, None):
        return (command, None)
    elif len(fields) == 2:
        if command in (COMMAND_WANT, COMMAND_HAVE, COMMAND_SHALLOW,
                       COMMAND_UNSHALLOW):
            if not valid_hexsha(fields[1]):
                raise GitProtocolError("Invalid sha")
            return tuple(fields)
        elif command == COMMAND_DEEPEN:
            return command, int(fields[1])
    raise GitProtocolError("Received invalid line from client: %r" % line)
Exemple #2
0
    def read_pkt_line(self):
        """Reads a pkt-line from the remote git process.

        This method may read from the readahead buffer; see unread_pkt_line.

        :return: The next string from the stream, without the length prefix, or
            None for a flush-pkt ('0000').
        """
        if self._readahead is None:
            read = self.read
        else:
            read = self._readahead.read
            self._readahead = None

        try:
            sizestr = read(4)
            if not sizestr:
                raise HangupException()
            size = int(sizestr, 16)
            if size == 0:
                if self.report_activity:
                    self.report_activity(4, "read")
                return None
            if self.report_activity:
                self.report_activity(size, "read")
            pkt_contents = read(size - 4)
        except socket.error as e:
            raise GitProtocolError(e)
        else:
            if len(pkt_contents) + 4 != size:
                raise GitProtocolError(
                    "Length of pkt read %04x does not match length prefix %04x"
                    % (len(pkt_contents) + 4, size)
                )
            return pkt_contents
Exemple #3
0
 def set_client_capabilities(self, caps):
     allowable_caps = set(self.innocuous_capabilities())
     allowable_caps.update(self.capabilities())
     for cap in caps:
         if cap not in allowable_caps:
             raise GitProtocolError("Client asked for capability %s that "
                                    "was not advertised." % cap)
     for cap in self.required_capabilities():
         if cap not in caps:
             raise GitProtocolError("Client does not support required "
                                    "capability %s." % cap)
     self._client_capabilities = set(caps)
     logger.info("Client capabilities: %s", caps)
Exemple #4
0
    def _apply_pack(self, refs):
        all_exceptions = (
            IOError,
            OSError,
            ChecksumMismatch,
            ApplyDeltaError,
            AssertionError,
            socket.error,
            zlib.error,
            ObjectFormatException,
        )
        status = []
        will_send_pack = False

        for command in refs:
            if command[1] != ZERO_SHA:
                will_send_pack = True

        if will_send_pack:
            # TODO: more informative error messages than just the exception
            # string
            try:
                recv = getattr(self.proto, "recv", None)
                self.repo.object_store.add_thin_pack(self.proto.read, recv)
                status.append((b"unpack", b"ok"))
            except all_exceptions as e:
                status.append((b"unpack", str(e).replace("\n", "")))
                # The pack may still have been moved in, but it may contain
                # broken objects. We trust a later GC to clean it up.
        else:
            # The git protocol want to find a status entry related to unpack
            # process even if no pack data has been sent.
            status.append((b"unpack", b"ok"))

        for oldsha, sha, ref in refs:
            ref_status = b"ok"
            try:
                if sha == ZERO_SHA:
                    if CAPABILITY_DELETE_REFS not in self.capabilities():
                        raise GitProtocolError(
                            "Attempted to delete refs without delete-refs "
                            "capability.")
                    try:
                        self.repo.refs.remove_if_equals(ref, oldsha)
                    except all_exceptions:
                        ref_status = b"failed to delete"
                else:
                    try:
                        self.repo.refs.set_if_equals(ref, oldsha, sha)
                    except all_exceptions:
                        ref_status = b"failed to write"
            except KeyError:
                ref_status = b"bad ref"
            status.append((ref, ref_status))

        return status
Exemple #5
0
    def handle(self):
        proto = ReceivableProtocol(self.connection.recv, self.wfile.write)
        command, args = proto.read_cmd()
        logger.info("Handling %s request, args=%s", command, args)

        cls = self.handlers.get(command, None)
        if not callable(cls):
            raise GitProtocolError("Invalid service %s" % command)
        h = cls(self.server.backend, args, proto)
        h.handle()
Exemple #6
0
    def write_pkt_line(self, line):
        """Sends a pkt-line to the remote git process.

        :param line: A string containing the data to send, without the length
            prefix.
        """
        try:
            line = pkt_line(line)
            self.write(line)
            if self.report_activity:
                self.report_activity(len(line), "write")
        except socket.error as e:
            raise GitProtocolError(e)
Exemple #7
0
    def determine_wants(self, heads):
        """Determine the wants for a set of heads.

        The given heads are advertised to the client, who then specifies which
        refs he wants using 'want' lines. This portion of the protocol is the
        same regardless of ack type, and in fact is used to set the ack type of
        the ProtocolGraphWalker.

        If the client has the 'shallow' capability, this method also reads and
        responds to the 'shallow' and 'deepen' lines from the client. These are
        not part of the wants per se, but they set up necessary state for
        walking the graph. Additionally, later code depends on this method
        consuming everything up to the first 'have' line.

        :param heads: a dict of refname->SHA1 to advertise
        :return: a list of SHA1s requested by the client
        """
        symrefs = self.get_symrefs()
        values = set(heads.values())
        if self.advertise_refs or not self.http_req:
            for i, (ref, sha) in enumerate(sorted(heads.items())):
                line = sha + b" " + ref
                if not i:
                    line += b"\x00" + self.handler.capability_line(
                        self.handler.capabilities() +
                        symref_capabilities(symrefs.items()))
                self.proto.write_pkt_line(line + b"\n")
                peeled_sha = self.get_peeled(ref)
                if peeled_sha != sha:
                    self.proto.write_pkt_line(peeled_sha + b" " + ref +
                                              ANNOTATED_TAG_SUFFIX + b"\n")

            # i'm done..
            self.proto.write_pkt_line(None)

            if self.advertise_refs:
                return []

        # Now client will sending want want want commands
        want = self.proto.read_pkt_line()
        if not want:
            return []
        line, caps = extract_want_line_capabilities(want)
        self.handler.set_client_capabilities(caps)
        self.set_ack_type(ack_type(caps))
        allowed = (COMMAND_WANT, COMMAND_SHALLOW, COMMAND_DEEPEN, None)
        command, sha = _split_proto_line(line, allowed)

        want_revs = []
        while command == COMMAND_WANT:
            if sha not in values:
                raise GitProtocolError("Client wants invalid object %s" % sha)
            want_revs.append(sha)
            command, sha = self.read_proto_line(allowed)

        self.set_wants(want_revs)
        if command in (COMMAND_SHALLOW, COMMAND_DEEPEN):
            self.unread_proto_line(command, sha)
            self._handle_shallow_request(want_revs)

        if self.http_req and self.proto.eof():
            # The client may close the socket at this point, expecting a
            # flush-pkt from the server. We might be ready to send a packfile
            # at this point, so we need to explicitly short-circuit in this
            # case.
            return []

        return want_revs
Exemple #8
0
 def has_capability(self, cap):
     if self._client_capabilities is None:
         raise GitProtocolError("Server attempted to access capability %s "
                                "before asking client" % cap)
     return cap in self._client_capabilities