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}