/* LookupTable::for_each callback */ static void cb(VMG_ const vm_val_t *key, const vm_val_t *val, void *ctx) { /* the context is our 'this' pointer */ metatab_saver *self = (metatab_saver *)ctx; CVmFile *fp = self->fp; /* both the key and value must be strings for us to save them */ const char *keystr = key->get_as_string(vmg0_); const char *valstr = val->get_as_string(vmg0_); if (keystr != 0 && valstr != 0) { size_t len; /* save the key string */ fp->write_uint2(len = vmb_get_len(keystr)); fp->write_bytes(keystr + VMB_LEN, len); /* save the value string */ fp->write_uint2(len = vmb_get_len(valstr)); fp->write_bytes(valstr + VMB_LEN, len); /* count the pair */ self->cnt += 1; } }
/* * Get the Unicode character code of the first character of a string */ void CVmBifT3Test::get_charcode(VMG_ uint argc) { const char *str; /* one argument required */ check_argc(vmg_ argc, 1); /* get the object ID as an integer */ str = pop_str_val(vmg0_); /* * if the string is empty, return nil; otherwise, return the Unicode * character code of the first character */ if (vmb_get_len(str) == 0) { /* empty string - return nil */ retval_nil(vmg0_); } else { /* * get the character code of the first character and return it * as an integer */ retval_int(vmg_ (int)utf8_ptr::s_getch(str + VMB_LEN)); } }
/* * find a local variable in our frame by name, given a VM string value */ int CVmObjFrameDesc::find_local(VMG_ const vm_val_t *nval, CVmDbgFrameSymPtr *symp) { /* make sure the name is a string */ const char *name = nval->get_as_string(vmg0_); if (name == 0) err_throw(VMERR_BAD_TYPE_BIF); /* parse the length */ size_t namelen = vmb_get_len(name); name += VMB_LEN; /* look up the symbol */ return find_local(vmg_ name, namelen, symp); }
/* get the storage server URL */ void CVmBifNet::get_storage_url(VMG_ uint oargc) { /* check arguments */ check_argc(vmg_ oargc, 1); /* set a default nil return in case we can't build the path */ retval_nil(vmg0_); /* get the resource name */ const char *page = G_stk->get(0)->get_as_string(vmg0_); if (page == 0) err_throw(VMERR_STRING_VAL_REQD); /* get the resource name length and buffer pointer */ size_t pagelen = vmb_get_len(page); page += VMB_LEN; /* if there's a network configuration, build the resource path */ const char *host = 0, *rootpath = 0; if (G_net_config != 0) { /* get the storage server host name and root path */ host = G_net_config->get("storage.domain"); rootpath = G_net_config->get("storage.rootpath", "/"); } /* we must have a host name to proceed */ if (host != 0) { /* build the full string */ G_interpreter->push_stringf(vmg_ "http://%s%s%.*s", host, rootpath, (int)pagelen, page); /* pop it into R0 */ G_stk->pop(G_interpreter->get_r0()); } /* discard arguments */ G_stk->discard(); }
/* * Look up a named argument. If 'mandatory' is set, we throw an error if * we can't find a resolution. */ void CVmBifT3::get_named_arg(VMG_ uint argc) { /* check arguments */ check_argc_range(vmg_ argc, 1, 2); /* get the name we're looking for */ const char *name = G_stk->get(0)->get_as_string(vmg0_); if (name == 0) err_throw(VMERR_STRING_VAL_REQD); /* get the length and buffer pointer */ size_t namelen = vmb_get_len(name); name += VMB_LEN; /* * Scan the stack for named parameter tables. A named parameter table * is always in the calling frame at the stack slot just beyond the * last argument. */ for (vm_val_t *fp = G_interpreter->get_frame_ptr() ; fp != 0 ; fp = G_interpreter->get_enclosing_frame_ptr(vmg_ fp)) { /* check for a table in this frame */ vm_val_t *argp; const uchar *t = CVmRun::get_named_args_from_frame(vmg_ fp, &argp); if (t != 0) { /* get the number of table entries */ int n = osrp2(t); t += 2; /* scan the table for the name */ for (int i = 0 ; n >= 0 ; --n, i += 2, ++argp) { /* get this element's offset, and figure its length */ uint eofs = osrp2(t + i); uint elen = osrp2(t + i + 2) - eofs; /* check for a match */ if (elen == namelen && memcmp(name, t + eofs, elen) == 0) { /* found it - return the value */ retval(vmg_ argp); /* discard arguments and return */ G_stk->discard(argc); return; } } } } /* * The argument is undefined. If a default value was supplied, simply * return the default value. Otherwise throw an error. */ if (argc >= 2) { /* a default value was supplied - simply return it */ retval(vmg_ G_stk->get(1)); /* discard arguments */ G_stk->discard(argc); } else { /* no default value - throw an error */ err_throw_a(VMERR_MISSING_NAMED_ARG, 1, ERR_TYPE_TEXTCHAR_LEN, name, namelen); } }
/* * Get a list of our properties */ void CVmObjClass::build_prop_list(VMG_ vm_obj_id_t self, vm_val_t *retval) { vm_obj_id_t mod_obj; vm_meta_entry_t *entry; size_t my_prop_cnt; size_t mod_prop_cnt; vm_val_t mod_val; CVmObjList *lst; CVmObjList *mod_lst; /* presume we won't find any static properties of our own */ my_prop_cnt = 0; /* get my metaclass table entry */ entry = get_meta_entry(vmg0_); /* if we have an entry, count the properties */ if (entry != 0) my_prop_cnt = list_class_props(vmg_ self, entry, 0, 0, FALSE); /* if we have a modifier object, get its property list */ if ((mod_obj = get_mod_obj()) != VM_INVALID_OBJ) { /* get the modifier's property list - we'll add it to our own */ vm_objp(vmg_ mod_obj)->build_prop_list(vmg_ self, &mod_val); /* get the result as a list object, properly cast */ mod_lst = (CVmObjList *)vm_objp(vmg_ mod_val.val.obj); /* * As an optimization, if we don't have any properties of our own * to add to the modifier list, just return the modifier list * directly (thus avoiding unnecessarily creating another copy of * the list with no changes). */ if (my_prop_cnt == 0) { /* the modifier list is the entire return value */ *retval = mod_val; return; } /* get the size of the modifier list */ mod_prop_cnt = vmb_get_len(mod_lst->get_as_list()); } else { /* * we have no modifier object - for the result list, we simply * need our own list, so set the modifier list to nil */ mod_val.set_nil(); mod_prop_cnt = 0; mod_lst = 0; } /* for gc protection, push the modifier's list */ G_stk->push(&mod_val); /* * Allocate a list big enough to hold the modifier's list plus our own * list. */ retval->set_obj(CVmObjList:: create(vmg_ FALSE, my_prop_cnt + mod_prop_cnt)); /* push the return value list for gc protection */ G_stk->push(retval); /* get it as a list object, properly cast */ lst = (CVmObjList *)vm_objp(vmg_ retval->val.obj); lst->cons_clear(); /* start the list with our own properties */ if (entry != 0) list_class_props(vmg_ self, entry, lst, 0, FALSE); /* copy the modifier list into the results, if there is a modifier list */ if (mod_prop_cnt != 0) lst->cons_copy_elements(my_prop_cnt, mod_lst->get_as_list()); /* done with the gc protection */ G_stk->discard(2); }
/* * Connect to the Web UI */ void CVmBifNet::connect_ui(VMG_ uint oargc) { /* check arguments */ check_argc(vmg_ oargc, 2); /* get the server object */ vm_val_t *srv = G_stk->get(0); if (srv->typ != VM_OBJ || !CVmObjHTTPServer::is_httpsrv_obj(vmg_ srv->val.obj)) err_throw(VMERR_BAD_TYPE_BIF); /* get the object pointer properly cast */ CVmObjHTTPServer *srvp = (CVmObjHTTPServer *)vm_objp(vmg_ srv->val.obj); /* get the URL path */ const char *path = G_stk->get(1)->get_as_string(vmg0_); if (path == 0) err_throw(VMERR_BAD_TYPE_BIF); /* make a null-terminated copy of the path */ char *pathb = lib_copy_str(path + VMB_LEN, vmb_get_len(path)); /* get the server network address information */ char *errmsg = 0; char *addr = 0, *ip = 0; int port; int ok = srvp->get_listener_addr(addr, ip, port); /* * If we don't have a network configuration yet, create one. This * notifies other subsystems that we have an active web UI; for * example, the presence of a network UI disables the regular console * UI, since all UI actions have to go through the web UI once it's * established. * * The interpreter startup creates a network configuration if it * receives command-line information telling it that the game was * launched by a Web server in response to an incoming client request. * When the user launches the game in stand-alone mode directly from * the operating system shell, there's no Web server launch * information, so the startup code doesn't create a net config object. * So, if we get here and find we don't have this object, it means that * we're running in local standalone mode. */ if (G_net_config == 0) G_net_config = new TadsNetConfig(); /* connect */ if (ok) { /* connect */ ok = osnet_connect_webui(vmg_ addr, port, pathb, &errmsg); } else { /* couldn't get the server address */ errmsg = lib_copy_str( "No address information available for HTTPServer"); } /* free strings */ lib_free_str(pathb); lib_free_str(addr); lib_free_str(ip); /* if there's an error, throw it */ if (!ok) { G_interpreter->push_string(vmg_ errmsg); lib_free_str(errmsg); G_interpreter->throw_new_class(vmg_ G_predef->net_exception, 1, "Error connecting to Web UI"); } /* no return value */ retval_nil(vmg0_); }
/* * Set up the thread. All string parameters are provided in internal * string format, with VMB_LEN length prefixes. */ HttpReqThread(VMG_ const vm_val_t *id, const char *url, const char *verb, int32 options, const char *hdrs, vm_val_t *body, const char *body_type, const vm_rcdesc *rc) { /* save the VM globals */ vmg = VMGLOB_ADDR; /* add a reference on the net event queue */ if ((queue = G_net_queue) != 0) queue->add_ref(); /* create a global for the ID value */ idg = G_obj_table->create_global_var(); idg->val = *id; /* save the option flags */ this->options = options; /* set up to parse the URL */ size_t urll = vmb_get_len(url); url += VMB_LEN; /* note whether the scheme is plain http or https */ https = (urll >= 8 && memcmp(url, "https://", 8) == 0); /* we don't have any URL pieces yet */ host = 0; resource = 0; port = (https ? 443 : 80); /* skip the scheme prefix */ size_t pfx = (urll >= 7 && memcmp(url, "http://", 7) == 0 ? 7 : urll >= 8 && memcmp(url, "https://", 8) == 0 ? 8 : 0); url += pfx, urll -= pfx; /* the host name is the part up to the ':', '/', or end of string */ const char *start; for (start = url ; urll > 0 && *url != ':' && *url != '/' ; ++url, --urll) ; /* save the host name */ host = lib_copy_str(start, url - start); /* parse the port, if present */ if (urll > 0 && *url == ':') { /* parse the port number string */ for (--urll, start = ++url, port = 0 ; urll > 0 && *url != '/' ; ++url, --urll) { if (is_digit(*url)) port = port*10 + value_of_digit(*url); } } /* * The rest (including the '/') is the resource string. If there's * nothing left, the resource is implicitly '/'. */ if (urll > 0) resource = lib_copy_str(url, urll); else resource = lib_copy_str("/"); /* make null-terminated copies of the various strings */ this->verb = lib_copy_str(verb + VMB_LEN, vmb_get_len(verb)); this->hdrs = (hdrs == 0 ? 0 : lib_copy_str(hdrs + VMB_LEN, vmb_get_len(hdrs))); /* if there's a content body, set up the payload */ this->body = 0; if (body != 0) { /* set up an empty payload */ this->body = new OS_HttpPayload(); /* * Convert the body to a stream payload, if it's a string or * ByteArray. If it's a LookupTable, it's a set of form * fields. */ if (body->typ == VM_OBJ && CVmObjLookupTable::is_lookup_table_obj(vmg_ body->val.obj)) { /* * The body is a LookupTable, so we have a set of form * fields to encode as HTML form data. */ /* cast the object value */ CVmObjLookupTable *tab = (CVmObjLookupTable *)vm_objp(vmg_ body->val.obj); /* add each key in the table as a form field */ iter_body_table_ctx ctx(this, rc); tab->for_each(vmg_ iter_body_table, &ctx); } else { /* * It's not a lookup table, so it must be bytes to upload, * as a string or ByteArray. */ const char *def_mime_type = 0; CVmStream *stream = val_to_stream(vmg_ body, &def_mime_type); /* presume we're going to use the default mime type */ const char *mime_type = def_mime_type; size_t mime_type_len = (mime_type != 0 ? strlen(mime_type) : 0); /* if the caller specified a mime type, use it instead */ if (body_type != 0) { mime_type = body_type + VMB_LEN; mime_type_len = vmb_get_len(body_type); } /* add the stream */ this->body->add( "", 0, "", 0, mime_type, mime_type_len, stream); } } }
/* add a form field in TADS-string (VMB_LEN prefixed) format */ void OS_HttpPayload::add_tstr(const char *name, const char *val) { add(new OS_HttpPayloadItem(name + VMB_LEN, vmb_get_len(name), val + VMB_LEN, vmb_get_len(val))); }
/* * displayText(str) - display a text string. */ void CVmBifSample::display_text(VMG_ uint argc) { const char *strp; size_t len; /* * Check to make sure we have the right number of arguments. 'argc' * tells us how many arguments we received from the T3 program. */ check_argc(vmg_ argc, 1); /* * Get the first argument, which is the string to display. * * This will give us a pointer to a string in internal format, which * has a two-byte length prefix. So, once we have the string, we must * get the length from the string pointer, and then skip the length * prefix to get to the real text of the string. * * Arguments from the T3 program to a native function always appear on * the VM stack, which we can access using the pop_xxx_val() functions. * The arguments appear on the stack in order such that the first * pop_xxx_val() gives us the first argument, the second pop_xxx_val() * gives us the second argument, and so on. The pop_xxx_val() * functions REMOVE an argument from the stack - once it's removed, we * can get to the next one. We MUST remove EXACTLY the number of * arguments that we receive before we return. */ strp = pop_str_val(vmg0_); len = vmb_get_len(strp); strp += VMB_LEN; /* * Okay, we have our string, but it's in UTF-8 format, which is an * encoding format for Unicode. We don't want to display Unicode; we * want to display the local character set. How do we do this? * Fortunately, T3 provides a handy character mapping subsystem that * will let us convert the string fairly automatically. The VM also * gives us a pre-loaded mapper for this specific kind of conversion, * in the object G_cmap_to_ui. G_cmap_to_ui will map characters from * UTF-8 to the local User Interface character set. * * To avoid the need to allocate a gigantic string buffer to convert * the characters, the mapper lets us map in chunks of any size. So, * we'll simply map and display chunks until we run out of string. */ while (len != 0) { char buf[128]; size_t cur_out; size_t cur_in; /* * Map as much as we can into our buffer. This will set cur_out to * the number of bytes in the local character set (the output of * the conversion), and will set cur_in to the number of bytes we * used from the Unicode string (the input). */ cur_out = G_cmap_to_ui->map_utf8(buf, sizeof(buf), strp, len, &cur_in); /* * Show the local characters. * * "%.*s" is just like "%s", but the ".*" tells printf to show * exactly the number of characters in the int argument before the * string, instead of showing everything until it finds a null byte * in the string. This is important because map_utf8 does NOT * null-terminate the result. */ printf("%.*s", (int)cur_out, buf); /* * skip the characters of input we just translated, so that on the * next iteration of this loop we'll translate the next bunch of * characters */ strp += cur_in; len -= cur_in; } }