You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中如何校验文件为真实有效MP3而非仅匹配.mp3后缀

MP3文件合法性校验实现方案

原有方案缺陷

当前使用的校验逻辑仅通过文件后缀匹配UTType规则,完全未校验文件本身的二进制内容,只要将任意文件后缀修改为.mp3就会被误判,无法识别伪造后缀的非MP3文件。

校验原理

不需要解码或播放音频,仅通过MP3格式固定的二进制文件头特征即可完成校验,完全匹配「仅确认文件为合法MP3格式、不校验可播放性」的需求:

  • 带ID3v2元数据标签的MP3:文件起始3个字节固定为ASCII字符ID3,对应十六进制值0x49 0x44 0x33,是目前绝大多数MP3文件的头特征
  • 不带ID3v2标签的裸MP3帧:文件起始为MPEG音频帧同步字,第一个字节固定为0xFF,第二个字节高3位固定为111(即第二个字节与0xE0做位与运算结果等于0xE0)
    校验时仅需要读取文件前10个字节即可完成判断,不需要加载整个文件,性能开销极低。

修正后代码

替换原有的isValidMP3函数,同时优化目录遍历逻辑,修复沙盒环境下的文件访问权限问题、路径拼接不规范问题:

import SwiftUI
import UniformTypeIdentifiers

struct ContentView: View {
    @State private var fileImporterIsPresented: Bool = false
    var body: some View {
        
        Button("Select your Folder") { fileImporterIsPresented = true }
            .fileImporter(isPresented: $fileImporterIsPresented, allowedContentTypes: [.folder], allowsMultipleSelection: false, onCompletion: { result in
                
                switch result {
                case .success(let urls):
                    
                    if let folderURL = urls.first {
                        // 沙盒环境下必须先获取目录访问权限
                        guard folderURL.startAccessingSecurityScopedResource() else {
                            print("No permission to access folder")
                            return
                        }
                        defer {
                            folderURL.stopAccessingSecurityScopedResource()
                        }
                        
                        if let contents = try? FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil) {
                            contents.forEach { fileURL in
                                if isValidMP3(filePath: fileURL.path) {
                                    print(fileURL.lastPathComponent)
                                }
                            }
                        }
                        
                    }
                    
                case .failure(let error):
                    print("Error selecting file \(error.localizedDescription)")
                }
                
            })
        
    }
}


func isValidMP3(filePath: String) -> Bool {
    let fileURL = URL(fileURLWithPath: filePath)
    // 过滤目录,仅校验普通文件
    var isDirectory: ObjCBool = false
    guard FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory),
          !isDirectory.boolValue else {
        return false
    }
    // 后缀预过滤,减少不必要的文件IO操作
    guard let utType = UTType(filenameExtension: fileURL.pathExtension),
          utType == .mp3 else {
        return false
    }
    // 读取文件前10字节校验头特征
    guard let fileHandle = try? FileHandle(forReadingFrom: fileURL) else {
        return false
    }
    defer {
        try? fileHandle.close()
    }
    let headerData = fileHandle.readData(ofLength: 10)
    guard headerData.count >= 3 else {
        return false
    }
    let headerBytes = [UInt8](headerData)
    // 匹配ID3v2头
    if headerBytes[0] == 0x49, headerBytes[1] == 0x44, headerBytes[2] == 0x33 {
        return true
    }
    // 匹配裸MPEG帧同步字
    if headerBytes[0] == 0xFF, (headerBytes[1] & 0xE0) == 0xE0 {
        return true
    }
    return false
}

补充说明

  • 校验逻辑不会判断MP3文件是否损坏、是否能正常播放,仅确认文件符合MP3格式的二进制结构要求
  • 后缀预过滤逻辑可以大幅减少需要读取文件的数量,遍历大文件夹时性能更好
  • 新增的安全作用域访问逻辑,解决了macOS/iOS沙盒环境下选中文件夹后无法读取内部文件的隐藏问题

内容的提问来源于stack exchange,提问作者swiftPunk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:57:22