View Javadoc
1   /*
2    * Copyright (C) 2008, 2010 Google Inc.
3    * Copyright (C) 2008, 2009 Robin Rosenberg <robin.rosenberg@dewire.com>
4    * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
5    *
6    * This program and the accompanying materials are made available under the
7    * terms of the Eclipse Distribution License v. 1.0 which is available at
8    * https://www.eclipse.org/org/documents/edl-v10.php.
9    *
10   * SPDX-License-Identifier: BSD-3-Clause
11   */
12  
13  package org.eclipse.jgit.transport;
14  
15  import static java.nio.charset.StandardCharsets.UTF_8;
16  
17  import java.io.IOException;
18  import java.io.OutputStream;
19  
20  import org.eclipse.jgit.lib.Constants;
21  import org.eclipse.jgit.util.RawParseUtils;
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  /**
26   * Write Git style pkt-line formatting to an output stream.
27   * <p>
28   * This class is not thread safe and may issue multiple writes to the underlying
29   * stream for each method call made.
30   * <p>
31   * This class performs no buffering on its own. This makes it suitable to
32   * interleave writes performed by this class with writes performed directly
33   * against the underlying OutputStream.
34   */
35  public class PacketLineOut {
36  
37  	private static final Logger log = LoggerFactory.getLogger(PacketLineOut.class);
38  
39  	private final OutputStream out;
40  
41  	private final byte[] lenbuffer;
42  
43  	private final boolean logEnabled;
44  
45  	private boolean flushOnEnd;
46  
47  	private boolean usingSideband;
48  
49  	/**
50  	 * Create a new packet line writer.
51  	 *
52  	 * @param outputStream
53  	 *            stream.
54  	 */
55  	public PacketLineOut(OutputStream outputStream) {
56  		this(outputStream, true);
57  	}
58  
59  	/**
60  	 * Create a new packet line writer that potentially doesn't log.
61  	 *
62  	 * @param outputStream
63  	 *            stream.
64  	 * @param enableLogging
65  	 *            {@code false} to suppress all logging; {@code true} to log
66  	 *            normally
67  	 * @since 5.11
68  	 */
69  	public PacketLineOut(OutputStream outputStream, boolean enableLogging) {
70  		out = outputStream;
71  		lenbuffer = new byte[5];
72  		flushOnEnd = true;
73  		logEnabled = enableLogging;
74  	}
75  
76  	/**
77  	 * Set the flush behavior during {@link #end()}.
78  	 *
79  	 * @param flushOnEnd
80  	 *            if true, a flush-pkt written during {@link #end()} also
81  	 *            flushes the underlying stream.
82  	 */
83  	public void setFlushOnEnd(boolean flushOnEnd) {
84  		this.flushOnEnd = flushOnEnd;
85  	}
86  
87  	/**
88  	 * @return whether to add a sideband designator to each non-flush and
89  	 *     non-delim packet
90  	 * @see #setUsingSideband
91  	 * @since 5.5
92  	 */
93  	public boolean isUsingSideband() {
94  		return usingSideband;
95  	}
96  
97  	/**
98  	 * @param value If true, when writing packet lines, add, as the first
99  	 *     byte, a sideband designator to each non-flush and non-delim
100 	 *     packet. See pack-protocol.txt and protocol-v2.txt from the Git
101 	 *     project for more information, specifically the "side-band" and
102 	 *     "sideband-all" sections.
103 	 * @since 5.5
104 	 */
105 	public void setUsingSideband(boolean value) {
106 		this.usingSideband = value;
107 	}
108 
109 	/**
110 	 * Write a UTF-8 encoded string as a single length-delimited packet.
111 	 *
112 	 * @param s
113 	 *            string to write.
114 	 * @throws java.io.IOException
115 	 *             the packet could not be written, the stream is corrupted as
116 	 *             the packet may have been only partially written.
117 	 */
118 	public void writeString(String s) throws IOException {
119 		writePacket(Constants.encode(s));
120 	}
121 
122 	/**
123 	 * Write a binary packet to the stream.
124 	 *
125 	 * @param packet
126 	 *            the packet to write; the length of the packet is equal to the
127 	 *            size of the byte array.
128 	 * @throws java.io.IOException
129 	 *             the packet could not be written, the stream is corrupted as
130 	 *             the packet may have been only partially written.
131 	 */
132 	public void writePacket(byte[] packet) throws IOException {
133 		writePacket(packet, 0, packet.length);
134 	}
135 
136 	/**
137 	 * Write a binary packet to the stream.
138 	 *
139 	 * @param buf
140 	 *            the packet to write
141 	 * @param pos
142 	 *            first index within {@code buf}.
143 	 * @param len
144 	 *            number of bytes to write.
145 	 * @throws java.io.IOException
146 	 *             the packet could not be written, the stream is corrupted as
147 	 *             the packet may have been only partially written.
148 	 * @since 4.5
149 	 */
150 	public void writePacket(byte[] buf, int pos, int len) throws IOException {
151 		if (usingSideband) {
152 			formatLength(len + 5);
153 			out.write(lenbuffer, 0, 4);
154 			out.write(1);
155 		} else {
156 			formatLength(len + 4);
157 			out.write(lenbuffer, 0, 4);
158 		}
159 		out.write(buf, pos, len);
160 		if (logEnabled && log.isDebugEnabled()) {
161 			// Escape a trailing \n to avoid empty lines in the log.
162 			if (len > 0 && buf[pos + len - 1] == '\n') {
163 				log.debug(
164 						"git> " + RawParseUtils.decode(UTF_8, buf, pos, len - 1) //$NON-NLS-1$
165 								+ "\\n"); //$NON-NLS-1$
166 			} else {
167 				log.debug("git> " + RawParseUtils.decode(UTF_8, buf, pos, len)); //$NON-NLS-1$
168 			}
169 		}
170 	}
171 
172 	/**
173 	 * Write a packet delim marker (0001).
174 	 *
175 	 * @throws java.io.IOException
176 	 *             the marker could not be written, the stream is corrupted
177 	 *             as the marker may have been only partially written.
178 	 * @since 5.0
179 	 */
180 	public void writeDelim() throws IOException {
181 		formatLength(1);
182 		out.write(lenbuffer, 0, 4);
183 		if (logEnabled && log.isDebugEnabled()) {
184 			log.debug("git> 0001"); //$NON-NLS-1$
185 		}
186 	}
187 
188 	/**
189 	 * Write a packet end marker, sometimes referred to as a flush command.
190 	 * <p>
191 	 * Technically this is a magical packet type which can be detected
192 	 * separately from an empty string or an empty packet.
193 	 * <p>
194 	 * Implicitly performs a flush on the underlying OutputStream to ensure the
195 	 * peer will receive all data written thus far.
196 	 *
197 	 * @throws java.io.IOException
198 	 *             the end marker could not be written, the stream is corrupted
199 	 *             as the end marker may have been only partially written.
200 	 */
201 	public void end() throws IOException {
202 		formatLength(0);
203 		out.write(lenbuffer, 0, 4);
204 		if (logEnabled && log.isDebugEnabled()) {
205 			log.debug("git> 0000"); //$NON-NLS-1$
206 		}
207 		if (flushOnEnd) {
208 			flush();
209 		}
210 	}
211 
212 	/**
213 	 * Flush the underlying OutputStream.
214 	 * <p>
215 	 * Performs a flush on the underlying OutputStream to ensure the peer will
216 	 * receive all data written thus far.
217 	 *
218 	 * @throws java.io.IOException
219 	 *             the underlying stream failed to flush.
220 	 */
221 	public void flush() throws IOException {
222 		out.flush();
223 	}
224 
225 	private static final byte[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
226 			'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
227 
228 	private void formatLength(int w) {
229 		formatLength(lenbuffer, w);
230 	}
231 
232 	static void formatLength(byte[] lenbuffer, int w) {
233 		int o = 3;
234 		while (o >= 0 && w != 0) {
235 			lenbuffer[o--] = hexchar[w & 0xf];
236 			w >>>= 4;
237 		}
238 		while (o >= 0)
239 			lenbuffer[o--] = '0';
240 	}
241 }