@@ -185,7 +185,7 @@ pub async fn get_available_skins() -> crate::Result<Vec<Skin>> {
185
185
// Several custom skins may reuse the same texture for different cape or skin model
186
186
// variations, so check all attributes for correctness
187
187
let is_equipped = !found_equipped_skin. load ( Ordering :: Acquire )
188
- && custom_skin. texture_key == & * current_skin. texture_key ( )
188
+ && custom_skin. texture_key == * current_skin. texture_key ( )
189
189
&& custom_skin. variant == current_skin. variant
190
190
&& custom_skin. cape_id
191
191
== if custom_skin. cape_id . is_some ( ) {
@@ -261,6 +261,11 @@ pub async fn add_and_equip_custom_skin(
261
261
variant : MinecraftSkinVariant ,
262
262
cape_override : Option < Cape > ,
263
263
) -> crate :: Result < ( ) > {
264
+ let ( skin_width, skin_height) = png_dimensions ( & texture_blob) ?;
265
+ if skin_width != 64 || ![ 32 , 64 ] . contains ( & skin_height) {
266
+ return Err ( ErrorKind :: InvalidSkinTexture ) ?;
267
+ }
268
+
264
269
let cape_override = cape_override. map ( |cape| cape. id ) ;
265
270
let state = State :: get ( ) . await ?;
266
271
@@ -270,7 +275,7 @@ pub async fn add_and_equip_custom_skin(
270
275
271
276
// We have to equip the skin first, as it's the Mojang API backend who knows
272
277
// how to compute the texture key we require, which we can then read from the
273
- // updated player profile. This also ensures the skin data is indeed valid
278
+ // updated player profile
274
279
mojang_api:: MinecraftSkinOperation :: equip (
275
280
& selected_credentials,
276
281
stream:: iter ( [ Ok :: < _ , String > ( Bytes :: clone ( & texture_blob) ) ] ) ,
@@ -478,13 +483,15 @@ async fn sync_cape(
478
483
Ok ( ( ) )
479
484
}
480
485
481
- fn texture_blob_to_data_url ( texture_blob : Option < Vec < u8 > > ) -> Arc < Url > {
482
- let data = texture_blob. map_or (
486
+ fn texture_blob_to_data_url ( texture_blob : Vec < u8 > ) -> Arc < Url > {
487
+ let data = if is_png ( & texture_blob) {
488
+ Cow :: Owned ( texture_blob)
489
+ } else {
490
+ // Fall back to a placeholder texture if the DB somehow contains corrupt data
483
491
Cow :: Borrowed (
484
492
& include_bytes ! ( "minecraft_skins/assets/default/MissingNo.png" ) [ ..] ,
485
- ) ,
486
- Cow :: Owned ,
487
- ) ;
493
+ )
494
+ } ;
488
495
489
496
Url :: parse ( & format ! (
490
497
"data:image/png;base64,{}" ,
@@ -493,3 +500,39 @@ fn texture_blob_to_data_url(texture_blob: Option<Vec<u8>>) -> Arc<Url> {
493
500
. unwrap ( )
494
501
. into ( )
495
502
}
503
+
504
+ fn is_png ( data : & [ u8 ] ) -> bool {
505
+ /// The initial 8 bytes of a PNG file, used to identify it as such.
506
+ ///
507
+ /// Reference: <https://www.w3.org/TR/png-3/#3PNGsignature>
508
+ const PNG_SIGNATURE : & [ u8 ] =
509
+ & [ 0x89 , 0x50 , 0x4E , 0x47 , 0x0D , 0x0A , 0x1A , 0x0A ] ;
510
+
511
+ data. starts_with ( PNG_SIGNATURE )
512
+ }
513
+
514
+ fn png_dimensions ( data : & [ u8 ] ) -> crate :: Result < ( u32 , u32 ) > {
515
+ if !is_png ( data) {
516
+ Err ( ErrorKind :: InvalidPng ) ?;
517
+ }
518
+
519
+ // Read the width and height fields from the IHDR chunk, which the
520
+ // PNG specification mandates to be the first in the file, just after
521
+ // the 8 signature bytes. See:
522
+ // https://www.w3.org/TR/png-3/#5DataRep
523
+ // https://www.w3.org/TR/png-3/#11IHDR
524
+ let width = u32:: from_be_bytes (
525
+ data. get ( 16 ..20 )
526
+ . ok_or ( ErrorKind :: InvalidPng ) ?
527
+ . try_into ( )
528
+ . unwrap ( ) ,
529
+ ) ;
530
+ let height = u32:: from_be_bytes (
531
+ data. get ( 20 ..24 )
532
+ . ok_or ( ErrorKind :: InvalidPng ) ?
533
+ . try_into ( )
534
+ . unwrap ( ) ,
535
+ ) ;
536
+
537
+ Ok ( ( width, height) )
538
+ }
0 commit comments