(hostname)
| 721 | } |
| 722 | |
| 723 | function buildDNSQuery(hostname) { |
| 724 | const normalizedHost = (() => { |
| 725 | const trimmed = hostname.trim().replace(/\.$/, ''); |
| 726 | try { return new URL(`http://${trimmed}`).hostname; } |
| 727 | catch { return trimmed; } |
| 728 | })(); |
| 729 | |
| 730 | const labels = normalizedHost.split('.').filter(Boolean); |
| 731 | if (labels.length === 0) throw new Error('Invalid hostname for DNS query'); |
| 732 | |
| 733 | const header = new Uint8Array(12); |
| 734 | if (typeof crypto !== 'undefined' && crypto.getRandomValues) { |
| 735 | crypto.getRandomValues(header.subarray(0, 2)); |
| 736 | } else { |
| 737 | const txid = Math.floor(Math.random() * 0xffff); |
| 738 | header[0] = (txid >> 8) & 0xff; |
| 739 | header[1] = txid & 0xff; |
| 740 | } |
| 741 | header[2] = 0x01; |
| 742 | header[5] = 0x01; |
| 743 | |
| 744 | const qnameParts = []; |
| 745 | labels.forEach(label => { |
| 746 | const bytes = Array.from(label).map(char => { |
| 747 | const code = char.charCodeAt(0); |
| 748 | if (code > 0x7f) throw new Error('Hostname must be ASCII/punycode encoded'); |
| 749 | return code; |
| 750 | }); |
| 751 | if (bytes.length === 0 || bytes.length > 63) throw new Error('Invalid label length in hostname'); |
| 752 | qnameParts.push(bytes.length, ...bytes); |
| 753 | }); |
| 754 | const qname = new Uint8Array(qnameParts.length + 1); |
| 755 | qname.set(qnameParts); |
| 756 | |
| 757 | const typeAndClass = new Uint8Array([0x00, 0x01, 0x00, 0x01]); |
| 758 | const query = new Uint8Array(header.length + qname.length + typeAndClass.length); |
| 759 | let offset = 0; |
| 760 | query.set(header, offset); offset += header.length; |
| 761 | query.set(qname, offset); offset += qname.length; |
| 762 | query.set(typeAndClass, offset); |
| 763 | return query; |
| 764 | } |
| 765 | |
| 766 | function encodeDnsQueryBase64Url(query) { |
| 767 | const binary = Array.from(query, byte => String.fromCharCode(byte)).join(''); |
no outgoing calls
no test coverage detected