| 1 | <?php |
| 2 | /************************************************************* |
| 3 | # 076 License |
| 4 | |
| 5 | Copyright (c) テクニカル諏訪子 |
| 6 | |
| 7 | Permission is hereby granted to any person obtaining a copy of the software |
| 8 | Little Beast (the "Software") to use, modify, merge, copy, publish, distribute, |
| 9 | sublicense, and/or sell copies of the Software, subject to the following conditions: |
| 10 | |
| 11 | 1. **Origin Attribution**: |
| 12 | - You must not misrepresent the origin of the Software; you must not claim |
| 13 | you created the original Software. |
| 14 | - If the Software is used in a product, you must either: |
| 15 | a. Provide clear attribution in the product's documentation, user interface, |
| 16 | or other visible areas, **OR** |
| 17 | b. Pay the original developers a fee they specify in writing. |
| 18 | 2. **Usage Restriction**: |
| 19 | - The Software, or any derivative works, dependencies, or libraries |
| 20 | incorporating it, must not be used for censorship or to suppress freedom of |
| 21 | speech, expression, or creativity. Prohibited uses include, but are not |
| 22 | limited to: |
| 23 | - Censorship of so-called "hate speech", visuals, non-mainstream opinions, |
| 24 | ideas, or objective reality. |
| 25 | - Tools or systems designed to restrict access to information or |
| 26 | artistic works. |
| 27 | 3. **Notice Preservation**: |
| 28 | - This license and the above copyright notice must remain intact in all copies |
| 29 | of the source code. |
| 30 | |
| 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
| 32 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A |
| 33 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| 34 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
| 35 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE |
| 36 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 37 | */ |
| 38 | namespace Std\Lib\Git; |
| 39 | |
| 40 | /** |
| 41 | * Smart HTTP(git clone https://…、git push) |
| 42 | * |
| 43 | * ルートから呼ぶ(アクセスの確認は先に済ませる): |
| 44 | * GET …/repo.git/info/refs?service=git-upload-pack|git-receive-pack |
| 45 | * POST …/repo.git/git-upload-pack |
| 46 | * POST …/repo.git/git-receive-pack |
| 47 | * 認証が必要な場合は、呼ぶ側が401とWWW-Authenticate: Basicを返す。 |
| 48 | */ |
| 49 | final class Http { |
| 50 | /** |
| 51 | * @param string $action info/refs、git-upload-pack、git-receive-pack |
| 52 | * @param array $opt push(プッシュを許すか)、receive(ReceivePackのオプション)、agent |
| 53 | * @param resource|null $in リクエストの本文(既定はphp://input) |
| 54 | * @param resource|null $out 応答(既定はphp://output) |
| 55 | * @return int HTTPのステータス |
| 56 | */ |
| 57 | public static function handle(Repository $repo, string $action, array $opt = [], $in = null, $out = null): int { |
| 58 | $push = !empty($opt['push']); |
| 59 | $version = str_contains((string)($_SERVER['HTTP_GIT_PROTOCOL'] ?? ''), 'version=2') ? 2 : 0; |
| 60 | $agent = ['agent' => (string)($opt['agent'] ?? 'LittleBeast')]; |
| 61 | $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; |
| 62 | if ($action === 'info/refs' && $method === 'GET') { |
| 63 | $service = (string)($_GET['service'] ?? ''); |
| 64 | if ($service !== 'git-upload-pack' && ($service !== 'git-receive-pack' || !$push)) return self::error(403, 'このサービスは使えません'); |
| 65 | self::headers("application/x-{$service}-advertisement"); |
| 66 | $out ??= fopen('php://output', 'wb'); |
| 67 | if ($service === 'git-upload-pack') (new UploadPack($repo, $agent))->advertise($out, $version !== 2, $version); |
| 68 | else (new ReceivePack($repo, $agent + ($opt['receive'] ?? [])))->advertise($out, true); |
| 69 | return 200; |
| 70 | } |
| 71 | if (($action === 'git-upload-pack' || $action === 'git-receive-pack') && $method === 'POST') { |
| 72 | if ($action === 'git-receive-pack' && !$push) return self::error(403, 'プッシュは出来ません'); |
| 73 | if (($_SERVER['CONTENT_TYPE'] ?? '') !== "application/x-{$action}-request") return self::error(415, '不正なContent-Type'); |
| 74 | $in ??= fopen('php://input', 'rb'); |
| 75 | $enc = strtolower((string)($_SERVER['HTTP_CONTENT_ENCODING'] ?? '')); |
| 76 | if ($enc === 'gzip' || $enc === 'x-gzip') stream_filter_append($in, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 31]); |
| 77 | @set_time_limit(0); |
| 78 | self::headers("application/x-{$action}-result"); |
| 79 | $out ??= fopen('php://output', 'wb'); |
| 80 | while (ob_get_level() > 0) ob_end_flush(); |
| 81 | if ($action === 'git-upload-pack') (new UploadPack($repo, $agent))->serve($in, $out, true, $version); |
| 82 | else (new ReceivePack($repo, $agent + ($opt['receive'] ?? [])))->serve($in, $out, true); |
| 83 | return 200; |
| 84 | } |
| 85 | return self::error(404, '有りません'); |
| 86 | } |
| 87 | |
| 88 | private static function headers(string $type): void { |
| 89 | if (headers_sent()) return; |
| 90 | header('Content-Type: '.$type); |
| 91 | header('Cache-Control: no-cache, max-age=0, must-revalidate'); |
| 92 | header('Pragma: no-cache'); |
| 93 | header('Expires: Fri, 01 Jan 1980 00:00:00 GMT'); |
| 94 | } |
| 95 | |
| 96 | private static function error(int $code, string $msg): int { |
| 97 | if (!headers_sent()) { |
| 98 | http_response_code($code); |
| 99 | header('Content-Type: text/plain; charset=UTF-8'); |
| 100 | } |
| 101 | echo $msg, "\n"; |
| 102 | return $code; |
| 103 | } |
| 104 | } |