<!DOCTYPE html> <html lang="zh-cmn-Has-ZN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"> <meta name="description" content="Dropper"> <title>Dropper</title> <link rel="icon" type="image/x-icon" href="//qiniu.yuhuofei.it/favicon.ico"> <link rel="stylesheet" href="https://cdn.staticfile.org/bulma/0.9.4/css/bulma.min.css"> <script src="//cdn.staticfile.org/crypto-js/4.1.1/crypto-js.min.js"></script> <script src="//cdn.staticfile.org/qiniu-js/3.4.1/qiniu.min.js"></script> <style> html, body { margin: 0; width: 100vw; height: 100%; overflow: hidden; position: relative; font-family: "Helvetica Neue", sans-serif; user-select: none; background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); background-size: 400% 400%; animation: gradient 12s ease infinite; } @keyframes gradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .logo { padding: 20px; color: #fff; font-weight: 200; letter-spacing: 3px; } #settingBtn { position: absolute; right: 0; top: 0; padding: 20px; font-size: 1.2em; transition: transform .5s; box-sizing: content-box; } .setting-wrap:hover #settingBtn { transform: rotate(60deg); } .setting-wrap:hover #setting { right: 0; opacity: 1; } #setting { padding: 20px; position: absolute; z-index: 1000; top: 0; width: 300px; right: -300px; height: 100%; background: #fff; box-shadow: 0 0 40px 0 #999; opacity: 0; transition: .5s; } .upload-wrap { width: 300px; margin: 0 auto; } .upload-box { margin: 12vh 0 60px; padding: 10% 10% 20%; border-radius: 0.8rem; border: 2px dashed rgba(255,255,255, .5); background: rgba(255,255,255, .5); position: relative; text-align: center; transition: 0.3s; width: 100%; } .upload-box:hover { background: rgba(255,255,255, .68); } .upload-box input { cursor: pointer; } #result { height: 0; transition: .5s; overflow: hidden; } </style> </head> <body tabIndex=0> <div class="logo">Dropper</div> <div class="upload-wrap"> <div class="upload-box"> <input id="fileInput" class="file-input" type="file" onclick="value=null"> <p style="font-size: 5em">🗂️</p> <span id='desc'>点击选择 或 拖入文件</span> </div> <div id="shareBox" class="field has-addons"> <div class="control is-expanded"> <div class="select is-fullwidth"> <select id='expire'> <option value="86400">有效期一天</option> <option value="604800">有效期一周</option> <option value="2592000">有效期一月</option> <option value="3153600000">有效期永久</option> </select> </div> </div> <div class="control"> <button id="share" class="button is-link is-light">分享</button> </div> </div> <div id="downloadBox" class="field has-addons" style="display: none"> <div class="control is-expanded"> <input id='secret' class="input" placeholder="输入提取码"> </div> <div class="control"> <button id="download" class="button is-link is-light">下载</button> </div> </div> <div id="result" class="control is-loading"> <textarea id='shareMsg' class="textarea is-link" placeholder="分享生成中..."></textarea> <i class="help">已自动复制到剪切板</i> </div> </div> <div class="setting-wrap"> <span id="settingBtn">⚙️</span> <div id="setting"> <h4>基础参数</h4> <i class="help">以下参数仅保存在本地</i><br> <input id="ak" class="input is-small" name="AK" placeholder="AccessKey"> <input id="sk" class="input is-small" name="SK" placeholder="SecretKey"> <i class="help">访问 <a href="https://portal.qiniu.com/user/key" target="_blank">密钥管理</a> 获取</i> <br> <input id="bucket" class="input is-small" name="BUCKET" placeholder="bucket"> <i class="help">访问 <a href="https://portal.qiniu.com/kodo/bucket" target="_blank">空间管理</a> 创建或选择</i> <br> <i class="help">请在空间设置处将默认首页开启,并将 <a href="" download="index.html">index.html</a> 文件上传至空间</i> <i class="help">有效期及提取码只能相对安全的保护文件,重要文件分享后请到七牛删除</i> <i class="help">使用测试域名请注意<a href="https://developer.qiniu.com/fusion/kb/1319/test-domain-access-restriction-rules" target="_blank">使用规范</a></i> <i class="help"><a href="https://github.com/sciooga/Dropper" target="_blank">❤️ GitHub/Dropper</a></i> </div> </div> <script> var AK = ak.value = localStorage.getItem('AK') var SK = sk.value = localStorage.getItem('SK') var BUCKET = bucket.value = localStorage.getItem('BUCKET') var DOMAIN = location.origin var utf16to8 = function(str) { var out, i, len, c out = "" len = str.length for (i = 0; i < len; i++) { c = str.charCodeAt(i) if ((c >= 0x0001) && (c <= 0x007F)) { out += str.charAt(i) } else if (c > 0x07FF) { out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)) out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)) out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)) } else { out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)) out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)) } } return out } // 生成七牛 upload token var genUpToken = function(bucketName, deadline) { var putPolicy = { "scope": bucketName, "deadline": deadline } var encoded = btoa(utf16to8(JSON.stringify(putPolicy))) var signature = CryptoJS.HmacSHA1(encoded, SK) var encoded_signed = signature.toString(CryptoJS.enc.Base64) encoded_signed = encoded_signed.replace(/\+/g, "-") // url 安全的 base64 encoded_signed = encoded_signed.replace(/\//g, "_") var upload_token = AK + ":" + encoded_signed + ":" + encoded return upload_token } var FILE_NAME, SECRET // 生成分享链接时会用到,通过全局变量传递 var dropHandler = function(e) { if (!AK || !SK || !BUCKET) return alert('请先在右上角配置基础参数') var fileList = e.dataTransfer ? e.dataTransfer.files : e.target.files if (!fileList.length) return if (fileList.length != 1) return alert('只支持上传单个文件') FILE_NAME = fileList[0].name SECRET = Math.random().toString(36).slice(-8) // 生成随机密码 var upToken = genUpToken(BUCKET, 16666666666) // 写死一个超大的日期 var observable = qiniu.upload(fileList[0], `${FILE_NAME}@${SECRET}`, upToken) var subscription = observable.subscribe({ next: function(e) { desc.innerText = e.total.percent.toFixed(2) + '%' }, error: function(e) { alert(e.message) location.reload() }, complete: function(e) { desc.innerText = FILE_NAME } }) } var hash160724 = function(str, salt) { for (var i = 0; i < 160724; i++) { str = CryptoJS.MD5(str + salt).toString() } return str } setting.addEventListener('change', function(e) { localStorage.setItem(e.target.name, e.target.value) window[e.target.name] = e.target.value; }) window.onload = function() { var searchParams = new URL(location.href).searchParams var secretMD5 = searchParams.get('s') var dt = searchParams.get('dt') var fileName = searchParams.get('n') if (fileName) { // 下载模式 desc.innerText = fileName shareBox.style.display = 'none' fileInput.style.display = 'none' downloadBox.style.display = 'flex' download.addEventListener('click', function() { if (dt < new Date) return alert('文件分享到期,无法下载') if (hash160724(secret.value, `${fileName}@${dt}`) != secretMD5) return alert('提取码错误') downloadURL = `${DOMAIN}/${fileName}@${secret.value}?attname=${fileName}` window.open(downloadURL) }) } else { // 上传模式 document.addEventListener("dragover", function(e) { e.preventDefault() }) document.addEventListener("drop", function(e) { e.preventDefault() dropHandler(e) }) fileInput.addEventListener('change', dropHandler) share.addEventListener('click', function() { if (!FILE_NAME) return alert('请上传文件') result.style.height = '200px' setTimeout(function() { var dt = +new Date + expire.value * 1000 var secretMD5 = hash160724(SECRET, `${FILE_NAME}@${dt}`) var resultURL = `${DOMAIN}?s=${secretMD5}&n=${encodeURI(FILE_NAME)}&dt=${dt}` shareMsg.value = `链接: ${resultURL} 提取码: ${SECRET}` result.classList.remove('is-loading') shareMsg.select(); document.execCommand('copy', true); }, 500) }) } } </script> </body> </html>