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