mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
0f60fbfbdd
* fix(slides): relax office token length check from 28 to >=25 The interleaved "OFL0X" product/region marker is read at fixed positions (1-based 5/10/15/20/25), so a token only has to be long enough to hold it. Pinning the total length to exactly 28 silently reclassified every other length as native. 28 is already stale: per #2509 the local-office format is "OFL0X + 21 random + 1 office type enum" = 27 characters, and sheets relaxed the identical guard to >= 25 in that PR. Slides was missed, so an imported office deck at the current length uploaded with parent_type "slide_file" instead of "office_slide_file". The marker positions are unchanged. Relaxing the length is only safe because of them: a false positive is the dangerous direction, since the drive backend does not validate that parent_node actually names an office file, so a misclassified native deck uploads successfully and only surfaces later as an image that will not render. The interleaved native token cases (same length, different marker) are what keep the floor honest. Tests: replaced two mislabelled rows with five verified ones covering 27, 29, 25, 24 characters and a 28-character token carrying the ppt office type enum. #2509's own labels were off by one or two characters and it never covered the 25/24 boundary; this does. * refactor(common): extract local-office token detection into common The office token shape existed in three identical copies: shortcuts/sheets/helpers.go, shortcuts/sheets/backward, and shortcuts/slides. Every copy is somewhere a format change has to be found again, and that is not hypothetical — #2509 had to apply the same 28-to->=25 relaxation twice inside sheets, and missed slides entirely. Moved the shape to common.IsLocalOfficeToken. It belongs there because recognising a local-office document is a drive-level property, not a per-domain one: an imported office file is an imported office file whether it backs a spreadsheet or a deck. What genuinely differs per domain is the parent_type the answer selects — office_sheet_file vs office_slide_file — so those mappings stay with each domain. The name deliberately matches the vocabulary #2509 already used ("local-office format"). Its doc comment calls out that "local office" is the whole category and not the LocalOfficeTokenPrefix case, since the two now share a word stem while the predicate also accepts FakeOfficeTokenPrefix and the interleaved marker. Only slides is rewired here. The two sheets copies are left alone on purpose to keep this reviewable as a pure no-op for them; they can follow separately. The marker offsets are now an array whose length is tied to the marker string, so adding a character to one without the other stops compiling, and TestOfficeTokenMinLenMatchesMarkerOffsets pins the length floor to one past the last offset rather than letting the two merely agree by coincidence. Behaviour is unchanged, verified by diffing dry-run parent_type between the pre-refactor and post-refactor binaries across 13 tokens covering both prefixes, the 24/25 boundary, 27/28/29 characters, interleaved native pptcn/shtcn markers, a leading-but-misaligned OFL0X, and an off-by-one offset: 13/13 identical. * refactor(sheets): route local-office detection through common Deletes the last two copies of the token shape, both byte-identical to the one now in common: shortcuts/sheets/helpers.go and shortcuts/sheets/backward/lark_sheets_float_images.go. sheetMediaParentType keeps owning the sheets half of the decision — which parent_type the answer selects — and only the shape moves. Equivalence was not assumed from reading. A throwaway fuzz test compared isOfficeSpreadsheet against common.IsLocalOfficeToken in both packages over an alphabet biased toward the characters that can actually disagree (OFL0X plus the native product markers), every single-byte mutation of a known office token at all 28 positions, and 400k fixed-seed random tokens of length 0-33. Zero disagreements in either package. Dry-run parent_type was then diffed binary-to-binary against origin/main across 11 tokens covering the 24/25 boundary, 27/28/29 characters, interleaved native shtcn/pptcn markers, a leading-but-misaligned OFL0X and an off-by-one offset: sheets identical on all 11. Two comment fixes that the extraction made unavoidable: The const-block doc in both files still described "a 28-character token". That was already wrong on main — #2509 relaxed the guard to >= 25 and left the comment behind — and the shape is no longer described here at all now, so both defer to common.IsLocalOfficeToken. Four rows in TestSheetMediaParentType were mislabelled: "25 char, at boundary" held a 27-character token and the three "new 27-char" rows held 28-character ones, so the floor those labels claimed to cover was never tested. Relabelled by measured length, and the real 25/24 boundary added.
83 lines
3.7 KiB
Go
83 lines
3.7 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package common
|
|
|
|
import "strings"
|
|
|
|
// Legacy synthetic prefixes an imported "office" document token may carry.
|
|
// Exported because callers legitimately need to name them — a test that spells
|
|
// "fake_office_" itself would drift from this list the moment it changes.
|
|
const (
|
|
FakeOfficeTokenPrefix = "fake_office_"
|
|
LocalOfficeTokenPrefix = "local_office_"
|
|
)
|
|
|
|
// officeTokenPrefixes is the prefix set IsLocalOfficeToken checks.
|
|
var officeTokenPrefixes = []string{FakeOfficeTokenPrefix, LocalOfficeTokenPrefix}
|
|
|
|
// officeTokenMarker is the interleaved product/region marker an imported office
|
|
// token carries.
|
|
const officeTokenMarker = "OFL0X"
|
|
|
|
// officeTokenMarkerOffsets are the byte offsets the marker occupies in an
|
|
// interleaved token — positions 5, 10, 15, 20, and 25, 1-based. The array
|
|
// length is tied to the marker so the two cannot drift apart silently: adding a
|
|
// character to one without the other stops compiling.
|
|
var officeTokenMarkerOffsets = [len(officeTokenMarker)]int{4, 9, 14, 19, 24}
|
|
|
|
// officeTokenMinLen is the shortest token the marker can be read out of, one
|
|
// past its last offset. It is a floor rather than an exact length on purpose;
|
|
// see IsLocalOfficeToken. TestOfficeTokenMinLenMatchesMarkerOffsets pins it
|
|
// to the offsets above.
|
|
const officeTokenMinLen = 25
|
|
|
|
// IsLocalOfficeToken reports whether token names a "local office" document —
|
|
// one backed by an imported office file (pptx / xlsx / docx) rather than created
|
|
// natively through the API.
|
|
//
|
|
// "Local office" is the whole category, not the LocalOfficeTokenPrefix case:
|
|
// this returns true for FakeOfficeTokenPrefix and for the interleaved marker
|
|
// too. The shared word stem is a naming coincidence, not a narrower contract.
|
|
//
|
|
// This lives in common because the token shape is a drive-level property, not a
|
|
// per-domain one: an imported office file is an imported office file whether it
|
|
// backs a spreadsheet or a deck. What differs per domain is only the drive media
|
|
// parent_type the answer selects — "office_sheet_file" vs "office_slide_file" —
|
|
// so that mapping stays with each domain and only the shape is shared. Every
|
|
// copy of the shape is somewhere a format change has to be found again; #2509
|
|
// had to be applied twice inside sheets alone, and slides was missed entirely.
|
|
//
|
|
// Two things are load-bearing about how the check is written.
|
|
//
|
|
// The marker is read at exact offsets, not by a looser strings.Contains, because
|
|
// a false positive is the dangerous direction. The drive backend does not
|
|
// validate that parent_node actually names an office file, so a native document
|
|
// wrongly classified here still uploads successfully — the damage only surfaces
|
|
// later as an image that will not render, far from its cause. A false negative
|
|
// fails loudly at the upload instead.
|
|
//
|
|
// The length is a floor rather than one exact value. Because the offsets are
|
|
// fixed, a token only has to be long enough to hold the marker; pinning the
|
|
// total length silently reclassifies every other length as native. 28 used to be
|
|
// pinned and is already stale — per #2509 the local-office format is "OFL0X + 21
|
|
// random + 1 office type enum", 27 characters. Widening the length is only safe
|
|
// because of the exact offsets: a token of the same length carrying a different
|
|
// marker (a native "pptcn" or "shtcn" one) still fails.
|
|
func IsLocalOfficeToken(token string) bool {
|
|
for _, prefix := range officeTokenPrefixes {
|
|
if strings.HasPrefix(token, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
if len(token) < officeTokenMinLen {
|
|
return false
|
|
}
|
|
for i, offset := range officeTokenMarkerOffsets {
|
|
if token[offset] != officeTokenMarker[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|