/* copyright (c) 2003 Peter Rockai (aka Yenar Calentaure) covered by GNU LGPL implementation of DSN parser in C language created for libplaton on Nepto's request should pass all tests from PEAR for DB::ParseDSN to compare, run $ php4 db_parsedsn.phpt > php.out $ cc -DTEST parse_dsn.c -o parse_dsn && ./parse_dsn > c.out $ diff -u php.out c.out (note that DSN part after '?' is treated differently in C) TODO: implement parsing of '?port=123&...' part this way: parts that have counterpart in dsn_elements but would be NULL othwerwise, should be used and removed from query (un)known bugs -- valgrind reports: Conditional jump or move depends on uninitialised value(s) at 0x40166976: strlen (vg_clientfuncs.c:436) by 0x80486CE: urldecode (parse_dsn.c:73) by 0x8048A9E: parse_dsn (parse_dsn.c:160) by 0x804907C: test (parse_dsn.c:286) by 0x804933F: main (parse_dsn.c:326) by 0x4023B14E: __libc_start_main (in /lib/libc-2.2.5.so) by 0x8048510: (within /home/yenar/devel/utils/parseDSN/parse_dsn) sheeh... otherwise, code should be quite safe and leak-free */ #include #include #include #include static void dsn_free_ptr(ptr) char **ptr; { if (*ptr != NULL) { free((void **) *ptr); *ptr = NULL; } } void dsn_free(el) struct dsn_elements *el; { /* {{{ */ dsn_free_ptr(&(el->phptype)); dsn_free_ptr(&(el->dbsyntax)); dsn_free_ptr(&(el->username)); dsn_free_ptr(&(el->password)); dsn_free_ptr(&(el->protocol)); dsn_free_ptr(&(el->hostspec)); dsn_free_ptr(&(el->port)); dsn_free_ptr(&(el->socket)); dsn_free_ptr(&(el->database)); dsn_free_ptr(&(el->query)); } /* }}} */ #define DSN_LEN(s) (s == NULL ? 0 : strlen(s)) char * dsn_glue(el) const struct dsn_elements *el; { /* {{{ */ register int len; register char *dsn; /* How much memory we have to allocate for DSN string */ len = DSN_LEN(el->phptype) + DSN_LEN(el->dbsyntax) + DSN_LEN(el->username) + DSN_LEN(el->password) + DSN_LEN(el->protocol) + DSN_LEN(el->hostspec) + DSN_LEN(el->port) + DSN_LEN(el->socket) + DSN_LEN(el->database) + DSN_LEN(el->query); /* 30 characters is enough for element separators */ if ((dsn = (char *) malloc((len + 30) * sizeof(char))) == NULL) return NULL; /* failure */ /* Initialize DSN to empty string */ *dsn = '\0'; /* dbsyntax */ if (el->dbsyntax != NULL) { strcat(dsn, el->dbsyntax); } /* phptype */ if (el->phptype != NULL && strcmp(el->phptype, dsn)) { /* Who understands this will get one beer free on my own. :-) */ memmove(dsn + strlen(el->phptype) + 1, dsn, strlen(dsn) + 1); strcpy(dsn, el->phptype); dsn[strlen(dsn)] = '('; /* THIS WON'T WORK: strncat(dsn, "(", 1); */ strcat(dsn, ")"); } if (len - DSN_LEN(el->dbsyntax) - DSN_LEN(el->phptype) > 0) { strcat(dsn, "://"); } /* username */ if (el->username != NULL) { strcat(dsn, el->username); /* password */ if (el->password != NULL) { strcat(dsn, ":"); strcat(dsn, el->password); } } /* protocol */ if (el->protocol != NULL && ! strcmp(el->protocol, "tcp")) { if (0) { /* strict DSN case, part 1/2 */ strcat(dsn, "tcp("); } /* hostspec */ if (el->hostspec != NULL) { strcat(dsn, el->hostspec); } /* port */ if (el->port != NULL && strcmp(el->port, "") && atoi(el->port) > 0) { strcat(dsn, ":"); strcat(dsn, el->port); } if (0) { /* strict DSN case, part 2/2 */ strcat(dsn, ")"); } } else if (el->protocol != NULL && ! strcmp(el->protocol, "unix")) { strcat(dsn, "unix("); /* socket */ if (el->socket != NULL) { strcat(dsn, el->socket); } strcat(dsn, ")"); } else { /* hostspec */ if (el->hostspec != NULL) { strcat(dsn, el->hostspec); } } /* database */ if (el->database != NULL) { strcat(dsn, "/"); strcat(dsn, el->database); } /* query */ /* TODO: query handling imrpovements */ if (el->query != NULL) { strcat(dsn, "?"); strcat(dsn, el->query); } return dsn; /* success */ } /* }}} */ #define DSN_TRIM(str, nptr, n) do { back[n] = str[nptr - str]; str[nptr - str] = 0; } while (0) #define DSN_RST(str, nptr, n) (str[nptr - str] = back[n]) int dsn_parse(input_dsn, el) const char *input_dsn; struct dsn_elements *el; { /* {{{ */ char back[8]; char *dsn = strdup(input_dsn); char *c; char *tmp = NULL, *tmp2 = NULL; char *phptype = NULL; char *dbsyntax = NULL; char *username = NULL; char *password = NULL; char *protocol = NULL; char *proto_opts = NULL; char *hostspec = NULL; char *port = NULL; char *socket = NULL; char *database = NULL; char *query = NULL; if (dsn == NULL) goto ERR; if ((c = strstr(dsn, "://")) != NULL) { DSN_TRIM(dsn, c, 0); phptype = urldecode(dsn); DSN_RST(dsn, c, 0); } else { phptype = urldecode(dsn); c = 0; } if (phptype == NULL) goto ERR; if ((tmp = strchr(phptype, '(')) != NULL) { DSN_TRIM(phptype, tmp, 0); tmp++; if ((tmp2 = strchr(tmp, ')')) != NULL) { DSN_TRIM(tmp, tmp2, 0); } else { goto ERR; } dbsyntax = urldecode(tmp); } else { dbsyntax = strdup(phptype); } if (dbsyntax == NULL) goto ERR; if (c) { c += 3; /* printf ("c: %s\n", c); */ if ((tmp = strrchr(c, '@')) != NULL) { DSN_TRIM(c, tmp, 0); /* printf ("c: %s\n", c); */ username = urldecode(c); if (username == NULL) goto ERR; DSN_RST(c, tmp, 0); c = tmp + 1; if ((tmp2 = strchr(username, ':')) != NULL) { /* maybe strrchr? */ DSN_TRIM(username, tmp2, 0); password = urldecode(tmp2 + 1); if (password == NULL) goto ERR; } } if ((tmp = strchr(c, '(')) && (tmp != c) && (tmp2 = strchr(c, ')')) && (tmp2 > tmp)) { DSN_TRIM(c, tmp, 0); DSN_TRIM(c, tmp2, 1); protocol = urldecode(c); if (protocol == NULL) goto ERR; proto_opts = urldecode(tmp + 1); /* printf ("proto_opts: %s\n", proto_opts); */ c = strchr(tmp2 + 1, '/'); if (c == NULL) goto ERR; c++; DSN_RST(c, tmp2, 1); DSN_RST(c, tmp, 0); } else { /* old format */ if ((tmp = strchr(c, '+')) != NULL) { DSN_TRIM(c, tmp, 0); protocol = urldecode(c); if (protocol == NULL) goto ERR; c = tmp + 1; } if ((tmp = strchr(c, '/')) != NULL) { DSN_TRIM(c, tmp, 0); proto_opts = urldecode(c); if (proto_opts == NULL) goto ERR; /* printf ("proto_opts: %s\n", proto_opts); */ c = tmp + 1; } else { proto_opts = urldecode(c); if (proto_opts == NULL) goto ERR; c = 0; } } if (protocol && ! strlen(protocol)) { free(protocol); protocol = NULL; } if (protocol == NULL) protocol = strdup("tcp"); if (protocol == NULL) goto ERR; } if (proto_opts) { /*tmp = urldecode(proto_opts); if (! tmp) goto ERR; free(proto_opts); proto_opts = tmp;*/ if (! strcmp(protocol, "tcp")) { if ((tmp = strchr(proto_opts, ':')) != NULL) { DSN_TRIM(proto_opts, tmp, 0); hostspec = strdup(proto_opts); if (hostspec == NULL) goto ERR; port = strdup(tmp + 1); if (port == NULL) goto ERR; free(proto_opts); proto_opts = 0; } else { hostspec = proto_opts; proto_opts = 0; } } else if (! strcmp(protocol, "unix")) { socket = proto_opts; proto_opts = 0; } else { free(proto_opts); proto_opts = 0; } } if (c && strlen(c)) { /* path + query string */ if ((tmp = strchr(c, '?')) != NULL) { DSN_TRIM(c, tmp, 0); database = strdup(c); if (database == NULL) goto ERR; query = strdup(tmp + 1); if (query == NULL) goto ERR; c = 0; } else { database = strdup(c); if (database == NULL) goto ERR; c = 0; } } else if (c) { c = 0; } dsn_free_ptr(&dsn); /* XXX it might be better to `el -> X = mystrdup(X); sfree(X);' where mystrdup returns NULL if X == NULL */ el->phptype = phptype; el->dbsyntax = dbsyntax; el->username = username; el->password = password; el->protocol = protocol; el->hostspec = hostspec; el->port = port; el->socket = socket; el->database = database; el->query = query; return 1; /* success */ ERR: dsn_free_ptr(&dsn); dsn_free_ptr(&phptype); dsn_free_ptr(&dbsyntax); dsn_free_ptr(&username); dsn_free_ptr(&password); dsn_free_ptr(&protocol); dsn_free_ptr(&hostspec); dsn_free_ptr(&port); dsn_free_ptr(&socket); dsn_free_ptr(&database); return 0; /* memory allocation failure */ } /* }}} */ #if defined(SELF) || defined(SELFTEST) || defined(SELF_DSN) /* {{{ */ #include int test(const char *dsn) { register char *dsn2; struct dsn_elements e; if (! dsn_parse(dsn, &e)) { printf("Memory allocation error when parsing DSN string.\n"); return 0; /* failure */ } if ((dsn2 = dsn_glue(&e)) == NULL) { printf("Memory allocation error when compiling DSN string.\n"); return 0; } if (strcmp(dsn, dsn2)) { printf("DSN strings do not match.\n"); printf(" original DSN: %s\n", dsn); printf(" compiled DSN: %s\n", dsn2); } free(dsn2); printf ("DSN: %s\nArray\n(\n", dsn); printf (" [phptype] => %s\n", e.phptype ? e.phptype : ""); printf (" [dbsyntax] => %s\n", e.dbsyntax ? e.dbsyntax : ""); printf (" [username] => %s\n", e.username ? e.username : ""); printf (" [password] => %s\n", e.password ? e.password : ""); printf (" [protocol] => %s\n", e.protocol ? e.protocol : ""); printf (" [hostspec] => %s\n", e.hostspec ? e.hostspec : ""); printf (" [port] => %s\n", e.port ? e.port : ""); printf (" [socket] => %s\n", e.socket ? e.socket : ""); printf (" [database] => %s\n", e.database ? e.database : ""); if (e.query != NULL) { printf (" [query] => %s\n", e.query); } printf (")\n"); dsn_free(&e); return 1; /* success */ } int main(void) { /* borrowed from db_parsedsn.phpt */ test("mysql"); test("odbc(mssql)"); test("mysql://localhost"); test("mysql://remote.host.com/db"); test("oci8://system:manager@"); test("oci8://user:pass@tns-name"); test("odbc(solid)://foo:bar@tcp+localhost+1313"); /* deprecated */ test("pgsql://user@unix+localhost/pear"); /* deprecated */ test("ibase://user%40domain:password@host"); test("ibase://user@domain:pass@word@/database"); /* also supported */ test("ifx://user@domain:pass@word@host.com//usr/db/general.db"); test("ifx://remote.host.com/c:\\windows\\my.db"); test("oci8://SHOOTOUT:******@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.101.161)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=TIS)))"); test("odbc(solid)://foo:bar@localhost:1313"); /* new formats */ test("pgsql://user@unix()/pear"); test("mysql://user@unix(/path/to/socket)/pear"); test("pgsql://user@tcp()/pear"); test("pgsql://user@tcp(somehost)/pear"); test("pgsql://user:pass@word@tcp(somehost:7777)/pear"); /* special backend options */ test("ibase://user:pass@localhost//var/lib/dbase.dbf?role=foo"); test("dbase://@/?role=foo&dialect=bar"); return 0; } #endif /* #if defined(SELF) || defined(SELFTEST) || defined(SELF_DSN) */ /* }}} */ /* Modeline for ViM {{{ * vim: set ts=4: * vim600: fdm=marker fdl=0 fdc=3: * }}} */