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 { 83 uninit(); 84 mParser.mEventCode = START_DOCUMENT; 85 86 if (!isTruthy(data) || !isTruthy(size)) { 87 return (mError=BAD_TYPE); 88 } 89 90 if (copyData) { 91 mOwnedData = new byte[size]; 92 // if (mOwnedData == null) { 93 // return (mError=NO_MEMORY); 94 // } 95 // memcpy(mOwnedData, data, size); 96 System.arraycopy(data, 0, mOwnedData, 0, size); 97 data = mOwnedData; 98 } 99 100 mBuffer = new XmlBuffer(data); 101 mHeader = new ResXMLTree_header(mBuffer.buf, 0); 102 mSize = dtohl(mHeader.header.size); 103 if (dtohs(mHeader.header.headerSize) > mSize || mSize > size) { 104 ALOGW("Bad XML block: header size %d or total size %d is larger than data size %d\n", 105 (int)dtohs(mHeader.header.headerSize), 106 (int)dtohl(mHeader.header.size), (int)size); 107 mError = BAD_TYPE; 108 mParser.restart(); 109 return mError; 110 } 111 // mDataEnd = ((final uint8_t*)mHeader) + mSize; 112 mDataLen = mSize; 113 114 mStrings.uninit(); 115 mRootNode = null; 116 mResIds = null; 117 mNumResIds = 0; 118 119 // First look for a couple interesting chunks: the string block 120 // and first XML node. 121 ResChunk_header chunk = 122 // (final ResChunk_header*)(((final uint8_t*)mHeader) + 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)*/ < (mDataLen- ResChunk_header.SIZEOF /*sizeof(ResChunk_header)*/) && 127 chunk.myOffset() /*((final uint8_t*)chunk)*/ < (mDataLen-dtohl(chunk.size))) { 128 int err = validate_chunk(chunk, ResChunk_header.SIZEOF /*sizeof(ResChunk_header)*/, mDataLen, "XML"); 129 if (err != NO_ERROR) { 130 mError = err; 131 // goto done; 132 mParser.restart(); 133 return mError; 134 } 135 final short type = dtohs(chunk.type); 136 final int size1 = dtohl(chunk.size); 137 if (kDebugXMLNoisy) { 138 // System.out.println(String.format("Scanning @ %s: type=0x%x, size=0x%zx\n", 139 // (void*)(((uintptr_t)chunk)-((uintptr_t)mHeader)), type, size1); 140 } 141 if (type == RES_STRING_POOL_TYPE) { 142 mStrings.setTo(mBuffer.buf, chunk.myOffset(), size, false); 143 } else if (type == RES_XML_RESOURCE_MAP_TYPE) { 144 // mResIds = (final int*) 145 // (((final uint8_t*)chunk)+dtohs(chunk.headerSize())); 146 mNumResIds = (dtohl(chunk.size)-dtohs(chunk.headerSize))/SIZEOF_INT /*sizeof(int)*/; 147 mResIds = new int[mNumResIds]; 148 for (int i = 0; i < mNumResIds; i++) { 149 mResIds[i] = mBuffer.buf.getInt(chunk.myOffset() + chunk.headerSize + i * SIZEOF_INT); 150 } 151 } else if (type >= RES_XML_FIRST_CHUNK_TYPE 152 && type <= RES_XML_LAST_CHUNK_TYPE) { 153 if (validateNode(new ResXMLTree_node(mBuffer.buf, chunk)) != NO_ERROR) { 154 mError = BAD_TYPE; 155 // goto done; 156 mParser.restart(); 157 return mError; 158 } 159 mParser.mCurNode = new ResXMLTree_node(mBuffer.buf, lastChunk.myOffset()); 160 if (mParser.nextNode() == BAD_DOCUMENT) { 161 mError = BAD_TYPE; 162 // goto done; 163 mParser.restart(); 164 return mError; 165 } 166 mRootNode = mParser.mCurNode; 167 mRootExt = mParser.mCurExt; 168 mRootCode = mParser.mEventCode; 169 break; 170 } else { 171 if (kDebugXMLNoisy) { 172 System.out.println("Skipping unknown chunk!\n"); 173 } 174 } 175 lastChunk = chunk; 176 // chunk = (final ResChunk_header*) 177 // (((final uint8_t*)chunk) + size1); 178 chunk = new ResChunk_header(mBuffer.buf, chunk.myOffset() + size1); 179 } 180 181 if (mRootNode == null) { 182 ALOGW("Bad XML block: no root element node found\n"); 183 mError = BAD_TYPE; 184 // goto done; 185 mParser.restart(); 186 return mError; 187 } 188 189 mError = mStrings.getError(); 190 191 done: 192 mParser.restart(); 193 return mError; 194 } 195 getError()196 public int getError() 197 { 198 return mError; 199 } 200 uninit()201 void uninit() 202 { 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 { 214 final short eventCode = dtohs(node.header.type); 215 216 int err = validate_chunk( 217 node.header, SIZEOF_RESXMLTREE_NODE /*sizeof(ResXMLTree_node)*/, 218 mDataLen, "ResXMLTree_node"); 219 220 if (err >= NO_ERROR) { 221 // Only perform additional validation on START nodes 222 if (eventCode != RES_XML_START_ELEMENT_TYPE) { 223 return NO_ERROR; 224 } 225 226 final short headerSize = dtohs(node.header.headerSize); 227 final int size = dtohl(node.header.size); 228 // final ResXMLTree_attrExt attrExt = (final ResXMLTree_attrExt*) 229 // (((final uint8_t*)node) + headerSize); 230 ResXMLTree_attrExt attrExt = new ResXMLTree_attrExt(mBuffer.buf, node.myOffset() + headerSize); 231 // check for sensical values pulled out of the stream so far... 232 if ((size >= headerSize + SIZEOF_RESXMLTREE_ATTR_EXT /*sizeof(ResXMLTree_attrExt)*/) 233 && (attrExt.myOffset() > node.myOffset())) { 234 final int attrSize = ((int)dtohs(attrExt.attributeSize)) 235 * dtohs(attrExt.attributeCount); 236 if ((dtohs(attrExt.attributeStart)+attrSize) <= (size-headerSize)) { 237 return NO_ERROR; 238 } 239 ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n", 240 (int)(dtohs(attrExt.attributeStart)+attrSize), 241 (int)(size-headerSize)); 242 } 243 else { 244 ALOGW("Bad XML start block: node header size 0x%x, size 0x%x\n", 245 (int)headerSize, (int)size); 246 } 247 return BAD_TYPE; 248 } 249 250 return err; 251 252 // if (false) { 253 // final boolean isStart = dtohs(node.header().type()) == RES_XML_START_ELEMENT_TYPE; 254 // 255 // final short headerSize = dtohs(node.header().headerSize()); 256 // final int size = dtohl(node.header().size()); 257 // 258 // if (headerSize >= (isStart ? sizeof(ResXMLTree_attrNode) : sizeof(ResXMLTree_node))) { 259 // if (size >= headerSize) { 260 // if ((( final uint8_t*)node) <=(mDataEnd - size)){ 261 // if (!isStart) { 262 // return NO_ERROR; 263 // } 264 // if ((((int) dtohs(node.attributeSize)) * dtohs(node.attributeCount)) 265 // <= (size - headerSize)) { 266 // return NO_ERROR; 267 // } 268 // ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n", 269 // ((int) dtohs(node.attributeSize)) * dtohs(node.attributeCount), 270 // (int) (size - headerSize)); 271 // return BAD_TYPE; 272 // } 273 // ALOGW("Bad XML block: node at 0x%x extends beyond data end 0x%x\n", 274 // (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),(int) mSize); 275 // return BAD_TYPE; 276 // } 277 // ALOGW("Bad XML block: node at 0x%x header size 0x%x smaller than total size 0x%x\n", 278 // (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)), 279 // (int) headerSize, (int) size); 280 // return BAD_TYPE; 281 // } 282 // ALOGW("Bad XML block: node at 0x%x header size 0x%x too small\n", 283 // (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)), 284 // (int) headerSize); 285 // return BAD_TYPE; 286 // } 287 } 288 getStrings()289 public ResStringPool getStrings() { 290 return mParser.getStrings(); 291 } 292 293 static class XmlBuffer { 294 final ByteBuffer buf; 295 XmlBuffer(byte[] data)296 public XmlBuffer(byte[] data) { 297 this.buf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); 298 } 299 } 300 } 301