| | |
| | public function render() { |
| | + if( $this->subPath === 'git-receive-pack' ) { |
| | + http_response_code( 403 ); |
| | + echo "Read-only repository."; |
| | + exit; |
| | + } |
| | + |
| | if( $this->subPath === 'info/refs' ) { |
| | $this->renderInfoRefs(); |
| | + return; |
| | + } |
| | + |
| | + if( $this->subPath === 'git-upload-pack' ) { |
| | + $this->handleUploadPack(); |
| | return; |
| | } |
 |
| | |
| | private function renderInfoRefs(): void { |
| | + $service = $_GET['service'] ?? ''; |
| | + |
| | + if( $service === 'git-receive-pack' ) { |
| | + http_response_code( 403 ); |
| | + echo "Read-only repository."; |
| | + exit; |
| | + } |
| | + |
| | + if( $service === 'git-upload-pack' ) { |
| | + header( 'Content-Type: application/x-git-upload-pack-advertisement' ); |
| | + header( 'Cache-Control: no-cache' ); |
| | + |
| | + $this->packetWrite( "# service=git-upload-pack\n" ); |
| | + $this->packetFlush(); |
| | + |
| | + $refs = []; |
| | + $this->git->eachRef( function( $ref, $sha ) use ( &$refs ) { |
| | + $refs[] = ['ref' => $ref, 'sha' => $sha]; |
| | + } ); |
| | + |
| | + if( !empty( $refs ) ) { |
| | + $caps = "multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag"; |
| | + $this->packetWrite( $refs[0]['sha'] . " " . $refs[0]['ref'] . "\0" . $caps . "\n" ); |
| | + |
| | + for( $i = 1; $i < count( $refs ); $i++ ) { |
| | + $this->packetWrite( $refs[$i]['sha'] . " " . $refs[$i]['ref'] . "\n" ); |
| | + } |
| | + } |
| | + |
| | + $this->packetFlush(); |
| | + exit; |
| | + } |
| | + |
| | header( 'Content-Type: text/plain' ); |
| | |
| | if( $this->git->streamRaw( 'info/refs' ) ) { |
| | exit; |
| | } |
| | |
| | $this->git->eachRef( function( $ref, $sha ) { |
| | echo "$sha\t$ref\n"; |
| | } ); |
| | + |
| | + exit; |
| | + } |
| | + |
| | + private function handleUploadPack(): void { |
| | + header( 'Content-Type: application/x-git-upload-pack-result' ); |
| | + header( 'Cache-Control: no-cache' ); |
| | + |
| | + $input = file_get_contents( 'php://input' ); |
| | + $wants = []; |
| | + $haves = []; |
| | + $offset = 0; |
| | + |
| | + while( $offset < strlen( $input ) ) { |
| | + $line = $this->readPacketLine( $input, $offset ); |
| | + if( $line === null ) break; |
| | + if( $line === '' ) continue; |
| | + |
| | + $line = trim( $line ); |
| | + |
| | + if( strpos( $line, 'want ' ) === 0 ) { |
| | + $parts = explode( ' ', $line ); |
| | + $wants[] = $parts[1]; |
| | + } elseif( strpos( $line, 'have ' ) === 0 ) { |
| | + $parts = explode( ' ', $line ); |
| | + $haves[] = $parts[1]; |
| | + } elseif( $line === 'done' ) { |
| | + break; |
| | + } |
| | + } |
| | + |
| | + if( empty( $wants ) ) { |
| | + $this->packetWrite( "ERR no wants provided\n" ); |
| | + $this->packetFlush(); |
| | + exit; |
| | + } |
| | + |
| | + $this->packetWrite( "NAK\n" ); |
| | + |
| | + $objects = $this->git->collectObjects( $wants, $haves ); |
| | + $packData = $this->git->generatePackfile( $objects ); |
| | + |
| | + $this->sendSidebandData( 1, $packData ); |
| | + $this->packetFlush(); |
| | |
| | exit; |
| | + } |
| | + |
| | + private function sendSidebandData( int $band, string $data ): void { |
| | + $chunkSize = 65000; |
| | + $offset = 0; |
| | + |
| | + while( $offset < strlen( $data ) ) { |
| | + $chunk = substr( $data, $offset, $chunkSize ); |
| | + $packet = chr( $band ) . $chunk; |
| | + $this->packetWrite( $packet ); |
| | + $offset += strlen( $chunk ); |
| | + } |
| | + } |
| | + |
| | + private function readPacketLine( string $input, int &$offset ): ?string { |
| | + if( $offset + 4 > strlen( $input ) ) { |
| | + return null; |
| | + } |
| | + |
| | + $lenHex = substr( $input, $offset, 4 ); |
| | + $len = hexdec( $lenHex ); |
| | + |
| | + if( $len === 0 ) { |
| | + $offset += 4; |
| | + return ''; |
| | + } |
| | + |
| | + if( $len < 4 ) { |
| | + return null; |
| | + } |
| | + |
| | + $offset += 4; |
| | + $data = substr( $input, $offset, $len - 4 ); |
| | + $offset += $len - 4; |
| | + |
| | + return $data; |
| | } |
| | |
| | private function serve( string $path, string $contentType ): void { |
| | header( 'Content-Type: ' . $contentType ); |
| | |
| | $success = $this->git->streamRaw( $path ); |
| | |
| | if( !$success ) { |
| | http_response_code( 404 ); |
| | - echo "File not found: $path"; |
| | + echo "Missing: $path"; |
| | } |
| | |
| | exit; |
| | + } |
| | + |
| | + private function packetWrite( string $data ): void { |
| | + $len = strlen( $data ) + 4; |
| | + printf( "%04x%s", $len, $data ); |
| | + } |
| | + |
| | + private function packetFlush(): void { |
| | + echo "0000"; |
| | } |
| | } |