View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.index.artifact;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  
24  import java.text.ParseException;
25  import java.text.SimpleDateFormat;
26  
27  /**
28   * An M2 <code>GavCalculator</code>.
29   *
30   * @author Jason van Zyl
31   * @author Tamas Cservenak
32   */
33  @Singleton
34  @Named("maven2")
35  public class M2GavCalculator implements GavCalculator {
36      public Gav pathToGav(String str) {
37          try {
38              String s = str.startsWith("/") ? str.substring(1) : str;
39  
40              int vEndPos = s.lastIndexOf('/');
41  
42              if (vEndPos == -1) {
43                  return null;
44              }
45  
46              int aEndPos = s.lastIndexOf('/', vEndPos - 1);
47  
48              if (aEndPos == -1) {
49                  return null;
50              }
51  
52              int gEndPos = s.lastIndexOf('/', aEndPos - 1);
53  
54              if (gEndPos == -1) {
55                  return null;
56              }
57  
58              String groupId = s.substring(0, gEndPos).replace('/', '.');
59              String artifactId = s.substring(gEndPos + 1, aEndPos);
60              String version = s.substring(aEndPos + 1, vEndPos);
61              String fileName = s.substring(vEndPos + 1);
62  
63              boolean checksum = false;
64              boolean signature = false;
65              Gav.HashType checksumType = null;
66              Gav.SignatureType signatureType = null;
67              if (s.endsWith(".md5")) {
68                  checksum = true;
69                  checksumType = Gav.HashType.md5;
70                  s = s.substring(0, s.length() - 4);
71              } else if (s.endsWith(".sha1")) {
72                  checksum = true;
73                  checksumType = Gav.HashType.sha1;
74                  s = s.substring(0, s.length() - 5);
75              } else if (s.endsWith(".sha256")) {
76                  checksum = true;
77                  checksumType = Gav.HashType.sha256;
78                  s = s.substring(0, s.length() - 7);
79              } else if (s.endsWith(".sha512")) {
80                  checksum = true;
81                  checksumType = Gav.HashType.sha512;
82                  s = s.substring(0, s.length() - 7);
83              }
84  
85              if (s.endsWith(".asc")) {
86                  signature = true;
87                  signatureType = Gav.SignatureType.gpg;
88                  s = s.substring(0, s.length() - 4);
89              }
90  
91              if (s.endsWith("maven-metadata.xml")
92                      || (fileName.startsWith("maven-metadata-") && fileName.contains(".xml"))
93                      || (fileName.startsWith("_") && fileName.endsWith(".repositories"))) {
94                  return null;
95              }
96  
97              boolean snapshot = version.endsWith("SNAPSHOT");
98  
99              if (snapshot) {
100                 return getSnapshotGav(
101                         s,
102                         vEndPos,
103                         groupId,
104                         artifactId,
105                         version,
106                         fileName,
107                         checksum,
108                         signature,
109                         checksumType,
110                         signatureType);
111             } else {
112                 return getReleaseGav(
113                         s,
114                         vEndPos,
115                         groupId,
116                         artifactId,
117                         version,
118                         fileName,
119                         checksum,
120                         signature,
121                         checksumType,
122                         signatureType);
123             }
124         } catch (NumberFormatException | StringIndexOutOfBoundsException e) {
125             return null;
126         }
127     }
128 
129     private Gav getReleaseGav(
130             String s,
131             int vEndPos,
132             String groupId,
133             String artifactId,
134             String version,
135             String fileName,
136             boolean checksum,
137             boolean signature,
138             Gav.HashType checksumType,
139             Gav.SignatureType signatureType) {
140         if (!fileName.startsWith(artifactId + "-" + version + ".")
141                 && !fileName.startsWith(artifactId + "-" + version + "-")) {
142             // The path does not represents an artifact (filename does not match artifactId-version)!
143             return null;
144         }
145 
146         int nTailPos = vEndPos + artifactId.length() + version.length() + 2;
147 
148         String tail = s.substring(nTailPos);
149 
150         int nExtPos = tail.indexOf('.');
151 
152         if (nExtPos == -1) {
153             // NX-563: not allowing extensionless paths to be interpreted as artifact
154             return null;
155         }
156 
157         String ext = tail.substring(nExtPos + 1);
158 
159         String classifier = tail.charAt(0) == '-' ? tail.substring(1, nExtPos) : null;
160 
161         return new Gav(
162                 groupId,
163                 artifactId,
164                 version,
165                 classifier,
166                 ext,
167                 null,
168                 null,
169                 fileName,
170                 checksum,
171                 checksumType,
172                 signature,
173                 signatureType);
174     }
175 
176     private Gav getSnapshotGav(
177             String s,
178             int vEndPos,
179             String groupId,
180             String artifactId,
181             String version,
182             String fileName,
183             boolean checksum,
184             boolean signature,
185             Gav.HashType checksumType,
186             Gav.SignatureType signatureType) {
187 
188         Integer snapshotBuildNo = null;
189 
190         Long snapshotTimestamp = null;
191 
192         int vSnapshotStart = vEndPos + artifactId.length() + version.length() - 9 + 3;
193 
194         String vSnapshot = s.substring(vSnapshotStart, vSnapshotStart + 8);
195 
196         String classifier;
197 
198         String ext;
199 
200         if ("SNAPSHOT".equals(vSnapshot)) {
201             int nTailPos = vEndPos + artifactId.length() + version.length() + 2;
202 
203             String tail = s.substring(nTailPos);
204 
205             int nExtPos = tail.indexOf('.');
206 
207             if (nExtPos == -1) {
208                 // NX-563: not allowing extensionless paths to be interpreted as artifact
209                 return null;
210             }
211 
212             ext = tail.substring(nExtPos + 1);
213 
214             classifier = tail.charAt(0) == '-' ? tail.substring(1, nExtPos) : null;
215         } else {
216             StringBuilder sb = new StringBuilder(vSnapshot);
217             sb.append(s, vSnapshotStart + sb.length(), vSnapshotStart + sb.length() + 8);
218 
219             try {
220                 SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd.HHmmss");
221                 snapshotTimestamp = df.parse(sb.toString()).getTime();
222             } catch (ParseException e) {
223             }
224 
225             int buildNumberPos = vSnapshotStart + sb.length();
226             StringBuilder bnr = new StringBuilder();
227             while (s.charAt(buildNumberPos) >= '0' && s.charAt(buildNumberPos) <= '9') {
228                 sb.append(s.charAt(buildNumberPos));
229                 bnr.append(s.charAt(buildNumberPos));
230                 buildNumberPos++;
231             }
232             String snapshotBuildNumber = sb.toString();
233             snapshotBuildNo = Integer.parseInt(bnr.toString());
234 
235             int n = version.length() > 9 ? version.length() - 9 + 1 : 0;
236 
237             String tail = s.substring(vEndPos + artifactId.length() + n + snapshotBuildNumber.length() + 2);
238 
239             int nExtPos = tail.indexOf('.');
240 
241             if (nExtPos == -1) {
242                 // NX-563: not allowing extensionless paths to be interpreted as artifact
243                 return null;
244             }
245 
246             ext = tail.substring(nExtPos + 1);
247 
248             classifier = tail.charAt(0) == '-' ? tail.substring(1, nExtPos) : null;
249 
250             version = version.substring(0, version.length() - 8) + snapshotBuildNumber;
251         }
252 
253         return new Gav(
254                 groupId,
255                 artifactId,
256                 version,
257                 classifier,
258                 ext,
259                 snapshotBuildNo,
260                 snapshotTimestamp,
261                 fileName,
262                 checksum,
263                 checksumType,
264                 signature,
265                 signatureType);
266     }
267 
268     public String gavToPath(Gav gav) {
269 
270         return "/" + gav.getGroupId().replaceAll("(?m)(.)\\.", "$1/") // replace all '.' except the first char
271                 + "/" + gav.getArtifactId() + "/" + gav.getBaseVersion() + "/" + calculateArtifactName(gav);
272     }
273 
274     public String calculateArtifactName(Gav gav) {
275         if (gav.getName() != null && !gav.getName().isBlank()) {
276             return gav.getName();
277         } else {
278             StringBuilder path = new StringBuilder(gav.getArtifactId());
279 
280             path.append("-");
281 
282             path.append(gav.getVersion());
283 
284             if (gav.getClassifier() != null && !gav.getClassifier().isBlank()) {
285                 path.append("-");
286 
287                 path.append(gav.getClassifier());
288             }
289 
290             if (gav.getExtension() != null) {
291                 path.append(".");
292 
293                 path.append(gav.getExtension());
294             }
295 
296             if (gav.isSignature()) {
297                 path.append(".");
298 
299                 path.append(gav.getSignatureType().toString());
300             }
301 
302             if (gav.isHash()) {
303                 path.append(".");
304 
305                 path.append(gav.getHashType().toString());
306             }
307 
308             return path.toString();
309         }
310     }
311 }