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