static void req_process_set(struct context *ctx, struct conn *conn, struct msg *msg) { uint8_t *key, nkey, cid; struct item *it; key = msg->key_start; nkey = (uint8_t)(msg->key_end - msg->key_start); cid = item_slabcid(nkey, msg->vlen); if (cid == SLABCLASS_INVALID_ID) { rsp_send_error(ctx, conn, msg, MSG_RSP_CLIENT_ERROR, EINVAL); return; } itemx_removex(msg->hash, msg->md); it = item_get(key, nkey, cid, msg->vlen, time_reltime(msg->expiry), msg->flags, msg->md, msg->hash); if (it == NULL) { rsp_send_error(ctx, conn, msg, MSG_RSP_SERVER_ERROR, ENOMEM); return; } mbuf_copy_to(&msg->mhdr, msg->value, item_data(it), msg->vlen); rsp_send_status(ctx, conn, msg, MSG_RSP_STORED); }
static int get_query_params(struct evhttp_request *req, struct evkeyvalq *query, struct query_params *qp) { const char *param; int ret; qp->offset = 0; param = evhttp_find_header(query, "offset"); if (param) { ret = safe_atoi32(param, &qp->offset); if (ret < 0) { rsp_send_error(req, "Invalid offset"); return -1; } } qp->limit = 0; param = evhttp_find_header(query, "limit"); if (param) { ret = safe_atoi32(param, &qp->limit); if (ret < 0) { rsp_send_error(req, "Invalid limit"); return -1; } } if (qp->offset || qp->limit) qp->idx_type = I_SUB; else qp->idx_type = I_NONE; qp->sort = S_NONE; param = evhttp_find_header(query, "query"); if (param) { DPRINTF(E_DBG, L_RSP, "RSP browse query filter: %s\n", param); qp->filter = rsp_query_parse_sql(param); if (!qp->filter) DPRINTF(E_LOG, L_RSP, "Ignoring improper RSP query\n"); } return 0; }
static void rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply) { struct evbuffer *evbuf; evbuf = mxml_to_evbuf(reply); mxmlDelete(reply); if (!evbuf) { rsp_send_error(req, "Could not finalize reply"); return; } evhttp_add_header(req->output_headers, "Content-Type", "text/xml; charset=utf-8"); evhttp_add_header(req->output_headers, "Connection", "close"); httpd_send_reply(req, HTTP_OK, "OK", evbuf); evbuffer_free(evbuf); }
static void req_process_get(struct context *ctx, struct conn *conn, struct msg *msg) { struct itemx *itx; struct item *it; itx = itemx_getx(msg->hash, msg->md); if (itx == NULL) { msg_type_t type; /* * On a miss, we send a "END\r\n" response, unless the request * is an intermediate fragment in a fragmented request. */ if (msg->frag_id == 0 || msg->last_fragment) { type = MSG_RSP_END; } else { type = MSG_EMPTY; } rsp_send_status(ctx, conn, msg, type); return; } /* * On a hit, we read the item with address [sid, offset] and respond * with item value if the item hasn't expired yet. */ it = slab_read_item(itx->sid, itx->offset); if (it == NULL) { rsp_send_error(ctx, conn, msg, MSG_RSP_SERVER_ERROR, errno); return; } if (item_expired(it)) { rsp_send_status(ctx, conn, msg, MSG_RSP_NOT_FOUND); return; } rsp_send_value(ctx, conn, msg, it, itx->cas); }
static void req_process_num(struct context *ctx, struct conn *conn, struct msg *msg) { rstatus_t status; uint8_t *key, nkey, cid; struct item *it; struct itemx *itx; uint64_t cnum, nnum; char numstr[FC_UINT64_MAXLEN]; int n; key = msg->key_start; nkey = (uint8_t)(msg->key_end - msg->key_start); /* 1). look up existing itemx */ itx = itemx_getx(msg->hash, msg->md); if (itx == NULL) { /* 2a). miss -> return NOT_FOUND */ rsp_send_status(ctx, conn, msg, MSG_RSP_NOT_FOUND); return; } /* 2b). hit -> read existing item into it */ it = slab_read_item(itx->sid, itx->offset); if (it == NULL) { rsp_send_error(ctx, conn, msg, MSG_RSP_SERVER_ERROR, errno); return; } if (item_expired(it)) { rsp_send_status(ctx, conn, msg, MSG_RSP_NOT_FOUND); return; } /* 3). sanity check item data to be a number */ status = fc_atou64(item_data(it), it->ndata, &cnum); if (status != FC_OK) { rsp_send_error(ctx, conn, msg, MSG_RSP_CLIENT_ERROR, EINVAL); return; } /* 4). remove existing itemx of it */ itemx_removex(msg->hash, msg->md); /* 5). compute the new incr/decr number nnum and numstr */ if (msg->type == MSG_REQ_INCR) { nnum = cnum + msg->num; } else { if (cnum < msg->num) { nnum = 0; } else { nnum = cnum - msg->num; } } n = fc_scnprintf(numstr, sizeof(numstr), "%"PRIu64"", nnum); /* 6). alloc new item that can hold n worth of bytes */ cid = item_slabcid(nkey, n); ASSERT(cid != SLABCLASS_INVALID_ID); it = item_get(key, nkey, cid, n, time_reltime(msg->expiry), msg->flags, msg->md, msg->hash); if (it == NULL) { rsp_send_error(ctx, conn, msg, MSG_RSP_SERVER_ERROR, ENOMEM); return; } /* 7). copy numstr to it */ fc_memcpy(item_data(it), numstr, n); rsp_send_num(ctx, conn, msg, it); }
static void req_process_concat(struct context *ctx, struct conn *conn, struct msg *msg) { uint8_t *key, nkey, cid; struct item *oit, *it; uint32_t ndata; struct itemx *itx; key = msg->key_start; nkey = (uint8_t)(msg->key_end - msg->key_start); /* 1). look up existing itemx */ itx = itemx_getx(msg->hash, msg->md); if (itx == NULL) { /* 2a). miss -> return NOT_STORED */ rsp_send_status(ctx, conn, msg, MSG_RSP_NOT_STORED); return; } /* 2b). hit -> read existing item into oit */ oit = slab_read_item(itx->sid, itx->offset); if (oit == NULL) { rsp_send_error(ctx, conn, msg, MSG_RSP_SERVER_ERROR, errno); return; } if (item_expired(oit)) { rsp_send_status(ctx, conn, msg, MSG_RSP_NOT_STORED); return; } ndata = msg->vlen + oit->ndata; cid = item_slabcid(nkey, ndata); if (cid == SLABCLASS_INVALID_ID) { rsp_send_error(ctx, conn, msg, MSG_RSP_CLIENT_ERROR, EINVAL); return; } /* 3). remove existing itemx of oit */ itemx_removex(msg->hash, msg->md); /* 4). alloc new item that can hold ndata worth of bytes */ it = item_get(key, nkey, cid, ndata, time_reltime(msg->expiry), msg->flags, msg->md, msg->hash); if (it == NULL) { rsp_send_error(ctx, conn, msg, MSG_RSP_SERVER_ERROR, ENOMEM); return; } /* 5). copy data from msg to head or tail of new item it */ switch (msg->type) { case MSG_REQ_PREPEND: mbuf_copy_to(&msg->mhdr, msg->value, item_data(it), msg->vlen); fc_memcpy(item_data(it) + msg->vlen, item_data(oit), oit->ndata); break; case MSG_REQ_APPEND: fc_memcpy(item_data(it), item_data(oit), oit->ndata); mbuf_copy_to(&msg->mhdr, msg->value, item_data(it) + oit->ndata, msg->vlen); break; default: NOT_REACHED(); } rsp_send_status(ctx, conn, msg, MSG_RSP_STORED); }
void rsp_request(struct evhttp_request *req) { char *full_uri; char *uri; char *ptr; char *uri_parts[5]; struct evkeyvalq query; cfg_t *lib; char *libname; char *passwd; int handler; int i; int ret; memset(&query, 0, sizeof(struct evkeyvalq)); full_uri = httpd_fixup_uri(req); if (!full_uri) { rsp_send_error(req, "Server error"); return; } ptr = strchr(full_uri, '?'); if (ptr) *ptr = '\0'; uri = strdup(full_uri); if (!uri) { rsp_send_error(req, "Server error"); free(full_uri); return; } if (ptr) *ptr = '?'; ptr = uri; uri = evhttp_decode_uri(uri); free(ptr); DPRINTF(E_DBG, L_RSP, "RSP request: %s\n", full_uri); handler = -1; for (i = 0; rsp_handlers[i].handler; i++) { ret = regexec(&rsp_handlers[i].preg, uri, 0, NULL, 0); if (ret == 0) { handler = i; break; } } if (handler < 0) { DPRINTF(E_LOG, L_RSP, "Unrecognized RSP request\n"); rsp_send_error(req, "Bad path"); free(uri); free(full_uri); return; } /* Check authentication */ lib = cfg_getsec(cfg, "library"); passwd = cfg_getstr(lib, "password"); if (passwd) { libname = cfg_getstr(lib, "name"); DPRINTF(E_DBG, L_HTTPD, "Checking authentication for library '%s'\n", libname); /* We don't care about the username */ ret = httpd_basic_auth(req, NULL, passwd, libname); if (ret != 0) { free(uri); free(full_uri); return; } DPRINTF(E_DBG, L_HTTPD, "Library authentication successful\n"); } memset(uri_parts, 0, sizeof(uri_parts)); uri_parts[0] = strtok_r(uri, "/", &ptr); for (i = 1; (i < sizeof(uri_parts) / sizeof(uri_parts[0])) && uri_parts[i - 1]; i++) { uri_parts[i] = strtok_r(NULL, "/", &ptr); } if (!uri_parts[0] || uri_parts[i - 1] || (i < 2)) { DPRINTF(E_LOG, L_RSP, "RSP URI has too many/few components (%d)\n", (uri_parts[0]) ? i : 0); rsp_send_error(req, "Bad path"); free(uri); free(full_uri); return; } evhttp_parse_query(full_uri, &query); rsp_handlers[handler].handler(req, uri_parts, &query); evhttp_clear_headers(&query); free(uri); free(full_uri); }
static void rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query) { struct query_params qp; char *browse_item; mxml_node_t *reply; mxml_node_t *status; mxml_node_t *items; mxml_node_t *node; int records; int ret; memset(&qp, 0, sizeof(struct query_params)); if (strcmp(uri[3], "artist") == 0) qp.type = Q_BROWSE_ARTISTS; else if (strcmp(uri[3], "genre") == 0) qp.type = Q_BROWSE_GENRES; else if (strcmp(uri[3], "album") == 0) qp.type = Q_BROWSE_ALBUMS; else if (strcmp(uri[3], "composer") == 0) qp.type = Q_BROWSE_COMPOSERS; else { DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", uri[3]); rsp_send_error(req, "Unsupported browse type"); return; } ret = safe_atoi32(uri[2], &qp.id); if (ret < 0) { rsp_send_error(req, "Invalid playlist ID"); return; } ret = get_query_params(req, query, &qp); if (ret < 0) return; ret = db_query_start(&qp); if (ret < 0) { DPRINTF(E_LOG, L_RSP, "Could not start query\n"); rsp_send_error(req, "Could not start query"); if (qp.filter) free(qp.filter); return; } if (qp.offset > qp.results) records = 0; else if (qp.limit > (qp.results - qp.offset)) records = qp.results - qp.offset; else records = qp.limit; /* We'd use mxmlNewXML(), but then we can't put any attributes * on the root node and we need some. */ reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT); node = mxmlNewElement(reply, "response"); status = mxmlNewElement(node, "status"); items = mxmlNewElement(node, "items"); /* Status block */ node = mxmlNewElement(status, "errorcode"); mxmlNewText(node, 0, "0"); node = mxmlNewElement(status, "errorstring"); mxmlNewText(node, 0, ""); node = mxmlNewElement(status, "records"); mxmlNewTextf(node, 0, "%d", records); node = mxmlNewElement(status, "totalrecords"); mxmlNewTextf(node, 0, "%d", qp.results); /* Items block (all items) */ while (((ret = db_query_fetch_string(&qp, &browse_item)) == 0) && (browse_item)) { node = mxmlNewElement(items, "item"); mxmlNewText(node, 0, browse_item); } if (qp.filter) free(qp.filter); if (ret < 0) { DPRINTF(E_LOG, L_RSP, "Error fetching results\n"); mxmlDelete(reply); db_query_end(&qp); rsp_send_error(req, "Error fetching query results"); return; } /* HACK * Add a dummy empty string to the items element if there is no data * to return - this prevents mxml from sending out an empty <items/> * tag that the SoundBridge does not handle. It's hackish, but it works. */ if (qp.results == 0) mxmlNewText(items, 0, ""); db_query_end(&qp); rsp_send_reply(req, reply); }
static void rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *query) { struct query_params qp; struct db_media_file_info dbmfi; const char *param; char **strval; mxml_node_t *reply; mxml_node_t *status; mxml_node_t *items; mxml_node_t *item; mxml_node_t *node; int mode; int records; int transcode; int32_t bitrate; int i; int ret; memset(&qp, 0, sizeof(struct query_params)); ret = safe_atoi32(uri[2], &qp.id); if (ret < 0) { rsp_send_error(req, "Invalid playlist ID"); return; } if (qp.id == 0) qp.type = Q_ITEMS; else qp.type = Q_PLITEMS; mode = F_FULL; param = evhttp_find_header(query, "type"); if (param) { if (strcasecmp(param, "full") == 0) mode = F_FULL; else if (strcasecmp(param, "browse") == 0) mode = F_BROWSE; else if (strcasecmp(param, "id") == 0) mode = F_ID; else if (strcasecmp(param, "detailed") == 0) mode = F_DETAILED; else DPRINTF(E_LOG, L_RSP, "Unknown browse mode %s\n", param); } ret = get_query_params(req, query, &qp); if (ret < 0) return; ret = db_query_start(&qp); if (ret < 0) { DPRINTF(E_LOG, L_RSP, "Could not start query\n"); rsp_send_error(req, "Could not start query"); if (qp.filter) free(qp.filter); return; } if (qp.offset > qp.results) records = 0; else if (qp.limit > (qp.results - qp.offset)) records = qp.results - qp.offset; else records = qp.limit; /* We'd use mxmlNewXML(), but then we can't put any attributes * on the root node and we need some. */ reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT); node = mxmlNewElement(reply, "response"); status = mxmlNewElement(node, "status"); items = mxmlNewElement(node, "items"); /* Status block */ node = mxmlNewElement(status, "errorcode"); mxmlNewText(node, 0, "0"); node = mxmlNewElement(status, "errorstring"); mxmlNewText(node, 0, ""); node = mxmlNewElement(status, "records"); mxmlNewTextf(node, 0, "%d", records); node = mxmlNewElement(status, "totalrecords"); mxmlNewTextf(node, 0, "%d", qp.results); /* Items block (all items) */ while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id)) { transcode = transcode_needed(req->input_headers, dbmfi.codectype); /* Item block (one item) */ item = mxmlNewElement(items, "item"); for (i = 0; rsp_fields[i].field; i++) { if (!(rsp_fields[i].flags & mode)) continue; strval = (char **) ((char *)&dbmfi + rsp_fields[i].offset); if (!(*strval) || (strlen(*strval) == 0)) continue; node = mxmlNewElement(item, rsp_fields[i].field); if (!transcode) mxmlNewText(node, 0, *strval); else { switch (rsp_fields[i].offset) { case dbmfi_offsetof(type): mxmlNewText(node, 0, "wav"); break; case dbmfi_offsetof(bitrate): bitrate = 0; ret = safe_atoi32(dbmfi.samplerate, &bitrate); if ((ret < 0) || (bitrate == 0)) bitrate = 1411; else bitrate = (bitrate * 8) / 250; mxmlNewTextf(node, 0, "%d", bitrate); break; case dbmfi_offsetof(description): mxmlNewText(node, 0, "wav audio file"); break; case dbmfi_offsetof(codectype): mxmlNewText(node, 0, "wav"); node = mxmlNewElement(item, "original_codec"); mxmlNewText(node, 0, *strval); break; default: mxmlNewText(node, 0, *strval); break; } } } } if (qp.filter) free(qp.filter); if (ret < 0) { DPRINTF(E_LOG, L_RSP, "Error fetching results\n"); mxmlDelete(reply); db_query_end(&qp); rsp_send_error(req, "Error fetching query results"); return; } /* HACK * Add a dummy empty string to the items element if there is no data * to return - this prevents mxml from sending out an empty <items/> * tag that the SoundBridge does not handle. It's hackish, but it works. */ if (qp.results == 0) mxmlNewText(items, 0, ""); db_query_end(&qp); rsp_send_reply(req, reply); }
static void rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query) { struct query_params qp; struct db_playlist_info dbpli; char **strval; mxml_node_t *reply; mxml_node_t *status; mxml_node_t *pls; mxml_node_t *pl; mxml_node_t *node; int i; int ret; memset(&qp, 0, sizeof(struct db_playlist_info)); qp.type = Q_PL; qp.idx_type = I_NONE; ret = db_query_start(&qp); if (ret < 0) { DPRINTF(E_LOG, L_RSP, "Could not start query\n"); rsp_send_error(req, "Could not start query"); return; } /* We'd use mxmlNewXML(), but then we can't put any attributes * on the root node and we need some. */ reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT); node = mxmlNewElement(reply, "response"); status = mxmlNewElement(node, "status"); pls = mxmlNewElement(node, "playlists"); /* Status block */ node = mxmlNewElement(status, "errorcode"); mxmlNewText(node, 0, "0"); node = mxmlNewElement(status, "errorstring"); mxmlNewText(node, 0, ""); node = mxmlNewElement(status, "records"); mxmlNewTextf(node, 0, "%d", qp.results); node = mxmlNewElement(status, "totalrecords"); mxmlNewTextf(node, 0, "%d", qp.results); /* Playlists block (all playlists) */ while (((ret = db_query_fetch_pl(&qp, &dbpli)) == 0) && (dbpli.id)) { /* Playlist block (one playlist) */ pl = mxmlNewElement(pls, "playlist"); for (i = 0; pl_fields[i].field; i++) { if (pl_fields[i].flags & F_FULL) { strval = (char **) ((char *)&dbpli + pl_fields[i].offset); node = mxmlNewElement(pl, pl_fields[i].field); mxmlNewText(node, 0, *strval); } } } if (ret < 0) { DPRINTF(E_LOG, L_RSP, "Error fetching results\n"); mxmlDelete(reply); db_query_end(&qp); rsp_send_error(req, "Error fetching query results"); return; } /* HACK * Add a dummy empty string to the playlists element if there is no data * to return - this prevents mxml from sending out an empty <playlists/> * tag that the SoundBridge does not handle. It's hackish, but it works. */ if (qp.results == 0) mxmlNewText(pls, 0, ""); db_query_end(&qp); rsp_send_reply(req, reply); }