| | |
| | public function render() { |
| | - error_log( "=== ClonePage render called ===" ); |
| | - error_log( "subPath: " . $this->subPath ); |
| | - error_log( "REQUEST_METHOD: " . $_SERVER['REQUEST_METHOD'] ); |
| | - |
| | if( $this->subPath === '' ) { |
| | - error_log( "Empty subPath - redirecting browser" ); |
| | $this->redirectBrowser(); |
| | - return; |
| | - } |
| | - |
| | - if( str_ends_with( $this->subPath, 'info/refs' ) ) { |
| | - error_log( "Handling info/refs" ); |
| | + } elseif( str_ends_with( $this->subPath, 'info/refs' ) ) { |
| | $this->renderInfoRefs(); |
| | - return; |
| | - } |
| | - |
| | - if( str_ends_with( $this->subPath, 'git-upload-pack' ) ) { |
| | - error_log( "Handling git-upload-pack" ); |
| | + } elseif( str_ends_with( $this->subPath, 'git-upload-pack' ) ) { |
| | $this->handleUploadPack(); |
| | - return; |
| | - } |
| | - |
| | - if( str_ends_with( $this->subPath, 'git-receive-pack' ) ) { |
| | - error_log( "Handling git-receive-pack - forbidden" ); |
| | + } elseif( str_ends_with( $this->subPath, 'git-receive-pack' ) ) { |
| | http_response_code( 403 ); |
| | echo "Read-only repository."; |
| | exit; |
| | - } |
| | - |
| | - if( $this->subPath === 'HEAD' ) { |
| | - error_log( "Serving HEAD file" ); |
| | + } elseif( $this->subPath === 'HEAD' ) { |
| | $this->serve( 'HEAD', 'text/plain' ); |
| | - return; |
| | - } |
| | - |
| | - if( strpos( $this->subPath, 'objects/' ) === 0 ) { |
| | - error_log( "Serving object: " . $this->subPath ); |
| | + } elseif( strpos( $this->subPath, 'objects/' ) === 0 ) { |
| | $this->serve( $this->subPath, 'application/x-git-object' ); |
| | - return; |
| | + } else { |
| | + http_response_code( 404 ); |
| | + echo "Not Found"; |
| | + exit; |
| | } |
| | - |
| | - error_log( "Path not found: " . $this->subPath ); |
| | - http_response_code( 404 ); |
| | - echo "Not Found"; |
| | - exit; |
| | } |
| | |
| | private function redirectBrowser(): void { |
| | $uiUrl = str_replace( '.git', '', $_SERVER['REQUEST_URI'] ); |
| | header( "Location: $uiUrl" ); |
| | exit; |
| | } |
| | |
| | private function renderInfoRefs(): void { |
| | - error_log( "=== renderInfoRefs DEBUG ===" ); |
| | - error_log( "QUERY_STRING: " . ($_SERVER['QUERY_STRING'] ?? 'NOT SET') ); |
| | - error_log( "\$_GET contents: " . print_r( $_GET, true ) ); |
| | - |
| | $service = $_GET['service'] ?? ''; |
| | - error_log( "service parameter: '" . $service . "'" ); |
| | |
| | if( $service === 'git-upload-pack' ) { |
| | - error_log( "Smart protocol - 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]; |
| | - error_log( "Ref: $ref => $sha" ); |
| | } ); |
| | |
| | - $caps = "multi_ack_detailed thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag"; |
| | + $caps = "multi_ack_detailed thin-pack side-band side-band-64k " . |
| | + "ofs-delta shallow no-progress include-tag"; |
| | |
| | if( !empty( $refs ) ) { |
| | - error_log( "Sending " . count( $refs ) . " refs with capabilities" ); |
| | - $this->packetWrite( $refs[0]['sha'] . " " . $refs[0]['ref'] . "\0" . $caps . "\n" ); |
| | - |
| | + $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" ); |
| | } |
| | } else { |
| | - error_log( "Empty repo - sending capabilities^{}" ); |
| | - $this->packetWrite( "0000000000000000000000000000000000000000 capabilities^{}\0" . $caps . "\n" ); |
| | + $this->packetWrite( |
| | + "0000000000000000000000000000000000000000 capabilities^{}\0" . |
| | + $caps . "\n" |
| | + ); |
| | } |
| | |
| | $this->packetFlush(); |
| | - error_log( "=== Info refs completed ===" ); |
| | - exit; |
| | - } |
| | - |
| | - error_log( "Dumb protocol - plain text refs" ); |
| | - header( 'Content-Type: text/plain' ); |
| | - if( $this->git->streamRaw( 'info/refs' ) ) { |
| | - exit; |
| | + } else { |
| | + header( 'Content-Type: text/plain' ); |
| | + if( !$this->git->streamRaw( 'info/refs' ) ) { |
| | + $this->git->eachRef( function( $ref, $sha ) { |
| | + echo "$sha\t$ref\n"; |
| | + } ); |
| | + } |
| | } |
| | - |
| | - $this->git->eachRef( function( $ref, $sha ) { |
| | - echo "$sha\t$ref\n"; |
| | - } ); |
| | exit; |
| | } |
| | |
| | private function handleUploadPack(): void { |
| | - error_log( "=== handleUploadPack called ===" ); |
| | - error_log( "REQUEST_METHOD: " . $_SERVER['REQUEST_METHOD'] ); |
| | - error_log( "REQUEST_URI: " . $_SERVER['REQUEST_URI'] ); |
| | - |
| | header( 'Content-Type: application/x-git-upload-pack-result' ); |
| | header( 'Cache-Control: no-cache' ); |
| | |
| | $input = file_get_contents( 'php://input' ); |
| | - error_log( "Input length: " . strlen( $input ) ); |
| | - |
| | $wants = []; |
| | $haves = []; |
 |
| | |
| | $line = trim( $line ); |
| | - |
| | if( strpos( $line, 'want ' ) === 0 ) { |
| | - $parts = explode( ' ', $line ); |
| | - $wants[] = $parts[1]; |
| | - error_log( "WANT: " . $parts[1] ); |
| | + $wants[] = explode( ' ', $line )[1]; |
| | } elseif( strpos( $line, 'have ' ) === 0 ) { |
| | - $parts = explode( ' ', $line ); |
| | - $haves[] = $parts[1]; |
| | - error_log( "HAVE: " . $parts[1] ); |
| | + $haves[] = explode( ' ', $line )[1]; |
| | } elseif( $line === 'done' ) { |
| | - error_log( "DONE received" ); |
| | break; |
| | } |
| | - } |
| | - |
| | - error_log( "Total wants: " . count( $wants ) ); |
| | - error_log( "Total haves: " . count( $haves ) ); |
| | - |
| | - if( empty( $wants ) ) { |
| | - error_log( "ERROR: No wants received!" ); |
| | - $this->packetFlush(); |
| | - exit; |
| | } |
| | - |
| | - $this->packetWrite( "NAK\n" ); |
| | - |
| | - try { |
| | - $objects = $this->git->collectObjects( $wants, $haves ); |
| | - error_log( "Collected objects: " . count( $objects ) ); |
| | |
| | - $packData = $this->git->generatePackfile( $objects ); |
| | - error_log( "Generated packfile size: " . strlen( $packData ) ); |
| | + if( !empty( $wants ) ) { |
| | + $this->packetWrite( "NAK\n" ); |
| | |
| | - $this->sendSidebandData( 1, $packData ); |
| | + try { |
| | + $objects = $this->git->collectObjects( $wants, $haves ); |
| | + $packData = $this->git->generatePackfile( $objects ); |
| | + $this->sendSidebandData( 1, $packData ); |
| | + $this->packetFlush(); |
| | + } catch( Exception $e ) { |
| | + http_response_code( 500 ); |
| | + } |
| | + } else { |
| | $this->packetFlush(); |
| | - |
| | - error_log( "=== Upload pack completed successfully ===" ); |
| | - } catch( Exception $e ) { |
| | - error_log( "ERROR in handleUploadPack: " . $e->getMessage() ); |
| | - error_log( "Stack trace: " . $e->getTraceAsString() ); |
| | } |
| | - |
| | exit; |
| | } |
| | |
| | private function sendSidebandData( int $band, string $data ): void { |
| | $chunkSize = 65000; |
| | $offset = 0; |
| | $len = strlen( $data ); |
| | - |
| | - if ( $len === 0 ) return; |
| | |
| | while( $offset < $len ) { |
| | $chunk = substr( $data, $offset, $chunkSize ); |
| | - $packet = chr( $band ) . $chunk; |
| | - $this->packetWrite( $packet ); |
| | + $this->packetWrite( chr( $band ) . $chunk ); |
| | $offset += strlen( $chunk ); |
| | } |
| | } |
| | |
| | private function readPacketLine( string $input, int &$offset ): ?string { |
| | - if( $offset + 4 > strlen( $input ) ) { |
| | - return null; |
| | - } |
| | - |
| | - $lenHex = substr( $input, $offset, 4 ); |
| | - |
| | - if( !ctype_xdigit( $lenHex ) ) return null; |
| | - |
| | - $len = hexdec( $lenHex ); |
| | - |
| | - if( $len === 0 ) { |
| | - $offset += 4; |
| | - return ''; |
| | - } |
| | + $line = null; |
| | |
| | - if( $len < 4 ) { |
| | - return null; |
| | - } |
| | + if( $offset + 4 <= strlen( $input ) ) { |
| | + $lenHex = substr( $input, $offset, 4 ); |
| | |
| | - $offset += 4; |
| | - $dataLen = $len - 4; |
| | + if( ctype_xdigit( $lenHex ) ) { |
| | + $len = hexdec( $lenHex ); |
| | + $offset += 4; |
| | |
| | - if( $offset + $dataLen > strlen( $input ) ) { |
| | - return null; |
| | + if( $len === 0 ) { |
| | + $line = ''; |
| | + } elseif( $len >= 4 && $offset + ( $len - 4 ) <= strlen( $input ) ) { |
| | + $line = substr( $input, $offset, $len - 4 ); |
| | + $offset += ( $len - 4 ); |
| | + } |
| | + } |
| | } |
| | - |
| | - $data = substr( $input, $offset, $dataLen ); |
| | - $offset += $dataLen; |
| | |
| | - return $data; |
| | + return $line; |
| | } |
| | |
| | private function serve( string $path, string $contentType ): void { |
| | header( 'Content-Type: ' . $contentType ); |
| | - |
| | - $success = $this->git->streamRaw( $path ); |
| | |
| | - if( !$success ) { |
| | + if( !$this->git->streamRaw( $path ) ) { |
| | http_response_code( 404 ); |
| | echo "Missing: $path"; |
| | } |
| | |
| | exit; |
| | } |
| | |
| | private function packetWrite( string $data ): void { |
| | - $len = strlen( $data ) + 4; |
| | - printf( "%04x%s", $len, $data ); |
| | + printf( "%04x%s", strlen( $data ) + 4, $data ); |
| | } |
| | |
| | private function packetFlush(): void { |
| | echo "0000"; |
| | } |
| | } |
| | + |
| | |