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