/// Set a value in a dict. Objects are recursively expanded into their /// vimscript equivalents. Passing 'nil' as value deletes the key. /// /// @param dict The vimscript dict /// @param key The key /// @param value The new value /// @param[out] err Details of an error that may have occurred /// @return the old value, if any Object dict_set_value(dict_T *dict, String key, Object value, Error *err) { Object rv = OBJECT_INIT; if (dict->dv_lock) { api_set_error(err, Exception, _("Dictionary is locked")); return rv; } if (key.size == 0) { api_set_error(err, Validation, _("Empty dictionary keys aren't allowed")); return rv; } if (key.size > INT_MAX) { api_set_error(err, Validation, _("Key length is too high")); return rv; } dictitem_T *di = dict_find(dict, (uint8_t *)key.data, (int)key.size); if (value.type == kObjectTypeNil) { // Delete the key if (di == NULL) { // Doesn't exist, fail api_set_error(err, Validation, _("Key \"%s\" doesn't exist"), key.data); } else { // Return the old value rv = vim_to_object(&di->di_tv); // Delete the entry hashitem_T *hi = hash_find(&dict->dv_hashtab, di->di_key); hash_remove(&dict->dv_hashtab, hi); dictitem_free(di); } } else { // Update the key typval_T tv; // Convert the object to a vimscript type in the temporary variable if (!object_to_vim(value, &tv, err)) { return rv; } if (di == NULL) { // Need to create an entry di = dictitem_alloc((uint8_t *) key.data); dict_add(dict, di); } else { // Return the old value clear_tv(&di->di_tv); } // Update the value copy_tv(&tv, &di->di_tv); // Clear the temporary variable clear_tv(&tv); } return rv; }
static Object remote_ui_attach(uint64_t channel_id, uint64_t request_id, Array args, Error *error) { if (pmap_has(uint64_t)(connected_uis, channel_id)) { api_set_error(error, Exception, _("UI already attached for channel")); return NIL; } if (args.size != 3 || args.items[0].type != kObjectTypeInteger || args.items[1].type != kObjectTypeInteger || args.items[2].type != kObjectTypeBoolean || args.items[0].data.integer <= 0 || args.items[1].data.integer <= 0) { api_set_error(error, Validation, _("Invalid arguments. Expected: " "(uint width > 0, uint height > 0, bool enable_rgb)")); return NIL; } UIData *data = xmalloc(sizeof(UIData)); data->channel_id = channel_id; data->buffer = (Array)ARRAY_DICT_INIT; UI *ui = xcalloc(1, sizeof(UI)); ui->width = (int)args.items[0].data.integer; ui->height = (int)args.items[1].data.integer; ui->rgb = args.items[2].data.boolean; ui->data = data; ui->resize = remote_ui_resize; ui->clear = remote_ui_clear; ui->eol_clear = remote_ui_eol_clear; ui->cursor_goto = remote_ui_cursor_goto; ui->busy_start = remote_ui_busy_start; ui->busy_stop = remote_ui_busy_stop; ui->mouse_on = remote_ui_mouse_on; ui->mouse_off = remote_ui_mouse_off; ui->insert_mode = remote_ui_insert_mode; ui->normal_mode = remote_ui_normal_mode; ui->set_scroll_region = remote_ui_set_scroll_region; ui->scroll = remote_ui_scroll; ui->highlight_set = remote_ui_highlight_set; ui->put = remote_ui_put; ui->bell = remote_ui_bell; ui->visual_bell = remote_ui_visual_bell; ui->update_fg = remote_ui_update_fg; ui->update_bg = remote_ui_update_bg; ui->flush = remote_ui_flush; ui->suspend = remote_ui_suspend; ui->set_title = remote_ui_set_title; ui->set_icon = remote_ui_set_icon; pmap_put(uint64_t)(connected_uis, channel_id, ui); ui_attach(ui); return NIL; }
/// Gets the value of a global or local(buffer, window) option. /// /// @param from If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer /// to the window or buffer. /// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF` /// @param name The option name /// @param[out] err Details of an error that may have occurred /// @return the option value Object get_option_from(void *from, int type, String name, Error *err) { Object rv = OBJECT_INIT; if (name.size == 0) { api_set_error(err, Validation, _("Empty option name")); return rv; } // Return values int64_t numval; char *stringval = NULL; int flags = get_option_value_strict(name.data, &numval, &stringval, type, from); if (!flags) { api_set_error(err, Validation, _("Invalid option name \"%s\""), name.data); return rv; } if (flags & SOPT_BOOL) { rv.type = kObjectTypeBoolean; rv.data.boolean = numval ? true : false; } else if (flags & SOPT_NUM) { rv.type = kObjectTypeInteger; rv.data.integer = numval; } else if (flags & SOPT_STRING) { if (stringval) { rv.type = kObjectTypeString; rv.data.string.data = stringval; rv.data.string.size = strlen(stringval); } else { api_set_error(err, Exception, _("Unable to get value for option \"%s\""), name.data); } } else { api_set_error(err, Exception, _("Unknown type for option \"%s\""), name.data); } return rv; }
void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) { if (grid_handle == DEFAULT_GRID_HANDLE) { screen_resize(width, height); return; } win_T *wp = get_win_by_grid_handle(grid_handle); if (wp == NULL) { api_set_error(error, kErrorTypeValidation, "No window with the given handle"); return; } if (wp->w_floating) { if (width != wp->w_width && height != wp->w_height) { wp->w_float_config.width = width; wp->w_float_config.height = height; win_config_float(wp, wp->w_float_config); } } else { // non-positive indicates no request wp->w_height_request = (int)MAX(height, 0); wp->w_width_request = (int)MAX(width, 0); win_set_inner_size(wp); } }
static void ui_set_option(UI *ui, bool init, String name, Object value, Error *error) { if (strequal(name.data, "rgb")) { if (value.type != kObjectTypeBoolean) { api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean"); return; } ui->rgb = value.data.boolean; // A little drastic, but only legacy uis need to use this option if (!init) { ui_refresh(); } return; } // LEGACY: Deprecated option, use `ext_cmdline` instead. bool is_popupmenu = strequal(name.data, "popupmenu_external"); for (UIExtension i = 0; i < kUIExtCount; i++) { if (strequal(name.data, ui_ext_names[i]) || (i == kUIPopupmenu && is_popupmenu)) { if (value.type != kObjectTypeBoolean) { snprintf((char *)IObuff, IOSIZE, "%s must be a Boolean", name.data); api_set_error(error, kErrorTypeValidation, (char *)IObuff); return; } bool boolval = value.data.boolean; if (!init && i == kUINewgrid && boolval != ui->ui_ext[i]) { // There shouldn't be a reason for an UI to do this ever // so explicitly don't support this. api_set_error(error, kErrorTypeValidation, "ext_newgrid option cannot be changed"); } ui->ui_ext[i] = boolval; if (!init) { ui_set_ext_option(ui, i, boolval); } return; } } api_set_error(error, kErrorTypeValidation, "No such UI option: %s", name.data); }
/// Calculates the number of display cells `str` occupies, tab is counted as /// one cell. /// /// @param str Some text /// @param[out] err Details of an error that may have occurred /// @return The number of cells Integer vim_strwidth(String str, Error *err) { if (str.size > INT_MAX) { api_set_error(err, Validation, _("String length is too high")); return 0; } return (Integer) mb_string2cells((char_u *) str.data); }
static void ui_set_option(UI *ui, String name, Object value, Error *error) { if (strcmp(name.data, "rgb") == 0) { if (value.type != kObjectTypeBoolean) { api_set_error(error, Validation, _("rgb must be a Boolean")); return; } ui->rgb = value.data.boolean; } else if (strcmp(name.data, "popupmenu_external") == 0) { if (value.type != kObjectTypeBoolean) { api_set_error(error, Validation, _("popupmenu_external must be a Boolean")); return; } ui->pum_external = value.data.boolean; } else { api_set_error(error, Validation, _("No such ui option")); } }
tabpage_T * find_tab_by_handle(Tabpage tabpage, Error *err) { tabpage_T *rv = handle_get_tabpage(tabpage); if (!rv) { api_set_error(err, Validation, _("Invalid tabpage id")); } return rv; }
win_T * find_window_by_handle(Window window, Error *err) { win_T *rv = handle_get_window(window); if (!rv) { api_set_error(err, Validation, _("Invalid window id")); } return rv; }
buf_T *find_buffer_by_handle(Buffer buffer, Error *err) { buf_T *rv = handle_get_buffer(buffer); if (!rv) { api_set_error(err, Validation, _("Invalid buffer id")); } return rv; }
static Object remote_ui_detach(uint64_t channel_id, uint64_t request_id, Array args, Error *error) { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { api_set_error(error, Exception, _("UI is not attached for channel")); } remote_ui_disconnect(channel_id); return NIL; }
void api_dispatch( void ) { api_method_t *m = NULL; char *uri = NULL; char *name = NULL; api_set_value( "version", "1.0" ); api_set_value( "env.uname", BOXNAME); ewf_fastcgi_set_content_type( "text/xml" ); uri = ewf_http_request_uri( ); if ( uri == NULL ) { nbu_log_info( "uri is NULL" ); api_set_error( API_ERROR_METHOD_NOT_FOUND ); ewf_fastcgi_display( "api/generic.xml" ); return; } /* nbu_log_debug("uri: '%s'", uri); */ /* nbu_log_debug("Query.Method: '%s'", hdf_get_valuef(cgi->hdf, "Query.method")); */ /* nbu_log_debug("CGI.QueryString: '%s'", hdf_get_valuef(cgi->hdf, "CGI.QueryString")); */ if ( ewf_fastcgi_get_parameter( "method", &name ) == EWF_ERROR ) { nbu_log_info( "method param is NULL" ); api_set_error( API_ERROR_METHOD_NOT_FOUND ); ewf_fastcgi_display( "api/generic.xml" ); return; } if ( ewf_hashtable_find( &methods.list, name, ( void * ) &m ) == EWF_ERROR ) { nbu_log_info( "method '%s' not found", name ); api_set_error( API_ERROR_METHOD_NOT_FOUND ); ewf_fastcgi_display( "api/generic.xml" ); return; } nbu_log_info( "method '%s' found", name ); api_handle_request( m, name ); return; }
/// Call the given function with the given arguments stored in an array. /// /// @param fname Function to call /// @param args Functions arguments packed in an Array /// @param[out] err Details of an error that may have occurred /// @return Result of the function call Object vim_call_function(String fname, Array args, Error *err) { Object rv = OBJECT_INIT; if (args.size > MAX_FUNC_ARGS) { api_set_error(err, Validation, _("Function called with too many arguments.")); return rv; } // Convert the arguments in args from Object to typval_T values typval_T vim_args[MAX_FUNC_ARGS + 1]; size_t i = 0; // also used for freeing the variables for (; i < args.size; i++) { if (!object_to_vim(args.items[i], &vim_args[i], err)) { goto free_vim_args; } } try_start(); // Call the function typval_T rettv; int dummy; int r = call_func((char_u *) fname.data, (int) fname.size, &rettv, (int) args.size, vim_args, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, NULL); if (r == FAIL) { api_set_error(err, Exception, _("Error calling function.")); } if (!try_end(err)) { rv = vim_to_object(&rettv); } clear_tv(&rettv); free_vim_args: while (i > 0) { clear_tv(&vim_args[--i]); } return rv; }
/// Recursively expands a vimscript value in a dict /// /// @param dict The vimscript dict /// @param key The key /// @param[out] err Details of an error that may have occurred Object dict_get_value(dict_T *dict, String key, Error *err) { hashitem_T *hi = hash_find(&dict->dv_hashtab, (uint8_t *) key.data); if (HASHITEM_EMPTY(hi)) { api_set_error(err, Validation, _("Key not found")); return (Object) OBJECT_INIT; } dictitem_T *di = dict_lookup(hi); return vim_to_object(&di->di_tv); }
static Object remote_ui_try_resize(uint64_t channel_id, uint64_t request_id, Array args, Error *error) { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { api_set_error(error, Exception, _("UI is not attached for channel")); } if (args.size != 2 || args.items[0].type != kObjectTypeInteger || args.items[1].type != kObjectTypeInteger || args.items[0].data.integer <= 0 || args.items[1].data.integer <= 0) { api_set_error(error, Validation, _("Invalid arguments. Expected: " "(uint width > 0, uint height > 0)")); return NIL; } UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); ui->width = (int)args.items[0].data.integer; ui->height = (int)args.items[1].data.integer; ui_refresh(); return NIL; }
/// Changes Vim working directory /// /// @param dir The new working directory /// @param[out] err Details of an error that may have occurred void vim_change_directory(String dir, Error *err) { if (dir.size >= MAXPATHL) { api_set_error(err, Validation, _("Directory string is too long")); return; } char string[MAXPATHL]; strncpy(string, dir.data, dir.size); string[dir.size] = NUL; try_start(); if (vim_chdir((char_u *)string)) { if (!try_end(err)) { api_set_error(err, Exception, _("Failed to change directory")); } return; } post_chdir(kCdScopeGlobal); try_end(err); }
/// End try block, set the error message if any and return true if an error /// occurred. /// /// @param err Pointer to the stack-allocated error object /// @return true if an error occurred bool try_end(Error *err) { --trylevel; // Without this it stops processing all subsequent VimL commands and // generates strange error messages if I e.g. try calling Test() in a // cycle did_emsg = false; if (got_int) { if (did_throw) { // If we got an interrupt, discard the current exception discard_current_exception(); } api_set_error(err, Exception, _("Keyboard interrupt")); got_int = false; } else if (msg_list != NULL && *msg_list != NULL) { int should_free; char *msg = (char *)get_exception_string(*msg_list, ET_ERROR, NULL, &should_free); xstrlcpy(err->msg, msg, sizeof(err->msg)); err->set = true; free_global_msglist(); if (should_free) { xfree(msg); } } else if (did_throw) { api_set_error(err, Exception, "%s", current_exception->value); discard_current_exception(); } return err->set; }
API_REGISTER_METHOD( wlan, setWlanMode, EWF_HTTP_REQUEST_METHOD_POST, API_ACCESS_POLICY_PRIVATE ) { char *param = NULL; int ret; if ( ewf_fastcgi_get_parameter( "mode", ¶m ) == EWF_SUCCESS && (ret = cfg_set("wlan_mode", param)) >= cfg_set_success) { if(ret == cfg_set_success) { nbd_nv_commit( "user" ); } } else { api_set_error(API_ERROR_INVALID_ARG); } }
/// Evaluates the expression str using the Vim internal expression /// evaluator (see |expression|). /// Dictionaries and lists are recursively expanded. /// /// @param str The expression str /// @param[out] err Details of an error that may have occurred /// @return The expanded object Object vim_eval(String str, Error *err) { Object rv = OBJECT_INIT; // Evaluate the expression try_start(); typval_T *expr_result = eval_expr((char_u *) str.data, NULL); if (!expr_result) { api_set_error(err, Exception, _("Failed to evaluate expression")); } if (!try_end(err)) { // No errors, convert the result rv = vim_to_object(expr_result); } // Free the vim object free_tv(expr_result); return rv; }
/// Sets the value of a global or local(buffer, window) option. /// /// @param to If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer /// to the window or buffer. /// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF` /// @param name The option name /// @param[out] err Details of an error that may have occurred void set_option_to(void *to, int type, String name, Object value, Error *err) { if (name.size == 0) { api_set_error(err, Validation, _("Empty option name")); return; } int flags = get_option_value_strict(name.data, NULL, NULL, type, to); if (flags == 0) { api_set_error(err, Validation, _("Invalid option name \"%s\""), name.data); return; } if (value.type == kObjectTypeNil) { if (type == SREQ_GLOBAL) { api_set_error(err, Exception, _("Unable to unset option \"%s\""), name.data); return; } else if (!(flags & SOPT_GLOBAL)) { api_set_error(err, Exception, _("Cannot unset option \"%s\" " "because it doesn't have a global value"), name.data); return; } else { unset_global_local_option(name.data, to); return; } } int opt_flags = (type ? OPT_LOCAL : OPT_GLOBAL); if (flags & SOPT_BOOL) { if (value.type != kObjectTypeBoolean) { api_set_error(err, Validation, _("Option \"%s\" requires a boolean value"), name.data); return; } bool val = value.data.boolean; set_option_value_for(name.data, val, NULL, opt_flags, type, to, err); } else if (flags & SOPT_NUM) { if (value.type != kObjectTypeInteger) { api_set_error(err, Validation, _("Option \"%s\" requires an integer value"), name.data); return; } if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) { api_set_error(err, Validation, _("Value for option \"%s\" is outside range"), name.data); return; } int val = (int) value.data.integer; set_option_value_for(name.data, val, NULL, opt_flags, type, to, err); } else { if (value.type != kObjectTypeString) { api_set_error(err, Validation, _("Option \"%s\" requires a string value"), name.data); return; } set_option_value_for(name.data, 0, value.data.string.data, opt_flags, type, to, err); } }
/// Copies a C string into a String (binary safe string, characters + length). /// The resulting string is also NUL-terminated, to facilitate interoperating /// with code using C strings. /// /// @param str the C string to copy /// @return the resulting String, if the input string was NULL, an /// empty String is returned String cstr_to_string(const char *str) { if (str == NULL) { return (String) STRING_INIT; } size_t len = strlen(str); return (String) { .data = xmemdupz(str, len), .size = len }; } /// Creates a String using the given C string. Unlike /// cstr_to_string this function DOES NOT copy the C string. /// /// @param str the C string to use /// @return The resulting String, or an empty String if /// str was NULL String cstr_as_string(char *str) FUNC_ATTR_PURE { if (str == NULL) { return (String) STRING_INIT; } return (String) {.data = str, .size = strlen(str)}; } bool object_to_vim(Object obj, typval_T *tv, Error *err) { tv->v_type = VAR_UNKNOWN; tv->v_lock = 0; switch (obj.type) { case kObjectTypeNil: tv->v_type = VAR_NUMBER; tv->vval.v_number = 0; break; case kObjectTypeBoolean: tv->v_type = VAR_NUMBER; tv->vval.v_number = obj.data.boolean; break; case kObjectTypeInteger: if (obj.data.integer > INT_MAX || obj.data.integer < INT_MIN) { api_set_error(err, Validation, _("Integer value outside range")); return false; } tv->v_type = VAR_NUMBER; tv->vval.v_number = (int)obj.data.integer; break; case kObjectTypeFloat: tv->v_type = VAR_FLOAT; tv->vval.v_float = obj.data.floating; break; case kObjectTypeString: tv->v_type = VAR_STRING; tv->vval.v_string = xmemdupz(obj.data.string.data, obj.data.string.size); break; case kObjectTypeArray: tv->v_type = VAR_LIST; tv->vval.v_list = list_alloc(); for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; listitem_T *li = listitem_alloc(); if (!object_to_vim(item, &li->li_tv, err)) { // cleanup listitem_free(li); list_free(tv->vval.v_list, true); return false; } list_append(tv->vval.v_list, li); } tv->vval.v_list->lv_refcount++; break; case kObjectTypeDictionary: tv->v_type = VAR_DICT; tv->vval.v_dict = dict_alloc(); for (uint32_t i = 0; i < obj.data.dictionary.size; i++) { KeyValuePair item = obj.data.dictionary.items[i]; String key = item.key; if (key.size == 0) { api_set_error(err, Validation, _("Empty dictionary keys aren't allowed")); // cleanup dict_free(tv->vval.v_dict, true); return false; } dictitem_T *di = dictitem_alloc((uint8_t *) key.data); if (!object_to_vim(item.value, &di->di_tv, err)) { // cleanup dictitem_free(di); dict_free(tv->vval.v_dict, true); return false; } dict_add(tv->vval.v_dict, di); } tv->vval.v_dict->dv_refcount++; break; default: abort(); } return true; }
void api_handle_request( api_method_t * m, const char *name ) { ewf_http_request_method_t reqm; ewf_session_t* session = NULL; char *token = NULL, *c_stat = NULL; int have_authoriz = 0, have_error = 0, ret = 0; char buf[64]; /* method struct must be initialized */ if ( m == NULL ) { nbu_log_info( "method struct is NULL" ); api_set_error( API_ERROR_METHOD_NOT_FOUND ); ewf_fastcgi_display( "api/generic.xml" ); return; } /* test if method exist */ reqm = ewf_http_request_method( ); if ( reqm != m->request_method ) { nbu_log_info ( "request method is wrong: got '%d', should be '%d'", reqm, m->request_method ); api_set_error( API_ERROR_METHOD_NOT_FOUND ); ewf_fastcgi_display( "api/generic.xml" ); return; } /* action handler is defined ? */ if ( m->action_handler == NULL ) { nbu_log_info( "action handler is NULL" ); api_set_error( API_ERROR_METHOD_NOT_FOUND ); ewf_fastcgi_display( "api/generic.xml" ); return; } /* the box being upgrade ? */ ret = nbd_autoconf_get("status", buf, sizeof(buf)); if (ret == NBD_SUCCESS) { if(strncmp("downloading", buf, 11) == 0) { nbu_log_info( "The box is being upgrade ..." ); api_set_error( API_ERROR_BOX_UPGRADING ); ewf_fastcgi_display( "api/generic.xml" ); ret = NBU_SUCCESS; } } /* if method is a POST method, parse POST arguments */ if ( reqm == EWF_HTTP_REQUEST_METHOD_POST ) { ewf_fastcgi_parse( ); } /* check auth if method is private */ if(m->access_policy == API_ACCESS_POLICY_PRIVATE) { nbu_log_debug( "method '%s' is private !", name ); /* maybe, api authentification is desactivated ? */ if(nbd_nv_get("web_auth", buf, sizeof(buf)) == NBD_SUCCESS && nbu_string_matches(buf, "off") == NBU_STRING_EQUAL) { nbu_log_debug( "api auth is desactivated" ); have_authoriz = 1; } else { /* check token */ if(ewf_fastcgi_get_parameter("token", &token) != EWF_SUCCESS) { /* no token submit, return need auth ! */ api_set_error(API_ERROR_NEED_AUTH); } else { /* token is here, try to get session */ nbu_log_debug("Token is here ! %s", token); if(ewf_session_get(token, &session) != EWF_SUCCESS) { /* no session found ! */ api_set_error(API_ERROR_NEED_AUTH); } else { nbu_log_debug("Token is good !"); /* verify that session is valid and if the case, update it ! */ if(ewf_session_check(session) != EWF_SUCCESS) { /* invalid session */ api_set_error(API_ERROR_NEED_AUTH); } else { /* authentified ! */ nbu_log_debug("User allowed to access private method"); have_authoriz = 1; } } } } } else { /* public method can be acceeded by anyone */ have_authoriz = 1; } if(!have_authoriz) { have_error = 1; } else { m->action_handler( ); /* check if an error has "throw" in the action method */ if((c_stat = hdf_get_value(cgi->hdf, "stat", NULL)) != NULL && nbu_string_compare(c_stat, "fail") == NBU_STRING_EQUAL) { have_error = 1; } else { if ( reqm == EWF_HTTP_REQUEST_METHOD_GET ) { nbu_string_printf( buffer, sizeof buffer, "api/%s.%s", name, "xml" ); } else { /* display ok msg with generic xml template */ nbu_string_printf( buffer, sizeof buffer, "api/%s.%s", "generic", "xml" ); } api_set_value( "stat", "ok" ); } } if(have_error) { /* display error with generic xml template */ nbu_string_printf( buffer, sizeof buffer, "api/%s.%s", "generic", "xml" ); } ewf_fastcgi_display( buffer ); return; }