{"record":{"id":"74fc6a7dd10300a8","repo":"jackwener/OpenCLI","slug":"name-is-not-a-valid-place-name-json-string","errorCode":null,"errorMessage":"--${name} is not a valid place name: ${JSON.stringify(raw)}","messagePattern":"--(.+?) is not a valid place name: (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/utils.js","lineNumber":524,"sourceCode":"      })()\n    `;\n}\n\n/** Validate a 1-50 result limit shared by the browser-mode list commands (default 20). */\nexport function parseListLimit(raw, fallback = 20) {\n    return parseStrictIntegerRange('limit', raw, fallback);\n}\n\n/** Validate a Chinese place keyword (station, city, port, or destination) for the browser list queries. */\nexport function parsePlaceName(name, raw) {\n    if (raw === undefined || raw === null || String(raw).trim() === '') {\n        throw new ArgumentError(`--${name} is required (e.g. 北京 / 上海)`);\n    }\n    const value = String(raw).trim();\n    // These list pages key on the raw Chinese place name; reject control\n    // characters and over-long input rather than passing them through.\n    if (value.length > 20 || /[\\x00-\\x1f]/.test(value)) {\n        throw new ArgumentError(`--${name} is not a valid place name: ${JSON.stringify(raw)}`);\n    }\n    return value;\n}\n\nexport function buildTrainListUrl(fromName, toName, date) {\n    const params = new URLSearchParams({\n        dStationName: fromName,\n        aStationName: toName,\n        dDate: date,\n        ticketType: '1',\n    });\n    return `https://trains.ctrip.com/webapp/train/list?${params.toString()}`;\n}\n\n/**\n * Browser-context IIFE that extracts train rows from the trains.ctrip.com\n * list page. Each `.card-white.list-item` exposes stable, class-keyed leaf\n * fields (`.from/.mid/.to/.rbox/.surplus-list`), so we read by selector rather","sourceCodeStart":506,"sourceCodeEnd":542,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/utils.js#L506-L542","documentation":"After the presence check, parsePlaceName rejects values longer than 20 characters or containing ASCII control characters (\\x00-\\x1f, including newlines and tabs), since the list queries pass the raw Chinese place name into URLs. The rejected input is echoed JSON-escaped in the ArgumentError message.","triggerScenarios":"Passing --from-city/--to-city/--port/--destination values that exceed 20 chars or embed control characters: multi-line paste, tab/newline inside the name, strings with stray escapes, or overly long descriptions instead of a place keyword.","commonSituations":"Copy-pasting a whole address or sentence from notes into the flag, pasting names with trailing newline from terminal output (e.g. $(...)), or script bugs that join multiple values with '\\n'.","solutions":["Shorten the value to the bare Chinese place name (<= 20 chars).","Strip newlines/tabs/control characters: value.replace(/[\\x00-\\x1f]/g, '').trim().","Fix scripts that interpolate untrimmed command output into the flag."],"exampleFix":"// before\nctrip trains --from-city \"上海市浦东新区世纪大道1000号\"\n// after\nctrip trains --from-city 上海","handlingStrategy":"validation","validationCode":"const name = String(raw).trim();\nif (name.length > 20 || /[\\x00-\\x1f]/.test(name)) {\n  throw new Error('place name must be <=20 chars with no control characters: ' + JSON.stringify(raw));\n}","typeGuard":"const isValidPlaceName = (v) => typeof v === 'string' && v.trim().length >= 1 && v.trim().length <= 20 && !/[\\x00-\\x1f]/.test(v.trim());","tryCatchPattern":"try {\n  const name = parsePlaceName('to-city', raw);\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.includes('not a valid place name')) {\n    const cleaned = String(raw).replace(/[\\x00-\\x1f]/g, '').trim().slice(0, 20);\n    return parsePlaceName('to-city', cleaned);\n  }\n  throw e;\n}","preventionTips":["Sanitize pasted input: strip control chars and trim newlines from command substitution.","Pass bare place keywords, not full addresses.","Cap input length at 20 characters in wrappers/forms."],"tags":["cli","validation","argument-error","input-sanitization"],"backgroundTag":"invalid-cli-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}