1 /*
2  *  This file is part of the KDE libraries
3  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
4  *  Copyright (C) 2004 Apple Computer, Inc.
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21 
22 #include "string_object.h"
23 #include "string_object.lut.h"
24 
25 #include "error_object.h"
26 #include "operations.h"
27 #include "PropertyNameArray.h"
28 #include "regexp_object.h"
29 #include "commonunicode.h"
30 #include <wtf/unicode/libc/UnicodeLibC.h>
31 
32 #if PLATFORM(WIN_OS)
33 #include <windows.h>
34 #endif
35 
36 using namespace WTF;
37 
38 namespace KJS
39 {
40 
41 // ------------------------------ StringInstance ----------------------------
42 
43 const ClassInfo StringInstance::info = {"String", nullptr, nullptr, nullptr};
44 
StringInstance(JSObject * proto)45 StringInstance::StringInstance(JSObject *proto)
46     : JSWrapperObject(proto), m_conversionsCustomized(false)
47 {
48     setInternalValue(jsString(""));
49 }
50 
StringInstance(JSObject * proto,StringImp * string)51 StringInstance::StringInstance(JSObject *proto, StringImp *string)
52     : JSWrapperObject(proto), m_conversionsCustomized(false)
53 {
54     setInternalValue(string);
55 }
56 
StringInstance(JSObject * proto,const UString & string)57 StringInstance::StringInstance(JSObject *proto, const UString &string)
58     : JSWrapperObject(proto), m_conversionsCustomized(false)
59 {
60     setInternalValue(jsString(string));
61 }
62 
lengthGetter(ExecState *,JSObject *,const Identifier &,const PropertySlot & slot)63 JSValue *StringInstance::lengthGetter(ExecState *, JSObject *, const Identifier &, const PropertySlot &slot)
64 {
65     return jsNumber(static_cast<StringInstance *>(slot.slotBase())->internalValue()->value().size());
66 }
67 
indexGetter(ExecState *,JSObject *,unsigned,const PropertySlot & slot)68 JSValue *StringInstance::indexGetter(ExecState *, JSObject *, unsigned, const PropertySlot &slot)
69 {
70     const UChar c = static_cast<StringInstance *>(slot.slotBase())->internalValue()->value()[slot.index()];
71     return jsString(UString(&c, 1));
72 }
73 
getOwnPropertySlot(ExecState * exec,const Identifier & propertyName,PropertySlot & slot)74 bool StringInstance::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
75 {
76     if (propertyName == exec->propertyNames().length) {
77         slot.setCustom(this, lengthGetter);
78         return true;
79     }
80 
81     bool isStrictUInt32;
82     unsigned i = propertyName.toStrictUInt32(&isStrictUInt32);
83     unsigned length = internalValue()->value().size();
84     if (isStrictUInt32 && i < length) {
85         slot.setCustomIndex(this, i, indexGetter);
86         return true;
87     }
88 
89     return JSObject::getOwnPropertySlot(exec, propertyName, slot);
90 }
91 
getOwnPropertySlot(ExecState * exec,unsigned propertyName,PropertySlot & slot)92 bool StringInstance::getOwnPropertySlot(ExecState *exec, unsigned propertyName, PropertySlot &slot)
93 {
94     unsigned length = internalValue()->value().size();
95     if (propertyName < length) {
96         slot.setCustomIndex(this, propertyName, indexGetter);
97         return true;
98     }
99 
100     return JSObject::getOwnPropertySlot(exec, Identifier::from(propertyName), slot);
101 }
102 
getOwnPropertyDescriptor(ExecState * exec,const Identifier & propertyName,PropertyDescriptor & desc)103 bool StringInstance::getOwnPropertyDescriptor(ExecState *exec, const Identifier &propertyName, PropertyDescriptor &desc)
104 {
105     if (propertyName == exec->propertyNames().length) {
106         desc.setPropertyDescriptorValues(exec, jsNumber(internalValue()->value().size()), ReadOnly | DontDelete | DontEnum);
107         return true;
108     }
109 
110     return KJS::JSObject::getOwnPropertyDescriptor(exec, propertyName, desc);
111 }
112 
put(ExecState * exec,const Identifier & propertyName,JSValue * value,int attr)113 void StringInstance::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
114 {
115     if (propertyName == exec->propertyNames().length) {
116         return;
117     }
118 
119     if (propertyName == exec->propertyNames().valueOf || propertyName == exec->propertyNames().toString) {
120         m_conversionsCustomized = true;
121     }
122 
123     JSObject::put(exec, propertyName, value, attr);
124 }
125 
deleteProperty(ExecState * exec,const Identifier & propertyName)126 bool StringInstance::deleteProperty(ExecState *exec, const Identifier &propertyName)
127 {
128     if (propertyName == exec->propertyNames().length) {
129         return false;
130     }
131     return JSObject::deleteProperty(exec, propertyName);
132 }
133 
getOwnPropertyNames(ExecState * exec,PropertyNameArray & propertyNames,PropertyMap::PropertyMode mode)134 void StringInstance::getOwnPropertyNames(ExecState *exec, PropertyNameArray &propertyNames, PropertyMap::PropertyMode mode)
135 {
136     int size = internalValue()->getString().size();
137     for (int i = 0; i < size; i++) {
138         propertyNames.add(Identifier(UString::from(i)));
139     }
140 
141     if (mode == PropertyMap::IncludeDontEnumProperties) {
142         propertyNames.add(exec->propertyNames().length);
143     }
144 
145     return JSObject::getOwnPropertyNames(exec, propertyNames, mode);
146 }
147 
toString(ExecState * exec) const148 UString StringInstance::toString(ExecState *exec) const
149 {
150     if (prototype() == originalProto() && !conversionsCustomized() &&
151             !static_cast<StringPrototype *>(prototype())->conversionsCustomized()) {
152         return internalValue()->value();
153     } else {
154         return JSObject::toString(exec);
155     }
156 }
157 
valueClone(Interpreter * targetCtx) const158 JSObject *StringInstance::valueClone(Interpreter *targetCtx) const
159 {
160     return new StringInstance(targetCtx->builtinStringPrototype(), internalValue());
161 }
162 
163 // ------------------------------ StringPrototype ---------------------------
164 const ClassInfo StringPrototype::info = {"String", &StringInstance::info, &stringTable, nullptr};
165 /* Source for string_object.lut.h
166 @begin stringTable 26
167   toString              StringProtoFunc::ToString       DontEnum|Function       0
168   valueOf               StringProtoFunc::ValueOf        DontEnum|Function       0
169   charAt                StringProtoFunc::CharAt         DontEnum|Function       1
170   charCodeAt            StringProtoFunc::CharCodeAt     DontEnum|Function       1
171   concat                StringProtoFunc::Concat         DontEnum|Function       1
172   endsWith		StringProtoFunc::EndsWith	DontEnum|Function	1
173   includes		StringProtoFunc::Includes	DontEnum|Function	1
174   indexOf               StringProtoFunc::IndexOf        DontEnum|Function       1
175   lastIndexOf           StringProtoFunc::LastIndexOf    DontEnum|Function       1
176   match                 StringProtoFunc::Match          DontEnum|Function       1
177   replace               StringProtoFunc::Replace        DontEnum|Function       2
178   search                StringProtoFunc::Search         DontEnum|Function       1
179   slice                 StringProtoFunc::Slice          DontEnum|Function       2
180   split                 StringProtoFunc::Split          DontEnum|Function       2
181   startsWith		StringProtoFunc::StartsWith	DontEnum|Function	1
182   substr                StringProtoFunc::Substr         DontEnum|Function       2
183   substring             StringProtoFunc::Substring      DontEnum|Function       2
184   toLowerCase           StringProtoFunc::ToLowerCase    DontEnum|Function       0
185   toUpperCase           StringProtoFunc::ToUpperCase    DontEnum|Function       0
186   toLocaleLowerCase     StringProtoFunc::ToLocaleLowerCase DontEnum|Function    0
187   toLocaleUpperCase     StringProtoFunc::ToLocaleUpperCase DontEnum|Function    0
188   trim                  StringProtoFunc::Trim           DontEnum|Function       0
189   localeCompare         StringProtoFunc::LocaleCompare  DontEnum|Function       1
190   repeat                StringProtoFunc::Repeat         DontEnum|Function       1
191 
192 #
193 # Under here: html extension, should only exist if KJS_PURE_ECMA is not defined
194 # I guess we need to generate two hashtables in the .lut.h file, and use #ifdef
195 # to select the right one... TODO. #####
196   big                   StringProtoFunc::Big            DontEnum|Function       0
197   small                 StringProtoFunc::Small          DontEnum|Function       0
198   blink                 StringProtoFunc::Blink          DontEnum|Function       0
199   bold                  StringProtoFunc::Bold           DontEnum|Function       0
200   fixed                 StringProtoFunc::Fixed          DontEnum|Function       0
201   italics               StringProtoFunc::Italics        DontEnum|Function       0
202   strike                StringProtoFunc::Strike         DontEnum|Function       0
203   sub                   StringProtoFunc::Sub            DontEnum|Function       0
204   sup                   StringProtoFunc::Sup            DontEnum|Function       0
205   fontcolor             StringProtoFunc::Fontcolor      DontEnum|Function       1
206   fontsize              StringProtoFunc::Fontsize       DontEnum|Function       1
207   anchor                StringProtoFunc::Anchor         DontEnum|Function       1
208   link                  StringProtoFunc::Link           DontEnum|Function       1
209   trimLeft              StringProtoFunc::TrimLeft       DontEnum|Function       0
210   trimRight             StringProtoFunc::TrimRight      DontEnum|Function       0
211 @end
212 */
213 // ECMA 15.5.4
StringPrototype(ExecState * exec,ObjectPrototype * objProto)214 StringPrototype::StringPrototype(ExecState *exec, ObjectPrototype *objProto)
215     : StringInstance(objProto)
216 {
217     // The constructor will be added later, after StringObjectImp has been built
218     putDirect(exec->propertyNames().length, jsNumber(0), DontDelete | ReadOnly | DontEnum);
219 }
220 
getOwnPropertySlot(ExecState * exec,const Identifier & propertyName,PropertySlot & slot)221 bool StringPrototype::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
222 {
223     return getStaticFunctionSlot<StringProtoFunc, StringInstance>(exec, &stringTable, this, propertyName, slot);
224 }
225 
226 // ------------------------------ StringProtoFunc ---------------------------
227 
StringProtoFunc(ExecState * exec,int i,int len,const Identifier & name)228 StringProtoFunc::StringProtoFunc(ExecState *exec, int i, int len, const Identifier &name)
229     : InternalFunctionImp(static_cast<FunctionPrototype *>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name)
230     , id(i)
231 {
232     putDirect(exec->propertyNames().length, len, DontDelete | ReadOnly | DontEnum);
233 }
234 
expandSourceRanges(UString::Range * & array,int & count,int & capacity)235 static inline void expandSourceRanges(UString::Range *&array, int &count, int &capacity)
236 {
237     int newCapacity;
238     if (capacity == 0) {
239         newCapacity = 16;
240     } else {
241         newCapacity = capacity * 2;
242     }
243 
244     UString::Range *newArray = new UString::Range[newCapacity];
245     for (int i = 0; i < count; i++) {
246         newArray[i] = array[i];
247     }
248 
249     delete [] array;
250 
251     capacity = newCapacity;
252     array = newArray;
253 }
254 
pushSourceRange(UString::Range * & array,int & count,int & capacity,UString::Range range)255 static void pushSourceRange(UString::Range *&array, int &count, int &capacity, UString::Range range)
256 {
257     if (count + 1 > capacity) {
258         expandSourceRanges(array, count, capacity);
259     }
260 
261     array[count] = range;
262     count++;
263 }
264 
expandReplacements(UString * & array,int & count,int & capacity)265 static inline void expandReplacements(UString *&array, int &count, int &capacity)
266 {
267     int newCapacity;
268     if (capacity == 0) {
269         newCapacity = 16;
270     } else {
271         newCapacity = capacity * 2;
272     }
273 
274     UString *newArray = new UString[newCapacity];
275     for (int i = 0; i < count; i++) {
276         newArray[i] = array[i];
277     }
278 
279     delete [] array;
280 
281     capacity = newCapacity;
282     array = newArray;
283 }
284 
pushReplacement(UString * & array,int & count,int & capacity,UString replacement)285 static void pushReplacement(UString *&array, int &count, int &capacity, UString replacement)
286 {
287     if (count + 1 > capacity) {
288         expandReplacements(array, count, capacity);
289     }
290 
291     array[count] = replacement;
292     count++;
293 }
294 
substituteBackreferences(const UString & replacement,const UString & source,int * ovector,RegExp * reg)295 static inline UString substituteBackreferences(const UString &replacement, const UString &source, int *ovector, RegExp *reg)
296 {
297     UString substitutedReplacement = replacement;
298 
299     int i = -1;
300     while ((i = substitutedReplacement.find(UString("$"), i + 1)) != -1) {
301         if (i + 1 == substitutedReplacement.size()) {
302             break;
303         }
304 
305         unsigned short ref = substitutedReplacement[i + 1].unicode();
306         int backrefStart = 0;
307         int backrefLength = 0;
308         int advance = 0;
309 
310         if (ref == '$') {  // "$$" -> "$"
311             substitutedReplacement = substitutedReplacement.substr(0, i + 1) + substitutedReplacement.substr(i + 2);
312             continue;
313         } else if (ref == '&') {
314             backrefStart = ovector[0];
315             backrefLength = ovector[1] - backrefStart;
316         } else if (ref == '`') {
317             backrefStart = 0;
318             backrefLength = ovector[0];
319         } else if (ref == '\'') {
320             backrefStart = ovector[1];
321             backrefLength = source.size() - backrefStart;
322         } else if (ref >= '0' && ref <= '9') {
323             // 1- and 2-digit back references are allowed
324             unsigned backrefIndex = ref - '0';
325             if (backrefIndex > (unsigned)reg->subPatterns()) {
326                 continue;
327             }
328             if (substitutedReplacement.size() > i + 2) {
329                 ref = substitutedReplacement[i + 2].unicode();
330                 if (ref >= '0' && ref <= '9') {
331                     backrefIndex = 10 * backrefIndex + ref - '0';
332                     if (backrefIndex > (unsigned)reg->subPatterns()) {
333                         backrefIndex = backrefIndex / 10;    // Fall back to the 1-digit reference
334                     } else {
335                         advance = 1;
336                     }
337                 }
338             }
339             backrefStart = ovector[2 * backrefIndex];
340             backrefLength = ovector[2 * backrefIndex + 1] - backrefStart;
341         } else {
342             continue;
343         }
344 
345         substitutedReplacement = substitutedReplacement.substr(0, i) + source.substr(backrefStart, backrefLength) + substitutedReplacement.substr(i + 2 + advance);
346         i += backrefLength - 1; // - 1 offsets 'i + 1'
347     }
348 
349     return substitutedReplacement;
350 }
351 
localeCompare(const UString & a,const UString & b)352 static inline int localeCompare(const UString &a, const UString &b)
353 {
354 #if PLATFORM(WIN_OS)
355     int retval = CompareStringW(LOCALE_USER_DEFAULT, 0,
356                                 reinterpret_cast<LPCWSTR>(a.data()), a.size(),
357                                 reinterpret_cast<LPCWSTR>(b.data()), b.size());
358     return !retval ? retval : retval - 2;
359 #elif PLATFORM(CF)
360     CFStringRef sa = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar *>(a.data()), a.size(), kCFAllocatorNull);
361     CFStringRef sb = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar *>(b.data()), b.size(), kCFAllocatorNull);
362 
363     int retval = CFStringCompare(sa, sb, kCFCompareLocalized);
364 
365     CFRelease(sa);
366     CFRelease(sb);
367 
368     return retval;
369 #else
370     // ### use as fallback only. implement locale aware version.
371     // ### other browsers have more detailed return values than -1, 0 and 1
372     return compare(a, b);
373 #endif
374 }
375 
replace(ExecState * exec,const UString & source,JSValue * pattern,JSValue * replacement)376 static JSValue *replace(ExecState *exec, const UString &source, JSValue *pattern, JSValue *replacement)
377 {
378     JSObject *replacementFunction = nullptr;
379     UString replacementString;
380 
381     if (JSValue::isObject(replacement) && JSValue::toObject(replacement, exec)->implementsCall()) {
382         replacementFunction = JSValue::toObject(replacement, exec);
383     } else {
384         replacementString = JSValue::toString(replacement, exec);
385     }
386 
387     if (JSValue::isObject(pattern) && static_cast<JSObject *>(pattern)->inherits(&RegExpImp::info)) {
388         RegExp *reg = static_cast<RegExpImp *>(pattern)->regExp();
389         bool global = reg->flags() & RegExp::Global;
390 
391         RegExpObjectImp *regExpObj = static_cast<RegExpObjectImp *>(exec->lexicalInterpreter()->builtinRegExp());
392 
393         int matchIndex = 0;
394         int lastIndex = 0;
395         int startPosition = 0;
396 
397         UString::Range *sourceRanges = nullptr;
398         int sourceRangeCount = 0;
399         int sourceRangeCapacity = 0;
400         UString *replacements = nullptr;
401         int replacementCount = 0;
402         int replacementCapacity = 0;
403 
404         // This is either a loop (if global is set) or a one-way (if not).
405         RegExpStringContext ctx(source);
406         do {
407             int *ovector;
408             UString matchString = regExpObj->performMatch(reg, exec, ctx, source, startPosition, &matchIndex, &ovector);
409             if (matchIndex == -1) {
410                 break;
411             }
412             int matchLen = matchString.size();
413 
414             pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, matchIndex - lastIndex));
415             UString substitutedReplacement;
416             if (replacementFunction) {
417                 int completeMatchStart = ovector[0];
418                 List args;
419 
420                 args.append(jsString(matchString));
421 
422                 for (unsigned i = 0; i < reg->subPatterns(); i++) {
423                     int matchStart = ovector[(i + 1) * 2];
424                     int matchLen = ovector[(i + 1) * 2 + 1] - matchStart;
425 
426                     args.append(jsString(source.substr(matchStart, matchLen)));
427                 }
428 
429                 args.append(jsNumber(completeMatchStart));
430                 args.append(jsString(source));
431 
432                 substitutedReplacement = JSValue::toString(replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), args), exec);
433             } else {
434                 substitutedReplacement = substituteBackreferences(replacementString, source, ovector, reg);
435             }
436             pushReplacement(replacements, replacementCount, replacementCapacity, substitutedReplacement);
437 
438             lastIndex = matchIndex + matchLen;
439             startPosition = lastIndex;
440 
441             // special case of empty match
442             if (matchLen == 0) {
443                 startPosition++;
444                 if (startPosition > source.size()) {
445                     break;
446                 }
447             }
448         } while (global);
449 
450         if (lastIndex < source.size()) {
451             pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, source.size() - lastIndex));
452         }
453 
454         UString result;
455         if (sourceRanges) {
456             result = source.spliceSubstringsWithSeparators(sourceRanges, sourceRangeCount, replacements, replacementCount);
457         }
458 
459         delete [] sourceRanges;
460         delete [] replacements;
461 
462         return jsString(result);
463     }
464 
465     // First arg is a string
466     UString patternString = JSValue::toString(pattern, exec);
467     int matchPos = source.find(patternString);
468     int matchLen = patternString.size();
469     // Do the replacement
470     if (matchPos == -1) {
471         return jsString(source);
472     }
473 
474     if (replacementFunction) {
475         List args;
476 
477         args.append(jsString(source.substr(matchPos, matchLen)));
478         args.append(jsNumber(matchPos));
479         args.append(jsString(source));
480 
481         replacementString = JSValue::toString(replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), args), exec);
482     }
483 
484     return jsString(source.substr(0, matchPos) + replacementString + source.substr(matchPos + matchLen));
485 }
486 
487 static UnicodeSupport::StringConversionFunction toUpperF = Unicode::toUpper;
488 static UnicodeSupport::StringConversionFunction toLowerF = Unicode::toLower;
489 
setToLowerFunction(UnicodeSupport::StringConversionFunction f)490 void StringProtoFunc::setToLowerFunction(UnicodeSupport::StringConversionFunction f)
491 {
492     toLowerF = f;
493 }
494 
setToUpperFunction(UnicodeSupport::StringConversionFunction f)495 void StringProtoFunc::setToUpperFunction(UnicodeSupport::StringConversionFunction f)
496 {
497     toUpperF = f;
498 }
499 
500 // ECMA 15.5.4.2 - 15.5.4.20
callAsFunction(ExecState * exec,JSObject * thisObj,const List & args)501 JSValue *StringProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
502 {
503     JSValue *result = nullptr;
504 
505     // toString and valueOf are no generic function.
506     if (id == ToString || id == ValueOf) {
507         if (!thisObj || !thisObj->inherits(&StringInstance::info)) {
508             return throwError(exec, TypeError);
509         }
510 
511         return jsString(static_cast<StringInstance *>(thisObj)->internalValue()->toString(exec));
512     }
513 
514     UString u, u2, u3;
515     int pos, p0, i;
516     double dpos;
517     double d = 0.0;
518 
519     UString s = thisObj->toString(exec);
520 
521     int len = s.size();
522     JSValue *a0 = args[0];
523     JSValue *a1 = args[1];
524 
525     switch (id) {
526     case ToString:
527     case ValueOf:
528         // handled above
529         break;
530     case CharAt:
531         // Other browsers treat an omitted parameter as 0 rather than NaN.
532         // That doesn't match the ECMA standard, but is needed for site compatibility.
533         dpos = JSValue::isUndefined(a0) ? 0 : JSValue::toInteger(a0, exec);
534         if (dpos >= 0 && dpos < len) { // false for NaN
535             u = s.substr(static_cast<int>(dpos), 1);
536         } else {
537             u = "";
538         }
539         result = jsString(u);
540         break;
541     case CharCodeAt:
542         // Other browsers treat an omitted parameter as 0 rather than NaN.
543         // That doesn't match the ECMA standard, but is needed for site compatibility.
544         dpos = JSValue::isUndefined(a0) ? 0 : JSValue::toInteger(a0, exec);
545         if (dpos >= 0 && dpos < len) { // false for NaN
546             result = jsNumber(s[static_cast<int>(dpos)].unicode());
547         } else {
548             result = jsNaN();
549         }
550         break;
551     case Concat: {
552         ListIterator it = args.begin();
553         for (; it != args.end(); ++it) {
554             s += JSValue::toString(*it, exec);
555         }
556         result = jsString(s);
557         break;
558     }
559     case EndsWith:
560 	if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
561 	    return throwError(exec, TypeError, "RegExp reserved for future");
562 	}
563 	u2 = JSValue::toString(a0, exec);
564 	if (JSValue::isUndefined(a1))
565 	    i = s.size();
566 	else
567 	    i = minInt(s.size(), JSValue::toInteger(a1, exec));
568 	pos = i - u2.size();
569 	result = jsBoolean(pos >= 0 && s.substr(pos, u2.size()) == u2);
570 	break;
571     case Includes:
572 	if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
573 	    return throwError(exec, TypeError, "RegExp reserved for future");
574 	}
575 	u2 = JSValue::toString(a0, exec);
576 	pos = JSValue::toInteger(a1, exec);
577 	i = s.find(u2, pos);
578 	result = jsBoolean(i >= 0);
579 	break;
580     case IndexOf:
581         u2 = JSValue::toString(a0, exec);
582         if (JSValue::isUndefined(a1)) {
583             dpos = 0;
584         } else {
585             dpos = JSValue::toInteger(a1, exec);
586             if (dpos >= 0) { // false for NaN
587                 if (dpos > len) {
588                     dpos = len;
589                 }
590             } else {
591                 dpos = 0;
592             }
593         }
594         result = jsNumber(s.find(u2, static_cast<int>(dpos)));
595         break;
596     case LastIndexOf:
597         u2 = JSValue::toString(a0, exec);
598         d = JSValue::toNumber(a1, exec);
599         if (JSValue::isUndefined(a1) || KJS::isNaN(d)) {
600             dpos = len;
601         } else {
602             dpos = JSValue::toInteger(a1, exec);
603             if (dpos >= 0) { // false for NaN
604                 if (dpos > len) {
605                     dpos = len;
606                 }
607             } else {
608                 dpos = 0;
609             }
610         }
611         result = jsNumber(s.rfind(u2, static_cast<int>(dpos)));
612         break;
613     case Match:
614     case Search: {
615         u = s;
616         RegExp *reg, *tmpReg = nullptr;
617         RegExpImp *imp = nullptr;
618         if (JSValue::isObject(a0) && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) {
619             reg = static_cast<RegExpImp *>(a0)->regExp();
620         } else {
621             /*
622              *  ECMA 15.5.4.12 String.prototype.search (regexp)
623              *  If regexp is not an object whose [[Class]] property is "RegExp", it is
624              *  replaced with the result of the expression new RegExp(regexp).
625              */
626             reg = tmpReg = new RegExp(JSValue::toString(a0, exec), RegExp::None);
627         }
628         if (!reg->isValid()) {
629             delete tmpReg;
630             return throwError(exec, SyntaxError, "Invalid regular expression");
631         }
632         RegExpObjectImp *regExpObj = static_cast<RegExpObjectImp *>(exec->lexicalInterpreter()->builtinRegExp());
633 
634         RegExpStringContext ctx(u);
635         UString mstr = regExpObj->performMatch(reg, exec, ctx, u, 0, &pos);
636 
637         if (id == Search) {
638             result = jsNumber(pos);
639         } else {
640             // Exec
641             if ((reg->flags() & RegExp::Global) == 0) {
642                 // case without 'g' flag is handled like RegExp.prototype.exec
643                 if (mstr.isNull()) {
644                     result = jsNull();
645                 } else {
646                     result = regExpObj->arrayOfMatches(exec, mstr);
647                 }
648             } else {
649                 // return array of matches
650                 List list;
651                 int lastIndex = 0;
652                 while (pos >= 0) {
653                     if (mstr.isNull()) {
654                         list.append(jsUndefined());
655                     } else {
656                         list.append(jsString(mstr));
657                     }
658                     lastIndex = pos;
659                     pos += mstr.isEmpty() ? 1 : mstr.size();
660                     mstr = regExpObj->performMatch(reg, exec, ctx, u, pos, &pos);
661                 }
662                 if (imp) {
663                     imp->put(exec, "lastIndex", jsNumber(lastIndex), DontDelete | DontEnum);
664                 }
665                 if (list.isEmpty()) {
666                     // if there are no matches at all, it's important to return
667                     // Null instead of an empty array, because this matches
668                     // other browsers and because Null is a false value.
669                     result = jsNull();
670                 } else {
671                     result = exec->lexicalInterpreter()->builtinArray()->construct(exec, list);
672                 }
673             }
674         }
675 
676         delete tmpReg;
677         break;
678     }
679     case Replace:
680         result = replace(exec, s, a0, a1);
681         break;
682     case Slice: {
683         // The arg processing is very much like ArrayProtoFunc::Slice
684         double start = JSValue::toInteger(a0, exec);
685         double end = JSValue::isUndefined(a1) ? len : JSValue::toInteger(a1, exec);
686         double from = start < 0 ? len + start : start;
687         double to = end < 0 ? len + end : end;
688         if (to > from && to > 0 && from < len) {
689             if (from < 0) {
690                 from = 0;
691             }
692             if (to > len) {
693                 to = len;
694             }
695             result = jsString(s.substr(static_cast<int>(from), static_cast<int>(to - from)));
696         } else {
697             result = jsString("");
698         }
699         break;
700     }
701     case Split: {
702         JSObject *constructor = exec->lexicalInterpreter()->builtinArray();
703         JSObject *res = static_cast<JSObject *>(constructor->construct(exec, List::empty()));
704         result = res;
705         u = s;
706         i = p0 = 0;
707         uint32_t limit = JSValue::isUndefined(a1) ? 0xFFFFFFFFU : JSValue::toUInt32(a1, exec);
708         if (JSValue::isObject(a0) && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) {
709             RegExp *reg = static_cast<RegExpImp *>(a0)->regExp();
710 
711             RegExpStringContext ctx(u);
712             bool error = false;
713             if (u.isEmpty() && !reg->match(ctx, u, &error, 0).isNull()) {
714 
715                 // empty string matched by regexp -> empty array
716                 res->put(exec, exec->propertyNames().length, jsNumber(0));
717                 break;
718             }
719             pos = 0;
720             while (!error && static_cast<uint32_t>(i) != limit && pos < u.size()) {
721                 // TODO: back references
722                 int mpos;
723                 int *ovector = nullptr;
724                 UString mstr = reg->match(ctx, u, &error, pos, &mpos, &ovector);
725                 delete [] ovector; ovector = nullptr;
726                 if (mpos < 0) {
727                     break;
728                 }
729                 pos = mpos + (mstr.isEmpty() ? 1 : mstr.size());
730                 if (mpos != p0 || !mstr.isEmpty()) {
731                     res->put(exec, i, jsString(u.substr(p0, mpos - p0)));
732                     p0 = mpos + mstr.size();
733                     i++;
734                 }
735             }
736 
737             if (error) {
738                 RegExpObjectImp::throwRegExpError(exec);
739             }
740 
741         } else {
742             u2 = JSValue::toString(a0, exec);
743             if (u2.isEmpty()) {
744                 if (u.isEmpty()) {
745                     // empty separator matches empty string -> empty array
746                     put(exec, exec->propertyNames().length, jsNumber(0));
747                     break;
748                 } else {
749                     while (static_cast<uint32_t>(i) != limit && i < u.size() - 1) {
750                         res->put(exec, i++, jsString(u.substr(p0++, 1)));
751                     }
752                 }
753             } else {
754                 while (static_cast<uint32_t>(i) != limit && (pos = u.find(u2, p0)) >= 0) {
755                     res->put(exec, i, jsString(u.substr(p0, pos - p0)));
756                     p0 = pos + u2.size();
757                     i++;
758                 }
759             }
760         }
761         // add remaining string, if any
762         if (static_cast<uint32_t>(i) != limit) {
763             res->put(exec, i++, jsString(u.substr(p0)));
764         }
765         res->put(exec, exec->propertyNames().length, jsNumber(i));
766     }
767     break;
768     case StartsWith:
769 	if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
770 	    return throwError(exec, TypeError, "RegExp reserved for future");
771 	}
772 	u2 = JSValue::toString(a0, exec);
773 	pos = maxInt(0, JSValue::toInteger(a1, exec));
774 	result = jsBoolean(s.substr(pos, u2.size()) == u2);
775 	break;
776     case Substr: { //B.2.3
777         // Note: NaN is effectively handled as 0 here for both length
778         // and start, hence toInteger does fine, and removes worries
779         // about weird comparison results below.
780         int len = s.size();
781         double start  = JSValue::toInteger(a0, exec);
782         double length = JSValue::isUndefined(a1) ? len : JSValue::toInteger(a1, exec);
783 
784         if (start >= len) {
785             return jsString("");
786         }
787         if (length < 0) {
788             return jsString("");
789         }
790         if (start < 0) {
791             start += s.size();
792             if (start < 0) {
793                 start = 0;
794             }
795         }
796 
797         if (length > len - start) {
798             length = len - start;
799         }
800 
801         result = jsString(s.substr(static_cast<int>(start), static_cast<int>(length)));
802         break;
803     }
804     case Substring: {
805         double start = JSValue::toNumber(a0, exec);
806         double end = JSValue::toNumber(a1, exec);
807         if (isNaN(start)) {
808             start = 0;
809         }
810         if (isNaN(end)) {
811             end = 0;
812         }
813         if (start < 0) {
814             start = 0;
815         }
816         if (end < 0) {
817             end = 0;
818         }
819         if (start > len) {
820             start = len;
821         }
822         if (end > len) {
823             end = len;
824         }
825         if (JSValue::isUndefined(a1)) {
826             end = len;
827         }
828         if (start > end) {
829             double temp = end;
830             end = start;
831             start = temp;
832         }
833         result = jsString(s.substr((int)start, (int)end - (int)start));
834     }
835     break;
836     case ToLowerCase:
837     case ToLocaleLowerCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented.
838         u = s;
839         u.copyForWriting();
840         uint16_t *dataPtr = reinterpret_cast<uint16_t *>(u.rep()->data());
841         uint16_t *destIfNeeded;
842 
843         int len = toLowerF(dataPtr, u.size(), destIfNeeded);
844         if (len >= 0) {
845             result = jsString(UString(reinterpret_cast<UChar *>(destIfNeeded ? destIfNeeded : dataPtr), len));
846         } else {
847             result = jsString(s);
848         }
849 
850         free(destIfNeeded);
851         break;
852     }
853     case ToUpperCase:
854     case ToLocaleUpperCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented.
855         u = s;
856         u.copyForWriting();
857         uint16_t *dataPtr = reinterpret_cast<uint16_t *>(u.rep()->data());
858         uint16_t *destIfNeeded;
859 
860         int len = toUpperF(dataPtr, u.size(), destIfNeeded);
861         if (len >= 0) {
862             result = jsString(UString(reinterpret_cast<UChar *>(destIfNeeded ? destIfNeeded : dataPtr), len));
863         } else {
864             result = jsString(s);
865         }
866 
867         free(destIfNeeded);
868         break;
869     }
870     case LocaleCompare:
871         if (args.size() < 1) {
872             return jsNumber(0);
873         }
874         return jsNumber(localeCompare(s, JSValue::toString(a0, exec)));
875     case Repeat: {
876         double n = JSValue::toInteger(args[0], exec);
877         if (exec->hadException())
878             return jsUndefined();
879         if (n < 0 || KJS::isPosInf(n))
880             return throwError(exec, RangeError, "Invalid repeat count");
881 
882         UString ret;
883         for (int i = 0; i < n; ++i)
884         {
885             ret += s;
886         }
887         return jsString(ret);
888     }
889     case Trim:
890     case TrimRight:
891     case TrimLeft: {
892         const uint16_t *dataPtr = reinterpret_cast<uint16_t *>(s.rep()->data());
893 
894         const int size = s.size();
895         int left = 0;
896         if (id != TrimRight)
897             while (left < size && CommonUnicode::isStrWhiteSpace(dataPtr[left])) {
898                 left++;
899             }
900 
901         int right = size;
902         if (id != TrimLeft)
903             while (right > left && CommonUnicode::isStrWhiteSpace(dataPtr[right - 1])) {
904                 right--;
905             }
906 
907         result = jsString(s.substr(left, right - left));
908         break;
909     }
910 #ifndef KJS_PURE_ECMA
911     case Big:
912         result = jsString("<big>" + s + "</big>");
913         break;
914     case Small:
915         result = jsString("<small>" + s + "</small>");
916         break;
917     case Blink:
918         result = jsString("<blink>" + s + "</blink>");
919         break;
920     case Bold:
921         result = jsString("<b>" + s + "</b>");
922         break;
923     case Fixed:
924         result = jsString("<tt>" + s + "</tt>");
925         break;
926     case Italics:
927         result = jsString("<i>" + s + "</i>");
928         break;
929     case Strike:
930         result = jsString("<strike>" + s + "</strike>");
931         break;
932     case Sub:
933         result = jsString("<sub>" + s + "</sub>");
934         break;
935     case Sup:
936         result = jsString("<sup>" + s + "</sup>");
937         break;
938     case Fontcolor:
939         result = jsString("<font color=\"" + JSValue::toString(a0, exec) + "\">" + s + "</font>");
940         break;
941     case Fontsize:
942         result = jsString("<font size=\"" + JSValue::toString(a0, exec) + "\">" + s + "</font>");
943         break;
944     case Anchor:
945         result = jsString("<a name=\"" + JSValue::toString(a0, exec) + "\">" + s + "</a>");
946         break;
947     case Link:
948         result = jsString("<a href=\"" + JSValue::toString(a0, exec) + "\">" + s + "</a>");
949         break;
950 #endif
951     }
952 
953     return result;
954 }
955 
956 // ------------------------------ StringObjectImp ------------------------------
957 
StringObjectImp(ExecState * exec,FunctionPrototype * funcProto,StringPrototype * stringProto)958 StringObjectImp::StringObjectImp(ExecState *exec,
959                                  FunctionPrototype *funcProto,
960                                  StringPrototype *stringProto)
961     : InternalFunctionImp(funcProto)
962 {
963     // ECMA 15.5.3.1 String.prototype
964     putDirect(exec->propertyNames().prototype, stringProto, DontEnum | DontDelete | ReadOnly);
965 
966     putDirectFunction(new StringObjectFuncImp(exec, funcProto, exec->propertyNames().fromCharCode), DontEnum);
967 
968     // no. of arguments for constructor
969     putDirect(exec->propertyNames().length, jsNumber(1), ReadOnly | DontDelete | DontEnum);
970 }
971 
implementsConstruct() const972 bool StringObjectImp::implementsConstruct() const
973 {
974     return true;
975 }
976 
977 // ECMA 15.5.2
construct(ExecState * exec,const List & args)978 JSObject *StringObjectImp::construct(ExecState *exec, const List &args)
979 {
980     JSObject *proto = exec->lexicalInterpreter()->builtinStringPrototype();
981     if (args.size() == 0) {
982         return new StringInstance(proto);
983     }
984     return new StringInstance(proto, JSValue::toString(*args.begin(), exec));
985 }
986 
987 // ECMA 15.5.1
callAsFunction(ExecState * exec,JSObject *,const List & args)988 JSValue *StringObjectImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
989 {
990     if (args.isEmpty()) {
991         return jsString("");
992     } else {
993         JSValue *v = args[0];
994         return jsString(JSValue::toString(v, exec));
995     }
996 }
997 
998 // ------------------------------ StringObjectFuncImp --------------------------
999 
1000 // ECMA 15.5.3.2 fromCharCode()
StringObjectFuncImp(ExecState * exec,FunctionPrototype * funcProto,const Identifier & name)1001 StringObjectFuncImp::StringObjectFuncImp(ExecState *exec, FunctionPrototype *funcProto, const Identifier &name)
1002     : InternalFunctionImp(funcProto, name)
1003 {
1004     putDirect(exec->propertyNames().length, jsNumber(1), DontDelete | ReadOnly | DontEnum);
1005 }
1006 
callAsFunction(ExecState * exec,JSObject *,const List & args)1007 JSValue *StringObjectFuncImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
1008 {
1009     UString s;
1010     if (args.size()) {
1011         UChar *buf = static_cast<UChar *>(fastMalloc(args.size() * sizeof(UChar)));
1012         UChar *p = buf;
1013         ListIterator it = args.begin();
1014         while (it != args.end()) {
1015             unsigned short u = JSValue::toUInt16(*it, exec);
1016             *p++ = UChar(u);
1017             it++;
1018         }
1019         s = UString(buf, args.size(), false);
1020     } else {
1021         s = "";
1022     }
1023 
1024     return jsString(s);
1025 }
1026 
1027 }
1028