4747 */
4848public class KmlLayer extends Layer {
4949
50+ private static final int DEFAULT_MAX_KMZ_ENTRY_COUNT = 200 ;
51+ private static final long DEFAULT_MAX_KMZ_UNCOMPRESSED_TOTAL_SIZE = 50 * 1024 * 1024 ; // 50MB
52+
5053 /**
5154 * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
52- *
55+ * <p>
5356 * Constructor may be called on a background thread, as I/O and parsing may be long-running.
5457 *
5558 * @param map GoogleMap object
@@ -60,12 +63,30 @@ public class KmlLayer extends Layer {
6063 */
6164 public KmlLayer (GoogleMap map , int resourceId , Context context )
6265 throws XmlPullParserException , IOException {
63- this (map , context .getResources ().openRawResource (resourceId ), context , new MarkerManager (map ), new PolygonManager (map ), new PolylineManager (map ), new GroundOverlayManager (map ), null , null );
66+ this (map , context .getResources ().openRawResource (resourceId ), context , new MarkerManager (map ), new PolygonManager (map ), new PolylineManager (map ), new GroundOverlayManager (map ), null , null , DEFAULT_MAX_KMZ_ENTRY_COUNT , DEFAULT_MAX_KMZ_UNCOMPRESSED_TOTAL_SIZE );
6467 }
6568
6669 /**
6770 * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
71+ * <p>
72+ * Constructor may be called on a background thread, as I/O and parsing may be long-running.
6873 *
74+ * @param map GoogleMap object
75+ * @param resourceId Raw resource KML or KMZ file
76+ * @param context The Context
77+ * @param maxKmzEntryCount The maximum number of entries a KMZ file can contain.
78+ * @param maxKmzUncompressedTotalSize The maximum size of the uncompressed KMZ file in bytes.
79+ * @throws XmlPullParserException if file cannot be parsed
80+ * @throws IOException if I/O error
81+ */
82+ public KmlLayer (GoogleMap map , int resourceId , Context context , int maxKmzEntryCount , long maxKmzUncompressedTotalSize )
83+ throws XmlPullParserException , IOException {
84+ this (map , context .getResources ().openRawResource (resourceId ), context , new MarkerManager (map ), new PolygonManager (map ), new PolylineManager (map ), new GroundOverlayManager (map ), null , null , maxKmzEntryCount , maxKmzUncompressedTotalSize );
85+ }
86+
87+ /**
88+ * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
89+ * <p>
6990 * Constructor may be called on a background thread, as I/O and parsing may be long-running.
7091 *
7192 * @param map GoogleMap object
@@ -76,14 +97,32 @@ public KmlLayer(GoogleMap map, int resourceId, Context context)
7697 */
7798 public KmlLayer (GoogleMap map , InputStream stream , Context context )
7899 throws XmlPullParserException , IOException {
79- this (map , stream , context , new MarkerManager (map ), new PolygonManager (map ), new PolylineManager (map ), new GroundOverlayManager (map ), null , null );
100+ this (map , stream , context , new MarkerManager (map ), new PolygonManager (map ), new PolylineManager (map ), new GroundOverlayManager (map ), null , null , DEFAULT_MAX_KMZ_ENTRY_COUNT , DEFAULT_MAX_KMZ_UNCOMPRESSED_TOTAL_SIZE );
80101 }
81102
82103 /**
83104 * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
84- *
105+ * <p>
85106 * Constructor may be called on a background thread, as I/O and parsing may be long-running.
86107 *
108+ * @param map GoogleMap object
109+ * @param stream InputStream containing KML or KMZ file
110+ * @param context The Context
111+ * @param maxKmzEntryCount The maximum number of entries a KMZ file can contain.
112+ * @param maxKmzUncompressedTotalSize The maximum size of the uncompressed KMZ file in bytes.
113+ * @throws XmlPullParserException if file cannot be parsed
114+ * @throws IOException if I/O error
115+ */
116+ public KmlLayer (GoogleMap map , InputStream stream , Context context , int maxKmzEntryCount , long maxKmzUncompressedTotalSize )
117+ throws XmlPullParserException , IOException {
118+ this (map , stream , context , new MarkerManager (map ), new PolygonManager (map ), new PolylineManager (map ), new GroundOverlayManager (map ), null , null , maxKmzEntryCount , maxKmzUncompressedTotalSize );
119+ }
120+
121+ /**
122+ * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
123+ * <p>
124+ * Constructor may be called on a background thread, as I/O and parsing may be long-running.
125+ * <p>
87126 * Use this constructor with shared object managers in order to handle multiple layers with
88127 * their own event handlers on the map.
89128 *
@@ -107,14 +146,49 @@ public KmlLayer(GoogleMap map,
107146 GroundOverlayManager groundOverlayManager ,
108147 Renderer .ImagesCache cache )
109148 throws XmlPullParserException , IOException {
110- this (map , context .getResources ().openRawResource (resourceId ), context , markerManager , polygonManager , polylineManager , groundOverlayManager , cache , null );
149+ this (map , context .getResources ().openRawResource (resourceId ), context , markerManager , polygonManager , polylineManager , groundOverlayManager , cache , null , DEFAULT_MAX_KMZ_ENTRY_COUNT , DEFAULT_MAX_KMZ_UNCOMPRESSED_TOTAL_SIZE );
111150 }
112151
113152 /**
114153 * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
115- *
154+ * <p>
116155 * Constructor may be called on a background thread, as I/O and parsing may be long-running.
156+ * <p>
157+ * Use this constructor with shared object managers in order to handle multiple layers with
158+ * their own event handlers on the map.
117159 *
160+ * @param map GoogleMap object
161+ * @param resourceId Raw resource KML or KMZ file
162+ * @param context The Context
163+ * @param markerManager marker manager to create marker collection from
164+ * @param polygonManager polygon manager to create polygon collection from
165+ * @param polylineManager polyline manager to create polyline collection from
166+ * @param groundOverlayManager ground overlay manager to create ground overlay collection from
167+ * @param cache cache to be used for fetched images
168+ * @param maxKmzEntryCount The maximum number of entries a KMZ file can contain.
169+ * @param maxKmzUncompressedTotalSize The maximum size of the uncompressed KMZ file in bytes.
170+ * @throws XmlPullParserException if file cannot be parsed
171+ * @throws IOException if I/O error
172+ */
173+ public KmlLayer (GoogleMap map ,
174+ @ RawRes int resourceId ,
175+ Context context ,
176+ MarkerManager markerManager ,
177+ PolygonManager polygonManager ,
178+ PolylineManager polylineManager ,
179+ GroundOverlayManager groundOverlayManager ,
180+ Renderer .ImagesCache cache ,
181+ int maxKmzEntryCount ,
182+ long maxKmzUncompressedTotalSize )
183+ throws XmlPullParserException , IOException {
184+ this (map , context .getResources ().openRawResource (resourceId ), context , markerManager , polygonManager , polylineManager , groundOverlayManager , cache , null , maxKmzEntryCount , maxKmzUncompressedTotalSize );
185+ }
186+
187+ /**
188+ * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
189+ * <p>
190+ * Constructor may be called on a background thread, as I/O and parsing may be long-running.
191+ * <p>
118192 * Use this constructor with shared object managers in order to handle multiple layers with
119193 * their own event handlers on the map.
120194 *
@@ -138,14 +212,49 @@ public KmlLayer(GoogleMap map,
138212 GroundOverlayManager groundOverlayManager ,
139213 Renderer .ImagesCache cache )
140214 throws XmlPullParserException , IOException {
141- this (map , stream , context , markerManager , polygonManager , polylineManager , groundOverlayManager , cache , null );
215+ this (map , stream , context , markerManager , polygonManager , polylineManager , groundOverlayManager , cache , null , DEFAULT_MAX_KMZ_ENTRY_COUNT , DEFAULT_MAX_KMZ_UNCOMPRESSED_TOTAL_SIZE );
142216 }
143217
144218 /**
145219 * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
146- *
220+ * <p>
147221 * Constructor may be called on a background thread, as I/O and parsing may be long-running.
222+ * <p>
223+ * Use this constructor with shared object managers in order to handle multiple layers with
224+ * their own event handlers on the map.
148225 *
226+ * @param map GoogleMap object
227+ * @param stream InputStream containing KML or KMZ file
228+ * @param context The Context
229+ * @param markerManager marker manager to create marker collection from
230+ * @param polygonManager polygon manager to create polygon collection from
231+ * @param polylineManager polyline manager to create polyline collection from
232+ * @param groundOverlayManager ground overlay manager to create ground overlay collection from
233+ * @param cache cache to be used for fetched images
234+ * @param maxKmzEntryCount The maximum number of entries a KMZ file can contain.
235+ * @param maxKmzUncompressedTotalSize The maximum size of the uncompressed KMZ file in bytes.
236+ * @throws XmlPullParserException if file cannot be parsed
237+ * @throws IOException if I/O error
238+ */
239+ public KmlLayer (GoogleMap map ,
240+ InputStream stream ,
241+ Context context ,
242+ MarkerManager markerManager ,
243+ PolygonManager polygonManager ,
244+ PolylineManager polylineManager ,
245+ GroundOverlayManager groundOverlayManager ,
246+ Renderer .ImagesCache cache ,
247+ int maxKmzEntryCount ,
248+ long maxKmzUncompressedTotalSize )
249+ throws XmlPullParserException , IOException {
250+ this (map , stream , context , markerManager , polygonManager , polylineManager , groundOverlayManager , cache , null , maxKmzEntryCount , maxKmzUncompressedTotalSize );
251+ }
252+
253+ /**
254+ * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
255+ * <p>
256+ * Constructor may be called on a background thread, as I/O and parsing may be long-running.
257+ * <p>
149258 * Use this constructor with shared object managers in order to handle multiple layers with
150259 * their own event handlers on the map.
151260 *
@@ -171,6 +280,43 @@ public KmlLayer(GoogleMap map,
171280 Renderer .ImagesCache cache ,
172281 @ Nullable KmlUrlSanitizer urlSanitizer )
173282 throws XmlPullParserException , IOException {
283+ this (map , stream , context , markerManager , polygonManager , polylineManager , groundOverlayManager , cache , urlSanitizer , DEFAULT_MAX_KMZ_ENTRY_COUNT , DEFAULT_MAX_KMZ_UNCOMPRESSED_TOTAL_SIZE );
284+ }
285+
286+ /**
287+ * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
288+ * <p>
289+ * Constructor may be called on a background thread, as I/O and parsing may be long-running.
290+ * <p>
291+ * Use this constructor with shared object managers in order to handle multiple layers with
292+ * their own event handlers on the map.
293+ *
294+ * @param map GoogleMap object
295+ * @param stream InputStream containing KML or KMZ file
296+ * @param context The Context
297+ * @param markerManager marker manager to create marker collection from
298+ * @param polygonManager polygon manager to create polygon collection from
299+ * @param polylineManager polyline manager to create polyline collection from
300+ * @param groundOverlayManager ground overlay manager to create ground overlay collection from
301+ * @param cache cache to be used for fetched images
302+ * @param urlSanitizer sanitizer to be used for external URLs
303+ * @param maxKmzEntryCount The maximum number of entries a KMZ file can contain.
304+ * @param maxKmzUncompressedTotalSize The maximum size of the uncompressed KMZ file in bytes.
305+ * @throws XmlPullParserException if file cannot be parsed
306+ * @throws IOException if I/O error
307+ */
308+ public KmlLayer (GoogleMap map ,
309+ InputStream stream ,
310+ Context context ,
311+ MarkerManager markerManager ,
312+ PolygonManager polygonManager ,
313+ PolylineManager polylineManager ,
314+ GroundOverlayManager groundOverlayManager ,
315+ Renderer .ImagesCache cache ,
316+ @ Nullable KmlUrlSanitizer urlSanitizer ,
317+ int maxKmzEntryCount ,
318+ long maxKmzUncompressedTotalSize )
319+ throws XmlPullParserException , IOException {
174320 if (stream == null ) {
175321 throw new IllegalArgumentException ("KML InputStream cannot be null" );
176322 }
@@ -179,16 +325,22 @@ public KmlLayer(GoogleMap map,
179325 BufferedInputStream bis = new BufferedInputStream (stream );
180326 bis .mark (1024 );
181327 ZipInputStream zip = new ZipInputStream (bis );
328+ CountingInputStream countingStream = new CountingInputStream (zip , maxKmzUncompressedTotalSize );
182329 try {
183330 KmlParser parser = null ;
184331 ZipEntry entry = zip .getNextEntry ();
185332 if (entry != null ) { // is a KMZ zip file
333+ int entryCount = 0 ;
186334 HashMap <String , Bitmap > images = new HashMap <>();
187335 while (entry != null ) {
336+ entryCount ++;
337+ if (entryCount > maxKmzEntryCount ) {
338+ throw new IOException ("Zip bomb detected! Max number of entries exceeded: " + maxKmzEntryCount );
339+ }
188340 if (parser == null && entry .getName ().toLowerCase ().endsWith (".kml" )) {
189- parser = parseKml (zip );
341+ parser = parseKml (countingStream );
190342 } else {
191- Bitmap bitmap = BitmapFactory .decodeStream (zip );
343+ Bitmap bitmap = BitmapFactory .decodeStream (countingStream );
192344 if (bitmap != null ) {
193345 images .put (entry .getName (), bitmap );
194346 } else {
@@ -216,6 +368,57 @@ public KmlLayer(GoogleMap map,
216368 }
217369 }
218370
371+ /**
372+ * Wrapper for an InputStream that counts the number of bytes read and throws an IOException
373+ * if the limit is exceeded.
374+ */
375+ private static class CountingInputStream extends InputStream {
376+ private final InputStream mIn ;
377+ private long mTotalBytes = 0 ;
378+ private final long mMaxBytes ;
379+
380+ public CountingInputStream (InputStream in , long maxBytes ) {
381+ mIn = in ;
382+ mMaxBytes = maxBytes ;
383+ }
384+
385+ @ Override
386+ public int read () throws IOException {
387+ int b = mIn .read ();
388+ if (b != -1 ) {
389+ mTotalBytes ++;
390+ checkLimit ();
391+ }
392+ return b ;
393+ }
394+
395+ @ Override
396+ public int read (byte [] b , int off , int len ) throws IOException {
397+ int n = mIn .read (b , off , len );
398+ if (n != -1 ) {
399+ mTotalBytes += n ;
400+ checkLimit ();
401+ }
402+ return n ;
403+ }
404+
405+ @ Override
406+ public long skip (long n ) throws IOException {
407+ long skipped = mIn .skip (n );
408+ if (skipped > 0 ) {
409+ mTotalBytes += skipped ;
410+ checkLimit ();
411+ }
412+ return skipped ;
413+ }
414+
415+ private void checkLimit () throws IOException {
416+ if (mTotalBytes > mMaxBytes ) {
417+ throw new IOException ("Zip bomb detected! Uncompressed size exceeds limit of " + mMaxBytes + " bytes." );
418+ }
419+ }
420+ }
421+
219422 private static KmlParser parseKml (InputStream stream ) throws XmlPullParserException , IOException {
220423 XmlPullParser xmlPullParser = createXmlParser (stream );
221424 KmlParser parser = new KmlParser (xmlPullParser );
0 commit comments