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