1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.common.rest.api.batch;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.nio.ByteBuffer;
24 import java.nio.charset.Charset;
25 import java.nio.charset.StandardCharsets;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Optional;
29 import javax.ws.rs.core.HttpHeaders;
30 import javax.ws.rs.core.MediaType;
31 import org.apache.syncope.common.rest.api.RESTHeaders;
32 import org.apache.syncope.common.rest.api.service.JAXRSService;
33
34 public class BatchPayloadLineReader implements AutoCloseable {
35
36 private static final byte CR = '\r';
37
38 private static final byte LF = '\n';
39
40 private static final int EOF = -1;
41
42 private static final int BUFFER_SIZE = 8192;
43
44 private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
45
46 private final ReadState readState = new ReadState();
47
48 private final InputStream in;
49
50 private final MediaType multipartMixed;
51
52 private final byte[] buffer = new byte[BUFFER_SIZE];
53
54 private Charset currentCharset = DEFAULT_CHARSET;
55
56 private String currentBoundary = null;
57
58 private int offset = 0;
59
60 private int limit = 0;
61
62 public BatchPayloadLineReader(final InputStream in, final MediaType multipartMixed) {
63 this.in = in;
64 this.multipartMixed = multipartMixed;
65 }
66
67 @Override
68 public void close() throws IOException {
69 in.close();
70 }
71
72 private boolean isBoundary(final String currentLine) {
73 return (currentBoundary + JAXRSService.CRLF).equals(currentLine)
74 || (currentBoundary + JAXRSService.DOUBLE_DASH + JAXRSService.CRLF).equals(currentLine);
75 }
76
77 private int fillBuffer() throws IOException {
78 limit = in.read(buffer, 0, buffer.length);
79 offset = 0;
80
81 return limit;
82 }
83
84 private String readLine() throws IOException {
85 if (limit == EOF) {
86 return null;
87 }
88
89 ByteBuffer innerBuffer = ByteBuffer.allocate(BUFFER_SIZE);
90
91 boolean foundLineEnd = false;
92
93 while (!foundLineEnd) {
94
95 if (limit == offset && fillBuffer() == EOF) {
96 foundLineEnd = true;
97 }
98
99 if (!foundLineEnd) {
100 byte currentChar = buffer[offset++];
101 if (!innerBuffer.hasRemaining()) {
102 innerBuffer.flip();
103 ByteBuffer tmp = ByteBuffer.allocate(innerBuffer.limit() * 2);
104 tmp.put(innerBuffer);
105 innerBuffer = tmp;
106 }
107 innerBuffer.put(currentChar);
108
109 if (currentChar == LF) {
110 foundLineEnd = true;
111 } else if (currentChar == CR) {
112 foundLineEnd = true;
113
114
115
116 if (limit == offset) {
117 fillBuffer();
118 }
119
120
121 if (limit != EOF && buffer[offset] == LF) {
122 innerBuffer.put(LF);
123 offset++;
124 }
125 }
126 }
127 }
128
129 if (innerBuffer.position() == 0) {
130 return null;
131 } else {
132 String currentLine = new String(innerBuffer.array(), 0, innerBuffer.position(),
133 readState.isReadBody() ? currentCharset : DEFAULT_CHARSET);
134
135 if (currentLine.startsWith(HttpHeaders.CONTENT_TYPE)) {
136 String charsetString = multipartMixed.getParameters().get(MediaType.CHARSET_PARAMETER);
137 currentCharset = Optional.ofNullable(charsetString).map(Charset::forName).orElse(DEFAULT_CHARSET);
138
139 currentBoundary = JAXRSService.DOUBLE_DASH + multipartMixed.getParameters().
140 get(RESTHeaders.BOUNDARY_PARAMETER);
141 } else if (JAXRSService.CRLF.equals(currentLine)) {
142 readState.foundLinebreak();
143 } else if (isBoundary(currentLine)) {
144 readState.foundBoundary();
145 }
146
147 return currentLine;
148 }
149 }
150
151 public List<BatchPayloadLine> read() throws IOException {
152 List<BatchPayloadLine> result = new ArrayList<>();
153
154 String currentLine = readLine();
155 if (currentLine != null) {
156 currentBoundary = currentLine.trim();
157 int counter = 1;
158 result.add(new BatchPayloadLine(currentLine, counter++));
159
160 while ((currentLine = readLine()) != null) {
161 result.add(new BatchPayloadLine(currentLine, counter++));
162 }
163 }
164
165 return result;
166 }
167
168
169
170
171 private static class ReadState {
172
173 private int state = 0;
174
175 public void foundLinebreak() {
176 state++;
177 }
178
179 public void foundBoundary() {
180 state = 0;
181 }
182
183 public boolean isReadBody() {
184 return state >= 2;
185 }
186
187 @Override
188 public String toString() {
189 return String.valueOf(state);
190 }
191 }
192 }