1 #include <algorithm>
2 #include <boost/algorithm/string/replace.hpp>
3 #include <boost/bind.hpp>
4 #include <deque>
5 #include <iostream>
6 
7 #if !defined(_WINDOWS)
8 #include <sys/time.h>
9 #endif
10 
11 #include "asserts.hpp"
12 #include "base64.hpp"
13 #include "filesystem.hpp"
14 #include "foreach.hpp"
15 #include "formatter.hpp"
16 #include "json_parser.hpp"
17 #include "md5.hpp"
18 #include "module_web_server.hpp"
19 #include "string_utils.hpp"
20 #include "utils.hpp"
21 #include "unit_test.hpp"
22 #include "variant.hpp"
23 
24 using boost::asio::ip::tcp;
25 
module_web_server(const std::string & data_path,boost::asio::io_service & io_service,int port)26 module_web_server::module_web_server(const std::string& data_path, boost::asio::io_service& io_service, int port)
27 	: http::web_server(io_service, port), timer_(io_service),
28 	nheartbeat_(0), data_path_(data_path)
29 {
30 	if(data_path_.empty() || data_path_[data_path_.size()-1] != '/') {
31 		data_path_ += "/";
32 	}
33 
34 	if(sys::file_exists(data_file_path())) {
35 		data_ = json::parse_from_file(data_file_path());
36 	} else {
37 		std::map<variant, variant> m;
38 		data_ = variant(&m);
39 	}
40 
41 	heartbeat();
42 }
43 
heartbeat()44 void module_web_server::heartbeat()
45 {
46 	timer_.expires_from_now(boost::posix_time::seconds(1));
47 	timer_.async_wait(boost::bind(&module_web_server::heartbeat, this));
48 }
49 
handle_post(socket_ptr socket,variant doc,const http::environment & env)50 void module_web_server::handle_post(socket_ptr socket, variant doc, const http::environment& env)
51 {
52 	std::map<variant,variant> response;
53 	try {
54 		const std::string msg_type = doc["type"].as_string();
55 		if(msg_type == "upload_module") {
56 			variant module_node = doc["module"];
57 			const std::string module_id = module_node["id"].as_string();
58 			ASSERT_LOG(std::count_if(module_id.begin(), module_id.end(), isalnum) + std::count(module_id.begin(), module_id.end(), '_') == module_id.size(), "ILLEGAL MODULE ID");
59 
60 			std::vector<variant> prev_versions;
61 
62 			variant current_data = data_[variant(module_id)];
63 			if(current_data.is_null() == false) {
64 				const variant new_version = module_node[variant("version")];
65 				const variant old_version = current_data[variant("version")];
66 				ASSERT_LOG(new_version > old_version, "VERSION " << new_version.write_json() << " IS NOT NEWER THAN EXISTING VERSION " << old_version.write_json());
67 				prev_versions = current_data[variant("previous_versions")].as_list();
68 				current_data.remove_attr_mutation(variant("previous_versions"));
69 				prev_versions.push_back(current_data);
70 			}
71 
72 			const std::string module_path = data_path_ + module_id + ".cfg";
73 			const std::string module_path_tmp = module_path + ".tmp";
74 			const std::string contents = module_node.write_json();
75 
76 			sys::write_file(module_path_tmp, contents);
77 			const int rename_result = rename(module_path_tmp.c_str(), module_path.c_str());
78 			ASSERT_LOG(rename_result == 0, "FAILED TO RENAME FILE: " << errno);
79 
80 			response[variant("status")] = variant("ok");
81 
82 			{
83 				std::map<variant, variant> summary;
84 				summary[variant("previous_versions")] = variant(&prev_versions);
85 				summary[variant("version")] = module_node[variant("version")];
86 				summary[variant("name")] = module_node[variant("name")];
87 				summary[variant("description")] = module_node[variant("description")];
88 				summary[variant("author")] = module_node[variant("author")];
89 				summary[variant("dependencies")] = module_node[variant("dependencies")];
90 				summary[variant("num_downloads")] = variant(0);
91 				summary[variant("num_ratings")] = variant(0);
92 				summary[variant("sum_ratings")] = variant(0);
93 
94 				std::vector<variant> reviews_list;
95 				summary[variant("reviews")] = variant(&reviews_list);
96 
97 				if(module_node.has_key("icon")) {
98 					std::string icon_str = base64::b64decode(module_node["icon"].as_string());
99 
100 					const std::string hash = md5::sum(icon_str);
101 					sys::write_file(data_path_ + "/.glob/" + hash, icon_str);
102 
103 					summary[variant("icon")] = variant(hash);
104 				}
105 
106 				data_.add_attr_mutation(variant(module_id), variant(&summary));
107 				write_data();
108 			}
109 
110 		} else if(msg_type == "query_globs") {
111 			response[variant("status")] = variant("ok");
112 			foreach(const std::string& k, doc["keys"].as_list_string()) {
113 				const std::string data = sys::read_file(data_path_ + "/.glob/" + k);
114 				response[variant(k)] = variant(base64::b64encode(data));
115 			}
116 		} else if(msg_type == "rate") {
117 			const std::string module_id = doc["module_id"].as_string();
118 			variant summary = data_[module_id];
119 			ASSERT_LOG(summary.is_map(), "UNKNOWN MODULE ID: " << module_id);
120 
121 			const int rating = doc["rating"].as_int();
122 			ASSERT_LOG(rating >= 1 && rating <= 5, "ILLEGAL RATING");
123 			summary.add_attr_mutation(variant("num_ratings"), variant(summary["num_ratings"].as_int() + 1));
124 			summary.add_attr_mutation(variant("sum_ratings"), variant(summary["sum_ratings"].as_int() + rating));
125 
126 			if(doc["review"].is_null() == false) {
127 				std::vector<variant> v = summary["reviews"].as_list();
128 				v.push_back(doc);
129 				summary.add_attr_mutation(variant("reviews"), variant(&v));
130 			}
131 
132 			response[variant("status")] = variant("ok");
133 		} else {
134 			ASSERT_LOG(false, "Unknown message type");
135 		}
136 	} catch(validation_failure_exception& e) {
137 		response[variant("status")] = variant("error");
138 		response[variant("message")] = variant(e.msg);
139 	}
140 
141 	send_msg(socket, "text/json", variant(&response).write_json(), "");
142 }
143 
handle_get(socket_ptr socket,const std::string & url,const std::map<std::string,std::string> & args)144 void module_web_server::handle_get(socket_ptr socket, const std::string& url, const std::map<std::string, std::string>& args)
145 {
146 	std::map<variant,variant> response;
147 	try {
148 		std::cerr << "URL: (" << url << ")\n";
149 		response[variant("status")] = variant("error");
150 		if(url == "/download_module" && args.count("module_id")) {
151 			const std::string module_id = args.find("module_id")->second;
152 			const std::string module_path = data_path_ + module_id + ".cfg";
153 			if(sys::file_exists(module_path)) {
154 				std::string response = "{\nstatus: \"ok\",\nmodule: ";
155 				{
156 					const std::string contents = sys::read_file(module_path);
157 					response += contents;
158 				}
159 
160 				response += "\n}";
161 				send_msg(socket, "text/json", response, "");
162 
163 				variant summary = data_[module_id];
164 				if(summary.is_map()) {
165 					summary.add_attr_mutation(variant("num_downloads"), variant(summary["num_downloads"].as_int() + 1));
166 				}
167 				return;
168 
169 			} else {
170 				response[variant("message")] = variant("No such module");
171 			}
172 		} else if(url == "/get_summary") {
173 			response[variant("status")] = variant("ok");
174 			response[variant("summary")] = data_;
175 		} else {
176 			response[variant("message")] = variant("Unknown path");
177 		}
178 	} catch(validation_failure_exception& e) {
179 		response[variant("status")] = variant("error");
180 		response[variant("message")] = variant(e.msg);
181 	}
182 
183 	send_msg(socket, "text/json", variant(&response).write_json(), "");
184 }
185 
data_file_path() const186 std::string module_web_server::data_file_path() const
187 {
188 	return data_path_ + "/module-data.json";
189 }
190 
write_data()191 void module_web_server::write_data()
192 {
193 	const std::string tmp_path = data_file_path() + ".tmp";
194 	sys::write_file(tmp_path, data_.write_json());
195 	const int rename_result = rename(tmp_path.c_str(), data_file_path().c_str());
196 		ASSERT_LOG(rename_result == 0, "FAILED TO RENAME FILE: " << errno);
197 }
198 
COMMAND_LINE_UTILITY(module_server)199 COMMAND_LINE_UTILITY(module_server)
200 {
201 	std::string path = ".";
202 	int port = 23456;
203 
204 	std::deque<std::string> arguments(args.begin(), args.end());
205 	while(!arguments.empty()) {
206 		const std::string arg = arguments.front();
207 		arguments.pop_front();
208 		if(arg == "--path") {
209 			ASSERT_LOG(arguments.empty() == false, "NEED ARGUMENT AFTER " << arg);
210 			path = arguments.front();
211 			arguments.pop_front();
212 		} else if(arg == "-p" || arg == "--port") {
213 			ASSERT_LOG(arguments.empty() == false, "NEED ARGUMENT AFTER " << arg);
214 			port = atoi(arguments.front().c_str());
215 			arguments.pop_front();
216 		} else {
217 			ASSERT_LOG(false, "UNRECOGNIZED ARGUMENT: " << arg);
218 		}
219 	}
220 
221 	const assert_recover_scope recovery;
222 	boost::asio::io_service io_service;
223 	module_web_server server(path, io_service, port);
224 	io_service.run();
225 }
226