View Javadoc
1   /*
2    * Copyright (C) 2008-2009, 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;
12  
13  import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
14  import static org.eclipse.jgit.util.StringUtils.toLowerCase;
15  
16  import java.io.File;
17  import java.util.EnumSet;
18  import java.util.HashMap;
19  import java.util.Map;
20  
21  import org.eclipse.jgit.annotations.Nullable;
22  import org.eclipse.jgit.internal.storage.file.LazyObjectIdSetFile;
23  import org.eclipse.jgit.lib.Config;
24  import org.eclipse.jgit.lib.Config.SectionParser;
25  import org.eclipse.jgit.lib.ObjectChecker;
26  import org.eclipse.jgit.lib.ObjectIdSet;
27  import org.eclipse.jgit.lib.Ref;
28  import org.eclipse.jgit.lib.Repository;
29  import org.eclipse.jgit.util.SystemReader;
30  
31  /**
32   * The standard "transfer", "fetch", "protocol", "receive", and "uploadpack"
33   * configuration parameters.
34   */
35  public class TransferConfig {
36  	private static final String FSCK = "fsck"; //$NON-NLS-1$
37  
38  	/** Key for {@link Config#get(SectionParser)}. */
39  	public static final Config.SectionParser<TransferConfig> KEY =
40  			TransferConfig::new;
41  
42  	/**
43  	 * A git configuration value for how to handle a fsck failure of a particular kind.
44  	 * Used in e.g. fsck.missingEmail.
45  	 * @since 4.9
46  	 */
47  	public enum FsckMode {
48  		/**
49  		 * Treat it as an error (the default).
50  		 */
51  		ERROR,
52  		/**
53  		 * Issue a warning (in fact, jgit treats this like IGNORE, but git itself does warn).
54  		 */
55  		WARN,
56  		/**
57  		 * Ignore the error.
58  		 */
59  		IGNORE;
60  	}
61  
62  	/**
63  	 * A git configuration variable for which versions of the Git protocol to prefer.
64  	 * Used in protocol.version.
65  	 */
66  	enum ProtocolVersion {
67  		V0("0"), //$NON-NLS-1$
68  		V2("2"); //$NON-NLS-1$
69  
70  		final String name;
71  
72  		ProtocolVersion(String name) {
73  			this.name = name;
74  		}
75  
76  		@Nullable
77  		static ProtocolVersion parse(@Nullable String name) {
78  			if (name == null) {
79  				return null;
80  			}
81  			for (ProtocolVersion v : ProtocolVersion.values()) {
82  				if (v.name.equals(name)) {
83  					return v;
84  				}
85  			}
86  			return null;
87  		}
88  	}
89  
90  	private final boolean fetchFsck;
91  	private final boolean receiveFsck;
92  	private final String fsckSkipList;
93  	private final EnumSet<ObjectChecker.ErrorType> ignore;
94  	private final boolean allowInvalidPersonIdent;
95  	private final boolean safeForWindows;
96  	private final boolean safeForMacOS;
97  	private final boolean allowRefInWant;
98  	private final boolean allowTipSha1InWant;
99  	private final boolean allowReachableSha1InWant;
100 	private final boolean allowFilter;
101 	private final boolean allowSidebandAll;
102 	private final boolean advertiseSidebandAll;
103 	final @Nullable ProtocolVersion protocolVersion;
104 	final String[] hideRefs;
105 
106 	/**
107 	 * Create a configuration honoring the repository's settings.
108 	 *
109 	 * @param db
110 	 *            the repository to read settings from. The repository is not
111 	 *            retained by the new configuration, instead its settings are
112 	 *            copied during the constructor.
113 	 * @since 5.1.4
114 	 */
115 	public TransferConfig(Repository db) {
116 		this(db.getConfig());
117 	}
118 
119 	/**
120 	 * Create a configuration honoring settings in a
121 	 * {@link org.eclipse.jgit.lib.Config}.
122 	 *
123 	 * @param rc
124 	 *            the source to read settings from. The source is not retained
125 	 *            by the new configuration, instead its settings are copied
126 	 *            during the constructor.
127 	 * @since 5.1.4
128 	 */
129 	@SuppressWarnings("nls")
130 	public TransferConfig(Config rc) {
131 		boolean fsck = rc.getBoolean("transfer", "fsckobjects", false);
132 		fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck);
133 		receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck);
134 		fsckSkipList = rc.getString(FSCK, null, "skipList");
135 		allowInvalidPersonIdent = rc.getBoolean(FSCK, "allowInvalidPersonIdent",
136 				false);
137 		safeForWindows = rc.getBoolean(FSCK, "safeForWindows",
138 						SystemReader.getInstance().isWindows());
139 		safeForMacOS = rc.getBoolean(FSCK, "safeForMacOS",
140 						SystemReader.getInstance().isMacOS());
141 
142 		ignore = EnumSet.noneOf(ObjectChecker.ErrorType.class);
143 		EnumSet<ObjectChecker.ErrorType> set = EnumSet
144 				.noneOf(ObjectChecker.ErrorType.class);
145 		for (String key : rc.getNames(FSCK)) {
146 			if (equalsIgnoreCase(key, "skipList")
147 					|| equalsIgnoreCase(key, "allowLeadingZeroFileMode")
148 					|| equalsIgnoreCase(key, "allowInvalidPersonIdent")
149 					|| equalsIgnoreCase(key, "safeForWindows")
150 					|| equalsIgnoreCase(key, "safeForMacOS")) {
151 				continue;
152 			}
153 
154 			ObjectChecker.ErrorType id = FsckKeyNameHolder.parse(key);
155 			if (id != null) {
156 				switch (rc.getEnum(FSCK, null, key, FsckMode.ERROR)) {
157 				case ERROR:
158 					ignore.remove(id);
159 					break;
160 				case WARN:
161 				case IGNORE:
162 					ignore.add(id);
163 					break;
164 				}
165 				set.add(id);
166 			}
167 		}
168 		if (!set.contains(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE)
169 				&& rc.getBoolean(FSCK, "allowLeadingZeroFileMode", false)) {
170 			ignore.add(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE);
171 		}
172 
173 		allowRefInWant = rc.getBoolean("uploadpack", "allowrefinwant", false);
174 		allowTipSha1InWant = rc.getBoolean(
175 				"uploadpack", "allowtipsha1inwant", false);
176 		allowReachableSha1InWant = rc.getBoolean(
177 				"uploadpack", "allowreachablesha1inwant", false);
178 		allowFilter = rc.getBoolean(
179 				"uploadpack", "allowfilter", false);
180 		protocolVersion = ProtocolVersion.parse(rc.getString("protocol", null, "version"));
181 		hideRefs = rc.getStringList("uploadpack", null, "hiderefs");
182 		allowSidebandAll = rc.getBoolean(
183 				"uploadpack", "allowsidebandall", false);
184 		advertiseSidebandAll = rc.getBoolean("uploadpack",
185 				"advertisesidebandall", false);
186 	}
187 
188 	/**
189 	 * Create checker to verify fetched objects
190 	 *
191 	 * @return checker to verify fetched objects, or null if checking is not
192 	 *         enabled in the repository configuration.
193 	 * @since 3.6
194 	 */
195 	@Nullable
196 	public ObjectChecker newObjectChecker() {
197 		return newObjectChecker(fetchFsck);
198 	}
199 
200 	/**
201 	 * Create checker to verify objects pushed into this repository
202 	 *
203 	 * @return checker to verify objects pushed into this repository, or null if
204 	 *         checking is not enabled in the repository configuration.
205 	 * @since 4.2
206 	 */
207 	@Nullable
208 	public ObjectChecker newReceiveObjectChecker() {
209 		return newObjectChecker(receiveFsck);
210 	}
211 
212 	private ObjectChecker newObjectChecker(boolean check) {
213 		if (!check) {
214 			return null;
215 		}
216 		return new ObjectChecker()
217 			.setIgnore(ignore)
218 			.setAllowInvalidPersonIdent(allowInvalidPersonIdent)
219 			.setSafeForWindows(safeForWindows)
220 			.setSafeForMacOS(safeForMacOS)
221 			.setSkipList(skipList());
222 	}
223 
224 	private ObjectIdSet skipList() {
225 		if (fsckSkipList != null && !fsckSkipList.isEmpty()) {
226 			return new LazyObjectIdSetFile(new File(fsckSkipList));
227 		}
228 		return null;
229 	}
230 
231 	/**
232 	 * Whether to allow clients to request non-advertised tip SHA-1s
233 	 *
234 	 * @return allow clients to request non-advertised tip SHA-1s?
235 	 * @since 3.1
236 	 */
237 	public boolean isAllowTipSha1InWant() {
238 		return allowTipSha1InWant;
239 	}
240 
241 	/**
242 	 * Whether to allow clients to request non-tip SHA-1s
243 	 *
244 	 * @return allow clients to request non-tip SHA-1s?
245 	 * @since 4.1
246 	 */
247 	public boolean isAllowReachableSha1InWant() {
248 		return allowReachableSha1InWant;
249 	}
250 
251 	/**
252 	 * @return true if clients are allowed to specify a "filter" line
253 	 * @since 5.0
254 	 */
255 	public boolean isAllowFilter() {
256 		return allowFilter;
257 	}
258 
259 	/**
260 	 * @return true if clients are allowed to specify a "want-ref" line
261 	 * @since 5.1
262 	 */
263 	public boolean isAllowRefInWant() {
264 		return allowRefInWant;
265 	}
266 
267 	/**
268 	 * @return true if the server accepts sideband-all requests (see
269 	 *         {{@link #isAdvertiseSidebandAll()} for the advertisement)
270 	 * @since 5.5
271 	 */
272 	public boolean isAllowSidebandAll() {
273 		return allowSidebandAll;
274 	}
275 
276 	/**
277 	 * @return true to advertise sideband all to the clients
278 	 * @since 5.6
279 	 */
280 	public boolean isAdvertiseSidebandAll() {
281 		return advertiseSidebandAll && allowSidebandAll;
282 	}
283 
284 	/**
285 	 * Get {@link org.eclipse.jgit.transport.RefFilter} respecting configured
286 	 * hidden refs.
287 	 *
288 	 * @return {@link org.eclipse.jgit.transport.RefFilter} respecting
289 	 *         configured hidden refs.
290 	 * @since 3.1
291 	 */
292 	public RefFilter getRefFilter() {
293 		if (hideRefs.length == 0)
294 			return RefFilter.DEFAULT;
295 
296 		return new RefFilter() {
297 			@Override
298 			public Map<String, Ref> filter(Map<String, Ref> refs) {
299 				Map<String, Ref> result = new HashMap<>();
300 				for (Map.Entry<String, Ref> e : refs.entrySet()) {
301 					boolean add = true;
302 					for (String hide : hideRefs) {
303 						if (e.getKey().equals(hide) || prefixMatch(hide, e.getKey())) {
304 							add = false;
305 							break;
306 						}
307 					}
308 					if (add)
309 						result.put(e.getKey(), e.getValue());
310 				}
311 				return result;
312 			}
313 
314 			private boolean prefixMatch(String p, String s) {
315 				return p.charAt(p.length() - 1) == '/' && s.startsWith(p);
316 			}
317 		};
318 	}
319 
320 	/**
321 	 * Like {@code getRefFilter() == RefFilter.DEFAULT}, but faster.
322 	 *
323 	 * @return {@code true} if no ref filtering is needed because there
324 	 *         are no configured hidden refs.
325 	 */
326 	boolean hasDefaultRefFilter() {
327 		return hideRefs.length == 0;
328 	}
329 
330 	static class FsckKeyNameHolder {
331 		private static final Map<String, ObjectChecker.ErrorType> errors;
332 
333 		static {
334 			errors = new HashMap<>();
335 			for (ObjectChecker.ErrorType m : ObjectChecker.ErrorType.values()) {
336 				errors.put(keyNameFor(m.name()), m);
337 			}
338 		}
339 
340 		@Nullable
341 		static ObjectChecker.ErrorType parse(String key) {
342 			return errors.get(toLowerCase(key));
343 		}
344 
345 		private static String keyNameFor(String name) {
346 			StringBuilder r = new StringBuilder(name.length());
347 			for (int i = 0; i < name.length(); i++) {
348 				char c = name.charAt(i);
349 				if (c != '_') {
350 					r.append(c);
351 				}
352 			}
353 			return toLowerCase(r.toString());
354 		}
355 
356 		private FsckKeyNameHolder() {
357 		}
358 	}
359 }