View Javadoc
1   /*
2    * Copyright (C) 2009-2010, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.transport.resolver;
12  
13  import java.io.File;
14  import java.io.IOException;
15  import java.util.Collection;
16  import java.util.Map;
17  import java.util.concurrent.ConcurrentHashMap;
18  import java.util.concurrent.CopyOnWriteArrayList;
19  
20  import org.eclipse.jgit.errors.RepositoryNotFoundException;
21  import org.eclipse.jgit.lib.Constants;
22  import org.eclipse.jgit.lib.Repository;
23  import org.eclipse.jgit.lib.RepositoryCache;
24  import org.eclipse.jgit.lib.RepositoryCache.FileKey;
25  import org.eclipse.jgit.util.FS;
26  
27  /**
28   * Default resolver serving from the local filesystem.
29   *
30   * @param <C>
31   *            type of connection
32   */
33  public class FileResolver<C> implements RepositoryResolver<C> {
34  	private volatile boolean exportAll;
35  
36  	private final Map<String, Repository> exports;
37  
38  	private final Collection<File> exportBase;
39  
40  	/**
41  	 * Initialize an empty file based resolver.
42  	 */
43  	public FileResolver() {
44  		exports = new ConcurrentHashMap<>();
45  		exportBase = new CopyOnWriteArrayList<>();
46  	}
47  
48  	/**
49  	 * Create a new resolver for the given path.
50  	 *
51  	 * @param basePath
52  	 *            the base path all repositories are rooted under.
53  	 * @param exportAll
54  	 *            if true, exports all repositories, ignoring the check for the
55  	 *            {@code git-daemon-export-ok} files.
56  	 */
57  	public FileResolver(File basePath, boolean exportAll) {
58  		this();
59  		exportDirectory(basePath);
60  		setExportAll(exportAll);
61  	}
62  
63  	/** {@inheritDoc} */
64  	@Override
65  	public Repository open(C req, String name)
66  			throws RepositoryNotFoundException, ServiceNotEnabledException {
67  		if (isUnreasonableName(name))
68  			throw new RepositoryNotFoundException(name);
69  
70  		Repository db = exports.get(nameWithDotGit(name));
71  		if (db != null) {
72  			db.incrementOpen();
73  			return db;
74  		}
75  
76  		for (File base : exportBase) {
77  			File dir = FileKey.resolve(new File(base, name), FS.DETECTED);
78  			if (dir == null)
79  				continue;
80  
81  			try {
82  				FileKey key = FileKey.exact(dir, FS.DETECTED);
83  				db = RepositoryCache.open(key, true);
84  			} catch (IOException e) {
85  				throw new RepositoryNotFoundException(name, e);
86  			}
87  
88  			try {
89  				if (isExportOk(req, name, db)) {
90  					// We have to leak the open count to the caller, they
91  					// are responsible for closing the repository if we
92  					// complete successfully.
93  					return db;
94  				}
95  				throw new ServiceNotEnabledException();
96  
97  			} catch (RuntimeException | IOException e) {
98  				db.close();
99  				throw new RepositoryNotFoundException(name, e);
100 
101 			} catch (ServiceNotEnabledException e) {
102 				db.close();
103 				throw e;
104 			}
105 		}
106 
107 		if (exportBase.size() == 1) {
108 			File dir = new File(exportBase.iterator().next(), name);
109 			throw new RepositoryNotFoundException(name,
110 					new RepositoryNotFoundException(dir));
111 		}
112 
113 		throw new RepositoryNotFoundException(name);
114 	}
115 
116 	/**
117 	 * Whether <code>git-daemon-export-ok</code> is required to export a
118 	 * repository
119 	 *
120 	 * @return false if <code>git-daemon-export-ok</code> is required to export
121 	 *         a repository; true if <code>git-daemon-export-ok</code> is
122 	 *         ignored.
123 	 * @see #setExportAll(boolean)
124 	 */
125 	public boolean isExportAll() {
126 		return exportAll;
127 	}
128 
129 	/**
130 	 * Set whether or not to export all repositories.
131 	 * <p>
132 	 * If false (the default), repositories must have a
133 	 * <code>git-daemon-export-ok</code> file to be accessed through this
134 	 * daemon.
135 	 * <p>
136 	 * If true, all repositories are available through the daemon, whether or
137 	 * not <code>git-daemon-export-ok</code> exists.
138 	 *
139 	 * @param export a boolean.
140 	 */
141 	public void setExportAll(boolean export) {
142 		exportAll = export;
143 	}
144 
145 	/**
146 	 * Add a single repository to the set that is exported by this daemon.
147 	 * <p>
148 	 * The existence (or lack-thereof) of <code>git-daemon-export-ok</code> is
149 	 * ignored by this method. The repository is always published.
150 	 *
151 	 * @param name
152 	 *            name the repository will be published under.
153 	 * @param db
154 	 *            the repository instance.
155 	 */
156 	public void exportRepository(String name, Repository db) {
157 		exports.put(nameWithDotGit(name), db);
158 	}
159 
160 	/**
161 	 * Recursively export all Git repositories within a directory.
162 	 *
163 	 * @param dir
164 	 *            the directory to export. This directory must not itself be a
165 	 *            git repository, but any directory below it which has a file
166 	 *            named <code>git-daemon-export-ok</code> will be published.
167 	 */
168 	public void exportDirectory(File dir) {
169 		exportBase.add(dir);
170 	}
171 
172 	/**
173 	 * Check if this repository can be served.
174 	 * <p>
175 	 * The default implementation of this method returns true only if either
176 	 * {@link #isExportAll()} is true, or the {@code git-daemon-export-ok} file
177 	 * is present in the repository's directory.
178 	 *
179 	 * @param req
180 	 *            the current HTTP request.
181 	 * @param repositoryName
182 	 *            name of the repository, as present in the URL.
183 	 * @param db
184 	 *            the opened repository instance.
185 	 * @return true if the repository is accessible; false if not.
186 	 * @throws java.io.IOException
187 	 *             the repository could not be accessed, the caller will claim
188 	 *             the repository does not exist.
189 	 */
190 	protected boolean isExportOk(C req, String repositoryName, Repository db)
191 			throws IOException {
192 		if (isExportAll())
193 			return true;
194 		else if (db.getDirectory() != null)
195 			return new File(db.getDirectory(), "git-daemon-export-ok").exists(); //$NON-NLS-1$
196 		else
197 			return false;
198 	}
199 
200 	private static String nameWithDotGit(String name) {
201 		if (name.endsWith(Constants.DOT_GIT_EXT))
202 			return name;
203 		return name + Constants.DOT_GIT_EXT;
204 	}
205 
206 	private static boolean isUnreasonableName(String name) {
207 		if (name.length() == 0)
208 			return true; // no empty paths
209 
210 		if (name.indexOf('\\') >= 0)
211 			return true; // no windows/dos style paths
212 		if (new File(name).isAbsolute())
213 			return true; // no absolute paths
214 
215 		if (name.startsWith("../")) //$NON-NLS-1$
216 			return true; // no "l../etc/passwd"
217 		if (name.contains("/../")) //$NON-NLS-1$
218 			return true; // no "foo/../etc/passwd"
219 		if (name.contains("/./")) //$NON-NLS-1$
220 			return true; // "foo/./foo" is insane to ask
221 		if (name.contains("//")) //$NON-NLS-1$
222 			return true; // double slashes is sloppy, don't use it
223 
224 		return false; // is a reasonable name
225 	}
226 }