|
| 1 | +# The one string the offline patch hardcodes in Chinese |
| 2 | + |
| 3 | +2026-09-12 |
| 4 | + |
| 5 | +## What users saw |
| 6 | + |
| 7 | +Insert a picture by URL, have the fetch fail -- a 404, a host that does not send |
| 8 | +CORS headers -- and the editor reports: |
| 9 | + |
| 10 | +> 无法加载图片:地址无效或目标站不允许跨域访问(可通过 editorConfig.imageProxy 配置图片代理) |
| 11 | +
|
| 12 | +In every one of the seven languages this site ships. On a Japanese or German or |
| 13 | +Korean UI, that sentence. And it advises configuring `editorConfig.imageProxy`, |
| 14 | +which is not a knob our users can reach -- it is a DocEditor config value, and |
| 15 | +we deliberately do not set it, because routing a user's image URLs through a |
| 16 | +third-party proxy is the one thing this site promises not to do. |
| 17 | + |
| 18 | +Found while reading the vendor for |
| 19 | +[2026-09-12-server-mock-already-in-vendor.md](2026-09-12-server-mock-already-in-vendor.md); |
| 20 | +it was not what we were looking for. |
| 21 | + |
| 22 | +## Why it happens |
| 23 | + |
| 24 | +`errorBadImageUrl` is an ordinary vendor locale key. All 45 locale files carry |
| 25 | +a translation: |
| 26 | + |
| 27 | +| locale | value | |
| 28 | +| ------ | ---------------------------- | |
| 29 | +| en | Image URL is incorrect | |
| 30 | +| zh | 图片URL地址不正确 | |
| 31 | +| ja | 画像のURLが正しくありません | |
| 32 | +| ko | 이미지 URL이 잘못되었습니다. | |
| 33 | + |
| 34 | +`Common.Locale._applyLocalization` walks each dotted key and writes it onto |
| 35 | +`window.<NS>.Controllers.Main`, and the controller definition then folds that |
| 36 | +object into the prototype with the usual idiom: |
| 37 | + |
| 38 | +```js |
| 39 | +DE.Controllers.Main = Backbone.Controller.extend(_.extend({ ...definition }, DE.Controllers.Main || {})); |
| 40 | +``` |
| 41 | + |
| 42 | +The editor reads it as `this.errorBadImageUrl` when the engine raises |
| 43 | +`Asc.c_oAscError.ID.UplImageUrl`. |
| 44 | + |
| 45 | +The offline build's `Offline` controller -- the same patch that provides the |
| 46 | +in-process server responder -- then overwrites it, on the **instance**, inside |
| 47 | +`loadDocument`: |
| 48 | + |
| 49 | +```js |
| 50 | +DE.Controllers.Main.prototype.loadDocument = async function (e) { |
| 51 | + original.call(this, e); |
| 52 | + if (window.isOffline) { |
| 53 | + this.errorBadImageUrl = '无法加载图片:地址无效或目标站不允许跨域访问(可通过 editorConfig.imageProxy 配置图片代理)'; |
| 54 | + this.editorConfig?.imageProxy && (window.offlineImageProxy = this.editorConfig.imageProxy); |
| 55 | + ... |
| 56 | + } |
| 57 | +}; |
| 58 | +``` |
| 59 | +
|
| 60 | +An own property on the instance shadows the translated one on the prototype, so |
| 61 | +the locale never gets a say. |
| 62 | +
|
| 63 | +`documenteditor`, `spreadsheeteditor` and `presentationeditor` all carry the |
| 64 | +assignment (in `main/app.js` and its `main/ie/app.js` twin). `pdfeditor` has the |
| 65 | +locale key but no override. |
| 66 | +
|
| 67 | +## The fix |
| 68 | +
|
| 69 | +Not to translate the sentence -- the vendor already translated the original into |
| 70 | +more languages than we ship. To keep the assignment from landing. |
| 71 | +
|
| 72 | +`lib/onlyoffice/guards/bad-image-url.ts` (guard 13) captures whatever the locale |
| 73 | +merge left on the prototype and replaces the property with an accessor whose |
| 74 | +setter swallows exactly that one literal: |
| 75 | +
|
| 76 | +```ts |
| 77 | +Object.defineProperty(proto, 'errorBadImageUrl', { |
| 78 | + get() { |
| 79 | + return this.__ooBadImageUrl ?? localized; |
| 80 | + }, |
| 81 | + set(value) { |
| 82 | + if (value !== OFFLINE_BAD_IMAGE_URL) this.__ooBadImageUrl = value; |
| 83 | + }, |
| 84 | +}); |
| 85 | +``` |
| 86 | +
|
| 87 | +Every other write goes through, so a vendor upgrade that reworded the message |
| 88 | +legitimately still wins. If `loadDocument` already ran, the instance carries its |
| 89 | +own copy, and the guard deletes it -- reads then fall through to the accessor. |
| 90 | +
|
| 91 | +If the locale fetch has not resolved yet there is nothing worth protecting, so |
| 92 | +the guard reports "not yet" and the guard timer re-applies. It can never leave |
| 93 | +the property emptier than it found it. |
| 94 | +
|
| 95 | +Deliberately **not** done: setting `editorConfig.imageProxy`. The patch reads it |
| 96 | +into `window.offlineImageProxy` and falls back to `proxy + encodeURIComponent(url)` |
| 97 | +when a direct image fetch fails, which would make cross-origin image insertion |
| 98 | +work -- by sending the URL, and the user's interest in it, to whoever runs the |
| 99 | +proxy. That is a product decision, not a bug fix. |
| 100 | +
|
| 101 | +## Tests |
| 102 | +
|
| 103 | +`test/e2e/bad-image-url-locale.spec.ts` opens a real docx and a real xlsx and |
| 104 | +reads the property the error handler reads, off the live controller: it must not |
| 105 | +be the Chinese literal, and must be the locale's string. |
| 106 | +
|
| 107 | +`test/unit/vendor-bad-image-url.test.ts` pins the vendor side -- that the three |
| 108 | +apps still assign exactly the literal the guard matches, and that the translated |
| 109 | +original is still present in `en.json` and in every language the site ships. A |
| 110 | +vendor upgrade that reworded or dropped the override turns it red, which is the |
| 111 | +signal to re-examine the guard rather than re-pin it. |
| 112 | +
|
| 113 | +Reverse validation (convention 5): with `installBadImageUrlGuard` commented out |
| 114 | +of `iframe-guards.ts`, both E2E cases fail with |
| 115 | +
|
| 116 | +``` |
| 117 | +Expected: not "无法加载图片:地址无效或目标站不允许跨域访问(可通过 editorConfig.imageProxy 配置图片代理)" |
| 118 | +``` |
| 119 | +
|
| 120 | +so the defect is real and the guard is what fixes it. |
0 commit comments