1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Pierre Phaneuf <pp@ludusdesign.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
41 #include "nsReadableUtils.h"
42 #include "nsIInputStream.h"
43 #include "nsUnicharInputStream.h"
45 #include "nsEnumeratorUtils.h"
46 #include "nsReadableUtils.h"
47 #include "nsPrintfCString.h"
48 #include "nsDependentString.h"
50 #define PL_ARENA_CONST_ALIGN_MASK 3
51 #include "nsPersistentProperties.h"
52 #include "nsIProperties.h"
53 #include "nsISupportsArray.h"
54 #include "nsProperties.h"
56 struct PropertyTableEntry
: public PLDHashEntryHdr
58 // both of these are arena-allocated
60 const PRUnichar
*mValue
;
64 ArenaStrdup(const nsAFlatString
& aString
, PLArenaPool
* aArena
)
67 // add one to include the null terminator
68 PRInt32 len
= (aString
.Length()+1) * sizeof(PRUnichar
);
69 PL_ARENA_ALLOCATE(mem
, aArena
, len
);
70 NS_ASSERTION(mem
, "Couldn't allocate space!\n");
72 memcpy(mem
, aString
.get(), len
);
74 return static_cast<PRUnichar
*>(mem
);
78 ArenaStrdup(const nsAFlatCString
& aString
, PLArenaPool
* aArena
)
81 // add one to include the null terminator
82 PRInt32 len
= (aString
.Length()+1) * sizeof(char);
83 PL_ARENA_ALLOCATE(mem
, aArena
, len
);
84 NS_ASSERTION(mem
, "Couldn't allocate space!\n");
86 memcpy(mem
, aString
.get(), len
);
87 return static_cast<char*>(mem
);
90 static const struct PLDHashTableOps property_HashTableOps
= {
94 PL_DHashMatchStringKey
,
95 PL_DHashMoveEntryStub
,
96 PL_DHashClearEntryStub
,
105 eParserState_AwaitingKey
,
107 eParserState_AwaitingValue
,
112 enum EParserSpecial
{
113 eParserSpecial_None
, // not parsing a special character
114 eParserSpecial_Escaped
, // awaiting a special character
115 eParserSpecial_Unicode
// parsing a \Uxxx value
118 class nsPropertiesParser
121 nsPropertiesParser(nsIPersistentProperties
* aProps
) :
122 mHaveMultiLine(PR_FALSE
), mState(eParserState_AwaitingKey
),
125 void FinishValueState(nsAString
& aOldValue
) {
126 static const char trimThese
[] = " \t";
127 mKey
.Trim(trimThese
, PR_FALSE
, PR_TRUE
);
129 // This is really ugly hack but it should be fast
130 PRUnichar backup_char
;
133 backup_char
= mValue
[mMinLength
-1];
134 mValue
.SetCharAt('x', mMinLength
-1);
136 mValue
.Trim(trimThese
, PR_FALSE
, PR_TRUE
);
138 mValue
.SetCharAt(backup_char
, mMinLength
-1);
140 mProps
->SetStringProperty(NS_ConvertUTF16toUTF8(mKey
), mValue
, aOldValue
);
141 mSpecialState
= eParserSpecial_None
;
145 EParserState
GetState() { return mState
; }
147 static NS_METHOD
SegmentWriter(nsIUnicharInputStream
* aStream
,
149 const PRUnichar
*aFromSegment
,
152 PRUint32
*aWriteCount
);
154 nsresult
ParseBuffer(const PRUnichar
* aBuffer
, PRUint32 aBufferLength
);
157 PRBool
ParseValueCharacter(
158 PRUnichar c
, // character that is just being parsed
159 const PRUnichar
* cur
, // pointer to character c in the buffer
160 const PRUnichar
* &tokenStart
, // string copying is done in blocks as big as
161 // possible, tokenStart points to the beginning
163 nsAString
& oldValue
); // when duplicate property is found, new value
164 // is stored into hashtable and the old one is
165 // placed in this variable
168 mState
= eParserState_AwaitingKey
;
171 void EnterKeyState() {
173 mState
= eParserState_Key
;
176 void WaitForValue() {
177 mState
= eParserState_AwaitingValue
;
180 void EnterValueState() {
183 mState
= eParserState_Value
;
184 mSpecialState
= eParserSpecial_None
;
187 void EnterCommentState() {
188 mState
= eParserState_Comment
;
194 PRUint32 mUnicodeValuesRead
; // should be 4!
195 PRUnichar mUnicodeValue
; // currently parsed unicode value
196 PRBool mHaveMultiLine
; // is TRUE when last processed characters form
197 // any of following sequences:
201 // - any sequence above followed by any
202 // combination of ' ' and '\t'
203 PRBool mMultiLineCanSkipN
; // TRUE if "\\\r" was detected
204 PRUint32 mMinLength
; // limit right trimming at the end to not trim
205 // escaped whitespaces
207 // if we see a '\' then we enter this special state
208 EParserSpecial mSpecialState
;
209 nsIPersistentProperties
* mProps
;
212 inline PRBool
IsWhiteSpace(PRUnichar aChar
)
214 return (aChar
== ' ') || (aChar
== '\t') ||
215 (aChar
== '\r') || (aChar
== '\n');
218 inline PRBool
IsEOL(PRUnichar aChar
)
220 return (aChar
== '\r') || (aChar
== '\n');
224 PRBool
nsPropertiesParser::ParseValueCharacter(
225 PRUnichar c
, const PRUnichar
* cur
, const PRUnichar
* &tokenStart
,
228 switch (mSpecialState
) {
230 // the normal state - look for special characters
231 case eParserSpecial_None
:
235 // there is nothing to append to mValue yet
236 mHaveMultiLine
= PR_FALSE
;
238 mValue
+= Substring(tokenStart
, cur
);
240 mSpecialState
= eParserSpecial_Escaped
;
244 // if we detected multiline and got only "\\\r" ignore next "\n" if any
245 if (mHaveMultiLine
&& mMultiLineCanSkipN
) {
246 // but don't allow another '\n' to be skipped
247 mMultiLineCanSkipN
= PR_FALSE
;
248 // Now there is nothing to append to the mValue since we are skipping
249 // whitespaces at the beginning of the new line of the multiline
250 // property. Set tokenStart properly to ensure that nothing is appended
251 // if we find regular line-end or the end of the buffer.
258 // we're done! We have a key and value
259 mValue
+= Substring(tokenStart
, cur
);
260 FinishValueState(oldValue
);
261 mHaveMultiLine
= PR_FALSE
;
265 // there is nothing to do with normal characters,
266 // but handle multilines correctly
267 if (mHaveMultiLine
) {
268 if (c
== ' ' || c
== '\t') {
269 // don't allow another '\n' to be skipped
270 mMultiLineCanSkipN
= PR_FALSE
;
271 // Now there is nothing to append to the mValue since we are skipping
272 // whitespaces at the beginning of the new line of the multiline
273 // property. Set tokenStart properly to ensure that nothing is appended
274 // if we find regular line-end or the end of the buffer.
278 mHaveMultiLine
= PR_FALSE
;
281 break; // from switch on (c)
283 break; // from switch on (mSpecialState)
285 // saw a \ character, so parse the character after that
286 case eParserSpecial_Escaped
:
287 // probably want to start parsing at the next token
288 // other characters, like 'u' might override this
290 mSpecialState
= eParserSpecial_None
;
294 // the easy characters - \t, \n, and so forth
296 mValue
+= PRUnichar('\t');
297 mMinLength
= mValue
.Length();
300 mValue
+= PRUnichar('\n');
301 mMinLength
= mValue
.Length();
304 mValue
+= PRUnichar('\r');
305 mMinLength
= mValue
.Length();
308 mValue
+= PRUnichar('\\');
311 // switch to unicode mode!
314 mSpecialState
= eParserSpecial_Unicode
;
315 mUnicodeValuesRead
= 0;
319 // a \ immediately followed by a newline means we're going multiline
322 mHaveMultiLine
= PR_TRUE
;
323 mMultiLineCanSkipN
= (c
== '\r');
324 mSpecialState
= eParserSpecial_None
;
328 // don't recognize the character, so just append it
334 // we're in the middle of parsing a 4-character unicode value
336 case eParserSpecial_Unicode
:
338 if(('0' <= c
) && (c
<= '9'))
340 (mUnicodeValue
<< 4) | (c
- '0');
341 else if(('a' <= c
) && (c
<= 'f'))
343 (mUnicodeValue
<< 4) | (c
- 'a' + 0x0a);
344 else if(('A' <= c
) && (c
<= 'F'))
346 (mUnicodeValue
<< 4) | (c
- 'A' + 0x0a);
348 // non-hex character. Append what we have, and move on.
349 mValue
+= mUnicodeValue
;
350 mMinLength
= mValue
.Length();
351 mSpecialState
= eParserSpecial_None
;
353 // leave tokenStart at this unknown character, so it gets appended
356 // ensure parsing this non-hex character again
360 if (++mUnicodeValuesRead
>= 4) {
362 mSpecialState
= eParserSpecial_None
;
363 mValue
+= mUnicodeValue
;
364 mMinLength
= mValue
.Length();
373 NS_METHOD
nsPropertiesParser::SegmentWriter(nsIUnicharInputStream
* aStream
,
375 const PRUnichar
*aFromSegment
,
378 PRUint32
*aWriteCount
)
380 nsPropertiesParser
*parser
=
381 static_cast<nsPropertiesParser
*>(aClosure
);
383 parser
->ParseBuffer(aFromSegment
, aCount
);
385 *aWriteCount
= aCount
;
389 nsresult
nsPropertiesParser::ParseBuffer(const PRUnichar
* aBuffer
,
390 PRUint32 aBufferLength
)
392 const PRUnichar
* cur
= aBuffer
;
393 const PRUnichar
* end
= aBuffer
+ aBufferLength
;
395 // points to the start/end of the current key or value
396 const PRUnichar
* tokenStart
= nsnull
;
398 // if we're in the middle of parsing a key or value, make sure
399 // the current token points to the beginning of the current buffer
400 if (mState
== eParserState_Key
||
401 mState
== eParserState_Value
) {
402 tokenStart
= aBuffer
;
405 nsAutoString oldValue
;
412 case eParserState_AwaitingKey
:
413 if (c
== '#' || c
== '!')
416 else if (!IsWhiteSpace(c
)) {
417 // not a comment, not whitespace, we must have found a key!
423 case eParserState_Key
:
424 if (c
== '=' || c
== ':') {
425 mKey
+= Substring(tokenStart
, cur
);
430 case eParserState_AwaitingValue
:
432 // no value at all! mimic the normal value-ending
434 FinishValueState(oldValue
);
437 // ignore white space leading up to the value
438 else if (!IsWhiteSpace(c
)) {
442 // make sure to handle this first character
443 if (ParseValueCharacter(c
, cur
, tokenStart
, oldValue
))
445 // If the character isn't consumed, don't do cur++ and parse
446 // the character again. This can happen f.e. for char 'X' in sequence
447 // "\u00X". This character can be control character and must be
453 case eParserState_Value
:
454 if (ParseValueCharacter(c
, cur
, tokenStart
, oldValue
))
456 // See few lines above for reason of doing this
459 case eParserState_Comment
:
460 // stay in this state till we hit EOL
461 if (c
== '\r' || c
== '\n')
466 // finally, advance to the next character
470 // if we're still parsing the value and are in eParserSpecial_None, then
471 // append whatever we have..
472 if (mState
== eParserState_Value
&& tokenStart
&&
473 mSpecialState
== eParserSpecial_None
) {
474 mValue
+= Substring(tokenStart
, cur
);
476 // if we're still parsing the key, then append whatever we have..
477 else if (mState
== eParserState_Key
&& tokenStart
) {
478 mKey
+= Substring(tokenStart
, cur
);
484 nsPersistentProperties::nsPersistentProperties()
487 mSubclass
= static_cast<nsIPersistentProperties
*>(this);
489 PL_INIT_ARENA_POOL(&mArena
, "PersistentPropertyArena", 2048);
492 nsPersistentProperties::~nsPersistentProperties()
494 PL_FinishArenaPool(&mArena
);
496 PL_DHashTableFinish(&mTable
);
500 nsPersistentProperties::Init()
502 if (!PL_DHashTableInit(&mTable
, &property_HashTableOps
, nsnull
,
503 sizeof(PropertyTableEntry
), 20)) {
505 return NS_ERROR_OUT_OF_MEMORY
;
511 nsPersistentProperties::Create(nsISupports
*aOuter
, REFNSIID aIID
, void **aResult
)
514 return NS_ERROR_NO_AGGREGATION
;
515 nsPersistentProperties
* props
= new nsPersistentProperties();
517 return NS_ERROR_OUT_OF_MEMORY
;
520 nsresult rv
= props
->Init();
521 if (NS_SUCCEEDED(rv
))
522 rv
= props
->QueryInterface(aIID
, aResult
);
528 NS_IMPL_THREADSAFE_ISUPPORTS2(nsPersistentProperties
, nsIPersistentProperties
, nsIProperties
)
531 nsPersistentProperties::Load(nsIInputStream
*aIn
)
533 nsresult rv
= nsSimpleUnicharStreamFactory::GetInstance()->
534 CreateInstanceFromUTF8Stream(aIn
, getter_AddRefs(mIn
));
537 NS_WARNING("Error creating UnicharInputStream");
538 return NS_ERROR_FAILURE
;
541 nsPropertiesParser
parser(mSubclass
);
544 // If this 4096 is changed to some other value, make sure to adjust
545 // the bug121341.properties test file accordingly.
546 while (NS_SUCCEEDED(rv
= mIn
->ReadSegments(nsPropertiesParser::SegmentWriter
, &parser
, 4096, &nProcessed
)) &&
552 // We may have an unprocessed value at this point
553 // if the last line did not have a proper line ending.
554 if (parser
.GetState() == eParserState_Value
) {
555 nsAutoString oldValue
;
556 parser
.FinishValueState(oldValue
);
563 nsPersistentProperties::SetStringProperty(const nsACString
& aKey
,
564 const nsAString
& aNewValue
,
565 nsAString
& aOldValue
)
567 const nsAFlatCString
& flatKey
= PromiseFlatCString(aKey
);
568 PropertyTableEntry
*entry
=
569 static_cast<PropertyTableEntry
*>
570 (PL_DHashTableOperate(&mTable
, flatKey
.get(), PL_DHASH_ADD
));
573 aOldValue
= entry
->mValue
;
574 NS_WARNING(nsPrintfCString(aKey
.Length() + 30,
575 "the property %s already exists\n",
576 flatKey
.get()).get());
579 entry
->mKey
= ArenaStrdup(flatKey
, &mArena
);
580 entry
->mValue
= ArenaStrdup(PromiseFlatString(aNewValue
), &mArena
);
586 nsPersistentProperties::Save(nsIOutputStream
* aOut
, const nsACString
& aHeader
)
588 return NS_ERROR_NOT_IMPLEMENTED
;
592 nsPersistentProperties::Subclass(nsIPersistentProperties
* aSubclass
)
595 mSubclass
= aSubclass
;
602 nsPersistentProperties::GetStringProperty(const nsACString
& aKey
,
605 const nsAFlatCString
& flatKey
= PromiseFlatCString(aKey
);
607 PropertyTableEntry
*entry
=
608 static_cast<PropertyTableEntry
*>
609 (PL_DHashTableOperate(&mTable
, flatKey
.get(), PL_DHASH_LOOKUP
));
611 if (PL_DHASH_ENTRY_IS_FREE(entry
))
612 return NS_ERROR_FAILURE
;
614 aValue
= entry
->mValue
;
618 PR_STATIC_CALLBACK(PLDHashOperator
)
619 AddElemToArray(PLDHashTable
* table
, PLDHashEntryHdr
*hdr
,
620 PRUint32 i
, void *arg
)
622 nsISupportsArray
*propArray
= (nsISupportsArray
*) arg
;
623 PropertyTableEntry
* entry
=
624 static_cast<PropertyTableEntry
*>(hdr
);
626 nsPropertyElement
*element
=
627 new nsPropertyElement(nsDependentCString(entry
->mKey
),
628 nsDependentString(entry
->mValue
));
630 return PL_DHASH_STOP
;
632 propArray
->InsertElementAt(element
, i
);
634 return PL_DHASH_NEXT
;
639 nsPersistentProperties::Enumerate(nsISimpleEnumerator
** aResult
)
641 nsCOMPtr
<nsISupportsArray
> propArray
;
642 nsresult rv
= NS_NewISupportsArray(getter_AddRefs(propArray
));
646 // We know the necessary size; we can avoid growing it while adding elements
647 if (!propArray
->SizeTo(mTable
.entryCount
))
648 return NS_ERROR_OUT_OF_MEMORY
;
650 // Step through hash entries populating a transient array
652 PL_DHashTableEnumerate(&mTable
, AddElemToArray
, (void *)propArray
);
653 if (n
< mTable
.entryCount
)
654 return NS_ERROR_OUT_OF_MEMORY
;
656 return NS_NewArrayEnumerator(aResult
, propArray
);
659 ////////////////////////////////////////////////////////////////////////////////
660 // XXX Some day we'll unify the nsIPersistentProperties interface with
661 // nsIProperties, but until now...
664 nsPersistentProperties::Get(const char* prop
, const nsIID
& uuid
, void* *result
)
666 return NS_ERROR_NOT_IMPLEMENTED
;
670 nsPersistentProperties::Set(const char* prop
, nsISupports
* value
)
672 return NS_ERROR_NOT_IMPLEMENTED
;
675 nsPersistentProperties::Undefine(const char* prop
)
677 return NS_ERROR_NOT_IMPLEMENTED
;
681 nsPersistentProperties::Has(const char* prop
, PRBool
*result
)
683 PropertyTableEntry
*entry
=
684 static_cast<PropertyTableEntry
*>
685 (PL_DHashTableOperate(&mTable
, prop
, PL_DHASH_LOOKUP
));
687 *result
= (entry
&& PL_DHASH_ENTRY_IS_BUSY(entry
));
693 nsPersistentProperties::GetKeys(PRUint32
*count
, char ***keys
)
695 return NS_ERROR_NOT_IMPLEMENTED
;
698 ////////////////////////////////////////////////////////////////////////////////
700 ////////////////////////////////////////////////////////////////////////////////
703 nsPropertyElement::Create(nsISupports
*aOuter
, REFNSIID aIID
, void **aResult
)
706 return NS_ERROR_NO_AGGREGATION
;
707 nsPropertyElement
* propElem
= new nsPropertyElement();
708 if (propElem
== nsnull
)
709 return NS_ERROR_OUT_OF_MEMORY
;
711 nsresult rv
= propElem
->QueryInterface(aIID
, aResult
);
712 NS_RELEASE(propElem
);
716 NS_IMPL_ISUPPORTS1(nsPropertyElement
, nsIPropertyElement
)
719 nsPropertyElement::GetKey(nsACString
& aReturnKey
)
726 nsPropertyElement::GetValue(nsAString
& aReturnValue
)
728 aReturnValue
= mValue
;
733 nsPropertyElement::SetKey(const nsACString
& aKey
)
740 nsPropertyElement::SetValue(const nsAString
& aValue
)
746 ////////////////////////////////////////////////////////////////////////////////