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