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 }