{"record":{"id":"d8c8a9ca5312a385","repo":"DIYgod/RSSHub","slug":"invalid-city","errorCode":null,"errorMessage":"Invalid city","messagePattern":"Invalid city","errorType":"validation","errorClass":"InvalidParameterError","httpStatus":503,"severity":"error","filePath":"lib/routes/19lou/index.ts","lineNumber":57,"sourceCode":"| www  | taizhou | jiaxing | ningbo | huzhou |\n\n| 绍兴     | 湖州   | 温州    | 金华   | 舟山     |\n| -------- | ------ | ------- | ------ | -------- |\n| shaoxing | huzhou | wenzhou | jinhua | zhoushan |\n\n| 衢州   | 丽水   | 义乌 | 萧山     | 余杭   |\n| ------ | ------ | ---- | -------- | ------ |\n| quzhou | lishui | yiwu | xiaoshan | yuhang |\n\n| 临安  | 富阳   | 桐庐   | 建德   | 淳安   |\n| ----- | ------ | ------ | ------ | ------ |\n| linan | fuyang | tonglu | jiande | chunan |`,\n};\n\nasync function handler(ctx) {\n    const city = ctx.req.param('city') ?? 'www';\n    if (!isValidHost(city)) {\n        throw new InvalidParameterError('Invalid city');\n    }\n\n    const rootUrl = `https://${city}.19lou.com`;\n\n    const response = await got({\n        method: 'get',\n        url: rootUrl,\n        responseType: 'buffer',\n    });\n\n    const $ = load(iconv.decode(response.data, 'gbk'));\n\n    $('.title-more').remove();\n\n    let items = $('.center-center-jiazi')\n        .find('a[title]')\n        .toArray()\n        .map((item): DataItem & { link: string } => {","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/19lou/index.ts#L39-L75","documentation":"19lou is a regional forum network with per-city subdomains; the route builds `https://${city}.19lou.com`. isValidHost rejects values that are not a safe single DNS label, which prevents host injection / SSRF via dots, slashes, or special characters. InvalidParameterError is thrown when the city segment fails the check.","triggerScenarios":"Calling /19lou/:city with a slug that is not in the documented city list, or that contains '.', '/', '@', or other non-hostname characters.","commonSituations":"Guessing a city slug; typo; copying a full URL into the city slot; upstream added a city not yet reflected in docs.","solutions":["Use one of the documented city slugs: www, hangzhou, ningbo, wenzhou, jiaxing, huzhou, shaoxing, jinhua, quzhou, lishui, yiwu, xiaoshan, yuhang, linan, fuyang, tonglu, jiande, chunan.","Omit the city segment entirely to default to 'www'.","Do not include the scheme or '.19lou.com' suffix in the parameter."],"exampleFix":"// before\n/19lou/hangzhou.19lou.com\n// after\n/19lou/hangzhou","handlingStrategy":"validation","validationCode":"import { isValidHost } from '@/utils/valid-host';\nconst CITIES_19LOU = new Set(['www','hangzhou','ningbo','wenzhou','jiaxing','huzhou','shaoxing','jinhua','quzhou','lishui','yiwu','xiaoshan','yuhang','linan','fuyang','tonglu','jiande','chunan']);\nfunction valid19louCity(city) {\n  return CITIES_19LOU.has(city) && isValidHost(city);\n}","typeGuard":"function is19louCity(c): boolean {\n  return typeof c === 'string' && /^[a-z]+$/.test(c) && CITIES_19LOU.has(c);\n}","tryCatchPattern":null,"preventionTips":["Never include the scheme or '.19lou.com' in the city parameter.","If unsure, omit the segment to use the 'www' default.","Treat isValidHost as a security boundary, not a convenience — don't bypass it."],"tags":["validation","parameter","host-injection","rsshub","ssrf"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}