// view.c — show one 16x16 graphic and a simple editor underneath (no CSS) // Build: // sudo gcc -o /ggs/cgi/view.cgi /ggs/src/view.c -lmysqlclient // sudo chmod 755 /ggs/cgi/view.cgi // Run: // /cgi-bin/view.cgi?graphicID=553 (also accepts ?id=553; optional ?size=3) #include #include #include #include #include #include "graphic_render.h" #include "graphic_render.c" // provides g(int graphicID, int cellSize) // ===== DB CONFIG (same as Loom) ===== #define DB_HOST "localhost" #define DB_USER "shridhar" #define DB_PASS "ramarama1!" #define DB_NAME "kamatpharma" #define DB_PORT 3306 // ===== tiny HTML ===== static void html_header(const char* title){ printf("Content-Type: text/html\r\n\r\n"); printf(""); printf("%s\n", title?title:"Graphic Editor"); } static void html_footer(void){ printf("\n"); } // ===== cookie gate ===== static const char* get_user_cookie(void){ const char* c = getenv("HTTP_COOKIE"); if(!c) return NULL; const char* t = strstr(c, "ggs_user="); if(!t) return NULL; t += (int)strlen("ggs_user="); static char user[64]; size_t i=0; while(*t && *t!=';' && i"); } // ===== QS/POST helpers ===== static const char* qs_get(const char* key){ const char* qs = getenv("QUERY_STRING"); if(!qs) return NULL; size_t klen = strlen(key); static char buf[256]; const char* p = qs; while(p && *p){ const char* amp = strchr(p,'&'); if(!amp) amp = p + strlen(p); const char* eq = strchr(p,'='); if(eq && (size_t)(eq-p)==klen && strncmp(p,key,klen)==0){ size_t vlen = (size_t)(amp - (eq+1)); if(vlen >= sizeof(buf)) vlen = sizeof(buf)-1; memcpy(buf, eq+1, vlen); buf[vlen]='\0'; return buf; } p = (*amp=='\0') ? NULL : amp+1; } return NULL; } static void urldecode(char *s){ char *src=s,*dst=s; while(*src){ if(*src=='+'){*dst++=' ';src++;} else if(*src=='%' && isxdigit((unsigned char)src[1]) && isxdigit((unsigned char)src[2])){ char hex[3]={src[1],src[2],0}; *dst++=(char)strtol(hex,NULL,16); src+=3; } else { *dst++=*src++; } } *dst=0; } static char* read_body(size_t* out_len){ const char* cl=getenv("CONTENT_LENGTH"); if(!cl||!*cl){*out_len=0;return NULL;} long n=strtol(cl,NULL,10); if(n<=0||n>65536){*out_len=0;return NULL;} char* b=(char*)malloc((size_t)n+1); if(!b) return NULL; size_t rd=fread(b,1,(size_t)n,stdin); b[rd]=0; *out_len=rd; return b; } static char* form_get(const char* body,const char* key){ if(!body) return NULL; size_t klen=strlen(key); const char* p=body; while(p && *p){ const char* amp=strchr(p,'&'); size_t seglen= amp? (size_t)(amp-p) : strlen(p); const char* eq = memchr(p,'=',seglen); if(eq && (size_t)(eq-p)==klen && strncmp(p,key,klen)==0){ size_t vlen=seglen-klen-1; char* v=(char*)malloc(vlen+1); if(!v) return NULL; memcpy(v,eq+1,vlen); v[vlen]=0; urldecode(v); return v; } p= amp? amp+1 : NULL; } return NULL; } // ===== DB ===== static MYSQL* db_connect(void){ MYSQL* m=mysql_init(NULL); if(!m) return NULL; if(!mysql_real_connect(m,DB_HOST,DB_USER,DB_PASS,DB_NAME,DB_PORT,NULL,0)) return NULL; mysql_set_character_set(m,"utf8mb4"); return m; } // Build a SELECT list for cells + colors static void build_select_cols(char* dst,size_t cap){ size_t off=0; for(int i=1;i<=256;i++){ off += snprintf(dst+off, cap-off, "cell_%d%s", i, (i<256?", ":"")); if(off>=cap) break; } for(int i=0;i<10;i++){ off += snprintf(dst+off, cap-off, "%scolor%d", (i==0? ", " : ", "), i); if(off>=cap) break; } } // Fetch grid into arrays (cells[256], colors[10]); returns 1 on success static int fetch_grid(MYSQL* db, long id, int cells[256], char colors[10][32]){ char cols[8192]; build_select_cols(cols,sizeof(cols)); char q[8400]; snprintf(q,sizeof(q),"SELECT %s FROM grid_data_16x16 WHERE id=%ld", cols, id); if(mysql_query(db,q)!=0) return 0; MYSQL_RES* res=mysql_store_result(db); if(!res) return 0; MYSQL_ROW row=mysql_fetch_row(res); if(!row){ mysql_free_result(res); return 0; } for(int i=0;i<256;i++) cells[i] = row[i]? atoi(row[i]) : 0; for(int j=0;j<10;j++){ const char* r = row[256+j]? row[256+j] : ""; snprintf(colors[j], sizeof(colors[j]), "%s", r); } mysql_free_result(res); return 1; } // Update current id with provided arrays static int update_grid(MYSQL* db, long id, int cells[256], char colors[10][32]){ char* q = (char*)malloc(32768); if(!q) return 0; size_t off=0; off += snprintf(q+off, 32768-off, "UPDATE grid_data_16x16 SET "); for(int i=1;i<=256;i++){ off += snprintf(q+off, 32768-off, "cell_%d=%d%s", i, cells[i-1], (i<256?", ":"")); if(off>=32700) break; } for(int j=0;j<10;j++){ char esc[128]; size_t len=strlen(colors[j]); if(len>60) len=60; mysql_real_escape_string(db, esc, colors[j], (unsigned long)len); off += snprintf(q+off, 32768-off, "%scolor%d='%s'", (j==0? ", " : ", "), j, esc); if(off>=32700) break; } off += snprintf(q+off, 32768-off, " WHERE id=%ld", id); int rc = mysql_query(db,q)==0; free(q); return rc; } // Duplicate current row -> new row id (dynamic buffer, no truncation) static long duplicate_grid(MYSQL* db, long src_id){ char cols[8192]; build_select_cols(cols, sizeof(cols)); size_t cols_len = strlen(cols); const char *tmpl = "INSERT INTO grid_data_16x16 (%s) SELECT %s FROM grid_data_16x16 WHERE id=%ld"; size_t needed = strlen(tmpl) + (2 * cols_len) + 32; char *q = (char*)malloc(needed); if(!q) return 0; int n = snprintf(q, needed, tmpl, cols, cols, src_id); int ok = (n > 0 && (size_t)n < needed && mysql_query(db, q) == 0); free(q); if(!ok) return 0; return (long)mysql_insert_id(db); } // Import from another id into current id static int import_grid(MYSQL* db, long dst_id, long src_id){ int cells[256]; char colors[10][32]; if(!fetch_grid(db, src_id, cells, colors)) return 0; return update_grid(db, dst_id, cells, colors); } // ===== render editor form (no CSS) ===== static void print_editor(MYSQL* db, long id){ int cells[256]; char colors[10][32]; if(!fetch_grid(db, id, cells, colors)){ printf("

No data found for ID %ld.

\n", id); return; } printf("

Back to Loom | Logout

\n"); printf("

Editor for ID %ld

\n", id); // Editor form (256 text inputs + 10 color strings) printf("
\n", id); // 16x16 table of inputs printf("\n"); for(int r=0;r<16;r++){ printf(""); for(int c=0;c<16;c++){ int idx = r*16 + c; // 0..255 printf("", idx+1, cells[idx]); } printf("\n"); } printf("
\n"); // Colors printf("

\n"); for(int j=0;j<10;j++){ printf("Color %d:   ", j, j, colors[j]); } printf("

\n"); // Actions printf("

\n"); printf(" "); printf(" "); printf(" "); printf("

\n"); // Import printf("

Import data from ID: "); printf("

\n"); printf("
\n"); } // ===== main ===== int main(void){ if(!get_user_cookie()){ redirect_to_login(); return 0; } // id from ?graphicID= or ?id= const char* sid = qs_get("graphicID"); if(!sid) sid = qs_get("id"); long id = (sid && *sid && isdigit((unsigned char)*sid)) ? strtol(sid,NULL,10) : 1; // optional ?size= (default 3, clamp 1..10) const char* ssz = qs_get("size"); long size = (ssz && *ssz && isdigit((unsigned char)*ssz)) ? strtol(ssz,NULL,10) : 3; if(size < 1) size = 1; if(size > 10) size = 10; MYSQL* db = db_connect(); // Handle POST actions const char* method = getenv("REQUEST_METHOD"); if(method && strcmp(method,"POST")==0){ size_t blen=0; char* body=read_body(&blen); char* action = form_get(body,"action"); if(action && strcmp(action,"Update")==0){ int cells[256]; char colors[10][32]; for(int i=1;i<=256;i++){ char key[16]; snprintf(key,sizeof(key),"cell_%d",i); char* v = form_get(body,key); cells[i-1] = v && *v ? atoi(v) : 0; free(v); } for(int j=0;j<10;j++){ char key[16]; snprintf(key,sizeof(key),"color%d",j); char* v = form_get(body,key); snprintf(colors[j], sizeof(colors[j]), "%s", v?v:""); free(v); } if(db && update_grid(db, id, cells, colors)){ printf("Status: 302 Found\r\nLocation: /cgi-bin/view.cgi?id=%ld\r\nContent-Type: text/html\r\n\r\n", id); printf("", id); free(action); free(body); if(db) mysql_close(db); return 0; } else { html_header("Graphic Editor"); printf("

Update failed.

\n"); print_editor(db, id); html_footer(); free(action); free(body); if(db) mysql_close(db); return 0; } } else if(action && strcmp(action,"Duplicate")==0){ long new_id = db ? duplicate_grid(db, id) : 0; html_header("Graphic Editor"); if(new_id>0){ printf("

Duplicate created: open %ld

\n", new_id, new_id); } else { printf("

Duplicate failed.

\n"); } print_editor(db, id); html_footer(); free(action); free(body); if(db) mysql_close(db); return 0; } else if(action && strcmp(action,"Delete")==0){ if(db){ char q[128]; snprintf(q,sizeof(q),"DELETE FROM grid_data_16x16 WHERE id=%ld", id); int ok = (mysql_query(db,q)==0); html_header("Graphic Editor"); if(ok) printf("

Grid %ld deleted. Return to Loom

\n", id); else printf("

Delete failed.

\n"); html_footer(); free(action); free(body); if(db) mysql_close(db); return 0; } } else if(action && strcmp(action,"Import")==0){ char* imp = form_get(body,"import_id"); long src = imp && *imp ? strtol(imp,NULL,10) : 0; html_header("Graphic Editor"); if(db && src>0 && import_grid(db, id, src)){ printf("

Imported data from %ld into %ld.

\n", src, id); } else { printf("

Import failed.

\n"); } print_editor(db, id); html_footer(); free(imp); free(action); free(body); if(db) mysql_close(db); return 0; } // default POST fallthrough: just show editor free(action); free(body); } // GET (or fallthrough): top graphic + editor html_header("Graphic Editor"); printf("

Graphic %ld

\n", id); g((int)id, (int)size); // render graphic (top-left) printf("

Back to Loom | Logout

\n"); printf("
\n"); if(db) print_editor(db, id); else printf("

DB connection failed.

\n"); html_footer(); if(db) mysql_close(db); return 0; }