]> source.dussan.org Git - archiva.git/blob
412433197a20e45006a9ebf1412b7ecfde734b72
[archiva.git] /
1 package org.apache.archiva.webdav;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *  http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import org.apache.archiva.admin.model.beans.ManagedRepository;
23 import org.apache.archiva.common.filelock.FileLockException;
24 import org.apache.archiva.common.filelock.FileLockManager;
25 import org.apache.archiva.common.filelock.FileLockTimeoutException;
26 import org.apache.archiva.common.filelock.Lock;
27 import org.apache.archiva.metadata.model.facets.AuditEvent;
28 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
29 import org.apache.archiva.repository.events.AuditListener;
30 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
31 import org.apache.archiva.scheduler.repository.model.RepositoryArchivaTaskScheduler;
32 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
33 import org.apache.archiva.webdav.util.IndexWriter;
34 import org.apache.archiva.webdav.util.MimeTypes;
35 import org.apache.commons.io.FileUtils;
36 import org.apache.commons.io.IOUtils;
37 import org.apache.jackrabbit.util.Text;
38 import org.apache.jackrabbit.webdav.*;
39 import org.apache.jackrabbit.webdav.io.InputContext;
40 import org.apache.jackrabbit.webdav.io.OutputContext;
41 import org.apache.jackrabbit.webdav.lock.*;
42 import org.apache.jackrabbit.webdav.property.*;
43 import org.joda.time.DateTime;
44 import org.joda.time.format.DateTimeFormatter;
45 import org.joda.time.format.ISODateTimeFormat;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import javax.servlet.http.HttpServletResponse;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.OutputStream;
53 import java.nio.file.Files;
54 import java.nio.file.Path;
55 import java.nio.file.Paths;
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.util.stream.Stream;
59
60 /**
61  */
62 public class ArchivaDavResource
63     implements DavResource
64 {
65     public static final String HIDDEN_PATH_PREFIX = ".";
66
67     private final ArchivaDavResourceLocator locator;
68
69     private final DavResourceFactory factory;
70
71     private final Path localResource;
72
73     private final String logicalResource;
74
75     private DavPropertySet properties = null;
76
77     private LockManager lockManager;
78
79     private final DavSession session;
80
81     private String remoteAddr;
82
83     private final ManagedRepository repository;
84
85     private final MimeTypes mimeTypes;
86
87     private List<AuditListener> auditListeners;
88
89     private String principal;
90
91     public static final String COMPLIANCE_CLASS = "1, 2";
92
93     private final ArchivaTaskScheduler scheduler;
94
95     private final FileLockManager fileLockManager;
96
97     private Logger log = LoggerFactory.getLogger( ArchivaDavResource.class );
98
99     public ArchivaDavResource( String localResource, String logicalResource, ManagedRepository repository,
100                                DavSession session, ArchivaDavResourceLocator locator, DavResourceFactory factory,
101                                MimeTypes mimeTypes, List<AuditListener> auditListeners,
102                                RepositoryArchivaTaskScheduler scheduler, FileLockManager fileLockManager )
103     {
104         this.localResource = Paths.get( localResource );
105         this.logicalResource = logicalResource;
106         this.locator = locator;
107         this.factory = factory;
108         this.session = session;
109
110         // TODO: push into locator as well as moving any references out of the resource factory
111         this.repository = repository;
112
113         // TODO: these should be pushed into the repository layer, along with the physical file operations in this class
114         this.mimeTypes = mimeTypes;
115         this.auditListeners = auditListeners;
116         this.scheduler = scheduler;
117         this.fileLockManager = fileLockManager;
118     }
119
120     public ArchivaDavResource( String localResource, String logicalResource, ManagedRepository repository,
121                                String remoteAddr, String principal, DavSession session,
122                                ArchivaDavResourceLocator locator, DavResourceFactory factory, MimeTypes mimeTypes,
123                                List<AuditListener> auditListeners, RepositoryArchivaTaskScheduler scheduler,
124                                FileLockManager fileLockManager )
125     {
126         this( localResource, logicalResource, repository, session, locator, factory, mimeTypes, auditListeners,
127               scheduler, fileLockManager );
128
129         this.remoteAddr = remoteAddr;
130         this.principal = principal;
131     }
132
133     @Override
134     public String getComplianceClass()
135     {
136         return COMPLIANCE_CLASS;
137     }
138
139     @Override
140     public String getSupportedMethods()
141     {
142         return METHODS;
143     }
144
145     @Override
146     public boolean exists()
147     {
148         return Files.exists(localResource);
149     }
150
151     @Override
152     public boolean isCollection()
153     {
154         return Files.isDirectory(localResource);
155     }
156
157     @Override
158     public String getDisplayName()
159     {
160         String resPath = getResourcePath();
161         return ( resPath != null ) ? Text.getName( resPath ) : resPath;
162     }
163
164     @Override
165     public DavResourceLocator getLocator()
166     {
167         return locator;
168     }
169
170     public Path getLocalResource()
171     {
172         return localResource;
173     }
174
175     @Override
176     public String getResourcePath()
177     {
178         return locator.getResourcePath();
179     }
180
181     @Override
182     public String getHref()
183     {
184         return locator.getHref( isCollection() );
185     }
186
187     @Override
188     public long getModificationTime()
189     {
190         try
191         {
192             return Files.getLastModifiedTime(localResource).toMillis();
193         }
194         catch ( IOException e )
195         {
196             log.error("Could not get modification time of {}: {}", localResource, e.getMessage(), e);
197             return 0;
198         }
199     }
200
201     @Override
202     public void spool( OutputContext outputContext )
203         throws IOException
204     {
205         if ( !isCollection() )
206         {
207             outputContext.setContentLength( Files.size( localResource ) );
208             outputContext.setContentType( mimeTypes.getMimeType( localResource.getFileName().toString() ) );
209         }
210
211         try
212         {
213             if ( !isCollection() && outputContext.hasStream() )
214             {
215                 Lock lock = fileLockManager.readFileLock( localResource.toFile() );
216                 try (InputStream is = Files.newInputStream( lock.getFile().toPath() ))
217                 {
218                     IOUtils.copy( is, outputContext.getOutputStream() );
219                 }
220             }
221             else if ( outputContext.hasStream() )
222             {
223                 IndexWriter writer = new IndexWriter( this, localResource, logicalResource );
224                 writer.write( outputContext );
225             }
226         }
227         catch ( FileLockException e )
228         {
229             throw new IOException( e.getMessage(), e );
230         }
231         catch ( FileLockTimeoutException e )
232         {
233             throw new IOException( e.getMessage(), e );
234         }
235     }
236
237     @Override
238     public DavPropertyName[] getPropertyNames()
239     {
240         return getProperties().getPropertyNames();
241     }
242
243     @Override
244     public DavProperty getProperty( DavPropertyName name )
245     {
246         return getProperties().get( name );
247     }
248
249     @Override
250     public DavPropertySet getProperties()
251     {
252         return initProperties();
253     }
254
255     @Override
256     public void setProperty( DavProperty property )
257         throws DavException
258     {
259     }
260
261     @Override
262     public void removeProperty( DavPropertyName propertyName )
263         throws DavException
264     {
265     }
266
267     public MultiStatusResponse alterProperties( DavPropertySet setProperties, DavPropertyNameSet removePropertyNames )
268         throws DavException
269     {
270         return null;
271     }
272
273     @SuppressWarnings("unchecked")
274     @Override
275     public MultiStatusResponse alterProperties( List changeList )
276         throws DavException
277     {
278         return null;
279     }
280
281     @Override
282     public DavResource getCollection()
283     {
284         DavResource parent = null;
285         if ( getResourcePath() != null && !getResourcePath().equals( "/" ) )
286         {
287             String parentPath = Text.getRelativeParent( getResourcePath(), 1 );
288             if ( parentPath.equals( "" ) )
289             {
290                 parentPath = "/";
291             }
292             DavResourceLocator parentloc =
293                 locator.getFactory().createResourceLocator( locator.getPrefix(), parentPath );
294             try
295             {
296                 parent = factory.createResource( parentloc, session );
297             }
298             catch ( DavException e )
299             {
300                 // should not occur
301             }
302         }
303         return parent;
304     }
305
306     @Override
307     public void addMember( DavResource resource, InputContext inputContext )
308         throws DavException
309     {
310         Path localFile = localResource.resolve( resource.getDisplayName() );
311         boolean exists = Files.exists(localFile);
312
313         if ( isCollection() && inputContext.hasStream() ) // New File
314         {
315             try (OutputStream stream = Files.newOutputStream( localFile ))
316             {
317                 IOUtils.copy( inputContext.getInputStream(), stream );
318             }
319             catch ( IOException e )
320             {
321                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
322             }
323
324             // TODO: a bad deployment shouldn't delete an existing file - do we need to write to a temporary location first?
325             long expectedContentLength = inputContext.getContentLength();
326             long actualContentLength = 0;
327             try
328             {
329                 actualContentLength = Files.size(localFile);
330             }
331             catch ( IOException e )
332             {
333                 log.error( "Could not get length of file {}: {}", localFile, e.getMessage(), e );
334             }
335             // length of -1 is given for a chunked request or unknown length, in which case we accept what was uploaded
336             if ( expectedContentLength >= 0 && expectedContentLength != actualContentLength )
337             {
338                 String msg = "Content Header length was " + expectedContentLength + " but was " + actualContentLength;
339                 log.debug( "Upload failed: {}", msg );
340
341                 org.apache.archiva.common.utils.FileUtils.deleteQuietly( localFile );
342                 throw new DavException( HttpServletResponse.SC_BAD_REQUEST, msg );
343             }
344
345             queueRepositoryTask( localFile );
346
347             log.debug( "File '{}{}(current user '{}')", resource.getDisplayName(),
348                        ( exists ? "' modified " : "' created " ), this.principal );
349
350             triggerAuditEvent( resource, exists ? AuditEvent.MODIFY_FILE : AuditEvent.CREATE_FILE );
351         }
352         else if ( !inputContext.hasStream() && isCollection() ) // New directory
353         {
354             try
355             {
356                 Files.createDirectories( localFile );
357             }
358             catch ( IOException e )
359             {
360                 log.error("Could not create directory {}: {}", localFile, e.getMessage(), e);
361             }
362
363             log.debug( "Directory '{}' (current user '{}')", resource.getDisplayName(), this.principal );
364
365             triggerAuditEvent( resource, AuditEvent.CREATE_DIR );
366         }
367         else
368         {
369             String msg = "Could not write member " + resource.getResourcePath() + " at " + getResourcePath()
370                 + " as this is not a DAV collection";
371             log.debug( msg );
372             throw new DavException( HttpServletResponse.SC_BAD_REQUEST, msg );
373         }
374     }
375
376     @Override
377     public DavResourceIterator getMembers()
378     {
379         List<DavResource> list = new ArrayList<>();
380         if ( exists() && isCollection() )
381         {
382             try ( Stream<Path> stream = Files.list(localResource))
383             {
384                 stream.forEach ( p ->
385                 {
386                     String item = p.toString();
387                     try
388                     {
389                         if ( !item.startsWith( HIDDEN_PATH_PREFIX ) )
390                         {
391                             String path = locator.getResourcePath( ) + '/' + item;
392                             DavResourceLocator resourceLocator =
393                                 locator.getFactory( ).createResourceLocator( locator.getPrefix( ), path );
394                             DavResource resource = factory.createResource( resourceLocator, session );
395
396                             if ( resource != null )
397                             {
398                                 list.add( resource );
399                             }
400                             log.debug( "Resource '{}' retrieved by '{}'", item, this.principal );
401                         }
402                     }
403                     catch ( DavException e )
404                     {
405                         // Should not occur
406                     }
407                 });
408             } catch (IOException e) {
409                 log.error("Error while listing {}", localResource);
410             }
411         }
412         return new DavResourceIteratorImpl( list );
413     }
414
415     @Override
416     public void removeMember( DavResource member )
417         throws DavException
418     {
419         Path resource = checkDavResourceIsArchivaDavResource( member ).getLocalResource();
420
421         if ( Files.exists(resource) )
422         {
423             try
424             {
425                 if ( Files.isDirectory(resource) )
426                 {
427                     org.apache.archiva.common.utils.FileUtils.deleteDirectory( resource );
428                     triggerAuditEvent( member, AuditEvent.REMOVE_DIR );
429                 }
430                 else
431                 {
432                     Files.deleteIfExists( resource );
433                     triggerAuditEvent( member, AuditEvent.REMOVE_FILE );
434                 }
435
436                 log.debug( "{}{}' removed (current user '{}')", ( Files.isDirectory(resource) ? "Directory '" : "File '" ),
437                            member.getDisplayName(), this.principal );
438
439             }
440             catch ( IOException e )
441             {
442                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
443             }
444         }
445         else
446         {
447             throw new DavException( HttpServletResponse.SC_NOT_FOUND );
448         }
449     }
450
451     private void triggerAuditEvent( DavResource member, String action )
452         throws DavException
453     {
454         String path = logicalResource + "/" + member.getDisplayName();
455
456         ArchivaDavResource resource = checkDavResourceIsArchivaDavResource( member );
457         AuditEvent auditEvent = new AuditEvent( locator.getRepositoryId(), resource.principal, path, action );
458         auditEvent.setRemoteIP( resource.remoteAddr );
459
460         for ( AuditListener listener : auditListeners )
461         {
462             listener.auditEvent( auditEvent );
463         }
464     }
465
466     @Override
467     public void move( DavResource destination )
468         throws DavException
469     {
470         if ( !exists() )
471         {
472             throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource to copy does not exist." );
473         }
474
475         try
476         {
477             ArchivaDavResource resource = checkDavResourceIsArchivaDavResource( destination );
478             if ( isCollection() )
479             {
480                 FileUtils.moveDirectory( getLocalResource().toFile(), resource.getLocalResource().toFile() );
481
482                 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.MOVE_DIRECTORY );
483             }
484             else
485             {
486                 FileUtils.moveFile( getLocalResource().toFile(), resource.getLocalResource().toFile() );
487
488                 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.MOVE_FILE );
489             }
490
491             log.debug( "{}{}' moved to '{}' (current user '{}')", ( isCollection() ? "Directory '" : "File '" ),
492                        getLocalResource().getFileName(), destination, this.principal );
493
494         }
495         catch ( IOException e )
496         {
497             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
498         }
499     }
500
501     @Override
502     public void copy( DavResource destination, boolean shallow )
503         throws DavException
504     {
505         if ( !exists() )
506         {
507             throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource to copy does not exist." );
508         }
509
510         if ( shallow && isCollection() )
511         {
512             throw new DavException( DavServletResponse.SC_FORBIDDEN, "Unable to perform shallow copy for collection" );
513         }
514
515         try
516         {
517             ArchivaDavResource resource = checkDavResourceIsArchivaDavResource( destination );
518             if ( isCollection() )
519             {
520                 FileUtils.copyDirectory( getLocalResource().toFile(), resource.getLocalResource().toFile() );
521
522                 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.COPY_DIRECTORY );
523             }
524             else
525             {
526                 FileUtils.copyFile( getLocalResource().toFile(), resource.getLocalResource().toFile() );
527
528                 triggerAuditEvent( remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.COPY_FILE );
529             }
530
531             log.debug( "{}{}' copied to '{}' (current user '{}')", ( isCollection() ? "Directory '" : "File '" ),
532                        getLocalResource().getFileName(), destination, this.principal );
533
534         }
535         catch ( IOException e )
536         {
537             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
538         }
539     }
540
541     @Override
542     public boolean isLockable( Type type, Scope scope )
543     {
544         return Type.WRITE.equals( type ) && Scope.EXCLUSIVE.equals( scope );
545     }
546
547     @Override
548     public boolean hasLock( Type type, Scope scope )
549     {
550         return getLock( type, scope ) != null;
551     }
552
553     @Override
554     public ActiveLock getLock( Type type, Scope scope )
555     {
556         ActiveLock lock = null;
557         if ( exists() && Type.WRITE.equals( type ) && Scope.EXCLUSIVE.equals( scope ) )
558         {
559             lock = lockManager.getLock( type, scope, this );
560         }
561         return lock;
562     }
563
564     @Override
565     public ActiveLock[] getLocks()
566     {
567         ActiveLock writeLock = getLock( Type.WRITE, Scope.EXCLUSIVE );
568         return ( writeLock != null ) ? new ActiveLock[]{ writeLock } : new ActiveLock[0];
569     }
570
571     @Override
572     public ActiveLock lock( LockInfo lockInfo )
573         throws DavException
574     {
575         ActiveLock lock = null;
576         if ( isLockable( lockInfo.getType(), lockInfo.getScope() ) )
577         {
578             lock = lockManager.createLock( lockInfo, this );
579         }
580         else
581         {
582             throw new DavException( DavServletResponse.SC_PRECONDITION_FAILED, "Unsupported lock type or scope." );
583         }
584         return lock;
585     }
586
587     @Override
588     public ActiveLock refreshLock( LockInfo lockInfo, String lockToken )
589         throws DavException
590     {
591         if ( !exists() )
592         {
593             throw new DavException( DavServletResponse.SC_NOT_FOUND );
594         }
595         ActiveLock lock = getLock( lockInfo.getType(), lockInfo.getScope() );
596         if ( lock == null )
597         {
598             throw new DavException( DavServletResponse.SC_PRECONDITION_FAILED,
599                                     "No lock with the given type/scope present on resource " + getResourcePath() );
600         }
601
602         lock = lockManager.refreshLock( lockInfo, lockToken, this );
603
604         return lock;
605     }
606
607     @Override
608     public void unlock( String lockToken )
609         throws DavException
610     {
611         ActiveLock lock = getLock( Type.WRITE, Scope.EXCLUSIVE );
612         if ( lock == null )
613         {
614             throw new DavException( HttpServletResponse.SC_PRECONDITION_FAILED );
615         }
616         else if ( lock.isLockedByToken( lockToken ) )
617         {
618             lockManager.releaseLock( lockToken, this );
619         }
620         else
621         {
622             throw new DavException( DavServletResponse.SC_LOCKED );
623         }
624     }
625
626     @Override
627     public void addLockManager( LockManager lockManager )
628     {
629         this.lockManager = lockManager;
630     }
631
632     @Override
633     public DavResourceFactory getFactory()
634     {
635         return factory;
636     }
637
638     @Override
639     public DavSession getSession()
640     {
641         return session;
642     }
643
644     /**
645      * Fill the set of properties
646      */
647     protected DavPropertySet initProperties()
648     {
649         if ( !exists() )
650         {
651             properties = new DavPropertySet();
652         }
653
654         if ( properties != null )
655         {
656             return properties;
657         }
658
659         DavPropertySet properties = new DavPropertySet();
660
661         // set (or reset) fundamental properties
662         if ( getDisplayName() != null )
663         {
664             properties.add( new DefaultDavProperty( DavPropertyName.DISPLAYNAME, getDisplayName() ) );
665         }
666         if ( isCollection() )
667         {
668             properties.add( new ResourceType( ResourceType.COLLECTION ) );
669             // Windows XP support
670             properties.add( new DefaultDavProperty( DavPropertyName.ISCOLLECTION, "1" ) );
671         }
672         else
673         {
674             properties.add( new ResourceType( ResourceType.DEFAULT_RESOURCE ) );
675
676             // Windows XP support
677             properties.add( new DefaultDavProperty( DavPropertyName.ISCOLLECTION, "0" ) );
678         }
679
680         // Need to get the ISO8601 date for properties
681         DateTime dt = null;
682         try
683         {
684             dt = new DateTime( Files.getLastModifiedTime( localResource ).toMillis() );
685         }
686         catch ( IOException e )
687         {
688             log.error("Could not get modification time of {}: {}", localResource, e.getMessage(), e);
689             dt = new DateTime();
690         }
691         DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
692         String modifiedDate = fmt.print( dt );
693
694         properties.add( new DefaultDavProperty( DavPropertyName.GETLASTMODIFIED, modifiedDate ) );
695
696         properties.add( new DefaultDavProperty( DavPropertyName.CREATIONDATE, modifiedDate ) );
697
698         try
699         {
700             properties.add( new DefaultDavProperty( DavPropertyName.GETCONTENTLENGTH, Files.size(localResource) ) );
701         }
702         catch ( IOException e )
703         {
704             log.error("Could not get file size of {}: {}", localResource, e.getMessage(), e);
705             properties.add( new DefaultDavProperty( DavPropertyName.GETCONTENTLENGTH, 0 ) );
706         }
707
708         this.properties = properties;
709
710         return properties;
711     }
712
713     private ArchivaDavResource checkDavResourceIsArchivaDavResource( DavResource resource )
714         throws DavException
715     {
716         if ( !( resource instanceof ArchivaDavResource ) )
717         {
718             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
719                                     "DavResource is not instance of ArchivaDavResource" );
720         }
721         return (ArchivaDavResource) resource;
722     }
723
724     private void triggerAuditEvent( String remoteIP, String repositoryId, String resource, String action )
725     {
726         AuditEvent event = new AuditEvent( repositoryId, principal, resource, action );
727         event.setRemoteIP( remoteIP );
728
729         for ( AuditListener listener : auditListeners )
730         {
731             listener.auditEvent( event );
732         }
733     }
734
735     private void queueRepositoryTask( Path localFile )
736     {
737         RepositoryTask task = new RepositoryTask();
738         task.setRepositoryId( repository.getId() );
739         task.setResourceFile( localFile );
740         task.setUpdateRelatedArtifacts( false );
741         task.setScanAll( false );
742
743         try
744         {
745             scheduler.queueTask( task );
746         }
747         catch ( TaskQueueException e )
748         {
749             log.error( "Unable to queue repository task to execute consumers on resource file ['{}"
750                            + "'].", localFile.getFileName() );
751         }
752     }
753 }