Viewing File: /usr/local/cpanel/share/libraries/cjt2/src/io/api2.js

/*
# api/io/api2.js                                     Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* eslint-env amd */
/* eslint camelcase: "off" */
/* --------------------------*/

// TODO: Add tests for these

/**
 * Contain the IAPI Driver implementation used to process cpanel api2
 * request/response messages.
 * @module cjt2/io/api2
 * @example
 *
 */
define(["lodash", "cjt/io/base", "cjt/util/test"], function(_, BASE, TEST) {

    "use strict";

    // ------------------------------
    // Module
    // ------------------------------
    var MODULE_NAME = "cjt/io/api2"; // requirejs(["cjt/io/api2"], function(api) {}); -> cjt/io/api2.js || cjt/io/api2.debug.js
    var MODULE_DESC = "Contains the unique bits for integration with API2 calls.";
    var MODULE_VERSION = "2.0";

    var API_VERSION = 2;

    // ------------------------------
    // Shortcuts
    // ------------------------------

    /**
     * IAPIDriver for API2
     *
     * @static
     * @public
     * @class API2
     */
    var api2 = {
        MODULE_NAME: MODULE_NAME,
        MODULE_DESC: MODULE_DESC,
        MODULE_VERSION: MODULE_VERSION,

        /**
         * Parse an api2-request object to extract
         * the interesting parts of a cPanel API 2 call response.
         *
         * @method parse_response
         * @param {object} response The asyncRequest response object
         * @return {object} See api_base._parse_response for the format of this object.
         */
        parse_response: function(response) {
            return BASE._parse_response(api2, response);
        },

        /**
         * Return a list of messages from a cPanel API 2 response, normalized as a
         * list of [ { level:"info|warn|error", content:"..." }, ... ]
         *
         * @method find_messages
         * @param {object} response The parsed API JSON response
         * @return {array} The messages that the API call returned
         */
        find_messages: function(response) {
            if (!response || !response.cpanelresult) {
                return [{
                    level: "error",
                    content: BASE._unknown_error_msg()
                }];
            }

            if ("error" in response.cpanelresult) {
                var err = response.cpanelresult.error;
                return [{
                    level: "error",
                    content: err ? _.escape(err) : BASE._unknown_error_msg()
                }];
            }

            // Some apis (e.g. ZoneEdit::fetchzone) return an error as part of the data in the first data argument
            // another api (ZoneEdit::remove_zone_record) returns an error as part of a result object in the data object
            // NOTE: This doesn't account for the actual status that is returned, it just grabs whatever message is there.
            // We are depending on the callers of the api call to check the status before displaying any messages found.
            if (response.cpanelresult.data &&
                response.cpanelresult.data[0]) {
                var msg;
                if (response.cpanelresult.data[0].hasOwnProperty("statusmsg")) {
                    msg = response.cpanelresult.data[0].statusmsg;
                } else if (response.cpanelresult.data[0].hasOwnProperty("result") &&
                    response.cpanelresult.data[0].result.hasOwnProperty("statusmsg")) {
                    msg = response.cpanelresult.data[0].result.statusmsg;
                }

                if (msg) {
                    return [{
                        level: "error",
                        content: msg ? _.escape(msg) : BASE._unknown_error_msg()
                    }];
                }
            }


            return [];
        },

        /**
         * Indicates whether this module’s find_messages() function
         * HTML-escapes.
         */
        HTML_ESCAPES_MESSAGES: true,

        /**
         * Return what a cPanel API 2 call says about whether it succeeded or not
         *
         * @method find_status
         * @param {object} response The parsed API JSON response
         * @return {boolean} Whether the API call says it succeeded
         */
        find_status: function(response) {
            try {
                var status = false;
                if (response && response.cpanelresult) {
                    var result = response.cpanelresult;
                    if (result.event && result.event.result) {

                        // We prefer to use this method, but some older api's may not correctly return this yet
                        // or under certain exceptional conditions, this may not be correctly filled.
                        status = (response.cpanelresult.event.result === 1);

                        if (status && result.error) {

                            // Some apis are really bad and return success and an error which should be an error.
                            status = false;
                        }

                        // Some apis (e.g. ZoneEdit::fetchzone) return an error as part of the data in the first data argument
                        // another api (ZoneEdit::remove_zone_record) returns an error as part of a result object in the data object
                        if (result.data &&
                            result.data[0]) {
                            if (result.data[0].hasOwnProperty("status")) {
                                status = result.data[0].status;
                            } else if (result.data[0].hasOwnProperty("result") &&
                                result.data[0].result.hasOwnProperty("status")) {
                                status = result.data[0].result.status;
                            }
                        }

                    } else {

                        // In the case of apis that don't quite comply with the API2 layout or ones that
                        // can fail without returning the event container above, we have this as a fallback
                        // to detecting an error.
                        status = !result.error;
                    }
                }
                return status;
            } catch (e) {
                return false;
            }
        },

        /**
         * Return normalized data from a cPanel API 2 call
         *
         * @method get_data
         * @param {object} response The parsed API JSON response
         * @return {array} The data that the API returned
         */
        get_data: function(response) {
            return response.cpanelresult.data;
        },


        /**
         * Return normalized data from a cPanel API 2 call
         *
         * @method get_meta
         * @param {object} response The parsed API JSON response
         * @return {array} The data that the API returned
         */
        get_meta: function(response) {
            var meta = {
                paginate: {
                    is_paged: false,
                    total_records: 0,
                    current_record: 0,
                    total_pages: 0,
                    current_page: 0,
                    page_size: 0
                },
                filter: {
                    is_filtered: false,
                    records_before_filter: NaN,
                    records_filtered: NaN
                }
            };

            if (TEST.objectHasPath(response, "cpanelresult.paginate")) {
                var paginate = meta.paginate;
                paginate.is_paged = true;
                paginate.total_records = response.cpanelresult.paginate.total_results || paginate.total_results || 0;
                paginate.current_record = response.cpanelresult.paginate.start_result || paginate.start_result || 0;
                paginate.total_pages = response.cpanelresult.paginate.total_pages || paginate.total_pages || 0;
                paginate.current_page = response.cpanelresult.paginate.current_page || paginate.current_page || 0;
                paginate.page_size = response.cpanelresult.paginate.results_per_page || paginate.results_per_page || 0;
            }

            /**
             * @todo API2 returns the filter data as part of the main object, not inside of a "filter" key.
             * It's ok though, since we catch that field in the loop over the "custom" metadata properties.
             * So this code is not used from what I can tell.
             */
            if (TEST.objectHasPath(response, "cpanelresult.filter")) {
                meta.filter.is_filtered = true;
                meta.filter.records_before_filter = response.cpanelresult.records_before_filter || 0;
            }

            // Copy any custom meta data properties.
            if (TEST.objectHasPath(response, "cpanelresult")) {
                for (var key in response.cpanelresult) {
                    if (response.cpanelresult.hasOwnProperty(key) &&
                        ( key !== "filter" && key !== "paginate")) {
                        meta[key] = response.cpanelresult[key];
                    }
                }
            }

            return meta;
        },

        /**
         * Build the call structure from the arguments and data.
         *
         * @method build_query
         * @param  {Object} args_obj    Arguments passed to the call.
         * @return {Object}             Object representation of the call arguments
         */
        build_query: function(args_obj) {

            // Utility variables, used in specific contexts below.
            var s, cur_sort, f, cur_filter;

            var api_prefix = "api2_";
            var api_call = {
                cpanel_jsonapi_apiversion: API_VERSION,
                cpanel_jsonapi_module: args_obj.module,
                cpanel_jsonapi_func: args_obj.func
            };

            if (args_obj.args) {
                _.extend(api_call, args_obj.args);
            }

            if (args_obj.meta) {
                if (args_obj.meta.sort) {
                    var sort_count = args_obj.meta.sort.length;

                    if (sort_count) {
                        api_call.api2_sort = 1;
                    }

                    if (sort_count === 1) {
                        cur_sort = args_obj.meta.sort[0];
                        if (cur_sort instanceof Array) {
                            api_call[api_prefix + "sort_method"] = cur_sort[1];
                            cur_sort = cur_sort[0];
                        }
                        if (cur_sort.charAt(0) === "!") {
                            api_call[api_prefix + "sort_reverse"] = 1;
                            cur_sort = cur_sort.substr(1);
                        }
                        api_call[api_prefix + "sort_column"] = cur_sort;
                    } else {
                        for (s = 0; s < sort_count; s++) {
                            cur_sort = args_obj.meta.sort[s];
                            if (cur_sort instanceof Array) {
                                api_call[api_prefix + "sort_method_" + s] = cur_sort[1];
                                cur_sort = cur_sort[0];
                            }
                            if (cur_sort.charAt(0) === "!") {
                                api_call[api_prefix + "sort_reverse_" + s] = 1;
                                cur_sort = cur_sort.substr(1);
                            }
                            api_call[api_prefix + "sort_column_" + s] = cur_sort;
                        }
                    }
                }

                if (args_obj.meta.filter) {
                    var filter_count = args_obj.meta.filter.length;

                    if (filter_count) {
                        api_call.api2_filter = 1;
                    }

                    if (filter_count === 1) {
                        cur_filter = args_obj.meta.filter[0];

                        api_call[api_prefix + "filter_column"] = cur_filter[0];
                        api_call[api_prefix + "filter_type"] = cur_filter[1];
                        api_call[api_prefix + "filter_term"] = cur_filter[2];
                    } else {
                        for (f = 0; f < filter_count; f++) {
                            cur_filter = args_obj.meta.filter[f];

                            api_call[api_prefix + "filter_column_" + f] = cur_filter[0];
                            api_call[api_prefix + "filter_type_" + f] = cur_filter[1];
                            api_call[api_prefix + "filter_term_" + f] = cur_filter[2];
                        }
                    }
                }
                if (args_obj.meta.paginate) {
                    api_call.api2_paginate = 1;
                    if ("start" in args_obj.meta.paginate) {
                        api_call[api_prefix + "paginate_start"] = args_obj.meta.paginate.start;
                    }
                    if ("size" in args_obj.meta.paginate) {
                        api_call[api_prefix + "paginate_size"] = args_obj.meta.paginate.size;
                    }
                }
                delete args_obj.meta;
            }

            return api_call;
        },

        /**
         * Assemble the url for the request.
         *
         * @method get_url
         * @param  {String} token    cPanel Security Token
         * @param  {Object} args_obj Arguments passed to the call.
         * @return {String}          Url prefix for the call
         */
        get_url: function(token) {
            return token + "/json-api/cpanel";
        }
    };

    return api2;
});
Back to Directory File Manager