summaryrefslogtreecommitdiffstats
path: root/simple/simple-http/src/main/java/org/simpleframework/http/message/BoundaryConsumer.java
blob: f519ce24d717e09003d76de3bbc29b05a06a8fb2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
 * BoundaryConsumer.java February 2007
 *
 * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 * implied. See the License for the specific language governing 
 * permissions and limitations under the License.
 */

package org.simpleframework.http.message;

import java.io.IOException;

import org.simpleframework.common.buffer.Allocator;
import org.simpleframework.common.buffer.Buffer;

/**
 * The <code>BoundaryConsumer</code> is used to consume a boundary
 * for a multipart message. This ensures that the boundary complies
 * with the multipart specification in that it ends with a carriage
 * return and line feed. This consumer implementation can be used
 * multiple times as its internal buffer can be cleared and reset. 
 *
 * @author Niall Gallagher
 */ 
class BoundaryConsumer extends ArrayConsumer {
  
   /**
    * This is the terminal token for a multipart boundary entity.
    */           
   private static final byte[] LAST = {  '-', '-', '\r', '\n', };
   
   /**
    * This is the terminal token for a multipart boundary line.
    */ 
   private static final byte[] LINE = { '\r', '\n' };
   
   /**
    * This represents the start of the boundary line for the part.
    */
   private static final byte[] TOKEN = { '-', '-' };
   
   /**
    * This is used to allocate a buffer for for the boundary. 
    */
   private Allocator allocator;
   
   /**
    * This is used to consume the contents of the consumed buffer.
    */
   private Buffer buffer;
   
   /**
    * This is the actual boundary value that is to be consumed.
    */ 
   private byte[] boundary;
   
   /**
    * This counts the number of characters read from the start.
    */ 
   private int seek;
   
   /**
    * Constructor for the <code>BoundaryConsumer</code> object. This 
    * is used to create a boundary consumer for validating boundaries
    * and consuming them from a provided source. This is used to help
    * in reading multipart messages by removing boundaries from the
    * stream.
    *
    * @param boundary this is the boundary value to be consumed
    */ 
   public BoundaryConsumer(Allocator allocator, byte[] boundary) {
      this.chunk = boundary.length + LAST.length + TOKEN.length;
      this.allocator = allocator;
      this.boundary = boundary;    
   }   
   
   /**
    * This does not perform any processing after the boundary has 
    * been consumed. Because the boundary consumer is used only as a
    * means to remove the boundary from the underlying stream there
    * is no need to perform any processing of the value consumed.
    */ 
   @Override
   protected void process() throws IOException {
      if(count < boundary.length + 4) {
         throw new IOException("Invalid boundary processed");
      }
   }
   
   /**
    * This method is used to scan for the terminal token. It searches
    * for the token and returns the number of bytes in the buffer 
    * after the terminal token. Returning the excess bytes allows the
    * consumer to reset the bytes within the consumer object.
    *
    * @return this returns the number of excess bytes consumed
    */
   @Override
   protected int scan() throws IOException {
      int size = boundary.length;

      if(count >= 2 && seek < 2) {
         if(scan(TOKEN)) {
            append(TOKEN);
         }
      }
      if(count >= 2 + size && seek < 2 + size) {
         if(scan(boundary)) {
            append(boundary);
         }
      }
      if(count >= 4 + size && seek < 4 + size) {
         if(array[size + 2] == TOKEN[0]) {           
            if(scan(TOKEN)) {
               append(TOKEN);
            } 
         } else if(array[size + 2] == LINE[0]) {
            if(scan(LINE)) {
               append(LINE);
            }
            done = true;
            return count - seek;
         }
      } 
      if(count >= 6 + size && seek < 6 + size) {
         if(scan(LINE)) {
            append(LINE);
         }
         done = true;
         return count - seek;
      }
      return 0;
   }
   
   /**
    * This is used to append a token to the underlying buffer. Adding
    * various tokens ensures that the whole message is reconstructed
    * and can be forwarded to any connected service if used as a proxy.
    * 
    * @param token this is the token that is to be appended
    */
   private void append(byte[] token) throws IOException {
      if(buffer == null) {
         buffer = allocator.allocate(chunk);
      }
      buffer.append(token);
   }
   
   /**
    * This is used to scan the specified token from the consumed bytes.
    * If the data scanned does not match the token provided then this
    * will throw an exception to signify a bad boundary. This will
    * return true only when the whole boundary has been consumed.
    * 
    * @param data this is the token to scan from the consumed bytes
    * 
    * @return this returns true of the token has been read
    */
   private boolean scan(byte[] data) throws IOException {
      int size = data.length;
      int pos = 0;
      
      while(seek < count) {
         if(array[seek++] != data[pos++]) {   
            throw new IOException("Invalid boundary");
         }
         if(pos == data.length) {
            return true;
         }
      }
      return pos == size;
   }
   
   /**
    * This is used to determine whether the boundary has been read
    * from the underlying stream. This is true only when the very
    * last boundary has been read. This will be the boundary value
    * that ends with the two <code>-</code> characters.
    *
    * @return this returns true with the terminal boundary is read
    */ 
   public boolean isEnd() {  
      return seek == chunk;
   }
  
   /**
    * This is used to clear the state of the of boundary consumer
    * such that it can be reused. This is required as the multipart
    * body may contain many parts, all delimited with the same 
    * boundary. Clearing allows the next boundary to be consumed.
    */  
   public void clear() {
      done = false;
      count = seek = 0;
   }
}