1 /*
2 * This file is part of dependency-check-utils.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * Copyright (c) 2020 Jeremy Long. All Rights Reserved.
17 */
18 package org.owasp.dependencycheck.utils.processing;
19
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.nio.charset.StandardCharsets;
23 import java.util.ArrayList;
24 import java.util.List;
25 import org.apache.commons.io.IOUtils;
26
27 /**
28 * Utility to read the output from a `Process` and places the output into
29 * provide storage containers.
30 *
31 * @author Jeremy Long
32 */
33 public class ProcessReader implements AutoCloseable {
34
35 /**
36 * A reference to the process that will be read.
37 */
38 private final Process process;
39 /**
40 * Reader for the error stream.
41 */
42 private Gobbler errorGobbler = null;
43 /**
44 * Reader for the input stream.
45 */
46 private Gobbler inputGobbler = null;
47 /**
48 * Processor for the input stream.
49 */
50 private Processor<InputStream> processor = null;
51 /**
52 * A list of threads that were started.
53 */
54 private final List<Thread> threads = new ArrayList<>();
55
56 /**
57 * Creates a new reader for the given process. The output from the process
58 * is written to the provided stores.
59 *
60 * @param process the process to read from
61 */
62 public ProcessReader(Process process) {
63 this(process, null);
64 }
65
66 /**
67 * Creates a new reader for the given process. The output from the process
68 * is written to the provided stores.
69 *
70 * @param process the process to read from
71 * @param processor used to process the input stream from the process
72 */
73 public ProcessReader(Process process, Processor<InputStream> processor) {
74 this.process = process;
75 this.processor = processor;
76 }
77
78 /**
79 * Returns the error stream output from the process.
80 *
81 * @return the error stream output
82 */
83 public String getError() {
84 return errorGobbler.getText();
85 }
86
87 /**
88 * Returns the output from standard out from the process.
89 *
90 * @return the output from standard out from the process
91 */
92 public String getOutput() {
93 return inputGobbler != null ? inputGobbler.getText() : null;
94 }
95
96 /**
97 * Reads the standard output and standard error from the process and waits
98 * for the process to complete.
99 *
100 * @throws InterruptedException thrown if the processing threads are
101 * interrupted
102 * @throws IOException thrown if there is an error reading from the process
103 */
104 public void readAll() throws InterruptedException, IOException {
105 start();
106 close();
107 }
108
109 /**
110 * Starts the processing of the `process`.
111 */
112 private void start() {
113 errorGobbler = new Gobbler(process.getErrorStream());
114 startProcessor(errorGobbler);
115 if (processor == null) {
116 inputGobbler = new Gobbler(process.getInputStream());
117 startProcessor(inputGobbler);
118 } else {
119 processor.setInput(process.getInputStream());
120 startProcessor(processor);
121 }
122 }
123
124 /**
125 * Starts the process in its own thread and collects the threads so `join`
126 * can be called later to ensure the thread finishes.
127 *
128 * @param p a reference to the processor to start.
129 */
130 private void startProcessor(Processor<InputStream> p) {
131 if (p != null) {
132 final Thread t = new Thread(p);
133 threads.add(t);
134 t.start();
135 }
136 }
137
138 /**
139 * Waits for the process and related threads to complete.
140 *
141 * @throws InterruptedException thrown if the processing threads are
142 * interrupted
143 * @throws IOException thrown if there was an error reading from the process
144 */
145 @Override
146 public void close() throws InterruptedException, IOException {
147 if (process.isAlive()) {
148 process.waitFor();
149 }
150 if (threads.size() > 0) {
151 for (Thread thread : threads) {
152 thread.join();
153 }
154 threads.clear();
155 }
156 errorGobbler.close();
157 if (inputGobbler != null) {
158 inputGobbler.close();
159 }
160 }
161
162 static class Gobbler extends Processor<InputStream> {
163
164 /**
165 * A store for an exception - if one is thrown during processing.
166 */
167 private IOException exception;
168 /**
169 * A store for the text read from the input stream.
170 */
171 private String text;
172
173 Gobbler(InputStream inputStream) {
174 super(inputStream);
175 }
176
177 @Override
178 public void run() {
179 try {
180 final InputStream inputStream = getInput();
181 text = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
182
183 } catch (IOException ex) {
184 exception = ex;
185 }
186 }
187
188 public String getText() {
189 return text;
190 }
191
192 @Override
193 public void close() throws IOException {
194 if (exception != null) {
195 throw exception;
196 }
197 }
198 }
199 }