client+server: URL-encode key text so it round-trips any character 45a475bb3fa8
The client sent key(text=) raw, so '+' decoded to a space and '&'/'#'/'%' truncated or corrupted the value. Percent-encode it on the client (urllib.parse.quote, safe=''), and percent-decode query values (%XX, '+'->space) in the server's query_str. Numeric params are read straight off the raw query with query_long, so only string values (text=) are decoded — unaffected. Git-Commit: 7b2fd15d34e4338bd06b7644e39c2a363a32d3c1 Git-Author: Andy McCall <[email protected]>
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-07-25 21:35 UTC
- Signature
- ◈ sealed by Andy McCall
- Hash
45a475bb3fa8e59f7556e9dbf6f58c6625d6d6304ecf8aaabce9eb929b50d8e2- Depends on
- 689a9ccb0138 961b3c610c66 d44319b1e712
- Depended on by
- nothing yet
- In bookmarks
- main perf-6
- Edits
- 6 atoms, 0 tree operations, 2 files
Browse the tree at this change
modified client/emu_control.py +4 −1
| … | 19 unchanged lines |
||
| 20 | 20 | import json |
|
| 21 | 21 | import time |
|
| 22 | 22 | import urllib.error |
|
| 23 | + | import urllib.parse |
|
| 23 | 24 | import urllib.request |
|
| 24 | 25 | ||
| 25 | 26 | CONTRACT_MAJOR = 0 |
|
| … | 91 unchanged lines |
||
| 117 | 118 | a program that samples input each frame.""" |
|
| 118 | 119 | parts = [] |
|
| 119 | 120 | if text is not None: |
|
| 120 | - | parts.append(f"text={text}") |
|
| 121 | + | # Percent-encode so a '+' isn't decoded to a space and '&'/'#'/'%' |
|
| 122 | + | # don't truncate or corrupt the value. safe="" encodes '+' too. |
|
| 123 | + | parts.append(f"text={urllib.parse.quote(text, safe='')}") |
|
| 121 | 124 | if code is not None: |
|
| 122 | 125 | parts.append(f"code={code}") |
|
| 123 | 126 | if down is not None: |
|
| … | 41 unchanged lines |
modified core/retro_control.c +29 −6
| … | 230 unchanged lines |
||
| 231 | 231 | return def; |
|
| 232 | 232 | } |
|
| 233 | 233 | ||
| 234 | - | /* copy the value of query key into out (NUL-terminated, truncated to cap). |
|
| 235 | - | * Returns 1 if the key was present, 0 otherwise. */ |
|
| 234 | + | /* one hex nibble, or -1. */ |
|
| 235 | + | static int hex_nibble(int c) |
|
| 236 | + | { |
|
| 237 | + | if (c >= '0' && c <= '9') return c - '0'; |
|
| 238 | + | if (c >= 'a' && c <= 'f') return c - 'a' + 10; |
|
| 239 | + | if (c >= 'A' && c <= 'F') return c - 'A' + 10; |
|
| 240 | + | return -1; |
|
| 241 | + | } |
|
| 242 | + | ||
| 243 | + | /* copy the value of query key into out (NUL-terminated, truncated to cap), |
|
| 244 | + | * percent-decoding it (form-urlencoded: %XX -> byte, '+' -> space). Numeric |
|
| 245 | + | * params are read with query_long straight off the raw query, so only string |
|
| 246 | + | * values (e.g. text=) are decoded here. Returns 1 if the key was present. */ |
|
| 236 | 247 | static int query_str(const char *q, const char *key, char *out, size_t cap) |
|
| 237 | 248 | { |
|
| 238 | 249 | size_t kl = strlen(key); |
|
| … | 2 unchanged lines |
||
| 241 | 252 | if (!strncmp(p, key, kl) && p[kl] == '=') { |
|
| 242 | 253 | const char *v = p + kl + 1; |
|
| 243 | 254 | const char *e = strchr(v, '&'); |
|
| 244 | - | size_t n = e ? (size_t)(e - v) : strlen(v); |
|
| 245 | - | if (n >= cap) n = cap - 1; |
|
| 246 | - | memcpy(out, v, n); |
|
| 247 | - | out[n] = 0; |
|
| 255 | + | const char *end = e ? e : v + strlen(v); |
|
| 256 | + | size_t w = 0; |
|
| 257 | + | for (const char *r = v; r < end && w + 1 < cap; ) { |
|
| 258 | + | int hi, lo; |
|
| 259 | + | if (r[0] == '%' && r + 2 < end && |
|
| 260 | + | (hi = hex_nibble(r[1])) >= 0 && (lo = hex_nibble(r[2])) >= 0) { |
|
| 261 | + | out[w++] = (char)((hi << 4) | lo); |
|
| 262 | + | r += 3; |
|
| 263 | + | } else if (r[0] == '+') { |
|
| 264 | + | out[w++] = ' '; |
|
| 265 | + | r += 1; |
|
| 266 | + | } else { |
|
| 267 | + | out[w++] = *r++; |
|
| 268 | + | } |
|
| 269 | + | } |
|
| 270 | + | out[w] = 0; |
|
| 248 | 271 | return 1; |
|
| 249 | 272 | } |
|
| 250 | 273 | p = strchr(p, '&'); |
|
| … | 189 unchanged lines |