View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.websocket.common.io.http;
20  
21  import java.nio.ByteBuffer;
22  import java.util.regex.Matcher;
23  import java.util.regex.Pattern;
24  
25  import org.eclipse.jetty.util.BufferUtil;
26  import org.eclipse.jetty.util.StringUtil;
27  import org.eclipse.jetty.util.Utf8LineParser;
28  
29  /**
30   * Responsible for reading UTF8 Response Header lines and parsing them into a provided UpgradeResponse object.
31   */
32  public class HttpResponseHeaderParser
33  {
34      @SuppressWarnings("serial")
35      public static class ParseException extends RuntimeException
36      {
37          public ParseException(String message)
38          {
39              super(message);
40          }
41  
42          public ParseException(String message, Throwable cause)
43          {
44              super(message,cause);
45          }
46      }
47  
48      private enum State
49      {
50          STATUS_LINE,
51          HEADER,
52          END
53      }
54  
55      private static final Pattern PAT_HEADER = Pattern.compile("([^:]+):\\s*(.*)");
56      private static final Pattern PAT_STATUS_LINE = Pattern.compile("^HTTP/1.[01]\\s+(\\d+)\\s+(.*)",Pattern.CASE_INSENSITIVE);
57  
58      private final HttpResponseHeaderParseListener listener;
59      private final Utf8LineParser lineParser;
60      private State state;
61  
62      public HttpResponseHeaderParser(HttpResponseHeaderParseListener listener)
63      {
64          this.listener = listener;
65          this.lineParser = new Utf8LineParser();
66          this.state = State.STATUS_LINE;
67      }
68  
69      public boolean isDone()
70      {
71          return (state == State.END);
72      }
73  
74      public HttpResponseHeaderParseListener parse(ByteBuffer buf) throws ParseException
75      {
76          while (!isDone() && (buf.remaining() > 0))
77          {
78              String line = lineParser.parse(buf);
79              if (line != null)
80              {
81                  if (parseHeader(line))
82                  {
83                      // Now finished with parsing the entire response header
84                      // Save the remaining bytes for WebSocket to process.
85                      
86                      ByteBuffer copy = ByteBuffer.allocate(buf.remaining());
87                      BufferUtil.put(buf,copy);
88                      BufferUtil.flipToFlush(copy,0);
89                      this.listener.setRemainingBuffer(copy);
90                      return listener;
91                  }
92              }
93          }
94          return null;
95      }
96  
97      private boolean parseHeader(String line) throws ParseException
98      {
99          switch (state)
100         {
101             case STATUS_LINE:
102             {
103                 Matcher mat = PAT_STATUS_LINE.matcher(line);
104                 if (!mat.matches())
105                 {
106                     throw new ParseException("Unexpected HTTP response status line [" + line + "]");
107                 }
108 
109                 try
110                 {
111                     listener.setStatusCode(Integer.parseInt(mat.group(1)));
112                 }
113                 catch (NumberFormatException e)
114                 {
115                     throw new ParseException("Unexpected HTTP response status code",e);
116                 }
117                 listener.setStatusReason(mat.group(2));
118                 state = State.HEADER;
119                 break;
120             }
121             case HEADER:
122             {
123                 if (StringUtil.isBlank(line))
124                 {
125                     state = State.END;
126                     return parseHeader(line);
127                 }
128 
129                 Matcher header = PAT_HEADER.matcher(line);
130                 if (header.matches())
131                 {
132                     String headerName = header.group(1);
133                     String headerValue = header.group(2);
134                     // do need to split header/value if comma delimited?
135                     listener.addHeader(headerName,headerValue);
136                 }
137                 break;
138             }
139             case END:
140                 state = State.STATUS_LINE;
141                 return true;
142         }
143         return false;
144     }
145 }