Coverage Report

Created: 2024-04-23 06:04

/src/resiprocate/resip/stack/MsgHeaderScanner.cxx
Line
Count
Source (jump to first uncovered line)
1
#if defined(HAVE_CONFIG_H)
2
#include "config.h"
3
#endif
4
5
#include <ctype.h>
6
#include <limits.h>
7
#include <stdio.h>
8
#include "resip/stack/HeaderTypes.hxx"
9
#include "resip/stack/SipMessage.hxx"
10
#include "resip/stack/MsgHeaderScanner.hxx"
11
#include "rutil/WinLeakCheck.hxx"
12
13
namespace resip 
14
{
15
16
///////////////////////////////////////////////////////////////////////////////
17
//   Any character could be used as the chunk terminating sentinel, as long as
18
//   it would otherwise be character category "other".  The null character
19
//   was chosen because it is unlikely to occur naturally -- but it's OK if it
20
//   does.
21
22
enum { chunkTermSentinelChar = '\0' };
23
24
enum CharCategoryEnum
25
{
26
   ccChunkTermSentinel,
27
   ccOther,
28
   ccFieldName,
29
   ccWhitespace,
30
   ccColon,
31
   ccDoubleQuotationMark,
32
   ccLeftAngleBracket,
33
   ccRightAngleBracket,
34
   ccBackslash,
35
   ccComma,
36
   ccCarriageReturn,
37
   ccLineFeed,
38
   numCharCategories
39
};
40
typedef char CharCategory;
41
42
char* 
43
MsgHeaderScanner::allocateBuffer(int size)
44
0
{
45
0
   return new char[size + MaxNumCharsChunkOverflow];
46
0
}
47
48
struct CharInfo
49
{
50
      CharCategory category;
51
      MsgHeaderScanner::TextPropBitMask textPropBitMask;
52
};
53
    
54
static CharInfo charInfoArray[UCHAR_MAX+1];
55
    
56
static inline int c2i(unsigned char c)
57
1.98k
{
58
1.98k
   return static_cast<int>(c); 
59
1.98k
}
60
61
static void initCharInfoArray()
62
1
{
63
257
   for(unsigned int charIndex = 0; charIndex <= UCHAR_MAX; ++charIndex) 
64
256
   {
65
256
      charInfoArray[charIndex].category = ccOther;
66
256
      charInfoArray[charIndex].textPropBitMask = 0;
67
256
   }
68
69
1
   for(const char *charPtr = "abcdefghijklmnopqrstuvwxyz"
70
1
          "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.!%*_+`'~";
71
73
       *charPtr;
72
72
       ++charPtr)
73
72
   {
74
72
      charInfoArray[c2i(*charPtr)].category = ccFieldName;
75
72
   }
76
77
1
   charInfoArray[c2i(' ')].category  = ccWhitespace;
78
1
   charInfoArray[c2i('\t')].category = ccWhitespace;
79
1
   charInfoArray[c2i(':')].category  = ccColon;
80
1
   charInfoArray[c2i('"')].category  = ccDoubleQuotationMark;
81
1
   charInfoArray[c2i('<')].category  = ccLeftAngleBracket;
82
1
   charInfoArray[c2i('>')].category  = ccRightAngleBracket;
83
1
   charInfoArray[c2i('\\')].category  = ccBackslash;
84
1
   charInfoArray[c2i(',')].category  = ccComma;
85
1
   charInfoArray[c2i('\r')].category = ccCarriageReturn;
86
1
   charInfoArray[c2i('\n')].category = ccLineFeed;
87
   // Assert: "chunkTermSentinelChar"'s category is still the default "ccOther".
88
1
   charInfoArray[c2i(chunkTermSentinelChar)].category = ccChunkTermSentinel;
89
   // Init text property bit masks.
90
1
   charInfoArray[c2i('\r')].textPropBitMask =
91
1
      MsgHeaderScanner::tpbmContainsLineBreak;
92
1
   charInfoArray[c2i('\n')].textPropBitMask =
93
1
      MsgHeaderScanner::tpbmContainsLineBreak;
94
1
   charInfoArray[c2i(' ')].textPropBitMask =
95
1
      MsgHeaderScanner::tpbmContainsWhitespace;
96
1
   charInfoArray[c2i('\t')].textPropBitMask =
97
1
      MsgHeaderScanner::tpbmContainsWhitespace;
98
1
   charInfoArray[c2i('\\')].textPropBitMask =
99
1
      MsgHeaderScanner::tpbmContainsBackslash;
100
1
   charInfoArray[c2i('%')].textPropBitMask =
101
1
      MsgHeaderScanner::tpbmContainsPercent;
102
1
   charInfoArray[c2i(';')].textPropBitMask =
103
1
      MsgHeaderScanner::tpbmContainsSemicolon;
104
1
   charInfoArray[c2i('(')].textPropBitMask =
105
1
      MsgHeaderScanner::tpbmContainsParen;
106
1
   charInfoArray[c2i(')')].textPropBitMask =
107
1
      MsgHeaderScanner::tpbmContainsParen;
108
1
}
109
110
///////////////////////////////////////////////////////////////////////////////
111
//   States marked '1' scan normal values.  States marked 'N' scan multi-values.
112
113
enum StateEnum
114
{
115
   sMsgStart,
116
   sHalfLineBreakAtMsgStart,
117
   sScanStatusLine,
118
   sHalfLineBreakAfterStatusLine,
119
   sAfterLineBreakAfterStatusLine,
120
   sScanFieldName,
121
   sScanWhitespaceAfter1FieldName,
122
   sScanWhitespaceAfterNFieldName,
123
   sScanWhitespaceOr1Value,
124
   sScanWhitespaceOrNValue,
125
   sHalfLineBreakInWhitespaceBefore1Value,
126
   sHalfLineBreakInWhitespaceBeforeNValue,
127
   sAfterLineBreakInWhitespaceBefore1Value,
128
   sAfterLineBreakInWhitespaceBeforeNValue,
129
   sScan1Value,
130
   sScanNValue,
131
   sHalfLineBreakIn1Value,
132
   sHalfLineBreakInNValue,
133
   sAfterLineBreakIn1Value,
134
   sAfterLineBreakInNValue,
135
   sScanNValueInQuotes,
136
   sAfterEscCharInQuotesInNValue,
137
   sHalfLineBreakInQuotesInNValue,
138
   sAfterLineBreakInQuotesInNValue,
139
   sScanNValueInAngles,
140
   sHalfLineBreakInAnglesInNValue,
141
   sAfterLineBreakInAnglesInNValue,
142
   sHalfLineBreakAfterLineBreak,
143
   numStates
144
};
145
146
typedef char State;
147
148
// For each '1' state, the 'N' state is "deltaOfNStateFrom1State" larger.
149
enum { deltaOfNStateFrom1State = 1 };
150
151
/////
152
    
153
enum TransitionActionEnum {
154
   taNone,
155
   taTermStatusLine,       // The current character terminates the status
156
   //     line.
157
   taTermFieldName,        // The current character terminates a field name.
158
   //     If the field supports multi-values, shift
159
   //     the state machine into multi-value scanning.
160
   taBeyondEmptyValue,     // The current character terminates an empty value.
161
   //     Implies taStartText.
162
   taTermValueAfterLineBreak, 
163
   // The previous two characters are a linebreak
164
   //      terminating a value.  Implies taStartText.
165
   taTermValue,            // The current character terminates a value.
166
   taStartText,            // The current character starts a text unit.
167
   //     (The status line, a field name, or a value.)
168
   taEndHeader,            // The current character mEnds_ the header.
169
   taChunkTermSentinel,    // Either the current character terminates the
170
   //    current chunk or it is an ordinary character.
171
   taError                 // The input is erroneous.
172
};
173
typedef char TransitionAction;
174
175
176
struct TransitionInfo
177
{
178
      TransitionAction  action;
179
      State             nextState;
180
};
181
182
static TransitionInfo stateMachine[numStates][numCharCategories];
183
184
inline void specTransition(State state,
185
                           CharCategory charCategory,
186
                           TransitionAction action,
187
                           State nextState)
188
474
{
189
474
   stateMachine[c2i(state)][c2i(charCategory)].action = action;
190
474
   stateMachine[c2i(state)][c2i(charCategory)].nextState = nextState;
191
474
}
192
193
static void specDefaultTransition(State state,
194
                                  TransitionAction action,
195
                                  State nextState)
196
28
{
197
28
   for (int charCategory = 0;
198
364
        charCategory < numCharCategories;
199
336
        ++charCategory) 
200
336
   {
201
336
      specTransition(state, charCategory, action, nextState);
202
336
   }
203
28
   specTransition(state, ccCarriageReturn, taError, state);
204
28
   specTransition(state, ccLineFeed, taError, state);
205
28
   specTransition(state, ccChunkTermSentinel, taChunkTermSentinel, state);
206
28
}
207
208
static void specHalfLineBreakState(State halfLineBreakState,
209
                                   State  afterLineBreakState)
210
9
{
211
9
   specDefaultTransition(halfLineBreakState, taError, halfLineBreakState);
212
9
   specTransition(halfLineBreakState, ccLineFeed, taNone, afterLineBreakState);
213
9
}
214
215
216
//   Single-value (1) scanning and multi-value (N) scanning involves several nearly
217
//   identical states.
218
//   "stateDelta" is either 0 or "deltaOfNStateFrom1State".
219
220
static void specXValueStates(int  stateDelta)
221
2
{
222
2
   specDefaultTransition(sScanWhitespaceAfter1FieldName + stateDelta,
223
2
                         taError,
224
2
                         sScanWhitespaceAfter1FieldName + stateDelta);
225
2
   specTransition(sScanWhitespaceAfter1FieldName + stateDelta,
226
2
                  ccWhitespace,
227
2
                  taNone,
228
2
                  sScanWhitespaceAfter1FieldName + stateDelta);
229
2
   specTransition(sScanWhitespaceAfter1FieldName + stateDelta,
230
2
                  ccColon,
231
2
                  taNone,
232
2
                  sScanWhitespaceOr1Value + stateDelta);
233
2
   specDefaultTransition(sScanWhitespaceOr1Value + stateDelta,
234
2
                         taStartText,
235
2
                         sScan1Value + stateDelta);
236
2
   specTransition(sScanWhitespaceOr1Value + stateDelta,
237
2
                  ccWhitespace,
238
2
                  taNone,
239
2
                  sScanWhitespaceOr1Value + stateDelta);
240
2
   if (stateDelta == deltaOfNStateFrom1State)
241
1
   {
242
1
      specTransition(sScanWhitespaceOr1Value + stateDelta,
243
1
                     ccComma,
244
1
                     taError,
245
1
                     sScanWhitespaceOr1Value + stateDelta);
246
1
      specTransition(sScanWhitespaceOr1Value + stateDelta,
247
1
                     ccLeftAngleBracket,
248
1
                     taStartText,
249
1
                     sScanNValueInAngles);
250
1
      specTransition(sScanWhitespaceOr1Value + stateDelta,
251
1
                     ccDoubleQuotationMark,
252
1
                     taStartText,
253
1
                     sScanNValueInQuotes);
254
1
   }
255
2
   specTransition(sScanWhitespaceOr1Value + stateDelta,
256
2
                  ccCarriageReturn,
257
2
                  taNone,
258
2
                  sHalfLineBreakInWhitespaceBefore1Value + stateDelta);
259
2
   specHalfLineBreakState(sHalfLineBreakInWhitespaceBefore1Value + stateDelta,
260
2
                          sAfterLineBreakInWhitespaceBefore1Value + stateDelta);
261
2
   specDefaultTransition(sAfterLineBreakInWhitespaceBefore1Value + stateDelta,
262
2
                         taError,
263
2
                         sAfterLineBreakInWhitespaceBefore1Value + stateDelta);
264
2
   specTransition(sAfterLineBreakInWhitespaceBefore1Value + stateDelta,
265
2
                  ccFieldName,
266
2
                  taBeyondEmptyValue,
267
2
                  sScanFieldName);
268
2
   specTransition(sAfterLineBreakInWhitespaceBefore1Value + stateDelta,
269
2
                  ccWhitespace,
270
2
                  taNone,
271
2
                  sScanWhitespaceOr1Value + stateDelta);
272
2
   specTransition(sAfterLineBreakInWhitespaceBefore1Value + stateDelta,
273
2
                  ccCarriageReturn,
274
2
                  taBeyondEmptyValue,
275
2
                  sHalfLineBreakAfterLineBreak);
276
2
   specDefaultTransition(sScan1Value + stateDelta,
277
2
                         taNone,
278
2
                         sScan1Value + stateDelta);
279
2
   if (stateDelta == deltaOfNStateFrom1State)
280
1
   {
281
1
      specTransition(sScan1Value + stateDelta,
282
1
                     ccComma,
283
1
                     taTermValue,
284
1
                     sScanWhitespaceOr1Value + stateDelta);
285
1
      specTransition(sScan1Value + stateDelta,
286
1
                     ccLeftAngleBracket,
287
1
                     taNone,
288
1
                     sScanNValueInAngles);
289
1
      specTransition(sScan1Value + stateDelta,
290
1
                     ccDoubleQuotationMark,
291
1
                     taNone,
292
1
                     sScanNValueInQuotes);
293
1
   }
294
2
   specTransition(sScan1Value + stateDelta,
295
2
                  ccCarriageReturn,
296
2
                  taNone,
297
2
                  sHalfLineBreakIn1Value + stateDelta);
298
2
   specHalfLineBreakState(sHalfLineBreakIn1Value + stateDelta,
299
2
                          sAfterLineBreakIn1Value + stateDelta);
300
2
   specDefaultTransition(sAfterLineBreakIn1Value + stateDelta,
301
2
                         taError,
302
2
                         sAfterLineBreakIn1Value + stateDelta);
303
2
   specTransition(sAfterLineBreakIn1Value + stateDelta,
304
2
                  ccFieldName,
305
2
                  taTermValueAfterLineBreak,
306
2
                  sScanFieldName);
307
2
   specTransition(sAfterLineBreakIn1Value + stateDelta,
308
2
                  ccWhitespace,
309
2
                  taNone,
310
2
                  sScan1Value + stateDelta);
311
2
   specTransition(sAfterLineBreakIn1Value + stateDelta,
312
2
                  ccCarriageReturn,
313
2
                  taTermValueAfterLineBreak,
314
2
                  sHalfLineBreakAfterLineBreak);
315
2
}
316
317
static void initStateMachine()
318
1
{
319
   // By convention, error transitions maintain the same state.
320
1
   specDefaultTransition(sMsgStart, taStartText, sScanStatusLine);
321
1
   specTransition(sMsgStart,
322
1
                  ccCarriageReturn,
323
1
                  taNone,
324
1
                  sHalfLineBreakAtMsgStart);
325
1
   specTransition(sMsgStart, ccLineFeed, taError, sMsgStart);
326
1
   specHalfLineBreakState(sHalfLineBreakAtMsgStart, sMsgStart);
327
1
   specDefaultTransition(sScanStatusLine, taNone, sScanStatusLine);
328
1
   specTransition(sScanStatusLine,
329
1
                  ccCarriageReturn,
330
1
                  taTermStatusLine,
331
1
                  sHalfLineBreakAfterStatusLine);
332
1
   specHalfLineBreakState(sHalfLineBreakAfterStatusLine,
333
1
                          sAfterLineBreakAfterStatusLine);
334
1
   specDefaultTransition(sAfterLineBreakAfterStatusLine,
335
1
                         taError,
336
1
                         sAfterLineBreakAfterStatusLine);
337
1
   specTransition(sAfterLineBreakAfterStatusLine,
338
1
                  ccFieldName,
339
1
                  taStartText,
340
1
                  sScanFieldName);
341
1
   specTransition(sAfterLineBreakAfterStatusLine,
342
1
                  ccWhitespace,
343
1
                  taError,
344
1
                  sAfterLineBreakAfterStatusLine);
345
1
   specTransition(sAfterLineBreakAfterStatusLine,
346
1
                  ccCarriageReturn,
347
1
                  taNone,
348
1
                  sHalfLineBreakAfterLineBreak);
349
1
   specDefaultTransition(sScanFieldName, taError, sScanFieldName);
350
1
   specTransition(sScanFieldName, ccFieldName, taNone, sScanFieldName);
351
1
   specTransition(sScanFieldName,
352
1
                  ccWhitespace,
353
1
                  taTermFieldName,
354
1
                  sScanWhitespaceAfter1FieldName);
355
1
   specTransition(sScanFieldName,
356
1
                  ccColon,
357
1
                  taTermFieldName,
358
1
                  sScanWhitespaceOr1Value);
359
1
   specXValueStates(0);
360
1
   specXValueStates(deltaOfNStateFrom1State);
361
1
   specDefaultTransition(sScanNValueInQuotes, taNone, sScanNValueInQuotes);
362
1
   specTransition(sScanNValueInQuotes,
363
1
                  ccDoubleQuotationMark,
364
1
                  taNone,
365
1
                  sScanNValue);
366
1
   specTransition(sScanNValueInQuotes,
367
1
                  ccBackslash,
368
1
                  taNone,
369
1
                  sAfterEscCharInQuotesInNValue);
370
1
   specTransition(sScanNValueInQuotes,
371
1
                  ccCarriageReturn,
372
1
                  taNone,
373
1
                  sHalfLineBreakInQuotesInNValue);
374
1
   specDefaultTransition(sAfterEscCharInQuotesInNValue,
375
1
                         taNone,
376
1
                         sScanNValueInQuotes);
377
1
   specHalfLineBreakState(sHalfLineBreakInQuotesInNValue,
378
1
                          sAfterLineBreakInQuotesInNValue);
379
1
   specDefaultTransition(sAfterLineBreakInQuotesInNValue,
380
1
                         taError,
381
1
                         sAfterLineBreakInQuotesInNValue);
382
1
   specTransition(sAfterLineBreakInQuotesInNValue,
383
1
                  ccWhitespace,
384
1
                  taNone,
385
1
                  sScanNValueInQuotes);
386
1
   specDefaultTransition(sScanNValueInAngles, taNone, sScanNValueInAngles);
387
1
   specTransition(sScanNValueInAngles,
388
1
                  ccRightAngleBracket,
389
1
                  taNone,
390
1
                  sScanNValue);
391
1
   specTransition(sScanNValueInAngles,
392
1
                  ccCarriageReturn,
393
1
                  taNone,
394
1
                  sHalfLineBreakInAnglesInNValue);
395
1
   specHalfLineBreakState(sHalfLineBreakInAnglesInNValue,
396
1
                          sAfterLineBreakInAnglesInNValue);
397
1
   specDefaultTransition(sAfterLineBreakInAnglesInNValue,
398
1
                         taError,
399
1
                         sAfterLineBreakInAnglesInNValue);
400
1
   specTransition(sAfterLineBreakInAnglesInNValue,
401
1
                  ccWhitespace,
402
1
                  taNone,
403
1
                  sScanNValueInAngles);
404
1
   specHalfLineBreakState(sHalfLineBreakAfterLineBreak, sMsgStart);
405
406
   // Most half-line-break states do nothing when they read a line feed,
407
   // but sHalfLineBreakAfterLineBreak must end the message header scanning.
408
409
1
   specTransition(sHalfLineBreakAfterLineBreak,
410
1
                  ccLineFeed,
411
1
                  taEndHeader,
412
1
                  sMsgStart); // Arbitrary but possibly handy.
413
1
}
414
415
// Debug follows
416
#if defined(RESIP_MSG_HEADER_SCANNER_DEBUG)  
417
418
static void printText(const char *  text,
419
                      unsigned int  textLength)
420
{
421
   const char *charPtr = text;
422
   for (unsigned int counter = 0; counter < textLength; ++charPtr, ++counter)
423
   {
424
      char c = *charPtr;
425
      switch (c)
426
      {
427
         case '\\': printf("\\\\");
428
            break;
429
         case '\r': printf("\\r");
430
            break;
431
         case '\n': printf("\\n");
432
            break;
433
         case '\t': printf("\\t");
434
            break;
435
         case '\0': printf("\\0");
436
            break;
437
         default:   putchar(c);
438
      }
439
   }
440
}
441
442
static const char *
443
categorySymbol(CharCategory c)
444
{
445
   switch(c)
446
   {
447
      case ccChunkTermSentinel: return "TERM";
448
      case ccOther: return "*";
449
      case ccFieldName: return "FName";
450
      case ccWhitespace: return "WS";
451
      case ccColon: return "\\\":\\\"";
452
      case ccDoubleQuotationMark: return "\\\"";
453
      case ccLeftAngleBracket: return "\\\"<\\\"";
454
      case ccRightAngleBracket: return "\\\">\\\"";
455
      case ccBackslash: return "\\\"\\\\\\\"";
456
      case ccComma: return "\\\",\\\"";
457
      case ccCarriageReturn: return "CR";
458
      case ccLineFeed: return "LF";
459
   }
460
   return "??CC??";
461
}
462
463
static const char *
464
categoryName(CharCategory c)
465
{
466
   switch(c)
467
   {
468
      case ccChunkTermSentinel: return "ccChunkTermSentinel";
469
      case ccOther: return "ccOther";
470
      case ccFieldName: return "ccFieldName";
471
      case ccWhitespace: return "ccWhitespace";
472
      case ccColon: return "ccColon";
473
      case ccDoubleQuotationMark: return "ccDoubleQuotationMark";
474
      case ccLeftAngleBracket: return "ccLeftAngleBracket";
475
      case ccRightAngleBracket: return "ccRightAngleBracket";
476
      case ccBackslash: return "ccBackslash";
477
      case ccComma: return "ccComma";
478
      case ccCarriageReturn: return "ccCarriageReturn";
479
      case ccLineFeed: return "ccLineFeed";
480
   }
481
   return "UNKNOWNCC";
482
}
483
484
static const char *
485
cleanName(const char * name)
486
{
487
   // Remove leading type-noise from name
488
   static char *leaders[] = {
489
      "cc",
490
      "s",
491
      "taChunkTerm", // hack to make ChunkTermSentinel smaller
492
      "ta"
493
   };
494
   const int nLeaders = sizeof(leaders)/sizeof(*leaders);
495
   int offset = 0;
496
   for(int i = 0 ; i < nLeaders ; i++)
497
   {
498
      unsigned int l = strlen(leaders[i]);
499
      if (strstr(name,leaders[i]) == name &&
500
          strlen(name) > l && 
501
          isupper(name[l]))
502
      {
503
         offset = l;
504
         break;
505
      }
506
   }
507
   return &name[offset];
508
}
509
510
static const char * 
511
stateName(State state)
512
{
513
   const char *stateName;
514
   switch (state) 
515
   {
516
      case sMsgStart:
517
         stateName = "sMsgStart";
518
         break;
519
      case sHalfLineBreakAtMsgStart:
520
         stateName = "sHalfLineBreakAtMsgStart";
521
         break;
522
      case sScanStatusLine:
523
         stateName = "sScanStatusLine";
524
         break;
525
      case sHalfLineBreakAfterStatusLine:
526
         stateName = "sHalfLineBreakAfterStatusLine";
527
         break;
528
      case sAfterLineBreakAfterStatusLine:
529
         stateName = "sAfterLineBreakAfterStatusLine";
530
         break;
531
      case sScanFieldName:
532
         stateName = "sScanFieldName";
533
         break;
534
      case sScanWhitespaceAfter1FieldName:
535
         stateName = "sScanWhitespaceAfter1FieldName";
536
         break;
537
      case sScanWhitespaceAfterNFieldName:
538
         stateName = "sScanWhitespaceAfterNFieldName";
539
         break;
540
      case sScanWhitespaceOr1Value:
541
         stateName = "sScanWhitespaceOr1Value";
542
         break;
543
      case sScanWhitespaceOrNValue:
544
         stateName = "sScanWhitespaceOrNValue";
545
         break;
546
      case sHalfLineBreakInWhitespaceBefore1Value:
547
         stateName = "sHalfLineBreakInWhitespaceBefore1Value";
548
         break;
549
      case sHalfLineBreakInWhitespaceBeforeNValue:
550
         stateName = "sHalfLineBreakInWhitespaceBeforeNValue";
551
         break;
552
      case sAfterLineBreakInWhitespaceBefore1Value:
553
         stateName = "sAfterLineBreakInWhitespaceBefore1Value";
554
         break;
555
      case sAfterLineBreakInWhitespaceBeforeNValue:
556
         stateName = "sAfterLineBreakInWhitespaceBeforeNValue";
557
         break;
558
      case sScan1Value:
559
         stateName = "sScan1Value";
560
         break;
561
      case sScanNValue:
562
         stateName = "sScanNValue";
563
         break;
564
      case sHalfLineBreakIn1Value:
565
         stateName = "sHalfLineBreakIn1Value";
566
         break;
567
      case sHalfLineBreakInNValue:
568
         stateName = "sHalfLineBreakInNValue";
569
         break;
570
      case sAfterLineBreakIn1Value:
571
         stateName = "sAfterLineBreakIn1Value";
572
         break;
573
      case sAfterLineBreakInNValue:
574
         stateName = "sAfterLineBreakInNValue";
575
         break;
576
      case sScanNValueInQuotes:
577
         stateName = "sScanNValueInQuotes";
578
         break;
579
      case sAfterEscCharInQuotesInNValue:
580
         stateName = "sAfterEscCharInQuotesInNValue";
581
         break;
582
      case sHalfLineBreakInQuotesInNValue:
583
         stateName = "sHalfLineBreakInQuotesInNValue";
584
         break;
585
      case sAfterLineBreakInQuotesInNValue:
586
         stateName = "sAfterLineBreakInQuotesInNValue";
587
         break;
588
      case sScanNValueInAngles:
589
         stateName = "sScanNValueInAngles";
590
         break;
591
      case sHalfLineBreakInAnglesInNValue:
592
         stateName = "sHalfLineBreakInAnglesInNValue";
593
         break;
594
      case sAfterLineBreakInAnglesInNValue:
595
         stateName = "sAfterLineBreakInAnglesInNValue";
596
         break;
597
      case sHalfLineBreakAfterLineBreak:
598
         stateName = "sHalfLineBreakAfterLineBreak";
599
         break;
600
      default:
601
         stateName = "<unknown>";
602
   }//switch
603
   return stateName;
604
}
605
606
static const char *
607
trActionName(TransitionAction transitionAction)
608
{  
609
   const char *transitionActionName;
610
   switch (transitionAction)
611
   {
612
      case taNone:
613
         transitionActionName = "taNone";
614
         break;
615
      case taTermStatusLine:
616
         transitionActionName = "taTermStatusLine";
617
         break;
618
      case taTermFieldName:
619
         transitionActionName = "taTermFieldName";
620
         break;
621
      case taBeyondEmptyValue:
622
         transitionActionName = "taBeyondEmptyValue";
623
         break;
624
      case taTermValueAfterLineBreak:
625
         transitionActionName = "taTermValueAfterLineBreak";
626
         break;
627
      case taTermValue:
628
         transitionActionName = "taTermValue";
629
         break;
630
      case taStartText:
631
         transitionActionName = "taStartText";
632
         break;
633
      case taEndHeader:
634
         transitionActionName = "taEndHeader";
635
         break;
636
      case taChunkTermSentinel:
637
         transitionActionName = "taChunkTermSentinel";
638
         break;
639
      case taError:
640
         transitionActionName = "taError";
641
         break;
642
      default:
643
         transitionActionName = "<unknown>";
644
   }
645
   return transitionActionName;
646
}
647
648
static void
649
printStateTransition(State state,
650
                     char character,
651
                     TransitionAction transitionAction)
652
{
653
   printf("                %s['", cleanName(stateName(state)));
654
   printText(&character, 1);
655
   printf("']: %s\n", cleanName(trActionName(transitionAction)));
656
}
657
#if !defined(RESIP_MSG_HEADER_SCANNER_DEBUG)
658
static const char* stateName(const char*)
659
{ return "RECOMPILE_WITH_SCANNER_DEBUG"; }
660
static const char* trActionName(const char*)
661
{ return stateName(0); }
662
#endif
663
/// START OF MEMBER METHODS
664
665
666
667
int
668
MsgHeaderScanner::dumpStateMachine(int fd)
669
{
670
   FILE *fp = fdopen(fd,"w");
671
   if (!fp) 
672
   {
673
      fprintf(stderr,"MsgHeaderScanner:: unable to open output file\n");
674
      return -1;
675
   }
676
   // Force instance so things are initialized -- YUCK! 
677
   MsgHeaderScanner scanner;(void)scanner;
678
   fprintf(fp,"digraph MsgHeaderScannerFSM {\n");
679
   fprintf(fp,"\tnode[shape=record\n\t\tfontsize=8\n\t\tfontname=\"Helvetica\"\n\t]\n");
680
   fprintf(fp,"\tedge [ fontsize=6 fontname=\"Helvetica\"]\n");
681
   
682
   fprintf(fp,"\tgraph [ ratio=0.8\n\t\tfontsize=6 compound=true ]");
683
   for(int state  = 0 ; state < numStates; ++state)
684
   {
685
      fprintf(fp,
686
              "  %s [ label = \"%d|%s\" ]\n",
687
              cleanName(stateName(state)),
688
              state,
689
              cleanName(stateName(state))
690
         );
691
      for(int category = 0 ; category < numCharCategories; ++category)
692
      {
693
         // Skip Verbose Error or Empty Transitions
694
         if (stateMachine[state][category].nextState == state &&
695
             (stateMachine[state][category].action == taError ||
696
              stateMachine[state][category].action == taNone
697
                )) continue;
698
              
699
         fprintf(fp,
700
                 "    %s -> %s [label=\"%s\\n%s\" ]\n",
701
                 cleanName(stateName(state)),
702
                 cleanName(stateName(stateMachine[state][category].nextState)),
703
                 categorySymbol(category),
704
                 cleanName(trActionName(stateMachine[state][category].action)));
705
      }
706
      fprintf(fp,"\n");
707
   }
708
   fprintf(fp,"}\n");
709
710
   return 0;
711
}
712
713
#endif //defined(RESIP_MSG_HEADER_SCANNER_DEBUG) 
714
715
716
717
#if defined(RESIP_MSG_HEADER_SCANNER_DEBUG)  
718
719
static const char *const multiValuedFieldNameArray[] = {
720
   "allow-events",
721
   "accept-encoding",
722
   "accept-language",
723
   "allow",
724
   "content-language",
725
   "proxy-require",
726
   "require",
727
   "supported",
728
   "subscription-state",
729
   "unsupported",
730
   "security-client",
731
   "security-server",
732
   "security-verify",
733
   "accept",
734
   "call-info",
735
   "alert-info",
736
   "error-info",
737
   "record-route",
738
   "route",
739
   "contact",
740
   "authorization",
741
   "proxy-authenticate",
742
   "proxy-authorization",
743
   "www-authenticate",
744
   "via",
745
   0
746
};
747
748
extern
749
void
750
lookupMsgHeaderFieldInfo(
751
   char *                             fieldName,               //inout
752
   unsigned int                       *fieldNameLength,        //inout
753
   MsgHeaderScanner::TextPropBitMask  fieldNameTextPropBitMask,
754
   int                                *fieldKind,              //out
755
   bool                               *isMultiValueAllowed)    //out
756
{
757
   *isMultiValueAllowed = false;
758
   const char *const *multiValuedFieldNamePtr = multiValuedFieldNameArray;
759
   for (;;)
760
   {
761
      const char *multiValuedFieldName = *multiValuedFieldNamePtr;
762
      if (!multiValuedFieldName) 
763
      {
764
         break;
765
      }
766
      if (strncmp(fieldName, multiValuedFieldName, *fieldNameLength) == 0) 
767
      {
768
         *isMultiValueAllowed = true;
769
         break;
770
      }
771
      ++multiValuedFieldNamePtr;
772
   }//for
773
}
774
775
static
776
bool
777
processMsgHeaderStatusLine(
778
   SipMessage *                       msg,
779
   char *                             lineText,
780
   unsigned int                       lineTextLength,
781
   MsgHeaderScanner::TextPropBitMask  lineTextPropBitMask)
782
{
783
   printf("status line: ");
784
   printText(lineText, lineTextLength);
785
   printf("\n");
786
   return true;
787
}
788
789
static
790
void
791
processMsgHeaderFieldNameAndValue(
792
   SipMessage *                       msg,
793
   int                                fieldKind,
794
   const char *                       fieldName,
795
   unsigned int                       fieldNameLength,
796
   char *                             valueText,
797
   unsigned int                       valueTextLength,
798
   MsgHeaderScanner::TextPropBitMask  valueTextPropBitMask)
799
{
800
   printText(fieldName, fieldNameLength);
801
   printf(": [[[[");
802
   printText(valueText, valueTextLength);
803
   printf("]]]]\n");
804
}
805
806
#else //!defined(RESIP_MSG_HEADER_SCANNER_DEBUG) } {
807
808
809
//   Determine a field's kind and whether it allows (comma separated) multi-values.
810
//   "fieldName" is not empty and contains only legal characters.
811
//   The text in "fieldName" may be canonicalized (eg, translating % escapes),
812
//   including shrinking it if necessary.
813
814
inline void
815
lookupMsgHeaderFieldInfo(char * fieldName,
816
                         unsigned int *fieldNameLength,   
817
                         MsgHeaderScanner::TextPropBitMask fieldNameTextPropBitMask,
818
                         int *fieldKind,             
819
                         bool *isMultiValueAllowed)    
820
1.53k
{
821
   //.jacob. Don't ignore fieldNameTextPropBitMask.
822
1.53k
   *fieldKind = Headers::getType(fieldName, *fieldNameLength);
823
1.53k
   *isMultiValueAllowed =
824
1.53k
      Headers::isCommaTokenizing(static_cast<Headers::Type>(*fieldKind));
825
1.53k
}
826
827
828
// "lineText" contains no carriage returns and no line feeds.
829
// Return true on success, false on failure.
830
831
inline bool
832
processMsgHeaderStatusLine(SipMessage * msg,
833
                           char * lineText,
834
                           unsigned int lineTextLength,
835
                           MsgHeaderScanner::TextPropBitMask lineTextPropBitMask)
836
547
{
837
   //.jacob. Don't ignore valueTextPropBitMask, and don't always return true.
838
547
   msg->setStartLine(lineText, lineTextLength);
839
547
   return true;
840
547
}
841
842
// This function is called once for a field with one value.  (The value could be
843
// several values, but separated by something other than commas.)
844
// This function is called once for a field with 0 comma-separated values, with
845
// an empty value.
846
// This function is called N times for a field with N comma-separated values,
847
// but with the same value of "fieldName" each time.
848
// "fieldName" is not empty and contains only legal characters.
849
// "valueText" may be empty, has no leading whitespace, may contain trailing
850
// whitespace, contains carriage returns and line feeds only in correct pairs
851
// and followed by whitespace, and, if the field is multi-valued, contains
852
// balanced '<'/'>' and '"' pairs, contains ',' only within '<'/'>' or '"'
853
// pairs, and respects '\\'s within '"' pairs.
854
// The text in "valueText" may be canonicalized (eg, translating % escapes),
855
// including shrinking it if necessary.
856
857
inline void
858
processMsgHeaderFieldNameAndValue(SipMessage * msg,
859
                                  int fieldKind,
860
                                  const char * fieldName,
861
                                  unsigned int fieldNameLength,
862
                                  char * valueText,
863
                                  unsigned int valueTextLength,
864
                                  MsgHeaderScanner::TextPropBitMask valueTextPropBitMask)
865
568k
{
866
   //.jacob. Don't ignore valueTextPropBitMask, particularly for '\r' & '\n'.
867
568k
   msg->addHeader(static_cast<Headers::Type>(fieldKind),
868
568k
                  fieldName,
869
568k
                  fieldNameLength,
870
568k
                  valueText,
871
568k
                  valueTextLength);
872
568k
}
873
874
#endif //!defined(RESIP_MSG_HEADER_SCANNER_DEBUG) }
875
876
bool MsgHeaderScanner::mInitialized = false;
877
878
MsgHeaderScanner::MsgHeaderScanner()
879
6.45k
{
880
6.45k
   if (!mInitialized)
881
1
   {
882
1
      mInitialized = true;
883
1
      initialize();
884
1
   }
885
6.45k
}
886
887
void
888
MsgHeaderScanner::prepareForMessage(SipMessage *  msg)
889
6.45k
{
890
6.45k
   mMsg = msg;
891
6.45k
   mState = sMsgStart;
892
6.45k
   mPrevScanChunkNumSavedTextChars = 0;
893
6.45k
   mNumHeaders=0;
894
6.45k
}
895
896
void
897
MsgHeaderScanner::prepareForFrag(SipMessage *  msg, bool hasStartLine)
898
0
{
899
0
   mMsg = msg;
900
0
   if (hasStartLine)
901
0
   {
902
0
      mState = sMsgStart;
903
0
   }
904
0
   else
905
0
   {
906
0
      mState = sAfterLineBreakAfterStatusLine;
907
0
   }
908
0
   mPrevScanChunkNumSavedTextChars = 0;
909
0
   mNumHeaders=0;
910
0
}
911
912
MsgHeaderScanner::ScanChunkResult
913
MsgHeaderScanner::scanChunk(char * chunk,
914
                            unsigned int chunkLength,
915
                            char ** unprocessedCharPtr)
916
6.45k
{
917
6.45k
   MsgHeaderScanner::ScanChunkResult result;
918
6.45k
   CharInfo* localCharInfoArray = charInfoArray;
919
6.45k
   TransitionInfo (*localStateMachine)[numCharCategories] = stateMachine;
920
6.45k
   State localState = mState;
921
6.45k
   char *charPtr = chunk + mPrevScanChunkNumSavedTextChars;
922
6.45k
   char *termCharPtr = chunk + chunkLength;
923
6.45k
   char saveChunkTermChar = *termCharPtr;
924
6.45k
   *termCharPtr = chunkTermSentinelChar;
925
6.45k
   char *textStartCharPtr;
926
6.45k
   MsgHeaderScanner::TextPropBitMask localTextPropBitMask = mTextPropBitMask;
927
6.45k
   if (mPrevScanChunkNumSavedTextChars == 0)
928
6.45k
   {
929
6.45k
      textStartCharPtr = 0;
930
6.45k
   }
931
0
   else
932
0
   {
933
0
      textStartCharPtr = chunk;
934
0
   }
935
6.45k
   --charPtr;  // The loop starts by advancing "charPtr", so pre-adjust it.
936
6.45k
   for (;;)
937
52.2M
   {
938
      // BEGIN message header character scan block BEGIN
939
      // The code in this block is executed once per message header character.
940
      // This entire file is designed specifically to minimize this block's size.
941
52.2M
      ++charPtr;
942
52.2M
      CharInfo *charInfo = &localCharInfoArray[((unsigned char) (*charPtr))];
943
52.2M
      CharCategory charCategory = charInfo->category;
944
52.2M
      localTextPropBitMask |= charInfo->textPropBitMask;
945
67.0M
     determineTransitionFromCharCategory:
946
67.0M
      TransitionInfo *transitionInfo =
947
67.0M
         &(localStateMachine[(unsigned)localState][(size_t)charCategory]);
948
67.0M
      TransitionAction transitionAction = transitionInfo->action;
949
#if defined(RESIP_MSG_HEADER_SCANNER_DEBUG)  
950
      printStateTransition(localState, *charPtr, transitionAction);
951
#endif
952
67.0M
      localState = transitionInfo->nextState;
953
67.0M
      if (transitionAction == taNone) continue;
954
      // END message header character scan block END
955
      // The loop remainder is executed about 4-5 times per message header line.
956
15.9M
      switch (transitionAction)
957
15.9M
      {
958
547
         case taTermStatusLine:
959
547
            if (!processMsgHeaderStatusLine(mMsg,
960
547
                                            textStartCharPtr,
961
547
                                            (unsigned int)(charPtr - textStartCharPtr),
962
547
                                            localTextPropBitMask))
963
0
            {
964
0
               result = MsgHeaderScanner::scrError;
965
0
               *unprocessedCharPtr = charPtr;
966
0
               goto endOfFunction;
967
0
            }
968
547
            textStartCharPtr = 0;
969
547
            break;
970
1.53k
         case taTermFieldName:
971
1.53k
         {
972
1.53k
            mFieldNameLength = (unsigned int)(charPtr - textStartCharPtr);
973
1.53k
            bool isMultiValueAllowed;
974
1.53k
            lookupMsgHeaderFieldInfo(textStartCharPtr,
975
1.53k
                                     &mFieldNameLength,
976
1.53k
                                     localTextPropBitMask,
977
1.53k
                                     &mFieldKind,
978
1.53k
                                     &isMultiValueAllowed);
979
1.53k
            mFieldName = textStartCharPtr;
980
1.53k
            textStartCharPtr = 0;
981
1.53k
            if (isMultiValueAllowed) 
982
424
            {
983
424
               localState += deltaOfNStateFrom1State;
984
424
            }
985
1.53k
         }
986
1.53k
         break;
987
1.02k
         case taBeyondEmptyValue:
988
1.02k
            processMsgHeaderFieldNameAndValue(mMsg,
989
1.02k
                                              mFieldKind,
990
1.02k
                                              mFieldName,
991
1.02k
                                              mFieldNameLength,
992
1.02k
                                              0,
993
1.02k
                                              0,
994
1.02k
                                              0);
995
1.02k
            ++mNumHeaders;
996
1.02k
            goto performStartTextAction;
997
368
         case taTermValueAfterLineBreak:
998
368
            processMsgHeaderFieldNameAndValue(mMsg,
999
368
                                              mFieldKind,
1000
368
                                              mFieldName,
1001
368
                                              mFieldNameLength,
1002
368
                                              textStartCharPtr,
1003
368
                                              (unsigned int)((charPtr - textStartCharPtr) - 2),
1004
368
                                              localTextPropBitMask);       //^:CRLF
1005
368
            ++mNumHeaders;
1006
368
            goto performStartTextAction;
1007
566k
         case taTermValue:
1008
566k
            processMsgHeaderFieldNameAndValue(mMsg,
1009
566k
                                              mFieldKind,
1010
566k
                                              mFieldName,
1011
566k
                                              mFieldNameLength,
1012
566k
                                              textStartCharPtr,
1013
566k
                                              (unsigned int)(charPtr - textStartCharPtr),
1014
566k
                                              localTextPropBitMask);
1015
566k
            textStartCharPtr = 0;
1016
566k
            ++mNumHeaders;
1017
566k
            break;
1018
574k
         case taStartText:
1019
575k
        performStartTextAction:
1020
575k
            textStartCharPtr = charPtr;
1021
575k
            localTextPropBitMask = 0;
1022
575k
            break;
1023
115
         case taEndHeader:
1024
            // textStartCharPtr is not 0.  Not currently relevant.
1025
115
            result = MsgHeaderScanner::scrEnd;
1026
115
            *unprocessedCharPtr = charPtr + 1;  // The current char is processed.
1027
115
            goto endOfFunction;
1028
0
            break;
1029
14.8M
         case taChunkTermSentinel:
1030
14.8M
            if (charPtr == termCharPtr)
1031
3.00k
            {
1032
               // The chunk has been consumed.  Save some state and request another.
1033
3.00k
               mState = localState;
1034
3.00k
               if (textStartCharPtr == 0) 
1035
180
               {
1036
180
                  mPrevScanChunkNumSavedTextChars = 0;
1037
180
               }
1038
2.82k
               else
1039
2.82k
               {
1040
2.82k
                  mPrevScanChunkNumSavedTextChars = (unsigned int)(termCharPtr - textStartCharPtr);
1041
2.82k
               }
1042
3.00k
               mTextPropBitMask = localTextPropBitMask;
1043
3.00k
               result = MsgHeaderScanner::scrNextChunk;
1044
3.00k
               *unprocessedCharPtr = termCharPtr - mPrevScanChunkNumSavedTextChars;
1045
3.00k
               goto endOfFunction;
1046
3.00k
            }
1047
14.7M
            else
1048
14.7M
            {
1049
               // The character is not the sentinel.  Treat it like any other.
1050
14.7M
               charCategory = ccOther;
1051
14.7M
               goto determineTransitionFromCharCategory;
1052
14.7M
            }
1053
0
            break;
1054
3.33k
         default:
1055
3.33k
            result = MsgHeaderScanner::scrError;
1056
3.33k
            *unprocessedCharPtr = charPtr;
1057
3.33k
            goto endOfFunction;
1058
15.9M
      }//switch
1059
15.9M
   }//for
1060
6.45k
  endOfFunction:
1061
6.45k
   *termCharPtr = saveChunkTermChar;
1062
6.45k
   return result;
1063
6.45k
}
1064
1065
bool
1066
MsgHeaderScanner::initialize()
1067
1
{
1068
1
   initCharInfoArray();
1069
1
   initStateMachine();
1070
1
   return true;
1071
1
}
1072
1073
1074
} //namespace resip
1075
1076
1077
1078
#if defined(RESIP_MSG_HEADER_SCANNER_DEBUG) && defined(MSG_SCANNER_STANDALONE)
1079
1080
extern
1081
int
1082
main(unsigned int   numArgs,
1083
     const char * * argVector)
1084
{
1085
   ::resip::MsgHeaderScanner scanner;
1086
   scanner.prepareForMessage(0);
1087
   char *text =
1088
      "status\r\n"
1089
      "bobby: dummy\r\n"
1090
      "allow: foo, bar, \"don,\\\"xyz\r\n zy\", buzz\r\n\r\n";
1091
   unsigned int textLength = strlen(text);
1092
   char chunk[10000];
1093
   strcpy(chunk, text);
1094
   ::resip::MsgHeaderScanner::ScanChunkResult scanChunkResult;
1095
   char *unprocessedCharPtr;
1096
   scanChunkResult = scanner.scanChunk(chunk, 21, &unprocessedCharPtr);
1097
   if (scanChunkResult == ::resip::MsgHeaderScanner::scrNextChunk)
1098
   {
1099
      printf("Scanning another chunk '.");
1100
      ::resip::printText(unprocessedCharPtr, 1);
1101
      printf("'\n");
1102
      scanChunkResult =
1103
         scanner.scanChunk(unprocessedCharPtr,
1104
                           (chunk + textLength) - unprocessedCharPtr,
1105
                           &unprocessedCharPtr);
1106
   }
1107
   if (scanChunkResult != ::resip::MsgHeaderScanner::scrEnd)
1108
   {
1109
      printf("Error %d at character %d.\n",
1110
             scanChunkResult,
1111
             unprocessedCharPtr - chunk);
1112
   }
1113
   return 0;
1114
}
1115
1116
#endif //!defined(RESIP_MSG_HEADER_SCANNER_DEBUG) }
1117
1118
/* ====================================================================
1119
 * The Vovida Software License, Version 1.0 
1120
 * 
1121
 * Copyright (c) 2000-2005
1122
 * 
1123
 * Redistribution and use in source and binary forms, with or without
1124
 * modification, are permitted provided that the following conditions
1125
 * are met:
1126
 * 
1127
 * 1. Redistributions of source code must retain the above copyright
1128
 *    notice, this list of conditions and the following disclaimer.
1129
 * 
1130
 * 2. Redistributions in binary form must reproduce the above copyright
1131
 *    notice, this list of conditions and the following disclaimer in
1132
 *    the documentation and/or other materials provided with the
1133
 *    distribution.
1134
 * 
1135
 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
1136
 *    and "Vovida Open Communication Application Library (VOCAL)" must
1137
 *    not be used to endorse or promote products derived from this
1138
 *    software without prior written permission. For written
1139
 *    permission, please contact vocal@vovida.org.
1140
 *
1141
 * 4. Products derived from this software may not be called "VOCAL", nor
1142
 *    may "VOCAL" appear in their name, without prior written
1143
 *    permission of Vovida Networks, Inc.
1144
 * 
1145
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
1146
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1147
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
1148
 * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
1149
 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
1150
 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
1151
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
1152
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
1153
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
1154
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1155
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1156
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1157
 * DAMAGE.
1158
 * 
1159
 * ====================================================================
1160
 * 
1161
 * This software consists of voluntary contributions made by Vovida
1162
 * Networks, Inc. and many individuals on behalf of Vovida Networks,
1163
 * Inc.  For more information on Vovida Networks, Inc., please see
1164
 * <http://www.vovida.org/>.
1165
 *
1166
 */