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