Exemple #1
0
static void
satip_discovery_send_msearch(void *aux)
{
#define MSG "\
M-SEARCH * HTTP/1.1\r\n\
HOST: 239.255.255.250:1900\r\n\
MAN: \"ssdp:discover\"\r\n\
MX: 2\r\n\
ST: urn:ses-com:device:SatIPServer:1\r\n"
  int attempt = ((intptr_t)aux) % 10;
  htsbuf_queue_t q;

  /* UDP is not reliable - send this message three times */
  if (attempt < 1 || attempt > 3)
    return;
  if (satip_discovery_service == NULL)
    return;

  htsbuf_queue_init(&q, 0);
  htsbuf_append(&q, MSG, sizeof(MSG)-1);
  htsbuf_qprintf(&q, "USER-AGENT: unix/1.0 UPnP/1.1 TVHeadend/%s\r\n", tvheadend_version);
  htsbuf_append(&q, "\r\n", 2);
  upnp_send(&q, NULL, 0);
  htsbuf_queue_flush(&q);

  gtimer_arm_ms(&satip_discovery_msearch_timer, satip_discovery_send_msearch,
                (void *)(intptr_t)(attempt + 1), attempt * 11);
#undef MSG
}
Exemple #2
0
static int
hc_prop(http_connection_t *hc, const char *remain, void *opaque,
        http_cmd_t method)
{
    htsbuf_queue_t out;
    rstr_t *r;
    int rval, i;
    prop_t *p;
    const char *action = http_arg_get_req(hc, "action");

    if(remain == NULL)
        return 404;

    p = prop_from_path(remain);

    if(p == NULL)
        return 404;

    htsbuf_queue_init(&out, 0);

    switch(method) {
    case HTTP_CMD_GET:

        if(action != NULL) {
            event_t *e = event_create_action_str(action);
            prop_send_ext_event(p, e);
            event_release(e);
            rval = HTTP_STATUS_OK;
            break;
        }

        r = prop_get_string(p, NULL);

        if(r == NULL) {

            char **childs = prop_get_name_of_childs(p);
            if(childs == NULL) {
                rval = HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE;
                break;
            }
            for(i = 0; childs[i] != NULL; i++) {
                htsbuf_qprintf(&out, "\t%s\n", childs[i]);
            }
        } else {
            htsbuf_append(&out, rstr_get(r), strlen(rstr_get(r)));
            htsbuf_append(&out, "\n", 1);
            rstr_release(r);
        }
        rval = http_send_reply(hc, 0, "text/ascii", NULL, NULL, 0, &out);
        break;

    default:
        rval = HTTP_STATUS_METHOD_NOT_ALLOWED;
        break;
    }

    prop_ref_dec(p);

    return rval;
}
Exemple #3
0
static void
md_style(htsbuf_queue_t *hq, const char *style, const char *s)
{
  size_t l = strlen(style);
  htsbuf_append(hq, style, l);
  htsbuf_append_str(hq, s);
  htsbuf_append(hq, style, l);
}
Exemple #4
0
static int
md_nl(htsbuf_queue_t *hq, int nl)
{
  if (nl)
    htsbuf_append(hq, "\n", 1);
  return 1;
}
Exemple #5
0
/**
 * List of all classes with documentation
 */
static int
http_markdown_classes(http_connection_t *hc)
{
  idclass_t const **all, **all2;
  const idclass_t *ic;
  htsbuf_queue_t *hq = &hc->hc_reply;

  pthread_mutex_lock(&global_lock);
  all = idclass_find_all();
  if (all == NULL) {
    pthread_mutex_unlock(&global_lock);
    return HTTP_STATUS_NOT_FOUND;
  }
  for (all2 = all; *all2; all2++) {
    ic = *all2;
    if (ic->ic_caption) {
      htsbuf_append_str(hq, ic->ic_class);
      htsbuf_append(hq, "\n", 1);
    }
  }
  pthread_mutex_unlock(&global_lock);

  free(all);
  return 0;
}
Exemple #6
0
static void
md_header(htsbuf_queue_t *hq, const char *prefix, const char *s)
{
  htsbuf_append_str(hq, prefix);
  htsbuf_append_str(hq, s);
  htsbuf_append(hq, "\n", 1);
}
Exemple #7
0
static void
screenshot_response_task(void *task)
{
  response_t *r = task;
  hts_mutex_lock(&screenshot_mutex);
  if(screenshot_connection == NULL) {
    hts_mutex_unlock(&screenshot_mutex);
  } else {
    http_connection_t *hc = screenshot_connection;
    screenshot_connection = NULL;
    hts_mutex_unlock(&screenshot_mutex);

    if(r->url != NULL) {
      http_redirect(hc, r->url);
    } else {
      const char *msg = r->errmsg;
      if(msg == NULL)
        msg = "Error not specified";
      htsbuf_queue_t out;
      htsbuf_queue_init(&out, 0);
      htsbuf_append(&out, msg, strlen(msg));
      htsbuf_append_byte(&out, '\n');
      http_send_reply(hc, 500, "text/plain", NULL, NULL, 0, &out);
    }
  }
  free(r->url);
  free(r->errmsg);
  free(r);
}
Exemple #8
0
static void
js_json_emit_jsval(JSContext *cx, jsval value, htsbuf_queue_t *out)
{
  char buf[100];
  if(JSVAL_IS_BOOLEAN(value)) {
    if(JSVAL_TO_BOOLEAN(value))
      htsbuf_append(out, "true", 4);
    else
      htsbuf_append(out, "false", 5);
  } else if(JSVAL_IS_INT(value)) {
    snprintf(buf, sizeof(buf), "%d", JSVAL_TO_INT(value));
    htsbuf_append(out, buf, strlen(buf));
  } else if(JSVAL_IS_DOUBLE(value)) {
    double dbl;
    if(JS_ValueToNumber(cx, value, &dbl) &&
       !my_double2str(buf, sizeof(buf), dbl))
      htsbuf_append(out, buf, strlen(buf));
    else
      htsbuf_append(out, "null", 4);
  } else if(JSVAL_IS_NULL(value)) {
    htsbuf_append(out, "null", 4);
  } else if(JSVAL_IS_STRING(value)) {
    js_json_emit_str(cx, value, out);
  } else if(JSVAL_IS_OBJECT(value)) {
    JSObject *obj = JSVAL_TO_OBJECT(value);
    JSClass *c = JS_GetClass(cx, obj);

    if(!strcmp(c->name, "XML"))   // Treat some classes special
      js_json_emit_str(cx, value, out);
    else {
      if(json_encode_from_object(cx, obj, out))
	htsbuf_append(out, "null", 4);
    }
  }
}
Exemple #9
0
static int
os_write(struct tcp_stream *ts, const void *data, int len)
{
  if(!ts->ts_nonblock)
    return safe_write(ts->ts_fd, data, len);

  htsbuf_append(&ts->ts_sendq, data, len);
  os_write_try(ts);
  return len;
}
Exemple #10
0
static void
md_text(htsbuf_queue_t *hq, const char *first, const char *next, const char *text)
{
  char *s, *t, *p;
  int col, nl;

  t = s = p = tvh_strdupa(text);
  col = nl = 0;
  while (*s) {
    if (++col > 74) {
      nl = md_nl(hq, nl);
      if (first) {
        htsbuf_append_str(hq, first);
        first = NULL;
      } else if (next) {
        htsbuf_append_str(hq, next);
      }
      if (p <= t)
        p = t + 74;
      htsbuf_append(hq, t, p - t);
      col = 0;
      t = s = p;
    } else if (*s <= ' ') {
      *s = ' ';
      p = ++s;
    } else {
      s++;
    }
  }
  if (t < s) {
    md_nl(hq, nl);
    if (first)
      htsbuf_append_str(hq, first);
    else if (next)
      htsbuf_append_str(hq, next);
    htsbuf_append(hq, t, s - t);
  }
}
Exemple #11
0
static int
hc_image(http_connection_t *hc, const char *remain, void *opaque,
         http_cmd_t method)
{
    htsbuf_queue_t out;
    pixmap_t *pm;
    char errbuf[200];
    const char *content;
    image_meta_t im = {0};
    im.im_no_decoding = 1;

    rstr_t *url = rstr_alloc(remain);

    pm = backend_imageloader(url, &im, NULL, errbuf, sizeof(errbuf), NULL,
                             NULL, NULL);
    rstr_release(url);
    if(pm == NULL)
        return http_error(hc, 404, "Unable to load image %s : %s",
                          remain, errbuf);

    if(!pixmap_is_coded(pm)) {
        pixmap_release(pm);
        return http_error(hc, 404,
                          "Unable to load image %s : Original data not available",
                          remain);
    }

    htsbuf_queue_init(&out, 0);
    htsbuf_append(&out, pm->pm_data, pm->pm_size);

    switch(pm->pm_type) {
    case PIXMAP_JPEG:
        content = "image/jpeg";
        break;
    case PIXMAP_PNG:
        content = "image/png";
        break;
    case PIXMAP_GIF:
        content = "image/gif";
        break;
    default:
        content = "image";
        break;
    }

    pixmap_release(pm);

    return http_send_reply(hc, 0, content, NULL, NULL, 0, &out);
}
Exemple #12
0
static int
hc_open(http_connection_t *hc, const char *remain, void *opaque,
	http_cmd_t method)
{
  htsbuf_queue_t out;

  const char *url = http_arg_get_req(hc, "url");

  if(url != NULL) {
    event_dispatch(event_create_openurl(url, NULL, NULL, NULL, NULL, NULL));
    return http_redirect(hc, "/showtime/open");
  }

  htsbuf_queue_init(&out, 0);
  htsbuf_append(&out, openpage, strlen(openpage));
  return http_send_reply(hc, 0, "text/html", NULL, NULL, 0, &out);
}
Exemple #13
0
static int
ssl_write(struct tcp_stream *ts, const void *data, int len)
{
  if(!ts->ts_nonblock) {
    int r = SSL_write(ts->ts_ssl, data, len);
    if(r > 0)
      return r;
    errno = EBADMSG;
    return -1;
  }

  htsbuf_append(&ts->ts_sendq, data, len);

  if(ts->ts_read_status != SSL_ERROR_WANT_WRITE)
    ssl_write_try(ts);

  return len;
}
Exemple #14
0
static int
vimeo_code(http_connection_t *hc, const char *remain,
      void *opaque)
{
  state_entry_t *vce;
  static state_entry_t *skel;

  const char *referer = http_arg_get(&hc->hc_args, "referer");
  if(referer == NULL || strcmp(referer, "https://movian.tv/"))
    return 403;

  const char *state = http_arg_get(&hc->hc_req_args, "state");
  if(state == NULL)
    return 400;

  if(skel == NULL)
    skel = calloc(1, sizeof(state_entry_t));

  skel->se_state = (char *)state;

  pthread_mutex_lock(&state_mutex);

  vce = RB_FIND(&state_entries, skel, se_entry, state_entry_cmp);

  pthread_mutex_unlock(&state_mutex);

  if (vce == NULL)
    return 404;

  char *code = vce->se_code;

  htsmsg_t *msg = htsmsg_create_map();
  htsmsg_add_str(msg, "code", code);

  char *out = htsmsg_json_serialize_to_str(msg, 0);
  htsbuf_append(&hc->hc_reply, out, strlen(out));
  free(out);
  return http_send_reply(hc, 200, "application/json", NULL, NULL, 0);
}
Exemple #15
0
static int
hc_prop(http_connection_t *hc, const char *remain, void *opaque,
	http_cmd_t method)
{
  htsbuf_queue_t out;
  rstr_t *r;
  int rval, i;
  prop_t *p = NULL;
  char *req = (char *)http_arg_get_req(hc, "requests");
  char *request;
  char *saved;

  if(req == NULL)
    return 404;

  htsbuf_queue_init(&out, 0);

  switch(method) {
  case HTTP_CMD_POST:
    for (request = strtok_r(req, ",", &saved);
         request;
         request = strtok_r(NULL, ",", &saved))
    {
      p = prop_from_path(request);
      if (p == NULL) {
        htsbuf_qprintf(&out, "error:404");
      }
      else {
        r = prop_get_string(p, NULL);

        if(r == NULL) {

          char **childs = prop_get_name_of_childs(p);
          if(childs == NULL) {
            htsbuf_qprintf(&out, "error:404");
          }
          else {
            htsbuf_qprintf(&out, "dir");
            for(i = 0; childs[i] != NULL; i++) {
	      htsbuf_qprintf(&out, "%c%s", i ? ',' : ':', childs[i]);
            }
          }
        } else {
          htsbuf_qprintf(&out, "value:");
          htsbuf_append(&out, rstr_get(r), strlen(rstr_get(r)));
          rstr_release(r);
        }
      }
      htsbuf_append(&out, "\n", 1);
    }
    rval = http_send_reply(hc, 0, "text/ascii", NULL, NULL, 0, &out);
    break;

  default:
    rval = HTTP_STATUS_METHOD_NOT_ALLOWED;
    break;
  }

  prop_ref_dec(p);

  return rval;
}
Exemple #16
0
static int
hc_image(http_connection_t *hc, const char *remain, void *opaque,
	http_cmd_t method)
{
  htsbuf_queue_t out;
  image_t *img;
  char errbuf[200];
  const char *content;
  image_meta_t im = {0};
  im.im_no_decoding = 1;
  rstr_t *url;
  const char *u = http_arg_get_req(hc, "url");
  
  if(u != NULL) {
    url = rstr_alloc(u);
    url_deescape(rstr_data(url));
  } else {
    if(remain == NULL) {
      return 404;
    }
    url = rstr_alloc(remain);
  }

  img = backend_imageloader(url, &im, NULL, errbuf, sizeof(errbuf), NULL,
                            NULL);
  rstr_release(url);
  if(img == NULL)
    return http_error(hc, 404, "Unable to load image %s : %s",
		      remain, errbuf);

  const image_component_t *ic = image_find_component(img, IMAGE_CODED);
  if(ic == NULL) {
    image_release(img);
    return http_error(hc, 404,
		      "Unable to load image %s : Original data not available",
		      remain);
  }
  const image_component_coded_t *icc = &ic->coded;

  htsbuf_queue_init(&out, 0);
  htsbuf_append(&out, buf_cstr(icc->icc_buf), buf_len(icc->icc_buf));

  switch(icc->icc_type) {
  case IMAGE_JPEG:
    content = "image/jpeg";
    break;
  case IMAGE_PNG:
    content = "image/png";
    break;
  case IMAGE_GIF:
    content = "image/gif";
    break;
  default:
    content = "image";
    break;
  }

  image_release(img);

  return http_send_reply(hc, 0, content, NULL, NULL, 0, &out);
}
Exemple #17
0
static int
md_props(htsbuf_queue_t *hq, htsmsg_t *m, const char *lang, int nl)
{
  htsmsg_t *l, *n, *e, *x;
  htsmsg_field_t *f, *f2;
  const char *s;
  int first = 1, b;

  l = htsmsg_get_list(m, "props");
  HTSMSG_FOREACH(f, l) {
    n = htsmsg_field_get_map(f);
    if (!n) continue;
    if (!htsmsg_get_bool(n, "noui", &b) && b) continue;
    s = htsmsg_get_str(n, "caption");
    if (!s) continue;
    if (first) {
      nl = md_nl(hq, nl);
      htsbuf_append_str(hq, "#### ");
      htsbuf_append_str(hq, tvh_gettext_lang(lang, N_("Items")));
      md_nl(hq, 1);
      md_nl(hq, 1);
      htsbuf_append_str(hq, tvh_gettext_lang(lang, N_("The items have the following functions:")));
      md_nl(hq, 1);
      first = 0;
    }
    nl = md_nl(hq, nl);
    md_style(hq, "**", s);
    if (!htsmsg_get_bool(n, "rdonly", &b) && b) {
      htsbuf_append(hq, " _", 2);
      htsbuf_append_str(hq, tvh_gettext_lang(lang, N_("(Read-only)")));
      htsbuf_append(hq, "_", 1);
    }
    md_nl(hq, 1);
    s = htsmsg_get_str(n, "description");
    if (s) {
      md_text(hq, ": ", "  ", s);
      md_nl(hq, 1);
    }
    s = htsmsg_get_str(n, "doc");
    if (s) {
      htsbuf_append_str(hq, s);
      md_nl(hq, 1);
    }
    if (!htsmsg_get_bool_or_default(n, "doc_nlist", 0)) {
      e = htsmsg_get_list(n, "enum");
      if (e) {
        HTSMSG_FOREACH(f2, e) {
          x = htsmsg_field_get_map(f2);
          if (x) {
            s = htsmsg_get_str(x, "val");
          } else {
            s = htsmsg_field_get_string(f2);
          }
          if (s) {
            md_nl(hq, 1);
            htsbuf_append(hq, "  * ", 4);
            md_style(hq, "**", s);
          }
        }
        md_nl(hq, 1);
      }
Exemple #18
0
static void
screenshot_process(void *task)
{
  pixmap_t *pm = task;

  if(pm == NULL) {
    screenshot_response(NULL, "Screenshot not supported on this platform");
    return;
  }

  TRACE(TRACE_DEBUG, "Screenshot", "Processing image %d x %d",
        pm->pm_width, pm->pm_height);

  int codecid = AV_CODEC_ID_PNG;
  if(screenshot_connection)
    codecid = AV_CODEC_ID_MJPEG;

  buf_t *b = screenshot_compress(pm, codecid);
  pixmap_release(pm);
  if(b == NULL) {
    screenshot_response(NULL, "Unable to compress image");
    return;
  }

  if(!screenshot_connection) {
    char path[512];
    char errbuf[512];
    snprintf(path, sizeof(path), "%s/screenshot.png",
             gconf.cache_path);
    fa_handle_t *fa = fa_open_ex(path, errbuf, sizeof(errbuf),
                                 FA_WRITE, NULL);
    if(fa == NULL) {
      TRACE(TRACE_ERROR, "SCREENSHOT", "Unable to open %s -- %s",
            path, errbuf);
      buf_release(b);
      return;
    }
    fa_write(fa, buf_data(b), buf_len(b));
    fa_close(fa);
    TRACE(TRACE_INFO, "SCREENSHOT", "Written to %s", path);
    buf_release(b);
    return;
  }

  buf_t *result = NULL;
  htsbuf_queue_t hq;
  htsbuf_queue_init(&hq, 0);

  htsbuf_append(&hq, "image=", 6);
  htsbuf_append_and_escape_url_len(&hq, buf_cstr(b), buf_len(b));

  char errbuf[256];

  int ret = http_req("https://api.imgur.com/3/upload",
                     HTTP_FLAGS(FA_CONTENT_ON_ERROR),
                     HTTP_REQUEST_HEADER("Authorization",
                                         "Client-ID 7c79b311d4797ed"),
                     HTTP_RESULT_PTR(&result),
                     HTTP_POSTDATA(&hq, "application/x-www-form-urlencoded"),
                     HTTP_ERRBUF(errbuf, sizeof(errbuf)),
                     NULL);


  if(ret) {
    screenshot_response(NULL, errbuf);
  } else {

    htsmsg_t *response = htsmsg_json_deserialize(buf_cstr(result));
    if(response == NULL) {
      screenshot_response(NULL, "Unable to parse imgur response");
    } else {

      if(htsmsg_get_u32_or_default(response, "success", 0)) {
        const char *url = htsmsg_get_str_multi(response, "data", "link", NULL);
        screenshot_response(url, "No link in imgur response");
      } else {
        const char *msg = htsmsg_get_str_multi(response, "data", "error", NULL);
        if(msg == NULL) {
          screenshot_response(NULL, "Unkown imgur error");
        } else {
          snprintf(errbuf, sizeof(errbuf), "Imgur error: %s", msg);
          screenshot_response(NULL, errbuf);
        }
      }
      htsmsg_release(response);
    }
    buf_release(result);
  }
  buf_release(b);
}
Exemple #19
0
static int
http_markdown_class(http_connection_t *hc, const char *clazz)
{
  const idclass_t *ic;
  const char *lang = hc->hc_access->aa_lang_ui;
  htsbuf_queue_t *hq = &hc->hc_reply;
  htsmsg_t *m, *l, *n, *e, *x;
  htsmsg_field_t *f, *f2;
  const char *s, **doc;
  int nl = 0, first = 1;

  pthread_mutex_lock(&global_lock);
  ic = idclass_find(clazz);
  if (ic == NULL) {
    pthread_mutex_unlock(&global_lock);
    return HTTP_STATUS_NOT_FOUND;
  }
  doc = ic->ic_doc;
  m = idclass_serialize(ic, lang);
  pthread_mutex_unlock(&global_lock);
  s = htsmsg_get_str(m, "caption");
  if (s) {
    md_header(hq, "##", s);
    nl = md_nl(hq, 1);
  }
  if (doc) {
    for (; *doc; doc++) {
      if (*doc[0] == '\xff') {
        htsbuf_append_str(hq, tvh_gettext_lang(lang, *doc + 1));
      } else {
        htsbuf_append_str(hq, *doc);
      }
    }
  }
  l = htsmsg_get_list(m, "props");
  HTSMSG_FOREACH(f, l) {
    n = htsmsg_field_get_map(f);
    if (!n) continue;
    s = htsmsg_get_str(n, "caption");
    if (!s) continue;
    if (first) {
      nl = md_nl(hq, nl);
      htsbuf_append_str(hq, "####");
      htsbuf_append_str(hq, tvh_gettext_lang(lang, N_("Items")));
      md_nl(hq, 1);
      md_nl(hq, 1);
      htsbuf_append_str(hq, tvh_gettext_lang(lang, N_("The items have the following functions:")));
      md_nl(hq, 1);
      first = 0;
    }
    nl = md_nl(hq, nl);
    md_style(hq, "**", s);
    md_nl(hq, 1);
    s = htsmsg_get_str(n, "description");
    if (s) {
      md_text(hq, ": ", "  ", s);
      md_nl(hq, 1);
    }
    e = htsmsg_get_list(n, "enum");
    if (e) {
      HTSMSG_FOREACH(f2, e) {
        x = htsmsg_field_get_map(f2);
        if (x) {
          s = htsmsg_get_str(x, "val");
        } else {
          s = htsmsg_field_get_string(f2);
        }
        if (s) {
          md_nl(hq, 1);
          htsbuf_append(hq, "  * ", 4);
          md_style(hq, "**", s);
        }
      }
      md_nl(hq, 1);
    }
Exemple #20
0
static void *
lirc_thread(void *aux)
{
  char buf[200];
  uint64_t ircode;
  uint32_t repeat;
  char keyname[100];
  int i, r, fd, len, n;
  htsbuf_queue_t q;
  struct pollfd fds;
  event_t *e;

  fd = lirc_fd;

  htsbuf_queue_init(&q, 0);
  fds.fd = fd;
  fds.events = POLLIN;

  while(1) {

    r = poll(&fds, 1, -1);
    if(r > 0) {
      if((r = read(fd, buf, sizeof(buf))) < 1) {
	TRACE(TRACE_ERROR, "lircd", "Read error: %s", strerror(errno));
	break;
      }
      htsbuf_append(&q, buf, r);
    }

    while((len = htsbuf_find(&q, 0xa)) != -1) {
      
      if(len >= sizeof(buf) - 1) {
	TRACE(TRACE_ERROR, "lircd", "Command buffer size exceeded");
	goto out;
      }

      htsbuf_read(&q, buf, len);
      buf[len] = 0;
      
      while(len > 0 && buf[len - 1] < 32)
	buf[--len] = 0;
      htsbuf_drop(&q, 1); /* Drop the \n */
      
      n = sscanf(buf, "%"PRIx64" %x %s", &ircode, &repeat, keyname);
      if(n != 3) {
	TRACE(TRACE_INFO, "lircd", "Invalid LIRC input: \"%s\"", buf);
	continue;
      }
      
      if(keyname[0] && keyname[1] == 0) {
	/* ASCII input */
	e = event_create_int(EVENT_UNICODE, keyname[0]);
      } else {
	e = NULL;
	for(i = 0; i < sizeof(lircmap) / sizeof(lircmap[0]); i++) {
	  if(!strcasecmp(keyname, lircmap[i].name)) {
	    action_type_t av[3] = {
	      lircmap[i].action1,
	      lircmap[i].action2,
	    };
	    if(av[1] != ACTION_NONE)
	      e = event_create_action_multi(av, 2);
	    else
	      e = event_create_action_multi(av, 1);
	    break;
	  }
	}
      }
      if(e == NULL) {
	snprintf(buf, sizeof(buf), "IR+%s", keyname);
	e = event_create_str(EVENT_KEYDESC, buf);
      }
      event_to_ui(e);
    }
  }
 out:
  close(fd);
  htsbuf_queue_flush(&q);
  return NULL;
}
Exemple #21
0
static int
json_encode_from_object(JSContext *cx, JSObject *obj, htsbuf_queue_t *out)
{
  int objtype = 0;
  JSIdArray *ida;
  int i;
  const char *n;

  if((ida = JS_Enumerate(cx, obj)) == NULL)
    return -1;
  
  for(i = 0; i < ida->length; i++) {
    jsval name, value;

    if(!JS_IdToValue(cx, ida->vector[i], &name))
      continue;

    if(JSVAL_IS_STRING(name)) {
      JSString *str = JSVAL_TO_STRING(name);
      n = JS_GetStringBytes(str);
      if(!JS_GetProperty(cx, obj, n, &value))
	continue;

      if(objtype == 0) {
	htsbuf_append(out, "{", 1);
	objtype = OBJTYPE_MAP;
      } else if(objtype != OBJTYPE_MAP)
	continue;
      else
	htsbuf_append(out, ",", 1);
      htsbuf_append_and_escape_jsonstr(out, n);
      htsbuf_append(out, ":", 1);

    } else if(JSVAL_IS_INT(name)) {
      if(!JS_GetElement(cx, obj, JSVAL_TO_INT(name), &value) ||
	 JSVAL_IS_VOID(value))
	continue;

      if(objtype == 0) {
	htsbuf_append(out, "[", 1);
	objtype = OBJTYPE_LIST;
      } else if(objtype != OBJTYPE_LIST)
	continue;
      else
	htsbuf_append(out, ",", 1);
      
    } else {
      continue;
    }

    js_json_emit_jsval(cx, value, out);
  }
  JS_DestroyIdArray(cx, ida);

  switch(objtype) {
  case OBJTYPE_LIST:
    htsbuf_append(out, "]", 1);
    break;
  case OBJTYPE_MAP:
    htsbuf_append(out, "}", 1);
    break;
  default:
    return -1;
  }

  return 0;
}