227 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <script setup lang="ts">
 | |
| import {
 | |
|   createAdler32, // (): Promise<IHasher>
 | |
|   createBLAKE2b, // (bits?: number, key?: IDataType): Promise<IHasher> // default is 512 bits
 | |
|   createBLAKE2s, // (bits?: number, key?: IDataType): Promise<IHasher> // default is 256 bits
 | |
|   createBLAKE3, // (bits?: number, key?: IDataType): Promise<IHasher> // default is 256 bits
 | |
|   createCRC32, // (): Promise<IHasher>
 | |
|   createCRC32C, // (): Promise<IHasher>
 | |
|   createKeccak, // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
 | |
|   createMD4, // (): Promise<IHasher>
 | |
|   createMD5, // (): Promise<IHasher>
 | |
|   createRIPEMD160, // (): Promise<IHasher>
 | |
|   createSHA1, // (): Promise<IHasher>
 | |
|   createSHA224, // (): Promise<IHasher>
 | |
|   createSHA256, // (): Promise<IHasher>
 | |
|   createSHA3, // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
 | |
|   createSHA384, // (): Promise<IHasher>
 | |
|   createSHA512, // (): Promise<IHasher>
 | |
|   createSM3, // (): Promise<IHasher>
 | |
|   createWhirlpool, // (): Promise<IHasher>
 | |
| // createXXHash32, //(seed: number): Promise<IHasher>
 | |
| // createXXHash64, //(seedLow: number, seedHigh: number): Promise<IHasher>
 | |
| // createXXHash3, //(seedLow: number, seedHigh: number): Promise<IHasher>
 | |
| // createXXHash128, //(seedLow: number, seedHigh: number): Promise<IHasher>
 | |
| } from 'hash-wasm';
 | |
| import type { lib } from 'crypto-js';
 | |
| import { enc } from 'crypto-js';
 | |
| 
 | |
| import type { IHasher } from 'hash-wasm/dist/lib/WASMInterface';
 | |
| import InputCopyable from '../../components/InputCopyable.vue';
 | |
| import { convertHexToBin } from '../hash-text/hash-text.service';
 | |
| import { useQueryParamOrStorage } from '@/composable/queryParams';
 | |
| import { withDefaultOnError } from '@/utils/defaults';
 | |
| 
 | |
| const status = ref<'idle' | 'done' | 'error' | 'processing'>('idle');
 | |
| const file = ref<File | null>(null);
 | |
| 
 | |
| async function getHashersAsync() {
 | |
|   return {
 | |
|     adler32: await createAdler32(), // (): Promise<IHasher>
 | |
|     BLAKE2b: await createBLAKE2b(), // (bits?: number, key?: IDataType): Promise<IHasher> // default is 512 bits
 | |
|     BLAKE2s: await createBLAKE2s(), // (bits?: number, key?: IDataType): Promise<IHasher> // default is 256 bits
 | |
|     BLAKE3: await createBLAKE3(), // (bits?: number, key?: IDataType): Promise<IHasher> // default is 256 bits
 | |
|     CRC32: await createCRC32(), // (): Promise<IHasher>
 | |
|     CRC32C: await createCRC32C(), // (): Promise<IHasher>
 | |
|     Keccak_224: await createKeccak(224), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
 | |
|     Keccak_256: await createKeccak(256), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
 | |
|     Keccak_384: await createKeccak(384), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
 | |
|     Keccak_512: await createKeccak(512), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
 | |
|     MD4: await createMD4(), // (): Promise<IHasher>
 | |
|     MD5: await createMD5(), // (): Promise<IHasher>
 | |
|     RIPEMD160: await createRIPEMD160(), // (): Promise<IHasher>
 | |
|     SHA1: await createSHA1(), // (): Promise<IHasher>
 | |
|     SHA224: await createSHA224(), // (): Promise<IHasher>
 | |
|     SHA256: await createSHA256(), // (): Promise<IHasher>
 | |
|     SHA3_224: await createSHA3(224), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
 | |
|     SHA3_256: await createSHA3(256), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
 | |
|     SHA3_384: await createSHA3(384), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
 | |
|     SHA3_512: await createSHA3(512), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
 | |
|     SHA384: await createSHA384(), // (): Promise<IHasher>
 | |
|     SHA512: await createSHA512(), // (): Promise<IHasher>
 | |
|     SM3: await createSM3(), // (): Promise<IHasher>
 | |
|     Whirlpool: await createWhirlpool(), // (): Promise<IHasher>
 | |
|   };
 | |
| }
 | |
| 
 | |
| const defaultHashWasmValues = {
 | |
|   adler32: '',
 | |
|   BLAKE2b: '',
 | |
|   BLAKE2s: '',
 | |
|   BLAKE3: '',
 | |
|   CRC32: '',
 | |
|   CRC32C: '',
 | |
|   Keccak_224: '',
 | |
|   Keccak_256: '',
 | |
|   Keccak_384: '',
 | |
|   Keccak_512: '',
 | |
|   MD4: '',
 | |
|   MD5: '',
 | |
|   RIPEMD160: '',
 | |
|   SHA1: '',
 | |
|   SHA224: '',
 | |
|   SHA256: '',
 | |
|   SHA3_224: '',
 | |
|   SHA3_256: '',
 | |
|   SHA3_384: '',
 | |
|   SHA3_512: '',
 | |
|   SHA384: '',
 | |
|   SHA512: '',
 | |
|   SM3: '',
 | |
|   Whirlpool: '',
 | |
| };
 | |
| const chunkSize = 64 * 1024 * 1024;
 | |
| const fileReader = new FileReader();
 | |
| 
 | |
| async function hashChunkAsync(chunk: Blob, hashers: IHasher[]) {
 | |
|   return new Promise<void>((resolve, _reject) => {
 | |
|     fileReader.onload = async (e) => {
 | |
|       const view = new Uint8Array((e.target?.result as ArrayBuffer)!);
 | |
|       for (const hasher of hashers) {
 | |
|         hasher.update(view);
 | |
|       }
 | |
|       resolve();
 | |
|     };
 | |
| 
 | |
|     fileReader.readAsArrayBuffer(chunk);
 | |
|   });
 | |
| }
 | |
| 
 | |
| type AlgoWasmNames = keyof typeof defaultHashWasmValues;
 | |
| const algoWasmNames = Object.keys(defaultHashWasmValues) as AlgoWasmNames[];
 | |
| 
 | |
| async function hashFileAsync(file: File) {
 | |
|   const chunkNumber = Math.floor(file.size / chunkSize);
 | |
| 
 | |
|   const hashers = await getHashersAsync();
 | |
|   for (let i = 0; i <= chunkNumber; i++) {
 | |
|     const chunk = file.slice(
 | |
|       chunkSize * i,
 | |
|       Math.min(chunkSize * (i + 1), file.size),
 | |
|     );
 | |
|     await hashChunkAsync(chunk, Object.values(hashers));
 | |
|   }
 | |
| 
 | |
|   const ret = { ...defaultHashWasmValues };
 | |
|   for (const algo of algoWasmNames) {
 | |
|     ret[algo as AlgoWasmNames] = hashers[algo as AlgoWasmNames].digest();
 | |
|   }
 | |
|   return Promise.resolve(ret);
 | |
| }
 | |
| 
 | |
| const hashes = ref(defaultHashWasmValues);
 | |
| async function onUpload(uploadedFile: File) {
 | |
|   file.value = uploadedFile;
 | |
| 
 | |
|   status.value = 'processing';
 | |
|   try {
 | |
|     hashes.value = await hashFileAsync(uploadedFile);
 | |
|     status.value = 'done';
 | |
|   }
 | |
|   catch (e) {
 | |
|     status.value = 'error';
 | |
|   }
 | |
| }
 | |
| 
 | |
| type Encoding = keyof typeof enc | 'Bin';
 | |
| 
 | |
| const encoding = useQueryParamOrStorage<Encoding>({ defaultValue: 'Hex', storageName: 'hash-text:encoding', name: 'encoding' });
 | |
| 
 | |
| function formatWithEncoding(words: lib.WordArray, encoding: Encoding) {
 | |
|   if (encoding === 'Bin') {
 | |
|     return convertHexToBin(words.toString(enc.Hex));
 | |
|   }
 | |
| 
 | |
|   return words.toString(enc[encoding]);
 | |
| }
 | |
| 
 | |
| const hashWasmValues = computed(() => withDefaultOnError(() => {
 | |
|   const encodingValue = encoding.value;
 | |
|   const hashesValue = hashes.value;
 | |
| 
 | |
|   const ret = defaultHashWasmValues;
 | |
|   for (const algo of algoWasmNames) {
 | |
|     ret[algo] = formatWithEncoding(enc.Hex.parse(hashesValue[algo]), encodingValue);
 | |
|   }
 | |
|   return ret;
 | |
| }, defaultHashWasmValues));
 | |
| </script>
 | |
| 
 | |
| <template>
 | |
|   <div>
 | |
|     <c-card>
 | |
|       <c-file-upload
 | |
|         title="Drag and drop a file here, or click to select a file"
 | |
|         @file-upload="onUpload"
 | |
|       />
 | |
| 
 | |
|       <n-divider />
 | |
| 
 | |
|       <c-select
 | |
|         v-model:value="encoding"
 | |
|         mb-4
 | |
|         label="Digest encoding"
 | |
|         :options="[
 | |
|           {
 | |
|             label: 'Binary (base 2)',
 | |
|             value: 'Bin',
 | |
|           },
 | |
|           {
 | |
|             label: 'Hexadecimal (base 16)',
 | |
|             value: 'Hex',
 | |
|           },
 | |
|           {
 | |
|             label: 'Base64 (base 64)',
 | |
|             value: 'Base64',
 | |
|           },
 | |
|           {
 | |
|             label: 'Base64url (base 64 with url safe chars)',
 | |
|             value: 'Base64url',
 | |
|           },
 | |
|         ]"
 | |
|       />
 | |
|     </c-card>
 | |
| 
 | |
|     <div mt-3 flex justify-center>
 | |
|       <c-alert v-if="status === 'error'" type="error">
 | |
|         An error occured hashing file '{{ file?.name }}'.
 | |
|       </c-alert>
 | |
|       <n-spin
 | |
|         v-if="status === 'processing'"
 | |
|         size="small"
 | |
|       />
 | |
|     </div>
 | |
| 
 | |
|     <c-card v-if="status === 'done'" :title="`Hashes of ${file?.name}`">
 | |
|       <div v-for="algo in algoWasmNames" :key="algo" style="margin: 5px 0">
 | |
|         <n-input-group>
 | |
|           <n-input-group-label style="flex: 0 0 120px">
 | |
|             {{ algo.toUpperCase() }}
 | |
|           </n-input-group-label>
 | |
|           <InputCopyable :value="hashWasmValues[algo]" readonly />
 | |
|         </n-input-group>
 | |
|       </div>
 | |
|     </c-card>
 | |
|   </div>
 | |
| </template>
 |