1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "ExpatParser"
18 
19 #include <expat.h>
20 #include <string.h>
21 
22 #include <memory>
23 
24 #include <android/log.h>
25 #include <android-base/stringprintf.h>
26 
27 #include <nativehelper/JNIHelp.h>
28 #include <nativehelper/ScopedLocalRef.h>
29 #include <nativehelper/ScopedPrimitiveArray.h>
30 #include <nativehelper/ScopedStringChars.h>
31 #include <nativehelper/ScopedUtfChars.h>
32 #include <nativehelper/jni_macros.h>
33 
34 #include <unicode/char16ptr.h>
35 #include <unicode/ustring.h>
36 
37 #include "JniConstants.h"
38 #include "JniException.h"
39 
40 #define BUCKET_COUNT 128
41 
42 /**
43  * Wrapper around an interned string.
44  */
45 struct InternedString {
InternedStringInternedString46     InternedString() : interned(NULL), bytes(NULL) {
47     }
48 
~InternedStringInternedString49     ~InternedString() {
50         delete[] bytes;
51     }
52 
53     /** The interned string itself. */
54     jstring interned;
55 
56     /** UTF-8 equivalent of the interned string. */
57     const char* bytes;
58 
59     /** Hash code of the interned string. */
60     int hash;
61 };
62 
63 /**
64  * Keeps track of strings between start and end events.
65  */
66 class StringStack {
67 public:
StringStack()68     StringStack() : array(new jstring[DEFAULT_CAPACITY]), capacity(DEFAULT_CAPACITY), size(0) {
69     }
70 
~StringStack()71     ~StringStack() {
72         delete[] array;
73     }
74 
push(JNIEnv * env,jstring s)75     void push(JNIEnv* env, jstring s) {
76         if (size == capacity) {
77             int newCapacity = capacity * 2;
78             jstring* newArray = new jstring[newCapacity];
79             if (newArray == NULL) {
80                 jniThrowOutOfMemoryError(env, NULL);
81                 return;
82             }
83             memcpy(newArray, array, capacity * sizeof(jstring));
84 
85             delete[] array;
86             array = newArray;
87             capacity = newCapacity;
88         }
89 
90         array[size++] = s;
91     }
92 
pop()93     jstring pop() {
94         return (size == 0) ? NULL : array[--size];
95     }
96 
97 private:
98     enum { DEFAULT_CAPACITY = 10 };
99 
100     jstring* array;
101     int capacity;
102     int size;
103 };
104 
105 /**
106  * Data passed to parser handler method by the parser.
107  */
108 struct ParsingContext {
ParsingContextParsingContext109     explicit ParsingContext(jobject object)
110         : env(NULL), object(object), buffer(NULL), bufferSize(-1) {
111         for (int i = 0; i < BUCKET_COUNT; i++) {
112             internedStrings[i] = NULL;
113         }
114     }
115 
116     // Warning: 'env' must be valid on entry.
~ParsingContextParsingContext117     ~ParsingContext() {
118         freeBuffer();
119 
120         // Free interned string cache.
121         for (int i = 0; i < BUCKET_COUNT; i++) {
122             if (internedStrings[i]) {
123                 InternedString** bucket = internedStrings[i];
124                 InternedString* current;
125                 while ((current = *(bucket++)) != NULL) {
126                     // Free the interned string reference.
127                     env->DeleteGlobalRef(current->interned);
128 
129                     // Free the bucket.
130                     delete current;
131                 }
132 
133                 // Free the buckets.
134                 delete[] internedStrings[i];
135             }
136         }
137     }
138 
ensureCapacityParsingContext139     jcharArray ensureCapacity(int length) {
140         if (bufferSize < length) {
141             // Free the existing char[].
142             freeBuffer();
143 
144             // Allocate a new char[].
145             jcharArray javaBuffer = env->NewCharArray(length);
146             if (javaBuffer == NULL) return NULL;
147 
148             // Create a global reference.
149             javaBuffer = reinterpret_cast<jcharArray>(env->NewGlobalRef(javaBuffer));
150             if (javaBuffer == NULL) return NULL;
151 
152             buffer = javaBuffer;
153             bufferSize = length;
154         }
155         return buffer;
156     }
157 
158 private:
freeBufferParsingContext159     void freeBuffer() {
160         if (buffer != NULL) {
161             env->DeleteGlobalRef(buffer);
162             buffer = NULL;
163             bufferSize = -1;
164         }
165     }
166 
167 public:
168     /**
169      * The JNI environment for the current thread. This should only be used
170      * to keep a reference to the env for use in Expat callbacks.
171      */
172     JNIEnv* env;
173 
174     /** The Java parser object. */
175     jobject object;
176 
177     /** Buffer for text events. */
178     jcharArray buffer;
179 
180 private:
181     /** The size of our buffer in jchars. */
182     int bufferSize;
183 
184 public:
185     /** Current attributes. */
186     const char** attributes;
187 
188     /** Number of attributes. */
189     int attributeCount;
190 
191     /** True if namespace support is enabled. */
192     bool processNamespaces;
193 
194     /** Keep track of names. */
195     StringStack stringStack;
196 
197     /** Cache of interned strings. */
198     InternedString** internedStrings[BUCKET_COUNT];
199 };
200 
toParsingContext(void * data)201 static ParsingContext* toParsingContext(void* data) {
202     return reinterpret_cast<ParsingContext*>(data);
203 }
204 
toParsingContext(XML_Parser parser)205 static ParsingContext* toParsingContext(XML_Parser parser) {
206     return reinterpret_cast<ParsingContext*>(XML_GetUserData(parser));
207 }
208 
toXMLParser(jlong address)209 static XML_Parser toXMLParser(jlong address) {
210   return reinterpret_cast<XML_Parser>(address);
211 }
212 
fromXMLParser(XML_Parser parser)213 static jlong fromXMLParser(XML_Parser parser) {
214   return reinterpret_cast<uintptr_t>(parser);
215 }
216 
217 static jmethodID commentMethod;
218 static jmethodID endCdataMethod;
219 static jmethodID endDtdMethod;
220 static jmethodID endElementMethod;
221 static jmethodID endNamespaceMethod;
222 static jmethodID handleExternalEntityMethod;
223 static jmethodID internMethod;
224 static jmethodID notationDeclMethod;
225 static jmethodID processingInstructionMethod;
226 static jmethodID startCdataMethod;
227 static jmethodID startDtdMethod;
228 static jmethodID startElementMethod;
229 static jmethodID startNamespaceMethod;
230 static jmethodID textMethod;
231 static jmethodID unparsedEntityDeclMethod;
232 static jstring emptyString;
233 
234 /**
235  * Calculates a hash code for a null-terminated string. This is *not* equivalent
236  * to Java's String.hashCode(). This hashes the bytes while String.hashCode()
237  * hashes UTF-16 chars.
238  *
239  * @param s null-terminated string to hash
240  * @returns hash code
241  */
hashString(const char * s)242 static int hashString(const char* s) {
243     int hash = 0;
244     if (s) {
245         while (*s) {
246             hash = hash * 31 + *s++;
247         }
248     }
249     return hash;
250 }
251 
252 /**
253  * Creates a new interned string wrapper. Looks up the interned string
254  * representing the given UTF-8 bytes.
255  *
256  * @param bytes null-terminated string to intern
257  * @param hash of bytes
258  * @returns wrapper of interned Java string
259  */
newInternedString(JNIEnv * env,const char * bytes,int hash)260 static InternedString* newInternedString(JNIEnv* env, const char* bytes, int hash) {
261     // Allocate a new wrapper.
262     std::unique_ptr<InternedString> wrapper(new InternedString);
263     if (wrapper.get() == NULL) {
264         jniThrowOutOfMemoryError(env, NULL);
265         return NULL;
266     }
267 
268     // Create a copy of the UTF-8 bytes.
269     // TODO: sometimes we already know the length. Reuse it if so.
270     char* copy = new char[strlen(bytes) + 1];
271     if (copy == NULL) {
272         jniThrowOutOfMemoryError(env, NULL);
273         return NULL;
274     }
275     strcpy(copy, bytes);
276     wrapper->bytes = copy;
277 
278     // Save the hash.
279     wrapper->hash = hash;
280 
281     // To intern a string, we must first create a new string and then call
282     // intern() on it. We then keep a global reference to the interned string.
283     ScopedLocalRef<jstring> newString(env, env->NewStringUTF(bytes));
284     if (env->ExceptionCheck()) {
285         return NULL;
286     }
287 
288     // Call intern().
289     ScopedLocalRef<jstring> interned(env,
290             reinterpret_cast<jstring>(env->CallObjectMethod(newString.get(), internMethod)));
291     if (env->ExceptionCheck()) {
292         return NULL;
293     }
294 
295     // Create a global reference to the interned string.
296     wrapper->interned = reinterpret_cast<jstring>(env->NewGlobalRef(interned.get()));
297     if (env->ExceptionCheck()) {
298         return NULL;
299     }
300 
301     return wrapper.release();
302 }
303 
304 /**
305  * Allocates a new bucket with one entry.
306  *
307  * @param entry to store in the bucket
308  * @returns a reference to the bucket
309  */
newInternedStringBucket(InternedString * entry)310 static InternedString** newInternedStringBucket(InternedString* entry) {
311     InternedString** bucket = new InternedString*[2];
312     if (bucket != NULL) {
313         bucket[0] = entry;
314         bucket[1] = NULL;
315     }
316     return bucket;
317 }
318 
319 /**
320  * Expands an interned string bucket and adds the given entry. Frees the
321  * provided bucket and returns a new one.
322  *
323  * @param existingBucket the bucket to replace
324  * @param entry to add to the bucket
325  * @returns a reference to the newly-allocated bucket containing the given entry
326  */
expandInternedStringBucket(InternedString ** existingBucket,InternedString * entry)327 static InternedString** expandInternedStringBucket(
328         InternedString** existingBucket, InternedString* entry) {
329     // Determine the size of the existing bucket.
330     int size = 0;
331     while (existingBucket[size]) size++;
332 
333     // Allocate the new bucket with enough space for one more entry and
334     // a null terminator.
335     InternedString** newBucket = new InternedString*[size + 2];
336     if (newBucket == NULL) return NULL;
337 
338     memcpy(newBucket, existingBucket, size * sizeof(InternedString*));
339     newBucket[size] = entry;
340     newBucket[size + 1] = NULL;
341     delete[] existingBucket;
342 
343     return newBucket;
344 }
345 
346 /**
347  * Returns an interned string for the given UTF-8 string.
348  *
349  * @param bucket to search for s
350  * @param s null-terminated string to find
351  * @param hash of s
352  * @returns interned Java string equivalent of s or null if not found
353  */
findInternedString(InternedString ** bucket,const char * s,int hash)354 static jstring findInternedString(InternedString** bucket, const char* s, int hash) {
355     InternedString* current;
356     while ((current = *(bucket++)) != NULL) {
357         if (current->hash != hash) continue;
358         if (!strcmp(s, current->bytes)) return current->interned;
359     }
360     return NULL;
361 }
362 
363 /**
364  * Returns an interned string for the given UTF-8 string.
365  *
366  * @param s null-terminated string to intern
367  * @returns interned Java string equivelent of s or NULL if s is null
368  */
internString(JNIEnv * env,ParsingContext * parsingContext,const char * s)369 static jstring internString(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
370     if (s == NULL) return NULL;
371 
372     int hash = hashString(s);
373     int bucketIndex = hash & (BUCKET_COUNT - 1);
374 
375     InternedString*** buckets = parsingContext->internedStrings;
376     InternedString** bucket = buckets[bucketIndex];
377     InternedString* internedString;
378 
379     if (bucket) {
380         // We have a bucket already. Look for the given string.
381         jstring found = findInternedString(bucket, s, hash);
382         if (found) {
383             // We found it!
384             return found;
385         }
386 
387         // We didn't find it. :(
388         // Create a new entry.
389         internedString = newInternedString(env, s, hash);
390         if (internedString == NULL) return NULL;
391 
392         // Expand the bucket.
393         bucket = expandInternedStringBucket(bucket, internedString);
394         if (bucket == NULL) {
395             delete internedString;
396             jniThrowOutOfMemoryError(env, NULL);
397             return NULL;
398         }
399 
400         buckets[bucketIndex] = bucket;
401 
402         return internedString->interned;
403     } else {
404         // We don't even have a bucket yet. Create an entry.
405         internedString = newInternedString(env, s, hash);
406         if (internedString == NULL) return NULL;
407 
408         // Create a new bucket with one entry.
409         bucket = newInternedStringBucket(internedString);
410         if (bucket == NULL) {
411             delete internedString;
412             jniThrowOutOfMemoryError(env, NULL);
413             return NULL;
414         }
415 
416         buckets[bucketIndex] = bucket;
417 
418         return internedString->interned;
419     }
420 }
421 
jniThrowExpatException(JNIEnv * env,XML_Error error)422 static void jniThrowExpatException(JNIEnv* env, XML_Error error) {
423     const char* message = XML_ErrorString(error);
424     jniThrowException(env, "org/apache/harmony/xml/ExpatException", message);
425 }
426 
427 /**
428  * Copies UTF-8 characters into the buffer. Returns the number of Java chars
429  * which were buffered.
430  *
431  * @returns number of UTF-16 characters which were copied
432  */
fillBuffer(ParsingContext * parsingContext,const char * utf8,int byteLength)433 static size_t fillBuffer(ParsingContext* parsingContext, const char* utf8, int byteLength) {
434     JNIEnv* env = parsingContext->env;
435 
436     // Grow buffer if necessary (the length in bytes is always >= the length in chars).
437     jcharArray javaChars = parsingContext->ensureCapacity(byteLength);
438     if (javaChars == NULL) {
439         return -1;
440     }
441 
442     // Decode UTF-8 characters into our char[].
443     ScopedCharArrayRW chars(env, javaChars);
444     if (chars.get() == NULL) {
445         return -1;
446     }
447     UErrorCode status = U_ZERO_ERROR;
448     int32_t length16;
449     // Use inline C++ class provided by ICU
450     icu::Char16Ptr dest(chars.get()); // Convert jchar (aka uint16_t*) to char16_t*
451     // Avoid icu::UnicodeString due to unstable C++ ABI.
452     u_strFromUTF8WithSub(dest.get(), // dest
453       byteLength, // destCapacity
454       &length16, // pDestLength
455       utf8, // src
456       byteLength, // srcLength
457       0xfffd,  // 0xfffd is the standard substitution character for malformed input sequence.
458       NULL,    // Don't care about number of substitutions.
459       &status);
460     return length16;
461 }
462 
463 /**
464  * Buffers the given text and passes it to the given method.
465  *
466  * @param method to pass the characters and length to with signature
467  *  (char[], int)
468  * @param data parsing context
469  * @param text to copy into the buffer
470  * @param length of text to copy (in bytes)
471  */
bufferAndInvoke(jmethodID method,void * data,const char * text,size_t length)472 static void bufferAndInvoke(jmethodID method, void* data, const char* text, size_t length) {
473     ParsingContext* parsingContext = toParsingContext(data);
474     JNIEnv* env = parsingContext->env;
475 
476     // Bail out if a previously called handler threw an exception.
477     if (env->ExceptionCheck()) return;
478 
479     // Buffer the element name.
480     size_t utf16length = fillBuffer(parsingContext, text, length);
481 
482     // Invoke given method.
483     jobject javaParser = parsingContext->object;
484     jcharArray buffer = parsingContext->buffer;
485     env->CallVoidMethod(javaParser, method, buffer, utf16length);
486 }
487 
toAttributes(jlong attributePointer)488 static const char** toAttributes(jlong attributePointer) {
489     return reinterpret_cast<const char**>(static_cast<uintptr_t>(attributePointer));
490 }
491 
492 /**
493  * The component parts of an attribute or element name.
494  */
495 class ExpatElementName {
496 public:
ExpatElementName(JNIEnv * env,ParsingContext * parsingContext,jlong attributePointer,jint index)497     ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, jlong attributePointer, jint index) {
498         const char** attributes = toAttributes(attributePointer);
499         const char* name = attributes[index * 2];
500         init(env, parsingContext, name);
501     }
502 
ExpatElementName(JNIEnv * env,ParsingContext * parsingContext,const char * s)503     ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
504         init(env, parsingContext, s);
505     }
506 
~ExpatElementName()507     ~ExpatElementName() {
508         free(mCopy);
509     }
510 
511     /**
512      * Returns the namespace URI, like "http://www.w3.org/1999/xhtml".
513      * Possibly empty.
514      */
uri()515     jstring uri() {
516         return internString(mEnv, mParsingContext, mUri);
517     }
518 
519     /**
520      * Returns the element or attribute local name, like "h1". Never empty. When
521      * namespace processing is disabled, this may contain a prefix, yielding a
522      * local name like "html:h1". In such cases, the qName will always be empty.
523      */
localName()524     jstring localName() {
525         return internString(mEnv, mParsingContext, mLocalName);
526     }
527 
528     /**
529      * Returns the namespace prefix, like "html". Possibly empty.
530      */
qName()531     jstring qName() {
532         if (*mPrefix == 0) {
533             return localName();
534         }
535 
536         // return prefix + ":" + localName
537         auto qName = android::base::StringPrintf("%s:%s", mPrefix, mLocalName);
538         return internString(mEnv, mParsingContext, qName.c_str());
539     }
540 
541     /**
542      * Returns true if this expat name has the same URI and local name.
543      */
matches(const char * uri,const char * localName)544     bool matches(const char* uri, const char* localName) {
545         return strcmp(uri, mUri) == 0 && strcmp(localName, mLocalName) == 0;
546     }
547 
548     /**
549      * Returns true if this expat name has the same qualified name.
550      */
matchesQName(const char * qName)551     bool matchesQName(const char* qName) {
552         const char* lastColon = strrchr(qName, ':');
553 
554         // Compare local names only if either:
555         //  - the input qualified name doesn't have a colon (like "h1")
556         //  - this element doesn't have a prefix. Such is the case when it
557         //    doesn't belong to a namespace, or when this parser's namespace
558         //    processing is disabled. In the latter case, this element's local
559         //    name may still contain a colon (like "html:h1").
560         if (lastColon == NULL || *mPrefix == 0) {
561             return strcmp(qName, mLocalName) == 0;
562         }
563 
564         // Otherwise compare both prefix and local name
565         size_t prefixLength = lastColon - qName;
566         return strlen(mPrefix) == prefixLength
567             && strncmp(qName, mPrefix, prefixLength) == 0
568             && strcmp(lastColon + 1, mLocalName) == 0;
569     }
570 
571 private:
572     JNIEnv* mEnv;
573     ParsingContext* mParsingContext;
574     char* mCopy;
575     const char* mUri;
576     const char* mLocalName;
577     const char* mPrefix;
578 
579     /**
580      * Decodes an Expat-encoded name of one of these three forms:
581      *     "uri|localName|prefix" (example: "http://www.w3.org/1999/xhtml|h1|html")
582      *     "uri|localName" (example: "http://www.w3.org/1999/xhtml|h1")
583      *     "localName" (example: "h1")
584      */
init(JNIEnv * env,ParsingContext * parsingContext,const char * s)585     void init(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
586         mEnv = env;
587         mParsingContext = parsingContext;
588         mCopy = strdup(s);
589 
590         // split the input into up to 3 parts: a|b|c
591         char* context = NULL;
592         char* a = strtok_r(mCopy, "|", &context);
593         char* b = strtok_r(NULL, "|", &context);
594         char* c = strtok_r(NULL, "|", &context);
595 
596         if (c != NULL) { // input of the form "uri|localName|prefix"
597             mUri = a;
598             mLocalName = b;
599             mPrefix = c;
600         } else if (b != NULL) { // input of the form "uri|localName"
601             mUri = a;
602             mLocalName = b;
603             mPrefix = "";
604         } else { // input of the form "localName"
605             mLocalName = a;
606             mUri = "";
607             mPrefix = "";
608         }
609     }
610 
611     // Disallow copy and assignment.
612     ExpatElementName(const ExpatElementName&);
613     void operator=(const ExpatElementName&);
614 };
615 
616 /**
617  * Called by Expat at the start of an element. Delegates to the same method
618  * on the Java parser.
619  *
620  * @param data parsing context
621  * @param elementName "uri|localName" or "localName" for the current element
622  * @param attributes alternating attribute names and values. Like element
623  * names, attribute names follow the format "uri|localName" or "localName".
624  */
startElement(void * data,const char * elementName,const char ** attributes)625 static void startElement(void* data, const char* elementName, const char** attributes) {
626     ParsingContext* parsingContext = toParsingContext(data);
627     JNIEnv* env = parsingContext->env;
628 
629     // Bail out if a previously called handler threw an exception.
630     if (env->ExceptionCheck()) return;
631 
632     // Count the number of attributes.
633     int count = 0;
634     while (attributes[count * 2]) count++;
635 
636     // Make the attributes available for the duration of this call.
637     parsingContext->attributes = attributes;
638     parsingContext->attributeCount = count;
639 
640     jobject javaParser = parsingContext->object;
641 
642     ExpatElementName e(env, parsingContext, elementName);
643     jstring uri = parsingContext->processNamespaces ? e.uri() : emptyString;
644     jstring localName = parsingContext->processNamespaces ? e.localName() : emptyString;
645     jstring qName = e.qName();
646 
647     parsingContext->stringStack.push(env, qName);
648     parsingContext->stringStack.push(env, uri);
649     parsingContext->stringStack.push(env, localName);
650 
651     jlong attributesAddress = reinterpret_cast<jlong>(attributes);
652     env->CallVoidMethod(javaParser, startElementMethod, uri, localName, qName, attributesAddress, count);
653 
654     parsingContext->attributes = NULL;
655     parsingContext->attributeCount = -1;
656 }
657 
658 /**
659  * Called by Expat at the end of an element. Delegates to the same method
660  * on the Java parser.
661  *
662  * @param data parsing context
663  * @param elementName "uri|localName" or "localName" for the current element;
664  *         we assume that this matches the last data on our stack.
665  */
endElement(void * data,const char *)666 static void endElement(void* data, const char* /*elementName*/) {
667     ParsingContext* parsingContext = toParsingContext(data);
668     JNIEnv* env = parsingContext->env;
669 
670     // Bail out if a previously called handler threw an exception.
671     if (env->ExceptionCheck()) return;
672 
673     jobject javaParser = parsingContext->object;
674 
675     jstring localName = parsingContext->stringStack.pop();
676     jstring uri = parsingContext->stringStack.pop();
677     jstring qName = parsingContext->stringStack.pop();
678 
679     env->CallVoidMethod(javaParser, endElementMethod, uri, localName, qName);
680 }
681 
682 /**
683  * Called by Expat when it encounters text. Delegates to the same method
684  * on the Java parser. This may be called mutiple times with incremental pieces
685  * of the same contiguous block of text.
686  *
687  * @param data parsing context
688  * @param characters buffer containing encountered text
689  * @param length number of characters in the buffer
690  */
text(void * data,const char * characters,int length)691 static void text(void* data, const char* characters, int length) {
692     bufferAndInvoke(textMethod, data, characters, length);
693 }
694 
695 /**
696  * Called by Expat when it encounters a comment. Delegates to the same method
697  * on the Java parser.
698 
699  * @param data parsing context
700  * @param comment 0-terminated
701  */
comment(void * data,const char * comment)702 static void comment(void* data, const char* comment) {
703     bufferAndInvoke(commentMethod, data, comment, strlen(comment));
704 }
705 
706 /**
707  * Called by Expat at the beginning of a namespace mapping.
708  *
709  * @param data parsing context
710  * @param prefix null-terminated namespace prefix used in the XML
711  * @param uri of the namespace
712  */
startNamespace(void * data,const char * prefix,const char * uri)713 static void startNamespace(void* data, const char* prefix, const char* uri) {
714     ParsingContext* parsingContext = toParsingContext(data);
715     JNIEnv* env = parsingContext->env;
716 
717     // Bail out if a previously called handler threw an exception.
718     if (env->ExceptionCheck()) return;
719 
720     jstring internedPrefix = emptyString;
721     if (prefix != NULL) {
722         internedPrefix = internString(env, parsingContext, prefix);
723         if (env->ExceptionCheck()) return;
724     }
725 
726     jstring internedUri = emptyString;
727     if (uri != NULL) {
728         internedUri = internString(env, parsingContext, uri);
729         if (env->ExceptionCheck()) return;
730     }
731 
732     parsingContext->stringStack.push(env, internedPrefix);
733 
734     jobject javaParser = parsingContext->object;
735     env->CallVoidMethod(javaParser, startNamespaceMethod, internedPrefix, internedUri);
736 }
737 
738 /**
739  * Called by Expat at the end of a namespace mapping.
740  *
741  * @param data parsing context
742  * @param prefix null-terminated namespace prefix used in the XML;
743  *         we assume this is the same as the last prefix on the stack.
744  */
endNamespace(void * data,const char *)745 static void endNamespace(void* data, const char* /*prefix*/) {
746     ParsingContext* parsingContext = toParsingContext(data);
747     JNIEnv* env = parsingContext->env;
748 
749     // Bail out if a previously called handler threw an exception.
750     if (env->ExceptionCheck()) return;
751 
752     jstring internedPrefix = parsingContext->stringStack.pop();
753 
754     jobject javaParser = parsingContext->object;
755     env->CallVoidMethod(javaParser, endNamespaceMethod, internedPrefix);
756 }
757 
758 /**
759  * Called by Expat at the beginning of a CDATA section.
760  *
761  * @param data parsing context
762  */
startCdata(void * data)763 static void startCdata(void* data) {
764     ParsingContext* parsingContext = toParsingContext(data);
765     JNIEnv* env = parsingContext->env;
766 
767     // Bail out if a previously called handler threw an exception.
768     if (env->ExceptionCheck()) return;
769 
770     jobject javaParser = parsingContext->object;
771     env->CallVoidMethod(javaParser, startCdataMethod);
772 }
773 
774 /**
775  * Called by Expat at the end of a CDATA section.
776  *
777  * @param data parsing context
778  */
endCdata(void * data)779 static void endCdata(void* data) {
780     ParsingContext* parsingContext = toParsingContext(data);
781     JNIEnv* env = parsingContext->env;
782 
783     // Bail out if a previously called handler threw an exception.
784     if (env->ExceptionCheck()) return;
785 
786     jobject javaParser = parsingContext->object;
787     env->CallVoidMethod(javaParser, endCdataMethod);
788 }
789 
790 /**
791  * Called by Expat at the beginning of a DOCTYPE section.
792  * Expat gives us 'hasInternalSubset', but the Java API doesn't expect it, so we don't need it.
793  */
startDtd(void * data,const char * name,const char * systemId,const char * publicId,int)794 static void startDtd(void* data, const char* name,
795         const char* systemId, const char* publicId, int /*hasInternalSubset*/) {
796     ParsingContext* parsingContext = toParsingContext(data);
797     JNIEnv* env = parsingContext->env;
798 
799     // Bail out if a previously called handler threw an exception.
800     if (env->ExceptionCheck()) return;
801 
802     jstring javaName = internString(env, parsingContext, name);
803     if (env->ExceptionCheck()) return;
804 
805     jstring javaPublicId = internString(env, parsingContext, publicId);
806     if (env->ExceptionCheck()) return;
807 
808     jstring javaSystemId = internString(env, parsingContext, systemId);
809     if (env->ExceptionCheck()) return;
810 
811     jobject javaParser = parsingContext->object;
812     env->CallVoidMethod(javaParser, startDtdMethod, javaName, javaPublicId,
813         javaSystemId);
814 }
815 
816 /**
817  * Called by Expat at the end of a DOCTYPE section.
818  *
819  * @param data parsing context
820  */
endDtd(void * data)821 static void endDtd(void* data) {
822     ParsingContext* parsingContext = toParsingContext(data);
823     JNIEnv* env = parsingContext->env;
824 
825     // Bail out if a previously called handler threw an exception.
826     if (env->ExceptionCheck()) return;
827 
828     jobject javaParser = parsingContext->object;
829     env->CallVoidMethod(javaParser, endDtdMethod);
830 }
831 
832 /**
833  * Called by Expat when it encounters processing instructions.
834  *
835  * @param data parsing context
836  * @param target of the instruction
837  * @param instructionData
838  */
processingInstruction(void * data,const char * target,const char * instructionData)839 static void processingInstruction(void* data, const char* target, const char* instructionData) {
840     ParsingContext* parsingContext = toParsingContext(data);
841     JNIEnv* env = parsingContext->env;
842 
843     // Bail out if a previously called handler threw an exception.
844     if (env->ExceptionCheck()) return;
845 
846     jstring javaTarget = internString(env, parsingContext, target);
847     if (env->ExceptionCheck()) return;
848 
849     ScopedLocalRef<jstring> javaInstructionData(env, env->NewStringUTF(instructionData));
850     if (env->ExceptionCheck()) return;
851 
852     jobject javaParser = parsingContext->object;
853     env->CallVoidMethod(javaParser, processingInstructionMethod, javaTarget, javaInstructionData.get());
854 }
855 
856 /**
857  * Creates a new entity parser.
858  *
859  * @param object the Java ExpatParser instance
860  * @param parentParser pointer
861  * @param javaEncoding the character encoding name
862  * @param javaContext that was provided to handleExternalEntity
863  * @returns the pointer to the C Expat entity parser
864  */
ExpatParser_createEntityParser(JNIEnv * env,jobject,jlong parentParser,jstring javaContext)865 static jlong ExpatParser_createEntityParser(JNIEnv* env, jobject, jlong parentParser, jstring javaContext) {
866     ScopedUtfChars context(env, javaContext);
867     if (context.c_str() == NULL) {
868         return 0;
869     }
870 
871     XML_Parser parent = toXMLParser(parentParser);
872     XML_Parser entityParser = XML_ExternalEntityParserCreate(parent, context.c_str(), NULL);
873     if (entityParser == NULL) {
874         jniThrowOutOfMemoryError(env, NULL);
875     }
876 
877     return fromXMLParser(entityParser);
878 }
879 
880 /**
881  * Handles external entities. We ignore the "base" URI and keep track of it
882  * ourselves.
883  */
handleExternalEntity(XML_Parser parser,const char * context,const char *,const char * systemId,const char * publicId)884 static int handleExternalEntity(XML_Parser parser, const char* context,
885         const char*, const char* systemId, const char* publicId) {
886     ParsingContext* parsingContext = toParsingContext(parser);
887     jobject javaParser = parsingContext->object;
888     JNIEnv* env = parsingContext->env;
889     jobject object = parsingContext->object;
890 
891     // Bail out if a previously called handler threw an exception.
892     if (env->ExceptionCheck()) {
893         return XML_STATUS_ERROR;
894     }
895 
896     ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
897     if (env->ExceptionCheck()) {
898         return XML_STATUS_ERROR;
899     }
900     ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
901     if (env->ExceptionCheck()) {
902         return XML_STATUS_ERROR;
903     }
904     ScopedLocalRef<jstring> javaContext(env, env->NewStringUTF(context));
905     if (env->ExceptionCheck()) {
906         return XML_STATUS_ERROR;
907     }
908 
909     // Pass the wrapped parser and both strings to java.
910     env->CallVoidMethod(javaParser, handleExternalEntityMethod, javaContext.get(),
911             javaPublicId.get(), javaSystemId.get());
912 
913     /*
914      * Parsing the external entity leaves parsingContext->env and object set to
915      * NULL, so we need to restore both.
916      *
917      * TODO: consider restoring the original env and object instead of setting
918      * them to NULL in the append() functions.
919      */
920     parsingContext->env = env;
921     parsingContext->object = object;
922 
923     return env->ExceptionCheck() ? XML_STATUS_ERROR : XML_STATUS_OK;
924 }
925 
926 /**
927  * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it.
928  */
unparsedEntityDecl(void * data,const char * name,const char *,const char * systemId,const char * publicId,const char * notationName)929 static void unparsedEntityDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId, const char* notationName) {
930     ParsingContext* parsingContext = toParsingContext(data);
931     jobject javaParser = parsingContext->object;
932     JNIEnv* env = parsingContext->env;
933 
934     // Bail out if a previously called handler threw an exception.
935     if (env->ExceptionCheck()) return;
936 
937     ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
938     if (env->ExceptionCheck()) return;
939     ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
940     if (env->ExceptionCheck()) return;
941     ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
942     if (env->ExceptionCheck()) return;
943     ScopedLocalRef<jstring> javaNotationName(env, env->NewStringUTF(notationName));
944     if (env->ExceptionCheck()) return;
945 
946     env->CallVoidMethod(javaParser, unparsedEntityDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get(), javaNotationName.get());
947 }
948 
949 /**
950  * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it.
951  */
notationDecl(void * data,const char * name,const char *,const char * systemId,const char * publicId)952 static void notationDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId) {
953     ParsingContext* parsingContext = toParsingContext(data);
954     jobject javaParser = parsingContext->object;
955     JNIEnv* env = parsingContext->env;
956 
957     // Bail out if a previously called handler threw an exception.
958     if (env->ExceptionCheck()) return;
959 
960     ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
961     if (env->ExceptionCheck()) return;
962     ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
963     if (env->ExceptionCheck()) return;
964     ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
965     if (env->ExceptionCheck()) return;
966 
967     env->CallVoidMethod(javaParser, notationDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get());
968 }
969 
970 /**
971  * Creates a new Expat parser. Called from the Java ExpatParser constructor.
972  *
973  * @param object the Java ExpatParser instance
974  * @param javaEncoding the character encoding name
975  * @param processNamespaces true if the parser should handle namespaces
976  * @returns the pointer to the C Expat parser
977  */
ExpatParser_initialize(JNIEnv * env,jobject object,jstring javaEncoding,jboolean processNamespaces)978 static jlong ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEncoding,
979         jboolean processNamespaces) {
980     // Allocate parsing context.
981     std::unique_ptr<ParsingContext> context(new ParsingContext(object));
982     if (context.get() == NULL) {
983         jniThrowOutOfMemoryError(env, NULL);
984         return 0;
985     }
986 
987     context->processNamespaces = processNamespaces;
988 
989     // Create a parser.
990     XML_Parser parser;
991     ScopedUtfChars encoding(env, javaEncoding);
992     if (encoding.c_str() == NULL) {
993         return 0;
994     }
995     if (processNamespaces) {
996         // Use '|' to separate URIs from local names.
997         parser = XML_ParserCreateNS(encoding.c_str(), '|');
998     } else {
999         parser = XML_ParserCreate(encoding.c_str());
1000     }
1001 
1002     if (parser != NULL) {
1003         if (processNamespaces) {
1004             XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
1005             XML_SetReturnNSTriplet(parser, 1);
1006         }
1007 
1008         XML_SetCdataSectionHandler(parser, startCdata, endCdata);
1009         XML_SetCharacterDataHandler(parser, text);
1010         XML_SetCommentHandler(parser, comment);
1011         XML_SetDoctypeDeclHandler(parser, startDtd, endDtd);
1012         XML_SetElementHandler(parser, startElement, endElement);
1013         XML_SetExternalEntityRefHandler(parser, handleExternalEntity);
1014         XML_SetNotationDeclHandler(parser, notationDecl);
1015         XML_SetProcessingInstructionHandler(parser, processingInstruction);
1016         XML_SetUnparsedEntityDeclHandler(parser, unparsedEntityDecl);
1017         XML_SetUserData(parser, context.release());
1018     } else {
1019         jniThrowOutOfMemoryError(env, NULL);
1020         return 0;
1021     }
1022 
1023     return fromXMLParser(parser);
1024 }
1025 
1026 /**
1027  * Decodes the bytes as characters and parse the characters as XML. This
1028  * performs character decoding using the charset specified at XML_Parser
1029  * creation. For Java chars, that charset must be UTF-16 so that a Java char[]
1030  * can be reinterpreted as a UTF-16 encoded byte[]. appendBytes, appendChars
1031  * and appendString all call through this method.
1032  */
append(JNIEnv * env,jobject object,jlong pointer,const char * bytes,size_t byteOffset,size_t byteCount,jboolean isFinal)1033 static void append(JNIEnv* env, jobject object, jlong pointer,
1034         const char* bytes, size_t byteOffset, size_t byteCount, jboolean isFinal) {
1035     XML_Parser parser = toXMLParser(pointer);
1036     ParsingContext* context = toParsingContext(parser);
1037     context->env = env;
1038     context->object = object;
1039     if (!XML_Parse(parser, bytes + byteOffset, byteCount, isFinal) && !env->ExceptionCheck()) {
1040         jniThrowExpatException(env, XML_GetErrorCode(parser));
1041     }
1042     context->object = NULL;
1043     context->env = NULL;
1044 }
1045 
ExpatParser_appendBytes(JNIEnv * env,jobject object,jlong pointer,jbyteArray xml,jint byteOffset,jint byteCount)1046 static void ExpatParser_appendBytes(JNIEnv* env, jobject object, jlong pointer,
1047         jbyteArray xml, jint byteOffset, jint byteCount) {
1048     ScopedByteArrayRO byteArray(env, xml);
1049     if (byteArray.get() == NULL) {
1050         return;
1051     }
1052 
1053     const char* bytes = reinterpret_cast<const char*>(byteArray.get());
1054     append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
1055 }
1056 
ExpatParser_appendChars(JNIEnv * env,jobject object,jlong pointer,jcharArray xml,jint charOffset,jint charCount)1057 static void ExpatParser_appendChars(JNIEnv* env, jobject object, jlong pointer,
1058         jcharArray xml, jint charOffset, jint charCount) {
1059     ScopedCharArrayRO charArray(env, xml);
1060     if (charArray.get() == NULL) {
1061         return;
1062     }
1063 
1064     const char* bytes = reinterpret_cast<const char*>(charArray.get());
1065     size_t byteOffset = 2 * charOffset;
1066     size_t byteCount = 2 * charCount;
1067     append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
1068 }
1069 
ExpatParser_appendString(JNIEnv * env,jobject object,jlong pointer,jstring javaXml,jboolean isFinal)1070 static void ExpatParser_appendString(JNIEnv* env, jobject object, jlong pointer, jstring javaXml, jboolean isFinal) {
1071     ScopedStringChars xml(env, javaXml);
1072     if (xml.get() == NULL) {
1073         return;
1074     }
1075     const char* bytes = reinterpret_cast<const char*>(xml.get());
1076     size_t byteCount = 2 * xml.size();
1077     append(env, object, pointer, bytes, 0, byteCount, isFinal);
1078 }
1079 
1080 /**
1081  * Releases parser only.
1082  */
ExpatParser_releaseParser(JNIEnv *,jobject,jlong address)1083 static void ExpatParser_releaseParser(JNIEnv*, jobject, jlong address) {
1084   XML_ParserFree(toXMLParser(address));
1085 }
1086 
1087 /**
1088  * Cleans up after the parser. Called at garbage collection time.
1089  */
ExpatParser_release(JNIEnv * env,jobject,jlong address)1090 static void ExpatParser_release(JNIEnv* env, jobject, jlong address) {
1091   XML_Parser parser = toXMLParser(address);
1092 
1093   ParsingContext* context = toParsingContext(parser);
1094   context->env = env;
1095   delete context;
1096 
1097   XML_ParserFree(parser);
1098 }
1099 
ExpatParser_line(JNIEnv *,jobject,jlong address)1100 static int ExpatParser_line(JNIEnv*, jobject, jlong address) {
1101   return XML_GetCurrentLineNumber(toXMLParser(address));
1102 }
1103 
ExpatParser_column(JNIEnv *,jobject,jlong address)1104 static int ExpatParser_column(JNIEnv*, jobject, jlong address) {
1105   return XML_GetCurrentColumnNumber(toXMLParser(address));
1106 }
1107 
1108 /**
1109  * Gets the URI of the attribute at the given index.
1110  *
1111  * @param attributePointer to the attribute array
1112  * @param index of the attribute
1113  * @returns interned Java string containing attribute's URI
1114  */
ExpatAttributes_getURI(JNIEnv * env,jobject,jlong address,jlong attributePointer,jint index)1115 static jstring ExpatAttributes_getURI(JNIEnv* env, jobject, jlong address,
1116         jlong attributePointer, jint index) {
1117   ParsingContext* context = toParsingContext(toXMLParser(address));
1118   return ExpatElementName(env, context, attributePointer, index).uri();
1119 }
1120 
1121 /**
1122  * Gets the local name of the attribute at the given index.
1123  *
1124  * @param attributePointer to the attribute array
1125  * @param index of the attribute
1126  * @returns interned Java string containing attribute's local name
1127  */
ExpatAttributes_getLocalName(JNIEnv * env,jobject,jlong address,jlong attributePointer,jint index)1128 static jstring ExpatAttributes_getLocalName(JNIEnv* env, jobject, jlong address,
1129         jlong attributePointer, jint index) {
1130   ParsingContext* context = toParsingContext(toXMLParser(address));
1131   return ExpatElementName(env, context, attributePointer, index).localName();
1132 }
1133 
1134 /**
1135  * Gets the qualified name of the attribute at the given index.
1136  *
1137  * @param attributePointer to the attribute array
1138  * @param index of the attribute
1139  * @returns interned Java string containing attribute's local name
1140  */
ExpatAttributes_getQName(JNIEnv * env,jobject,jlong address,jlong attributePointer,jint index)1141 static jstring ExpatAttributes_getQName(JNIEnv* env, jobject, jlong address,
1142         jlong attributePointer, jint index) {
1143   ParsingContext* context = toParsingContext(toXMLParser(address));
1144   return ExpatElementName(env, context, attributePointer, index).qName();
1145 }
1146 
1147 /**
1148  * Gets the value of the attribute at the given index.
1149  *
1150  * @param object Java ExpatParser instance
1151  * @param attributePointer to the attribute array
1152  * @param index of the attribute
1153  * @returns Java string containing attribute's value
1154  */
ExpatAttributes_getValueByIndex(JNIEnv * env,jobject,jlong attributePointer,jint index)1155 static jstring ExpatAttributes_getValueByIndex(JNIEnv* env, jobject,
1156         jlong attributePointer, jint index) {
1157     const char** attributes = toAttributes(attributePointer);
1158     const char* value = attributes[(index * 2) + 1];
1159     return env->NewStringUTF(value);
1160 }
1161 
1162 /**
1163  * Gets the index of the attribute with the given qualified name.
1164  *
1165  * @param attributePointer to the attribute array
1166  * @param qName to look for
1167  * @returns index of attribute with the given uri and local name or -1 if not
1168  *  found
1169  */
ExpatAttributes_getIndexForQName(JNIEnv * env,jobject,jlong attributePointer,jstring qName)1170 static jint ExpatAttributes_getIndexForQName(JNIEnv* env, jobject,
1171         jlong attributePointer, jstring qName) {
1172     ScopedUtfChars qNameBytes(env, qName);
1173     if (qNameBytes.c_str() == NULL) {
1174         return -1;
1175     }
1176 
1177     const char** attributes = toAttributes(attributePointer);
1178     int found = -1;
1179     for (int index = 0; attributes[index * 2]; ++index) {
1180         if (ExpatElementName(NULL, NULL, attributePointer, index).matchesQName(qNameBytes.c_str())) {
1181             found = index;
1182             break;
1183         }
1184     }
1185 
1186     return found;
1187 }
1188 
1189 /**
1190  * Gets the index of the attribute with the given URI and name.
1191  *
1192  * @param attributePointer to the attribute array
1193  * @param uri to look for
1194  * @param localName to look for
1195  * @returns index of attribute with the given uri and local name or -1 if not
1196  *  found
1197  */
ExpatAttributes_getIndex(JNIEnv * env,jobject,jlong attributePointer,jstring uri,jstring localName)1198 static jint ExpatAttributes_getIndex(JNIEnv* env, jobject, jlong attributePointer,
1199         jstring uri, jstring localName) {
1200     ScopedUtfChars uriBytes(env, uri);
1201     if (uriBytes.c_str() == NULL) {
1202         return -1;
1203     }
1204 
1205     ScopedUtfChars localNameBytes(env, localName);
1206     if (localNameBytes.c_str() == NULL) {
1207         return -1;
1208     }
1209 
1210     const char** attributes = toAttributes(attributePointer);
1211     for (int index = 0; attributes[index * 2]; ++index) {
1212         if (ExpatElementName(NULL, NULL, attributePointer, index).matches(uriBytes.c_str(),
1213                 localNameBytes.c_str())) {
1214             return index;
1215         }
1216     }
1217     return -1;
1218 }
1219 
1220 /**
1221  * Gets the value of the attribute with the given qualified name.
1222  *
1223  * @param attributePointer to the attribute array
1224  * @param uri to look for
1225  * @param localName to look for
1226  * @returns value of attribute with the given uri and local name or NULL if not
1227  *  found
1228  */
ExpatAttributes_getValueForQName(JNIEnv * env,jobject clazz,jlong attributePointer,jstring qName)1229 static jstring ExpatAttributes_getValueForQName(JNIEnv* env, jobject clazz,
1230         jlong attributePointer, jstring qName) {
1231     jint index = ExpatAttributes_getIndexForQName(env, clazz, attributePointer, qName);
1232     return index == -1 ? NULL
1233             : ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index);
1234 }
1235 
1236 /**
1237  * Gets the value of the attribute with the given URI and name.
1238  *
1239  * @param attributePointer to the attribute array
1240  * @param uri to look for
1241  * @param localName to look for
1242  * @returns value of attribute with the given uri and local name or NULL if not
1243  *  found
1244  */
ExpatAttributes_getValue(JNIEnv * env,jobject clazz,jlong attributePointer,jstring uri,jstring localName)1245 static jstring ExpatAttributes_getValue(JNIEnv* env, jobject clazz,
1246         jlong attributePointer, jstring uri, jstring localName) {
1247     jint index = ExpatAttributes_getIndex(env, clazz, attributePointer, uri, localName);
1248     return index == -1 ? NULL
1249             : ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index);
1250 }
1251 
1252 /**
1253  * Clones an array of strings. Uses one contiguous block of memory so as to
1254  * maximize performance.
1255  *
1256  * @param address char** to clone
1257  * @param count number of attributes
1258  */
ExpatParser_cloneAttributes(JNIEnv * env,jobject,jlong address,jint count)1259 static jlong ExpatParser_cloneAttributes(JNIEnv* env, jobject, jlong address, jint count) {
1260     const char** source = reinterpret_cast<const char**>(static_cast<uintptr_t>(address));
1261     count *= 2;
1262 
1263     // Figure out how big the buffer needs to be.
1264     int arraySize = (count + 1) * sizeof(char*);
1265     int totalSize = arraySize;
1266     int stringLengths[count];
1267     for (int i = 0; i < count; i++) {
1268         int length = strlen(source[i]);
1269         stringLengths[i] = length;
1270         totalSize += length + 1;
1271     }
1272 
1273     char* buffer = new char[totalSize];
1274     if (buffer == NULL) {
1275         jniThrowOutOfMemoryError(env, NULL);
1276         return 0;
1277     }
1278 
1279     // Array is at the beginning of the buffer.
1280     char** clonedArray = reinterpret_cast<char**>(buffer);
1281     clonedArray[count] = NULL; // null terminate
1282 
1283     // String data follows immediately after.
1284     char* destinationString = buffer + arraySize;
1285     for (int i = 0; i < count; i++) {
1286         const char* sourceString = source[i];
1287         int stringLength = stringLengths[i];
1288         memcpy(destinationString, sourceString, stringLength + 1);
1289         clonedArray[i] = destinationString;
1290         destinationString += stringLength + 1;
1291     }
1292 
1293     return reinterpret_cast<uintptr_t>(buffer);
1294 }
1295 
1296 /**
1297  * Frees cloned attributes.
1298  */
ExpatAttributes_freeAttributes(JNIEnv *,jobject,jlong pointer)1299 static void ExpatAttributes_freeAttributes(JNIEnv*, jobject, jlong pointer) {
1300     delete[] reinterpret_cast<char*>(static_cast<uintptr_t>(pointer));
1301 }
1302 
1303 /**
1304  * Called when we initialize our Java parser class.
1305  *
1306  * @param clazz Java ExpatParser class
1307  */
ExpatParser_staticInitialize(JNIEnv * env,jobject classObject,jstring empty)1308 static void ExpatParser_staticInitialize(JNIEnv* env, jobject classObject, jstring empty) {
1309     jclass clazz = reinterpret_cast<jclass>(classObject);
1310     startElementMethod = env->GetMethodID(clazz, "startElement",
1311         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JI)V");
1312     if (startElementMethod == NULL) return;
1313 
1314     endElementMethod = env->GetMethodID(clazz, "endElement",
1315         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
1316     if (endElementMethod == NULL) return;
1317 
1318     textMethod = env->GetMethodID(clazz, "text", "([CI)V");
1319     if (textMethod == NULL) return;
1320 
1321     commentMethod = env->GetMethodID(clazz, "comment", "([CI)V");
1322     if (commentMethod == NULL) return;
1323 
1324     startCdataMethod = env->GetMethodID(clazz, "startCdata", "()V");
1325     if (startCdataMethod == NULL) return;
1326 
1327     endCdataMethod = env->GetMethodID(clazz, "endCdata", "()V");
1328     if (endCdataMethod == NULL) return;
1329 
1330     startDtdMethod = env->GetMethodID(clazz, "startDtd",
1331         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
1332     if (startDtdMethod == NULL) return;
1333 
1334     endDtdMethod = env->GetMethodID(clazz, "endDtd", "()V");
1335     if (endDtdMethod == NULL) return;
1336 
1337     startNamespaceMethod = env->GetMethodID(clazz, "startNamespace",
1338         "(Ljava/lang/String;Ljava/lang/String;)V");
1339     if (startNamespaceMethod == NULL) return;
1340 
1341     endNamespaceMethod = env->GetMethodID(clazz, "endNamespace",
1342         "(Ljava/lang/String;)V");
1343     if (endNamespaceMethod == NULL) return;
1344 
1345     processingInstructionMethod = env->GetMethodID(clazz,
1346         "processingInstruction", "(Ljava/lang/String;Ljava/lang/String;)V");
1347     if (processingInstructionMethod == NULL) return;
1348 
1349     handleExternalEntityMethod = env->GetMethodID(clazz,
1350         "handleExternalEntity",
1351         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
1352     if (handleExternalEntityMethod == NULL) return;
1353 
1354     notationDeclMethod = env->GetMethodID(clazz, "notationDecl",
1355             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
1356     if (notationDeclMethod == NULL) return;
1357 
1358     unparsedEntityDeclMethod = env->GetMethodID(clazz, "unparsedEntityDecl",
1359             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
1360     if (unparsedEntityDeclMethod == NULL) return;
1361 
1362     internMethod = env->GetMethodID(JniConstants::GetStringClass(env), "intern", "()Ljava/lang/String;");
1363     if (internMethod == NULL) return;
1364 
1365     // Reference to "".
1366     emptyString = reinterpret_cast<jstring>(env->NewGlobalRef(empty));
1367 }
1368 
1369 static JNINativeMethod parserMethods[] = {
1370     NATIVE_METHOD(ExpatParser, appendString, "(JLjava/lang/String;Z)V"),
1371     NATIVE_METHOD(ExpatParser, appendBytes, "(J[BII)V"),
1372     NATIVE_METHOD(ExpatParser, appendChars, "(J[CII)V"),
1373     NATIVE_METHOD(ExpatParser, cloneAttributes, "(JI)J"),
1374     NATIVE_METHOD(ExpatParser, column, "(J)I"),
1375     NATIVE_METHOD(ExpatParser, createEntityParser, "(JLjava/lang/String;)J"),
1376     NATIVE_METHOD(ExpatParser, initialize, "(Ljava/lang/String;Z)J"),
1377     NATIVE_METHOD(ExpatParser, line, "(J)I"),
1378     NATIVE_METHOD(ExpatParser, release, "(J)V"),
1379     NATIVE_METHOD(ExpatParser, releaseParser, "(J)V"),
1380     NATIVE_METHOD(ExpatParser, staticInitialize, "(Ljava/lang/String;)V"),
1381 };
1382 
1383 static JNINativeMethod attributeMethods[] = {
1384     NATIVE_METHOD(ExpatAttributes, freeAttributes, "(J)V"),
1385     NATIVE_METHOD(ExpatAttributes, getIndexForQName, "(JLjava/lang/String;)I"),
1386     NATIVE_METHOD(ExpatAttributes, getIndex, "(JLjava/lang/String;Ljava/lang/String;)I"),
1387     NATIVE_METHOD(ExpatAttributes, getLocalName, "(JJI)Ljava/lang/String;"),
1388     NATIVE_METHOD(ExpatAttributes, getQName, "(JJI)Ljava/lang/String;"),
1389     NATIVE_METHOD(ExpatAttributes, getURI, "(JJI)Ljava/lang/String;"),
1390     NATIVE_METHOD(ExpatAttributes, getValueByIndex, "(JI)Ljava/lang/String;"),
1391     NATIVE_METHOD(ExpatAttributes, getValueForQName, "(JLjava/lang/String;)Ljava/lang/String;"),
1392     NATIVE_METHOD(ExpatAttributes, getValue, "(JLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
1393 };
register_org_apache_harmony_xml_ExpatParser(JNIEnv * env)1394 void register_org_apache_harmony_xml_ExpatParser(JNIEnv* env) {
1395     jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatParser", parserMethods, NELEM(parserMethods));
1396     jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatAttributes", attributeMethods, NELEM(attributeMethods));
1397 }
1398