JS 把 img 元素转 Base64:canvas + 跨域 tainted 问题

前端要把一张 <img> 转成 Base64——通常是为了上传、缓存、内联到别的地方。最直接的方式是 canvas。 基础版:canvas.toDataURL function imgToBase64(img, type = "image/png") { const canvas = document.createElement("canvas"); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); return canvas.toDataURL(type); }const img = document.querySelector("img"); img.onload = () => console.log(imgToBase64(img));关键点:用 naturalWidth/naturalHeight,不是 width/height(后者是显示尺寸,会被 CSS 缩放) 图片必须加载完成才能 draw,判 img.complete 或者监听 onload type 可选 image/png、image/jpeg、image/webp JPEG 可以传第二个参数控制画质:canvas.toDataURL("image/jpeg", 0.8)只有 URL 没有 img 元素 async function urlToBase64(url) { const img = new Image(); img.crossOrigin = "anonymous"; // 关键,让 canvas 不 tainted img.src = url; await new Promise((res, rej) => { img.onload = res; img.onerror = rej; }); const canvas = document.createElement("canvas"); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; canvas.getContext("2d").drawImage(img, 0, 0); return canvas.toDataURL("image/png"); }跨域的 tainted 坑 跨域图片经 canvas 一处理,toDataURL 直接抛: SecurityError: Tainted canvases may not be exported原因:canvas 一旦画了跨域资源就"被污染",浏览器不让你导出数据(防止读取用户其它站的私有图片)。 要出数据,需要满足:图片服务器返回 Access-Control-Allow-Origin: *(或匹配你的域) <img> 元素设置 crossOrigin="anonymous"如果对方服务器没配 CORS,只能:走自己的 Nginx 反代加 CORS 头 fetch + blob + FileReader 绕开 canvas更稳的方案:fetch + blob + FileReader async function urlToBase64(url) { const res = await fetch(url); const blob = await res.blob(); return await new Promise(resolve => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }); }返回的就是: data:image/png;base64,iVBORw0KGgoAAAANS...优势:不经过 canvas,没有 tainted 问题 支持任何图片格式,包括 GIF 动图(canvas 会把动图变成第一帧) 保持原始质量,不会重新压缩但 fetch 依然受 CORS 约束——服务器不配 CORS 时仍然会失败,只是不再看到"tainted"的错误,而是看到网络请求被拦。 想跳过 CORS 的最后一招 在自己的服务端加个代理: 浏览器 → your-server/proxy?url=xxx → 目标图片服务端拿到图片,加上 CORS 头再回给浏览器。前端就走同源了。 Base64 有多大 Base64 编码后体积变大约 33%。10 KB 的图变 13.3 KB 左右。做本地缓存 / 内联小图(favicon、图标)合适;大图别用 Base64,走 URL 更省。 一句话总结 canvas.toDataURL 一行搞定,跨域首选 fetch → blob → FileReader。用 canvas 就得先解决 CORS,别硬撞 tainted 报错。

JavaScript 将 img 元素导出为 Base64:canvas.toDataURL 与跨域处理

基础方法:canvas.toDataURL 将页面中已有的 <img> 元素转为 Base64: function imgToBase64(img, type = 'image/png') { const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); return canvas.toDataURL(type); // 'data:image/png;base64,...' }// 等待图片加载后调用 const img = document.querySelector('img'); img.onload = () => { const base64 = imgToBase64(img); console.log(base64); };// 图片已加载时直接调用 if (img.complete) { const base64 = imgToBase64(img); }naturalWidth/naturalHeight 是图片的原始尺寸,避免取到 CSS 设置的显示尺寸。 跨域图片:crossOrigin='anonymous' 图片来自不同域名时,需要设置 crossOrigin 才能在 canvas 中使用: async function urlToBase64(url) { const img = new Image(); img.crossOrigin = 'anonymous'; img.src = url; await new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; }); const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; canvas.getContext('2d').drawImage(img, 0, 0); return canvas.toDataURL('image/png'); }服务端必须在响应头中返回 Access-Control-Allow-Origin: *,否则 canvas 被标记为"污染(tainted)",调用 toDataURL() 会报错: SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.跨域无 CORS 头:fetch + FileReader 当目标服务器没有设置 CORS 头时,可以走服务端代理,或在同源页面中用 fetch 请求(如果存在跨域会被拦截)。另一种方案是在已有权限的上下文(如油猴脚本设置 @grant GM_xmlhttpRequest)中绕过同源限制: // 同源或有权限的上下文 async function urlToBase64ViaFetch(url) { const res = await fetch(url); const blob = await res.blob(); return new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }); }指定输出格式和质量 canvas.toDataURL('image/png'); // PNG,无损 canvas.toDataURL('image/jpeg', 0.8); // JPEG,质量 80% canvas.toDataURL('image/webp', 0.9); // WebP(非所有浏览器支持)JPEG 不支持透明通道,带透明背景的图片转 JPEG 后透明部分会变黑,建议用 PNG。

tomcat:7.0-jre7 镜像不存在了?三条替代路

抄一份老教程写 Dockerfile: FROM tomcat:7.0-jre7Error response from daemon: manifest for tomcat:7.0-jre7 not found不是拼错了——Docker Hub 官方镜像库把老 tag 清掉了,尤其 Java 7 / Tomcat 7 / 旧 Debian 基础镜像这些 EOL 很久的组合。 现状 Docker Hub 上现在能拉到的 Tomcat 7: docker pull tomcat:7.0.109-jdk8 # 最后一个 7.x + JDK 8 docker pull tomcat:7.0.109-jdk8-openjdk docker pull tomcat:7.0.103 # 老一点的备选拉不到的:所有 -jre7 tag。Java 7 的 openjdk 基础镜像早就下架,官方 tomcat 镜像自然也不能再基于它构建。 方案一:换用 JDK 8(推荐) 绝大多数 Servlet 2.5 项目 JDK 8 兼容良好: FROM tomcat:7.0.109-jdk8COPY target/myapp.war /usr/local/tomcat/webapps/ROOT.warEXPOSE 8080 CMD ["catalina.sh", "run"]大多数老项目这样跑就好。只有极少数依赖 Java 7 特有行为的(比如 SSL cipher 顺序、某些反射细节),才需要坚持 Java 7。 方案二:自己构建 JRE 7 + Tomcat 7 镜像 必须坚持 Java 7 的情况,自己拼一个。Alpine 有历史包: FROM alpine:3.10# Java 7 需要 32-bit 兼容层 RUN apk add --no-cache openjdk7-jre wget tarENV TOMCAT_VERSION=7.0.109 ENV CATALINA_HOME=/usr/local/tomcat ENV PATH=$CATALINA_HOME/bin:$PATHRUN wget -q https://archive.apache.org/dist/tomcat/tomcat-7/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz \ && tar -xzf apache-tomcat-${TOMCAT_VERSION}.tar.gz -C /usr/local \ && mv /usr/local/apache-tomcat-${TOMCAT_VERSION} $CATALINA_HOME \ && rm apache-tomcat-${TOMCAT_VERSION}.tar.gzEXPOSE 8080 CMD ["catalina.sh", "run"]Alpine 3.10 的 openjdk7-jre 包还能装。更新的 Alpine 都下架了 Java 7。 或者用 Eclipse Temurin 归档(Adoptium 前身): FROM eclipse-temurin:7-jre-focal但目前 Adoptium 也不再维护 7.x 镜像,能拉到什么看运气。 方案三:第三方社区镜像 Docker Hub 上有些几年没维护的社区镜像还挂着: docker pull consol/tomcat-7.0这个镜像里是 Tomcat 7 + OpenJDK 7。缺点:9 年没更新,安全性堪忧 只适合内网 / 本地测试 生产用必须盯着 CVE我的推荐路线 按业务需求排序:能升 JDK 8 就直接升——tomcat:7.0.109-jdk8,80% 情况没兼容问题 必须 Java 7 且是内网/开发环境——用 Alpine 自建 Dockerfile 老 DSpace / 学术软件之类死绑 Java 7 的项目——尝试 consol/tomcat-7.0 或者自己拼迁移信号 看到这些错误就是要升级 JDK 了: Unsupported major.minor version 52.0 # class 是 JDK 8 编译的,JRE 7 跑不了反过来: Unsupported major.minor version 51.0 # class 是 JDK 7 编译的,JRE 6 跑不了老 JAR 里 class 版本对应表:Java 版本 class versionJava 6 50Java 7 51Java 8 52Java 11 55Java 17 61Java 21 65一句话总结 tomcat:7.0-jre7 已经从官方镜像库消失。首选 tomcat:7.0.109-jdk8,非要 Java 7 就自己拼 Alpine 镜像,社区镜像只做兜底。

Servlet 规范与 Tomcat 版本对照表 + JDK 兼容性

维护老 SSM/SSH 项目常遇到——web.xml 里 version="2.5",跑起来应该配哪个 Tomcat?JDK 8 能跑吗?先给对照表: Servlet 与 Tomcat 版本对照Servlet 版本 Tomcat 版本 官方支持 JDKServlet 2.4 Tomcat 5.0/5.5 Java 1.4 / 5Servlet 2.5 Tomcat 6.x Java 5 / 6Servlet 3.0 Tomcat 7.x Java 6 / 7Servlet 3.1 Tomcat 8.0 Java 7 / 8Servlet 3.1 Tomcat 8.5 Java 7 / 8Servlet 4.0 Tomcat 9.x Java 8+Jakarta Servlet 5 Tomcat 10.x Java 8+Jakarta Servlet 6 Tomcat 11.x Java 17+Tomcat 10 是分水岭——包名从 javax.servlet.* 改到 jakarta.servlet.*。老代码原封不动放 Tomcat 10 直接编译不过。 判断项目该用哪个 打开 web.xml,看头部: <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> </web-app>version="2.5" 就是 Servlet 2.5,对应 Tomcat 6+。 JDK 8 能跑 Servlet 2.5 项目吗 能。稳定组合:项 推荐Servlet 规范 2.5(保持 web.xml 不变)Tomcat 7.0.109(最后一个 7.x)JDK 1.8Spring 3.x / 4.xMyBatis 3.x理由:Tomcat 7 向下兼容 Servlet 2.5(Tomcat 是超集) Tomcat 7 完整支持 JDK 8,无 PermGen 问题 Tomcat 6 对 JDK 8 支持不完整,从 6.0.53 才勉强能跑,还是别用了迁移 Tomcat 6 到 Tomcat 7 要改什么 九成情况什么都不用改。真要改的坑: 1. web.xml schema 版本:可以不动。Tomcat 7 依然接受 web-app version="2.5"。 2. JSP 的 EL 表达式:Tomcat 7 默认 EL 2.2、Tomcat 6 是 EL 2.1。有些字符串比较写法(比如 ${x eq null})在两版本略有差异,一般不影响。 3. 端口 / 编码:server.xml 里 Connector 改: <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" URIEncoding="UTF-8" redirectPort="8443" />URIEncoding="UTF-8" 治所有 GET 参数中文乱码。 4. session:Tomcat 7 默认关闭了 sessionId URL 追加(;jsessionid=xxx)。老 JSP 依赖 <c:url> 处理会不同,一般不影响。 Maven 里跑 Tomcat:tomcat7-maven-plugin 不装 Tomcat 也能跑,用 Maven 插件: <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/</path> <uriEncoding>UTF-8</uriEncoding> </configuration> </plugin> </plugins> </build>跑: mvn tomcat7:run这个插件已经没维护 10 多年了,但对 Servlet 2.5 老项目照样能跑。生产别用,本地开发够。 Tomcat 8/9 呢 Servlet 2.5 项目理论上放 Tomcat 8/9 也能跑(javax.servlet 包名不变)。但会遇到:老 Spring 3.x 和 Tomcat 8 的 WebSocket 冲突 一些 EL 2.2 / 3.0 的表达式行为差异 JSP 编译器更严格迁移建议:如果项目还在开发新功能,直接升到 Tomcat 9 + JDK 8 + Servlet 3.x(web.xml 头改一下);只是维护,稳留 Tomcat 7 + JDK 8。 Tomcat 10+ 的 jakarta 大坑 老代码: import javax.servlet.http.HttpServletRequest;Tomcat 10 只认: import jakarta.servlet.http.HttpServletRequest;想迁移,用官方工具批量替换: # Eclipse Transformer java -jar org.eclipse.transformer.cli-*.jar \ --input old.war --output new.war或者手工找替换: find . -name "*.java" -exec sed -i 's/javax\.servlet/jakarta.servlet/g' {} +改完还要处理 Spring 版本——Spring 5.x 只支持 javax.servlet,要 jakarta 得升到 Spring 6.x(同时 JDK 17+)。这是个大工程,不是小改。 一句话总结 老 Servlet 2.5 项目最稳的组合是 Tomcat 7.0.109 + JDK 8。Tomcat 10 起改包名 jakarta,迁移是大工程。开发用 tomcat7-maven-plugin 快。

Python str.translate 自定义 Base64:字符替换编解码原理

在逆向接口参数时,经常遇到这类代码: b64 = base64.b64encode(data.encode("utf-8")).decode("ascii") return b64.translate(TRANS)b64.translate(TRANS) 是 Python 字符串的内置 translate() 方法,不是某个库的 API。 translate() 做了什么 先 Base64 编码,再按照 TRANS 映射表替换字符。 translate() 一次可以替换多个字符,比链式 replace() 更高效: TRANS = str.maketrans({ "+": "-", "/": "_", })"a+b/c=".translate(TRANS) # → "a-b_c="TRANS 的常见形式 替换少量字符 TRANS = str.maketrans({ "+": "-", "/": "_", })这是标准的 URL Safe Base64 变体(RFC 4648),把 +/ 换成 -_,避免 URL 编码问题。 整体字符映射(自定义 Base64) src = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" dst = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm0123456789-_"TRANS = str.maketrans(src, dst)这是在对整个 Base64 字符集做轮换映射,常用于接口参数混淆。 解码:把映射反过来 编码是 src → dst,解码就是 dst → src: REVERSE_TRANS = str.maketrans(dst, src)完整编解码实现 import base64src = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" dst = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm0123456789-_"TRANS = str.maketrans(src, dst) REVERSE_TRANS = str.maketrans(dst, src)def encode(data: str) -> str: b64 = base64.b64encode(data.encode()).decode() return b64.translate(TRANS)def decode(data: str) -> str: raw = data.translate(REVERSE_TRANS) return base64.b64decode(raw).decode()x = encode("hello world") print(x) # 输出混淆后的字符串 print(decode(x)) # → hello world简单的 URL Safe Base64 变体 如果 TRANS 只替换 +/=,编解码更简单: import base64def url_safe_encode(data: str) -> str: b64 = base64.b64encode(data.encode()).decode() return b64.translate(str.maketrans({"+": "-", "/": "_", "=": ""}))def url_safe_decode(data: str) -> str: # 补齐 padding padded = data + "=" * (-len(data) % 4) raw = padded.translate(str.maketrans({"-": "+", "_": "/"})) return base64.b64decode(raw).decode()逆向时如何确认 TRANS 内容 遇到 b64.translate(TRANS) 时,找到 TRANS 的定义:如果是 str.maketrans(src, dst):把 dst 作为新的 src,src 作为新的 dst,即可反向还原 如果是 str.maketrans({...}):把字典的键值互换即可很多登录接口、签名参数都会用这种方式,目的是避免 +/= 在 URL 中被转义,或者简单混淆防止直接调用。