Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/ds/nsPersistentProperties.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "nsArrayEnumerator.h"
8
#include "nsID.h"
9
#include "nsCOMArray.h"
10
#include "nsUnicharInputStream.h"
11
#include "nsPrintfCString.h"
12
#include "nsAutoPtr.h"
13
14
#include "nsPersistentProperties.h"
15
#include "nsIProperties.h"
16
17
#include "mozilla/ArenaAllocatorExtensions.h"
18
19
using mozilla::ArenaStrdup;
20
21
struct PropertyTableEntry : public PLDHashEntryHdr
22
{
23
  // both of these are arena-allocated
24
  const char* mKey;
25
  const char16_t* mValue;
26
};
27
28
static const struct PLDHashTableOps property_HashTableOps = {
29
  PLDHashTable::HashStringKey,
30
  PLDHashTable::MatchStringKey,
31
  PLDHashTable::MoveEntryStub,
32
  PLDHashTable::ClearEntryStub,
33
  nullptr,
34
};
35
36
//
37
// parser stuff
38
//
39
enum EParserState
40
{
41
  eParserState_AwaitingKey,
42
  eParserState_Key,
43
  eParserState_AwaitingValue,
44
  eParserState_Value,
45
  eParserState_Comment
46
};
47
48
enum EParserSpecial
49
{
50
  eParserSpecial_None,          // not parsing a special character
51
  eParserSpecial_Escaped,       // awaiting a special character
52
  eParserSpecial_Unicode        // parsing a \Uxxx value
53
};
54
55
class MOZ_STACK_CLASS nsPropertiesParser
56
{
57
public:
58
  explicit nsPropertiesParser(nsIPersistentProperties* aProps)
59
    : mUnicodeValuesRead(0)
60
    , mUnicodeValue(u'\0')
61
    , mHaveMultiLine(false)
62
    , mMultiLineCanSkipN(false)
63
    , mMinLength(0)
64
    , mState(eParserState_AwaitingKey)
65
    , mSpecialState(eParserSpecial_None)
66
    , mProps(aProps)
67
0
  {
68
0
  }
69
70
  void FinishValueState(nsAString& aOldValue)
71
0
  {
72
0
    static const char trimThese[] = " \t";
73
0
    mKey.Trim(trimThese, false, true);
74
0
75
0
    // This is really ugly hack but it should be fast
76
0
    char16_t backup_char;
77
0
    uint32_t minLength = mMinLength;
78
0
    if (minLength) {
79
0
      backup_char = mValue[minLength - 1];
80
0
      mValue.SetCharAt('x', minLength - 1);
81
0
    }
82
0
    mValue.Trim(trimThese, false, true);
83
0
    if (minLength) {
84
0
      mValue.SetCharAt(backup_char, minLength - 1);
85
0
    }
86
0
87
0
    mProps->SetStringProperty(NS_ConvertUTF16toUTF8(mKey), mValue, aOldValue);
88
0
    mSpecialState = eParserSpecial_None;
89
0
    WaitForKey();
90
0
  }
91
92
0
  EParserState GetState() { return mState; }
93
94
  static nsresult SegmentWriter(nsIUnicharInputStream* aStream,
95
                                void* aClosure,
96
                                const char16_t* aFromSegment,
97
                                uint32_t aToOffset,
98
                                uint32_t aCount,
99
                                uint32_t* aWriteCount);
100
101
  nsresult ParseBuffer(const char16_t* aBuffer, uint32_t aBufferLength);
102
103
private:
104
  bool ParseValueCharacter(
105
    char16_t aChar,               // character that is just being parsed
106
    const char16_t* aCur,         // pointer to character aChar in the buffer
107
    const char16_t*& aTokenStart, // string copying is done in blocks as big as
108
                                  // possible, aTokenStart points to the beginning
109
                                  // of this block
110
    nsAString& aOldValue);        // when duplicate property is found, new value
111
                                  // is stored into hashtable and the old one is
112
                                  // placed in this variable
113
114
  void WaitForKey()
115
0
  {
116
0
    mState = eParserState_AwaitingKey;
117
0
  }
118
119
  void EnterKeyState()
120
0
  {
121
0
    mKey.Truncate();
122
0
    mState = eParserState_Key;
123
0
  }
124
125
  void WaitForValue()
126
0
  {
127
0
    mState = eParserState_AwaitingValue;
128
0
  }
129
130
  void EnterValueState()
131
0
  {
132
0
    mValue.Truncate();
133
0
    mMinLength = 0;
134
0
    mState = eParserState_Value;
135
0
    mSpecialState = eParserSpecial_None;
136
0
  }
137
138
  void EnterCommentState()
139
0
  {
140
0
    mState = eParserState_Comment;
141
0
  }
142
143
  nsAutoString mKey;
144
  nsAutoString mValue;
145
146
  uint32_t  mUnicodeValuesRead; // should be 4!
147
  char16_t mUnicodeValue;      // currently parsed unicode value
148
  bool      mHaveMultiLine;     // is TRUE when last processed characters form
149
                                // any of following sequences:
150
                                //  - "\\\r"
151
                                //  - "\\\n"
152
                                //  - "\\\r\n"
153
                                //  - any sequence above followed by any
154
                                //    combination of ' ' and '\t'
155
  bool      mMultiLineCanSkipN; // TRUE if "\\\r" was detected
156
  uint32_t  mMinLength;         // limit right trimming at the end to not trim
157
                                // escaped whitespaces
158
  EParserState mState;
159
  // if we see a '\' then we enter this special state
160
  EParserSpecial mSpecialState;
161
  nsCOMPtr<nsIPersistentProperties> mProps;
162
};
163
164
inline bool
165
IsWhiteSpace(char16_t aChar)
166
0
{
167
0
  return (aChar == ' ') || (aChar == '\t') ||
168
0
         (aChar == '\r') || (aChar == '\n');
169
0
}
170
171
inline bool
172
IsEOL(char16_t aChar)
173
0
{
174
0
  return (aChar == '\r') || (aChar == '\n');
175
0
}
176
177
178
bool
179
nsPropertiesParser::ParseValueCharacter(char16_t aChar, const char16_t* aCur,
180
                                        const char16_t*& aTokenStart,
181
                                        nsAString& aOldValue)
182
0
{
183
0
  switch (mSpecialState) {
184
0
    // the normal state - look for special characters
185
0
    case eParserSpecial_None:
186
0
      switch (aChar) {
187
0
        case '\\':
188
0
          if (mHaveMultiLine) {
189
0
            // there is nothing to append to mValue yet
190
0
            mHaveMultiLine = false;
191
0
          } else {
192
0
            mValue += Substring(aTokenStart, aCur);
193
0
          }
194
0
195
0
          mSpecialState = eParserSpecial_Escaped;
196
0
          break;
197
0
198
0
        case '\n':
199
0
          // if we detected multiline and got only "\\\r" ignore next "\n" if any
200
0
          if (mHaveMultiLine && mMultiLineCanSkipN) {
201
0
            // but don't allow another '\n' to be skipped
202
0
            mMultiLineCanSkipN = false;
203
0
            // Now there is nothing to append to the mValue since we are skipping
204
0
            // whitespaces at the beginning of the new line of the multiline
205
0
            // property. Set aTokenStart properly to ensure that nothing is appended
206
0
            // if we find regular line-end or the end of the buffer.
207
0
            aTokenStart = aCur + 1;
208
0
            break;
209
0
          }
210
0
          MOZ_FALLTHROUGH;
211
0
212
0
        case '\r':
213
0
          // we're done! We have a key and value
214
0
          mValue += Substring(aTokenStart, aCur);
215
0
          FinishValueState(aOldValue);
216
0
          mHaveMultiLine = false;
217
0
          break;
218
0
219
0
        default:
220
0
          // there is nothing to do with normal characters,
221
0
          // but handle multilines correctly
222
0
          if (mHaveMultiLine) {
223
0
            if (aChar == ' ' || aChar == '\t') {
224
0
              // don't allow another '\n' to be skipped
225
0
              mMultiLineCanSkipN = false;
226
0
              // Now there is nothing to append to the mValue since we are skipping
227
0
              // whitespaces at the beginning of the new line of the multiline
228
0
              // property. Set aTokenStart properly to ensure that nothing is appended
229
0
              // if we find regular line-end or the end of the buffer.
230
0
              aTokenStart = aCur + 1;
231
0
              break;
232
0
            }
233
0
            mHaveMultiLine = false;
234
0
            aTokenStart = aCur;
235
0
          }
236
0
          break; // from switch on (aChar)
237
0
      }
238
0
      break; // from switch on (mSpecialState)
239
0
240
0
    // saw a \ character, so parse the character after that
241
0
    case eParserSpecial_Escaped:
242
0
      // probably want to start parsing at the next token
243
0
      // other characters, like 'u' might override this
244
0
      aTokenStart = aCur + 1;
245
0
      mSpecialState = eParserSpecial_None;
246
0
247
0
      switch (aChar) {
248
0
        // the easy characters - \t, \n, and so forth
249
0
        case 't':
250
0
          mValue += char16_t('\t');
251
0
          mMinLength = mValue.Length();
252
0
          break;
253
0
        case 'n':
254
0
          mValue += char16_t('\n');
255
0
          mMinLength = mValue.Length();
256
0
          break;
257
0
        case 'r':
258
0
          mValue += char16_t('\r');
259
0
          mMinLength = mValue.Length();
260
0
          break;
261
0
        case '\\':
262
0
          mValue += char16_t('\\');
263
0
          break;
264
0
265
0
        // switch to unicode mode!
266
0
        case 'u':
267
0
        case 'U':
268
0
          mSpecialState = eParserSpecial_Unicode;
269
0
          mUnicodeValuesRead = 0;
270
0
          mUnicodeValue = 0;
271
0
          break;
272
0
273
0
        // a \ immediately followed by a newline means we're going multiline
274
0
        case '\r':
275
0
        case '\n':
276
0
          mHaveMultiLine = true;
277
0
          mMultiLineCanSkipN = (aChar == '\r');
278
0
          mSpecialState = eParserSpecial_None;
279
0
          break;
280
0
281
0
        default:
282
0
          // don't recognize the character, so just append it
283
0
          mValue += aChar;
284
0
          break;
285
0
      }
286
0
      break;
287
0
288
0
    // we're in the middle of parsing a 4-character unicode value
289
0
    // like \u5f39
290
0
    case eParserSpecial_Unicode:
291
0
      if ('0' <= aChar && aChar <= '9') {
292
0
        mUnicodeValue =
293
0
          (mUnicodeValue << 4) | (aChar - '0');
294
0
      } else if ('a' <= aChar && aChar <= 'f') {
295
0
        mUnicodeValue =
296
0
          (mUnicodeValue << 4) | (aChar - 'a' + 0x0a);
297
0
      } else if ('A' <= aChar && aChar <= 'F') {
298
0
        mUnicodeValue =
299
0
          (mUnicodeValue << 4) | (aChar - 'A' + 0x0a);
300
0
      } else {
301
0
        // non-hex character. Append what we have, and move on.
302
0
        mValue += mUnicodeValue;
303
0
        mMinLength = mValue.Length();
304
0
        mSpecialState = eParserSpecial_None;
305
0
306
0
        // leave aTokenStart at this unknown character, so it gets appended
307
0
        aTokenStart = aCur;
308
0
309
0
        // ensure parsing this non-hex character again
310
0
        return false;
311
0
      }
312
0
313
0
      if (++mUnicodeValuesRead >= 4) {
314
0
        aTokenStart = aCur + 1;
315
0
        mSpecialState = eParserSpecial_None;
316
0
        mValue += mUnicodeValue;
317
0
        mMinLength = mValue.Length();
318
0
      }
319
0
320
0
      break;
321
0
  }
322
0
323
0
  return true;
324
0
}
325
326
nsresult
327
nsPropertiesParser::SegmentWriter(nsIUnicharInputStream* aStream,
328
                                  void* aClosure,
329
                                  const char16_t* aFromSegment,
330
                                  uint32_t aToOffset,
331
                                  uint32_t aCount,
332
                                  uint32_t* aWriteCount)
333
0
{
334
0
  nsPropertiesParser* parser = static_cast<nsPropertiesParser*>(aClosure);
335
0
  parser->ParseBuffer(aFromSegment, aCount);
336
0
337
0
  *aWriteCount = aCount;
338
0
  return NS_OK;
339
0
}
340
341
nsresult
342
nsPropertiesParser::ParseBuffer(const char16_t* aBuffer,
343
                                uint32_t aBufferLength)
344
0
{
345
0
  const char16_t* cur = aBuffer;
346
0
  const char16_t* end = aBuffer + aBufferLength;
347
0
348
0
  // points to the start/end of the current key or value
349
0
  const char16_t* tokenStart = nullptr;
350
0
351
0
  // if we're in the middle of parsing a key or value, make sure
352
0
  // the current token points to the beginning of the current buffer
353
0
  if (mState == eParserState_Key ||
354
0
      mState == eParserState_Value) {
355
0
    tokenStart = aBuffer;
356
0
  }
357
0
358
0
  nsAutoString oldValue;
359
0
360
0
  while (cur != end) {
361
0
362
0
    char16_t c = *cur;
363
0
364
0
    switch (mState) {
365
0
      case eParserState_AwaitingKey:
366
0
        if (c == '#' || c == '!') {
367
0
          EnterCommentState();
368
0
        }
369
0
370
0
        else if (!IsWhiteSpace(c)) {
371
0
          // not a comment, not whitespace, we must have found a key!
372
0
          EnterKeyState();
373
0
          tokenStart = cur;
374
0
        }
375
0
        break;
376
0
377
0
      case eParserState_Key:
378
0
        if (c == '=' || c == ':') {
379
0
          mKey += Substring(tokenStart, cur);
380
0
          WaitForValue();
381
0
        }
382
0
        break;
383
0
384
0
      case eParserState_AwaitingValue:
385
0
        if (IsEOL(c)) {
386
0
          // no value at all! mimic the normal value-ending
387
0
          EnterValueState();
388
0
          FinishValueState(oldValue);
389
0
        }
390
0
391
0
        // ignore white space leading up to the value
392
0
        else if (!IsWhiteSpace(c)) {
393
0
          tokenStart = cur;
394
0
          EnterValueState();
395
0
396
0
          // make sure to handle this first character
397
0
          if (ParseValueCharacter(c, cur, tokenStart, oldValue)) {
398
0
            cur++;
399
0
          }
400
0
          // If the character isn't consumed, don't do cur++ and parse
401
0
          // the character again. This can happen f.e. for char 'X' in sequence
402
0
          // "\u00X". This character can be control character and must be
403
0
          // processed again.
404
0
          continue;
405
0
        }
406
0
        break;
407
0
408
0
      case eParserState_Value:
409
0
        if (ParseValueCharacter(c, cur, tokenStart, oldValue)) {
410
0
          cur++;
411
0
        }
412
0
        // See few lines above for reason of doing this
413
0
        continue;
414
0
415
0
      case eParserState_Comment:
416
0
        // stay in this state till we hit EOL
417
0
        if (c == '\r' || c == '\n') {
418
0
          WaitForKey();
419
0
        }
420
0
        break;
421
0
    }
422
0
423
0
    // finally, advance to the next character
424
0
    cur++;
425
0
  }
426
0
427
0
  // if we're still parsing the value and are in eParserSpecial_None, then
428
0
  // append whatever we have..
429
0
  if (mState == eParserState_Value && tokenStart &&
430
0
      mSpecialState == eParserSpecial_None) {
431
0
    mValue += Substring(tokenStart, cur);
432
0
  }
433
0
  // if we're still parsing the key, then append whatever we have..
434
0
  else if (mState == eParserState_Key && tokenStart) {
435
0
    mKey += Substring(tokenStart, cur);
436
0
  }
437
0
438
0
  return NS_OK;
439
0
}
440
441
nsPersistentProperties::nsPersistentProperties()
442
  : mIn(nullptr)
443
  , mTable(&property_HashTableOps, sizeof(PropertyTableEntry), 16)
444
  , mArena()
445
0
{
446
0
}
447
448
nsPersistentProperties::~nsPersistentProperties()
449
0
{
450
0
}
451
452
size_t
453
nsPersistentProperties::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
454
0
{
455
0
  // The memory used by mTable is accounted for in mArena.
456
0
  size_t n = 0;
457
0
  n += mArena.SizeOfExcludingThis(aMallocSizeOf);
458
0
  n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
459
0
  return aMallocSizeOf(this) + n;
460
0
}
461
462
NS_IMPL_ISUPPORTS(nsPersistentProperties, nsIPersistentProperties, nsIProperties)
463
464
NS_IMETHODIMP
465
nsPersistentProperties::Load(nsIInputStream* aIn)
466
0
{
467
0
  nsresult rv = NS_NewUnicharInputStream(aIn, getter_AddRefs(mIn));
468
0
469
0
  if (rv != NS_OK) {
470
0
    NS_WARNING("Error creating UnicharInputStream");
471
0
    return NS_ERROR_FAILURE;
472
0
  }
473
0
474
0
  nsPropertiesParser parser(this);
475
0
476
0
  uint32_t nProcessed;
477
0
  // If this 4096 is changed to some other value, make sure to adjust
478
0
  // the bug121341.properties test file accordingly.
479
0
  while (NS_SUCCEEDED(rv = mIn->ReadSegments(nsPropertiesParser::SegmentWriter,
480
0
                                             &parser, 4096, &nProcessed)) &&
481
0
         nProcessed != 0);
482
0
  mIn = nullptr;
483
0
  if (NS_FAILED(rv)) {
484
0
    return rv;
485
0
  }
486
0
487
0
  // We may have an unprocessed value at this point
488
0
  // if the last line did not have a proper line ending.
489
0
  if (parser.GetState() == eParserState_Value) {
490
0
    nsAutoString oldValue;
491
0
    parser.FinishValueState(oldValue);
492
0
  }
493
0
494
0
  return NS_OK;
495
0
}
496
497
NS_IMETHODIMP
498
nsPersistentProperties::SetStringProperty(const nsACString& aKey,
499
                                          const nsAString& aNewValue,
500
                                          nsAString& aOldValue)
501
0
{
502
0
  const nsCString& flatKey = PromiseFlatCString(aKey);
503
0
  auto entry = static_cast<PropertyTableEntry*>
504
0
                          (mTable.Add(flatKey.get()));
505
0
506
0
  if (entry->mKey) {
507
0
    aOldValue = entry->mValue;
508
0
    NS_WARNING(nsPrintfCString("the property %s already exists",
509
0
                               flatKey.get()).get());
510
0
  } else {
511
0
    aOldValue.Truncate();
512
0
  }
513
0
514
0
  entry->mKey = ArenaStrdup(flatKey, mArena);
515
0
  entry->mValue = ArenaStrdup(aNewValue, mArena);
516
0
517
0
  return NS_OK;
518
0
}
519
520
NS_IMETHODIMP
521
nsPersistentProperties::Save(nsIOutputStream* aOut, const nsACString& aHeader)
522
0
{
523
0
  return NS_ERROR_NOT_IMPLEMENTED;
524
0
}
525
526
NS_IMETHODIMP
527
nsPersistentProperties::GetStringProperty(const nsACString& aKey,
528
                                          nsAString& aValue)
529
0
{
530
0
  const nsCString& flatKey = PromiseFlatCString(aKey);
531
0
532
0
  auto entry = static_cast<PropertyTableEntry*>(mTable.Search(flatKey.get()));
533
0
  if (!entry) {
534
0
    return NS_ERROR_FAILURE;
535
0
  }
536
0
537
0
  aValue = entry->mValue;
538
0
  return NS_OK;
539
0
}
540
541
NS_IMETHODIMP
542
nsPersistentProperties::Enumerate(nsISimpleEnumerator** aResult)
543
0
{
544
0
  nsCOMArray<nsIPropertyElement> props;
545
0
546
0
  // We know the necessary size; we can avoid growing it while adding elements
547
0
  props.SetCapacity(mTable.EntryCount());
548
0
549
0
  // Step through hash entries populating a transient array
550
0
  for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
551
0
    auto entry = static_cast<PropertyTableEntry*>(iter.Get());
552
0
553
0
    RefPtr<nsPropertyElement> element =
554
0
      new nsPropertyElement(nsDependentCString(entry->mKey),
555
0
                            nsDependentString(entry->mValue));
556
0
557
0
    if (!props.AppendObject(element)) {
558
0
      return NS_ERROR_OUT_OF_MEMORY;
559
0
    }
560
0
  }
561
0
562
0
  return NS_NewArrayEnumerator(aResult, props, NS_GET_IID(nsIPropertyElement));
563
0
}
564
565
////////////////////////////////////////////////////////////////////////////////
566
// XXX Some day we'll unify the nsIPersistentProperties interface with
567
// nsIProperties, but until now...
568
569
NS_IMETHODIMP
570
nsPersistentProperties::Get(const char* aProp, const nsIID& aUUID,
571
                            void** aResult)
572
0
{
573
0
  return NS_ERROR_NOT_IMPLEMENTED;
574
0
}
575
576
NS_IMETHODIMP
577
nsPersistentProperties::Set(const char* aProp, nsISupports* value)
578
0
{
579
0
  return NS_ERROR_NOT_IMPLEMENTED;
580
0
}
581
NS_IMETHODIMP
582
nsPersistentProperties::Undefine(const char* aProp)
583
0
{
584
0
  return NS_ERROR_NOT_IMPLEMENTED;
585
0
}
586
587
NS_IMETHODIMP
588
nsPersistentProperties::Has(const char* aProp, bool* aResult)
589
0
{
590
0
  *aResult = !!mTable.Search(aProp);
591
0
  return NS_OK;
592
0
}
593
594
NS_IMETHODIMP
595
nsPersistentProperties::GetKeys(uint32_t* aCount, char*** aKeys)
596
0
{
597
0
  return NS_ERROR_NOT_IMPLEMENTED;
598
0
}
599
600
////////////////////////////////////////////////////////////////////////////////
601
// PropertyElement
602
////////////////////////////////////////////////////////////////////////////////
603
604
nsresult
605
nsPropertyElement::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult)
606
0
{
607
0
  if (aOuter) {
608
0
    return NS_ERROR_NO_AGGREGATION;
609
0
  }
610
0
  RefPtr<nsPropertyElement> propElem = new nsPropertyElement();
611
0
  return propElem->QueryInterface(aIID, aResult);
612
0
}
613
614
NS_IMPL_ISUPPORTS(nsPropertyElement, nsIPropertyElement)
615
616
NS_IMETHODIMP
617
nsPropertyElement::GetKey(nsACString& aReturnKey)
618
0
{
619
0
  aReturnKey = mKey;
620
0
  return NS_OK;
621
0
}
622
623
NS_IMETHODIMP
624
nsPropertyElement::GetValue(nsAString& aReturnValue)
625
0
{
626
0
  aReturnValue = mValue;
627
0
  return NS_OK;
628
0
}
629
630
NS_IMETHODIMP
631
nsPropertyElement::SetKey(const nsACString& aKey)
632
0
{
633
0
  mKey = aKey;
634
0
  return NS_OK;
635
0
}
636
637
NS_IMETHODIMP
638
nsPropertyElement::SetValue(const nsAString& aValue)
639
0
{
640
0
  mValue = aValue;
641
0
  return NS_OK;
642
0
}
643
644
////////////////////////////////////////////////////////////////////////////////