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.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 			return null;
105 		}
106 	}
107 
108 	private final boolean fetchFsck;
109 	private final boolean receiveFsck;
110 	private final String fsckSkipList;
111 	private final EnumSet<ObjectChecker.ErrorType> ignore;
112 	private final boolean allowInvalidPersonIdent;
113 	private final boolean safeForWindows;
114 	private final boolean safeForMacOS;
115 	private final boolean allowRefInWant;
116 	private final boolean allowTipSha1InWant;
117 	private final boolean allowReachableSha1InWant;
118 	private final boolean allowFilter;
119 	private final boolean allowSidebandAll;
120 	private final boolean advertiseSidebandAll;
121 	final @Nullable ProtocolVersion protocolVersion;
122 	final String[] hideRefs;
123 
124 	/**
125 	 * Create a configuration honoring the repository's settings.
126 	 *
127 	 * @param db
128 	 *            the repository to read settings from. The repository is not
129 	 *            retained by the new configuration, instead its settings are
130 	 *            copied during the constructor.
131 	 * @since 5.1.4
132 	 */
133 	public TransferConfig(Repository db) {
134 		this(db.getConfig());
135 	}
136 
137 	/**
138 	 * Create a configuration honoring settings in a
139 	 * {@link org.eclipse.jgit.lib.Config}.
140 	 *
141 	 * @param rc
142 	 *            the source to read settings from. The source is not retained
143 	 *            by the new configuration, instead its settings are copied
144 	 *            during the constructor.
145 	 * @since 5.1.4
146 	 */
147 	@SuppressWarnings("nls")
148 	public TransferConfig(Config rc) {
149 		boolean fsck = rc.getBoolean("transfer", "fsckobjects", false);
150 		fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck);
151 		receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck);
152 		fsckSkipList = rc.getString(FSCK, null, "skipList");
153 		allowInvalidPersonIdent = rc.getBoolean(FSCK, "allowInvalidPersonIdent",
154 				false);
155 		safeForWindows = rc.getBoolean(FSCK, "safeForWindows",
156 						SystemReader.getInstance().isWindows());
157 		safeForMacOS = rc.getBoolean(FSCK, "safeForMacOS",
158 						SystemReader.getInstance().isMacOS());
159 
160 		ignore = EnumSet.noneOf(ObjectChecker.ErrorType.class);
161 		EnumSet<ObjectChecker.ErrorType> set = EnumSet
162 				.noneOf(ObjectChecker.ErrorType.class);
163 		for (String key : rc.getNames(FSCK)) {
164 			if (equalsIgnoreCase(key, "skipList")
165 					|| equalsIgnoreCase(key, "allowLeadingZeroFileMode")
166 					|| equalsIgnoreCase(key, "allowInvalidPersonIdent")
167 					|| equalsIgnoreCase(key, "safeForWindows")
168 					|| equalsIgnoreCase(key, "safeForMacOS")) {
169 				continue;
170 			}
171 
172 			ObjectChecker.ErrorType id = FsckKeyNameHolder.parse(key);
173 			if (id != null) {
174 				switch (rc.getEnum(FSCK, null, key, FsckMode.ERROR)) {
175 				case ERROR:
176 					ignore.remove(id);
177 					break;
178 				case WARN:
179 				case IGNORE:
180 					ignore.add(id);
181 					break;
182 				}
183 				set.add(id);
184 			}
185 		}
186 		if (!set.contains(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE)
187 				&& rc.getBoolean(FSCK, "allowLeadingZeroFileMode", false)) {
188 			ignore.add(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE);
189 		}
190 
191 		allowRefInWant = rc.getBoolean("uploadpack", "allowrefinwant", false);
192 		allowTipSha1InWant = rc.getBoolean(
193 				"uploadpack", "allowtipsha1inwant", false);
194 		allowReachableSha1InWant = rc.getBoolean(
195 				"uploadpack", "allowreachablesha1inwant", false);
196 		allowFilter = rc.getBoolean(
197 				"uploadpack", "allowfilter", false);
198 		protocolVersion = ProtocolVersion.parse(rc
199 				.getString(ConfigConstants.CONFIG_PROTOCOL_SECTION, null,
200 						ConfigConstants.CONFIG_KEY_VERSION));
201 		hideRefs = rc.getStringList("uploadpack", null, "hiderefs");
202 		allowSidebandAll = rc.getBoolean(
203 				"uploadpack", "allowsidebandall", false);
204 		advertiseSidebandAll = rc.getBoolean("uploadpack",
205 				"advertisesidebandall", false);
206 	}
207 
208 	/**
209 	 * Create checker to verify fetched objects
210 	 *
211 	 * @return checker to verify fetched objects, or null if checking is not
212 	 *         enabled in the repository configuration.
213 	 * @since 3.6
214 	 */
215 	@Nullable
216 	public ObjectChecker newObjectChecker() {
217 		return newObjectChecker(fetchFsck);
218 	}
219 
220 	/**
221 	 * Create checker to verify objects pushed into this repository
222 	 *
223 	 * @return checker to verify objects pushed into this repository, or null if
224 	 *         checking is not enabled in the repository configuration.
225 	 * @since 4.2
226 	 */
227 	@Nullable
228 	public ObjectChecker newReceiveObjectChecker() {
229 		return newObjectChecker(receiveFsck);
230 	}
231 
232 	private ObjectChecker newObjectChecker(boolean check) {
233 		if (!check) {
234 			return null;
235 		}
236 		return new ObjectChecker()
237 			.setIgnore(ignore)
238 			.setAllowInvalidPersonIdent(allowInvalidPersonIdent)
239 			.setSafeForWindows(safeForWindows)
240 			.setSafeForMacOS(safeForMacOS)
241 			.setSkipList(skipList());
242 	}
243 
244 	private ObjectIdSet skipList() {
245 		if (fsckSkipList != null && !fsckSkipList.isEmpty()) {
246 			return new LazyObjectIdSetFile(new File(fsckSkipList));
247 		}
248 		return null;
249 	}
250 
251 	/**
252 	 * Whether to allow clients to request non-advertised tip SHA-1s
253 	 *
254 	 * @return allow clients to request non-advertised tip SHA-1s?
255 	 * @since 3.1
256 	 */
257 	public boolean isAllowTipSha1InWant() {
258 		return allowTipSha1InWant;
259 	}
260 
261 	/**
262 	 * Whether to allow clients to request non-tip SHA-1s
263 	 *
264 	 * @return allow clients to request non-tip SHA-1s?
265 	 * @since 4.1
266 	 */
267 	public boolean isAllowReachableSha1InWant() {
268 		return allowReachableSha1InWant;
269 	}
270 
271 	/**
272 	 * @return true if clients are allowed to specify a "filter" line
273 	 * @since 5.0
274 	 */
275 	public boolean isAllowFilter() {
276 		return allowFilter;
277 	}
278 
279 	/**
280 	 * @return true if clients are allowed to specify a "want-ref" line
281 	 * @since 5.1
282 	 */
283 	public boolean isAllowRefInWant() {
284 		return allowRefInWant;
285 	}
286 
287 	/**
288 	 * @return true if the server accepts sideband-all requests (see
289 	 *         {{@link #isAdvertiseSidebandAll()} for the advertisement)
290 	 * @since 5.5
291 	 */
292 	public boolean isAllowSidebandAll() {
293 		return allowSidebandAll;
294 	}
295 
296 	/**
297 	 * @return true to advertise sideband all to the clients
298 	 * @since 5.6
299 	 */
300 	public boolean isAdvertiseSidebandAll() {
301 		return advertiseSidebandAll && allowSidebandAll;
302 	}
303 
304 	/**
305 	 * Get {@link org.eclipse.jgit.transport.RefFilter} respecting configured
306 	 * hidden refs.
307 	 *
308 	 * @return {@link org.eclipse.jgit.transport.RefFilter} respecting
309 	 *         configured hidden refs.
310 	 * @since 3.1
311 	 */
312 	public RefFilter getRefFilter() {
313 		if (hideRefs.length == 0)
314 			return RefFilter.DEFAULT;
315 
316 		return new RefFilter() {
317 			@Override
318 			public Map<String, Ref> filter(Map<String, Ref> refs) {
319 				Map<String, Ref> result = new HashMap<>();
320 				for (Map.Entry<String, Ref> e : refs.entrySet()) {
321 					boolean add = true;
322 					for (String hide : hideRefs) {
323 						if (e.getKey().equals(hide) || prefixMatch(hide, e.getKey())) {
324 							add = false;
325 							break;
326 						}
327 					}
328 					if (add)
329 						result.put(e.getKey(), e.getValue());
330 				}
331 				return result;
332 			}
333 
334 			private boolean prefixMatch(String p, String s) {
335 				return p.charAt(p.length() - 1) == '/' && s.startsWith(p);
336 			}
337 		};
338 	}
339 
340 	/**
341 	 * Like {@code getRefFilter() == RefFilter.DEFAULT}, but faster.
342 	 *
343 	 * @return {@code true} if no ref filtering is needed because there
344 	 *         are no configured hidden refs.
345 	 */
346 	boolean hasDefaultRefFilter() {
347 		return hideRefs.length == 0;
348 	}
349 
350 	static class FsckKeyNameHolder {
351 		private static final Map<String, ObjectChecker.ErrorType> errors;
352 
353 		static {
354 			errors = new HashMap<>();
355 			for (ObjectChecker.ErrorType m : ObjectChecker.ErrorType.values()) {
356 				errors.put(keyNameFor(m.name()), m);
357 			}
358 		}
359 
360 		@Nullable
361 		static ObjectChecker.ErrorType parse(String key) {
362 			return errors.get(toLowerCase(key));
363 		}
364 
365 		private static String keyNameFor(String name) {
366 			StringBuilder r = new StringBuilder(name.length());
367 			for (int i = 0; i < name.length(); i++) {
368 				char c = name.charAt(i);
369 				if (c != '_') {
370 					r.append(c);
371 				}
372 			}
373 			return toLowerCase(r.toString());
374 		}
375 
376 		private FsckKeyNameHolder() {
377 		}
378 	}
379 }