static void httpd_handler(const struct pl *uri, struct mbuf *mb) { struct pl cmd, params, r; uint32_t refresh = 0; if (re_regex(uri->p, uri->l, "/[^?]*[^]*", &cmd, ¶ms)) return; if (!re_regex(params.p, params.l, "[?&]1r=[0-9]+", NULL, &r)) refresh = pl_u32(&r); mbuf_write_str(mb, "<html>\n<head>\n"); mbuf_write_str(mb, " <title>Restund Server Status</title>\n"); if (refresh) mbuf_printf(mb, " <meta http-equiv=\"refresh\" content=\"%u\">\n", refresh); mbuf_write_str(mb, "</head>\n<body>\n"); mbuf_write_str(mb, "<h2>Restund Server Status</h2>\n"); server_info(mb); mbuf_write_str(mb, "<hr size=\"1\"/>\n<pre>\n"); restund_cmd(&cmd, mb); mbuf_write_str(mb, "</pre>\n</body>\n</html>\n"); }
static int parse_resolv_conf(char *domain, size_t dsize, struct sa *srvv, uint32_t *n) { FILE *f; struct pl dom = pl_null; uint32_t i = 0; int err = 0; if (!srvv || !n || !*n) return EINVAL; f = fopen("/etc/resolv.conf", "r"); if (!f) return errno; for (;;) { char line[128]; struct pl srv; size_t len; if (1 != fscanf(f, "%127[^\n]\n", line)) break; if ('#' == line[0]) continue; len = str_len(line); /* Set domain if not already set */ if (!pl_isset(&dom)) { if (0 == re_regex(line, len, "domain [^ ]+", &dom)) { (void)pl_strcpy(&dom, domain, dsize); } if (0 == re_regex(line, len, "search [^ ]+", &dom)) { (void)pl_strcpy(&dom, domain, dsize); } } /* Use the first entry */ if (i < *n && 0 == re_regex(line, len, "nameserver [^\n]+", &srv)) { err = sa_set(&srvv[i], &srv, DNS_PORT); if (err) { DEBUG_WARNING("sa_set: %r (%m)\n", &srv, err); } ++i; } } *n = i; (void)fclose(f); return err; }
static void notify_handler(struct sip *sip, const struct sip_msg *msg, void *arg) { enum presence_status status = PRESENCE_CLOSED; struct presence *pres = arg; const struct sip_hdr *hdr; struct pl pl; pres->failc = 0; hdr = sip_msg_hdr(msg, SIP_HDR_CONTENT_TYPE); if (!hdr || 0 != pl_strcasecmp(&hdr->val, "application/pidf+xml")) { if (hdr) (void)re_printf("presence: unsupported" " content-type: '%r'\n", &hdr->val); sip_treplyf(NULL, NULL, sip, msg, false, 415, "Unsupported Media Type", "Accept: application/pidf+xml\r\n" "Content-Length: 0\r\n" "\r\n"); return; } if (!re_regex((const char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), "<status>[^<]*<basic>[^<]*</basic>[^<]*</status>", NULL, &pl, NULL)) { if (!pl_strcasecmp(&pl, "open")) status = PRESENCE_OPEN; } if (!re_regex((const char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), "<rpid:away/>")) { status = PRESENCE_CLOSED; } else if (!re_regex((const char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), "<rpid:busy/>")) { status = PRESENCE_BUSY; } else if (!re_regex((const char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), "<rpid:on-the-phone/>")) { status = PRESENCE_BUSY; } (void)sip_treply(NULL, sip, msg, 200, "OK"); contact_set_presence(pres->contact, status); }
static int decode_hostport(const struct pl *hostport, struct pl *host, struct pl *port) { /* Try IPv6 first */ if (!re_regex(hostport->p, hostport->l, "\\[[0-9a-f:]+\\][:]*[0-9]*", host, NULL, port)) return 0; /* Then non-IPv6 host */ return re_regex(hostport->p, hostport->l, "[^:]+[:]*[0-9]*", host, NULL, port); }
static int media_decode(struct sdp_media **mp, struct sdp_session *sess, bool offer, const struct pl *pl) { struct pl name, port, proto, fmtv, fmt; struct sdp_media *m; int err; if (re_regex(pl->p, pl->l, "[a-z]+ [^ ]+ [^ ]+[^]*", &name, &port, &proto, &fmtv)) return EBADMSG; m = list_ledata(*mp ? (*mp)->le.next : sess->medial.head); if (!m) { if (!offer) return EPROTO; m = sdp_media_find(sess, &name, &proto); if (!m) { err = sdp_media_radd(&m, sess, &name, &proto); if (err) return err; } else { list_unlink(&m->le); list_append(&sess->medial, &m->le, m); } } else { if (pl_strcmp(&name, m->name)) return offer ? ENOTSUP : EPROTO; if (pl_strcmp(&proto, m->proto)) return ENOTSUP; } while (!re_regex(fmtv.p, fmtv.l, " [^ ]+", &fmt)) { pl_advance(&fmtv, fmt.p + fmt.l - fmtv.p); err = sdp_format_radd(m, &fmt); if (err) return err; } m->raddr = sess->raddr; sa_set_port(&m->raddr, pl_u32(&port)); m->rdir = sess->rdir; *mp = m; return 0; }
/** Compare two SDP messages line-by-line (exclude owner) */ static bool sdp_cmp(struct mbuf *mb, const char *msg) { struct pl pl; if (!mb || !msg) return false; pl.p = (char *)mb->buf; pl.l = mb->end; while (pl.l && strlen(msg)) { struct pl n1, v1, n2, v2; if (re_regex(pl.p, pl.l, "[^=]1=[^\r\n]+", &n1, &v1)) return false; if (re_regex(msg, strlen(msg), "[^=]1=[^\r\n]+", &n2, &v2)) return false; pl_advance(&pl, 2 + v1.l + 2); msg += (2 + v2.l + 2); if (0 != pl_cmp(&n1, &n2)) { DEBUG_WARNING("name mismatch: %r=%r\n", &n1, &v1); return false; } /* ignore owner */ if (n1.p[0] == 'o') continue; if (0 != pl_cmp(&v1, &v2)) { DEBUG_WARNING("value mismatch: %r=%r\n", &n1, &v1); return false; } } if (pl.l) { DEBUG_WARNING("%u bytes junk at end: %r\n", pl.l, &pl); } if (strlen(msg)) { DEBUG_WARNING("%u bytes junk at end: %s\n", strlen(msg), msg); } return !pl.l && !strlen(msg); }
static void sipsess_info_handler(struct sip *sip, const struct sip_msg *msg, void *arg) { struct call *call = arg; if (msg_ctype_cmp(&msg->ctyp, "application", "dtmf-relay")) { struct pl body, sig, dur; int err; pl_set_mbuf(&body, msg->mb); err = re_regex(body.p, body.l, "Signal=[0-9]+", &sig); err |= re_regex(body.p, body.l, "Duration=[0-9]+", &dur); if (err) { (void)sip_reply(sip, msg, 400, "Bad Request"); } else { char s = pl_u32(&sig); uint32_t duration = pl_u32(&dur); if (s == 10) s = '*'; else if (s == 11) s = '#'; else s += '0'; info("received DTMF: '%c' (duration=%r)\n", s, &dur); (void)sip_reply(sip, msg, 200, "OK"); if (call->dtmfh) { tmr_start(&call->tmr_dtmf, duration, dtmfend_handler, call); call->dtmfh(call, s, call->arg); } } } #ifdef USE_VIDEO else if (msg_ctype_cmp(&msg->ctyp, "application", "media_control+xml")) { call_handle_info_req(call, msg); (void)sip_reply(sip, msg, 200, "OK"); } #endif else { (void)sip_reply(sip, msg, 488, "Not Acceptable Here"); } }
static int attr_decode_rtpmap(struct sdp_media *m, const struct pl *pl) { struct pl id, name, srate, ch; struct sdp_format *fmt; int err; if (!m) return 0; if (re_regex(pl->p, pl->l, "[^ ]+ [^/]+/[0-9]+[/]*[^]*", &id, &name, &srate, NULL, &ch)) return EBADMSG; fmt = sdp_format_find(&m->rfmtl, &id); if (!fmt) return 0; fmt->name = mem_deref(fmt->name); err = pl_strdup(&fmt->name, &name); if (err) return err; fmt->srate = pl_u32(&srate); fmt->ch = ch.l ? pl_u32(&ch) : 1; return 0; }
static void sipsub_notify_handler(struct sip *sip, const struct sip_msg *msg, void *arg) { struct call *call = arg; struct pl scode, reason; uint32_t sc; if (re_regex((char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), "SIP/2.0 [0-9]+ [^\r\n]+", &scode, &reason)) { (void)sip_reply(sip, msg, 400, "Bad sipfrag"); return; } (void)sip_reply(sip, msg, 200, "OK"); sc = pl_u32(&scode); if (sc >= 300) { warning("call: transfer failed: %u %r\n", sc, &reason); call_event_handler(call, CALL_EVENT_TRANSFER_FAILED, "%u %r", sc, &reason); } else if (sc >= 200) { call_event_handler(call, CALL_EVENT_CLOSED, "Call transfered"); } }
TEST_F(TestMedia, verify_sha256_fingerprint_in_offer) { char sdp[4096]; struct pl pl; size_t i; int err; err = mediaflow_generate_offer(mf, sdp, sizeof(sdp)); ASSERT_EQ(0, err); ASSERT_TRUE(find_in_sdp(sdp, "fingerprint:sha-256")); ASSERT_TRUE(find_in_sdp(sdp, "setup:actpass")); err = re_regex(sdp, strlen(sdp), "fingerprint:sha-256 [^\r\n]+", &pl); ASSERT_EQ(0, err); /* Firefox has a strict SDP parser, hex values MUST be uppercase! */ for (i=0; i<pl.l; i++) { char c = pl.p[i]; if (c == ':' || ('0' <= c && c <= '9') || ('A' <= c && c <= 'F')) continue; re_fprintf(stderr, "invalid character in fingerprint (%r)\n", &pl); ASSERT_TRUE(false); } }
int conf_get_vidsz(const struct conf *conf, const char *name, struct vidsz *sz) { struct pl r, w, h; int err; err = conf_get(conf, name, &r); if (err) return err; w.l = h.l = 0; err = re_regex(r.p, r.l, "[0-9]+x[0-9]+", &w, &h); if (err) return err; if (pl_isset(&w) && pl_isset(&h)) { sz->w = pl_u32(&w); sz->h = pl_u32(&h); } /* check resolution */ if (sz->w & 0x1 || sz->h & 0x1) { warning("conf: %s: should be multiple of 2 (%u x %u)\n", name, sz->w, sz->h); return EINVAL; } return 0; }
/** * Apply a function handler to all config items of a certain key * * @param conf Configuration object * @param name Name of config item key * @param ch Config item handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int conf_apply(const struct conf *conf, const char *name, conf_h *ch, void *arg) { char expr[512]; struct pl pl, val; int err = 0; if (!conf || !name || !ch) return EINVAL; pl.p = (const char *)conf->mb->buf; pl.l = conf->mb->end; (void)re_snprintf(expr, sizeof(expr), "[\r\n]+[ \t]*%s[ \t]+[~ \t\r\n]+", name); while (!re_regex(pl.p, pl.l, expr, NULL, NULL, NULL, &val)) { err = ch(&val, arg); if (err) break; pl.l -= val.p + val.l - pl.p; pl.p = val.p + val.l; } return err; }
/* * Start the audio loop (for testing) */ static int auloop_start(struct re_printf *pf, void *arg) { struct cmd_arg *carg = arg; struct pl pl_srate, pl_ch; uint32_t srate, ch; int err; if (gal) return re_hprintf(pf, "audio-loop already running.\n"); err = re_regex(carg->prm, str_len(carg->prm), "[0-9]+ [0-9]+", &pl_srate, &pl_ch); if (err) { return re_hprintf(pf, "Usage:" " /auloop <samplerate> <channels>\n"); } srate = pl_u32(&pl_srate); ch = pl_u32(&pl_ch); if (!srate || !ch) return re_hprintf(pf, "invalid samplerate or channels\n"); err = audio_loop_alloc(&gal, srate, ch); if (err) { warning("auloop: alloc failed %m\n", err); } return err; }
static void decode_part(const struct pl *part, struct mbuf *mb) { struct pl hdrs, body; if (re_regex(part->p, part->l, "\r\n\r\n[^]+", &body)) return; hdrs.p = part->p; hdrs.l = body.p - part->p - 2; if (0 == re_regex(hdrs.p, hdrs.l, "application/sdp")) { mb->pos += (body.p - (char *)mbuf_buf(mb)); mb->end = mb->pos + body.l; } }
static void sig_hash_decode(struct zrtp_stream_t *stream, const struct sdp_media *m) { const char *attr_val; struct pl major, minor, hash; uint32_t version; int err; zrtp_status_t s; attr_val = sdp_media_rattr(m, "zrtp-hash"); if (!attr_val) return; err = re_regex(attr_val, strlen(attr_val), "[0-9]+.[0-9]2 [0-9a-f]+", &major, &minor, &hash); if (err || hash.l < ZRTP_SIGN_ZRTP_HASH_LENGTH) { warning("zrtp: malformed zrtp-hash attribute, ignoring...\n"); return; } version = pl_u32(&major) * 100 + pl_u32(&minor); /* more version checks? */ if (version < 110) { warning("zrtp: zrtp-hash: version (%d) is too low, " "ignoring...", version); } s = zrtp_signaling_hash_set(stream, hash.p, (uint32_t)hash.l); if (s != zrtp_status_ok) warning("zrtp: zrtp_signaling_hash_set: status = %d\n", s); }
/** * Decode an SDP fingerprint value * * @param attr SDP attribute value * @param hash Returned hash method * @param md Returned message digest * @param sz Message digest size, set on return * * @return 0 if success, otherwise errorcode * * Reference: RFC 4572 */ int sdp_fingerprint_decode(const char *attr, struct pl *hash, uint8_t *md, size_t *sz) { struct pl f; const char *p; int err; if (!attr || !hash) return EINVAL; err = re_regex(attr, str_len(attr), "[^ ]+ [0-9A-F:]+", hash, &f); if (err) return err; if (md && sz) { if (*sz < (f.l+1)/3) return EOVERFLOW; for (p = f.p; p < (f.p+f.l); p += 3) { *md++ = ch_hex(p[0]) << 4 | ch_hex(p[1]); } *sz = (f.l+1)/3; } return 0; }
TEST_F(TestMedia, sdp_offer_with_webrtc_rtp_profile) { char sdp[4096]; int err; err = mediaflow_add_video(mf, &vidcodecl); ASSERT_EQ(0, err); err = mediaflow_generate_offer(mf, sdp, sizeof(sdp)); ASSERT_EQ(0, err); /* simple verification of SDP offer */ ASSERT_EQ(0, re_regex(sdp, strlen(sdp), "m=audio [0-9]+ UDP/TLS/RTP/SAVPF ", NULL)); ASSERT_EQ(0, re_regex(sdp, strlen(sdp), "m=video [0-9]+ UDP/TLS/RTP/SAVPF ", NULL)); }
static void decode_param(const struct pl *name, const struct pl *val, void *arg) { struct aucodec_st *st = arg; int err; if (0 == pl_strcasecmp(name, "bitrate")) { st->bitrate = pl_u32(val) * 1000; } else if (0 == pl_strcasecmp(name, "frame-size")) { st->frame_size = pl_u32(val); if (st->frame_size & 0x1) { DEBUG_WARNING("frame-size is NOT even: %u\n", st->frame_size); } } else if (0 == pl_strcasecmp(name, "low-overhead")) { struct pl fs, bpfv; uint32_t i; st->low_overhead = true; err = re_regex(val->p, val->l, "[0-9]+/[0-9,]+", &fs, &bpfv); if (err) return; st->frame_size = pl_u32(&fs); for (i=0; i<ARRAY_SIZE(st->bpfv) && bpfv.l > 0; i++) { struct pl bpf, co; co.l = 0; if (re_regex(bpfv.p, bpfv.l, "[0-9]+[,]*", &bpf, &co)) break; pl_advance(&bpfv, bpf.l + co.l); st->bpfv[i] = pl_u32(&bpf); } st->bpfn = i; } else { DEBUG_NOTICE("unknown param: %r = %r\n", name, val); } }
int url_decode(struct url* url, struct pl *pl) { int ok; ok = re_regex(pl->p, pl->l, "[^:]+://[^/]+[^]*", &url->scheme, &url->host, &url->path); url->port = 0; return 0; }
static void tcp_recv_handler(struct mbuf *mb, void *arg) { struct request *request = arg; int ok; struct pl ver; struct pl code; struct pl phrase; struct pl headers; struct pl body; DEBUG_INFO("recv data[%d]\n", mbuf_get_left(mb)); if(request->state == STREAM) { request->stream_h(request, HTTP_STREAM_DATA, mb, request->arg); return; } if(request->body) { ok = mbuf_write_mem(request->body, mbuf_buf(mb), mbuf_get_left(mb)); goto clen; } ok = re_regex((const char*)mbuf_buf(mb), mbuf_get_left(mb), "HTTP/[^ \t\r\n]+ [0-9]+ [^\t\r\n]+\r\n[^]1", &ver, &code, &phrase, &headers); // XXX: check ok // XXX: check headers.l request->status = pl_u32(&code); headers.l = mbuf_get_left(mb) - (headers.p - (const char*)mbuf_buf(mb)); body.l = 0; parse_headers(request, (char*)headers.p, headers.l, &body); if(body.l) { request->body = mbuf_alloc(body.l); mbuf_write_mem(request->body, (const unsigned char*)body.p, body.l); } request->response = mem_ref(mb); clen: if(request->body && request->clen > request->body->end) return; if(request->status >= 200 || request->stream_h == NULL) { request->done_h(request, request->status, request->arg); request->state = END; mem_deref(request); return; } request->state = STREAM; request->stream_h(request, HTTP_STREAM_EST, mb, request->arg); }
static int uri_complete(struct ua *ua, struct mbuf *buf, const char *uri) { size_t len; int err = 0; /* Skip initial whitespace */ while (isspace(*uri)) ++uri; len = str_len(uri); /* Append sip: scheme if missing */ if (0 != re_regex(uri, len, "sip:")) err |= mbuf_printf(buf, "sip:"); err |= mbuf_write_str(buf, uri); /* Append domain if missing */ if (0 != re_regex(uri, len, "[^@]+@[^]+", NULL, NULL)) { #if HAVE_INET6 if (AF_INET6 == ua->acc->luri.af) err |= mbuf_printf(buf, "@[%r]", &ua->acc->luri.host); else #endif err |= mbuf_printf(buf, "@%r", &ua->acc->luri.host); /* Also append port if specified and not 5060 */ switch (ua->acc->luri.port) { case 0: case SIP_PORT: break; default: err |= mbuf_printf(buf, ":%u", ua->acc->luri.port); break; } } return err; }
/* * Append module extension, if not exist * * input: foobar * output: foobar.so * */ static void append_extension(char *buf, size_t sz, const char *name) { if (0 == re_regex(name, str_len(name), "[^.]+"MOD_EXT, NULL)) { str_ncpy(buf, name, sz); } else { re_snprintf(buf, sz, "%s"MOD_EXT, name); } }
int sipevent_substate_decode(struct sipevent_substate *ss, const struct pl *pl) { struct pl state, param; int err; if (!ss || !pl) return EINVAL; err = re_regex(pl->p, pl->l, "[a-z]+[ \t\r\n]*[^]*", &state, NULL, &ss->params); if (err) return EBADMSG; if (!pl_strcasecmp(&state, "active")) ss->state = SIPEVENT_ACTIVE; else if (!pl_strcasecmp(&state, "pending")) ss->state = SIPEVENT_PENDING; else if (!pl_strcasecmp(&state, "terminated")) ss->state = SIPEVENT_TERMINATED; else ss->state = -1; if (!msg_param_decode(&ss->params, "reason", ¶m)) { if (!pl_strcasecmp(¶m, "deactivated")) ss->reason = SIPEVENT_DEACTIVATED; else if (!pl_strcasecmp(¶m, "probation")) ss->reason = SIPEVENT_PROBATION; else if (!pl_strcasecmp(¶m, "rejected")) ss->reason = SIPEVENT_REJECTED; else if (!pl_strcasecmp(¶m, "timeout")) ss->reason = SIPEVENT_TIMEOUT; else if (!pl_strcasecmp(¶m, "giveup")) ss->reason = SIPEVENT_GIVEUP; else if (!pl_strcasecmp(¶m, "noresource")) ss->reason = SIPEVENT_NORESOURCE; else ss->reason = -1; } else { ss->reason = -1; } if (!msg_param_decode(&ss->params, "expires", ¶m)) ss->expires = param; else ss->expires = pl_null; if (!msg_param_decode(&ss->params, "retry-after", ¶m)) ss->retry_after = param; else ss->retry_after = pl_null; return 0; }
/** * Fetch a semicolon separated parameter from a PL string * * @param pl PL string to search * @param pname Parameter name * @param val Parameter value, set on return * * @return true if found, false if not found */ bool fmt_param_get(const struct pl *pl, const char *pname, struct pl *val) { char expr[128]; if (!pl) return false; (void)re_snprintf(expr, sizeof(expr), "%s[=]*[^;]*", pname); return 0 == re_regex(pl->p, pl->l, expr, NULL, val); }
static int conn_decode(struct sa *sa, const struct pl *pl) { struct pl v; if (re_regex(pl->p, pl->l, "IN IP[46]1 [^ ]+", NULL, &v)) return EBADMSG; (void)sa_set(sa, &v, sa_port(sa)); return 0; }
static int attr_decode_rtcp(struct sdp_media *m, const struct pl *pl) { struct pl port, addr; int err = 0; if (!m) return 0; if (!re_regex(pl->p, pl->l, "[0-9]+ IN IP[46]1 [^ ]+", &port, NULL, &addr)) { (void)sa_set(&m->raddr_rtcp, &addr, pl_u32(&port)); } else if (!re_regex(pl->p, pl->l, "[0-9]+", &port)) { sa_set_port(&m->raddr_rtcp, pl_u32(&port)); } else err = EBADMSG; return err; }
static void decoder_fmtp_decode(struct audec_state *st, const char *fmtp) { struct pl mode; if (!fmtp) return; if (re_regex(fmtp, strlen(fmtp), "mode=[0-9]+", &mode)) return; set_decoder_mode(st, pl_u32(&mode)); }
/** * Decode a multipart/mixed message and find the part with application/sdp * * @param ctype_prm Content type parameter * @param mb Mbuffer containing the SDP * * @return 0 if success, otherwise errorcode */ int sdp_decode_multipart(const struct pl *ctype_prm, struct mbuf *mb) { struct pl bnd, s, e, p; char expr[64]; int err; if (!ctype_prm || !mb) return EINVAL; /* fetch the boundary tag, excluding quotes */ err = re_regex(ctype_prm->p, ctype_prm->l, "boundary=[~]+", &bnd); if (err) return err; if (re_snprintf(expr, sizeof(expr), "--%r[^]+", &bnd) < 0) return ENOMEM; /* find 1st boundary */ err = re_regex((char *)mbuf_buf(mb), mbuf_get_left(mb), expr, &s); if (err) return err; /* iterate over each part */ while (s.l > 2) { if (re_regex(s.p, s.l, expr, &e)) return 0; p.p = s.p + 2; p.l = e.p - p.p - bnd.l - 2; /* valid part in "p" */ decode_part(&p, mb); s = e; } return 0; }
/** * Get the value of a configuration item PL string * * @param conf Configuration object * @param name Name of config item key * @param pl Value of config item, if present * * @return 0 if success, otherwise errorcode */ int conf_get(const struct conf *conf, const char *name, struct pl *pl) { char expr[512]; struct pl spl; if (!conf || !name || !pl) return EINVAL; spl.p = (const char *)conf->mb->buf; spl.l = conf->mb->end; (void)re_snprintf(expr, sizeof(expr), "[\r\n]+[ \t]*%s[ \t]+[~ \t\r\n]+", name); return re_regex(spl.p, spl.l, expr, NULL, NULL, NULL, pl); }
int cmd_process_long(struct commands *commands, const char *str, size_t len, struct re_printf *pf_resp, void *data) { struct cmd_arg arg; const struct cmd *cmd_long; char *name = NULL, *prm = NULL; struct pl pl_name, pl_prm; int err; if (!str || !len) return EINVAL; memset(&arg, 0, sizeof(arg)); err = re_regex(str, len, "[^ ]+[ ]*[~]*", &pl_name, NULL, &pl_prm); if (err) { return err; } err = pl_strdup(&name, &pl_name); if (pl_isset(&pl_prm)) err |= pl_strdup(&prm, &pl_prm); if (err) goto out; cmd_long = cmd_find_long(commands, name); if (cmd_long) { arg.key = LONG_PREFIX; arg.prm = prm; arg.complete = true; arg.data = data; if (cmd_long->h) err = cmd_long->h(pf_resp, &arg); } else { err = re_hprintf(pf_resp, "command not found (%s)\n", name); } out: mem_deref(name); mem_deref(prm); return err; }