]> source.dussan.org Git - archiva.git/blob
ad9fc7039dcd246caf4931756765465fb7aedcee
[archiva.git] /
1 package org.apache.maven.archiva.web.repository;
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.maven.archiva.common.utils.PathUtil;
23 import org.apache.maven.archiva.model.ArtifactReference;
24 import org.apache.maven.archiva.model.ProjectReference;
25 import org.apache.maven.archiva.model.VersionedReference;
26 import org.apache.maven.archiva.proxy.ProxyException;
27 import org.apache.maven.archiva.proxy.RepositoryProxyConnectors;
28 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
29 import org.apache.maven.archiva.repository.RepositoryContentFactory;
30 import org.apache.maven.archiva.repository.RepositoryException;
31 import org.apache.maven.archiva.repository.RepositoryNotFoundException;
32 import org.apache.maven.archiva.repository.audit.AuditEvent;
33 import org.apache.maven.archiva.repository.audit.AuditListener;
34 import org.apache.maven.archiva.repository.audit.Auditable;
35 import org.apache.maven.archiva.repository.content.RepositoryRequest;
36 import org.apache.maven.archiva.repository.layout.LayoutException;
37 import org.apache.maven.archiva.repository.metadata.MetadataTools;
38 import org.apache.maven.archiva.repository.metadata.RepositoryMetadataException;
39 import org.apache.maven.archiva.security.ArchivaUser;
40 import org.apache.maven.model.DistributionManagement;
41 import org.apache.maven.model.Model;
42 import org.apache.maven.model.Relocation;
43 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
44 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
45 import org.codehaus.plexus.webdav.AbstractDavServerComponent;
46 import org.codehaus.plexus.webdav.DavServerComponent;
47 import org.codehaus.plexus.webdav.DavServerException;
48 import org.codehaus.plexus.webdav.DavServerListener;
49 import org.codehaus.plexus.webdav.servlet.DavServerRequest;
50 import org.codehaus.plexus.webdav.util.WebdavMethodUtil;
51
52 import java.io.File;
53 import java.io.FileNotFoundException;
54 import java.io.FileReader;
55 import java.io.IOException;
56 import java.io.PrintWriter;
57 import java.util.ArrayList;
58 import java.util.List;
59
60 import javax.servlet.ServletConfig;
61 import javax.servlet.ServletException;
62 import javax.servlet.http.HttpServletResponse;
63
64 /**
65  * ProxiedDavServer
66  *
67  * @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
68  * @version $Id$
69  * @plexus.component role="org.codehaus.plexus.webdav.DavServerComponent"
70  * role-hint="proxied" instantiation-strategy="per-lookup"
71  */
72 public class ProxiedDavServer
73     extends AbstractDavServerComponent
74     implements Auditable
75 {
76     /**
77      * @plexus.requirement role-hint="simple"
78      */
79     private DavServerComponent davServer;
80     
81     /**
82      * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
83      */
84     private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
85     
86     /**
87      * @plexus.requirement
88      */
89     private RepositoryContentFactory repositoryFactory;
90
91     /**
92      * @plexus.requirement
93      */
94     private RepositoryRequest repositoryRequest;
95
96     /**
97      * @plexus.requirement role-hint="default"
98      */
99     private RepositoryProxyConnectors connectors;
100
101     /**
102      * @plexus.requirement
103      */
104     private MetadataTools metadataTools;
105     
106     /**
107      * @plexus.requirement role-hint="xwork"
108      */
109     private ArchivaUser archivaUser;
110
111     private ManagedRepositoryContent managedRepository;
112
113     public String getPrefix()
114     {
115         return davServer.getPrefix();
116     }
117
118     public File getRootDirectory()
119     {
120         return davServer.getRootDirectory();
121     }
122
123     public void setPrefix( String prefix )
124     {
125         davServer.setPrefix( prefix );
126     }
127
128     public void setRootDirectory( File rootDirectory )
129     {
130         davServer.setRootDirectory( rootDirectory );
131     }
132
133     public void init( ServletConfig servletConfig )
134         throws DavServerException
135     {
136         davServer.init( servletConfig );
137
138         try
139         {
140             managedRepository = repositoryFactory.getManagedRepositoryContent( getPrefix() );
141         }
142         catch ( RepositoryNotFoundException e )
143         {
144             throw new DavServerException( e.getMessage(), e );
145         }
146         catch ( RepositoryException e )
147         {
148             throw new DavServerException( e.getMessage(), e );
149         }
150     }
151
152     public void process( DavServerRequest request, HttpServletResponse response )
153         throws DavServerException, ServletException, IOException
154     {
155         boolean isGet = WebdavMethodUtil.isReadMethod( request.getRequest().getMethod() );
156         boolean isPut = WebdavMethodUtil.isWriteMethod( request.getRequest().getMethod() );
157         String resource = request.getLogicalResource();
158         
159         if ( isGet )
160         {
161             // Default behaviour is to treat the resource natively.
162             File resourceFile = new File( managedRepository.getRepoRoot(), resource );
163
164             // If this a directory resource, then we are likely browsing.
165             if ( resourceFile.exists() && resourceFile.isDirectory() )
166             {
167                 String requestURL = request.getRequest().getRequestURL().toString();
168                 
169                 // [MRM-440] - If webdav URL lacks a trailing /, navigating to all links in the listing return 404.
170                 if( !requestURL.endsWith( "/" ) )
171                 {
172                     String redirectToLocation = requestURL + "/";
173                     response.sendRedirect( redirectToLocation );
174                     return;
175                 }
176                 
177                 // Process the request.
178                 davServer.process( request, response );
179                 
180                 // All done.
181                 return;
182             }
183
184             // At this point the incoming request can either be in default or legacy layout format.
185             try
186             {
187                 // Perform an adjustment of the resource to the managed repository expected path.
188                 resource = repositoryRequest.toNativePath( request.getLogicalResource(), managedRepository );
189                 resourceFile = new File( managedRepository.getRepoRoot(), resource );
190
191                 // Adjust the pathInfo resource to be in the format that the dav server impl expects.
192                 request.getRequest().setPathInfo( resource );
193
194                 boolean previouslyExisted = resourceFile.exists();
195                 
196                 // Attempt to fetch the resource from any defined proxy.
197                 if( fetchContentFromProxies( request, resource ) )
198                 {
199                     processAuditEvents( request, resource, previouslyExisted, resourceFile, " (proxied)" );
200                 }
201             }
202             catch ( LayoutException e )
203             {
204                 // Invalid resource, pass it on.
205                 respondResourceMissing( request, response, e );
206
207                 // All done.
208                 return;
209             }
210
211             if ( resourceFile.exists() )
212             {
213                 // [MRM-503] - Metadata file need Pragma:no-cache response header.
214                 if ( request.getLogicalResource().endsWith( "/maven-metadata.xml" ) )
215                 {
216                     response.addHeader( "Pragma", "no-cache" );
217                     response.addHeader( "Cache-Control", "no-cache" );
218                 }
219
220                 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
221
222                 davServer.process( request, response );
223             }
224             else
225             {
226                 respondResourceMissing( request, response, null );
227             }
228         }
229
230         if ( isPut )
231         {
232             /* Create parent directories that don't exist when writing a file
233              * This actually makes this implementation not compliant to the
234              * WebDAV RFC - but we have enough knowledge
235              * about how the collection is being used to do this reasonably and
236              * some versions of Maven's WebDAV don't
237              * correctly create the collections themselves.
238              */
239
240             File rootDirectory = getRootDirectory();
241             if ( rootDirectory != null )
242             {
243                 File destDir = new File( rootDirectory, resource ).getParentFile();
244                 if( !destDir.exists() )
245                 {
246                     destDir.mkdirs();
247                     String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
248                     triggerAuditEvent( request, relPath, AuditEvent.CREATE_DIR );
249                 }
250             }
251             
252             File resourceFile = new File( managedRepository.getRepoRoot(), resource );
253             
254             boolean previouslyExisted = resourceFile.exists();
255             
256             // Allow the dav server to process the put request.
257             davServer.process( request, response );
258             
259             processAuditEvents( request, resource, previouslyExisted, resourceFile, null );
260             
261             // All done.
262             return;
263         }
264     }
265
266     private void respondResourceMissing( DavServerRequest request, HttpServletResponse response, Throwable t )
267     {
268         response.setStatus( HttpServletResponse.SC_NOT_FOUND );
269
270         try
271         {
272             StringBuffer missingUrl = new StringBuffer();
273             missingUrl.append( request.getRequest().getScheme() ).append( "://" );
274             missingUrl.append( request.getRequest().getServerName() ).append( ":" );
275             missingUrl.append( request.getRequest().getServerPort() );
276             missingUrl.append( request.getRequest().getServletPath() );
277
278             String message = "Error 404 Not Found";
279
280             PrintWriter out = new PrintWriter( response.getOutputStream() );
281
282             response.setContentType( "text/html; charset=\"UTF-8\"" );
283
284             out.println( "<html>" );
285             out.println( "<head><title>" + message + "</title></head>" );
286             out.println( "<body>" );
287
288             out.print( "<p><h1>" );
289             out.print( message );
290             out.println( "</h1></p>" );
291
292             out.print( "<p>The following resource does not exist: <a href=\"" );
293             out.print( missingUrl.toString() );
294             out.println( "\">" );
295             out.print( missingUrl.toString() );
296             out.println( "</a></p>" );
297             
298             if ( t != null )
299             {
300                 out.println( "<pre>" );
301                 t.printStackTrace( out );
302                 out.println( "</pre>" );
303             }
304
305             out.println( "</body></html>" );
306
307             out.flush();
308         }
309         catch ( IOException e )
310         {
311             e.printStackTrace();
312         }
313     }
314
315     private boolean fetchContentFromProxies( DavServerRequest request, String resource )
316         throws ServletException
317     {
318         if ( repositoryRequest.isSupportFile( resource ) )
319         {
320             // Checksums are fetched with artifact / metadata.
321             
322             // Need to adjust the path for the checksum resource.
323             return false;
324         }
325
326         // Is it a Metadata resource?
327         if ( repositoryRequest.isDefault( resource ) && repositoryRequest.isMetadata( resource ) )
328         {
329             return fetchMetadataFromProxies( request, resource );
330         }
331
332         // Not any of the above? Then it's gotta be an artifact reference.
333         try
334         {
335             // Get the artifact reference in a layout neutral way.
336             ArtifactReference artifact = repositoryRequest.toArtifactReference( resource );
337             
338             if ( artifact != null )
339             {
340                 applyServerSideRelocation( artifact );
341
342                 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
343                 
344                 // Set the path to the resource using managed repository specific layout format.
345                 request.getRequest().setPathInfo( managedRepository.toPath( artifact ) );
346                 return ( proxiedFile != null );
347             }
348         }
349         catch ( LayoutException e )
350         {
351             /* eat it */
352         }
353         catch ( ProxyException e )
354         {
355             throw new ServletException( "Unable to fetch artifact resource.", e );
356         }
357         return false;
358     }
359
360     private boolean fetchMetadataFromProxies( DavServerRequest request, String resource )
361         throws ServletException
362     {
363         ProjectReference project;
364         VersionedReference versioned;
365
366         try
367         {
368
369             versioned = metadataTools.toVersionedReference( resource );
370             if ( versioned != null )
371             {
372                 connectors.fetchFromProxies( managedRepository, versioned );
373                 return true;
374             }
375         }
376         catch ( RepositoryMetadataException e )
377         {
378             /* eat it */
379         }
380         catch ( ProxyException e )
381         {
382             throw new ServletException( "Unable to fetch versioned metadata resource.", e );
383         }
384
385         try
386         {
387             project = metadataTools.toProjectReference( resource );
388             if ( project != null )
389             {
390                 connectors.fetchFromProxies( managedRepository, project );
391                 return true;
392             }
393         }
394         catch ( RepositoryMetadataException e )
395         {
396             /* eat it */
397         }
398         catch ( ProxyException e )
399         {
400             throw new ServletException( "Unable to fetch project metadata resource.", e );
401         }
402         
403         return false;
404     }
405
406     /**
407      * A relocation capable client will request the POM prior to the artifact,
408      * and will then read meta-data and do client side relocation. A simplier
409      * client (like maven 1) will only request the artifact and not use the
410      * metadatas.
411      * <p>
412      * For such clients, archiva does server-side relocation by reading itself
413      * the &lt;relocation&gt; element in metadatas and serving the expected
414      * artifact.
415      */
416     protected void applyServerSideRelocation( ArtifactReference artifact )
417         throws ProxyException
418     {
419         if ( "pom".equals( artifact.getType() ) )
420         {
421             return;
422         }
423
424         // Build the artifact POM reference
425         ArtifactReference pomReference = new ArtifactReference();
426         pomReference.setGroupId( artifact.getGroupId() );
427         pomReference.setArtifactId( artifact.getArtifactId() );
428         pomReference.setVersion( artifact.getVersion() );
429         pomReference.setType( "pom" );
430
431         // Get the artifact POM from proxied repositories if needed
432         connectors.fetchFromProxies( managedRepository, pomReference );
433
434         // Open and read the POM from the managed repo
435         File pom = managedRepository.toFile( pomReference );
436         
437         if( !pom.exists() )
438         {
439             return;
440         }
441         
442         try
443         {
444             Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
445             DistributionManagement dist = model.getDistributionManagement();
446             if ( dist != null )
447             {
448                 Relocation relocation = dist.getRelocation();
449                 if ( relocation != null )
450                 {
451                     // artifact is relocated : update the repositoryPath
452                     if ( relocation.getGroupId() != null )
453                     {
454                         artifact.setGroupId( relocation.getGroupId() );
455                     }
456                     if ( relocation.getArtifactId() != null )
457                     {
458                         artifact.setArtifactId( relocation.getArtifactId() );
459                     }
460                     if ( relocation.getVersion() != null )
461                     {
462                         artifact.setVersion( relocation.getVersion() );
463                     }
464                 }
465             }
466         }
467         catch ( FileNotFoundException e )
468         {
469             // Artifact has no POM in repo : ignore
470         }
471         catch ( IOException e )
472         {
473             // Unable to read POM : ignore.
474         }
475         catch ( XmlPullParserException e )
476         {
477             // Invalid POM : ignore
478         }
479     }
480     
481     @Override
482     public void addListener( DavServerListener listener )
483     {
484         super.addListener( listener );
485         davServer.addListener( listener );
486     }
487     
488     @Override
489     public boolean isUseIndexHtml()
490     {
491         return davServer.isUseIndexHtml();
492     }
493     
494     @Override
495     public boolean hasResource( String resource )
496     {
497         return davServer.hasResource( resource );
498     }
499     
500     @Override
501     public void removeListener( DavServerListener listener )
502     {
503         davServer.removeListener( listener );
504     }
505     
506     @Override
507     public void setUseIndexHtml( boolean useIndexHtml )
508     {
509         super.setUseIndexHtml( useIndexHtml );
510         davServer.setUseIndexHtml( useIndexHtml );
511     }
512     
513     public ManagedRepositoryContent getRepository()
514     {
515         return managedRepository;
516     }
517     
518     private void processAuditEvents( DavServerRequest request, String resource, boolean previouslyExisted,
519                                      File resourceFile, String suffix )
520     {
521         if( suffix == null )
522         {
523             suffix = "";
524         }
525         
526         // Process Create Audit Events.
527         if ( !previouslyExisted && resourceFile.exists() )
528         {
529             if ( resourceFile.isFile() )
530             {
531                 triggerAuditEvent( request, resource, AuditEvent.CREATE_FILE + suffix );
532             }
533             else if ( resourceFile.isDirectory() )
534             {
535                 triggerAuditEvent( request, resource, AuditEvent.CREATE_DIR + suffix );
536             }
537         }
538         // Process Remove Audit Events.
539         else if ( previouslyExisted && !resourceFile.exists() )
540         {
541             if ( resourceFile.isFile() )
542             {
543                 triggerAuditEvent( request, resource, AuditEvent.REMOVE_FILE + suffix );
544             }
545             else if ( resourceFile.isDirectory() )
546             {
547                 triggerAuditEvent( request, resource, AuditEvent.REMOVE_DIR + suffix );
548             }
549         }
550         // Process modify events.
551         else
552         {
553             if ( resourceFile.isFile() )
554             {
555                 triggerAuditEvent( request, resource, AuditEvent.MODIFY_FILE + suffix );
556             }
557         }
558     }
559     
560     private void triggerAuditEvent( String user, String remoteIP, String resource, String action )
561     {
562         AuditEvent event = new AuditEvent( this.getPrefix(), user, resource, action );
563         event.setRemoteIP( remoteIP );
564
565         for ( AuditListener listener : auditListeners )
566         {
567             listener.auditEvent( event );
568         }
569     }
570
571     private void triggerAuditEvent( DavServerRequest request, String resource, String action )
572     {
573         triggerAuditEvent( archivaUser.getActivePrincipal(), getRemoteIP( request ), resource, action );
574     }
575
576     private String getRemoteIP( DavServerRequest request )
577     {
578         return request.getRequest().getRemoteAddr();
579     }
580
581     public void addAuditListener( AuditListener listener )
582     {
583         this.auditListeners.add( listener );
584     }
585
586     public void clearAuditListeners()
587     {
588         this.auditListeners.clear();
589     }
590
591     public void removeAuditListener( AuditListener listener )
592     {
593         this.auditListeners.remove( listener );
594     }
595 }