@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 137 lines 4.5 kB view raw
1<?php 2 3final class DiffusionMercurialWireProtocol extends Phobject { 4 5 public static function getCommandArgs($command) { 6 // We need to enumerate all of the Mercurial wire commands because the 7 // argument encoding varies based on the command. "Why?", you might ask, 8 // "Why would you do this?". 9 10 $commands = array( 11 'batch' => array('cmds', '*'), 12 'between' => array('pairs'), 13 'branchmap' => array(), 14 'branches' => array('nodes'), 15 'capabilities' => array(), 16 'changegroup' => array('roots'), 17 'changegroupsubset' => array('bases heads'), 18 'debugwireargs' => array('one two *'), 19 'getbundle' => array('*'), 20 'heads' => array(), 21 'hello' => array(), 22 'known' => array('nodes', '*'), 23 'listkeys' => array('namespace'), 24 'lookup' => array('key'), 25 'pushkey' => array('namespace', 'key', 'old', 'new'), 26 'protocaps' => array('caps'), 27 'stream_out' => array(''), 28 'unbundle' => array('heads'), 29 ); 30 31 if (!isset($commands[$command])) { 32 throw new Exception( 33 pht( 34 'Unknown Mercurial command "%s"!', 35 $command)); 36 } 37 38 return $commands[$command]; 39 } 40 41 public static function isReadOnlyCommand($command) { 42 $read_only = array( 43 'between' => true, 44 'branchmap' => true, 45 'branches' => true, 46 'capabilities' => true, 47 'changegroup' => true, 48 'changegroupsubset' => true, 49 'debugwireargs' => true, 50 'getbundle' => true, 51 'heads' => true, 52 'hello' => true, 53 'known' => true, 54 'listkeys' => true, 55 'lookup' => true, 56 'protocaps' => true, 57 'stream_out' => true, 58 ); 59 60 // Notably, the write commands are "pushkey" and "unbundle". The 61 // "batch" command is theoretically read only, but we require explicit 62 // analysis of the actual commands. 63 64 return isset($read_only[$command]); 65 } 66 67 public static function isReadOnlyBatchCommand($cmds) { 68 if (!strlen($cmds)) { 69 // We expect a "batch" command to always have a "cmds" string, so err 70 // on the side of caution and throw if we don't get any data here. This 71 // either indicates a mangled command from the client or a programming 72 // error in our code. 73 throw new Exception(pht("Expected nonempty '%s' specification!", 'cmds')); 74 } 75 76 // For "batch" we get a "cmds" argument like: 77 // 78 // heads ;known nodes= 79 // 80 // We need to examine the commands (here, "heads" and "known") to make sure 81 // they're all read-only. 82 83 // NOTE: Mercurial has some code to escape semicolons, but it does not 84 // actually function for command separation. For example, these two batch 85 // commands will produce completely different results (the former will run 86 // the lookup; the latter will fail with a parser error): 87 // 88 // lookup key=a:xb;lookup key=z* 0 89 // lookup key=a:;b;lookup key=z* 0 90 // ^ 91 // | 92 // +-- Note semicolon. 93 // 94 // So just split unconditionally. 95 96 $cmds = explode(';', $cmds); 97 foreach ($cmds as $sub_cmd) { 98 $name = head(explode(' ', $sub_cmd, 2)); 99 if (!self::isReadOnlyCommand($name)) { 100 return false; 101 } 102 } 103 104 return true; 105 } 106 107 /** If the server version is running 3.4+ it will respond 108 * with 'bundle2' capability in the format of "bundle2=(url-encoding)". 109 * Until we manage to properly package up bundles to send back we 110 * disallow the client from knowing we speak bundle2 by removing it 111 * from the capabilities listing. 112 * 113 * The format of the capabilities string is: "a space separated list 114 * of strings representing what commands the server supports" 115 * @link https://www.mercurial-scm.org/wiki/CommandServer#Protocol 116 * 117 * @param string $capabilities - The string of capabilities to 118 * strip the bundle2 capability from. This is expected to be 119 * the space-separated list of strings resulting from the 120 * querying the 'capabilities' command. 121 * 122 * @return string The resulting space-separated list of capabilities 123 * which no longer contains the 'bundle2' capability. This is meant 124 * to replace the original $body to send back to client. 125 */ 126 public static function filterBundle2Capability($capabilities) { 127 $parts = explode(' ', $capabilities); 128 foreach ($parts as $key => $part) { 129 if (preg_match('/^bundle2=/', $part)) { 130 unset($parts[$key]); 131 break; 132 } 133 } 134 return implode(' ', $parts); 135 } 136 137}