1// This file is part of SmallBASIC
2//
3// Copyright(C) 2001-2020 Chris Warren-Smith.
4//
5// This program is distributed under the terms of the GPL v2.0 or later
6// Download the GNU Public License (GPL) from www.gnu.org
7//
8
9#include "config.h"
10
11#if defined(_Win32)
12#include <windows.h>
13#include <shellapi.h>
14#else
15#include <sys/socket.h>
16#include <unistd.h>
17#include <errno.h>
18#endif
19#include <stdint.h>
20#include "lib/str.h"
21#include "platform/fltk/utils.h"
22#include "ui/utils.h"
23
24#define RX_BUFFER_SIZE 1024
25#if defined(_Win32)
26static char appName[OS_PATHNAME_SIZE + 1];
27#endif
28
29Fl_Color get_color(int argb) {
30 // Fl_Color => 0xrrggbbii
31 return (argb << 8) & 0xffffff00;
32}
33
34Fl_Color get_color(const char *name, Fl_Color def) {
35 Fl_Color result = def;
36 if (!name || name[0] == '\0') {
37 result = def;
38 } else if (name[0] == '#') {
39 // do hex color lookup
40 int rgb = strtol(name + 1, NULL, 16);
41 if (!rgb) {
42 result = FL_BLACK;
43 } else {
44 uchar r = rgb >> 16;
45 uchar g = (rgb >> 8) & 255;
46 uchar b = rgb & 255;
47 result = fl_rgb_color(r, g, b);
48 }
49 } else if (strcasecmp(name, "black") == 0) {
50 result = FL_BLACK;
51 } else if (strcasecmp(name, "red") == 0) {
52 result = FL_RED;
53 } else if (strcasecmp(name, "green") == 0) {
54 result = fl_rgb_color(0, 0x80, 0);
55 } else if (strcasecmp(name, "yellow") == 0) {
56 result = FL_YELLOW;
57 } else if (strcasecmp(name, "blue") == 0) {
58 result = FL_BLUE;
59 } else if (strcasecmp(name, "magenta") == 0 ||
60 strcasecmp(name, "fuchsia") == 0) {
61 result = FL_MAGENTA;
62 } else if (strcasecmp(name, "cyan") == 0 ||
63 strcasecmp(name, "aqua") == 0) {
64 result = FL_CYAN;
65 } else if (strcasecmp(name, "white") == 0) {
66 result = FL_WHITE;
67 } else if (strcasecmp(name, "gray") == 0 ||
68 strcasecmp(name, "grey") == 0) {
69 result = fl_rgb_color(0x80, 0x80, 0x80);
70 } else if (strcasecmp(name, "lime") == 0) {
71 result = FL_GREEN;
72 } else if (strcasecmp(name, "maroon") == 0) {
73 result = fl_rgb_color(0x80, 0, 0);
74 } else if (strcasecmp(name, "navy") == 0) {
75 result = fl_rgb_color(0, 0, 0x80);
76 } else if (strcasecmp(name, "olive") == 0) {
77 result = fl_rgb_color(0x80, 0x80, 0);
78 } else if (strcasecmp(name, "purple") == 0) {
79 result = fl_rgb_color(0x80, 0, 0x80);
80 } else if (strcasecmp(name, "silver") == 0) {
81 result = fl_rgb_color(0xc0, 0xc0, 0xc0);
82 } else if (strcasecmp(name, "teal") == 0) {
83 result = fl_rgb_color(0, 0x80, 0x80);
84 }
85 return result;
86}
87
88Fl_Font get_font(const char *name) {
89 Fl_Font result = FL_COURIER;
90 if (strcasecmp(name, "helvetica") == 0) {
91 result = FL_HELVETICA;
92 } else if (strcasecmp(name, "times") == 0) {
93 result = FL_TIMES;
94 }
95 return result;
96}
97
98void getHomeDir(char *fileName, size_t size, bool appendSlash) {
99 const int homeIndex = 1;
100 static const char *envVars[] = {
101 "APPDATA", "HOME", "TMP", "TEMP", "TMPDIR", ""
102 };
103
104 fileName[0] = '\0';
105
106 for (int i = 0; envVars[i][0] != '\0' && fileName[0] == '\0'; i++) {
107 const char *home = getenv(envVars[i]);
108 if (home && access(home, R_OK) == 0) {
109 strlcpy(fileName, home, size);
110 if (i == homeIndex) {
111 strlcat(fileName, "/.config", size);
112 makedir(fileName);
113 }
114 strlcat(fileName, "/SmallBASIC", size);
115 if (appendSlash) {
116 strlcat(fileName, "/", size);
117 }
118 makedir(fileName);
119 break;
120 }
121 }
122}
123
124// copy the url into the local cache
125bool cacheLink(dev_file_t *df, char *localFile, size_t size) {
126 char rxbuff[RX_BUFFER_SIZE];
127 FILE *fp;
128 const char *url = df->name;
129 const char *pathBegin = strchr(url + 7, '/');
130 const char *pathEnd = strrchr(url + 7, '/');
131 const char *pathNext;
132 bool inHeader = true;
133 bool httpOK = false;
134
135 getHomeDir(localFile, size, true);
136 strlcat(localFile, "cache/", size);
137 makedir(localFile);
138
139 // create host name component
140 strncat(localFile, url + 7, pathBegin - url - 7);
141 strlcat(localFile, "/", size);
142 makedir(localFile);
143
144 if (pathBegin != 0 && pathBegin < pathEnd) {
145 // re-create the server path in cache
146 int level = 0;
147 pathBegin++;
148 do {
149 pathNext = strchr(pathBegin, '/');
150 strncat(localFile, pathBegin, pathNext - pathBegin + 1);
151 makedir(localFile);
152 pathBegin = pathNext + 1;
153 }
154 while (pathBegin < pathEnd && ++level < 20);
155 }
156 if (pathEnd == 0 || pathEnd[1] == 0 || pathEnd[1] == '?') {
157 strlcat(localFile, "index.html", size);
158 } else {
159 strlcat(localFile, pathEnd + 1, size);
160 }
161
162 fp = fopen(localFile, "wb");
163 if (fp == 0) {
164 if (df->handle != -1) {
165 shutdown(df->handle, df->handle);
166 }
167 return false;
168 }
169
170 if (df->handle == -1) {
171 // pass the cache file modified time to the HTTP server
172 struct stat st;
173 if (stat(localFile, &st) == 0) {
174 df->drv_dw[2] = st.st_mtime;
175 }
176 if (http_open(df) == 0) {
177 fclose(fp);
178 return false;
179 }
180 }
181
182 while (true) {
183 int bytes = recv(df->handle, (char *)rxbuff, sizeof(rxbuff), 0);
184 if (bytes == 0) {
185 break; // no more data
186 }
187 // assumes http header < 1024 bytes
188 if (inHeader) {
189 int i = 0;
190 while (true) {
191 int iattr = i;
192 while (rxbuff[i] != 0 && rxbuff[i] != '\n') {
193 i++;
194 }
195 if (rxbuff[i] == 0) {
196 inHeader = false;
197 break; // no end delimiter
198 }
199 if (rxbuff[i + 2] == '\n') {
200 if (!fwrite(rxbuff + i + 3, bytes - i - 3, 1, fp)) {
201 break;
202 }
203 inHeader = false;
204 break; // found start of content
205 }
206 // null terminate attribute (in \r)
207 rxbuff[i - 1] = 0;
208 i++;
209 if (strstr(rxbuff + iattr, "200 OK") != 0) {
210 httpOK = true;
211 }
212 if (strncmp(rxbuff + iattr, "Location: ", 10) == 0) {
213 // handle redirection
214 shutdown(df->handle, df->handle);
215 strcpy(df->name, rxbuff + iattr + 10);
216 if (http_open(df) == 0) {
217 fclose(fp);
218 return false;
219 }
220 break; // scan next header
221 }
222 }
223 } else if (!fwrite(rxbuff, bytes, 1, fp)) {
224 break;
225 }
226 }
227
228 // cleanup
229 fclose(fp);
230 shutdown(df->handle, df->handle);
231 return httpOK;
232}
233
234void vsncat(char *buffer, size_t size, ...) {
235 va_list args;
236 va_start(args, size);
237 strlcpy(buffer, va_arg(args, char *), size);
238 for (char *next = va_arg(args, char *);
239 next != NULL;
240 next = va_arg(args, char *)) {
241 strlcat(buffer, next, size);
242 }
243 va_end(args);
244}
245
246void setAppName(const char *path) {
247#if defined(_Win32)
248 appName[0] = '\0';
249 if (path[0] == '/' ||
250 (path[1] == ':' && ((path[2] == '\\') || path[2] == '/'))) {
251 // full path or C:/
252 strlcpy(appName, path, sizeof(appName));
253 } else {
254 // relative path
255 char cwd[OS_PATHNAME_SIZE + 1];
256 cwd[0] = '\0';
257 getcwd(cwd, sizeof(cwd) - 1);
258 strlcpy(appName, cwd, sizeof(appName));
259 strlcat(appName, "/", sizeof(appName));
260 strlcat(appName, path, sizeof(appName));
261 }
262 const auto file = "sbasici.exe";
263 char *exe = strstr(appName, file);
264 if (exe) {
265 strcpy(exe, "sbasicg.exe");
266 }
267#endif
268}
269
270#if defined(_Win32)
271void launchExec(const char *file) {
272 STARTUPINFO info = {sizeof(info)};
273 PROCESS_INFORMATION processInfo;
274 char cmd[MAX_PATH];
275 sprintf(cmd, "\"%s\" -x \"%s\"", appName, file);
276 appLog(cmd);
277 if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &info, &processInfo)) {
278 appLog("failed to start %d %s %s\n", GetLastError(), appName, cmd);
279 }
280}
281#else
282void launchExec(const char *file) {
283 pid_t pid = fork();
284 auto app = "/usr/bin/sbasicg";
285
286 switch (pid) {
287 case -1:
288 // failed
289 break;
290 case 0:
291 // child process
292 if (execl(app, app, "-x", file, (char *)0) == -1) {
293 fprintf(stderr, "exec failed [%s] %s\n", strerror(errno), app);
294 exit(1);
295 }
296 break;
297 default:
298 // parent process - continue
299 break;
300 }
301}
302#endif
303