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.ftp;
020
021
022import java.io.ByteArrayInputStream;
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.OutputStream;
026import java.util.ArrayList;
027import java.util.Calendar;
028import java.util.List;
029
030import lucee.commons.date.JREDateTimeUtil;
031import lucee.commons.io.IOUtil;
032import lucee.commons.io.ModeUtil;
033import lucee.commons.io.res.Resource;
034import lucee.commons.io.res.ResourceProvider;
035import lucee.commons.io.res.util.ResourceSupport;
036import lucee.commons.io.res.util.ResourceUtil;
037import lucee.commons.lang.StringUtil;
038import lucee.runtime.PageContext;
039import lucee.runtime.engine.ThreadLocalPageContext;
040import lucee.runtime.op.Caster;
041
042import org.apache.commons.net.ftp.FTP;
043import org.apache.commons.net.ftp.FTPFile;
044
045public final class FTPResource extends ResourceSupport {
046
047
048        private final FTPResourceProvider provider;
049        private final String path;
050        private final String name;
051        private final FTPConnectionData data;
052        
053
054        
055        /**
056         * Constructor of the class
057         * @param factory
058         * @param data 
059         * @param path
060         */
061         FTPResource(FTPResourceProvider provider, FTPConnectionData data, String path) {
062                this.provider=provider;
063                this.data=data;
064                
065                String[] pathName=ResourceUtil.translatePathName(path);
066                this.path=pathName[0];
067                this.name=pathName[1];
068        }
069
070         /**
071         * Constructor of the class
072         * @param factory
073         * @param data 
074         * @param path
075         */
076         private FTPResource(FTPResourceProvider provider, FTPConnectionData data, String path,String name) {
077                this.provider=provider;
078                this.data=data;
079                this.path=path;
080                this.name=name;
081        }
082
083        @Override
084        public boolean isReadable() {
085                Boolean rtn = hasPermission(FTPFile.READ_PERMISSION);
086                if(rtn==null) return false;
087                return rtn.booleanValue();
088        }
089
090        public boolean isWriteable() {
091                Boolean rtn = hasPermission(FTPFile.WRITE_PERMISSION);
092                if(rtn==null) return false;
093                return rtn.booleanValue();
094        }
095
096        private Boolean hasPermission(int permission) {
097                FTPResourceClient client=null;
098                try {
099                        provider.read(this);
100                        client=provider.getClient(data);
101                        FTPFile file=client.getFTPFile(this);
102                        if(file==null) return null;
103                        return  Caster.toBoolean(file.hasPermission(FTPFile.USER_ACCESS,permission) || 
104                                        file.hasPermission(FTPFile.GROUP_ACCESS,permission) || 
105                                        file.hasPermission(FTPFile.WORLD_ACCESS,permission));
106                } 
107                catch (IOException e) {
108                        return Boolean.FALSE;
109                }
110                finally {
111                        provider.returnClient(client);
112                }
113        }
114        
115        @Override
116        public void remove(boolean alsoRemoveChildren) throws IOException {
117                if(isRoot()) throw new FTPResoucreException("can't delete root of ftp server");
118                
119                if(alsoRemoveChildren)ResourceUtil.removeChildren(this);
120                FTPResourceClient client=null;
121                try {
122                        provider.lock(this);
123                        client = provider.getClient(data);
124                        boolean result = client.deleteFile(getInnerPath());
125                        if(!result) throw new IOException("can't delete file ["+getPath()+"]");
126                }
127                finally {
128                        provider.returnClient(client);
129                        provider.unlock(this);
130                }
131        }
132
133        @Override
134        public boolean delete() {
135                if(isRoot()) return false;
136                FTPResourceClient client = null;
137                try {
138                        provider.lock(this);
139                        client = provider.getClient(data);
140                        return client.deleteFile(getInnerPath());
141                } 
142                catch (IOException e) {
143                        return false;
144                }
145                finally {
146                        provider.returnClient(client);
147                        provider.unlock(this);
148                }
149        }
150
151        @Override
152        public boolean exists() {
153                try {
154                        provider.read(this);
155                } catch (IOException e) {
156                        return true;
157                }
158                FTPResourceClient client = null;
159                InputStream is=null;
160                try {
161//                       getClient muss zuerst sein so wird verbindung geprueft
162                        client = provider.getClient(data);
163                        if(isRoot()) return true;
164
165                        FTPFile file = client.getFTPFile(this);
166                        if(file!=null) {
167                                return !file.isUnknown();
168                        }
169                        
170                        //String pathname = getInnerPath();
171                        String p = getInnerPath();
172                        if(!StringUtil.endsWith(p, '/'))p+="/";
173                        if(client.listNames(p)!=null) return true;
174                        return false;
175                } 
176                catch (IOException e) {
177                        return false;
178                }
179                finally {
180                        IOUtil.closeEL(is);
181                        provider.returnClient(client);
182                }
183        }
184
185        @Override
186        public String getName() {
187                return name;
188        }
189
190        @Override
191        public String getParent() {
192                if(isRoot()) return null;
193                return provider.getScheme().concat("://").concat(data.key()).concat(path.substring(0,path.length()-1));
194        } 
195        
196        public String getInnerParent() {
197                return path;
198        }
199
200        @Override
201        public Resource getParentResource() {
202                if(isRoot()) return null;
203                return new FTPResource(provider,data,path);
204        }
205
206        @Override
207        public Resource getRealResource(String realpath) {
208                realpath=ResourceUtil.merge(getInnerPath(), realpath);
209                if(realpath.startsWith("../"))return null;
210                return new FTPResource(provider,data,realpath);
211        }
212
213        @Override
214        public String getPath() {       
215                return provider.getScheme().concat("://").concat(data.key()).concat(path).concat(name);
216        }
217        /**
218         * @return returns path starting from ftp root
219         */
220        String getInnerPath() { 
221                return path.concat(name);
222        }
223
224        @Override
225        public boolean isAbsolute() {
226                // TODO impl isAbolute
227                return true;
228        }
229
230        @Override
231        public boolean isDirectory() {
232                try {
233                        provider.read(this);
234                } 
235                catch (IOException e1) {
236                        return false;
237                }
238                FTPResourceClient client=null;
239                try {
240                        // getClient muss zuerst sein so wird verbindung geprueft
241                        client = provider.getClient(data);
242                        if(isRoot())return true;
243
244                        FTPFile file = client.getFTPFile(this);
245                        if(file!=null) {
246                                return file.isDirectory();
247                        }
248                        //if(file==null) return false;
249                        //return file.isDirectory();
250                        
251                        String p = getInnerPath();
252                        if(!StringUtil.endsWith(p, '/'))p+="/";
253                        return client.listNames(p)!=null;
254                        
255                } 
256                catch (IOException e) {
257                        return false;
258                }
259                finally {
260                        provider.returnClient(client);
261                }
262        }
263
264        @Override
265        public boolean isFile() {
266                if(isRoot()) return false;
267                try {
268                        provider.read(this);
269                } 
270                catch (IOException e1) {
271                        return false;
272                }
273                FTPResourceClient client=null;
274                InputStream is=null;
275                try {
276                        client = provider.getClient(data);
277                        FTPFile file = client.getFTPFile(this);
278                        if(file!=null) {
279                                return file.isFile();
280                        }
281                        return false;
282                        //String pathname = getInnerPath();
283                        //return (is=client.retrieveFileStream(pathname))!=null;
284                } 
285                        
286                        
287
288                catch (IOException e) {
289                        return false;
290                }
291                finally {
292                        IOUtil.closeEL(is);
293                        provider.returnClient(client);
294                }
295        }
296
297        @Override
298        public long lastModified() {
299                //if(isRoot()) return 0;
300                
301                FTPResourceClient client=null;
302                try {
303                        provider.read(this);
304                        client=provider.getClient(data);
305                        FTPFile file = client.getFTPFile(this);
306                        if(file==null) return 0;
307                        return file.getTimestamp().getTimeInMillis();
308                }
309                catch (IOException e) {
310                        return 0;
311                }
312                finally {
313                        provider.returnClient(client);
314                }
315        }
316
317        @Override
318        public long length() {
319                if(isRoot()) return 0;
320                FTPResourceClient client=null;
321                try {
322                        provider.read(this);
323                        client = provider.getClient(data);
324                        FTPFile file = client.getFTPFile(this);
325                        if(file==null) return 0;
326                        return file.getSize();
327                } 
328                catch (IOException e) {
329                        return 0;
330                }
331                finally {
332                        provider.returnClient(client);
333                }
334        }
335
336        @Override
337        public Resource[] listResources() {
338                if(isFile()) return null;//new Resource[0];
339                
340                FTPResourceClient client=null;
341                try {
342                        client = provider.getClient(data);
343                        FTPFile[] files=null;
344                        String p = getInnerPath();
345                        if(!StringUtil.endsWith(p, '/'))p+="/";
346                        files=client.listFiles(p);
347                        if(files==null) return new Resource[0];
348                        
349                        List list=new ArrayList();
350                        String parent=path.concat(name).concat("/");
351                        String name;
352                        FTPResource res;
353                    for(int i=0;i<files.length;i++) {
354                        name=files[i].getName();
355                        if(!".".equals(name) && !"..".equals(name)) {
356                                res=new FTPResource(provider,data,parent,name);
357                                client.registerFTPFile(res, files[i]);
358                                list.add(res);
359                        }
360                    }
361                        return (Resource[]) list.toArray(new FTPResource[list.size()]);
362                }
363                catch(IOException ioe) {
364                        return null;
365                }
366                finally {
367                        provider.returnClient(client);
368                }
369        }
370
371        @Override
372        public boolean setLastModified(long time) {
373                //if(isRoot()) return false;
374                
375                FTPResourceClient client=null;
376                try {
377                        provider.lock(this);
378                        client=provider.getClient(data);
379                        
380                        PageContext pc = ThreadLocalPageContext.get();
381                        Calendar c=JREDateTimeUtil.getThreadCalendar();
382                        if(pc!=null)c.setTimeZone(pc.getTimeZone());
383                        c.setTimeInMillis(time);
384                        FTPFile file = client.getFTPFile(this);
385                        if(file==null) return false;
386                        file.setTimestamp(c);
387                        client.unregisterFTPFile(this);
388                        return true;
389                }
390                catch (IOException e) {}
391                finally {
392                        provider.returnClient(client);
393                        provider.unlock(this);
394                }
395
396                return false;
397        }
398
399        public boolean setReadOnly() {
400                try {
401                        setMode(ModeUtil.setWritable(getMode(), false));
402                        return true;
403                } catch (IOException e) {
404                        return false;
405                }
406        }
407
408        @Override
409        public void createFile(boolean createParentWhenNotExists) throws IOException {
410                ResourceUtil.checkCreateFileOK(this, createParentWhenNotExists);
411                //client.unregisterFTPFile(this);
412                IOUtil.copy(new ByteArrayInputStream(new byte[0]), getOutputStream(), true, true);
413        }
414        
415        @Override
416        public void moveTo(Resource dest) throws IOException {
417                FTPResourceClient client=null;
418                ResourceUtil.checkMoveToOK(this, dest);
419                try {
420                        provider.lock(this);
421                        client = provider.getClient(data);
422                        
423                        client.unregisterFTPFile(this);
424                        
425                        if(dest instanceof FTPResource) moveTo(client,(FTPResource)dest);
426                        else super.moveTo(dest);
427                        
428                }
429                finally {
430                        provider.returnClient(client);
431                        provider.unlock(this);
432                }
433        }
434
435        private void moveTo(FTPResourceClient client, FTPResource dest) throws IOException {
436                if(!dest.data.equals(data)) {
437                        super.moveTo(dest);
438                        return;
439                }
440                if(dest.exists())dest.delete();
441                
442                client.unregisterFTPFile(dest);
443                boolean ok = client.rename(getInnerPath(), dest.getInnerPath());
444                if(!ok) throw new IOException("can't create file "+this);
445                
446        }
447
448        @Override
449        public void createDirectory(boolean createParentWhenNotExists) throws IOException {
450                ResourceUtil.checkCreateDirectoryOK(this, createParentWhenNotExists);
451                
452                FTPResourceClient client=null;
453                try {
454                        provider.lock(this);
455                        client = provider.getClient(data);
456                        client.unregisterFTPFile(this);
457                        boolean ok = client.makeDirectory(getInnerPath());
458                        if(!ok) throw new IOException("can't create file "+this);
459                        
460                }
461                finally {
462                        provider.returnClient(client);
463                        provider.unlock(this);
464                }
465        }
466
467        @Override
468        public InputStream getInputStream() throws IOException {
469                ResourceUtil.checkGetInputStreamOK(this);
470                provider.lock(this);
471                FTPResourceClient client=provider.getClient(data);
472                client.setFileType(FTP.BINARY_FILE_TYPE);
473                try {
474                        return IOUtil.toBufferedInputStream(new FTPResourceInputStream(client,this,client.retrieveFileStream(getInnerPath())));
475                } 
476                catch (IOException e) {
477                        provider.returnClient(client);
478                        provider.unlock(this);
479                        throw e;
480                }
481        }
482
483        @Override
484        public OutputStream getOutputStream(boolean append) throws IOException {
485                ResourceUtil.checkGetOutputStreamOK(this);
486                FTPResourceClient client=null;
487                try {
488                        provider.lock(this);
489                        client=provider.getClient(data);
490                        client.unregisterFTPFile(this);
491                        client.setFileType(FTP.BINARY_FILE_TYPE);
492                        OutputStream os = append?client.appendFileStream(getInnerPath()):client.storeFileStream(getInnerPath());
493                        if(os==null)throw new IOException("can not open stream to file ["+this+"]");
494                        
495                        return IOUtil.toBufferedOutputStream(new FTPResourceOutputStream(client,this,os));
496                }
497                catch (IOException e) {
498                        provider.returnClient(client);
499                        provider.unlock(this);
500                        throw e;
501                }
502        }
503
504
505        @Override
506        public String[] list() {
507                if(isFile()) return new String[0];
508                
509                FTPResourceClient client=null;
510                try {
511                        client = provider.getClient(data);
512                        String[] files=null;
513                        
514                        String p = getInnerPath();
515                        if(!StringUtil.endsWith(p, '/'))p+="/";
516                        files=client.listNames(p);
517                        if(files==null) return new String[0];
518                        for(int i=0;i<files.length;i++) {
519                                files[i]=cutName(files[i]);
520                    }
521                        return files;
522                }
523                catch(IOException ioe) {
524                        return null;
525                }
526                finally {
527                        provider.returnClient(client);
528                }
529        }
530
531        private String cutName(String path) {
532                int index=path.lastIndexOf('/');
533                if(index==-1) return path;
534                return path.substring(index+1);
535        }
536
537        @Override
538        public ResourceProvider getResourceProvider() {
539                return provider;
540        }
541        
542        public FTPResourceProvider getFTPResourceProvider() {
543                return provider;
544        }
545
546        
547        boolean isRoot() {
548                return StringUtil.isEmpty(name);
549        }
550
551        public int getMode() {
552                //if(isRoot()) return 0;
553                
554                FTPResourceClient client=null;
555                try {
556                        provider.read(this);
557                        client=provider.getClient(data);
558                        
559                        FTPFile file = client.getFTPFile(this);
560                        int mode=0;
561                        if(file==null)return 0;
562                        
563                        // World
564                        if(file.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION))        mode+=01;
565                        if(file.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION))          mode+=02;
566                        if(file.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION))           mode+=04;
567
568                        // Group
569                        if(file.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION))        mode+=010;
570                        if(file.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION))          mode+=020;
571                        if(file.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION))           mode+=040;
572
573                        // Owner
574                        if(file.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION))         mode+=0100;
575                        if(file.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION))           mode+=0200;
576                        if(file.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION))            mode+=0400;
577                        
578                        return mode;
579                        
580                }
581                catch (IOException e) {}
582                finally {
583                        provider.returnClient(client);
584                }
585
586                return 0;
587        }
588
589        public void setMode(int mode) throws IOException {
590                //if(isRoot()) throw new IOException("can't change mode of root");
591        
592                FTPResourceClient client=null;
593                try {
594                        provider.lock(this);
595                        client=provider.getClient(data);
596                        
597                        FTPFile file = client.getFTPFile(this);
598                        if(file!=null) {
599                                // World
600                                file.setPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION,(mode&01)>0);
601                                file.setPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION,(mode&02)>0);
602                                file.setPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION,(mode&04)>0);
603        
604                                // Group
605                                file.setPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION,(mode&010)>0);
606                                file.setPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION,(mode&020)>0);
607                                file.setPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION,(mode&040)>0);
608        
609                                // Owner
610                                file.setPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION,(mode&0100)>0);
611                                file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION,(mode&0200)>0);
612                                file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION,(mode&0400)>0);
613        
614                                client.unregisterFTPFile(this);
615                        }
616                }
617                catch (IOException e) {}
618                finally {
619                        provider.returnClient(client);
620                        provider.unlock(this);
621                }
622
623        }
624
625        public boolean setReadable(boolean value) {
626                try {
627                        setMode(ModeUtil.setReadable(getMode(), value));
628                        return true;
629                } catch (IOException e) {
630                        return false;
631                }
632        }
633
634        public boolean setWritable(boolean value) {
635                try {
636                        setMode(ModeUtil.setWritable(getMode(), value));
637                        return true;
638                } catch (IOException e) {
639                        return false;
640                }
641        }
642}