diff --git a/src/plugins/uhttpd/uhttpd-rest-api/builders/cmake/CMakeLists.txt b/src/plugins/uhttpd/uhttpd-rest-api/builders/cmake/CMakeLists.txt new file mode 100644 index 00000000..728e2055 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/builders/cmake/CMakeLists.txt @@ -0,0 +1,85 @@ +cmake_minimum_required (VERSION 3.0) + +project (uhttpd-awox-api-plugin) + +set (CMAKE_MODULE_PATH "${MODULE_PATH}") +set (CMAKE_CXX_STANDARD 11) + +include (aw) + +include_directories ($ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/) + +file ( + GLOB_RECURSE + source_files + + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/plugin.c + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/uhttp-server.cpp + # core + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/core/http-reason.cpp + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/core/http-parameter.cpp + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/core/http-header.cpp + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/core/uri-transform.cpp + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/core/web-controller.cpp + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/core/web-connection.cpp + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/core/ubus-connection.cpp + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/core/ubus-reason.cpp + # rest + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/rest/rest-controller.cpp + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/rest/rest-connection.cpp + #etag-rest + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/etag-rest/etag-rest-controller.cpp + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/etag-rest/etag-rest-connection.cpp + # notification + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/notification/notification-controller.cpp + $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/notification/notification-connection.cpp + ) + +add_library (awox-api-plugin SHARED ${source_files}) + +target_link_libraries (awox-api-plugin + LINK_PUBLIC + ubus + ubox + blobmsg_json + json-c + fmt + aw-ubus +) + +set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") + +set_target_properties (awox-api-plugin PROPERTIES PREFIX "uhttpd_") + +target_include_directories (awox-api-plugin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +install (TARGETS awox-api-plugin LIBRARY DESTINATION local/bin/Plugins/) + +if (INSTALL_CORE) + install (FILES $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/configs/core.json DESTINATION local/configs/restd/) +endif () + +if (INSTALL_SPOTIFY) + install (FILES $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/configs/spotify.json DESTINATION local/configs/restd/) +endif () + +if (INSTALL_DEAP) + install (FILES $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/configs/deap.json DESTINATION local/configs/restd/) +endif () + +if (INSTALL_LIGHT) + install (FILES $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/configs/light.json DESTINATION local/configs/restd/) +endif () + +if (INSTALL_LIGHT_COLOR) + install (FILES $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/configs/light-color.json DESTINATION local/configs/restd/) +endif () + +if (INSTALL_LIGHT_WHITE) + install (FILES $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/configs/light-white.json DESTINATION local/configs/restd/) +endif () + +if (INSTALL_VOICE_CONTROL) + install (FILES $ENV{AWOXCVS}/AwoxAudio/Plugins/uhttpd/uhttpd-awox-api/configs/voice-control.json DESTINATION local/configs/restd/) +endif () + diff --git a/src/plugins/uhttpd/uhttpd-rest-api/configs/core.json b/src/plugins/uhttpd/uhttpd-rest-api/configs/core.json new file mode 100644 index 00000000..04367595 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/configs/core.json @@ -0,0 +1,431 @@ +// Comments +{ + "controller": [ + // --- Network + { + // Network Info Controller + "path": "/Network/Info.json", + "model_name": "network.info" + }, + { + // WIFI AP List. + "path": "/Network/Wifi/List.json", + "model_name": "network.wifi", + "get_method": "list" + }, + { + // WIFI Connection. + "path": "/Network/Wifi/Connect.json", + "model_name": "network.wifi", + "set_method": "connect" + }, + { + // Static Settings. + "path": "/Network/StaticConfig.json", + "model_name": "network.config", + "set_method": "static_config" + }, + { + // Enable/Disable Wifi. + "path": "/Network/EnableWifi.json", + "model_name": "network.config", + "set_method": "wifi_enable" + }, + // --- System + { + // Locale Control Controller + "path": "/System/Locale.json", + "model_name": "system.locale" + }, + { + // Time Zone Controller + "path": "/System/Timezone.json", + "model_name": "system.timezone" + }, + { + // FriendlyName Control Controller + "path": "/System/FriendlyName.json", + "model_name": "system.friendlyname" + }, + { + // SoundNotification Control Controller + "path": "/System/SoundNotification.json", + "model_name": "system.soundnotification" + }, + { + // Reboot + "path": "/System/Reboot.json", + "model_name": "system.maintenance", + "set_method": "reboot" + }, + { + // Reset + "path": "/System/Reset.json", + "model_name": "system.maintenance", + "set_method": "reset" + }, + { + // DeviceInfo Controller + "path": "/System/DeviceInfo.json", + "model_name": "system.deviceinfo" + }, + { + // Standby + "path": "/System/Standby.json", + "model_name": "system.standby" + }, + { + // Standby switch + "path": "/System/Standby/Switch.json", + "model_name": "system.standby", + "set_method": "switch" + }, + { + // Block Input + "path": "/System/Input/Block.json", + "model_name": "system.input.control" + }, + // System - Upgrade Auto + { + "path": "/System/Upgrade/Auto.json", + "model_name": "system.upgrade.auto" + }, + { + "path": "/System/Upgrade/Check.json", + "model_name": "system.upgrade", + "set_method": "check" + }, + { + "path": "/System/Upgrade/Start.json", + "model_name": "system.upgrade", + "set_method": "start" + }, + { + "path": "/System/Upgrade/Stop.json", + "model_name": "system.upgrade", + "set_method": "stop" + }, + // -- Miscellaneous + { + "path": "/System/RangeExtender.json", + "model_name": "network.range_extender" + }, + { + "path": "/System/Status/Led.json", + "model_name": "light.capability.blue_led", + "get_method": "get_statewhenlightisoff", + "set_method": "set_statewhenlightisoff" + }, + // --- Hardware Control Controller + { + "path": "/Hardware/Cpu/Info.json", + "model_name": "hardware.cpuinfo" + }, + { + "path": "/Hardware/Memory/Info.json", + "model_name": "hardware.memoryinfo" + }, + // --- Presets Controller + { + // Browse. + "path": "/Player/Presets.json", + "model_name": "player.dlna.presets", + "set_method": "" + }, + { + // Play Preset. + "path": "/Player/PlayPreset.json", + "model_name": "player.dlna.presets", + "set_method": "play" + }, + { + // Store Preset. + "path": "/Player/StorePreset.json", + "model_name": "player.dlna.presets", + "set_method": "store" + }, + // --- Alarms Controller + { + // Configuration + "path": "/Alarm/Configuration.json", + "model_name": "alarm.configuration" + }, + { + // Count + "path": "/Alarm/Count.json", + "model_name": "alarm.count" + }, + { + // Preset Control + "path": "/Alarm/Preset/Info.json", + "model_name": "alarm.presets" + }, + // --- Mixer Controller + { + "path": "/Mixer/Capabilities.json", + "model_name": "system.mixer.capabilities" + }, + { + "path": "/Mixer/Capability/Volume.json", + "model_name": "zone.mixer.volume" + }, + { + "path": "/Mixer/Capability/Mute.json", + "model_name": "zone.mixer.mute" + }, + { + "path": "/Mixer/Capability/Mute/Switch.json", + "model_name": "zone.mixer.mute", + "set_method": "switch" + }, + // --- Song Queue Controller + // --- Content + { + "path": "/SongQueue/Content/List.json", + "model_name": "songqueue.content", + "get_method": "list" + }, + { + "path": "/SongQueue/Content/Add.json", + "model_name": "songqueue.content", + "set_method": "add" + }, + { + "path": "/SongQueue/Content/Remove.json", + "model_name": "songqueue.content", + "set_method": "remove" + }, + { + "path": "/SongQueue/Content/Clear.json", + "model_name": "songqueue.content", + "set_method": "clear" + }, + { + "path": "/SongQueue/Content/MaxSongsNumber.json", + "model_name": "songqueue.content", + "get_method": "get_maxsongsnumber" + }, + { + "path": "/SongQueue/Content/MovePosition.json", + "model_name": "songqueue.content", + "set_method": "move_position" + }, + //--- Control + { + "path": "/SongQueue/Control/Position.json", + "model_name": "songqueue.control", + "get_method": "get_position", + "set_method": "set_position" + }, + { + "path": "/SongQueue/Control/Play.json", + "model_name": "songqueue.control", + "set_method": "play" + }, + { + "path": "/SongQueue/Control/Pause.json", + "model_name": "songqueue.control", + "set_method": "pause" + }, + { + "path": "/SongQueue/Control/PlayPause.json", + "model_name": "songqueue.control", + "set_method": "play_pause" + }, + { + "path": "/SongQueue/Control/Stop.json", + "model_name": "songqueue.control", + "set_method": "stop" + }, + { + "path": "/SongQueue/Control/Next.json", + "model_name": "songqueue.control", + "set_method": "next" + }, + { + "path": "/SongQueue/Control/Previous.json", + "model_name": "songqueue.control", + "set_method": "previous" + }, + { + "path": "/SongQueue/Control/RepeatMode.json", + "model_name": "songqueue.control", + "get_method": "get_repeatmode", + "set_method": "set_repeatmode" + }, + { + "path": "/SongQueue/Control/ToggleRepeatMode.json", + "model_name": "songqueue.control", + "set_method": "toggle_repeatmode" + }, + { + "path": "/SongQueue/Control/ShuffleMode.json", + "model_name": "songqueue.control", + "get_method": "get_shufflemode", + "set_method": "set_shufflemode" + }, + { + "path": "/SongQueue/Control/ToggleShuffleMode.json", + "model_name": "songqueue.control", + "set_method": "toggle_shufflemode" + }, + { + "path": "/SongQueue/Control/ActiveState.json", + "model_name": "songqueue.control", + "get_method": "get_activestate" + }, + // --- Player Controller + { + "path": "/Player/PlayPause.json", + "model_name": "player.meta", + "set_method": "play_pause" + }, + { + "path": "/Player/Pause.json", + "model_name": "player.meta", + "set_method": "pause" + }, + { + "path": "/Player/Play.json", + "model_name": "player.meta", + "set_method": "play" + }, + { + "path": "/Player/Stop.json", + "model_name": "player.meta", + "set_method": "stop" + }, + { + "path": "/Player/Previous.json", + "model_name": "player.meta", + "set_method": "previous" + }, + { + "path": "/Player/Next.json", + "model_name": "player.meta", + "set_method": "next" + }, + { + "path": "/Player/SetPosition.json", + "model_name": "player.meta", + "set_method": "set_position" + }, + { + "path": "/Player/PlayURI.json", + "model_name": "player.meta", + "set_method": "play_uri" + }, + { + "path": "/Player/Say.json", + "model_name": "player.meta", + "set_method": "say" + }, + { + "path": "/Player/NotifyURI.json", + "model_name": "player.meta", + "set_method": "notify" + }, + { + // --- source selection + "path": "/Player/SourceSelection.json", + "model_name": "player.audio.source" + }, + // --- Private Controller + { + // --- PreProd Controller + "path": "/Control/PreProd.json", + "model_name": "system.upgrade", + "get_method": "get_preprod_state", + "set_method": "set_preprod_state" + }, + // --- Discovery Controller + { + "path": "/System/Discovery.json", + "model_name": "mdns.browser" + }, + // --- Playback stats controller + { + "path": "/ContentService/PlaybackStat/Start.json", + "model_name": "contentService.playbackStats", + "set_method": "start" + }, + { + "path": "/ContentService/PlaybackStat/Stop.json", + "model_name": "contentService.playbackStats", + "set_method": "stop" + }, + // --- AudioHub controller + { + // --- Control + "path": "/System/AudioHub.json", + "model_name": "system.audiohub" + }, + { + // --- Troubleshooting and statistics + "path": "/Stats/AudioHub/Bandwidth.json", + "model_name": "stats.audiohub.bandwidth" + }, + ], + "etag_controller": [ + { + // Zone State Controller + "path": "/Zone/State.json", + "model_name": "zone.state", + "event_name": "zone.state.updated", + "etag_key": "version_above", + "set_method": "post" + }, + { + // Zone Status (monitoring) Controller + "path": "/Zone/Status.json", + "model_name": "zone.status", + "event_name": "zone.status.updated", + "etag_key": "version_above" + }, + { + // Player State Controller + "path": "/Player/State.json", + "model_name": "player.meta", + "event_name": "player.state.updated", + "etag_key": "version_above", + "get_method": "get_state" + } + ], + "notification": [ + { + // Source selection. + "path": "/Service/Notification/SourceSelection.json", + "event_name": "player.sourceSelection" + }, + { + // Player Presets. + "path": "/Service/Notification/Presets.json", + "event_name": "player.preset" + }, + { + // Mixer. + "path": "/Service/Notification/Mixer.json", + "event_name": "system_mixer" + }, + { + // Song Queue. + "path": "/Service/Notification/SongQueue.json", + "event_name": "songqueue.notification" + }, + { + // Firmware upgrade. + "path": "/Service/Notification/Upgrade.json", + "event_name": "system.upgrade.notification" + }, + { + // Player state. + "path": "/Service/Notification/PlayerState.json", + "event_name": "player.state" + }, + { + // Player track. + "path": "/Service/Notification/PlayerTrack.json", + "event_name": "player.track" + } + ] +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/configs/spotify.json b/src/plugins/uhttpd/uhttpd-rest-api/configs/spotify.json new file mode 100644 index 00000000..fe18b1bf --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/configs/spotify.json @@ -0,0 +1,11 @@ +// Comments +{ + "controller": [ + { + // --- Spotify Connect Controller + "path": "/spotify/Connect.json", + "model_name": "player.spotify.connect", + "raw_response": true + } + ] +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/configs/voice-control.json b/src/plugins/uhttpd/uhttpd-rest-api/configs/voice-control.json new file mode 100644 index 00000000..7b4580a6 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/configs/voice-control.json @@ -0,0 +1,20 @@ +// Comments +{ + "controller": [ + { + "path": "/VoiceControl/State.json", + "model_name": "voice.control", + "get_method": "get" + }, + { + "path": "/VoiceControl/Associate.json", + "model_name": "voice.control", + "set_method": "associate" + }, + { + "path": "/VoiceControl/Disassociate.json", + "model_name": "voice.control", + "set_method": "disassociate" + } + ] +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/http-header.cpp b/src/plugins/uhttpd/uhttpd-rest-api/core/http-header.cpp new file mode 100644 index 00000000..0e1c358a --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/http-header.cpp @@ -0,0 +1,178 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 30/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +extern "C" { +#include +#include +} + +#include + +#include +#include + +#include "http-reason.h" + +#include "http-header.h" + + +const char * const g_http_versions[] = { + [UH_HTTP_VER_0_9] = "HTTP/0.9", + [UH_HTTP_VER_1_0] = "HTTP/1.0", + [UH_HTTP_VER_1_1] = "HTTP/1.1", +}; + + +/*! ---------------------------------------------------------------------------- + * @fn HttpHeader + * + * @brief constructor of the http header. + */ +HttpHeader::HttpHeader (void) : + m_origin("*"), + m_timeout(0) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn HttpHeader + * + * @brief constructor of the http header. + */ +HttpHeader::HttpHeader (uint16_t a_timeout) : + m_origin("*"), + m_timeout(a_timeout) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn send + * + * @brief send an http header response. + */ +void HttpHeader::send (uhttpd_ops *an_ops, client *a_client, uint16_t a_code, bool a_chunked) +{ + prepare_header(a_client, a_code, HttpReason::get(a_code)); + ustream_printf(a_client->us, "Access-Control-Allow-Origin: %s\r\n", m_origin.c_str()); + + if (!m_etag.empty()) { + + ustream_printf(a_client->us, "ETag: %s\r\n", m_etag.c_str()); + } + + ustream_printf(a_client->us, "Content-Type: application/json\r\n"); + + if (!a_chunked) + ustream_printf(a_client->us, "\r\n"); +} + + +/*! ---------------------------------------------------------------------------- + * @fn send_option + * + * @brief send an http header option response. + */ +void HttpHeader::send_option (uhttpd_ops *an_ops, client *a_client, uint16_t a_code) +{ + prepare_header(a_client, a_code, HttpReason::get(a_code)); + ustream_printf(a_client->us, "Access-Control-Allow-Headers: Accept, Content-Type, If-None-Match, Prefer\r\n"); + ustream_printf(a_client->us, "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS\r\n"); + ustream_printf(a_client->us, "Access-Control-Allow-Origin: %s\r\n", m_origin.c_str()); + + an_ops->request_done(a_client); +} + + +/*! ---------------------------------------------------------------------------- + * @fn add_origin + * + * @brief add an etag to the header request. + */ +void HttpHeader::add_origin (const std::string &an_origin) +{ + m_origin = an_origin; +} + + +/*! ---------------------------------------------------------------------------- + * @fn add_etag + * + * @brief add an etag to the header request. + */ +void HttpHeader::add_etag (const std::string &an_etag) +{ + m_etag = an_etag; +} + + +/*! ---------------------------------------------------------------------------- + * @fn add_timeout + * + * @brief change the time out of the header request. + */ +void HttpHeader::add_timeout (uint16_t a_timeout) +{ + m_timeout = a_timeout; +} + + +/*! ---------------------------------------------------------------------------- + * @fn use_chunked + * + * @brief return true if the connection could use a chunked response. + */ +bool HttpHeader::use_chunked (struct client *a_client) +{ + if (a_client->request.version != UH_HTTP_VER_1_1) + return false; + + if (a_client->request.method == UH_HTTP_MSG_HEAD || a_client->request.method == UH_HTTP_MSG_OPTIONS) + return false; + + /* RFC2616 10.2.5, 10.3.5 */ + if (a_client->http_code == 204 || a_client->http_code == 304) + return false; + + return !a_client->request.disable_chunked; +} + + +/*! ---------------------------------------------------------------------------- + * @fn prepare_header + * + * @brief prepare and fill the first part of the http header. + */ +void HttpHeader::prepare_header (struct client *a_client, int a_code, const std::string &a_summary) +{ + struct http_request *the_req = &a_client->request; + const char *the_enc = "Transfer-Encoding: chunked\r\n"; + const char *the_conn; + + a_client->http_code = a_code; + + if (!use_chunked(a_client)) + the_enc = ""; + + if (the_req->connection_close) + the_conn = "Connection: close"; + else + the_conn = "Connection: Keep-Alive"; + + ustream_printf(a_client->us, "%s %03i %s\r\n%s\r\n%s", + g_http_versions[a_client->request.version], + a_code, a_summary.c_str(), the_conn, the_enc); + + if (!the_req->connection_close) + ustream_printf(a_client->us, "Keep-Alive: timeout=%d\r\n", m_timeout); +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/http-header.h b/src/plugins/uhttpd/uhttpd-rest-api/core/http-header.h new file mode 100644 index 00000000..cd4f9345 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/http-header.h @@ -0,0 +1,48 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 30/06/2017 + */ + +#ifndef _HTTP_HEADER_H +#define _HTTP_HEADER_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include + +/*----------------------------- Dependencies --------------------------------*/ + +struct client; +struct uhttpd_ops; + +/*--------------------------------- CLASS ----------------------------------*/ + +class HttpHeader { + +public: + HttpHeader (void); + HttpHeader (uint16_t a_timeout); + + void send (uhttpd_ops *an_ops, client *a_client, uint16_t a_code, bool a_chunked = false); + void send_option (uhttpd_ops *an_ops, client *a_client, uint16_t a_code); + + void add_origin (const std::string &an_origin); + void add_etag (const std::string &an_etag); + void add_timeout (uint16_t a_timeout); + +private: + bool use_chunked (struct client *a_client); + void prepare_header (struct client *a_client, int a_code, const std::string &a_summary); + +private: + std::string m_etag; + std::string m_origin; + uint16_t m_timeout; +}; + +#endif /* _HTTP_HEADER_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/http-parameter.cpp b/src/plugins/uhttpd/uhttpd-rest-api/core/http-parameter.cpp new file mode 100644 index 00000000..7b356089 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/http-parameter.cpp @@ -0,0 +1,218 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 30/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +extern "C" { +#include +} + +#include + +#include + +#include "core/uri-transform.h" + +#include "http-parameter.h" + + +#define kIfNoneMatch "if-none-match" +#define kPrefer "prefer" +#define kOrigin "origin" + +/*! ---------------------------------------------------------------------------- + * @fn HttpParameter + * + * @brief constructor of the http parameter. + */ +HttpParameter::HttpParameter (void) : + m_request_timeout(-1) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn parse_from_uri + * + * @brief parse parameters from the uri. + */ +std::string HttpParameter::parse_from_uri (const std::string &a_parameters) +{ + std::string the_result; + std::vector the_list; + std::vector::iterator the_it; + std::string the_key, the_value, the_value_decoded; + std::size_t the_pos; + struct json_object *the_root_node = NULL; + the_root_node = json_object_new_object(); + + the_list = split_params(a_parameters, '&'); + for (the_it = the_list.begin(); the_it != the_list.end(); ++the_it) { + + // printf("--> %s\n", (*the_it).c_str()); + the_pos = (*the_it).find_first_of("=", 0); + if (the_pos != std::string::npos) { + + the_key = (*the_it).substr(0, the_pos); + the_value = (*the_it).substr(the_pos + 1); + the_value_decoded = UriTransform::decode(the_value); + +#if 0 + printf(" - key = %s\n", the_key.c_str()); + printf(" - value = %s\n", the_value.c_str()); + printf(" - valuedecode = %s\n", the_value_decoded.c_str()); +#endif + if (is_number(the_value_decoded)) + json_object_object_add(the_root_node, the_key.c_str(), json_object_new_int(std::stoi(the_value_decoded))); + else if (the_value_decoded == "true") + json_object_object_add(the_root_node, the_key.c_str(), json_object_new_boolean(true)); + else if (the_value_decoded == "false") + json_object_object_add(the_root_node, the_key.c_str(), json_object_new_boolean(false)); + else + json_object_object_add(the_root_node, the_key.c_str(), json_object_new_string(the_value_decoded.c_str())); + } + } + + the_result = json_object_to_json_string(the_root_node); + + // Clean the json object. + json_object_put(the_root_node); + + return the_result; +} + + +/*! ---------------------------------------------------------------------------- + * @fn parse + * + * @brief parse the http header and save only the wanted headers. + */ +bool HttpParameter::parse (client *a_client) +{ + std::string the_prefer_string; + char *the_string = blobmsg_format_json(a_client->hdr.head, true); + struct json_object *the_root_node = NULL, *the_value_node; + + the_root_node = json_tokener_parse(the_string); + // printf("HttpParameter::parse: <%s>\n", the_string); + free(the_string); + + if (the_root_node == NULL) { + + return false; + } + + // Get if-none-match + if (json_object_object_get_ex(the_root_node, kIfNoneMatch, &the_value_node)) { + + m_etag = json_object_get_string(the_value_node); + } + + // Get Prefer - wait RFC 7240 // Prefer: wait=120 + if (json_object_object_get_ex(the_root_node, kPrefer, &the_value_node)) { + + std::vector the_list; + the_prefer_string = json_object_get_string(the_value_node); + the_list = split_params(the_prefer_string, '='); + std::string the_token = the_list [1]; + //printf("the_prefer_string: %s\n", the_prefer_string.c_str()); + //printf("the_token: %s\n", the_token.c_str()); + + if (is_number(the_token)) { + + m_request_timeout = std::atoi(the_token.c_str()); + } + // printf("timeout = %d\n", m_request_timeout); + } + + // Get Origin + if (json_object_object_get_ex(the_root_node, kOrigin, &the_value_node)) { + + m_origin = json_object_get_string(the_value_node); + } + + json_object_put(the_root_node); + + // printf("parse: E-tag: %s, timeout: %d, origin: %s\n", m_etag.c_str(), m_request_timeout, m_origin.c_str()); + + return true; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_origin + * + * @brief get the etag from the header request. + */ +std::string HttpParameter::get_origin (void) +{ + return m_origin; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_etag + * + * @brief get the etag from the header request. + */ +std::string HttpParameter::get_etag (void) +{ + return m_etag; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_request_timeout + * + * @brief extract the the keep-alive timeout from the header (as second) + * and return it as millisecond timeout. + */ +int16_t HttpParameter::get_request_timeout (void) +{ + return m_request_timeout; +} + + +/*! ---------------------------------------------------------------------------- + * @fn split_params + * + * @brief method to split a string into sub string from a separator. and return a + * vector from all the substrings. + */ +std::vector HttpParameter::split_params (const std::string &a_string, char a_seperator) +{ + std::vector the_output; + + std::string::size_type the_prev_pos = 0, the_pos = 0; + + while ((the_pos = a_string.find(a_seperator, the_pos)) != std::string::npos) { + + std::string substring(a_string.substr(the_prev_pos, the_pos - the_prev_pos)); + the_output.push_back(substring); + the_prev_pos = ++the_pos; + } + + the_output.push_back(a_string.substr(the_prev_pos, the_pos - the_prev_pos)); // Last word + + return the_output; +} + + +/*! ---------------------------------------------------------------------------- + * @fn is_number + * + * @brief return true if the string given has parameter is a number. + */ +bool HttpParameter::is_number (const std::string & a_string) +{ + char* p; + strtod (a_string.c_str(), &p); + return *p == 0; +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/http-parameter.h b/src/plugins/uhttpd/uhttpd-rest-api/core/http-parameter.h new file mode 100644 index 00000000..3849ccea --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/http-parameter.h @@ -0,0 +1,49 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 30/06/2017 + */ + +#ifndef _HTTP_PARAMETERS_H +#define _HTTP_PARAMETERS_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include + +#include +#include + +/*----------------------------- Dependencies --------------------------------*/ + +struct client; + +/*--------------------------------- CLASS ----------------------------------*/ + +class HttpParameter { + +public: + HttpParameter (void); + + std::string parse_from_uri (const std::string &a_parameters); + + bool parse (client *a_client); + + std::string get_origin (void); + std::string get_etag (void); + int16_t get_request_timeout (void); + +private: + std::vector split_params (const std::string &a_string, char a_seperator); + bool is_number (const std::string & a_string); + + std::string m_origin; + std::string m_etag; + int16_t m_request_timeout; +}; + +#endif /* _HTTP_PARAMETERS_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/http-reason.cpp b/src/plugins/uhttpd/uhttpd-rest-api/core/http-reason.cpp new file mode 100644 index 00000000..0ea6f95e --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/http-reason.cpp @@ -0,0 +1,161 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 22/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include "http-reason.h" + + +/*! ---------------------------------------------------------------------------- + * @fn HttpReason + * + * @brief constructor of the http reason. + */ +HttpReason::HttpReason (void) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn get + * + * @brief return as string the reazson from the http code. + */ +std::string HttpReason::get (uint16_t a_code) +{ + switch (a_code) { + // --- 100 + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + // --- 200 + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + // --- 300 + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Moved Temporarily"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + case 310: + return "Too many Redirects"; + // --- 400 + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Time-out"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Request Entity Too Large"; + case 414: + return "Request-URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Requested range unsatisfiable"; + case 417: + return "Expectation failed"; + case 418: + return "I’m a teapot"; + case 421: + return "Bad mapping / Misdirected Request"; + case 422: + return "Unprocessable entity"; + case 423: + return "Locked"; + case 424: + return "Method failure"; + case 425: + return "Unordered Collection"; + case 426: + return "Upgrade Required"; + case 449: + return "Retry With"; + case 450: + return "Blocked by Windows Parental Controls"; + case 451: + return "Unavailable For Legal Reasons"; + case 456: + return "Unrecoverable Error"; + // --- 500 + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Time-out"; + case 505: + return "HTTP Version not supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient storage"; + case 509: + return "Bandwidth Limit Exceeded"; + case 510: + return "Not extended"; + case 511: + return "Network authentication required"; + } + return "Unknown"; +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/http-reason.h b/src/plugins/uhttpd/uhttpd-rest-api/core/http-reason.h new file mode 100644 index 00000000..1f1b5d0e --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/http-reason.h @@ -0,0 +1,29 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 22/06/2017 + */ + +#ifndef _HTTP_REASON_H +#define _HTTP_REASON_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include + +/*--------------------------------- CLASS ----------------------------------*/ + +class HttpReason { + +public: + HttpReason (void); + + static std::string get (uint16_t a_code); +}; + +#endif /* _HTTP_REASON_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/ubus-reason.cpp b/src/plugins/uhttpd/uhttpd-rest-api/core/ubus-reason.cpp new file mode 100644 index 00000000..fe8c582e --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/ubus-reason.cpp @@ -0,0 +1,72 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 07/07/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include + +#include "ubus-reason.h" + + +/*! ---------------------------------------------------------------------------- + * @fn UbusReason + * + * @brief constructor of the ubus reason. + */ +UbusReason::UbusReason (void) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn get + * + * @brief return as an http code from an ubus code. + */ +uint16_t UbusReason::get (int16_t an_exec_result) +{ + int the_result_code; + // printf("%s result code: %d\n", __PRETTY_FUNCTION__, an_exec_result); + + switch (an_exec_result) { + + case 0: + the_result_code = 200; // HTTP_OK + break; + case -1: + the_result_code = 404; // HTTP_NOT_FOUND + break; + case -2: + the_result_code = 507; // HTTP_INSUFFICIENT_STORAGE + break; + case UBUS_STATUS_METHOD_NOT_FOUND: + the_result_code = 405; // HTTP_METHOD_NOT_ALLOWED + break; + case -3: + the_result_code = 500; // HTTP_INTERNAL_SERVER_ERROR + break; + case -4: // NotImplemented + case 4: + the_result_code = 501; // HTTP_NOT_IMPLEMENTED + break; + case kExecInvalidArguments: + case UBUS_STATUS_INVALID_ARGUMENT: + the_result_code = 412; // HTTP_PRECONDITION_FAILED + break; + case kExecFailed: + the_result_code = 400; // HTTP_BAD_REQUEST + break; + default: + the_result_code = 500; // HTTP_INTERNAL_SERVER_ERROR + break; + } + + return the_result_code; +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/ubus-reason.h b/src/plugins/uhttpd/uhttpd-rest-api/core/ubus-reason.h new file mode 100644 index 00000000..14ab15e0 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/ubus-reason.h @@ -0,0 +1,29 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 07/07/2017 + */ + +#ifndef _UBUS_REASON_H +#define _UBUS_REASON_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include + +/*--------------------------------- CLASS ----------------------------------*/ + +class UbusReason { + +public: + UbusReason (void); + + static uint16_t get (int16_t an_exec_result); +}; + +#endif /* _UBUS_REASON_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/uri-transform.cpp b/src/plugins/uhttpd/uhttpd-rest-api/core/uri-transform.cpp new file mode 100644 index 00000000..767fcb11 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/uri-transform.cpp @@ -0,0 +1,176 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 22/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include "uri-transform.h" + + + +// Only alphanum is safe. +const char SAFE[256] = +{ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /* 0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + /* 1 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + /* 2 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + /* 3 */ 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0, + + /* 4 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, + /* 5 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0, + /* 6 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, + /* 7 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0, + + /* 8 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + /* 9 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + /* A */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + /* B */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + + /* C */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + /* D */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + /* E */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + /* F */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 +}; + +const int8_t HEX2DEC[256] = +{ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /* 0 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* 1 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* 2 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* 3 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1, -1,-1,-1,-1, + + /* 4 */ -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* 5 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* 6 */ -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* 7 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + + /* 8 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* 9 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* A */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* B */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + + /* C */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* D */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* E */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + /* F */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 +}; + +std::string iso_8859_1_to_utf8 (std::string str) +{ + std::string strOut; + for (std::string::iterator it = str.begin(); it != str.end(); ++it) + { + uint8_t ch = *it; + if (ch < 0x80) { + strOut.push_back(ch); + } + else { + strOut.push_back(0xc0 | ch >> 6); + strOut.push_back(0x80 | (ch & 0x3f)); + } + } + return strOut; +} + + +/*! ---------------------------------------------------------------------------- + * @fn UriTransform + * + * @brief class to encode and decode a string from an uri. + */ +UriTransform::UriTransform (void) +{ +} + +/*! ---------------------------------------------------------------------------- + * @fn encode + * + * @brief Encode A string. + */ +std::string UriTransform::encode (const std::string &a_src) +{ + const char DEC2HEX[16 + 1] = "0123456789ABCDEF"; + const unsigned char * p_src = (const unsigned char *)a_src.c_str(); + const int SRC_LEN = a_src.length(); + unsigned char * const p_start = new unsigned char[SRC_LEN * 3]; + unsigned char * p_end = p_start; + const unsigned char * const SRC_END = p_src + SRC_LEN; + + for (; p_src < SRC_END; ++p_src) { + + if (SAFE[*p_src]) + *p_end++ = *p_src; + else { + + // escape this char + *p_end++ = '%'; + *p_end++ = DEC2HEX[*p_src >> 4]; + *p_end++ = DEC2HEX[*p_src & 0x0F]; + } + } + + std::string s_result((char *)p_start, (char *)p_end); + delete [] p_start; + return s_result; +} + + +/*! ---------------------------------------------------------------------------- + * @fn decode + * + * @brief Decode a String + */ +std::string UriTransform::decode (const std::string &a_src) +{ + // Note from RFC1630: "Sequences which start with a percent sign + // but are not followed by two hexadecimal characters (0-9, A-F) are reserved + // for future extension" + + const unsigned char * p_src = (const unsigned char *)a_src.c_str(); + const int SRC_LEN = a_src.length(); + const unsigned char * const SRC_END = p_src + SRC_LEN; + const unsigned char * const SRC_LAST_DEC = SRC_END - 2; // last decodable '%' + + char * const p_start = new char[SRC_LEN]; + char * p_end = p_start; + + while (p_src < SRC_LAST_DEC) { + + if (*p_src == '%') { + + char dec1, dec2; + if (-1 != (dec1 = HEX2DEC[*(p_src + 1)]) + && -1 != (dec2 = HEX2DEC[*(p_src + 2)])) { + + *p_end++ = (dec1 << 4) + dec2; + p_src += 3; + continue; + } + } + + *p_end++ = *p_src++; + } + + // the last 2- chars + while (p_src < SRC_END) + *p_end++ = *p_src++; + + std::string s_result(p_start, p_end); + delete [] p_start; + + //char *theData = awUTF_ISO88591ToUTF8 (sResult.c_str(), sResult.length()); + + std::string theoutput = iso_8859_1_to_utf8(s_result); + //sResult= theData; + //free(theData); + + return theoutput; +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/uri-transform.h b/src/plugins/uhttpd/uhttpd-rest-api/core/uri-transform.h new file mode 100644 index 00000000..e59525ae --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/uri-transform.h @@ -0,0 +1,30 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 22/06/2017 + */ + +#ifndef _URI_TRANSFORM_H +#define _URI_TRANSFORM_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include + +/*--------------------------------- CLASS ----------------------------------*/ + +class UriTransform { + +public: + UriTransform (void); + + static std::string encode (const std::string &a_src); + static std::string decode (const std::string &a_src); +}; + +#endif /* _URI_TRANSFORM_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/web-connection.cpp b/src/plugins/uhttpd/uhttpd-rest-api/core/web-connection.cpp new file mode 100644 index 00000000..c151df28 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/web-connection.cpp @@ -0,0 +1,85 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 23/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include + +#include "core/web-connection.h" + + +/*! ---------------------------------------------------------------------------- + * @fn WebConnection + * + * @brief constructor of the web connection object. + */ +WebConnection::WebConnection (struct uhttpd_ops *an_ops, struct client *a_client) : + m_ops(an_ops), + m_client(a_client), + m_should_be_removed(true) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn ~WebConnection + * + * @brief destructor of the web connection object. + */ +WebConnection::~WebConnection (void) +{ + // printf("WebConnection:: desctructor....\n"); +} + + +/*! ---------------------------------------------------------------------------- + * @fn add_data + * + * @brief add data to the request. + */ +int WebConnection::add_data (const char *a_data, int a_len) +{ + m_connection_data += a_data; + + return a_len; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_client + * + * @brief return pointer on the client object. + */ +struct client * WebConnection::get_client (void) +{ + return m_client; +} + + +/*! ---------------------------------------------------------------------------- + * @fn invoke + * + * @brief invoke a web connection. + */ +void WebConnection::invoke (struct ubus_context *a_ctx) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn should_be_removed + * + * @brief return true if the connection should be removed. + */ +bool WebConnection::should_be_removed (void) +{ + return m_should_be_removed; +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/web-connection.h b/src/plugins/uhttpd/uhttpd-rest-api/core/web-connection.h new file mode 100644 index 00000000..64dc50f3 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/web-connection.h @@ -0,0 +1,46 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 23/06/2017 + */ + +#ifndef _WEB_CONNECTION_H +#define _WEB_CONNECTION_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include + +/*----------------------------- Dependencies --------------------------------*/ + +struct client; +struct uhttpd_ops; +struct ubus_context; + +/*--------------------------------- CLASS ----------------------------------*/ + +class WebConnection { + +public: + WebConnection (struct uhttpd_ops *an_ops, struct client *a_client); + virtual ~WebConnection (void); + + int add_data (const char *a_data, int a_len); + struct client *get_client (void); + + virtual void invoke (struct ubus_context *a_ctx); + + bool should_be_removed (void); + +protected: + struct uhttpd_ops *m_ops; + struct client *m_client; + std::string m_connection_data; + bool m_should_be_removed; +}; + +#endif /* _WEB_CONNECTION_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/web-controller.cpp b/src/plugins/uhttpd/uhttpd-rest-api/core/web-controller.cpp new file mode 100644 index 00000000..e96fa7fe --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/web-controller.cpp @@ -0,0 +1,79 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 23/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include "core/web-connection.h" + +#include "core/web-controller.h" + +/*! ---------------------------------------------------------------------------- + * @fn WebController + * + * @brief constructor of the web controller object. + */ +WebController::WebController (const std::string &a_path) : + m_path(a_path) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn ~WebController + * + * @brief destructor of the web controller object. + */ +WebController::~WebController (void) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn set_name + * + * @brief set the name of the controller + */ +void WebController::set_name (const std::string &a_name) +{ + m_name = a_name; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_name + * + * @brief return the name of the controller. + */ +const std::string &WebController::get_name (void) +{ + return m_name; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_path + * + * @brief return the model path of the controller. + */ +std::string WebController::get_path (void) +{ + return m_path; +} + + +/*! ---------------------------------------------------------------------------- + * @fn new_connection + * + * @brief return a new connection object. + */ +WebConnection *WebController::new_connection (struct uhttpd_ops *an_ops, struct client *a_client, const std::string &a_parameters) +{ + return new WebConnection(an_ops, a_client); +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/core/web-controller.h b/src/plugins/uhttpd/uhttpd-rest-api/core/web-controller.h new file mode 100644 index 00000000..9c2e7fc9 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/core/web-controller.h @@ -0,0 +1,47 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 23/06/2017 + */ + +#ifndef _WEB_CONTROLLER_H +#define _WEB_CONTROLLER_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include + +/*--------------------------------- Define ----------------------------------*/ + +/*----------------------------- Dependencies --------------------------------*/ + +class WebConnection; +struct uhttpd_ops; +struct client; + +/*--------------------------------- CLASS ----------------------------------*/ + +class WebController { + +public: + WebController (const std::string &a_path); + virtual ~WebController (void); + + void set_name (const std::string &a_name); + const std::string &get_name (void); + + std::string get_path (void); + + virtual WebConnection *new_connection (struct uhttpd_ops *an_ops, struct client *a_client, const std::string &a_parameters); + +private: + std::string m_name; + std::string m_path; +}; + + +#endif /* _WEB_CONTROLLER_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-connection.cpp b/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-connection.cpp new file mode 100644 index 00000000..9d5d61d1 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-connection.cpp @@ -0,0 +1,281 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 28/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include + +#include + +#include + +#include + +#include "etag-rest/etag-rest-controller.h" + +#include "core/http-reason.h" +#include "core/http-parameter.h" + +#include "etag-rest-connection.h" + +#include +#include + + +#define kdefaultTimeout 30000 + +/*! ---------------------------------------------------------------------------- + * @fn EtagRestConnection + * + * @brief constructor of the etag rest connection object. + */ +EtagRestConnection::EtagRestConnection (struct uhttpd_ops *an_ops, struct client *a_client, EtagRestController *a_controller, const std::string &a_parameters) : + WebConnection(an_ops, a_client), + m_controller(a_controller), + m_http_header(kdefaultTimeout), + m_ctx(NULL), + m_wait_response_timeout(kdefaultTimeout) +{ + uh_client_ref(a_client); + // printf("%s\n", __PRETTY_FUNCTION__); +} + + +/*! ---------------------------------------------------------------------------- + * @fn ~EtagRestConnection + * + * @brief destructor of the etag rest connection object. + */ +EtagRestConnection::~EtagRestConnection (void) +{ + // printf("%s for client: %d\n", __PRETTY_FUNCTION__, m_client->id); + // Stop Timers. + stop(); + // Stop receive async events. + if (m_ctx != NULL) { + + unregister_event(m_ctx); + abort(m_ctx); + } + + // printf("%s-done\n", __PRETTY_FUNCTION__); + uh_client_unref(m_client); +} + + +/*! ---------------------------------------------------------------------------- + * @fn invoke + * + * @brief invoke the etag rest connection. + */ +void EtagRestConnection::invoke (struct ubus_context *a_ctx) +{ + uint32_t the_id = 0; + awUBusCall the_cmd; + std::string the_method; + + m_ctx = a_ctx; + + the_method = m_controller->get_method(m_client->request.method); + + parse_parameter(); + + // printf("%s (object: %s)....\n", __PRETTY_FUNCTION__, m_controller->get_path().c_str()); + if (!ubus_lookup_id(a_ctx, m_controller->get_path().c_str(), &the_id)) { + + // printf ("%s launch async call (%s)(%s)....\n", __PRETTY_FUNCTION__, the_method.c_str(), m_parameters.c_str()); + the_cmd.ExecAsync(a_ctx, the_id, the_method, m_parameters, this); + } + + if (m_client->request.method == UH_HTTP_MSG_GET) { + // printf("start timer (%d)....\n", m_wait_response_timeout); + start(m_wait_response_timeout, false); + } +} + + +/*! ---------------------------------------------------------------------------- + * @fn complete + * + * @brief method called when an async exec result is arrived for this connection. + */ +void EtagRestConnection::complete (void) +{ + // printf("%s.\n", __PRETTY_FUNCTION__); + manage_response(get_data()); +} + + +/*! ---------------------------------------------------------------------------- + * @fn handle_event + * + * @brief method called when a registered event is arrived for this connection. + */ +void EtagRestConnection::handle_event (const char *a_type, const char *a_json_msg) +{ + // printf("%s.\n", __PRETTY_FUNCTION__); + manage_response(a_json_msg); +} + + +/*! ---------------------------------------------------------------------------- + * @fn parse_parameter + * + * @brief Extract parameters from the http header to get the wanted parameters by this controller. + */ +int EtagRestConnection::parse_parameter (void) +{ + HttpParameter the_http_params; + std::string the_origin; + std::string the_etag; + int32_t the_wait_response_timeout = -1; + struct json_object *the_response_node; + + if (the_http_params.parse(m_client)) { + + the_etag = the_http_params.get_etag(); + the_wait_response_timeout = the_http_params.get_request_timeout(); + the_origin = the_http_params.get_origin(); + if (!the_origin.empty()) + m_http_header.add_origin(the_origin); + } + // printf("EtagRestConnection::parse_parameter timeout: %d\n", the_wait_response_timeout); + if (the_wait_response_timeout != -1) { + + // Convert it as milli seconds. + m_wait_response_timeout = the_wait_response_timeout * 1000; + } + + // printf("%s - etag: %s, keep alive: %d\n", __PRETTY_FUNCTION__, the_etag.c_str(), the_wait_response_timeout); + + if (m_client->request.method == UH_HTTP_MSG_GET) { + + the_response_node = json_object_new_object(); + + if (!the_etag.empty()) + json_object_object_add(the_response_node, m_controller->get_etag_key().c_str(), json_object_new_string(the_etag.c_str())); + + m_parameters = json_object_to_json_string(the_response_node); + json_object_put(the_response_node); + } + else { + struct json_object *the_content_node, *the_root_node; + + if (!m_connection_data.empty()) { + + the_root_node = json_object_new_object(); + the_content_node = json_tokener_parse(m_connection_data.c_str()); + if (the_content_node != NULL) { + + json_object_object_add(the_root_node, kEvent_Message_content_entry, the_content_node); + } + + m_parameters = json_object_to_json_string(the_root_node); + json_object_put(the_root_node); + } + } + + return 0; +} + + +/*! ---------------------------------------------------------------------------- + * @fn manage_response + * + * @brief manage the response rerceived by an event or an async response. + */ +void EtagRestConnection::manage_response (std::string a_response_data) +{ + std::string the_etag; + struct json_object *the_root_node, *the_status_node, *the_content_node, *the_etag_node; + int the_result_code = 500; + + // printf("receive notification client id: %d!!!!\n", m_client->id); + // printf("\tdata: (%s)\n", a_response_data.c_str()); + + the_root_node = json_tokener_parse(a_response_data.c_str()); + if (the_root_node == NULL) { + + // response couldn't be parsed. + m_http_header.send(m_ops, m_client, 500); + m_ops->chunk_printf(m_client, "0\r\n\r\n"); + // printf("response couldn't be parsed.\n"); + return; + } + + // get status + if (json_object_object_get_ex(the_root_node, kEvent_Message_status_entry, &the_status_node)) { + + the_result_code = json_object_get_int(the_status_node); + } + + // get etag. + if (json_object_object_get_ex(the_root_node, kEvent_Message_version_entry, &the_etag_node)) { + + enum json_type the_json_type; + + the_json_type = json_object_get_type(the_etag_node); + if (the_json_type == json_type_int) { + + int the_tmp_value; + the_tmp_value = json_object_get_int(the_etag_node); + the_etag = std::to_string(the_tmp_value); + } + else if (the_json_type == json_type_string) { + the_etag = json_object_get_string(the_etag_node); + } + + m_http_header.add_etag(the_etag); + } + + // get contents + the_content_node = NULL; + if (!json_object_object_get_ex(the_root_node, kEvent_Message_content_entry, &the_content_node)) { + + the_content_node = the_root_node; + } + + if (the_result_code == 204) { + + json_object_put(the_root_node); + register_event(m_ctx, m_controller->get_event_name()); + return; + } + + // stop the timer. + stop(); + + m_http_header.send(m_ops, m_client, the_result_code); + + m_ops->chunk_printf(m_client, "%s", json_object_to_json_string(the_content_node)); + + json_object_put(the_root_node); + + m_ops->request_done(m_client); +} + + +/*! ---------------------------------------------------------------------------- + * @fn expire + * + * @brief method called when the connection time has expired. we will close the connection + * with a 204 + */ +int EtagRestConnection::expire (void) +{ + // printf("%s client id: %d, path:%s\n", __PRETTY_FUNCTION__, m_client->id, m_controller->get_path().c_str()); + stop(); + + m_http_header.send(m_ops, m_client, 204); + uh_client_ref(m_client); + m_ops->request_done(m_client); + + return 0; +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-connection.h b/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-connection.h new file mode 100644 index 00000000..59b33eac --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-connection.h @@ -0,0 +1,64 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 28/06/2017 + */ + +#ifndef _ETAG_REST_CONNECTION_H +#define _ETAG_REST_CONNECTION_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include + +#include +#include +#include "aw-ubus/UBusEventReceiver.h" +#include + +#include "core/http-header.h" + +#include "core/web-connection.h" + +/*----------------------------- Dependencies --------------------------------*/ + +class EtagRestController; +struct ubus_context; + +/*--------------------------------- CLASS ----------------------------------*/ + +class EtagRestConnection : public WebConnection, public awUBusExecReceiver, public awUBusEventReceiver, public ULoopTimer { + +public: + EtagRestConnection (struct uhttpd_ops *an_ops, struct client *a_client, EtagRestController *a_controller, const std::string &a_parameters); + ~EtagRestConnection (void); + + // WebConnection + void invoke (struct ubus_context *a_ctx); + + // awUBusExecReceiver + void complete (void); + + // awUBusEventReceiver + void handle_event (const char *a_type, const char *a_json_msg); + + // ULoopTimer. + int expire (void); + +private: + int parse_parameter (void); + void manage_response (std::string a_response_data); + +private: + EtagRestController *m_controller; + HttpHeader m_http_header; + struct ubus_context *m_ctx; + int32_t m_wait_response_timeout; + std::string m_parameters; +}; + +#endif /* _ETAG_REST_CONNECTION_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-controller.cpp b/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-controller.cpp new file mode 100644 index 00000000..7a6633b0 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-controller.cpp @@ -0,0 +1,75 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 28/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + + +#include "etag-rest/etag-rest-connection.h" + +#include "etag-rest/etag-rest-controller.h" + + +/*! ---------------------------------------------------------------------------- + * @fn EtagRestController + * + * @brief constructor of the etag rest controller object. + */ +EtagRestController::EtagRestController (const std::string &a_path, const std::string &an_event_name, const std::string &an_etag_key, + const std::string &a_method_get, const std::string &a_method_put, int a_timeout, bool a_raw_response) : + RestController(a_path, a_method_get, a_method_put, a_timeout, a_raw_response), + m_event_name(an_event_name), + m_etag_key(an_etag_key) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn ~EtagRestController + * + * @brief destructor of the etag rest controller object. + */ +EtagRestController::~EtagRestController (void) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn new_connection + * + * @brief return a new etag rest connection attached to this controller. + */ +WebConnection *EtagRestController::new_connection (struct uhttpd_ops *an_ops, struct client *a_client, const std::string &a_parameters) +{ + // printf("%s.\n", __PRETTY_FUNCTION__); + + return new EtagRestConnection(an_ops, a_client, this, a_parameters); +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_event_name + * + * @brief set the etag key attached to the etag rest controller. + */ +std::string EtagRestController::get_event_name (void) +{ + return m_event_name; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_etag_key + * + * @brief return the etag key attached to the etag rest controller. + */ +std::string EtagRestController::get_etag_key (void) +{ + return m_etag_key; +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-controller.h b/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-controller.h new file mode 100644 index 00000000..9d6c46a1 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/etag-rest/etag-rest-controller.h @@ -0,0 +1,50 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 28/06/2017 + */ + +#ifndef _ETAG_REST_CONTROLLER_H +#define _ETAG_REST_CONTROLLER_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include + +#include + +/*--------------------------------- Define ----------------------------------*/ + +#define kDefaultTimeout 20 +#define kPassed "passed" +#define kFailed "failed" + +/*----------------------------- Dependencies --------------------------------*/ + +/*--------------------------------- CLASS ----------------------------------*/ + +class EtagRestController : public RestController { + +public: + EtagRestController (const std::string &a_path, const std::string &an_event_name, const std::string &an_etag_key, + const std::string &a_method_get = "", const std::string &a_method_put = "", + int a_timeout = kDefaultTimeout, bool a_raw_response = false); + ~EtagRestController (void); + + WebConnection *new_connection (struct uhttpd_ops *an_ops, struct client *a_client, const std::string &a_parameters); + + std::string get_event_name (void); + std::string get_etag_key (void); + +private: + std::string m_event_name; + std::string m_etag_key; +}; + + +#endif /* _ETAG_REST_CONTROLLER_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-connection.cpp b/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-connection.cpp new file mode 100644 index 00000000..cd9a0237 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-connection.cpp @@ -0,0 +1,101 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 23/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +extern "C" { +#include +} + +#include +#include + +#include + +#include "core/http-reason.h" + +#include "notification/notification-controller.h" +#include "notification/notification-connection.h" + +#define kdefaultTimeout 20000 + + +/*! ---------------------------------------------------------------------------- + * @fn NotificationConnection + * + * @brief constructor of the notification connection object. + */ +NotificationConnection::NotificationConnection (struct uhttpd_ops *an_ops, struct client *a_client, NotificationController *a_controller) : + WebConnection(an_ops, a_client), + m_controller(a_controller), + m_ctx(NULL) +{ + // printf("constructor NotificationConnection : %p\n", this); +} + + +/*! ---------------------------------------------------------------------------- + * @fn ~NotificationConnection + * + * @brief destructor of the notification controller object. + */ +NotificationConnection::~NotificationConnection (void) +{ + //printf("destructor NotificationConnection : %p\n", this); + stop(); + if (m_ctx != NULL) { + unregister_event(m_ctx); + } +} + + +/*! ---------------------------------------------------------------------------- + * @fn invoke + * + * @brief invoke an async connection. + */ +void NotificationConnection::invoke (struct ubus_context *a_ctx) +{ + m_ctx = a_ctx; + + register_event(m_ctx, m_controller->get_path()); + + m_ops->http_header(m_client, 200, HttpReason::get(200).c_str()); + ustream_printf(m_client->us, "Content-Type: application/json\r\n"); + start(kdefaultTimeout, true); +} + + +/*! ---------------------------------------------------------------------------- + * @fn handle_event + * + * @brief method called when a registered event is arrived for this connection. + */ +void NotificationConnection::handle_event (const char *a_type, const char *a_json_msg) +{ + // printf("NotificationConnection::handle_event\n"); + stop(); + m_ops->chunk_printf(m_client, "%s", a_json_msg); + start(kdefaultTimeout, true); +} + + +/*! ---------------------------------------------------------------------------- + * @fn expire + * + * @brief method called when no data is arrived to the notification canal. + */ +int NotificationConnection::expire (void) +{ + // printf ("NotificationConnection::expire\n"); + m_ops->chunk_printf(m_client, "{}"); + + return 0; +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-connection.h b/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-connection.h new file mode 100644 index 00000000..000fc675 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-connection.h @@ -0,0 +1,47 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 23/06/2017 + */ + +#ifndef _NOTIFICATION_CONNECTION_H +#define _NOTIFICATION_CONNECTION_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include "aw-ubus/UBusEventReceiver.h" +#include "core/web-connection.h" + +/*----------------------------- Dependencies --------------------------------*/ + +class NotificationController; +struct ubus_context; + +/*--------------------------------- CLASS ----------------------------------*/ + +class NotificationConnection : public WebConnection, public awUBusEventReceiver, public ULoopTimer { + +public: + NotificationConnection (struct uhttpd_ops *an_ops, struct client *a_client, NotificationController *a_controller); + virtual ~NotificationConnection (void); + + // WebConnection + void invoke (struct ubus_context *a_ctx); + + // awUBusEventReceiver + void handle_event (const char *a_type, const char *a_json_msg); + + // Timer. + int expire (void); + +private: + NotificationController *m_controller; + struct ubus_context *m_ctx; +}; + +#endif /* _NOTIFICATION_CONNECTION_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-controller.cpp b/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-controller.cpp new file mode 100644 index 00000000..230b5f9d --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-controller.cpp @@ -0,0 +1,48 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 23/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include "notification/notification-connection.h" + +#include "notification/notification-controller.h" + +/*! ---------------------------------------------------------------------------- + * @fn NotificationController + * + * @brief constructor of the notification controller object. + */ +NotificationController::NotificationController (const std::string &a_path) : + WebController(a_path) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn ~NotificationController + * + * @brief destructor of the notification controller object. + */ +NotificationController::~NotificationController (void) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn new_connection + * + * @brief create a new connection for this controller. + */ +WebConnection *NotificationController::new_connection (struct uhttpd_ops *an_ops, struct client *a_client, const std::string &a_parameters) +{ + // printf("%s.\n", __PRETTY_FUNCTION__); + + return new NotificationConnection(an_ops, a_client, this); +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-controller.h b/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-controller.h new file mode 100644 index 00000000..5d14f843 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/notification/notification-controller.h @@ -0,0 +1,31 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 22/06/2017 + */ + +#ifndef _NOTIFICATION_CONTROLLER_H +#define _NOTIFICATION_CONTROLLER_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include + +#include + +/*--------------------------------- CLASS ----------------------------------*/ + +class NotificationController : public WebController { +public: + NotificationController (const std::string &a_path); + ~NotificationController (void); + + WebConnection *new_connection (struct uhttpd_ops *an_ops, struct client *a_client, const std::string &a_parameters); +}; + +#endif /* _NOTIFICATION_CONTROLLER_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/plugin.c b/src/plugins/uhttpd/uhttpd-rest-api/plugin.c new file mode 100644 index 00000000..cfafa6ea --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/plugin.c @@ -0,0 +1,83 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @author: Awox + * @date: 15/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include + +#include +#include + +/*--------------------------------- PLUGINS ----------------------------------*/ + +extern struct ubus_context *get_uhttp_server_ctx (void); +extern int uhttp_server_setup (struct ubus_context *a_ctx, const struct uhttpd_ops *an_ops); +extern bool uhttp_server_check_url (const char *an_url); +extern void uhttp_server_handle_request (struct client *a_cl, char *an_url, struct path_info *a_pi); + + +bool uhttp_server_check_path(struct path_info *pi, const char *url) +{ + // printf ("uhttp_server_check_path: url: %s\n", url); + return true; +} + +/*! ---------------------------------------------------------------------------- */ + +static struct dispatch_handler g_ubus_dispatch = { + + .check_url = uhttp_server_check_url, + .handle_request = uhttp_server_handle_request, +}; + + +/*! ---------------------------------------------------------------------------- + * @fn uh_awox_api_plugin_init + * + * @brief + */ +static int uh_awox_api_plugin_init (const struct uhttpd_ops *an_ops, struct config *a_conf) +{ + struct ubus_context *the_ctx; + + the_ctx = ubus_connect(NULL); + if (!the_ctx) { + + fprintf(stderr, "Unable to connect to ubus socket\n"); + return -1; + } + + an_ops->dispatch_add(&g_ubus_dispatch); + + uloop_done(); + + uhttp_server_setup(the_ctx, an_ops); + + return 0; +} + + +/*! ---------------------------------------------------------------------------- + * @fn uh_awox_api_plugin_post_init + * + * @brief + */ +static void uh_awox_api_plugin_post_init (void) +{ + ubus_add_uloop(get_uhttp_server_ctx()); +} + +/*! ---------------------------------------------------------------------------- */ + +struct uhttpd_plugin uhttpd_plugin = { + + .init = uh_awox_api_plugin_init, + .post_init = uh_awox_api_plugin_post_init, +}; diff --git a/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-connection.cpp b/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-connection.cpp new file mode 100644 index 00000000..d57fbd9b --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-connection.cpp @@ -0,0 +1,189 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 20/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include + +#include + +extern "C" { +#include +#include +} + +#include + +#include "core/http-reason.h" +#include "core/http-parameter.h" +#include "core/http-header.h" +#include "core/ubus-reason.h" +#include "core/uri-transform.h" + +#include "rest/rest-controller.h" + +#include "rest/rest-connection.h" + + +/*! ---------------------------------------------------------------------------- + * @fn RestConnection + * + * @brief constructor of the rest connection object. + */ +RestConnection::RestConnection (struct uhttpd_ops *an_ops, struct client *a_client, RestController *a_controller, const std::string &a_parameters) : + WebConnection(an_ops, a_client), + m_controller(a_controller), m_ctx(0) +{ + if (!a_parameters.empty()) { + + HttpParameter the_http_parm; + m_connection_data = the_http_parm.parse_from_uri(a_parameters); + } + // printf("RestConnection:: constructor....\n"); +} + + +/*! ---------------------------------------------------------------------------- + * @fn ~RestConnection + * + * @brief destructor of the rest connection object. + */ +RestConnection::~RestConnection (void) +{ + abort(m_ctx); +} + + +/*! ---------------------------------------------------------------------------- + * @fn complete + * + * @brief method called when the async call has been done. + */ +void RestConnection::complete (void) +{ + int the_result_code; + // printf("resultat: <%s>\n", get_data().c_str()); + + the_result_code = UbusReason::get(get_result()); + + send_response(the_result_code, get_data().c_str()); +} + + +/*! ---------------------------------------------------------------------------- + * @fn invoke + * + * @brief invoke an async connection. + */ +void RestConnection::invoke (struct ubus_context *a_ctx) +{ + uint32_t the_id = 0; + awUBusCall the_cmd; + std::string the_method; + + m_ctx = a_ctx; + + the_method = m_controller->get_method(m_client->request.method); + + // printf("RestConnection::invoke(object: %s)....\n", m_controller->get_path().c_str()); + if (!ubus_lookup_id(a_ctx, m_controller->get_path().c_str(), &the_id)) { + json_object *the_parameter_doc; + + the_parameter_doc = json_tokener_parse(m_connection_data.c_str()); + + if (the_parameter_doc == 0) + the_parameter_doc = parse_form_encoded_data(); + + // printf("RestConnection::invoke launch async call (%d)....\n", the_id); + the_cmd.ExecAsync(a_ctx, the_id, the_method, json_object_to_json_string(the_parameter_doc), this); + + json_object_put(the_parameter_doc); + } + else + { + send_response(404); + } +} + +/*! ---------------------------------------------------------------------------- + * @fn parse_form_encoded_data + * + * @brief Parse the document content as form encoded parameters + * and return them as a json object with an attribute for each parameter. + */ +json_object* RestConnection::parse_form_encoded_data () +{ + json_object *the_parsed_document; + char *the_parameters = strdup(m_connection_data.c_str()); + const char *the_encoded_name; + const char *the_encoded_value; + + the_parsed_document = json_object_new_object(); + + for (the_encoded_name = strtok(the_parameters, "="); the_encoded_name != NULL; the_encoded_name = strtok(NULL, "=")) { + std::string the_name, the_value; + + the_encoded_value = strtok(NULL, "&"); + + if (the_encoded_value == NULL) + the_encoded_value = ""; + + the_name = UriTransform::decode(std::string(the_encoded_name)); + the_value = UriTransform::decode(std::string(the_encoded_value)); + + json_object_object_add(the_parsed_document, the_name.c_str(), json_object_new_string(the_value.c_str())); + } + + free(the_parameters); + + return the_parsed_document; +} + +/*! ---------------------------------------------------------------------------- + * @fn send_response + * + * @brief Send a response to the client + */ +void RestConnection::send_response(int a_result_code, std::string a_content_document) +{ + std::string the_status; + struct json_object *the_root_node; + HttpHeader the_http_header; + + if (m_controller->is_raw_response()) { + + the_http_header.send(m_ops, m_client, a_result_code); + m_ops->chunk_printf(m_client, "%s", get_data().c_str()); + m_ops->request_done(m_client); + return; + } + + if (a_result_code == 200) + the_status = kPassed; + else + the_status = kFailed; + + if (a_content_document.empty()) { + the_root_node = json_object_new_object(); + } + else { + the_root_node = json_tokener_parse(get_data().c_str()); + } + + json_object_object_add(the_root_node, "id", json_object_new_string(m_controller->get_name().c_str())); + json_object_object_add(the_root_node, kEvent_Message_status_entry, json_object_new_string(the_status.c_str())); + json_object_object_add(the_root_node, "response_code", json_object_new_int(a_result_code)); + + the_http_header.send(m_ops, m_client, a_result_code); + m_ops->chunk_printf(m_client, "%s", json_object_to_json_string(the_root_node)); + m_ops->request_done(m_client); + + json_object_put(the_root_node); +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-connection.h b/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-connection.h new file mode 100644 index 00000000..72c67713 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-connection.h @@ -0,0 +1,48 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 20/06/2017 + */ + +#ifndef _REST_CONNECTION_H +#define _REST_CONNECTION_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include + +#include "core/web-connection.h" + +#include + +/*----------------------------- Dependencies --------------------------------*/ + +class RestController; +struct json_object; + +/*--------------------------------- CLASS ----------------------------------*/ + +class RestConnection : public WebConnection, public awUBusExecReceiver { + +public: + RestConnection (struct uhttpd_ops *an_ops, struct client *a_client, RestController *a_controller, const std::string &a_parameters); + ~RestConnection (void); + + void complete (void); + + void invoke (struct ubus_context *a_ctx); + +private: + RestController *m_controller; + struct ubus_context *m_ctx; + + json_object *parse_form_encoded_data (void); + void send_response (int a_result_code, std::string a_content_document = ""); +}; + +#endif /* _REST_CONNECTION_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-controller.cpp b/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-controller.cpp new file mode 100644 index 00000000..bfcbb977 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-controller.cpp @@ -0,0 +1,121 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 19/06/2017 + */ + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include "rest/rest-connection.h" + +#include "rest/rest-controller.h" + +#include + +/*! ---------------------------------------------------------------------------- + * @fn RestController + * + * @brief constructor of the rest controller object. + */ +RestController::RestController (const std::string &a_path, const std::string &a_method_get, const std::string &a_method_put, int a_timeout, bool a_raw_response) : + WebController(a_path), + m_method_get(a_method_get), + m_method_put(a_method_put), + m_timeout(a_timeout), + mf_raw_response(a_raw_response) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn ~RestController + * + * @brief destructor of the rest controller object. + */ +RestController::~RestController (void) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn new_connection + * + * @brief return a new rest connection attached to this controller. + */ +WebConnection *RestController::new_connection (struct uhttpd_ops *an_ops, struct client *a_client, const std::string &a_parameters) +{ + return new RestConnection(an_ops, a_client, this, a_parameters); +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_method_get + * + * @brief return the get method of the controller. + */ +std::string RestController::get_method_get (void) +{ + return m_method_get; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_method_put + * + * @brief return the put method of the controller. + */ +std::string RestController::get_method_put (void) +{ + return m_method_put; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_timeout + * + * @brief return the wanted timeout of the controller. + */ +uint16_t RestController::get_timeout (void) +{ + return m_timeout; +} + + +/*! ---------------------------------------------------------------------------- + * @fn is_raw_response + * + * @brief return the true if the controller didn't need the awox api response template. + */ +bool RestController::is_raw_response (void) +{ + return mf_raw_response; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_method + * + * @brief return the right method in function of the client connection. + */ +std::string RestController::get_method (uint8_t a_method) +{ + switch (a_method) { + + case UH_HTTP_MSG_GET: + return m_method_get; + break; + case UH_HTTP_MSG_PUT: + case UH_HTTP_MSG_POST: + return m_method_put; + break; + default: + // TODO ERROR + break; + }; + + return ""; +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-controller.h b/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-controller.h new file mode 100644 index 00000000..5dd8f492 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/rest/rest-controller.h @@ -0,0 +1,57 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 19/06/2017 + */ + +#ifndef _REST_CONTROLLER_H +#define _REST_CONTROLLER_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include + +#include + +/*--------------------------------- Define ----------------------------------*/ + +#define kDefaultTimeout 20 +#define kPassed "passed" +#define kFailed "failed" + +/*----------------------------- Dependencies --------------------------------*/ + + +/*--------------------------------- CLASS ----------------------------------*/ + +class RestController : public WebController { + +public: + RestController (const std::string &a_path, const std::string &a_method_get = "", const std::string &a_method_put = "", int a_timeout = kDefaultTimeout, bool a_raw_response = false); + virtual ~RestController (void); + + WebConnection *new_connection (struct uhttpd_ops *an_ops, struct client *a_client, const std::string &a_parameters); + + std::string get_method_get (void); + std::string get_method_put (void); + uint16_t get_timeout (void); + bool is_raw_response (void); + + std::string get_method (uint8_t a_method); + +protected: + std::string m_name; + std::string m_path; + std::string m_method_get; + std::string m_method_put; + uint16_t m_timeout; + bool mf_raw_response; +}; + + +#endif /* _REST_CONTROLLER_H */ diff --git a/src/plugins/uhttpd/uhttpd-rest-api/uhttp-server.cpp b/src/plugins/uhttpd/uhttpd-rest-api/uhttp-server.cpp new file mode 100644 index 00000000..b8dc67db --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/uhttp-server.cpp @@ -0,0 +1,693 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 16/06/2017 + */ + + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +#include + +#include "core/web-connection.h" +#include "core/http-header.h" + +#include "rest/rest-connection.h" +#include "rest/rest-controller.h" + +#include "etag-rest/etag-rest-controller.h" + +#include "notification/notification-controller.h" + +#include "uhttp-server.h" + +#include +#include + + + +/*------------------------------- GLOBALS ----------------------------------*/ + +#define kConfigDirectory "/usr/local/configs/restd/" + +#define kControllerKey "controller" +#define kEtagControllerKey "etag_controller" + +#define kNotificationKey "notification" + +#define kControllerPathKey "path" +#define kControllerModelNameKey "model_name" +#define kControllerEventNameKey "event_name" +#define kControllerEtagKey "etag_key" +#define kControllerGetMethodKey "get_method" +#define kControllerSetMethodKey "set_method" +#define kControllerRawResponseKey "raw_response" + +#define kdefaultGetMethod "get" +#define kdefaultSetMethod "set" + + +#define kJsonControlerNotFound "{{\n" \ + " \"id\": \"{}\",\n" \ + " \"status\": \"failed\",\n" \ + " \"error\": \"Controller Not Found\",\n" \ + " \"response_code\": 404\n}}" + + +#define DEBUG_REQUEST 1 +#define BUFFER_SIZE 100 + +#ifdef DEBUG_REQUEST +/*! ---------------------------------------------------------------------------- + * @fn log_with_timestamp + * + * @brief method to log with time stamp. + */ +void log_with_timestamp (const char *a_msg) +{ + char the_buffer[512]; + time_t the_time; + struct timeval the_timeval; + + the_time = time(NULL); + strftime(the_buffer, sizeof(the_buffer), "%m/%d/%Y %H:%M:%S", localtime(&the_time)); + + gettimeofday(&the_timeval, NULL); + fprintf (stdout, "%s.%09ld - %s\n", the_buffer, the_timeval.tv_usec*1000, a_msg); +} + +double time_diff(struct timeval x , struct timeval y) +{ + double x_ms , y_ms , diff; + + x_ms = (double)x.tv_sec*1000000 + (double)x.tv_usec; + y_ms = (double)y.tv_sec*1000000 + (double)y.tv_usec; + + diff = (double)y_ms - (double)x_ms; + + return diff; +} +#endif + +/*! ---------------------------------------------------------------------------- + * @fn get_uhttp_server_ctx + * + * @brief method to return ubux context to c plugin. + */ +extern "C" struct ubus_context *get_uhttp_server_ctx (void) +{ + return UhttpServer::get_instance().get_context(); + +} + +/*! ---------------------------------------------------------------------------- + * @fn set_uhttp_server_ctx + * + * @brief init method from c plugin to uhttp server. + */ +extern "C" int uhttp_server_setup (struct ubus_context *a_ctx, const struct uhttpd_ops *an_ops) +{ + return UhttpServer::get_instance().setup(a_ctx, an_ops); +} + + +/*! ---------------------------------------------------------------------------- + * @fn uhttp_server_check_url + * + * @brief Check if the url is managed by this plugin + */ +extern "C" bool uhttp_server_check_url (const char *an_url) +{ + return UhttpServer::get_instance().check_url(an_url); +} + + +/*! ---------------------------------------------------------------------------- + * @fn uhttp_server_handle_request + * + * @brief Handle the request. + */ +extern "C" void uhttp_server_handle_request (struct client *a_cl, char *an_url, struct path_info *a_pi) +{ +#ifdef DEBUG_REQUEST + gettimeofday(&a_cl->start_request, NULL); + char buffer[BUFFER_SIZE]; + snprintf(buffer, sizeof(buffer), "uhttp handle_request : client id: %d - %s", a_cl->id, an_url); + log_with_timestamp(buffer); +#endif + UhttpServer::get_instance().handle_request(a_cl, an_url, a_pi); +} + + +/*! ---------------------------------------------------------------------------- + * @fn uhttp_server_data_send + * + * @brief callback called when data should . + */ +static int uhttp_server_data_send (struct client *a_cl, const char *a_data, int a_len) +{ +#ifdef DEBUG_REQUEST + log_with_timestamp("uhttp_server_data_send"); +#endif + return UhttpServer::get_instance().data_send(a_cl, a_data, a_len); +} + + +/*! ---------------------------------------------------------------------------- + * @fn uhttp_server_data_done + * + * @brief callback called when the server is ready to launch the request. + */ +static void uhttp_server_data_done (struct client *a_cl) +{ + UhttpServer::get_instance().invoke_request(a_cl); +} + + +/*! ---------------------------------------------------------------------------- + * @fn uhttp_server_request_free + * + * @brief callback call to free the request. + */ +static void uhttp_server_request_free (struct client *a_cl) +{ +#ifdef DEBUG_REQUEST + struct timeval the_current_timeval; + gettimeofday(&the_current_timeval, NULL); + char buffer [BUFFER_SIZE]; + snprintf (buffer, sizeof(buffer), "client id: %d duree: %.0lf ms", a_cl->id, time_diff(a_cl->start_request , the_current_timeval) / 1000); + log_with_timestamp(buffer); +#endif + UhttpServer::get_instance().request_free(a_cl); +} + + +/*! ---------------------------------------------------------------------------- + * @fn uhttp_server_close_fds + * + * @brief callback call when the process close. (needed ? to be dfefined). + */ +static void uhttp_server_close_fds (struct client *cl) +{ + // printf("uhttp_server_close_fds(%p)... client id: %d\n", cl, cl->id); +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_instance + * + * @brief singleton to return the instance of UhttpServer. + */ +UhttpServer &UhttpServer::get_instance (void) +{ + static UhttpServer m_ptr; + return m_ptr; +} + + +/*! ---------------------------------------------------------------------------- + * @fn UhttpServer + * + * @brief Constructor of the Uhttp Server + */ +UhttpServer::UhttpServer (void): + m_ctx(NULL), + m_ops(NULL) +{ +} + + +/*! ---------------------------------------------------------------------------- + * @fn ~UhttpServer + * + * @brief Destructor of the Uhttp Server + */ +UhttpServer::~UhttpServer (void) +{ + ControllerIterator it; + ConnectionIterator the_cnx_it; + for (it=m_controller_array.begin(); it!=m_controller_array.end(); ++it) { + + delete it->second; + } + + for (the_cnx_it=m_connections.begin(); the_cnx_it!=m_connections.end(); ++the_cnx_it) { + + delete the_cnx_it->second; + } + + ubus_free(m_ctx); +} + + +/*! ---------------------------------------------------------------------------- + * @fn setup + * + * @brief setup the http server. + */ +int UhttpServer::setup (struct ubus_context *a_ctx, const struct uhttpd_ops *an_ops) +{ + m_ctx = a_ctx; + m_ops = const_cast(an_ops); + + return load_config_dir(); +} + + + +/*! ---------------------------------------------------------------------------- + * @fn get_context + * + * @brief return the ubus context. + */ +struct ubus_context *UhttpServer::get_context (void) +{ + return m_ctx; +} + + +/*! ---------------------------------------------------------------------------- + * @fn check_url + * + * @brief check if the url is managed by this plugin. + */ +bool UhttpServer::check_url (const std::string &an_url) +{ + std::list::iterator the_it; + // printf("UhttpServer::check_url: %s\n", an_url.c_str()); + + for (the_it = m_path_list.begin(); the_it != m_path_list.end(); the_it++) { + + if (an_url.find(*the_it) != std::string::npos) + return true; + } + + return false; +} + + +/*! ---------------------------------------------------------------------------- + * @fn handle_request + * + * @brief Handle the Request. + */ +void UhttpServer::handle_request (struct client *a_cl, const std::string &an_url, struct path_info *a_pi) +{ + std::string the_url, the_parameters; + std::size_t the_pos; + WebController *the_controller; + // printf("uhttp_server_handle_request : url: <%s> client: %p\n", an_url.c_str(), a_cl); + + // Check if parameters are present on the url. + the_pos = an_url.find_first_of("?", 0); + if (the_pos != std::string::npos) { + // parameters are present. + the_url = an_url.substr(0, the_pos); + the_parameters = an_url.substr(the_pos + 1); + // printf("url: <%s> | parameters: <%s>\n", the_url.c_str(), the_parameters.c_str()); + } + else { + the_url = an_url; + } + + the_controller = m_controller_array [the_url]; + if (the_controller == NULL) { + + std::string the_msg; + the_msg = fmt::format(kJsonControlerNotFound, the_url); + send_error(a_cl, 404, "Not Found", the_msg); + return; + } + + // We found the controller. + // printf("method: %d\n", a_cl->request.method); + struct dispatch *d = &a_cl->dispatch; + switch (a_cl->request.method) + { + case UH_HTTP_MSG_GET: + case UH_HTTP_MSG_PUT: + case UH_HTTP_MSG_POST: + case UH_HTTP_MSG_DELETE: + d->data_send = uhttp_server_data_send; + d->data_done = uhttp_server_data_done; + d->close_fds = uhttp_server_close_fds; + d->free = uhttp_server_request_free; + m_connections[a_cl->id] = the_controller->new_connection(m_ops, a_cl, the_parameters); + break; + + case UH_HTTP_MSG_OPTIONS: + { + HttpHeader the_http_header; + the_http_header.send_option(m_ops, a_cl, 200); + } + break; + + default: + // printf("Invalid Request\n"); + send_error(a_cl, 400, "Bad Request", "Invalid Request"); + } + + // printf("uhttp_server_handle_request -done.\n"); +} + + +/*! ---------------------------------------------------------------------------- + * @fn invoke_request + * + * @brief This method to invoke the request. + */ +void UhttpServer::invoke_request (struct client *a_client) +{ + uh_client_ref(a_client); + // printf("UhttpServer::invoke_request\n"); + m_connections[a_client->id]->invoke(m_ctx); + + uh_client_unref(a_client); +} + + +/*! ---------------------------------------------------------------------------- + * @fn request_free + * + * @brief This method free the complete request. + */ +void UhttpServer::request_free (struct client *a_client) +{ + ConnectionIterator the_it; + // printf("UhttpServer::request_free id:%d nb connection: %ld\n", a_client->id, m_connections.size()); + uh_client_ref(a_client); + + the_it = m_connections.find(a_client->id); + if (the_it != m_connections.end()) { + + if ((the_it->second)->should_be_removed()) { + delete the_it->second; + m_connections.erase(the_it); + } + } + + uh_client_unref(a_client); + // printf("UhttpServer::request_free-done\n"); +} + + +/*! ---------------------------------------------------------------------------- + * @fn data_send + * + * @brief This method called when data arrived from the client. + */ +int UhttpServer::data_send (struct client *a_client, const char *a_data, int a_len) +{ + return m_connections[a_client->id]->add_data(a_data, a_len); +} + + +/*! ---------------------------------------------------------------------------- + * @fn load_config_dir + * + * @brief Load config from the configuration directory + */ +int UhttpServer::load_config_dir (void) +{ + DIR *the_rep = NULL; + struct dirent *the_dir_ent = NULL; + std::string the_path; + struct json_object *the_root_node; + the_rep = opendir(kConfigDirectory); + + while ((the_dir_ent = readdir(the_rep)) != NULL) { + + if (strcmp(the_dir_ent->d_name, ".") != 0 && strcmp(the_dir_ent->d_name, "..") != 0) { + + // printf ("load: %s\n", the_dir_ent->d_name); + the_path = kConfigDirectory; + the_path += the_dir_ent->d_name; + + the_root_node = load(the_path); + if (the_root_node == NULL) { + + fprintf(stderr, "Failed to load controller configuration file (%s).\n", the_path.c_str()); + return -1; + } + + add_controller_from_json(the_root_node); + json_object_put(the_root_node); + } + } + + closedir(the_rep); + + return 0; +} + + +/*! ---------------------------------------------------------------------------- + * @fn add_controller_from_json + * + * @brief Load config from the configuration directory + */ +int UhttpServer::add_controller_from_json (struct json_object *a_root_node) +{ + struct json_object *the_ctr_array_node, *the_ctrl_node, *the_note_array_node, *the_etag_ctrl_array_node; + + std::string the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method; + bool the_raw_response = false; + int the_len; + int i; + + if (json_object_object_get_ex(a_root_node, kControllerKey, &the_ctr_array_node)) { + + the_len = json_object_array_length(the_ctr_array_node); + + for (i = 0; i < the_len; i++) { + + the_ctrl_node = json_object_array_get_idx(the_ctr_array_node, i); + + if (get_controller_fields(the_ctrl_node, the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, the_raw_response)) { +#if 0 + printf("Path: <%s>\n", the_path.c_str()); + printf("\t- model name: <%s>\n", the_model_name.c_str()); + printf("\t- get: <%s>\n", the_get_method.c_str()); + printf("\t- set: <%s>\n", the_set_method.c_str()); + printf("\t- raw response: <%s>\n", the_raw_response?"true":"false"); +#endif + add_controller(the_path, new RestController(the_model_name, the_get_method, the_set_method, kDefaultTimeout, the_raw_response)); + } + } + } + if (json_object_object_get_ex(a_root_node, kEtagControllerKey, &the_etag_ctrl_array_node)) { + + the_len = json_object_array_length(the_etag_ctrl_array_node); + + for (i = 0; i < the_len; i++) { + + the_ctrl_node = json_object_array_get_idx(the_etag_ctrl_array_node, i); + + if (get_controller_fields(the_ctrl_node, the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, the_raw_response)) { + + EtagRestController *the_etag_rest_controller; + the_etag_rest_controller = new EtagRestController(the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, kDefaultTimeout, the_raw_response); + + add_controller(the_path, the_etag_rest_controller); + } + } + } + if (json_object_object_get_ex(a_root_node, kNotificationKey, &the_note_array_node)) { + + the_len = json_object_array_length(the_note_array_node); + + for (i = 0; i < the_len; i++) { + + the_ctrl_node = json_object_array_get_idx(the_note_array_node, i); + + if (get_controller_fields(the_ctrl_node, the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, the_raw_response)) { + + NotificationController *the_notification_controller; + the_notification_controller = new NotificationController(the_event_name); + add_controller(the_path, the_notification_controller); + } + } + } + + return 0; +} + + +/*! ---------------------------------------------------------------------------- + * @fn get_controller_fields + * + * @brief This method extract the controller fields from a json node. + */ +bool UhttpServer::get_controller_fields (struct json_object *a_node, std::string &a_path, std::string &a_model_name, + std::string &an_event_name, std::string &an_etag_key, + std::string &a_get_method, std::string &a_set_method, bool &a_raw_response) +{ + bool the_result = false; + struct json_object *the_path_node, *the_ubus_model_node, *the_ubus_event_node, *the_get_method_node, *the_etag_key_node; + struct json_object *the_set_method_node, *the_raw_response_node; + + // path + if (json_object_object_get_ex(a_node, kControllerPathKey, &the_path_node)) { + a_path = json_object_get_string(the_path_node); + the_result = true; + } + else { + a_path.clear(); + } + // model_name + if (json_object_object_get_ex(a_node, kControllerModelNameKey, &the_ubus_model_node)) { + a_model_name = json_object_get_string(the_ubus_model_node); + } + else { + a_model_name.clear(); + } + // event name + if (json_object_object_get_ex(a_node, kControllerEventNameKey, &the_ubus_event_node)) { + an_event_name = json_object_get_string(the_ubus_event_node); + } + else { + an_event_name.clear(); + } + // etag key + if (json_object_object_get_ex(a_node, kControllerEtagKey, &the_etag_key_node)) { + an_etag_key = json_object_get_string(the_etag_key_node); + } + else { + an_etag_key.clear(); + } + // get_method + if (json_object_object_get_ex(a_node, kControllerGetMethodKey, &the_get_method_node)) { + a_get_method = json_object_get_string(the_get_method_node); + } + else { + a_get_method = kdefaultGetMethod; + } + // set_method + if (json_object_object_get_ex(a_node, kControllerSetMethodKey, &the_set_method_node)) { + a_set_method = json_object_get_string(the_set_method_node); + } + else { + a_set_method = kdefaultSetMethod; + } + // raw_response + if (json_object_object_get_ex(a_node, kControllerRawResponseKey, &the_raw_response_node)) { + a_raw_response = json_object_get_boolean(the_raw_response_node); + } + else { + a_raw_response = false; + } + + return the_result; +} + + +/*! ---------------------------------------------------------------------------- + * @fn load + * + * @brief This method load the restd controllers and file it into a json object. + * return null otherwise. + */ +struct json_object *UhttpServer::load (const std::string &a_file) +{ + struct json_object *the_root_node = NULL; + struct stat the_stat; + int the_fd; + char *the_addr; + + the_fd = open(a_file.c_str(), O_RDONLY); + if (the_fd == -1) { + + perror("open"); + return NULL; + } + if (fstat(the_fd, &the_stat) == -1) { + + perror("fstat"); + close(the_fd); + return NULL; + } + + the_addr = (char *) mmap(NULL, the_stat.st_size, PROT_READ, MAP_PRIVATE, the_fd, 0); + if (the_addr == MAP_FAILED) { + + perror("mmap"); + close(the_fd); + return NULL; + } + + the_root_node = json_tokener_parse(the_addr); + + close(the_fd); + + return the_root_node; +} + + +/*! ---------------------------------------------------------------------------- + * @fn add_rest_controller + * + * @brief add a rest controller. + */ +int UhttpServer::add_controller (const std::string &an_uri, WebController *a_controller) +{ + std::string the_path; + std::size_t the_pos; + if (a_controller == NULL) + return -1; + + a_controller->set_name(an_uri); + m_controller_array[an_uri] = a_controller; + + // printf("add: an_uri:%s\n", an_uri.c_str()); + + // Keep a list of the API root. to check if a controller is managed by this plugin or not. + the_pos = an_uri.find_first_of("/", 1); + if (the_pos != std::string::npos) { + + the_path = an_uri.substr(0, the_pos + 1); + if (std::find(m_path_list.begin(), m_path_list.end(), the_path) == m_path_list.end()) { + // uniq version. we add it. + m_path_list.push_back(the_path); + } + } + + return 0; +} + + +/*! ---------------------------------------------------------------------------- + * @fn send_error + * + * @brief Send an error message to the client. + */ +void UhttpServer::send_error (struct client *a_cl, int a_code, const char *a_summary, const std::string &a_msg) +{ + m_ops->http_header(a_cl, a_code, a_summary); + ustream_printf(a_cl->us, "Content-Type: application/json\r\n\r\n"); + + m_ops->chunk_printf(a_cl, "%s", a_msg.c_str()); + + m_ops->request_done(a_cl); +} diff --git a/src/plugins/uhttpd/uhttpd-rest-api/uhttp-server.h b/src/plugins/uhttpd/uhttpd-rest-api/uhttp-server.h new file mode 100644 index 00000000..310fcfa3 --- /dev/null +++ b/src/plugins/uhttpd/uhttpd-rest-api/uhttp-server.h @@ -0,0 +1,77 @@ +/*! + * (C) Copyright 2003-2017 Awox SA. All rights reserved. + * This work contains confidential trade secrets of Awox. + * Use, examination, copying, transfer and disclosure to others + * are prohibited, except with the express written agreement of Awox. + * + * @Author: Awox + * @Date: 16/06/2017 + */ + +#ifndef _UHTTP_SERVER_H +#define _UHTTP_SERVER_H + +/*------------------------------- INCLUDES ----------------------------------*/ + +#include +#include +#include + +/*--------------------------------- Define ----------------------------------*/ + + +/*----------------------------- Dependencies --------------------------------*/ + +struct ubus_context; +struct uhttpd_ops; +struct json_object; + +class WebController; +class WebConnection; + +/*--------------------------------- CLASS ----------------------------------*/ + +class UhttpServer { + +public: + typedef std::unordered_map ControllerContainer; + typedef ControllerContainer::iterator ControllerIterator; + typedef std::unordered_map ConnectionContainer; + typedef ConnectionContainer::iterator ConnectionIterator; + +public: + UhttpServer (void); + ~UhttpServer (void); + + static UhttpServer &get_instance (void); + + int setup (struct ubus_context *a_ctx, const struct uhttpd_ops *an_ops); + + struct ubus_context *get_context (void); + + bool check_url (const std::string &an_url); + void handle_request (struct client *a_cl, const std::string &an_url, struct path_info *a_pi); + void invoke_request (struct client *a_client); + void request_free (struct client *a_client); + + int data_send (struct client *a_client, const char *a_data, int a_len); + +private: + int load_config_dir (void); + int add_controller_from_json (struct json_object *a_root_node); + bool get_controller_fields (struct json_object *a_node, std::string &a_path, std::string &a_model_name, + std::string &an_event_name, std::string &an_etag_key, + std::string &a_get_method, std::string &a_set_method, bool &a_raw_response); + struct json_object *load (const std::string &a_file); + int add_controller (const std::string &an_uri, WebController *a_controller); + void send_error (struct client *a_cl, int a_code, const char *a_summary, const std::string &a_msg); + +private: + struct ubus_context *m_ctx; + struct uhttpd_ops *m_ops; + ControllerContainer m_controller_array; + ConnectionContainer m_connections; + std::list m_path_list; +}; + +#endif /* _UHTTP_SERVER_H */