1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.catalina.webresources;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.net.MalformedURLException;
23 import java.net.URISyntaxException;
24 import java.net.URL;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Set;
32 import java.util.concurrent.ConcurrentHashMap;
33
34 import javax.management.ObjectName;
35
36 import org.apache.catalina.Context;
37 import org.apache.catalina.Host;
38 import org.apache.catalina.LifecycleException;
39 import org.apache.catalina.LifecycleState;
40 import org.apache.catalina.TrackedWebResource;
41 import org.apache.catalina.WebResource;
42 import org.apache.catalina.WebResourceRoot;
43 import org.apache.catalina.WebResourceSet;
44 import org.apache.catalina.util.LifecycleMBeanBase;
45 import org.apache.juli.logging.Log;
46 import org.apache.juli.logging.LogFactory;
47 import org.apache.tomcat.util.buf.UriUtil;
48 import org.apache.tomcat.util.compat.JreCompat;
49 import org.apache.tomcat.util.http.RequestUtil;
50 import org.apache.tomcat.util.res.StringManager;
51
52 /**
53  * <p>
54  * Provides the resources implementation for a web application. The
55  * {@link org.apache.catalina.Lifecycle} of this class should be aligned with
56  * that of the associated {@link Context}.
57  * </p><p>
58  * This implementation assumes that the base attribute supplied to {@link
59  * StandardRoot#createWebResourceSet(
60  * org.apache.catalina.WebResourceRoot.ResourceSetType, String, String, String,
61  * String)} represents the absolute path to a file.
62  * </p>
63  */

64 public class StandardRoot extends LifecycleMBeanBase implements WebResourceRoot {
65
66     private static final Log log = LogFactory.getLog(StandardRoot.class);
67     protected static final StringManager sm = StringManager.getManager(StandardRoot.class);
68
69     private Context context;
70     private boolean allowLinking = false;
71     private final List<WebResourceSet> preResources = new ArrayList<>();
72     private WebResourceSet main;
73     private final List<WebResourceSet> classResources = new ArrayList<>();
74     private final List<WebResourceSet> jarResources = new ArrayList<>();
75     private final List<WebResourceSet> postResources = new ArrayList<>();
76
77     private final Cache cache = new Cache(this);
78     private boolean cachingAllowed = true;
79     private ObjectName cacheJmxName = null;
80
81     private boolean trackLockedFiles = false;
82     private final Set<TrackedWebResource> trackedResources =
83             Collections.newSetFromMap(new ConcurrentHashMap<TrackedWebResource,Boolean>());
84
85     // Constructs to make iteration over all WebResourceSets simpler
86     private final List<WebResourceSet> mainResources = new ArrayList<>();
87     private final List<List<WebResourceSet>> allResources =
88             new ArrayList<>();
89     {
90         allResources.add(preResources);
91         allResources.add(mainResources);
92         allResources.add(classResources);
93         allResources.add(jarResources);
94         allResources.add(postResources);
95     }
96
97
98     /**
99      * Creates a new standard implementation of {@link WebResourceRoot}. A no
100      * argument constructor is required for this to work with the digester.
101      * {@link #setContext(Context)} must be called before this component is
102      * initialized.
103      */

104     public StandardRoot() {
105         // NO-OP
106     }
107
108     public StandardRoot(Context context) {
109         this.context = context;
110     }
111
112     @Override
113     public String[] list(String path) {
114         return list(path, true);
115     }
116
117     private String[] list(String path, boolean validate) {
118         if (validate) {
119             path = validate(path);
120         }
121
122         // Set because we don't want duplicates
123         // LinkedHashSet to retain the order. It is the order of the
124         // WebResourceSet that matters but it is simpler to retain the order
125         // over all of the JARs.
126         HashSet<String> result = new LinkedHashSet<>();
127         for (List<WebResourceSet> list : allResources) {
128             for (WebResourceSet webResourceSet : list) {
129                 if (!webResourceSet.getClassLoaderOnly()) {
130                     String[] entries = webResourceSet.list(path);
131                     for (String entry : entries) {
132                         result.add(entry);
133                     }
134                 }
135             }
136         }
137         return result.toArray(new String[result.size()]);
138     }
139
140
141     @Override
142     public Set<String> listWebAppPaths(String path) {
143         path = validate(path);
144
145         // Set because we don't want duplicates
146         Set<String> result = new HashSet<>();
147         for (List<WebResourceSet> list : allResources) {
148             for (WebResourceSet webResourceSet : list) {
149                 if (!webResourceSet.getClassLoaderOnly()) {
150                     result.addAll(webResourceSet.listWebAppPaths(path));
151                 }
152             }
153         }
154         if (result.size() == 0) {
155             return null;
156         }
157         return result;
158     }
159
160     @Override
161     public boolean mkdir(String path) {
162         path = validate(path);
163
164         if (preResourceExists(path)) {
165             return false;
166         }
167
168         boolean mkdirResult = main.mkdir(path);
169
170         if (mkdirResult && isCachingAllowed()) {
171             // Remove the entry from the cache so the new directory is visible
172             cache.removeCacheEntry(path);
173         }
174         return mkdirResult;
175     }
176
177     @Override
178     public boolean write(String path, InputStream is, boolean overwrite) {
179         path = validate(path);
180
181         if (!overwrite && preResourceExists(path)) {
182             return false;
183         }
184
185         boolean writeResult = main.write(path, is, overwrite);
186
187         if (writeResult && isCachingAllowed()) {
188             // Remove the entry from the cache so the new resource is visible
189             cache.removeCacheEntry(path);
190         }
191
192         return writeResult;
193     }
194
195     private boolean preResourceExists(String path) {
196         for (WebResourceSet webResourceSet : preResources) {
197             WebResource webResource = webResourceSet.getResource(path);
198             if (webResource.exists()) {
199                 return true;
200             }
201         }
202         return false;
203     }
204
205     @Override
206     public WebResource getResource(String path) {
207         return getResource(path, truefalse);
208     }
209
210     protected WebResource getResource(String path, boolean validate,
211             boolean useClassLoaderResources) {
212         if (validate) {
213             path = validate(path);
214         }
215
216         if (isCachingAllowed()) {
217             return cache.getResource(path, useClassLoaderResources);
218         } else {
219             return getResourceInternal(path, useClassLoaderResources);
220         }
221     }
222
223
224     @Override
225     public WebResource getClassLoaderResource(String path) {
226         return getResource("/WEB-INF/classes" + path, truetrue);
227     }
228
229
230     @Override
231     public WebResource[] getClassLoaderResources(String path) {
232         return getResources("/WEB-INF/classes" + path, true);
233     }
234
235
236     /**
237      * Ensures that this object is in a valid state to serve resources, checks
238      * that the path is a String that starts with '/' and checks that the path
239      * can be normalized without stepping outside of the root.
240      *
241      * @param path
242      * @return  the normalized path
243      */

244     private String validate(String path) {
245         if (!getState().isAvailable()) {
246             throw new IllegalStateException(
247                     sm.getString("standardRoot.checkStateNotStarted"));
248         }
249
250         if (path == null || path.length() == 0 || !path.startsWith("/")) {
251             throw new IllegalArgumentException(
252                     sm.getString("standardRoot.invalidPath", path));
253         }
254
255         String result;
256         if (File.separatorChar == '\\') {
257             // On Windows '\\' is a separator so in case a Windows style
258             // separator has managed to make it into the path, replace it.
259             result = RequestUtil.normalize(path, true);
260         } else {
261             // On UNIX and similar systems, '\\' is a valid file name so do not
262             // convert it to '/'
263             result = RequestUtil.normalize(path, false);
264         }
265         if (result == null || result.length() == 0 || !result.startsWith("/")) {
266             throw new IllegalArgumentException(
267                     sm.getString("standardRoot.invalidPathNormal", path, result));
268         }
269
270         return result;
271     }
272
273     protected final WebResource getResourceInternal(String path,
274             boolean useClassLoaderResources) {
275         WebResource result = null;
276         WebResource virtual = null;
277         WebResource mainEmpty = null;
278         for (List<WebResourceSet> list : allResources) {
279             for (WebResourceSet webResourceSet : list) {
280                 if (!useClassLoaderResources &&  !webResourceSet.getClassLoaderOnly() ||
281                         useClassLoaderResources && !webResourceSet.getStaticOnly()) {
282                     result = webResourceSet.getResource(path);
283                     if (result.exists()) {
284                         return result;
285                     }
286                     if (virtual == null) {
287                         if (result.isVirtual()) {
288                             virtual = result;
289                         } else if (main.equals(webResourceSet)) {
290                             mainEmpty = result;
291                         }
292                     }
293                 }
294             }
295         }
296
297         // Use the first virtual result if no real result was found
298         if (virtual != null) {
299             return virtual;
300         }
301
302         // Default is empty resource in main resources
303         return mainEmpty;
304     }
305
306     @Override
307     public WebResource[] getResources(String path) {
308         return getResources(path, false);
309     }
310
311     private WebResource[] getResources(String path,
312             boolean useClassLoaderResources) {
313         path = validate(path);
314
315         if (isCachingAllowed()) {
316             return cache.getResources(path, useClassLoaderResources);
317         } else {
318             return getResourcesInternal(path, useClassLoaderResources);
319         }
320     }
321
322     protected WebResource[] getResourcesInternal(String path,
323             boolean useClassLoaderResources) {
324         List<WebResource> result = new ArrayList<>();
325         for (List<WebResourceSet> list : allResources) {
326             for (WebResourceSet webResourceSet : list) {
327                 if (useClassLoaderResources || !webResourceSet.getClassLoaderOnly()) {
328                     WebResource webResource = webResourceSet.getResource(path);
329                     if (webResource.exists()) {
330                         result.add(webResource);
331                     }
332                 }
333             }
334         }
335
336         if (result.size() == 0) {
337             result.add(main.getResource(path));
338         }
339
340         return result.toArray(new WebResource[result.size()]);
341     }
342
343     @Override
344     public WebResource[] listResources(String path) {
345         return listResources(path, true);
346     }
347
348     protected WebResource[] listResources(String path, boolean validate) {
349         if (validate) {
350             path = validate(path);
351         }
352
353         String[] resources = list(path, false);
354         WebResource[] result = new WebResource[resources.length];
355         for (int i = 0; i < resources.length; i++) {
356             if (path.charAt(path.length() - 1) == '/') {
357                 result[i] = getResource(path + resources[i], falsefalse);
358             } else {
359                 result[i] = getResource(path + '/' + resources[i], falsefalse);
360             }
361         }
362         return result;
363     }
364
365     // TODO: Should the createWebResourceSet() methods be removed to some
366     //       utility class for file system based resource sets?
367
368     @Override
369     public void createWebResourceSet(ResourceSetType type, String webAppMount,
370             URL url, String internalPath) {
371         BaseLocation baseLocation = new BaseLocation(url);
372         createWebResourceSet(type, webAppMount, baseLocation.getBasePath(),
373                 baseLocation.getArchivePath(), internalPath);
374     }
375
376     @Override
377     public void createWebResourceSet(ResourceSetType type, String webAppMount,
378             String base, String archivePath, String internalPath) {
379         List<WebResourceSet> resourceList;
380         WebResourceSet resourceSet;
381
382         switch (type) {
383             case PRE:
384                 resourceList = preResources;
385                 break;
386             case CLASSES_JAR:
387                 resourceList = classResources;
388                 break;
389             case RESOURCE_JAR:
390                 resourceList = jarResources;
391                 break;
392             case POST:
393                 resourceList = postResources;
394                 break;
395             default:
396                 throw new IllegalArgumentException(
397                         sm.getString("standardRoot.createUnknownType", type));
398         }
399
400         // This implementation assumes that the base for all resources will be a
401         // file.
402         File file = new File(base);
403
404         if (file.isFile()) {
405             if (archivePath != null) {
406                 // Must be a JAR nested inside a WAR if archivePath is non-null
407                 resourceSet = new JarWarResourceSet(this, webAppMount, base,
408                         archivePath, internalPath);
409             } else if (file.getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) {
410                 resourceSet = new JarResourceSet(this, webAppMount, base,
411                         internalPath);
412             } else {
413                 resourceSet = new FileResourceSet(this, webAppMount, base,
414                         internalPath);
415             }
416         } else if (file.isDirectory()) {
417             resourceSet =
418                     new DirResourceSet(this, webAppMount, base, internalPath);
419         } else {
420             throw new IllegalArgumentException(
421                     sm.getString("standardRoot.createInvalidFile", file));
422         }
423
424         if (type.equals(ResourceSetType.CLASSES_JAR)) {
425             resourceSet.setClassLoaderOnly(true);
426         } else if (type.equals(ResourceSetType.RESOURCE_JAR)) {
427             resourceSet.setStaticOnly(true);
428         }
429
430         resourceList.add(resourceSet);
431     }
432
433     @Override
434     public void addPreResources(WebResourceSet webResourceSet) {
435         webResourceSet.setRoot(this);
436         preResources.add(webResourceSet);
437     }
438
439     @Override
440     public WebResourceSet[] getPreResources() {
441         return preResources.toArray(new WebResourceSet[preResources.size()]);
442     }
443
444     @Override
445     public void addJarResources(WebResourceSet webResourceSet) {
446         webResourceSet.setRoot(this);
447         jarResources.add(webResourceSet);
448     }
449
450     @Override
451     public WebResourceSet[] getJarResources() {
452         return jarResources.toArray(new WebResourceSet[jarResources.size()]);
453     }
454
455     @Override
456     public void addPostResources(WebResourceSet webResourceSet) {
457         webResourceSet.setRoot(this);
458         postResources.add(webResourceSet);
459     }
460
461     @Override
462     public WebResourceSet[] getPostResources() {
463         return postResources.toArray(new WebResourceSet[postResources.size()]);
464     }
465
466     protected WebResourceSet[] getClassResources() {
467         return classResources.toArray(new WebResourceSet[classResources.size()]);
468     }
469
470     protected void addClassResources(WebResourceSet webResourceSet) {
471         webResourceSet.setRoot(this);
472         classResources.add(webResourceSet);
473     }
474
475     @Override
476     public void setAllowLinking(boolean allowLinking) {
477         if (this.allowLinking != allowLinking && cachingAllowed) {
478             // If allow linking changes, invalidate the cache.
479             cache.clear();
480         }
481         this.allowLinking = allowLinking;
482     }
483
484     @Override
485     public boolean getAllowLinking() {
486         return allowLinking;
487     }
488
489     @Override
490     public void setCachingAllowed(boolean cachingAllowed) {
491         this.cachingAllowed = cachingAllowed;
492         if (!cachingAllowed) {
493             cache.clear();
494         }
495     }
496
497     @Override
498     public boolean isCachingAllowed() {
499         return cachingAllowed;
500     }
501
502     @Override
503     public long getCacheTtl() {
504         return cache.getTtl();
505     }
506
507     @Override
508     public void setCacheTtl(long cacheTtl) {
509         cache.setTtl(cacheTtl);
510     }
511
512     @Override
513     public long getCacheMaxSize() {
514         return cache.getMaxSize();
515     }
516
517     @Override
518     public void setCacheMaxSize(long cacheMaxSize) {
519         cache.setMaxSize(cacheMaxSize);
520     }
521
522     @Override
523     public void setCacheObjectMaxSize(int cacheObjectMaxSize) {
524         cache.setObjectMaxSize(cacheObjectMaxSize);
525         // Don't enforce the limit when not running as attributes may get set in
526         // any order.
527         if (getState().isAvailable()) {
528             cache.enforceObjectMaxSizeLimit();
529         }
530     }
531
532     @Override
533     public int getCacheObjectMaxSize() {
534         return cache.getObjectMaxSize();
535     }
536
537     @Override
538     public void setTrackLockedFiles(boolean trackLockedFiles) {
539         this.trackLockedFiles = trackLockedFiles;
540         if (!trackLockedFiles) {
541             trackedResources.clear();
542         }
543     }
544
545     @Override
546     public boolean getTrackLockedFiles() {
547         return trackLockedFiles;
548     }
549
550     public List<String> getTrackedResources() {
551         List<String> result = new ArrayList<>(trackedResources.size());
552         for (TrackedWebResource resource : trackedResources) {
553             result.add(resource.toString());
554         }
555         return result;
556     }
557
558     @Override
559     public Context getContext() {
560         return context;
561     }
562
563     @Override
564     public void setContext(Context context) {
565         this.context = context;
566     }
567
568     /**
569      * Class loader resources are handled by treating JARs in WEB-INF/lib as
570      * resource JARs (without the internal META-INF/resources/ prefix) mounted
571      * at WEB-INF/classes (rather than the web app root). This enables reuse
572      * of the resource handling plumbing.
573      *
574      * These resources are marked as class loader only so they are only used in
575      * the methods that are explicitly defined to return class loader resources.
576      * This prevents calls to getResource("/WEB-INF/classes") returning from one
577      * or more of the JAR files.
578      *
579      * @throws LifecycleException If an error occurs that should stop the web
580      *                            application from starting
581      */

582     protected void processWebInfLib() throws LifecycleException {
583         WebResource[] possibleJars = listResources("/WEB-INF/lib"false);
584
585         for (WebResource possibleJar : possibleJars) {
586             if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) {
587                 createWebResourceSet(ResourceSetType.CLASSES_JAR,
588                         "/WEB-INF/classes", possibleJar.getURL(), "/");
589             }
590         }
591     }
592
593     /**
594      * For unit testing.
595      * @param main The main resources
596      */

597     protected final void setMainResources(WebResourceSet main) {
598         this.main = main;
599         mainResources.clear();
600         mainResources.add(main);
601     }
602
603
604     @Override
605     public void backgroundProcess() {
606         cache.backgroundProcess();
607         gc();
608     }
609
610
611
612     @Override
613     public void gc() {
614         for (List<WebResourceSet> list : allResources) {
615             for (WebResourceSet webResourceSet : list) {
616                 webResourceSet.gc();
617             }
618         }
619     }
620
621     @Override
622     public void registerTrackedResource(TrackedWebResource trackedResource) {
623         trackedResources.add(trackedResource);
624     }
625
626
627     @Override
628     public void deregisterTrackedResource(TrackedWebResource trackedResource) {
629         trackedResources.remove(trackedResource);
630     }
631
632
633     @Override
634     public List<URL> getBaseUrls() {
635         List<URL> result = new ArrayList<>();
636         for (List<WebResourceSet> list : allResources) {
637             for (WebResourceSet webResourceSet : list) {
638                 if (!webResourceSet.getClassLoaderOnly()) {
639                     URL url = webResourceSet.getBaseUrl();
640                     if (url != null) {
641                         result.add(url);
642                     }
643                 }
644             }
645         }
646         return result;
647     }
648
649
650
651     /*
652      * Returns true if and only if all the resources for this web application
653      * are provided via a packed WAR file. It is used to optimise cache
654      * validation in this case on the basis that the WAR file will not change.
655      */

656     protected boolean isPackedWarFile() {
657         return main instanceof WarResourceSet && preResources.isEmpty() && postResources.isEmpty();
658     }
659
660
661     // ----------------------------------------------------------- JMX Lifecycle
662     @Override
663     protected String getDomainInternal() {
664         return context.getDomain();
665     }
666
667     @Override
668     protected String getObjectNameKeyProperties() {
669         StringBuilder keyProperties = new StringBuilder("type=WebResourceRoot");
670         keyProperties.append(context.getMBeanKeyProperties());
671
672         return keyProperties.toString();
673     }
674
675     // --------------------------------------------------------------- Lifecycle
676
677     @Override
678     protected void initInternal() throws LifecycleException {
679         super.initInternal();
680
681         cacheJmxName = register(cache, getObjectNameKeyProperties() + ",name=Cache");
682
683         registerURLStreamHandlerFactory();
684
685         if (context == null) {
686             throw new IllegalStateException(
687                     sm.getString("standardRoot.noContext"));
688         }
689
690         for (List<WebResourceSet> list : allResources) {
691             for (WebResourceSet webResourceSet : list) {
692                 webResourceSet.init();
693             }
694         }
695     }
696
697     protected void registerURLStreamHandlerFactory() {
698         if (!JreCompat.isGraalAvailable()) {
699             // Ensure support for jar:war:file:/ URLs will be available (required
700             // for resource JARs in packed WAR files).
701             TomcatURLStreamHandlerFactory.register();
702         }
703     }
704
705     @Override
706     protected void startInternal() throws LifecycleException {
707         mainResources.clear();
708
709         main = createMainResourceSet();
710
711         mainResources.add(main);
712
713         for (List<WebResourceSet> list : allResources) {
714             // Skip class resources since they are started below
715             if (list != classResources) {
716                 for (WebResourceSet webResourceSet : list) {
717                     webResourceSet.start();
718                 }
719             }
720         }
721
722         // This has to be called after the other resources have been started
723         // else it won't find all the matching resources
724         processWebInfLib();
725         // Need to start the newly found resources
726         for (WebResourceSet classResource : classResources) {
727             classResource.start();
728         }
729
730         cache.enforceObjectMaxSizeLimit();
731
732         setState(LifecycleState.STARTING);
733     }
734
735     protected WebResourceSet createMainResourceSet() {
736         String docBase = context.getDocBase();
737
738         WebResourceSet mainResourceSet;
739         if (docBase == null) {
740             mainResourceSet = new EmptyResourceSet(this);
741         } else {
742             File f = new File(docBase);
743             if (!f.isAbsolute()) {
744                 f = new File(((Host)context.getParent()).getAppBaseFile(), f.getPath());
745             }
746             if (f.isDirectory()) {
747                 mainResourceSet = new DirResourceSet(this"/", f.getAbsolutePath(), "/");
748             } else if(f.isFile() && docBase.endsWith(".war")) {
749                 mainResourceSet = new WarResourceSet(this"/", f.getAbsolutePath());
750             } else {
751                 throw new IllegalArgumentException(
752                         sm.getString("standardRoot.startInvalidMain",
753                                 f.getAbsolutePath()));
754             }
755         }
756
757         return mainResourceSet;
758     }
759
760     @Override
761     protected void stopInternal() throws LifecycleException {
762         for (List<WebResourceSet> list : allResources) {
763             for (WebResourceSet webResourceSet : list) {
764                 webResourceSet.stop();
765             }
766         }
767
768         if (main != null) {
769             main.destroy();
770         }
771         mainResources.clear();
772
773         for (WebResourceSet webResourceSet : jarResources) {
774             webResourceSet.destroy();
775         }
776         jarResources.clear();
777
778         for (WebResourceSet webResourceSet : classResources) {
779             webResourceSet.destroy();
780         }
781         classResources.clear();
782
783         for (TrackedWebResource trackedResource : trackedResources) {
784             log.error(sm.getString("standardRoot.lockedFile",
785                     context.getName(),
786                     trackedResource.getName()),
787                     trackedResource.getCreatedBy());
788             try {
789                 trackedResource.close();
790             } catch (IOException e) {
791                 // Ignore
792             }
793         }
794         cache.clear();
795
796         setState(LifecycleState.STOPPING);
797     }
798
799     @Override
800     protected void destroyInternal() throws LifecycleException {
801         for (List<WebResourceSet> list : allResources) {
802             for (WebResourceSet webResourceSet : list) {
803                 webResourceSet.destroy();
804             }
805         }
806
807         unregister(cacheJmxName);
808
809         super.destroyInternal();
810     }
811
812
813     // Unit tests need to access this class
814     static class BaseLocation {
815
816         private final String basePath;
817         private final String archivePath;
818
819         BaseLocation(URL url) {
820             File f = null;
821
822             if ("jar".equals(url.getProtocol()) || "war".equals(url.getProtocol())) {
823                 String jarUrl = url.toString();
824                 int endOfFileUrl = -1;
825                 if ("jar".equals(url.getProtocol())) {
826                     endOfFileUrl = jarUrl.indexOf("!/");
827                 } else {
828                     endOfFileUrl = jarUrl.indexOf(UriUtil.getWarSeparator());
829                 }
830                 String fileUrl = jarUrl.substring(4, endOfFileUrl);
831                 try {
832                     f = new File(new URL(fileUrl).toURI());
833                 } catch (MalformedURLException | URISyntaxException e) {
834                     throw new IllegalArgumentException(e);
835                 }
836                 int startOfArchivePath = endOfFileUrl + 2;
837                 if (jarUrl.length() >  startOfArchivePath) {
838                     archivePath = jarUrl.substring(startOfArchivePath);
839                 } else {
840                     archivePath = null;
841                 }
842             } else if ("file".equals(url.getProtocol())){
843                 try {
844                     f = new File(url.toURI());
845                 } catch (URISyntaxException e) {
846                     throw new IllegalArgumentException(e);
847                 }
848                 archivePath = null;
849             } else {
850                 throw new IllegalArgumentException(sm.getString(
851                         "standardRoot.unsupportedProtocol", url.getProtocol()));
852             }
853
854             basePath = f.getAbsolutePath();
855         }
856
857
858         String getBasePath() {
859             return basePath;
860         }
861
862
863         String getArchivePath() {
864             return archivePath;
865         }
866     }
867 }
868