1 /******************************************************************************
2 * qDecoder - http://www.qdecoder.org
3 *
4 * Copyright (c) 2000-2012 Seungyoung Kim.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 ******************************************************************************
28 * $Id: qcgires.c 654 2012-12-02 22:23:00Z seungyoung.kim $
29 ******************************************************************************/
30
31 /**
32 * @file qcgires.c CGI Response API
33 */
34
35 #ifdef ENABLE_FASTCGI
36 #include "fcgi_stdio.h"
37 #else
38 #include <stdio.h>
39 #endif
40 #include <stdlib.h>
41 #include <stdbool.h>
42 #include <stdarg.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <time.h>
46 #include <fcntl.h>
47 #include "qdecoder.h"
48 #include "internal.h"
49
50 /**
51 * Set cookie
52 *
53 * @param request a pointer of request structure
54 * @param name cookie name
55 * @param value cookie value
56 * @param expire expire related time in seconds (0 means end of session)
57 * @param path cookie path (NULL can current path)
58 * @param domain cookie domain (NULL means current domain)
59 * @param secure secure flag
60 *
61 * @return true in case of success, otherwise returns false
62 *
63 * @code
64 * // Apply cookie in the current domain and directory for 1 day.
65 * qcgires_setcookie(req, "NAME", "VALUE", 86400, NULL, NULL, false);
66 *
67 * // Apply cookie to the "/" directory of "*.qdecoder.org" until the
68 * // browser is closed.
69 * qcgires_setcookie(req, name, value, 0, "/", ".qdecoder.org", false);
70 *
71 * // As for the followings, cookies will be set up only when security
72 * // requirements are satisfied.
73 * qcgires_setcookie(req, name, value, 0, NULL, NULL, true);
74 * @endcode
75 */
qcgires_setcookie(qentry_t * request,const char * name,const char * value,int expire,const char * path,const char * domain,bool secure)76 bool qcgires_setcookie(qentry_t *request, const char *name, const char *value,
77 int expire, const char *path, const char *domain, bool secure)
78 {
79 if (qcgires_getcontenttype(request) != NULL) {
80 DEBUG("Should be called before qcgires_setcontenttype().");
81 return false;
82 }
83
84 char *encname = _q_urlencode(name, strlen(name));
85 char *encvalue = _q_urlencode(value, strlen(value));
86 char cookie[(4 * 1024) + 256];
87 snprintf(cookie, sizeof(cookie), "%s=%s", encname, encvalue);
88 free(encname), free(encvalue);
89
90 if (expire != 0) {
91 char gmtstr[sizeof(char) * (CONST_STRLEN("Mon, 00 Jan 0000 00:00:00 GMT") + 1)];
92 time_t utctime = time(NULL) + expire;
93 struct tm *gmtm = gmtime(&utctime);
94 strftime(gmtstr, sizeof(gmtstr), "%a, %d %b %Y %H:%M:%S GMT", gmtm);
95
96 strcat(cookie, "; expires=");
97 strcat(cookie, gmtstr);
98 }
99
100 if (path != NULL) {
101 if (path[0] != '/') {
102 DEBUG("Path string(%s) must start with '/' character.", path);
103 return false;
104 }
105 strcat(cookie, "; path=");
106 strcat(cookie, path);
107 }
108
109 if (domain != NULL) {
110 if (strstr(domain, "/") != NULL || strstr(domain, ".") == NULL) {
111 DEBUG("Invalid domain name(%s).", domain);
112 return false;
113 }
114 strcat(cookie, "; domain=");
115 strcat(cookie, domain);
116 }
117
118 if (secure == true) {
119 strcat(cookie, "; secure");
120 }
121
122 printf("Set-Cookie: %s" CRLF, cookie);
123
124 return true;
125 }
126
127 /**
128 * Remove cookie
129 *
130 * @param request a pointer of request structure
131 * @param name cookie name
132 * @param path cookie path
133 * @param domain cookie domain
134 * @param secure secure flag
135 *
136 * @return true in case of success, otherwise returns false
137 *
138 * @code
139 * qcgires_setcookie(req, "NAME", "VALUE", 0, NULL, NULL, NULL);
140 * qcgires_removecookie(req, "NAME", NULL, NULL, NULL);
141 *
142 * qcgires_setcookie(req, "NAME", "VALUE", 0, "/", "www.qdecoder.org", NULL);
143 * qcgires_removecookie(req, "NAME", "/", "www.qdecoder.org", NULL);
144 * @endcode
145 */
qcgires_removecookie(qentry_t * request,const char * name,const char * path,const char * domain,bool secure)146 bool qcgires_removecookie(qentry_t *request, const char *name, const char *path,
147 const char *domain, bool secure)
148 {
149 return qcgires_setcookie(request, name, "", -1, path, domain, secure);
150 }
151
152 /**
153 * Set responding content-type
154 *
155 * @param request a pointer of request structure
156 * @param mimetype mimetype
157 *
158 * @return true in case of success, otherwise returns false
159 *
160 * @code
161 * qcgires_setcontenttype(req, "text/html");
162 * @endcode
163 */
qcgires_setcontenttype(qentry_t * request,const char * mimetype)164 bool qcgires_setcontenttype(qentry_t *request, const char *mimetype)
165 {
166 if (request != NULL &&
167 request->getstr(request, "_Q_CONTENTTYPE", false) != NULL) {
168 DEBUG("alreay set.");
169 return false;
170 }
171
172 printf("Content-Type: %s" CRLF CRLF, mimetype);
173
174 if (request != NULL) {
175 request->putstr(request, "_Q_CONTENTTYPE", mimetype, true);
176 }
177 return true;
178 }
179
180 /**
181 * Get content-type
182 *
183 * @param request a pointer of request structure
184 *
185 * @return a pointer of mimetype string in case of success,
186 * otherwise returns NULL
187 *
188 * @code
189 * qcgires_setcontenttype(req, "text/html");
190 * @endcode
191 */
qcgires_getcontenttype(qentry_t * request)192 const char *qcgires_getcontenttype(qentry_t *request)
193 {
194 if (request == NULL) return NULL;
195 return request->getstr(request, "_Q_CONTENTTYPE", false);
196 }
197
198 /**
199 * Send redirection header
200 *
201 * @param request a pointer of request structure
202 * @param uri new URI
203 *
204 * @return true in case of success, otherwise returns false
205 *
206 * @code
207 * qcgires_redirect(req, "http://www.qdecoder.org/");
208 * @endcode
209 */
qcgires_redirect(qentry_t * request,const char * uri)210 bool qcgires_redirect(qentry_t *request, const char *uri)
211 {
212 if (qcgires_getcontenttype(request) != NULL) {
213 DEBUG("Should be called before qcgires_setcontenttype().");
214 return false;
215 }
216
217 printf("Location: %s" CRLF CRLF, uri);
218 return true;
219 }
220
221 /**
222 * Force to send(download) file to client in accordance with given mime type.
223 *
224 * @param request a pointer of request structure
225 * @param filepath file to send
226 * @param mimetype mimetype. NULL can be used for "application/octet-stream".
227 *
228 * @return the number of bytes sent. otherwise(file not found) returns -1.
229 *
230 * @note
231 * Do not call qcgires_getcontenttype() before.
232 * The results of this function are the same as those acquired
233 * when the corresponding files are directly linked to the Web.
234 * But this is especially useful in preprocessing files to be downloaded
235 * only with user certification and in enabling downloading those files,
236 * which cannot be opend on the Web, only through specific programs.
237 */
qcgires_download(qentry_t * request,const char * filepath,const char * mimetype)238 int qcgires_download(qentry_t *request, const char *filepath,
239 const char *mimetype)
240 {
241 if (qcgires_getcontenttype(request) != NULL) {
242 DEBUG("Should be called before qcgires_setcontenttype().");
243 return -1;
244 }
245
246 FILE *fp;
247 if (filepath == NULL || (fp = fopen(filepath, "r")) == NULL) {
248 DEBUG("Can't open file.");
249 return -1;
250 }
251
252 const char *mime;
253 if (mimetype == NULL) mime = "application/octet-stream";
254 else mime = mimetype;
255
256 char *disposition;
257 if (!strcmp(mime, "application/octet-stream")) disposition = "attachment";
258 else disposition = "inline";
259
260 char *filename = _q_filename(filepath);
261 off_t filesize = _q_filesize(filepath);
262
263 printf("Content-Disposition: %s;filename=\"%s\"" CRLF, disposition, filename);
264 printf("Content-Transfer-Encoding: binary" CRLF);
265 printf("Accept-Ranges: bytes" CRLF);
266 printf("Content-Length: %lu" CRLF, (unsigned long)filesize);
267 printf("Connection: close" CRLF);
268 qcgires_setcontenttype(request, mime);
269
270 free(filename);
271
272 fflush(stdout);
273
274 int sent = _q_iosend(stdout, fp, filesize);
275
276 fclose(fp);
277 return sent;
278 }
279
280 /**
281 * Print out HTML error page and exit program
282 *
283 * @param request a pointer of request structure
284 * @param format error message
285 *
286 * @return none
287 *
288 * @code
289 * qcgires_error(req, "Error: can't find userid.");
290 * @endcode
291 */
qcgires_error(qentry_t * request,char * format,...)292 void qcgires_error(qentry_t *request, char *format, ...)
293 {
294 char *buf;
295 DYNAMIC_VSPRINTF(buf, format);
296 if (buf == NULL) {
297 exit(EXIT_FAILURE);
298 }
299
300 if (getenv("REMOTE_ADDR") == NULL) {
301 printf("Error: %s\n", buf);
302 } else {
303 qcgires_setcontenttype(request, "text/html");
304
305 printf("<html>\n");
306 printf("<head>\n");
307 printf("<title>Error: %s</title>\n", buf);
308 printf("<script language='JavaScript'>\n");
309 printf(" alert(\"%s\");\n", buf);
310 printf(" history.back();\n");
311 printf("</script>\n");
312 printf("</head>\n");
313 printf("</html>\n");
314 }
315
316 free(buf);
317 if (request != NULL) request->free(request);
318 exit(EXIT_FAILURE);
319 }
320