You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

smb.php 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <?php
  2. /**
  3. * @author Arthur Schiwon <blizzz@owncloud.com>
  4. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  5. * @author Michael Gapczynski <GapczynskiM@gmail.com>
  6. * @author Morris Jobke <hey@morrisjobke.de>
  7. * @author Philipp Kapfer <philipp.kapfer@gmx.at>
  8. * @author Robin Appelman <icewind@owncloud.com>
  9. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  10. * @author Thomas Müller <thomas.mueller@tmit.eu>
  11. * @author Vincent Petry <pvince81@owncloud.com>
  12. *
  13. * @copyright Copyright (c) 2015, ownCloud, Inc.
  14. * @license AGPL-3.0
  15. *
  16. * This code is free software: you can redistribute it and/or modify
  17. * it under the terms of the GNU Affero General Public License, version 3,
  18. * as published by the Free Software Foundation.
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU Affero General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU Affero General Public License, version 3,
  26. * along with this program. If not, see <http://www.gnu.org/licenses/>
  27. *
  28. */
  29. namespace OC\Files\Storage;
  30. use Icewind\SMB\Exception\Exception;
  31. use Icewind\SMB\Exception\NotFoundException;
  32. use Icewind\SMB\NativeServer;
  33. use Icewind\SMB\Server;
  34. use Icewind\Streams\CallbackWrapper;
  35. use Icewind\Streams\IteratorDirectory;
  36. use OC\Files\Filesystem;
  37. class SMB extends Common {
  38. /**
  39. * @var \Icewind\SMB\Server
  40. */
  41. protected $server;
  42. /**
  43. * @var \Icewind\SMB\Share
  44. */
  45. protected $share;
  46. /**
  47. * @var \Icewind\SMB\FileInfo[]
  48. */
  49. protected $statCache = array();
  50. public function __construct($params) {
  51. if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) {
  52. if (Server::NativeAvailable()) {
  53. $this->server = new NativeServer($params['host'], $params['user'], $params['password']);
  54. } else {
  55. $this->server = new Server($params['host'], $params['user'], $params['password']);
  56. }
  57. $this->share = $this->server->getShare(trim($params['share'], '/'));
  58. $this->root = isset($params['root']) ? $params['root'] : '/';
  59. if (!$this->root || $this->root[0] != '/') {
  60. $this->root = '/' . $this->root;
  61. }
  62. if (substr($this->root, -1, 1) != '/') {
  63. $this->root .= '/';
  64. }
  65. } else {
  66. throw new \Exception('Invalid configuration');
  67. }
  68. }
  69. /**
  70. * @return string
  71. */
  72. public function getId() {
  73. return 'smb::' . $this->server->getUser() . '@' . $this->server->getHost() . '/' . $this->share->getName() . '/' . $this->root;
  74. }
  75. /**
  76. * @param string $path
  77. * @return string
  78. */
  79. protected function buildPath($path) {
  80. return Filesystem::normalizePath($this->root . '/' . $path);
  81. }
  82. /**
  83. * @param string $path
  84. * @return \Icewind\SMB\IFileInfo
  85. */
  86. protected function getFileInfo($path) {
  87. $path = $this->buildPath($path);
  88. if (!isset($this->statCache[$path])) {
  89. $this->statCache[$path] = $this->share->stat($path);
  90. }
  91. return $this->statCache[$path];
  92. }
  93. /**
  94. * @param string $path
  95. * @return \Icewind\SMB\IFileInfo[]
  96. */
  97. protected function getFolderContents($path) {
  98. $path = $this->buildPath($path);
  99. $files = $this->share->dir($path);
  100. foreach ($files as $file) {
  101. $this->statCache[$path . '/' . $file->getName()] = $file;
  102. }
  103. return $files;
  104. }
  105. /**
  106. * @param \Icewind\SMB\IFileInfo $info
  107. * @return array
  108. */
  109. protected function formatInfo($info) {
  110. return array(
  111. 'size' => $info->getSize(),
  112. 'mtime' => $info->getMTime()
  113. );
  114. }
  115. /**
  116. * @param string $path
  117. * @return array
  118. */
  119. public function stat($path) {
  120. return $this->formatInfo($this->getFileInfo($path));
  121. }
  122. /**
  123. * @param string $path
  124. * @return bool
  125. */
  126. public function unlink($path) {
  127. try {
  128. if ($this->is_dir($path)) {
  129. return $this->rmdir($path);
  130. } else {
  131. $path = $this->buildPath($path);
  132. unset($this->statCache[$path]);
  133. $this->share->del($path);
  134. return true;
  135. }
  136. } catch (NotFoundException $e) {
  137. return false;
  138. }
  139. }
  140. /**
  141. * check if a file or folder has been updated since $time
  142. *
  143. * @param string $path
  144. * @param int $time
  145. * @return bool
  146. */
  147. public function hasUpdated($path, $time) {
  148. if (!$path and $this->root == '/') {
  149. // mtime doesn't work for shares, but giving the nature of the backend,
  150. // doing a full update is still just fast enough
  151. return true;
  152. } else {
  153. $actualTime = $this->filemtime($path);
  154. return $actualTime > $time;
  155. }
  156. }
  157. /**
  158. * @param string $path
  159. * @param string $mode
  160. * @return resource
  161. */
  162. public function fopen($path, $mode) {
  163. $fullPath = $this->buildPath($path);
  164. try {
  165. switch ($mode) {
  166. case 'r':
  167. case 'rb':
  168. if (!$this->file_exists($path)) {
  169. return false;
  170. }
  171. return $this->share->read($fullPath);
  172. case 'w':
  173. case 'wb':
  174. return $this->share->write($fullPath);
  175. case 'a':
  176. case 'ab':
  177. case 'r+':
  178. case 'w+':
  179. case 'wb+':
  180. case 'a+':
  181. case 'x':
  182. case 'x+':
  183. case 'c':
  184. case 'c+':
  185. //emulate these
  186. if (strrpos($path, '.') !== false) {
  187. $ext = substr($path, strrpos($path, '.'));
  188. } else {
  189. $ext = '';
  190. }
  191. if ($this->file_exists($path)) {
  192. if (!$this->isUpdatable($path)) {
  193. return false;
  194. }
  195. $tmpFile = $this->getCachedFile($path);
  196. } else {
  197. if (!$this->isCreatable(dirname($path))) {
  198. return false;
  199. }
  200. $tmpFile = \OCP\Files::tmpFile($ext);
  201. }
  202. $source = fopen($tmpFile, $mode);
  203. $share = $this->share;
  204. return CallBackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
  205. $share->put($tmpFile, $fullPath);
  206. unlink($tmpFile);
  207. });
  208. }
  209. return false;
  210. } catch (NotFoundException $e) {
  211. return false;
  212. }
  213. }
  214. public function rmdir($path) {
  215. try {
  216. $this->statCache = array();
  217. $content = $this->share->dir($this->buildPath($path));
  218. foreach ($content as $file) {
  219. if ($file->isDirectory()) {
  220. $this->rmdir($path . '/' . $file->getName());
  221. } else {
  222. $this->share->del($file->getPath());
  223. }
  224. }
  225. $this->share->rmdir($this->buildPath($path));
  226. return true;
  227. } catch (NotFoundException $e) {
  228. return false;
  229. }
  230. }
  231. public function touch($path, $time = null) {
  232. if (!$this->file_exists($path)) {
  233. $fh = $this->share->write($this->buildPath($path));
  234. fclose($fh);
  235. return true;
  236. }
  237. return false;
  238. }
  239. public function opendir($path) {
  240. $files = $this->getFolderContents($path);
  241. $names = array_map(function ($info) {
  242. /** @var \Icewind\SMB\IFileInfo $info */
  243. return $info->getName();
  244. }, $files);
  245. return IteratorDirectory::wrap($names);
  246. }
  247. public function filetype($path) {
  248. try {
  249. return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
  250. } catch (NotFoundException $e) {
  251. return false;
  252. }
  253. }
  254. public function mkdir($path) {
  255. $path = $this->buildPath($path);
  256. try {
  257. $this->share->mkdir($path);
  258. return true;
  259. } catch (Exception $e) {
  260. return false;
  261. }
  262. }
  263. public function file_exists($path) {
  264. try {
  265. $this->getFileInfo($path);
  266. return true;
  267. } catch (NotFoundException $e) {
  268. return false;
  269. }
  270. }
  271. /**
  272. * check if smbclient is installed
  273. */
  274. public static function checkDependencies() {
  275. $smbClientExists = (bool)\OC_Helper::findBinaryPath('smbclient');
  276. return $smbClientExists ? true : array('smbclient');
  277. }
  278. }