// Copyright (C) 2010 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.gerrit.server.tools; import com.google.gerrit.common.Version; import com.google.inject.Inject; import com.google.inject.Singleton; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.util.RawParseUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; /** * Listing of all client side tools stored on this server. * <p> * Clients may download these tools through our file server, as they are * packaged with our own software releases. */ @Singleton public class ToolsCatalog { private static final Logger log = LoggerFactory.getLogger(ToolsCatalog.class); private final SortedMap<String, Entry> toc; @Inject ToolsCatalog() throws IOException { this.toc = readToc(); } /** * Lookup an entry in the tools catalog. * * @param name path of the item, relative to the root of the catalog. * @return the entry; null if the item is not part of the catalog. */ public Entry get(String name) { if (name.startsWith("/")) { name = name.substring(1); } if (name.endsWith("/")) { name = name.substring(0, name.length() - 1); } return toc.get(name); } private static SortedMap<String, Entry> readToc() throws IOException { SortedMap<String, Entry> toc = new TreeMap<>(); final BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream( read("TOC")), "UTF-8")); String line; while ((line = br.readLine()) != null) { if (line.length() > 0 && !line.startsWith("#")) { final Entry e = new Entry(Entry.Type.FILE, line); toc.put(e.getPath(), e); } } final List<Entry> all = new ArrayList<>(toc.values()); for (Entry e : all) { String path = dirOf(e.getPath()); while (path != null) { Entry d = toc.get(path); if (d == null) { d = new Entry(Entry.Type.DIR, 0755, path); toc.put(d.getPath(), d); } d.children.add(e); path = dirOf(path); e = d; } } final Entry top = new Entry(Entry.Type.DIR, 0755, ""); for (Entry e : toc.values()) { if (dirOf(e.getPath()) == null) { top.children.add(e); } } toc.put(top.getPath(), top); return Collections.unmodifiableSortedMap(toc); } private static byte[] read(String path) { String name = "root/" + path; InputStream in = ToolsCatalog.class.getResourceAsStream(name); if (in == null) { return null; } try { final ByteArrayOutputStream out = new ByteArrayOutputStream(); try { final byte[] buf = new byte[8192]; int n; while ((n = in.read(buf, 0, buf.length)) > 0) { out.write(buf, 0, n); } } finally { in.close(); } return out.toByteArray(); } catch (Exception e) { log.debug("Cannot read " + path, e); return null; } } private static String dirOf(String path) { final int s = path.lastIndexOf('/'); return s < 0 ? null : path.substring(0, s); } /** A file served out of the tools root directory. */ public static class Entry { public static enum Type { DIR, FILE } private final Type type; private final int mode; private final String path; private final List<Entry> children; Entry(Type type, String line) { int s = line.indexOf(' '); String mode = line.substring(0, s); String path = line.substring(s + 1); this.type = type; this.mode = Integer.parseInt(mode, 8); this.path = path; if (type == Type.FILE) { this.children = Collections.emptyList(); } else { this.children = new ArrayList<>(); } } Entry(Type type, int mode, String path) { this.type = type; this.mode = mode; this.path = path; this.children = new ArrayList<>(); } public Type getType() { return type; } /** @return the preferred UNIX file mode, e.g. {@code 0755}. */ public int getMode() { return mode; } /** @return path of the entry, relative to the catalog root. */ public String getPath() { return path; } /** @return name of the entry, within its parent directory. */ public String getName() { final int s = path.lastIndexOf('/'); return s < 0 ? path : path.substring(s + 1); } /** @return collection of entries below this one, if this is a directory. */ public List<Entry> getChildren() { return Collections.unmodifiableList(children); } /** @return a copy of the file's contents. */ public byte[] getBytes() { byte[] data = read(getPath()); if (isScript(data)) { // Embed Gerrit's version number into the top of the script. // final String version = Version.getVersion(); final int lf = RawParseUtils.nextLF(data, 0); if (version != null && lf < data.length) { byte[] versionHeader = Constants.encode("# From Gerrit Code Review " + version + "\n"); ByteArrayOutputStream buf = new ByteArrayOutputStream(); buf.write(data, 0, lf); buf.write(versionHeader, 0, versionHeader.length); buf.write(data, lf, data.length - lf); data = buf.toByteArray(); } } return data; } private boolean isScript(byte[] data) { return data != null && data.length > 3 // && data[0] == '#' // && data[1] == '!' // && data[2] == '/'; } } }