Files
larksuite__cli/shortcuts/common/office_token.go
R0bynZhu 0f60fbfbdd fix(slides): relax office token length check from 28 to >=25 (#2531)
* 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.
2026-08-28 10:39:04 +08:00

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
}