[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * This protocol has a good spec here: 5 * 6 * http://svn.apache.org/repos/asf/subversion/trunk/subversion/libsvn_ra_svn/protocol 7 * 8 */ 9 final class DiffusionSSHSubversionServeWorkflow 10 extends DiffusionSSHSubversionWorkflow { 11 12 private $didSeeWrite; 13 14 private $inProtocol; 15 private $outProtocol; 16 17 private $inSeenGreeting; 18 19 private $outPhaseCount = 0; 20 21 private $internalBaseURI; 22 private $externalBaseURI; 23 24 public function didConstruct() { 25 $this->setName('svnserve'); 26 $this->setArguments( 27 array( 28 array( 29 'name' => 'tunnel', 30 'short' => 't', 31 ), 32 )); 33 } 34 35 protected function executeRepositoryOperations() { 36 $args = $this->getArgs(); 37 if (!$args->getArg('tunnel')) { 38 throw new Exception('Expected `svnserve -t`!'); 39 } 40 41 $command = csprintf( 42 'svnserve -t --tunnel-user=%s', 43 $this->getUser()->getUsername()); 44 $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); 45 46 $future = new ExecFuture('%C', $command); 47 48 $this->inProtocol = new DiffusionSubversionWireProtocol(); 49 $this->outProtocol = new DiffusionSubversionWireProtocol(); 50 51 $err = id($this->newPassthruCommand()) 52 ->setIOChannel($this->getIOChannel()) 53 ->setCommandChannelFromExecFuture($future) 54 ->setWillWriteCallback(array($this, 'willWriteMessageCallback')) 55 ->setWillReadCallback(array($this, 'willReadMessageCallback')) 56 ->execute(); 57 58 if (!$err && $this->didSeeWrite) { 59 $this->getRepository()->writeStatusMessage( 60 PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, 61 PhabricatorRepositoryStatusMessage::CODE_OKAY); 62 } 63 64 return $err; 65 } 66 67 public function willWriteMessageCallback( 68 PhabricatorSSHPassthruCommand $command, 69 $message) { 70 71 $proto = $this->inProtocol; 72 $messages = $proto->writeData($message); 73 74 $result = array(); 75 foreach ($messages as $message) { 76 $message_raw = $message['raw']; 77 $struct = $message['structure']; 78 79 if (!$this->inSeenGreeting) { 80 $this->inSeenGreeting = true; 81 82 // The first message the client sends looks like: 83 // 84 // ( version ( cap1 ... ) url ... ) 85 // 86 // We want to grab the URL, load the repository, make sure it exists and 87 // is accessible, and then replace it with the location of the 88 // repository on disk. 89 90 $uri = $struct[2]['value']; 91 $struct[2]['value'] = $this->makeInternalURI($uri); 92 93 $message_raw = $proto->serializeStruct($struct); 94 } else if (isset($struct[0]) && $struct[0]['type'] == 'word') { 95 96 if (!$proto->isReadOnlyCommand($struct)) { 97 $this->didSeeWrite = true; 98 $this->requireWriteAccess($struct[0]['value']); 99 } 100 101 // Several other commands also pass in URLs. We need to translate 102 // all of these into the internal representation; this also makes sure 103 // they're valid and accessible. 104 105 switch ($struct[0]['value']) { 106 case 'reparent': 107 // ( reparent ( url ) ) 108 $struct[1]['value'][0]['value'] = $this->makeInternalURI( 109 $struct[1]['value'][0]['value']); 110 $message_raw = $proto->serializeStruct($struct); 111 break; 112 case 'switch': 113 // ( switch ( ( rev ) target recurse url ... ) ) 114 $struct[1]['value'][3]['value'] = $this->makeInternalURI( 115 $struct[1]['value'][3]['value']); 116 $message_raw = $proto->serializeStruct($struct); 117 break; 118 case 'diff': 119 // ( diff ( ( rev ) target recurse ignore-ancestry url ... ) ) 120 $struct[1]['value'][4]['value'] = $this->makeInternalURI( 121 $struct[1]['value'][4]['value']); 122 $message_raw = $proto->serializeStruct($struct); 123 break; 124 case 'add-file': 125 // ( add-file ( path dir-token file-token [ copy-path copy-rev ] ) ) 126 if (isset($struct[1]['value'][3]['value'][0]['value'])) { 127 $copy_from = $struct[1]['value'][3]['value'][0]['value']; 128 $copy_from = $this->makeInternalURI($copy_from); 129 $struct[1]['value'][3]['value'][0]['value'] = $copy_from; 130 } 131 $message_raw = $proto->serializeStruct($struct); 132 break; 133 } 134 } 135 136 $result[] = $message_raw; 137 } 138 139 if (!$result) { 140 return null; 141 } 142 143 return implode('', $result); 144 } 145 146 public function willReadMessageCallback( 147 PhabricatorSSHPassthruCommand $command, 148 $message) { 149 150 $proto = $this->outProtocol; 151 $messages = $proto->writeData($message); 152 153 $result = array(); 154 foreach ($messages as $message) { 155 $message_raw = $message['raw']; 156 $struct = $message['structure']; 157 158 if (isset($struct[0]) && ($struct[0]['type'] == 'word')) { 159 160 if ($struct[0]['value'] == 'success') { 161 switch ($this->outPhaseCount) { 162 case 0: 163 // This is the "greeting", which announces capabilities. 164 break; 165 case 1: 166 // This responds to the client greeting, and announces auth. 167 break; 168 case 2: 169 // This responds to auth, which should be trivial over SSH. 170 break; 171 case 3: 172 // This contains the URI of the repository. We need to edit it; 173 // if it does not match what the client requested it will reject 174 // the response. 175 $struct[1]['value'][1]['value'] = $this->makeExternalURI( 176 $struct[1]['value'][1]['value']); 177 $message_raw = $proto->serializeStruct($struct); 178 break; 179 default: 180 // We don't care about other protocol frames. 181 break; 182 } 183 184 $this->outPhaseCount++; 185 } else if ($struct[0]['value'] == 'failure') { 186 // Find any error messages which include the internal URI, and 187 // replace the text with the external URI. 188 foreach ($struct[1]['value'] as $key => $error) { 189 $code = $error['value'][0]['value']; 190 $message = $error['value'][1]['value']; 191 192 $message = str_replace( 193 $this->internalBaseURI, 194 $this->externalBaseURI, 195 $message); 196 197 // Derp derp derp derp derp. The structure looks like this: 198 // ( failure ( ( code message ... ) ... ) ) 199 $struct[1]['value'][$key]['value'][1]['value'] = $message; 200 } 201 $message_raw = $proto->serializeStruct($struct); 202 } 203 204 } 205 206 $result[] = $message_raw; 207 } 208 209 if (!$result) { 210 return null; 211 } 212 213 return implode('', $result); 214 } 215 216 private function makeInternalURI($uri_string) { 217 $uri = new PhutilURI($uri_string); 218 219 $proto = $uri->getProtocol(); 220 if ($proto !== 'svn+ssh') { 221 throw new Exception( 222 pht( 223 'Protocol for URI "%s" MUST be "svn+ssh".', 224 $uri_string)); 225 } 226 227 $path = $uri->getPath(); 228 229 // Subversion presumably deals with this, but make sure there's nothing 230 // skethcy going on with the URI. 231 if (preg_match('(/\\.\\./)', $path)) { 232 throw new Exception( 233 pht( 234 'String "/../" is invalid in path specification "%s".', 235 $uri_string)); 236 } 237 238 $repository = $this->loadRepository($path); 239 240 $path = preg_replace( 241 '(^/diffusion/[A-Z]+)', 242 rtrim($repository->getLocalPath(), '/'), 243 $path); 244 245 if (preg_match('(^/diffusion/[A-Z]+/\z)', $path)) { 246 $path = rtrim($path, '/'); 247 } 248 249 $uri->setPath($path); 250 251 // If this is happening during the handshake, these are the base URIs for 252 // the request. 253 if ($this->externalBaseURI === null) { 254 $pre = (string)id(clone $uri)->setPath(''); 255 $this->externalBaseURI = $pre.'/diffusion/'.$repository->getCallsign(); 256 $this->internalBaseURI = $pre.rtrim($repository->getLocalPath(), '/'); 257 } 258 259 return (string)$uri; 260 } 261 262 private function makeExternalURI($uri) { 263 $internal = $this->internalBaseURI; 264 $external = $this->externalBaseURI; 265 266 if (strncmp($uri, $internal, strlen($internal)) === 0) { 267 $uri = $external.substr($uri, strlen($internal)); 268 } 269 270 return $uri; 271 } 272 273 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |