1<?php 2 3declare(strict_types=1); 4 5/** 6 * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> 7 * 8 * @author Christoph Wurst <christoph@winzerhof-wurst.at> 9 * @author Robin Appelman <robin@icewind.nl> 10 * 11 * @license GNU AGPL version 3 or any later version 12 * 13 * This program is free software: you can redistribute it and/or modify 14 * it under the terms of the GNU Affero General Public License as 15 * published by the Free Software Foundation, either version 3 of the 16 * License, or (at your option) any later version. 17 * 18 * This program is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU Affero General Public License for more details. 22 * 23 * You should have received a copy of the GNU Affero General Public License 24 * along with this program. If not, see <http://www.gnu.org/licenses/>. 25 * 26 */ 27namespace OCA\Files_External\Lib\Storage; 28 29use Icewind\Streams\File; 30use phpseclib\Net\SSH2; 31 32class SFTPWriteStream implements File { 33 /** @var resource */ 34 public $context; 35 36 /** @var \phpseclib\Net\SFTP */ 37 private $sftp; 38 39 /** @var resource */ 40 private $handle; 41 42 /** @var int */ 43 private $internalPosition = 0; 44 45 /** @var int */ 46 private $writePosition = 0; 47 48 /** @var bool */ 49 private $eof = false; 50 51 private $buffer = ''; 52 53 public static function register($protocol = 'sftpwrite') { 54 if (in_array($protocol, stream_get_wrappers(), true)) { 55 return false; 56 } 57 return stream_wrapper_register($protocol, get_called_class()); 58 } 59 60 /** 61 * Load the source from the stream context and return the context options 62 * 63 * @param string $name 64 * @return array 65 * @throws \BadMethodCallException 66 */ 67 protected function loadContext($name) { 68 $context = stream_context_get_options($this->context); 69 if (isset($context[$name])) { 70 $context = $context[$name]; 71 } else { 72 throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); 73 } 74 if (isset($context['session']) and $context['session'] instanceof \phpseclib\Net\SFTP) { 75 $this->sftp = $context['session']; 76 } else { 77 throw new \BadMethodCallException('Invalid context, session not set'); 78 } 79 return $context; 80 } 81 82 public function stream_open($path, $mode, $options, &$opened_path) { 83 [, $path] = explode('://', $path); 84 $path = '/' . ltrim($path); 85 $path = str_replace('//', '/', $path); 86 87 $this->loadContext('sftp'); 88 89 if (!($this->sftp->bitmap & SSH2::MASK_LOGIN)) { 90 return false; 91 } 92 93 $remote_file = $this->sftp->_realpath($path); 94 if ($remote_file === false) { 95 return false; 96 } 97 98 $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_TRUNCATE, 0); 99 if (!$this->sftp->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { 100 return false; 101 } 102 103 $response = $this->sftp->_get_sftp_packet(); 104 switch ($this->sftp->packet_type) { 105 case NET_SFTP_HANDLE: 106 $this->handle = substr($response, 4); 107 break; 108 case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED 109 $this->sftp->_logError($response); 110 return false; 111 default: 112 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); 113 return false; 114 } 115 116 return true; 117 } 118 119 public function stream_seek($offset, $whence = SEEK_SET) { 120 return false; 121 } 122 123 public function stream_tell() { 124 return $this->writePosition; 125 } 126 127 public function stream_read($count) { 128 return false; 129 } 130 131 public function stream_write($data) { 132 $written = strlen($data); 133 $this->writePosition += $written; 134 135 $this->buffer .= $data; 136 137 if (strlen($this->buffer) > 64 * 1024) { 138 if (!$this->stream_flush()) { 139 return false; 140 } 141 } 142 143 return $written; 144 } 145 146 public function stream_set_option($option, $arg1, $arg2) { 147 return false; 148 } 149 150 public function stream_truncate($size) { 151 return false; 152 } 153 154 public function stream_stat() { 155 return false; 156 } 157 158 public function stream_lock($operation) { 159 return false; 160 } 161 162 public function stream_flush() { 163 $size = strlen($this->buffer); 164 $packet = pack('Na*N3a*', strlen($this->handle), $this->handle, $this->internalPosition / 4294967296, $this->internalPosition, $size, $this->buffer); 165 if (!$this->sftp->_send_sftp_packet(NET_SFTP_WRITE, $packet)) { 166 return false; 167 } 168 $this->internalPosition += $size; 169 $this->buffer = ''; 170 171 return $this->sftp->_read_put_responses(1); 172 } 173 174 public function stream_eof() { 175 return $this->eof; 176 } 177 178 public function stream_close() { 179 $this->stream_flush(); 180 if (!$this->sftp->_close_handle($this->handle)) { 181 return false; 182 } 183 } 184} 185