1 /*
2  * Copyright 2009-2010 Michael Dirolf
3  *
4  * Dual Licensed under the Apache License, Version 2.0 and the GNU
5  * General Public License, version 2 or (at your option) any later
6  * version.
7  *
8  * -- Apache License
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  *
21  * -- GNU GPL
22  * This program is free software; you can redistribute it and/or modify
23  * it under the terms of the GNU General Public License as published by
24  * the Free Software Foundation; either version 2 of the License, or
25  * (at your option) any later version.
26  *
27  * This program is distributed in the hope that it will be useful,
28  * but WITHOUT ANY WARRANTY; without even the implied warranty of
29  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30  * GNU General Public License for more details.
31  *
32  * You should have received a copy of the GNU General Public License along
33  * with this program; if not, write to the Free Software Foundation, Inc.,
34  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
35  */
36 /*
37  * TODO range support http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
38  */
39 
40 #include <ngx_config.h>
41 #include <ngx_core.h>
42 #include <ngx_http.h>
43 #include "mongo-c-driver/src/mongo.h"
44 #include "mongo-c-driver/src/gridfs.h"
45 #include <signal.h>
46 #include <stdio.h>
47 
48 #define MONGO_MAX_RETRIES_PER_REQUEST 1
49 #define MONGO_RECONNECT_WAITTIME 500 //ms
50 #define TRUE 1
51 #define FALSE 0
52 
53 /* Parse config directive */
54 static char *ngx_http_mongo(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy);
55 
56 /* Parse config directive */
57 static char *ngx_http_gridfs(ngx_conf_t *directive, ngx_command_t *command, void *gridfs_conf);
58 
59 static void *ngx_http_gridfs_create_main_conf(ngx_conf_t *directive);
60 
61 static void *ngx_http_gridfs_create_loc_conf(ngx_conf_t *directive);
62 
63 static char *ngx_http_gridfs_merge_loc_conf(ngx_conf_t *directive, void *parent, void *child);
64 
65 static ngx_int_t ngx_http_gridfs_init_worker(ngx_cycle_t *cycle);
66 
67 static ngx_int_t ngx_http_gridfs_handler(ngx_http_request_t *request);
68 
69 static void ngx_http_gridfs_cleanup(void *data);
70 
71 typedef struct {
72     ngx_str_t db;
73     ngx_str_t root_collection;
74     ngx_str_t field;
75     ngx_uint_t type;
76     ngx_str_t user;
77     ngx_str_t pass;
78     ngx_str_t mongo;
79     ngx_array_t *mongods; /* ngx_http_mongod_server_t */
80     ngx_str_t replset; /* Name of the replica set, if connecting. */
81 } ngx_http_gridfs_loc_conf_t;
82 
83 typedef struct {
84     ngx_str_t db;
85     ngx_str_t user;
86     ngx_str_t pass;
87 } ngx_http_mongo_auth_t;
88 
89 typedef struct {
90     ngx_str_t name;
91     mongo conn;
92     ngx_array_t *auths; /* ngx_http_mongo_auth_t */
93 } ngx_http_mongo_connection_t;
94 
95 /* Maybe we should store a list of addresses instead. */
96 typedef struct {
97     ngx_str_t host;
98     in_port_t port;
99 } ngx_http_mongod_server_t;
100 
101 typedef struct {
102     ngx_array_t loc_confs; /* ngx_http_gridfs_loc_conf_t */
103 } ngx_http_gridfs_main_conf_t;
104 
105 typedef struct {
106     mongo_cursor **cursors;
107     ngx_uint_t numchunks;
108 } ngx_http_gridfs_cleanup_t;
109 
110 /* Array specifying how to handle configuration directives. */
111 static ngx_command_t ngx_http_gridfs_commands[] = {
112 
113     {
114         ngx_string("mongo"),
115         NGX_HTTP_LOC_CONF | NGX_CONF_1MORE,
116         ngx_http_mongo,
117         NGX_HTTP_LOC_CONF_OFFSET,
118         0,
119         NULL
120     },
121 
122     {
123         ngx_string("gridfs"),
124         NGX_HTTP_LOC_CONF | NGX_CONF_1MORE,
125         ngx_http_gridfs,
126         NGX_HTTP_LOC_CONF_OFFSET,
127         0,
128         NULL
129     },
130 
131     ngx_null_command
132 };
133 
134 /* Module context. */
135 static ngx_http_module_t ngx_http_gridfs_module_ctx = {
136     NULL, /* preconfiguration */
137     NULL, /* postconfiguration */
138     ngx_http_gridfs_create_main_conf,
139     NULL, /* init main configuration */
140     NULL, /* create server configuration */
141     NULL, /* init serever configuration */
142     ngx_http_gridfs_create_loc_conf,
143     ngx_http_gridfs_merge_loc_conf
144 };
145 
146 /* Module definition. */
147 ngx_module_t ngx_http_gridfs_module = {
148     NGX_MODULE_V1,
149     &ngx_http_gridfs_module_ctx,
150     ngx_http_gridfs_commands,
151     NGX_HTTP_MODULE,
152     NULL,
153     NULL,
154     ngx_http_gridfs_init_worker,
155     NULL,
156     NULL,
157     NULL,
158     NULL,
159     NGX_MODULE_V1_PADDING
160 };
161 
162 ngx_array_t ngx_http_mongo_connections;
163 
164 /* Parse the 'mongo' directive. */
ngx_http_mongo(ngx_conf_t * cf,ngx_command_t * cmd,void * void_conf)165 static char *ngx_http_mongo(ngx_conf_t *cf, ngx_command_t *cmd, void *void_conf) {
166     ngx_str_t *value;
167     ngx_url_t u;
168     ngx_uint_t i;
169     ngx_uint_t start;
170     ngx_http_mongod_server_t *mongod_server;
171     ngx_http_gridfs_loc_conf_t *gridfs_loc_conf;
172 
173     gridfs_loc_conf = void_conf;
174 
175     value = cf->args->elts;
176     gridfs_loc_conf->mongo = value[1];
177     gridfs_loc_conf->mongods = ngx_array_create(cf->pool, 7,
178                                                 sizeof(ngx_http_mongod_server_t));
179     if (gridfs_loc_conf->mongods == NULL) {
180         return NULL;
181     }
182 
183     /* If nelts is greater than 3, then the user has specified more than one
184      * setting in the 'mongo' directive. So we assume that we're connecting
185      * to a replica set and that the first string of the directive is the replica
186      * set name. We also start looking for host-port pairs at position 2; otherwise,
187      * we start at position 1.
188      */
189     if( cf->args->nelts >= 3 ) {
190         gridfs_loc_conf->replset.len = strlen( (char *)(value + 1)->data );
191         gridfs_loc_conf->replset.data = ngx_pstrdup( cf->pool, value + 1 );
192         start = 2;
193     } else
194         start = 1;
195 
196     for (i = start; i < cf->args->nelts; i++) {
197 
198         ngx_memzero(&u, sizeof(ngx_url_t));
199 
200         u.url = value[i];
201         u.default_port = 27017;
202 
203         if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
204             if (u.err) {
205                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
206                                    "%s in mongo \"%V\"", u.err, &u.url);
207             }
208             return NGX_CONF_ERROR;
209         }
210 
211         mongod_server = ngx_array_push(gridfs_loc_conf->mongods);
212         mongod_server->host = u.host;
213         mongod_server->port = u.port;
214 
215     }
216 
217     return NGX_CONF_OK;
218 }
219 
220 /* Parse the 'gridfs' directive. */
ngx_http_gridfs(ngx_conf_t * cf,ngx_command_t * command,void * void_conf)221 static char *ngx_http_gridfs(ngx_conf_t *cf, ngx_command_t *command, void *void_conf) {
222     ngx_http_gridfs_loc_conf_t *gridfs_loc_conf = void_conf;
223     ngx_http_core_loc_conf_t *core_conf;
224     ngx_str_t *value, type;
225     volatile ngx_uint_t i;
226 
227     core_conf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
228     core_conf-> handler = ngx_http_gridfs_handler;
229 
230     value = cf->args->elts;
231     gridfs_loc_conf->db = value[1];
232 
233     /* Parse the parameters */
234     for (i = 2; i < cf->args->nelts; i++) {
235         if (ngx_strncmp(value[i].data, "root_collection=", 16) == 0) {
236             gridfs_loc_conf->root_collection.data = (u_char *) &value[i].data[16];
237             gridfs_loc_conf->root_collection.len = ngx_strlen(&value[i].data[16]);
238             continue;
239         }
240 
241         if (ngx_strncmp(value[i].data, "field=", 6) == 0) {
242             gridfs_loc_conf->field.data = (u_char *) &value[i].data[6];
243             gridfs_loc_conf->field.len = ngx_strlen(&value[i].data[6]);
244 
245             /* Currently only support for "_id", "filename" and "md5" */
246             if (gridfs_loc_conf->field.data != NULL
247                 && ngx_strcmp(gridfs_loc_conf->field.data, "filename") != 0
248                 && ngx_strcmp(gridfs_loc_conf->field.data, "_id") != 0
249                 && ngx_strcmp(gridfs_loc_conf->field.data, "md5") != 0) {
250                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
251                                    "Unsupported Field: %s", gridfs_loc_conf->field.data);
252                 return NGX_CONF_ERROR;
253             }
254 
255             continue;
256         }
257 
258         if (ngx_strncmp(value[i].data, "type=", 5) == 0) {
259             type = (ngx_str_t) ngx_string(&value[i].data[5]);
260 
261             /* Currently only support for "objectid", "string", and "int" */
262             if (type.len == 0) {
263                 gridfs_loc_conf->type = NGX_CONF_UNSET_UINT;
264             } else if (ngx_strcasecmp(type.data, (u_char *)"objectid") == 0) {
265                 gridfs_loc_conf->type = BSON_OID;
266             } else if (ngx_strcasecmp(type.data, (u_char *)"string") == 0) {
267                 gridfs_loc_conf->type = BSON_STRING;
268             } else if (ngx_strcasecmp(type.data, (u_char *)"int") == 0) {
269                 gridfs_loc_conf->type = BSON_INT;
270             } else {
271                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
272                                    "Unsupported Type: %s", (char *)value[i].data);
273                 return NGX_CONF_ERROR;
274             }
275 
276             continue;
277         }
278 
279         if (ngx_strncmp(value[i].data, "user=", 5) == 0) {
280             gridfs_loc_conf->user.data = (u_char *) &value[i].data[5];
281             gridfs_loc_conf->user.len = ngx_strlen(&value[i].data[5]);
282             continue;
283         }
284 
285         if (ngx_strncmp(value[i].data, "pass=", 5) == 0) {
286             gridfs_loc_conf->pass.data = (u_char *) &value[i].data[5];
287             gridfs_loc_conf->pass.len = ngx_strlen(&value[i].data[5]);
288             continue;
289         }
290 
291         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
292                            "invalid parameter \"%V\"", &value[i]);
293         return NGX_CONF_ERROR;
294     }
295 
296     if (gridfs_loc_conf->field.data != NULL
297         && ngx_strcmp(gridfs_loc_conf->field.data, "filename") == 0
298         && gridfs_loc_conf->type != BSON_STRING) {
299         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
300                            "Field: filename, must be of Type: string");
301         return NGX_CONF_ERROR;
302     }
303 
304     if (gridfs_loc_conf->field.data != NULL
305         && ngx_strcmp(gridfs_loc_conf->field.data, "md5") == 0
306         && gridfs_loc_conf->type != BSON_STRING) {
307         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
308                            "Field: md5, must be of Type: string");
309         return NGX_CONF_ERROR;
310     }
311 
312     if ((gridfs_loc_conf->user.data == NULL || gridfs_loc_conf->user.len == 0)
313         && !(gridfs_loc_conf->pass.data == NULL || gridfs_loc_conf->pass.len == 0)) {
314         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
315                            "Password without username");
316         return NGX_CONF_ERROR;
317     }
318 
319     if (!(gridfs_loc_conf->user.data == NULL || gridfs_loc_conf->user.len == 0)
320         && (gridfs_loc_conf->pass.data == NULL || gridfs_loc_conf->pass.len == 0)) {
321         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
322                            "Username without password");
323         return NGX_CONF_ERROR;
324     }
325 
326     return NGX_CONF_OK;
327 }
328 
ngx_http_gridfs_create_main_conf(ngx_conf_t * cf)329 static void *ngx_http_gridfs_create_main_conf(ngx_conf_t *cf) {
330     ngx_http_gridfs_main_conf_t  *gridfs_main_conf;
331 
332     gridfs_main_conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gridfs_main_conf_t));
333     if (gridfs_main_conf == NULL) {
334         return NULL;
335     }
336 
337     if (ngx_array_init(&gridfs_main_conf->loc_confs, cf->pool, 4,
338                        sizeof(ngx_http_gridfs_loc_conf_t *))
339         != NGX_OK) {
340         return NULL;
341     }
342 
343     return gridfs_main_conf;
344 }
345 
ngx_http_gridfs_create_loc_conf(ngx_conf_t * directive)346 static void *ngx_http_gridfs_create_loc_conf(ngx_conf_t *directive) {
347     ngx_http_gridfs_loc_conf_t *gridfs_conf;
348 
349     gridfs_conf = ngx_pcalloc(directive->pool, sizeof(ngx_http_gridfs_loc_conf_t));
350     if (gridfs_conf == NULL) {
351         ngx_conf_log_error(NGX_LOG_EMERG, directive, 0,
352                            "Failed to allocate memory for GridFS Location Config.");
353         return NGX_CONF_ERROR;
354     }
355 
356     gridfs_conf->db.data = NULL;
357     gridfs_conf->db.len = 0;
358     gridfs_conf->root_collection.data = NULL;
359     gridfs_conf->root_collection.len = 0;
360     gridfs_conf->field.data = NULL;
361     gridfs_conf->field.len = 0;
362     gridfs_conf->type = NGX_CONF_UNSET_UINT;
363     gridfs_conf->user.data = NULL;
364     gridfs_conf->user.len = 0;
365     gridfs_conf->pass.data = NULL;
366     gridfs_conf->pass.len = 0;
367     gridfs_conf->mongo.data = NULL;
368     gridfs_conf->mongo.len = 0;
369     gridfs_conf->mongods = NGX_CONF_UNSET_PTR;
370 
371     return gridfs_conf;
372 }
373 
ngx_http_gridfs_merge_loc_conf(ngx_conf_t * cf,void * void_parent,void * void_child)374 static char *ngx_http_gridfs_merge_loc_conf(ngx_conf_t *cf, void *void_parent, void *void_child) {
375     ngx_http_gridfs_loc_conf_t *parent = void_parent;
376     ngx_http_gridfs_loc_conf_t *child = void_child;
377     ngx_http_gridfs_main_conf_t *gridfs_main_conf = ngx_http_conf_get_module_main_conf(cf, ngx_http_gridfs_module);
378     ngx_http_gridfs_loc_conf_t **gridfs_loc_conf;
379     ngx_http_mongod_server_t *mongod_server;
380 
381     ngx_conf_merge_str_value(child->db, parent->db, NULL);
382     ngx_conf_merge_str_value(child->root_collection, parent->root_collection, "fs");
383     ngx_conf_merge_str_value(child->field, parent->field, "_id");
384     ngx_conf_merge_uint_value(child->type, parent->type, BSON_OID);
385     ngx_conf_merge_str_value(child->user, parent->user, NULL);
386     ngx_conf_merge_str_value(child->pass, parent->pass, NULL);
387     ngx_conf_merge_str_value(child->mongo, parent->mongo, "127.0.0.1:27017");
388 
389     if (child->mongods == NGX_CONF_UNSET_PTR) {
390         if (parent->mongods != NGX_CONF_UNSET_PTR) {
391             child->mongods = parent->mongods;
392         } else {
393             child->mongods = ngx_array_create(cf->pool, 4,
394                                               sizeof(ngx_http_mongod_server_t));
395             mongod_server = ngx_array_push(child->mongods);
396             mongod_server->host.data = (u_char *)"127.0.0.1";
397             mongod_server->host.len = sizeof("127.0.0.1") - 1;
398             mongod_server->port = 27017;
399         }
400     }
401 
402     // Add the local gridfs conf to the main gridfs conf
403     if (child->db.data) {
404         gridfs_loc_conf = ngx_array_push(&gridfs_main_conf->loc_confs);
405         *gridfs_loc_conf = child;
406     }
407 
408     return NGX_CONF_OK;
409 }
410 
ngx_http_get_mongo_connection(ngx_str_t name)411 ngx_http_mongo_connection_t *ngx_http_get_mongo_connection( ngx_str_t name ) {
412     ngx_http_mongo_connection_t *mongo_conns;
413     ngx_uint_t i;
414 
415     mongo_conns = ngx_http_mongo_connections.elts;
416 
417     for ( i = 0; i < ngx_http_mongo_connections.nelts; i++ ) {
418         if ( name.len == mongo_conns[i].name.len
419              && ngx_strncmp(name.data, mongo_conns[i].name.data, name.len) == 0 ) {
420             return &mongo_conns[i];
421         }
422     }
423 
424     return NULL;
425 }
426 
ngx_http_mongo_authenticate(ngx_log_t * log,ngx_http_gridfs_loc_conf_t * gridfs_loc_conf)427 static ngx_int_t ngx_http_mongo_authenticate(ngx_log_t *log, ngx_http_gridfs_loc_conf_t *gridfs_loc_conf) {
428     ngx_http_mongo_connection_t *mongo_conn;
429     ngx_http_mongo_auth_t *mongo_auth;
430     mongo_cursor *cursor = NULL;
431     bson empty;
432     char *test;
433     int error;
434 
435     mongo_conn = ngx_http_get_mongo_connection( gridfs_loc_conf->mongo );
436     if (mongo_conn == NULL) {
437         ngx_log_error(NGX_LOG_ERR, log, 0,
438                   "Mongo Connection not found: \"%V\"", &gridfs_loc_conf->mongo);
439     }
440 
441     // Authenticate
442     if (gridfs_loc_conf->user.data != NULL && gridfs_loc_conf->pass.data != NULL) {
443         if (mongo_cmd_authenticate( &mongo_conn->conn,
444 				    (const char*)gridfs_loc_conf->db.data,
445 				    (const char*)gridfs_loc_conf->user.data,
446 				    (const char*)gridfs_loc_conf->pass.data )
447 	    != MONGO_OK) {
448             ngx_log_error(NGX_LOG_ERR, log, 0,
449                           "Invalid mongo user/pass: %s/%s",
450                           gridfs_loc_conf->user.data,
451                           gridfs_loc_conf->pass.data);
452             return NGX_ERROR;
453         }
454 
455         mongo_auth = ngx_array_push(mongo_conn->auths);
456         mongo_auth->db = gridfs_loc_conf->db;
457         mongo_auth->user = gridfs_loc_conf->user;
458         mongo_auth->pass = gridfs_loc_conf->pass;
459     }
460 
461     // Run a test command to test authentication.
462     test = (char*)malloc( gridfs_loc_conf->db.len + sizeof(".test"));
463     ngx_cpystrn((u_char*)test, (u_char*)gridfs_loc_conf->db.data, gridfs_loc_conf->db.len+1);
464     ngx_cpystrn((u_char*)(test+gridfs_loc_conf->db.len),(u_char*)".test", sizeof(".test"));
465     bson_empty(&empty);
466     cursor = mongo_find(&mongo_conn->conn, test, &empty, NULL, 0, 0, 0);
467     error =  mongo_cmd_get_last_error(&mongo_conn->conn, (char*)gridfs_loc_conf->db.data, NULL);
468     free(test);
469     mongo_cursor_destroy(cursor);
470     if (error) {
471         ngx_log_error(NGX_LOG_ERR, log, 0, "Authentication Required");
472         return NGX_ERROR;
473     }
474 
475     return NGX_OK;
476 }
477 
ngx_http_mongo_add_connection(ngx_cycle_t * cycle,ngx_http_gridfs_loc_conf_t * gridfs_loc_conf)478 static ngx_int_t ngx_http_mongo_add_connection(ngx_cycle_t *cycle, ngx_http_gridfs_loc_conf_t *gridfs_loc_conf) {
479     ngx_http_mongo_connection_t *mongo_conn;
480     int status;
481     ngx_http_mongod_server_t *mongods;
482     volatile ngx_uint_t i;
483     u_char host[255];
484 
485     mongods = gridfs_loc_conf->mongods->elts;
486 
487     mongo_conn = ngx_http_get_mongo_connection( gridfs_loc_conf->mongo );
488     if (mongo_conn != NULL) {
489         return NGX_OK;
490     }
491 
492     mongo_conn = ngx_array_push(&ngx_http_mongo_connections);
493     if (mongo_conn == NULL) {
494         return NGX_ERROR;
495     }
496 
497     mongo_conn->name = gridfs_loc_conf->mongo;
498     mongo_conn->auths = ngx_array_create(cycle->pool, 4, sizeof(ngx_http_mongo_auth_t));
499 
500     if ( gridfs_loc_conf->mongods->nelts == 1 ) {
501         ngx_cpystrn( host, mongods[0].host.data, mongods[0].host.len + 1 );
502         status = mongo_connect( &mongo_conn->conn, (const char*)host, mongods[0].port );
503     } else if ( gridfs_loc_conf->mongods->nelts >= 2 && gridfs_loc_conf->mongods->nelts < 9 ) {
504 
505         /* Initiate replica set connection. */
506         mongo_replset_init( &mongo_conn->conn, (const char *)gridfs_loc_conf->replset.data );
507 
508         /* Add replica set seeds. */
509         for( i=0; i<gridfs_loc_conf->mongods->nelts; ++i ) {
510             ngx_cpystrn( host, mongods[i].host.data, mongods[i].host.len + 1 );
511             mongo_replset_add_seed( &mongo_conn->conn, (const char *)host, mongods[i].port );
512         }
513         status = mongo_replset_connect( &mongo_conn->conn );
514     } else {
515         ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
516                       "Mongo Nginx Exception: Too many strings provided in 'mongo' directive.");
517         return NGX_ERROR;
518     }
519 
520     switch (mongo_conn->conn.err) {
521         case MONGO_CONN_SUCCESS:
522             break;
523         case MONGO_CONN_NO_SOCKET:
524             ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
525                           "Mongo Exception: No Socket");
526             return NGX_ERROR;
527         case MONGO_CONN_FAIL:
528             ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
529                           "Mongo Exception: Connection Failure.");
530             return NGX_ERROR;
531         case MONGO_CONN_ADDR_FAIL:
532             ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
533                           "Mongo Exception: getaddrinfo Failure.");
534             return NGX_ERROR;
535         case MONGO_CONN_NOT_MASTER:
536             ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
537                           "Mongo Exception: Not Master");
538             return NGX_ERROR;
539         case MONGO_CONN_BAD_SET_NAME:
540             ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
541                           "Mongo Exception: Replica set name %s does not match.", gridfs_loc_conf->replset.data);
542             return NGX_ERROR;
543         case MONGO_CONN_NO_PRIMARY:
544             ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
545                           "Mongo Exception: Cannot connect to primary node.");
546             return NGX_ERROR;
547         default:
548             ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
549                           "Mongo Exception: Unknown Error");
550             return NGX_ERROR;
551     }
552 
553     return NGX_OK;
554 }
555 
ngx_http_gridfs_init_worker(ngx_cycle_t * cycle)556 static ngx_int_t ngx_http_gridfs_init_worker(ngx_cycle_t *cycle) {
557     ngx_http_gridfs_main_conf_t *gridfs_main_conf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_gridfs_module);
558     ngx_http_gridfs_loc_conf_t **gridfs_loc_confs;
559     ngx_uint_t i;
560 
561     signal(SIGPIPE, SIG_IGN);
562 
563     gridfs_loc_confs = gridfs_main_conf->loc_confs.elts;
564 
565     ngx_array_init(&ngx_http_mongo_connections, cycle->pool, 4, sizeof(ngx_http_mongo_connection_t));
566 
567     for (i = 0; i < gridfs_main_conf->loc_confs.nelts; i++) {
568         if (ngx_http_mongo_add_connection(cycle, gridfs_loc_confs[i]) == NGX_ERROR) {
569             return NGX_ERROR;
570         }
571         if (ngx_http_mongo_authenticate(cycle->log, gridfs_loc_confs[i]) == NGX_ERROR) {
572             return NGX_ERROR;
573         }
574     }
575 
576     return NGX_OK;
577 }
578 
ngx_http_mongo_reconnect(ngx_log_t * log,ngx_http_mongo_connection_t * mongo_conn)579 static ngx_int_t ngx_http_mongo_reconnect(ngx_log_t *log, ngx_http_mongo_connection_t *mongo_conn) {
580     volatile int status = MONGO_CONN_FAIL;
581 
582     if (&mongo_conn->conn.connected) {
583         mongo_disconnect(&mongo_conn->conn);
584         ngx_msleep(MONGO_RECONNECT_WAITTIME);
585         status = mongo_reconnect(&mongo_conn->conn);
586     } else {
587         ngx_log_error(NGX_LOG_ERR, log, 0,
588                       "Mongo Nginx Exception: Not Connected");
589         return NGX_ERROR;
590     }
591 
592     switch (mongo_conn->conn.err) {
593         case MONGO_CONN_SUCCESS:
594             break;
595         case MONGO_CONN_NO_SOCKET:
596             ngx_log_error(NGX_LOG_ERR, log, 0,
597                           "Mongo Exception: No Socket");
598             return NGX_ERROR;
599         case MONGO_CONN_FAIL:
600             ngx_log_error(NGX_LOG_ERR, log, 0,
601                           "Mongo Exception: Connection Failure %s:%i;",
602                           mongo_conn->conn.primary->host,
603                           mongo_conn->conn.primary->port);
604             return NGX_ERROR;
605         case MONGO_CONN_ADDR_FAIL:
606             ngx_log_error(NGX_LOG_ERR, log, 0,
607                           "Mongo Exception: getaddrinfo Failure");
608             return NGX_ERROR;
609         case MONGO_CONN_NOT_MASTER:
610             ngx_log_error(NGX_LOG_ERR, log, 0,
611                           "Mongo Exception: Not Master");
612             return NGX_ERROR;
613         default:
614             ngx_log_error(NGX_LOG_ERR, log, 0,
615                           "Mongo Exception: Unknown Error");
616             return NGX_ERROR;
617     }
618 
619     return NGX_OK;
620 }
621 
ngx_http_mongo_reauth(ngx_log_t * log,ngx_http_mongo_connection_t * mongo_conn)622 static ngx_int_t ngx_http_mongo_reauth(ngx_log_t *log, ngx_http_mongo_connection_t *mongo_conn) {
623     ngx_http_mongo_auth_t *auths;
624     volatile ngx_uint_t i;
625     volatile ngx_int_t status = 0;
626     auths = mongo_conn->auths->elts;
627 
628     for (i = 0; i < mongo_conn->auths->nelts; i++) {
629         status = mongo_cmd_authenticate( &mongo_conn->conn,
630 					 (const char*)auths[i].db.data,
631 					 (const char*)auths[i].user.data,
632 					 (const char*)auths[i].pass.data );
633         if (status != MONGO_OK) {
634             ngx_log_error(NGX_LOG_ERR, log, 0,
635                           "Invalid mongo user/pass: %s/%s, during reauth",
636                           auths[i].user.data,
637                           auths[i].pass.data);
638             return NGX_ERROR;
639         }
640     }
641 
642     return NGX_OK;
643 }
644 
h_digit(char hex)645 static char h_digit(char hex) {
646     return (hex >= '0' && hex <= '9') ? hex - '0': ngx_tolower(hex)-'a'+10;
647 }
648 
htoi(char * h)649 static int htoi(char *h) {
650     char ok[] = "0123456789AaBbCcDdEeFf";
651 
652     if (ngx_strchr(ok, h[0]) == NULL || ngx_strchr(ok,h[1]) == NULL) { return -1; }
653     return h_digit(h[0])*16 + h_digit(h[1]);
654 }
655 
url_decode(char * filename)656 static int url_decode(char *filename) {
657     char *read = filename;
658     char *write = filename;
659     char hex[3];
660     int c;
661 
662     hex[2] = '\0';
663     while (*read != '\0'){
664         if (*read == '%') {
665             hex[0] = *(++read);
666             if (hex[0] == '\0') return 0;
667             hex[1] = *(++read);
668             if (hex[1] == '\0') return 0;
669             c = htoi(hex);
670             if (c == -1) return 0;
671             *write = (char)c;
672         }
673         else *write = *read;
674         read++;
675         write++;
676     }
677     *write = '\0';
678     return 1;
679 }
680 
gridfs_parse_range(ngx_http_request_t * r,ngx_str_t * range_str,uint64_t * range_start,uint64_t * range_end,gridfs_offset content_length)681 static void gridfs_parse_range(ngx_http_request_t *r, ngx_str_t *range_str, uint64_t *range_start, uint64_t *range_end, gridfs_offset content_length) {
682     u_char *p, *last;
683     off_t start, end;
684     ngx_uint_t bad;
685     enum {
686         sw_start = 0,
687         sw_first_byte_pos,
688         sw_first_byte_pos_n,
689         sw_last_byte_pos,
690         sw_last_byte_pos_n,
691         sw_done
692     } state = 0;
693 
694     p = (u_char *) ngx_strnstr(range_str->data, "bytes=", range_str->len);
695 
696     if (p == NULL) {
697         return;
698     }
699 
700     p += sizeof("bytes=") - 1;
701     last = range_str->data + range_str->len;
702 
703     /*
704      * bytes= contain ranges compatible with RFC 2616, "14.35.1 Byte Ranges",
705      * but no whitespaces permitted
706      */
707 
708     bad = 0;
709     start = 0;
710     end = 0;
711 
712     while (p < last) {
713 
714         switch (state) {
715 
716         case sw_start:
717         case sw_first_byte_pos:
718             if (*p == '-') {
719                 p++;
720                 state = sw_last_byte_pos;
721                 break;
722             }
723             start = 0;
724             state = sw_first_byte_pos_n;
725 
726             /* fall through */
727 
728         case sw_first_byte_pos_n:
729             if (*p == '-') {
730                 p++;
731                 state = sw_last_byte_pos;
732                 break;
733             }
734             if (*p < '0' || *p > '9') {
735                 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
736                                "bytes header filter: unexpected char '%c'"
737                                " (expected first-byte-pos)", *p);
738                 bad = 1;
739                 break;
740             }
741             start = start * 10 + *p - '0';
742             p++;
743             break;
744 
745         case sw_last_byte_pos:
746             if (*p == ',' || *p == '&' || *p == ';') {
747                 /* no last byte pos, assume end of file */
748                 end = content_length - 1;
749                 state = sw_done;
750                 break;
751             }
752             end = 0;
753             state = sw_last_byte_pos_n;
754 
755             /* fall though */
756 
757         case sw_last_byte_pos_n:
758             if (*p == ',' || *p == '&' || *p == ';') {
759                 state = sw_done;
760                 break;
761             }
762             if (*p < '0' || *p > '9') {
763                 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
764                                "bytes header filter: unexpected char '%c'"
765                                " (expected last-byte-pos)", *p);
766                 bad = 1;
767                 break;
768             }
769             end = end * 10 + *p - '0';
770             p++;
771             break;
772 
773         case sw_done:
774             *range_start = start;
775             *range_end = end;
776 
777             break;
778         }
779 
780         if (bad) {
781             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
782                            "bytes header filter: invalid range specification");
783             return;
784         }
785     }
786 
787     switch (state) {
788 
789     case sw_last_byte_pos:
790         end = content_length - 1;
791 
792     case sw_last_byte_pos_n:
793         if (start > end) {
794             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
795                            "bytes header filter: invalid range specification");
796             return;
797         }
798 
799         *range_start = start;
800         *range_end = end;
801         break;
802 
803     default:
804         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
805                        "bytes header filter: invalid range specification");
806         return;
807 
808     }
809 }
810 
ngx_http_gridfs_handler(ngx_http_request_t * request)811 static ngx_int_t ngx_http_gridfs_handler(ngx_http_request_t *request) {
812     ngx_http_gridfs_loc_conf_t *gridfs_conf;
813     ngx_http_core_loc_conf_t *core_conf;
814     ngx_buf_t *buffer;
815     ngx_chain_t out;
816     ngx_str_t location_name;
817     ngx_str_t full_uri;
818     char *value;
819     ngx_http_mongo_connection_t *mongo_conn;
820     gridfs gfs;
821     gridfile gfile;
822     gridfs_offset length;
823     ngx_uint_t numchunks;
824     char *contenttype;
825     char *md5;
826     bson_date_t last_modified;
827 
828     volatile ngx_uint_t i;
829     ngx_int_t rc = NGX_OK;
830     bson query;
831     bson_oid_t oid;
832     mongo_cursor **cursors;
833     gridfs_offset chunk_len;
834     const char  *chunk_data;
835     bson_iterator it;
836     bson chunk;
837     ngx_pool_cleanup_t *gridfs_cln;
838     ngx_http_gridfs_cleanup_t *gridfs_clndata;
839     int status;
840     volatile ngx_uint_t e = FALSE;
841     volatile ngx_uint_t ecounter = 0;
842     uint64_t range_start = 0;
843     uint64_t range_end   = 0;
844     uint64_t current_buf_pos = 0;
845 
846     gridfs_conf = ngx_http_get_module_loc_conf(request, ngx_http_gridfs_module);
847     core_conf = ngx_http_get_module_loc_conf(request, ngx_http_core_module);
848 
849     // ---------- ENSURE MONGO CONNECTION ---------- //
850 
851     mongo_conn = ngx_http_get_mongo_connection( gridfs_conf->mongo );
852     if (mongo_conn == NULL) {
853         ngx_log_error(NGX_LOG_ERR, request->connection->log, 0,
854                       "Mongo Connection not found: \"%V\"", &gridfs_conf->mongo);
855         return NGX_HTTP_INTERNAL_SERVER_ERROR;
856     }
857 
858     if (mongo_conn->conn.connected == 0) {
859         if (ngx_http_mongo_reconnect(request->connection->log, mongo_conn) == NGX_ERROR) {
860             ngx_log_error(NGX_LOG_ERR, request->connection->log, 0,
861                           "Could not connect to mongo: \"%V\"", &gridfs_conf->mongo);
862             if(mongo_conn->conn.connected) { mongo_disconnect(&mongo_conn->conn); }
863             return NGX_HTTP_SERVICE_UNAVAILABLE;
864         }
865         if (ngx_http_mongo_reauth(request->connection->log, mongo_conn) == NGX_ERROR) {
866             ngx_log_error(NGX_LOG_ERR, request->connection->log, 0,
867                           "Failed to reauth to mongo: \"%V\"", &gridfs_conf->mongo);
868             if(mongo_conn->conn.connected) { mongo_disconnect(&mongo_conn->conn); }
869             return NGX_HTTP_SERVICE_UNAVAILABLE;
870         }
871     }
872 
873     // ---------- RETRIEVE KEY ---------- //
874 
875     location_name = core_conf->name;
876     full_uri = request->uri;
877 
878     if (full_uri.len < location_name.len) {
879         ngx_log_error(NGX_LOG_ERR, request->connection->log, 0,
880                       "Invalid location name or uri.");
881         return NGX_HTTP_INTERNAL_SERVER_ERROR;
882     }
883 
884     value = (char*)malloc(sizeof(char) * (full_uri.len - location_name.len + 1));
885     if (value == NULL) {
886         ngx_log_error(NGX_LOG_ERR, request->connection->log, 0,
887                       "Failed to allocate memory for value buffer.");
888         return NGX_HTTP_INTERNAL_SERVER_ERROR;
889     }
890     memcpy(value, full_uri.data + location_name.len, full_uri.len - location_name.len);
891     value[full_uri.len - location_name.len] = '\0';
892 
893     if (!url_decode(value)) {
894         ngx_log_error(NGX_LOG_ERR, request->connection->log, 0,
895                       "Malformed request.");
896         free(value);
897         return NGX_HTTP_BAD_REQUEST;
898     }
899 
900     // ---------- RETRIEVE GRIDFILE ---------- //
901 
902     do {
903         e = FALSE;
904         status = gridfs_init(&mongo_conn->conn,
905                              (const char*)gridfs_conf->db.data,
906                              (const char*)gridfs_conf->root_collection.data,
907                              &gfs);
908         if (status != MONGO_OK) {
909             e = TRUE; ecounter++;
910             if (ecounter > MONGO_MAX_RETRIES_PER_REQUEST
911                 || ngx_http_mongo_reconnect(request->connection->log, mongo_conn) == NGX_ERROR
912                 || ngx_http_mongo_reauth(request->connection->log, mongo_conn) == NGX_ERROR) {
913                 ngx_log_error(NGX_LOG_ERR, request->connection->log, 0,
914                               "Mongo connection dropped, could not reconnect");
915                 if(mongo_conn->conn.connected) { mongo_disconnect(&mongo_conn->conn); }
916                 free(value);
917                 return NGX_HTTP_SERVICE_UNAVAILABLE;
918             }
919         }
920     } while (e);
921 
922     bson_init(&query);
923     switch (gridfs_conf->type) {
924     case  BSON_OID:
925         bson_oid_from_string(&oid, value);
926         bson_append_oid(&query, (char*)gridfs_conf->field.data, &oid);
927         break;
928     case BSON_INT:
929       bson_append_int(&query, (char*)gridfs_conf->field.data, ngx_atoi((u_char*)value, strlen(value)));
930         break;
931     case BSON_STRING:
932         bson_append_string(&query, (char*)gridfs_conf->field.data, value);
933         break;
934     }
935     bson_finish(&query);
936 
937     status = gridfs_find_query(&gfs, &query, &gfile);
938 
939     bson_destroy(&query);
940     free(value);
941 
942     if(status == MONGO_ERROR) {
943         gridfs_destroy(&gfs);
944         return NGX_HTTP_NOT_FOUND;
945     }
946 
947     /* Get information about the file */
948     length = gridfile_get_contentlength(&gfile);
949     numchunks = gridfile_get_numchunks(&gfile);
950 
951     // NaN workaround
952     if (numchunks > INT_MAX)
953     {
954         gridfile_destroy(&gfile);
955         gridfs_destroy(&gfs);
956         return NGX_HTTP_NOT_FOUND;
957     }
958 
959     contenttype = (char*)gridfile_get_contenttype(&gfile);
960     md5 = (char*)gridfile_get_md5(&gfile);
961     last_modified = gridfile_get_uploaddate(&gfile);
962 
963     if (request->headers_in.range) {
964         gridfs_parse_range(request, &request->headers_in.range->value, &range_start, &range_end, length);
965     }
966 
967     // ---------- SEND THE HEADERS ---------- //
968 
969     if (range_start == 0 && range_end == 0) {
970         request->headers_out.status = NGX_HTTP_OK;
971         request->headers_out.content_length_n = length;
972     } else {
973         request->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
974         request->headers_out.content_length_n = length;
975 
976         ngx_table_elt_t   *content_range;
977 
978         content_range = ngx_list_push(&request->headers_out.headers);
979         if (content_range == NULL) {
980             return NGX_ERROR;
981         }
982 
983         request->headers_out.content_range = content_range;
984 
985         content_range->hash = 1;
986         ngx_str_set(&content_range->key, "Content-Range");
987 
988         content_range->value.data = ngx_pnalloc(request->pool,sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
989         if (content_range->value.data == NULL) {
990             return NGX_ERROR;
991         }
992 
993         /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
994         content_range->value.len = ngx_sprintf(content_range->value.data,
995                                                "bytes %O-%O/%O",
996                                                range_start, range_end,
997                                                request->headers_out.content_length_n)
998             - content_range->value.data;
999 
1000         request->headers_out.content_length_n = range_end - range_start + 1;
1001     }
1002     if (contenttype != NULL) {
1003         request->headers_out.content_type.len = strlen(contenttype);
1004         request->headers_out.content_type.data = (u_char*)contenttype;
1005     }
1006     else ngx_http_set_content_type(request);
1007 
1008     // use md5 field as ETag if possible
1009     if (md5 != NULL) {
1010         request->headers_out.etag = ngx_list_push(&request->headers_out.headers);
1011         request->headers_out.etag->hash = 1;
1012         request->headers_out.etag->key.len = sizeof("ETag") - 1;
1013         request->headers_out.etag->key.data = (u_char*)"ETag";
1014 
1015         ngx_buf_t *b;
1016         b = ngx_create_temp_buf(request->pool, strlen(md5) + 2);
1017         b->last = ngx_sprintf(b->last, "\"%s\"", md5);
1018         request->headers_out.etag->value.len = strlen(md5) + 2;
1019         request->headers_out.etag->value.data = b->start;
1020     }
1021 
1022     // use uploadDate field as last_modified if possible
1023     if (last_modified) {
1024         request->headers_out.last_modified_time = (time_t)(last_modified/1000);
1025     }
1026 
1027     /* Determine if content is gzipped, set headers accordingly */
1028     if ( gridfile_get_boolean(&gfile,"gzipped") ) {
1029         ngx_log_error(NGX_LOG_ERR, request->connection->log, 0, gridfile_get_field(&gfile,"gzipped") );
1030         request->headers_out.content_encoding = ngx_list_push(&request->headers_out.headers);
1031         if (request->headers_out.content_encoding == NULL) {
1032             gridfile_destroy(&gfile);
1033             gridfs_destroy(&gfs);
1034             return NGX_ERROR;
1035         }
1036         request->headers_out.content_encoding->hash = 1;
1037         request->headers_out.content_encoding->key.len = sizeof("Content-Encoding") - 1;
1038         request->headers_out.content_encoding->key.data = (u_char *) "Content-Encoding";
1039         request->headers_out.content_encoding->value.len = sizeof("gzip") - 1;
1040         request->headers_out.content_encoding->value.data = (u_char *) "gzip";
1041     }
1042 
1043     ngx_http_send_header(request);
1044 
1045     if (request->method == NGX_HTTP_HEAD) {
1046         gridfile_destroy(&gfile);
1047         gridfs_destroy(&gfs);
1048         request->header_only = 1;
1049         return NGX_OK;
1050     }
1051 
1052     // ---------- SEND THE BODY ---------- //
1053 
1054     /* Empty file */
1055     if (numchunks == 0) {
1056         /* Allocate space for the response buffer */
1057         buffer = ngx_pcalloc(request->pool, sizeof(ngx_buf_t));
1058         if (buffer == NULL) {
1059             ngx_log_error(NGX_LOG_ERR, request->connection->log, 0,
1060                           "Failed to allocate response buffer");
1061             gridfile_destroy(&gfile);
1062             gridfs_destroy(&gfs);
1063             return NGX_HTTP_INTERNAL_SERVER_ERROR;
1064         }
1065 
1066         buffer->pos = NULL;
1067         buffer->last = NULL;
1068         buffer->memory = 1;
1069         buffer->last_buf = 1;
1070         out.buf = buffer;
1071         out.next = NULL;
1072 
1073         gridfile_destroy(&gfile);
1074         gridfs_destroy(&gfs);
1075 
1076         return ngx_http_output_filter(request, &out);
1077     }
1078 
1079     cursors = (mongo_cursor **)ngx_pcalloc(request->pool, sizeof(mongo_cursor *) * numchunks);
1080     if (cursors == NULL) {
1081       gridfile_destroy(&gfile);
1082       gridfs_destroy(&gfs);
1083       return NGX_HTTP_INTERNAL_SERVER_ERROR;
1084     }
1085 
1086     ngx_memzero( cursors, sizeof(mongo_cursor *) * numchunks);
1087 
1088     /* Hook in the cleanup function */
1089     gridfs_cln = ngx_pool_cleanup_add(request->pool, sizeof(ngx_http_gridfs_cleanup_t));
1090     if (gridfs_cln == NULL) {
1091       gridfile_destroy(&gfile);
1092       gridfs_destroy(&gfs);
1093       return NGX_HTTP_INTERNAL_SERVER_ERROR;
1094     }
1095     gridfs_cln->handler = ngx_http_gridfs_cleanup;
1096     gridfs_clndata = gridfs_cln->data;
1097     gridfs_clndata->cursors = cursors;
1098     gridfs_clndata->numchunks = numchunks;
1099 
1100     /* Read and serve chunk by chunk */
1101     for (i = 0; i < numchunks; i++) {
1102 
1103         /* Allocate space for the response buffer */
1104         buffer = ngx_pcalloc(request->pool, sizeof(ngx_buf_t));
1105         if (buffer == NULL) {
1106             ngx_log_error(NGX_LOG_ERR, request->connection->log, 0,
1107                           "Failed to allocate response buffer");
1108             gridfile_destroy(&gfile);
1109             gridfs_destroy(&gfs);
1110             return NGX_HTTP_INTERNAL_SERVER_ERROR;
1111         }
1112 
1113         /* Fetch the chunk from mongo */
1114         do {
1115             e = FALSE;
1116             cursors[i] = gridfile_get_chunks(&gfile, i, 1);
1117             if (!(cursors[i] && mongo_cursor_next(cursors[i]) == MONGO_OK)) {
1118                 e = TRUE; ecounter++;
1119                 if (ecounter > MONGO_MAX_RETRIES_PER_REQUEST
1120                     || ngx_http_mongo_reconnect(request->connection->log, mongo_conn) == NGX_ERROR
1121                     || ngx_http_mongo_reauth(request->connection->log, mongo_conn) == NGX_ERROR) {
1122                     ngx_log_error(NGX_LOG_ERR, request->connection->log, 0,
1123                                   "Mongo connection dropped, could not reconnect");
1124                     if(mongo_conn->conn.connected) { mongo_disconnect(&mongo_conn->conn); }
1125                     gridfile_destroy(&gfile);
1126                     gridfs_destroy(&gfs);
1127                     return NGX_HTTP_SERVICE_UNAVAILABLE;
1128                 }
1129             }
1130         } while (e);
1131 
1132         chunk = cursors[i]->current;
1133         bson_find(&it, &chunk, "data");
1134         chunk_len = bson_iterator_bin_len( &it );
1135         chunk_data = bson_iterator_bin_data( &it );
1136 
1137         if (range_start == 0 && range_end == 0) {
1138             /* <<no range request>> */
1139             /* Set up the buffer chain */
1140             buffer->pos = (u_char*)chunk_data;
1141             buffer->last = (u_char*)chunk_data + chunk_len;
1142             buffer->memory = 1;
1143             buffer->last_buf = (i == numchunks-1);
1144             out.buf = buffer;
1145             out.next = NULL;
1146 
1147             /* Serve the Chunk */
1148             rc = ngx_http_output_filter(request, &out);
1149         } else {
1150             /* <<range request>> */
1151             if ( range_start >= (current_buf_pos+chunk_len) ||
1152                  range_end <= current_buf_pos) {
1153                 /* no output */
1154                 ngx_pfree(request->pool, buffer);
1155             } else {
1156                 if (range_start <= current_buf_pos) {
1157                     buffer->pos = (u_char*)chunk_data;
1158                 } else {
1159                     buffer->pos = (u_char*)chunk_data + (range_start - current_buf_pos);
1160                 }
1161                 if (range_end < (current_buf_pos+chunk_len)) {
1162                     buffer->last = (u_char*)chunk_data + (range_end - current_buf_pos + 1);
1163                 } else {
1164                     buffer->last = (u_char*)chunk_data + chunk_len;
1165                 }
1166                 if (buffer->pos == buffer->last) {
1167                     ngx_log_error(NGX_LOG_ALERT, request->connection->log, 0,
1168                                   "zero size buf in writer "
1169                                   "range_start:%d range_end:%d "
1170                                   "current_buf_pos:%d chunk_len:%d i:%d numchunk:%d",
1171                                   range_start,range_end,
1172                                   current_buf_pos, chunk_len,
1173                                   i,numchunks);
1174                 }
1175                 buffer->memory = 1;
1176                 buffer->last_buf = (i == numchunks-1) || (range_end < (current_buf_pos+chunk_len));
1177                 out.buf = buffer;
1178                 out.next = NULL;
1179 
1180                 /* Serve the Chunk */
1181                 rc = ngx_http_output_filter(request, &out);
1182             }
1183         }
1184 
1185         current_buf_pos += chunk_len;
1186 
1187         /* TODO: More Codes to Catch? */
1188         if (rc == NGX_ERROR) {
1189             gridfile_destroy(&gfile);
1190             gridfs_destroy(&gfs);
1191             return NGX_ERROR;
1192         }
1193     }
1194 
1195     gridfile_destroy(&gfile);
1196     gridfs_destroy(&gfs);
1197 
1198     return rc;
1199 }
1200 
1201 
ngx_http_gridfs_cleanup(void * data)1202 static void ngx_http_gridfs_cleanup(void *data) {
1203     ngx_http_gridfs_cleanup_t *gridfs_clndata;
1204     volatile ngx_uint_t i;
1205 
1206     gridfs_clndata = data;
1207 
1208     for (i = 0; i < gridfs_clndata->numchunks; i++) {
1209         mongo_cursor_destroy(gridfs_clndata->cursors[i]);
1210     }
1211 }
1212