Line data Source code
1 : // Copyright (C) 2010 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.server.tools;
16 :
17 : import static java.nio.charset.StandardCharsets.UTF_8;
18 :
19 : import com.google.common.base.Strings;
20 : import com.google.common.flogger.FluentLogger;
21 : import com.google.gerrit.common.Nullable;
22 : import com.google.gerrit.common.Version;
23 : import com.google.inject.Inject;
24 : import com.google.inject.Singleton;
25 : import java.io.BufferedReader;
26 : import java.io.ByteArrayInputStream;
27 : import java.io.ByteArrayOutputStream;
28 : import java.io.IOException;
29 : import java.io.InputStream;
30 : import java.io.InputStreamReader;
31 : import java.util.ArrayList;
32 : import java.util.Collections;
33 : import java.util.List;
34 : import java.util.NavigableMap;
35 : import java.util.TreeMap;
36 : import org.eclipse.jgit.lib.Constants;
37 : import org.eclipse.jgit.util.RawParseUtils;
38 :
39 : /**
40 : * Listing of all client side tools stored on this server.
41 : *
42 : * <p>Clients may download these tools through our file server, as they are packaged with our own
43 : * software releases.
44 : */
45 : @Singleton
46 : public class ToolsCatalog {
47 138 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
48 :
49 : private final NavigableMap<String, Entry> toc;
50 :
51 : @Inject
52 138 : ToolsCatalog() throws IOException {
53 138 : this.toc = readToc();
54 138 : }
55 :
56 : /**
57 : * Lookup an entry in the tools catalog.
58 : *
59 : * @param name path of the item, relative to the root of the catalog.
60 : * @return the entry; null if the item is not part of the catalog.
61 : */
62 : @Nullable
63 : public Entry get(@Nullable String name) {
64 0 : if (Strings.isNullOrEmpty(name)) {
65 0 : return null;
66 : }
67 0 : if (name.startsWith("/")) {
68 0 : name = name.substring(1);
69 : }
70 0 : if (name.endsWith("/")) {
71 0 : name = name.substring(0, name.length() - 1);
72 : }
73 0 : return toc.get(name);
74 : }
75 :
76 : private static NavigableMap<String, Entry> readToc() throws IOException {
77 138 : NavigableMap<String, Entry> toc = new TreeMap<>();
78 138 : final BufferedReader br =
79 138 : new BufferedReader(new InputStreamReader(new ByteArrayInputStream(read("TOC")), UTF_8));
80 : String line;
81 138 : while ((line = br.readLine()) != null) {
82 138 : if (line.length() > 0 && !line.startsWith("#")) {
83 138 : final Entry e = new Entry(Entry.Type.FILE, line);
84 138 : toc.put(e.getPath(), e);
85 138 : }
86 : }
87 :
88 138 : final List<Entry> all = new ArrayList<>(toc.values());
89 138 : for (Entry e : all) {
90 138 : String path = dirOf(e.getPath());
91 138 : while (path != null) {
92 138 : Entry d = toc.get(path);
93 138 : if (d == null) {
94 138 : d = new Entry(Entry.Type.DIR, 0755, path);
95 138 : toc.put(d.getPath(), d);
96 : }
97 138 : d.children.add(e);
98 138 : path = dirOf(path);
99 138 : e = d;
100 138 : }
101 138 : }
102 :
103 138 : final Entry top = new Entry(Entry.Type.DIR, 0755, "");
104 138 : for (Entry e : toc.values()) {
105 138 : if (dirOf(e.getPath()) == null) {
106 138 : top.children.add(e);
107 : }
108 138 : }
109 138 : toc.put(top.getPath(), top);
110 :
111 138 : return Collections.unmodifiableNavigableMap(toc);
112 : }
113 :
114 : @Nullable
115 : private static byte[] read(String path) {
116 138 : String name = "root/" + path;
117 138 : try (InputStream in = ToolsCatalog.class.getResourceAsStream(name)) {
118 138 : if (in == null) {
119 0 : return null;
120 : }
121 138 : final ByteArrayOutputStream out = new ByteArrayOutputStream();
122 138 : final byte[] buf = new byte[8192];
123 : int n;
124 138 : while ((n = in.read(buf, 0, buf.length)) > 0) {
125 138 : out.write(buf, 0, n);
126 : }
127 138 : return out.toByteArray();
128 0 : } catch (Exception e) {
129 0 : logger.atFine().withCause(e).log("Cannot read %s", path);
130 0 : return null;
131 : }
132 : }
133 :
134 : @Nullable
135 : private static String dirOf(String path) {
136 138 : final int s = path.lastIndexOf('/');
137 138 : return s < 0 ? null : path.substring(0, s);
138 : }
139 :
140 : /** A file served out of the tools root directory. */
141 : public static class Entry {
142 138 : public enum Type {
143 138 : DIR,
144 138 : FILE
145 : }
146 :
147 : private final Type type;
148 : private final int mode;
149 : private final String path;
150 : private final List<Entry> children;
151 :
152 138 : Entry(Type type, String line) {
153 138 : int s = line.indexOf(' ');
154 138 : String mode = line.substring(0, s);
155 138 : String path = line.substring(s + 1);
156 :
157 138 : this.type = type;
158 138 : this.mode = Integer.parseInt(mode, 8);
159 138 : this.path = path;
160 138 : if (type == Type.FILE) {
161 138 : this.children = Collections.emptyList();
162 : } else {
163 0 : this.children = new ArrayList<>();
164 : }
165 138 : }
166 :
167 138 : Entry(Type type, int mode, String path) {
168 138 : this.type = type;
169 138 : this.mode = mode;
170 138 : this.path = path;
171 138 : this.children = new ArrayList<>();
172 138 : }
173 :
174 : public Type getType() {
175 0 : return type;
176 : }
177 :
178 : /** Returns the preferred UNIX file mode, e.g. {@code 0755}. */
179 : public int getMode() {
180 0 : return mode;
181 : }
182 :
183 : /** Returns path of the entry, relative to the catalog root. */
184 : public String getPath() {
185 138 : return path;
186 : }
187 :
188 : /** Returns the name of the entry, within its parent directory. */
189 : public String getName() {
190 0 : final int s = path.lastIndexOf('/');
191 0 : return s < 0 ? path : path.substring(s + 1);
192 : }
193 :
194 : /** Returns collection of entries below this one, if this is a directory. */
195 : public List<Entry> getChildren() {
196 0 : return Collections.unmodifiableList(children);
197 : }
198 :
199 : /** Returns a copy of the file's contents. */
200 : public byte[] getBytes() {
201 0 : byte[] data = read(getPath());
202 :
203 0 : if (isScript(data)) {
204 : // Embed Gerrit's version number into the top of the script.
205 : //
206 0 : final String version = Version.getVersion();
207 0 : final int lf = RawParseUtils.nextLF(data, 0);
208 0 : if (version != null && lf < data.length) {
209 0 : byte[] versionHeader = Constants.encode("# From Gerrit Code Review " + version + "\n");
210 :
211 0 : ByteArrayOutputStream buf = new ByteArrayOutputStream();
212 0 : buf.write(data, 0, lf);
213 0 : buf.write(versionHeader, 0, versionHeader.length);
214 0 : buf.write(data, lf, data.length - lf);
215 0 : data = buf.toByteArray();
216 : }
217 : }
218 :
219 0 : return data;
220 : }
221 :
222 : private boolean isScript(byte[] data) {
223 0 : return data != null
224 : && data.length > 3 //
225 : && data[0] == '#' //
226 : && data[1] == '!' //
227 : && data[2] == '/';
228 : }
229 : }
230 : }
|