001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.commons.io.res.type.smb; 020 021import java.io.ByteArrayInputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.net.MalformedURLException; 026import java.net.URL; 027import java.util.Random; 028 029import jcifs.smb.NtlmPasswordAuthentication; 030import jcifs.smb.SmbException; 031import jcifs.smb.SmbFile; 032import jcifs.smb.SmbFileOutputStream; 033import lucee.commons.io.IOUtil; 034import lucee.commons.io.res.Resource; 035import lucee.commons.io.res.ResourceProvider; 036import lucee.commons.io.res.util.ResourceOutputStream; 037import lucee.commons.io.res.util.ResourceSupport; 038import lucee.commons.io.res.util.ResourceUtil; 039 040import org.apache.commons.io.IOUtils; 041import org.apache.commons.lang.StringUtils; 042 043public class SMBResource extends ResourceSupport implements Resource{ 044 045 private SMBResourceProvider provider; 046 private String path; 047 private NtlmPasswordAuthentication auth; 048 private SmbFile _smbFile; 049 private SmbFile _smbDir; 050 051 052 private SMBResource(SMBResourceProvider provider) { 053 this.provider = provider; 054 } 055 056 public SMBResource(SMBResourceProvider provider, String path) { 057 this(provider); 058 _init(_stripAuth(path), _extractAuth(path)); 059 } 060 061 public SMBResource(SMBResourceProvider provider, String path, NtlmPasswordAuthentication auth) { 062 this(provider); 063 _init(path, auth); 064 } 065 066 public SMBResource(SMBResourceProvider provider, String parent, String child) { 067 this(provider); 068 _init(ResourceUtil.merge(_stripAuth(parent), child), _extractAuth(parent)); 069 } 070 071 public SMBResource(SMBResourceProvider provider, String parent, String child, NtlmPasswordAuthentication auth) { 072 this(provider); 073 _init(ResourceUtil.merge(_stripAuth(parent), child), auth); 074 075 } 076 077 private void _init (String path, NtlmPasswordAuthentication auth ) { 078 //String[] pathName=ResourceUtil.translatePathName(path); 079 this.path = _stripScheme(path); 080 this.auth = auth; 081 082 } 083 084 private String _stripScheme(String path) { 085 return path.replace(_scheme(), "/"); 086 } 087 088 private String _userInfo (String path) { 089 090 try { 091 //use http scheme just so we can parse the url and get the user info out 092 String schemeless = _stripScheme(path); 093 schemeless = schemeless.replaceFirst("^/", ""); 094 String result = new URL("http://".concat(schemeless)).getUserInfo(); 095 return SMBResourceProvider.unencryptUserInfo(result); 096 } 097 catch (MalformedURLException e) { 098 return ""; 099 } 100 } 101 102 103 private static String _userInfo (NtlmPasswordAuthentication auth,boolean addAtSign) { 104 String result = ""; 105 if( auth != null) { 106 if( !StringUtils.isEmpty( auth.getDomain() ) ) { 107 result += auth.getDomain() + ";"; 108 } 109 if( !StringUtils.isEmpty( auth.getUsername() ) ) { 110 result += auth.getUsername() + ":"; 111 } 112 if( !StringUtils.isEmpty( auth.getPassword() ) ) { 113 result += auth.getPassword(); 114 } 115 if( addAtSign && !StringUtils.isEmpty( result ) ) { 116 result += "@"; 117 } 118 } 119 return result; 120 } 121 122 private NtlmPasswordAuthentication _extractAuth(String path) { 123 return new NtlmPasswordAuthentication( _userInfo(path) ); 124 } 125 126 private String _stripAuth(String path) { 127 return _calculatePath(path).replaceFirst(_scheme().concat("[^/]*@"),""); 128 } 129 130 private SmbFile _file() { 131 return _file(false); 132 } 133 134 private SmbFile _file( boolean expectDirectory ) { 135 String _path = _calculatePath(getInnerPath()); 136 SmbFile result; 137 if(expectDirectory) { 138 if(!_path.endsWith("/")) _path += "/"; 139 if(_smbDir == null) { 140 _smbDir = provider.getFile(_path,auth); 141 } 142 result = _smbDir; 143 } else { 144 if(_smbFile == null) { 145 _smbFile = provider.getFile(_path,auth); 146 } 147 result = _smbFile; 148 } 149 return result; 150 } 151 152 private String _calculatePath(String path) { 153 return _calculatePath(path,null); 154 } 155 156 private String _calculatePath(String path, NtlmPasswordAuthentication auth) { 157 158 if ( !path.startsWith( _scheme() ) ) { 159 if(path.startsWith("/") || path.startsWith("\\")) { 160 path = path.substring(1); 161 } 162 if (auth != null) { 163 path = SMBResourceProvider.encryptUserInfo(_userInfo(auth,false)).concat("@").concat(path); 164 } 165 path = _scheme().concat( path ); 166 } 167 return path; 168 } 169 170 private String _scheme() { 171 return provider.getScheme().concat("://"); 172 173 } 174 175 @Override 176 public boolean isReadable() { 177 SmbFile file = _file(); 178 try { 179 return file != null && file.canRead(); 180 } 181 catch (SmbException e) { 182 return false; 183 } 184 } 185 186 @Override 187 public boolean isWriteable() { 188 SmbFile file = _file(); 189 if(file == null) return false; 190 try { 191 if(file.canWrite()) return true; 192 } 193 catch (SmbException e1) { 194 return false; 195 } 196 197 try { 198 if (file.getType() == SmbFile.TYPE_SHARE) { 199 // canWrite() doesn't work on shares. always returns false even if you can truly write, test this by opening a file on the share 200 201 SmbFile testFile = _getTempFile(file,auth); 202 if (testFile == null) return false; 203 if (testFile.canWrite()) return true; 204 205 OutputStream os=null; 206 try { 207 os = testFile.getOutputStream(); 208 } 209 catch (IOException e) { 210 return false; 211 } 212 finally { 213 if (os != null) IOUtils.closeQuietly(os); 214 testFile.delete(); 215 } 216 return true; 217 218 } 219 return file.canWrite(); 220 } 221 catch (SmbException e) { 222 return false; 223 } 224 } 225 226 private SmbFile _getTempFile(SmbFile directory, NtlmPasswordAuthentication auth) throws SmbException { 227 if (!directory.isDirectory()) return null; 228 Random r = new Random(); 229 230 SmbFile result = provider.getFile(directory.getCanonicalPath() + "/write-test-file.unknown." + r.nextInt(), auth); 231 if (result.exists()) return _getTempFile(directory,auth); //try again 232 return result; 233 } 234 235 @Override 236 public void remove(boolean alsoRemoveChildren) throws IOException { 237 if(alsoRemoveChildren)ResourceUtil.removeChildren(this); 238 239 _delete(); 240 } 241 242 private void _delete() throws IOException{ 243 provider.lock(this); 244 try { 245 SmbFile file = _file(); 246 if (file == null) throw new IOException("Can't delete [" + getPath() + "], SMB path is invalid or inaccessable"); 247 if (file.isDirectory()) { 248 file = _file(true); 249 } 250 file.delete(); 251 } catch (SmbException e) { 252 throw new IOException(e);// for cfcatch type="java.io.IOException" 253 } finally { 254 provider.unlock(this); 255 } 256 } 257 258 @Override 259 public boolean exists() { 260 SmbFile file = _file(); 261 try { 262 return file != null && file.exists(); 263 } 264 catch (SmbException e) { 265 return false; 266 } 267 } 268 269 @Override 270 public String getName() { 271 SmbFile file = _file(); 272 if(file == null) 273 return ""; 274 return file.getName().replaceFirst("/$", ""); //remote trailing slash for directories 275 } 276 277 @Override 278 public String getParent() { 279 // SmbFile's getParent function seems to return just smb:// no matter what, implement custom getParent Function() 280 String path = getPath().replaceFirst("[\\\\/]+$", ""); 281 282 int location = Math.max(path.lastIndexOf('/'),path.lastIndexOf('\\')); 283 if (location == -1 || location == 0) return ""; 284 return path.substring(0,location); 285 } 286 287 @Override 288 public Resource getParentResource() { 289 String p = getParent(); 290 if(p==null) return null; 291 return new SMBResource(provider,_stripAuth(p),auth); 292 } 293 294 @Override 295 public Resource getRealResource(String realpath) { 296 realpath=ResourceUtil.merge(getInnerPath() +"/", realpath); 297 298 if(realpath.startsWith("../"))return null; 299 return new SMBResource( provider, _calculatePath(realpath,auth), auth ); 300 } 301 302 private String getInnerPath() { 303 if(path==null) return "/"; 304 return path; 305 } 306 307 @Override 308 public String getPath() { 309 return _calculatePath(path,auth); 310 } 311 312 @Override 313 public boolean isAbsolute() { 314 return _file() != null; 315 } 316 317 @Override 318 public boolean isDirectory() { 319 SmbFile file = _file(); 320 try { 321 return file != null && _file().isDirectory(); 322 } 323 catch (SmbException e) { 324 return false; 325 } 326 } 327 328 @Override 329 public boolean isFile() { 330 SmbFile file = _file(); 331 try { 332 return file != null && file.isFile(); 333 } 334 catch (SmbException e) { 335 return false; 336 } 337 } 338 339 @Override 340 public boolean isHidden() { 341 return _isFlagSet(_file(), SmbFile.ATTR_HIDDEN); 342 } 343 344 @Override 345 public boolean isArchive() { 346 return _isFlagSet(_file(), SmbFile.ATTR_ARCHIVE); 347 } 348 349 @Override 350 public boolean isSystem() { 351 return _isFlagSet(_file(), SmbFile.ATTR_SYSTEM); 352 } 353 354 private boolean _isFlagSet(SmbFile file, int flag) { 355 if (file == null) return false; 356 try { 357 return (file.getAttributes() & flag) == flag; 358 } 359 catch (SmbException e) { 360 return false; 361 } 362 } 363 364 @Override 365 public long lastModified() { 366 SmbFile file = _file(); 367 if (file == null) return 0; 368 try { 369 return file.lastModified(); 370 } 371 catch (SmbException e) { 372 return 0; 373 } 374 375 } 376 377 @Override 378 public long length() { 379 SmbFile file = _file(); 380 if (file == null) return 0; 381 try { 382 return file.length(); 383 } 384 catch (SmbException e) { 385 return 0; 386 } 387 } 388 389 @Override 390 public Resource[] listResources() { 391 if(isFile()) return null; 392 try { 393 SmbFile dir = _file(true); 394 SmbFile[] files = dir.listFiles(); 395 396 Resource[] result = new Resource[files.length]; 397 for(int i = 0; i < files.length ; i++) { 398 SmbFile file = files[i]; 399 result[i] = new SMBResource(provider,file.getCanonicalPath(),auth); 400 } 401 return result; 402 } 403 catch (SmbException e) { 404 return new Resource[0]; 405 } 406 407 408 } 409 410 @Override 411 public boolean setLastModified(long time){ 412 SmbFile file = _file(); 413 if (file == null) return false; 414 try { 415 provider.lock(this); 416 file.setLastModified(time); 417 } 418 catch (SmbException e) { 419 return false; 420 } 421 catch (IOException e) { 422 return false; 423 } finally { 424 provider.unlock(this); 425 } 426 return true; 427 } 428 429 @Override 430 public boolean setWritable(boolean writable) { 431 SmbFile file = _file(); 432 if( file == null) return false; 433 try { 434 setAttribute((short)SmbFile.ATTR_READONLY, !writable); 435 } 436 catch (IOException e1) { 437 return false; 438 } 439 return true; 440 441 } 442 443 @Override 444 public boolean setReadable(boolean readable) { 445 return setWritable(!readable); 446 } 447 448 @Override 449 public void createFile(boolean createParentWhenNotExists) throws IOException { 450 try { 451 452 ResourceUtil.checkCreateFileOK(this, createParentWhenNotExists); 453 //client.unregisterFTPFile(this); 454 IOUtil.copy(new ByteArrayInputStream(new byte[0]), getOutputStream(), true, true); 455 } catch (SmbException e) { 456 throw new IOException(e); // for cfcatch type="java.io.IOException" 457 } 458 459 } 460 461 @Override 462 public void createDirectory(boolean createParentWhenNotExists) throws IOException { 463 SmbFile file= _file(true); 464 if (file == null) throw new IOException("SMBFile is inaccessible"); 465 ResourceUtil.checkCreateDirectoryOK(this, createParentWhenNotExists); 466 try { 467 provider.lock(this); 468 file.mkdir(); 469 } catch (SmbException e) { 470 throw new IOException(e); // for cfcatch type="java.io.IOException" 471 } 472 finally { 473 provider.unlock(this); 474 } 475 476 } 477 478 @Override 479 public InputStream getInputStream() throws IOException { 480 try { 481 return _file().getInputStream(); 482 } catch (SmbException e) { 483 throw new IOException(e);// for cfcatch type="java.io.IOException" 484 } 485 } 486 487 @Override 488 public OutputStream getOutputStream(boolean append) throws IOException { 489 ResourceUtil.checkGetOutputStreamOK(this); 490 try { 491 provider.lock(this); 492 SmbFile file =_file(); 493 OutputStream os = new SmbFileOutputStream(file, append); 494 return IOUtil.toBufferedOutputStream(new ResourceOutputStream(this,os)); 495 } 496 catch (IOException e) { 497 provider.unlock(this); 498 throw new IOException(e);// just in case it is an SmbException too... for cfcatch type="java.io.IOException" 499 } 500 } 501 502 @Override 503 public ResourceProvider getResourceProvider() { 504 return provider; 505 } 506 507 @Override 508 public int getMode() { 509 return 0; 510 } 511 512 @Override 513 public void setMode(int mode) throws IOException { 514 // TODO 515 } 516 517 @Override 518 public void setHidden(boolean value) throws IOException { 519 setAttribute((short)SmbFile.ATTR_SYSTEM, value); 520 } 521 522 @Override 523 public void setSystem(boolean value) throws IOException { 524 setAttribute((short)SmbFile.ATTR_SYSTEM, value); 525 } 526 527 @Override 528 public void setArchive(boolean value) throws IOException { 529 setAttribute((short)SmbFile.ATTR_ARCHIVE, value); 530 } 531 532 @Override 533 public void setAttribute(short attribute, boolean value) throws IOException { 534 int newAttribute = _lookupAttribute(attribute); 535 SmbFile file = _file(); 536 if (file == null) throw new IOException("SMB File is not valid"); 537 try { 538 provider.lock(this); 539 int atts = file.getAttributes(); 540 if (value) { 541 atts = atts | newAttribute; 542 } else { 543 atts = atts & (~newAttribute); 544 } 545 file.setAttributes(atts); 546 } catch (SmbException e) { 547 throw new IOException(e); // for cfcatch type="java.io.IOException" 548 } finally { 549 provider.unlock(this); 550 } 551 } 552 553 @Override 554 public void moveTo(Resource dest) throws IOException { 555 try { 556 if(dest instanceof SMBResource) { 557 SMBResource destination = (SMBResource)dest; 558 SmbFile file = _file(); 559 file.renameTo(destination._file()); 560 } else { 561 ResourceUtil.moveTo(this, dest,false); 562 } 563 } catch (SmbException e) { 564 throw new IOException(e); // for cfcatch type="java.io.IOException" 565 } 566 } 567 568 @Override 569 public boolean getAttribute(short attribute) { 570 try { 571 int newAttribute = _lookupAttribute(attribute); 572 return (_file().getAttributes() & newAttribute) != 0; 573 } 574 catch (SmbException e) { 575 return false; 576 } 577 578 } 579 580 public SmbFile getSmbFile() { 581 return _file(); 582 } 583 584 private int _lookupAttribute(short attribute) { 585 int result = attribute; 586 switch (attribute) { 587 case Resource.ATTRIBUTE_ARCHIVE: 588 result = SmbFile.ATTR_ARCHIVE; 589 break; 590 case Resource.ATTRIBUTE_SYSTEM: 591 result = SmbFile.ATTR_SYSTEM; 592 break; 593 case Resource.ATTRIBUTE_HIDDEN: 594 result = SmbFile.ATTR_HIDDEN; 595 break; 596 } 597 return result; 598 } 599 600}