1 //
2 // Copyright (c) ZeroC, Inc. All rights reserved.
3 //
4 
5 namespace IceInternal
6 {
7     using System.Diagnostics;
8     using System.Collections.Generic;
9     using System.Text;
10 
11     internal sealed class WebSocketException : System.Exception
12     {
WebSocketException()13         internal WebSocketException() :
14             base("", null)
15         {
16         }
17 
WebSocketException(string message)18         internal WebSocketException(string message) :
19             base(message, null)
20         {
21         }
22 
WebSocketException(string message, System.Exception cause)23         internal WebSocketException(string message, System.Exception cause) :
24             base(message, cause)
25         {
26         }
27 
WebSocketException(System.Exception cause)28         internal WebSocketException(System.Exception cause) :
29             base("", cause)
30         {
31         }
32     }
33 
34     internal sealed class HttpParser
35     {
HttpParser()36         internal HttpParser()
37         {
38             _type = Type.Unknown;
39             _versionMajor = 0;
40             _versionMinor = 0;
41             _status = 0;
42             _state = State.Init;
43         }
44 
45         internal enum Type
46         {
47             Unknown,
48             Request,
49             Response
50         };
51 
isCompleteMessage(ByteBuffer buf, int begin, int end)52         internal int isCompleteMessage(ByteBuffer buf, int begin, int end)
53         {
54             byte[] raw = buf.rawBytes();
55             int p = begin;
56 
57             //
58             // Skip any leading CR-LF characters.
59             //
60             while(p < end)
61             {
62                 byte ch = raw[p];
63                 if(ch != (byte)'\r' && ch != (byte)'\n')
64                 {
65                     break;
66                 }
67                 ++p;
68             }
69 
70             //
71             // Look for adjacent CR-LF/CR-LF or LF/LF.
72             //
73             bool seenFirst = false;
74             while(p < end)
75             {
76                 byte ch = raw[p++];
77                 if(ch == (byte)'\n')
78                 {
79                     if(seenFirst)
80                     {
81                         return p;
82                     }
83                     else
84                     {
85                         seenFirst = true;
86                     }
87                 }
88                 else if(ch != (byte)'\r')
89                 {
90                     seenFirst = false;
91                 }
92             }
93 
94             return -1;
95         }
96 
parse(ByteBuffer buf, int begin, int end)97         internal bool parse(ByteBuffer buf, int begin, int end)
98         {
99             byte[] raw = buf.rawBytes();
100             int p = begin;
101             int start = 0;
102             const char CR = '\r';
103             const char LF = '\n';
104 
105             if(_state == State.Complete)
106             {
107                 _state = State.Init;
108             }
109 
110             while(p != end && _state != State.Complete)
111             {
112                 char c = (char)raw[p];
113 
114                 switch(_state)
115                 {
116                 case State.Init:
117                 {
118                     _method = new StringBuilder();
119                     _uri = new StringBuilder();
120                     _versionMajor = -1;
121                     _versionMinor = -1;
122                     _status = -1;
123                     _reason = "";
124                     _headers.Clear();
125                     _state = State.Type;
126                     continue;
127                 }
128                 case State.Type:
129                 {
130                     if(c == CR || c == LF)
131                     {
132                         break;
133                     }
134                     else if(c == 'H')
135                     {
136                         //
137                         // Could be the start of "HTTP/1.1" or "HEAD".
138                         //
139                         _state = State.TypeCheck;
140                         break;
141                     }
142                     else
143                     {
144                         _state = State.Request;
145                         continue;
146                     }
147                 }
148                 case State.TypeCheck:
149                 {
150                     if(c == 'T') // Continuing "H_T_TP/1.1"
151                     {
152                         _state = State.Response;
153                     }
154                     else if(c == 'E') // Expecting "HEAD"
155                     {
156                         _state = State.Request;
157                         _method.Append('H');
158                         _method.Append('E');
159                     }
160                     else
161                     {
162                         throw new WebSocketException("malformed request or response");
163                     }
164                     break;
165                 }
166                 case State.Request:
167                 {
168                     _type = Type.Request;
169                     _state = State.RequestMethod;
170                     continue;
171                 }
172                 case State.RequestMethod:
173                 {
174                     if(c == ' ' || c == CR || c == LF)
175                     {
176                         _state = State.RequestMethodSP;
177                         continue;
178                     }
179                     _method.Append(c);
180                     break;
181                 }
182                 case State.RequestMethodSP:
183                 {
184                     if(c == ' ')
185                     {
186                         break;
187                     }
188                     else if(c == CR || c == LF)
189                     {
190                         throw new WebSocketException("malformed request");
191                     }
192                     _state = State.RequestURI;
193                     continue;
194                 }
195                 case State.RequestURI:
196                 {
197                     if(c == ' ' || c == CR || c == LF)
198                     {
199                         _state = State.RequestURISP;
200                         continue;
201                     }
202                     _uri.Append(c);
203                     break;
204                 }
205                 case State.RequestURISP:
206                 {
207                     if(c == ' ')
208                     {
209                         break;
210                     }
211                     else if(c == CR || c == LF)
212                     {
213                         throw new WebSocketException("malformed request");
214                     }
215                     _state = State.Version;
216                     continue;
217                 }
218                 case State.RequestLF:
219                 {
220                     if(c != LF)
221                     {
222                         throw new WebSocketException("malformed request");
223                     }
224                     _state = State.HeaderFieldStart;
225                     break;
226                 }
227                 case State.HeaderFieldStart:
228                 {
229                     //
230                     // We've already seen a LF to reach this state.
231                     //
232                     // Another CR or LF indicates the end of the header fields.
233                     //
234                     if(c == CR)
235                     {
236                         _state = State.HeaderFieldEndLF;
237                         break;
238                     }
239                     else if(c == LF)
240                     {
241                         _state = State.Complete;
242                         break;
243                     }
244                     else if(c == ' ')
245                     {
246                         //
247                         // Could be a continuation line.
248                         //
249                         _state = State.HeaderFieldContStart;
250                         break;
251                     }
252 
253                     _state = State.HeaderFieldNameStart;
254                     continue;
255                 }
256                 case State.HeaderFieldContStart:
257                 {
258                     if(c == ' ')
259                     {
260                         break;
261                     }
262 
263                     _state = State.HeaderFieldCont;
264                     start = p;
265                     continue;
266                 }
267                 case State.HeaderFieldCont:
268                 {
269                     if(c == CR || c == LF)
270                     {
271                         if(p > start)
272                         {
273                             if(_headerName.Length == 0)
274                             {
275                                 throw new WebSocketException("malformed header");
276                             }
277                             Debug.Assert(_headers.ContainsKey(_headerName));
278                             string s = _headers[_headerName];
279                             StringBuilder newValue = new StringBuilder(s);
280                             newValue.Append(' ');
281                             for(int i = start; i < p; ++i)
282                             {
283                                 newValue.Append((char)raw[i]);
284                             }
285                             _headers[_headerName] = newValue.ToString();
286                             _state = c == CR ? State.HeaderFieldLF : State.HeaderFieldStart;
287                         }
288                         else
289                         {
290                             //
291                             // Could mark the end of the header fields.
292                             //
293                             _state = c == CR ? State.HeaderFieldEndLF : State.Complete;
294                         }
295                     }
296 
297                     break;
298                 }
299                 case State.HeaderFieldNameStart:
300                 {
301                     Debug.Assert(c != ' ');
302                     start = p;
303                     _headerName = "";
304                     _state = State.HeaderFieldName;
305                     continue;
306                 }
307                 case State.HeaderFieldName:
308                 {
309                     if(c == ' ' || c == ':')
310                     {
311                         _state = State.HeaderFieldNameEnd;
312                         continue;
313                     }
314                     else if(c == CR || c == LF)
315                     {
316                         throw new WebSocketException("malformed header");
317                     }
318                     break;
319                 }
320                 case State.HeaderFieldNameEnd:
321                 {
322                     if(_headerName.Length == 0)
323                     {
324                         StringBuilder str = new StringBuilder();
325                         for(int i = start; i < p; ++i)
326                         {
327                             str.Append((char)raw[i]);
328                         }
329                         _headerName = str.ToString().ToLower();
330                         //
331                         // Add a placeholder entry if necessary.
332                         //
333                         if(!_headers.ContainsKey(_headerName))
334                         {
335                             _headers[_headerName] = "";
336                             _headerNames[_headerName] = str.ToString();
337                         }
338                     }
339 
340                     if(c == ' ')
341                     {
342                         break;
343                     }
344                     else if(c != ':' || p == start)
345                     {
346                         throw new WebSocketException("malformed header");
347                     }
348 
349                     _state = State.HeaderFieldValueStart;
350                     break;
351                 }
352                 case State.HeaderFieldValueStart:
353                 {
354                     if(c == ' ')
355                     {
356                         break;
357                     }
358 
359                     //
360                     // Check for "Name:\r\n"
361                     //
362                     if(c == CR)
363                     {
364                         _state = State.HeaderFieldLF;
365                         break;
366                     }
367                     else if(c == LF)
368                     {
369                         _state = State.HeaderFieldStart;
370                         break;
371                     }
372 
373                     start = p;
374                     _state = State.HeaderFieldValue;
375                     continue;
376                 }
377                 case State.HeaderFieldValue:
378                 {
379                     if(c == CR || c == LF)
380                     {
381                         _state = State.HeaderFieldValueEnd;
382                         continue;
383                     }
384                     break;
385                 }
386                 case State.HeaderFieldValueEnd:
387                 {
388                     Debug.Assert(c == CR || c == LF);
389                     if(p > start)
390                     {
391                         StringBuilder str = new StringBuilder();
392                         for(int i = start; i < p; ++i)
393                         {
394                             str.Append((char)raw[i]);
395                         }
396                         string s = null;
397                         if(!_headers.TryGetValue(_headerName, out s) || s.Length == 0)
398                         {
399                             _headers[_headerName] = str.ToString();
400                         }
401                         else
402                         {
403                             _headers[_headerName] = s + ", " + str.ToString();
404                         }
405                     }
406 
407                     if(c == CR)
408                     {
409                         _state = State.HeaderFieldLF;
410                     }
411                     else
412                     {
413                         _state = State.HeaderFieldStart;
414                     }
415                     break;
416                 }
417                 case State.HeaderFieldLF:
418                 {
419                     if(c != LF)
420                     {
421                         throw new WebSocketException("malformed header");
422                     }
423                     _state = State.HeaderFieldStart;
424                     break;
425                 }
426                 case State.HeaderFieldEndLF:
427                 {
428                     if(c != LF)
429                     {
430                         throw new WebSocketException("malformed header");
431                     }
432                     _state = State.Complete;
433                     break;
434                 }
435                 case State.Version:
436                 {
437                     if(c != 'H')
438                     {
439                         throw new WebSocketException("malformed version");
440                     }
441                     _state = State.VersionH;
442                     break;
443                 }
444                 case State.VersionH:
445                 {
446                     if(c != 'T')
447                     {
448                         throw new WebSocketException("malformed version");
449                     }
450                     _state = State.VersionHT;
451                     break;
452                 }
453                 case State.VersionHT:
454                 {
455                     if(c != 'T')
456                     {
457                         throw new WebSocketException("malformed version");
458                     }
459                     _state = State.VersionHTT;
460                     break;
461                 }
462                 case State.VersionHTT:
463                 {
464                     if(c != 'P')
465                     {
466                         throw new WebSocketException("malformed version");
467                     }
468                     _state = State.VersionHTTP;
469                     break;
470                 }
471                 case State.VersionHTTP:
472                 {
473                     if(c != '/')
474                     {
475                         throw new WebSocketException("malformed version");
476                     }
477                     _state = State.VersionMajor;
478                     break;
479                 }
480                 case State.VersionMajor:
481                 {
482                     if(c == '.')
483                     {
484                         if(_versionMajor == -1)
485                         {
486                             throw new WebSocketException("malformed version");
487                         }
488                         _state = State.VersionMinor;
489                         break;
490                     }
491                     else if(c < '0' || c > '9')
492                     {
493                         throw new WebSocketException("malformed version");
494                     }
495                     if(_versionMajor == -1)
496                     {
497                         _versionMajor = 0;
498                     }
499                     _versionMajor *= 10;
500                     _versionMajor += (int)(c - '0');
501                     break;
502                 }
503                 case State.VersionMinor:
504                 {
505                     if(c == CR)
506                     {
507                         if(_versionMinor == -1 || _type != Type.Request)
508                         {
509                             throw new WebSocketException("malformed version");
510                         }
511                         _state = State.RequestLF;
512                         break;
513                     }
514                     else if(c == LF)
515                     {
516                         if(_versionMinor == -1 || _type != Type.Request)
517                         {
518                             throw new WebSocketException("malformed version");
519                         }
520                         _state = State.HeaderFieldStart;
521                         break;
522                     }
523                     else if(c == ' ')
524                     {
525                         if(_versionMinor == -1 || _type != Type.Response)
526                         {
527                             throw new WebSocketException("malformed version");
528                         }
529                         _state = State.ResponseVersionSP;
530                         break;
531                     }
532                     else if(c < '0' || c > '9')
533                     {
534                         throw new WebSocketException("malformed version");
535                     }
536                     if(_versionMinor == -1)
537                     {
538                         _versionMinor = 0;
539                     }
540                     _versionMinor *= 10;
541                     _versionMinor += (c - '0');
542                     break;
543                 }
544                 case State.Response:
545                 {
546                     _type = Type.Response;
547                     _state = State.VersionHT;
548                     continue;
549                 }
550                 case State.ResponseVersionSP:
551                 {
552                     if(c == ' ')
553                     {
554                         break;
555                     }
556 
557                     _state = State.ResponseStatus;
558                     continue;
559                 }
560                 case State.ResponseStatus:
561                 {
562                     // TODO: Is reason string optional?
563                     if(c == CR)
564                     {
565                         if(_status == -1)
566                         {
567                             throw new WebSocketException("malformed response status");
568                         }
569                         _state = State.ResponseLF;
570                         break;
571                     }
572                     else if(c == LF)
573                     {
574                         if(_status == -1)
575                         {
576                             throw new WebSocketException("malformed response status");
577                         }
578                         _state = State.HeaderFieldStart;
579                         break;
580                     }
581                     else if(c == ' ')
582                     {
583                         if(_status == -1)
584                         {
585                             throw new WebSocketException("malformed response status");
586                         }
587                         _state = State.ResponseReasonStart;
588                         break;
589                     }
590                     else if(c < '0' || c > '9')
591                     {
592                         throw new WebSocketException("malformed response status");
593                     }
594                     if(_status == -1)
595                     {
596                         _status = 0;
597                     }
598                     _status *= 10;
599                     _status += (c - '0');
600                     break;
601                 }
602                 case State.ResponseReasonStart:
603                 {
604                     //
605                     // Skip leading spaces.
606                     //
607                     if(c == ' ')
608                     {
609                         break;
610                     }
611 
612                     _state = State.ResponseReason;
613                     start = p;
614                     continue;
615                 }
616                 case State.ResponseReason:
617                 {
618                     if(c == CR || c == LF)
619                     {
620                         if(p > start)
621                         {
622                             StringBuilder str = new StringBuilder();
623                             for(int i = start; i < p; ++i)
624                             {
625                                 str.Append((char)raw[i]);
626                             }
627                             _reason = str.ToString();
628                         }
629                         _state = c == CR ? State.ResponseLF : State.HeaderFieldStart;
630                     }
631 
632                     break;
633                 }
634                 case State.ResponseLF:
635                 {
636                     if(c != LF)
637                     {
638                         throw new WebSocketException("malformed status line");
639                     }
640                     _state = State.HeaderFieldStart;
641                     break;
642                 }
643                 case State.Complete:
644                 {
645                     Debug.Assert(false); // Shouldn't reach
646                     break;
647                 }
648                 }
649 
650                 ++p;
651             }
652 
653             return _state == State.Complete;
654         }
655 
type()656         internal Type type()
657         {
658             return _type;
659         }
660 
method()661         internal string method()
662         {
663             Debug.Assert(_type == Type.Request);
664             return _method.ToString();
665         }
666 
uri()667         internal string uri()
668         {
669             Debug.Assert(_type == Type.Request);
670             return _uri.ToString();
671         }
672 
versionMajor()673         internal int versionMajor()
674         {
675             return _versionMajor;
676         }
677 
versionMinor()678         internal int versionMinor()
679         {
680             return _versionMinor;
681         }
682 
status()683         internal int status()
684         {
685             return _status;
686         }
687 
reason()688         internal string reason()
689         {
690             return _reason;
691         }
692 
getHeader(string name, bool toLower)693         internal string getHeader(string name, bool toLower)
694         {
695             string s = null;
696             if(_headers.TryGetValue(name.ToLower(), out s))
697             {
698                 return toLower ? s.Trim().ToLower() : s.Trim();
699             }
700 
701             return null;
702         }
703 
getHeaders()704         internal Dictionary<string, string> getHeaders()
705         {
706             Dictionary<string, string> dict = new Dictionary<string, string>();
707             foreach(KeyValuePair<string, string> e in _headers)
708             {
709                 dict[_headerNames[e.Key]] = e.Value.Trim();
710             }
711             return dict;
712         }
713 
714         private Type _type;
715 
716         private StringBuilder _method = new StringBuilder();
717         private StringBuilder _uri = new StringBuilder();
718 
719         private Dictionary<string, string> _headers = new Dictionary<string, string>();
720         private Dictionary<string, string> _headerNames = new Dictionary<string, string>();
721         private string _headerName = "";
722 
723         private int _versionMajor;
724         private int _versionMinor;
725 
726         private int _status;
727         private string _reason;
728 
729         private enum State
730         {
731             Init,
732             Type,
733             TypeCheck,
734             Request,
735             RequestMethod,
736             RequestMethodSP,
737             RequestURI,
738             RequestURISP,
739             RequestLF,
740             HeaderFieldStart,
741             HeaderFieldContStart,
742             HeaderFieldCont,
743             HeaderFieldNameStart,
744             HeaderFieldName,
745             HeaderFieldNameEnd,
746             HeaderFieldValueStart,
747             HeaderFieldValue,
748             HeaderFieldValueEnd,
749             HeaderFieldLF,
750             HeaderFieldEndLF,
751             Version,
752             VersionH,
753             VersionHT,
754             VersionHTT,
755             VersionHTTP,
756             VersionMajor,
757             VersionMinor,
758             Response,
759             ResponseVersionSP,
760             ResponseStatus,
761             ResponseReasonStart,
762             ResponseReason,
763             ResponseLF,
764             Complete
765         };
766         private State _state;
767     }
768 }
769