1 package org.robolectric.res.android; 2 3 import static org.robolectric.res.android.Errors.BAD_TYPE; 4 import static org.robolectric.res.android.Errors.NO_ERROR; 5 import static org.robolectric.res.android.Errors.NO_INIT; 6 import static org.robolectric.res.android.ResTable.kDebugResXMLTree; 7 import static org.robolectric.res.android.ResTable.kDebugXMLNoisy; 8 import static org.robolectric.res.android.ResXMLParser.SIZEOF_RESXMLTREE_ATTR_EXT; 9 import static org.robolectric.res.android.ResXMLParser.SIZEOF_RESXMLTREE_NODE; 10 import static org.robolectric.res.android.ResXMLParser.event_code_t.BAD_DOCUMENT; 11 import static org.robolectric.res.android.ResXMLParser.event_code_t.START_DOCUMENT; 12 import static org.robolectric.res.android.ResourceTypes.RES_STRING_POOL_TYPE; 13 import static org.robolectric.res.android.ResourceTypes.RES_XML_FIRST_CHUNK_TYPE; 14 import static org.robolectric.res.android.ResourceTypes.RES_XML_LAST_CHUNK_TYPE; 15 import static org.robolectric.res.android.ResourceTypes.RES_XML_RESOURCE_MAP_TYPE; 16 import static org.robolectric.res.android.ResourceTypes.RES_XML_START_ELEMENT_TYPE; 17 import static org.robolectric.res.android.ResourceTypes.validate_chunk; 18 import static org.robolectric.res.android.Util.ALOGI; 19 import static org.robolectric.res.android.Util.ALOGW; 20 import static org.robolectric.res.android.Util.SIZEOF_INT; 21 import static org.robolectric.res.android.Util.dtohl; 22 import static org.robolectric.res.android.Util.dtohs; 23 import static org.robolectric.res.android.Util.isTruthy; 24 25 import java.nio.ByteBuffer; 26 import java.nio.ByteOrder; 27 import java.util.concurrent.atomic.AtomicInteger; 28 import org.robolectric.res.android.ResourceTypes.ResChunk_header; 29 import org.robolectric.res.android.ResourceTypes.ResXMLTree_attrExt; 30 import org.robolectric.res.android.ResourceTypes.ResXMLTree_header; 31 import org.robolectric.res.android.ResourceTypes.ResXMLTree_node; 32 33 public class ResXMLTree { 34 35 final DynamicRefTable mDynamicRefTable; 36 public final ResXMLParser mParser; 37 38 int mError; 39 byte[] mOwnedData; 40 XmlBuffer mBuffer; 41 ResXMLTree_header mHeader; 42 int mSize; 43 // final uint8_t* mDataEnd; 44 int mDataLen; 45 ResStringPool mStrings = new ResStringPool(); 46 int[] mResIds; 47 int mNumResIds; 48 ResXMLTree_node mRootNode; 49 int mRootExt; 50 int mRootCode; 51 52 static volatile AtomicInteger gCount = new AtomicInteger(0); 53 ResXMLTree(DynamicRefTable dynamicRefTable)54 public ResXMLTree(DynamicRefTable dynamicRefTable) { 55 mParser = new ResXMLParser(this); 56 57 mDynamicRefTable = dynamicRefTable; 58 mError = NO_INIT; 59 mOwnedData = null; 60 61 if (kDebugResXMLTree) { 62 ALOGI("Creating ResXMLTree %s #%d\n", this, gCount.getAndIncrement() + 1); 63 } 64 mParser.restart(); 65 } 66 67 // ResXMLTree() { 68 // this(null); 69 // } 70 71 // ~ResXMLTree() 72 // { 73 @Override finalize()74 protected void finalize() { 75 if (kDebugResXMLTree) { 76 ALOGI("Destroying ResXMLTree in %s #%d\n", this, gCount.getAndDecrement() - 1); 77 } 78 uninit(); 79 } 80 setTo(byte[] data, int size, boolean copyData)81 public int setTo(byte[] data, int size, boolean copyData) { 82 uninit(); 83 mParser.mEventCode = START_DOCUMENT; 84 85 if (!isTruthy(data) || !isTruthy(size)) { 86 return (mError = BAD_TYPE); 87 } 88 89 if (copyData) { 90 mOwnedData = new byte[size]; 91 // if (mOwnedData == null) { 92 // return (mError=NO_MEMORY); 93 // } 94 // memcpy(mOwnedData, data, size); 95 System.arraycopy(data, 0, mOwnedData, 0, size); 96 data = mOwnedData; 97 } 98 99 mBuffer = new XmlBuffer(data); 100 mHeader = new ResXMLTree_header(mBuffer.buf, 0); 101 mSize = dtohl(mHeader.header.size); 102 if (dtohs(mHeader.header.headerSize) > mSize || mSize > size) { 103 ALOGW( 104 "Bad XML block: header size %d or total size %d is larger than data size %d\n", 105 (int) dtohs(mHeader.header.headerSize), (int) dtohl(mHeader.header.size), (int) size); 106 mError = BAD_TYPE; 107 mParser.restart(); 108 return mError; 109 } 110 // mDataEnd = ((final uint8_t*)mHeader) + mSize; 111 mDataLen = mSize; 112 113 mStrings.uninit(); 114 mRootNode = null; 115 mResIds = null; 116 mNumResIds = 0; 117 118 // First look for a couple interesting chunks: the string block 119 // and first XML node. 120 ResChunk_header chunk = 121 // (final ResChunk_header*)(((final uint8_t*)mHeader) + 122 // dtohs(mHeader.header.headerSize)); 123 new ResChunk_header(mBuffer.buf, mHeader.header.headerSize); 124 125 ResChunk_header lastChunk = chunk; 126 while (chunk.myOffset() /*((final uint8_t*)chunk)*/ 127 < (mDataLen - ResChunk_header.SIZEOF /*sizeof(ResChunk_header)*/) 128 && chunk.myOffset() /*((final uint8_t*)chunk)*/ < (mDataLen - dtohl(chunk.size))) { 129 int err = 130 validate_chunk( 131 chunk, ResChunk_header.SIZEOF /*sizeof(ResChunk_header)*/, mDataLen, "XML"); 132 if (err != NO_ERROR) { 133 mError = err; 134 // goto done; 135 mParser.restart(); 136 return mError; 137 } 138 final short type = dtohs(chunk.type); 139 final int size1 = dtohl(chunk.size); 140 if (kDebugXMLNoisy) { 141 // System.out.println(String.format("Scanning @ %s: type=0x%x, size=0x%zx\n", 142 // (void*)(((uintptr_t)chunk)-((uintptr_t)mHeader)), type, size1); 143 } 144 if (type == RES_STRING_POOL_TYPE) { 145 mStrings.setTo(mBuffer.buf, chunk.myOffset(), size, false); 146 } else if (type == RES_XML_RESOURCE_MAP_TYPE) { 147 // mResIds = (final int*) 148 // (((final uint8_t*)chunk)+dtohs(chunk.headerSize())); 149 mNumResIds = (dtohl(chunk.size) - dtohs(chunk.headerSize)) / SIZEOF_INT /*sizeof(int)*/; 150 mResIds = new int[mNumResIds]; 151 for (int i = 0; i < mNumResIds; i++) { 152 mResIds[i] = mBuffer.buf.getInt(chunk.myOffset() + chunk.headerSize + i * SIZEOF_INT); 153 } 154 } else if (type >= RES_XML_FIRST_CHUNK_TYPE && type <= RES_XML_LAST_CHUNK_TYPE) { 155 if (validateNode(new ResXMLTree_node(mBuffer.buf, chunk)) != NO_ERROR) { 156 mError = BAD_TYPE; 157 // goto done; 158 mParser.restart(); 159 return mError; 160 } 161 mParser.mCurNode = new ResXMLTree_node(mBuffer.buf, lastChunk.myOffset()); 162 if (mParser.nextNode() == BAD_DOCUMENT) { 163 mError = BAD_TYPE; 164 // goto done; 165 mParser.restart(); 166 return mError; 167 } 168 mRootNode = mParser.mCurNode; 169 mRootExt = mParser.mCurExt; 170 mRootCode = mParser.mEventCode; 171 break; 172 } else { 173 if (kDebugXMLNoisy) { 174 System.out.println("Skipping unknown chunk!\n"); 175 } 176 } 177 lastChunk = chunk; 178 // chunk = (final ResChunk_header*) 179 // (((final uint8_t*)chunk) + size1); 180 chunk = new ResChunk_header(mBuffer.buf, chunk.myOffset() + size1); 181 } 182 183 if (mRootNode == null) { 184 ALOGW("Bad XML block: no root element node found\n"); 185 mError = BAD_TYPE; 186 // goto done; 187 mParser.restart(); 188 return mError; 189 } 190 191 mError = mStrings.getError(); 192 193 done: 194 mParser.restart(); 195 return mError; 196 } 197 getError()198 public int getError() { 199 return mError; 200 } 201 uninit()202 void uninit() { 203 mError = NO_INIT; 204 mStrings.uninit(); 205 if (isTruthy(mOwnedData)) { 206 // free(mOwnedData); 207 mOwnedData = null; 208 } 209 mParser.restart(); 210 } 211 validateNode(final ResXMLTree_node node)212 int validateNode(final ResXMLTree_node node) { 213 final short eventCode = dtohs(node.header.type); 214 215 int err = 216 validate_chunk( 217 node.header, 218 SIZEOF_RESXMLTREE_NODE /*sizeof(ResXMLTree_node)*/, 219 mDataLen, 220 "ResXMLTree_node"); 221 222 if (err >= NO_ERROR) { 223 // Only perform additional validation on START nodes 224 if (eventCode != RES_XML_START_ELEMENT_TYPE) { 225 return NO_ERROR; 226 } 227 228 final short headerSize = dtohs(node.header.headerSize); 229 final int size = dtohl(node.header.size); 230 // final ResXMLTree_attrExt attrExt = (final ResXMLTree_attrExt*) 231 // (((final uint8_t*)node) + headerSize); 232 ResXMLTree_attrExt attrExt = 233 new ResXMLTree_attrExt(mBuffer.buf, node.myOffset() + headerSize); 234 // check for sensical values pulled out of the stream so far... 235 if ((size >= headerSize + SIZEOF_RESXMLTREE_ATTR_EXT /*sizeof(ResXMLTree_attrExt)*/) 236 && (attrExt.myOffset() > node.myOffset())) { 237 final int attrSize = ((int) dtohs(attrExt.attributeSize)) * dtohs(attrExt.attributeCount); 238 if ((dtohs(attrExt.attributeStart) + attrSize) <= (size - headerSize)) { 239 return NO_ERROR; 240 } 241 ALOGW( 242 "Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n", 243 (int) (dtohs(attrExt.attributeStart) + attrSize), (int) (size - headerSize)); 244 } else { 245 ALOGW( 246 "Bad XML start block: node header size 0x%x, size 0x%x\n", 247 (int) headerSize, (int) size); 248 } 249 return BAD_TYPE; 250 } 251 252 return err; 253 254 // if (false) { 255 // final boolean isStart = dtohs(node.header().type()) == RES_XML_START_ELEMENT_TYPE; 256 // 257 // final short headerSize = dtohs(node.header().headerSize()); 258 // final int size = dtohl(node.header().size()); 259 // 260 // if (headerSize >= (isStart ? sizeof(ResXMLTree_attrNode) : sizeof(ResXMLTree_node))) { 261 // if (size >= headerSize) { 262 // if ((( final uint8_t*)node) <=(mDataEnd - size)){ 263 // if (!isStart) { 264 // return NO_ERROR; 265 // } 266 // if ((((int) dtohs(node.attributeSize)) * dtohs(node.attributeCount)) 267 // <= (size - headerSize)) { 268 // return NO_ERROR; 269 // } 270 // ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n", 271 // ((int) dtohs(node.attributeSize)) * dtohs(node.attributeCount), 272 // (int) (size - headerSize)); 273 // return BAD_TYPE; 274 // } 275 // ALOGW("Bad XML block: node at 0x%x extends beyond data end 0x%x\n", 276 // (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),(int) mSize); 277 // return BAD_TYPE; 278 // } 279 // ALOGW("Bad XML block: node at 0x%x header size 0x%x smaller than total size 0x%x\n", 280 // (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)), 281 // (int) headerSize, (int) size); 282 // return BAD_TYPE; 283 // } 284 // ALOGW("Bad XML block: node at 0x%x header size 0x%x too small\n", 285 // (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)), 286 // (int) headerSize); 287 // return BAD_TYPE; 288 // } 289 } 290 getStrings()291 public ResStringPool getStrings() { 292 return mParser.getStrings(); 293 } 294 295 static class XmlBuffer { 296 final ByteBuffer buf; 297 XmlBuffer(byte[] data)298 public XmlBuffer(byte[] data) { 299 this.buf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); 300 } 301 } 302 } 303