1 #ifdef FOSSIL_ENABLE_JSON
2 /*
3 ** Copyright (c) 2011 D. Richard Hipp
4 **
5 ** This program is free software; you can redistribute it and/or
6 ** modify it under the terms of the Simplified BSD License (also
7 ** known as the "2-Clause License" or "FreeBSD License".)
8 **
9 ** This program is distributed in the hope that it will be useful,
10 ** but without any warranty; without even the implied warranty of
11 ** merchantability or fitness for a particular purpose.
12 **
13 ** Author contact information:
14 **   drh@hwaci.com
15 **   http://www.hwaci.com/drh/
16 **
17 */
18 #include "VERSION.h"
19 #include "config.h"
20 #include "json_dir.h"
21 
22 #if INTERFACE
23 #include "json_detail.h"
24 #endif
25 
26 static cson_value * json_page_dir_list();
27 /*
28 ** Mapping of /json/wiki/XXX commands/paths to callbacks.
29 */
30 #if 0 /* TODO: Not used? */
31 static const JsonPageDef JsonPageDefs_Dir[] = {
32 /* Last entry MUST have a NULL name. */
33 {NULL,NULL,0}
34 };
35 #endif
36 
37 #if 0 /* TODO: Not used? */
38 static char const * json_dir_path_extra(){
39   static char const * zP = NULL;
40   if( !zP ){
41     zP = g.zExtra;
42     while(zP && *zP && ('/'==*zP)){
43       ++zP;
44     }
45   }
46   return zP;
47 }
48 #endif
49 
50 /*
51 ** Impl of /json/dir. 98% of it was taken directly
52 ** from browse.c::page_dir()
53 */
json_page_dir_list()54 static cson_value * json_page_dir_list(){
55   cson_object * zPayload = NULL; /* return value */
56   cson_array * zEntries = NULL; /* accumulated list of entries. */
57   cson_object * zEntry = NULL;  /* a single dir/file entry. */
58   cson_array * keyStore = NULL; /* garbage collector for shared strings. */
59   cson_string * zKeyName = NULL;
60   cson_string * zKeySize = NULL;
61   cson_string * zKeyIsDir = NULL;
62   cson_string * zKeyUuid = NULL;
63   cson_string * zKeyTime = NULL;
64   cson_string * zKeyRaw = NULL;
65   char * zD = NULL;
66   char const * zDX = NULL;
67   int nD;
68   char * zUuid = NULL;
69   char const * zCI = NULL;
70   Manifest * pM = NULL;
71   Stmt q = empty_Stmt;
72   int rid = 0;
73   if( !g.perm.Read ){
74     json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions.");
75     return NULL;
76   }
77   zCI = json_find_option_cstr("checkin",NULL,"ci" );
78 
79   /* If a specific check-in is requested, fetch and parse it.  If the
80   ** specific check-in does not exist, clear zCI.  zCI==0 will cause all
81   ** files from all check-ins to be displayed.
82   */
83   if( zCI && *zCI ){
84     pM = manifest_get_by_name(zCI, &rid);
85     if( pM ){
86       zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
87     }else{
88       json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
89                    "Check-in name [%s] is unresolved.",
90                    zCI);
91       return NULL;
92     }
93   }
94 
95   /* Jump through some hoops to find the directory name... */
96   zDX = json_find_option_cstr("name",NULL,NULL);
97   if(!zDX && !g.isHTTP){
98     zDX = json_command_arg(g.json.dispatchDepth+1);
99   }
100   if(zDX && (!*zDX || (0==strcmp(zDX,"/")))){
101     zDX = NULL;
102   }
103   zD = zDX ? fossil_strdup(zDX) : NULL;
104   nD = zD ? strlen(zD)+1 : 0;
105   while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
106 
107   sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
108                           pathelementFunc, 0, 0);
109 
110   /* Compute the temporary table "localfiles" containing the names
111   ** of all files and subdirectories in the zD[] directory.
112   **
113   ** Subdirectory names begin with "/".  This causes them to sort
114   ** first and it also gives us an easy way to distinguish files
115   ** from directories in the loop that follows.
116   */
117 
118   if( zCI ){
119     Stmt ins;
120     ManifestFile *pFile;
121     ManifestFile *pPrev = 0;
122     int nPrev = 0;
123     int c;
124 
125     db_multi_exec(
126                   "CREATE TEMP TABLE json_dir_files("
127                   "  n UNIQUE NOT NULL," /* file name */
128                   "  fn UNIQUE NOT NULL," /* full file name */
129                   "  u DEFAULT NULL," /* file uuid */
130                   "  sz DEFAULT -1," /* file size */
131                   "  mtime DEFAULT NULL" /* file mtime in unix epoch format */
132                   ");"
133                   );
134 
135     db_prepare(&ins,
136                "INSERT OR IGNORE INTO json_dir_files (n,fn,u,sz,mtime) "
137                "SELECT"
138                "  pathelement(:path,0),"
139                "  CASE WHEN %Q IS NULL THEN '' ELSE %Q||'/' END ||:abspath,"
140                "  a.uuid,"
141                "  a.size,"
142                "  CAST(strftime('%%s',e.mtime) AS INTEGER) "
143                "FROM"
144                "  mlink m, "
145                "  event e,"
146                "  blob a,"
147                "  blob b "
148                "WHERE"
149                " e.objid=m.mid"
150                " AND a.rid=m.fid"/*FILE artifact*/
151                " AND b.rid=m.mid"/*CHECKIN artifact*/
152                " AND a.uuid=:uuid",
153                zD, zD
154                );
155     manifest_file_rewind(pM);
156     while( (pFile = manifest_file_next(pM,0))!=0 ){
157       if( nD>0
158         && ((pFile->zName[nD-1]!='/') || (0!=memcmp(pFile->zName, zD, nD-1)))
159       ){
160         continue;
161       }
162       /*printf("zD=%s, nD=%d, pFile->zName=%s\n", zD, nD, pFile->zName);*/
163       if( pPrev
164        && memcmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0
165        && (pFile->zName[nD+nPrev]==0 || pFile->zName[nD+nPrev]=='/')
166       ){
167         continue;
168       }
169       db_bind_text( &ins, ":path", &pFile->zName[nD] );
170       db_bind_text( &ins, ":abspath", &pFile->zName[nD] );
171       db_bind_text( &ins, ":uuid", pFile->zUuid );
172       db_step(&ins);
173       db_reset(&ins);
174       pPrev = pFile;
175       for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){}
176       if( c=='/' ) nPrev++;
177     }
178     db_finalize(&ins);
179   }else if( zD && *zD ){
180     db_multi_exec(
181       "CREATE TEMP VIEW json_dir_files AS"
182       " SELECT DISTINCT(pathelement(name,%d)) AS n,"
183       " %Q||'/'||name AS fn,"
184       " NULL AS u, NULL AS sz, NULL AS mtime"
185       " FROM filename"
186       "  WHERE name GLOB '%q/*'"
187       " GROUP BY n",
188       nD, zD, zD
189     );
190   }else{
191     db_multi_exec(
192       "CREATE TEMP VIEW json_dir_files"
193       " AS SELECT DISTINCT(pathelement(name,0)) AS n, NULL AS fn"
194       " FROM filename"
195     );
196   }
197 
198   if(zCI){
199     db_prepare( &q, "SELECT"
200                 "  n as name,"
201                 "  fn as fullname,"
202                 "  u as uuid,"
203                 "  sz as size,"
204                 "  mtime as mtime "
205                 "FROM json_dir_files ORDER BY n");
206   }else{/* UUIDs are all NULL. */
207     db_prepare( &q, "SELECT n, fn FROM json_dir_files ORDER BY n");
208   }
209 
210   zKeyName = cson_new_string("name",4);
211   zKeyUuid = cson_new_string("uuid",4);
212   zKeyIsDir = cson_new_string("isDir",5);
213   keyStore = cson_new_array();
214   cson_array_append( keyStore, cson_string_value(zKeyName) );
215   cson_array_append( keyStore, cson_string_value(zKeyUuid) );
216   cson_array_append( keyStore, cson_string_value(zKeyIsDir) );
217 
218   if( zCI ){
219     zKeySize = cson_new_string("size",4);
220     cson_array_append( keyStore, cson_string_value(zKeySize) );
221     zKeyTime = cson_new_string("timestamp",9);
222     cson_array_append( keyStore, cson_string_value(zKeyTime) );
223     zKeyRaw = cson_new_string("downloadPath",12);
224     cson_array_append( keyStore, cson_string_value(zKeyRaw) );
225   }
226   zPayload = cson_new_object();
227   cson_object_set_s( zPayload, zKeyName,
228                      json_new_string((zD&&*zD) ? zD : "/") );
229   if( zUuid ){
230     cson_object_set( zPayload, "checkin", json_new_string(zUuid) );
231   }
232 
233   while( (SQLITE_ROW==db_step(&q)) ){
234     cson_value * name = NULL;
235     char const * n = db_column_text(&q,0);
236     char const isDir = ('/'==*n);
237     zEntry = cson_new_object();
238     if(!zEntries){
239       zEntries = cson_new_array();
240       cson_object_set( zPayload, "entries", cson_array_value(zEntries) );
241     }
242     cson_array_append(zEntries, cson_object_value(zEntry) );
243     if(isDir){
244       name = json_new_string( n+1 );
245       cson_object_set_s(zEntry, zKeyIsDir, cson_value_true() );
246     } else{
247       name = json_new_string( n );
248     }
249     cson_object_set_s(zEntry, zKeyName, name );
250     if( zCI && !isDir){
251       /* Don't add the uuid/size for dir entries - that data refers to
252          one of the files in that directory :/. Entries with no
253          --checkin may refer to N versions, and therefore we cannot
254          associate a single size and uuid with them (and fetching all
255          would be overkill for most use cases).
256       */
257       char const * fullName = db_column_text(&q,1);
258       char const * u = db_column_text(&q,2);
259       sqlite_int64 const sz = db_column_int64(&q,3);
260       sqlite_int64 const ts = db_column_int64(&q,4);
261       cson_object_set_s(zEntry, zKeyUuid, json_new_string( u ) );
262       cson_object_set_s(zEntry, zKeySize,
263                         cson_value_new_integer( (cson_int_t)sz ));
264       cson_object_set_s(zEntry, zKeyTime,
265           cson_value_new_integer( (cson_int_t)ts ));
266       cson_object_set_s(zEntry, zKeyRaw,
267                         json_new_string_f("/raw/%T?name=%t",
268                                           fullName, u));
269     }
270   }
271   db_finalize(&q);
272   if(pM){
273     manifest_destroy(pM);
274   }
275   cson_free_array( keyStore );
276 
277   free( zUuid );
278   free( zD );
279   return cson_object_value(zPayload);
280 }
281 
282 /*
283 ** Implements the /json/dir family of pages/commands.
284 **
285 */
json_page_dir()286 cson_value * json_page_dir(){
287 #if 1
288   return json_page_dir_list();
289 #else
290   return json_page_dispatch_helper(&JsonPageDefs_Dir[0]);
291 #endif
292 }
293 
294 #endif /* FOSSIL_ENABLE_JSON */
295