001/* 002 * acme4j - Java ACME client 003 * 004 * Copyright (C) 2018 Richard "Shred" Körber 005 * http://acme4j.shredzone.org 006 * 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 013 */ 014package org.shredzone.acme4j.connector; 015 016import java.io.BufferedInputStream; 017import java.io.IOException; 018import java.io.InputStream; 019 020/** 021 * Normalizes line separators in an InputStream. Converts all line separators to '\n'. 022 * Multiple line separators are compressed to a single line separator. Leading line 023 * separators are removed. Trailing line separators are compressed to a single separator. 024 */ 025public class TrimmingInputStream extends InputStream { 026 private final BufferedInputStream in; 027 private boolean startOfFile = true; 028 029 /** 030 * Creates a new {@link TrimmingInputStream}. 031 * 032 * @param in 033 * {@link InputStream} to read from. Will be closed when this stream is 034 * closed. 035 */ 036 public TrimmingInputStream(InputStream in) { 037 this.in = new BufferedInputStream(in, 1024); 038 } 039 040 @Override 041 public int read() throws IOException { 042 int ch = in.read(); 043 044 if (!isLineSeparator(ch)) { 045 startOfFile = false; 046 return ch; 047 } 048 049 in.mark(1); 050 ch = in.read(); 051 while (isLineSeparator(ch)) { 052 in.mark(1); 053 ch = in.read(); 054 } 055 056 if (startOfFile) { 057 startOfFile = false; 058 return ch; 059 } else { 060 in.reset(); 061 return '\n'; 062 } 063 } 064 065 @Override 066 public int available() throws IOException { 067 // Workaround for https://github.com/google/conscrypt/issues/1068. Conscrypt 068 // requires the stream to have at least one non-blocking byte available for 069 // reading, otherwise generateCertificates() will not read the stream, but 070 // immediately returns an empty list. This workaround pre-fills the buffer 071 // of the BufferedInputStream by reading 1 byte ahead. 072 if (in.available() == 0) { 073 in.mark(1); 074 int read = in.read(); 075 in.reset(); 076 if (read < 0) { 077 return 0; 078 } 079 } 080 return in.available(); 081 } 082 083 @Override 084 public void close() throws IOException { 085 in.close(); 086 super.close(); 087 } 088 089 /** 090 * Checks if the character is a line separator. 091 */ 092 private static boolean isLineSeparator(int ch) { 093 return ch == '\n' || ch == '\r'; 094 } 095 096}