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