| 813 | } |
| 814 | |
| 815 | bool cbm_http_query_param(const char *query, const char *name, char *buf, int bufsz) { |
| 816 | if (!query || !name || !buf || bufsz <= 0) |
| 817 | return false; |
| 818 | buf[0] = '\0'; |
| 819 | size_t name_len = strlen(name); |
| 820 | |
| 821 | const char *p = query; |
| 822 | while (*p) { |
| 823 | const char *pair_end = strchr(p, '&'); |
| 824 | if (!pair_end) |
| 825 | pair_end = p + strlen(p); |
| 826 | const char *eq = memchr(p, '=', (size_t)(pair_end - p)); |
| 827 | size_t klen = eq ? (size_t)(eq - p) : (size_t)(pair_end - p); |
| 828 | |
| 829 | if (klen == name_len && memcmp(p, name, name_len) == 0) { |
| 830 | if (!eq) |
| 831 | return false; /* present but no value */ |
| 832 | /* Percent-decode the value into buf. */ |
| 833 | const char *v = eq + 1; |
| 834 | int out = 0; |
| 835 | while (v < pair_end) { |
| 836 | char ch = *v; |
| 837 | if (ch == '+') { |
| 838 | ch = ' '; |
| 839 | v++; |
| 840 | } else if (ch == '%') { |
| 841 | if (pair_end - v < 3) /* needs two hex digits */ |
| 842 | return false; |
| 843 | int hi = hex_val(v[1]); |
| 844 | int lo = hex_val(v[2]); |
| 845 | if (hi < 0 || lo < 0) |
| 846 | return false; |
| 847 | ch = (char)((hi << 4) | lo); |
| 848 | if (ch == '\0') |
| 849 | return false; /* decoded NUL never allowed */ |
| 850 | v += 3; |
| 851 | } else { |
| 852 | v++; |
| 853 | } |
| 854 | if (out >= bufsz - 1) { |
| 855 | buf[0] = '\0'; |
| 856 | return false; /* oversize — reject, never truncate */ |
| 857 | } |
| 858 | buf[out++] = ch; |
| 859 | } |
| 860 | buf[out] = '\0'; |
| 861 | return out > 0; |
| 862 | } |
| 863 | p = (*pair_end == '&') ? pair_end + 1 : pair_end; |
| 864 | } |
| 865 | return false; |
| 866 | } |
no test coverage detected