diff --git a/src/rest_vol.c b/src/rest_vol.c index 1c3a8fb0..a23c409a 100644 --- a/src/rest_vol.c +++ b/src/rest_vol.c @@ -141,6 +141,9 @@ const char *attributes_keys[] = {"attributes", (const char *)0}; /* JSON keys to retrieve allocated size */ const char *allocated_size_keys[] = {"allocated_size", (const char *)0}; +/* JSON keys to retrieve objects accessed through path(s) */ +const char *h5paths_keys[] = {"h5paths", (const char *)0}; + /* Default size for the buffer to allocate during base64-encoding if the caller * of RV_base64_encode supplies a 0-sized buffer. */ @@ -1681,10 +1684,12 @@ H5_rest_url_encode_path(const char *_path) herr_t RV_parse_object_class(char *HTTP_response, const void *callback_data_in, void *callback_data_out) { - yajl_val parse_tree = NULL, key_obj; + yajl_val parse_tree = NULL, key_obj = NULL, class_obj = NULL, target_tree = NULL; char *parsed_object_string; - H5I_type_t *object_type = (H5I_type_t *)callback_data_out; - herr_t ret_value = SUCCEED; + const char *object_class_keys[] = {"class", (const char *)0}; + const char *path_name = NULL; + H5I_type_t *object_type = (H5I_type_t *)callback_data_out; + herr_t ret_value = SUCCEED; #ifdef RV_CONNECTOR_DEBUG printf("-> Retrieving object's class from server's HTTP response\n\n"); @@ -1698,9 +1703,25 @@ RV_parse_object_class(char *HTTP_response, const void *callback_data_in, void *c if (NULL == (parse_tree = yajl_tree_parse(HTTP_response, NULL, 0))) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsing JSON failed"); - const char *object_class_keys[] = {"class", (const char *)0}; + target_tree = parse_tree; + + /* If the response contains 'h5paths', + * it may describe multiple objects. Needs to be unwrapped first. */ + if (NULL != yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object)) { + if (NULL == (key_obj = yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "can't parse h5paths object"); + + /* Access the first object under h5paths */ + if (NULL == (path_name = key_obj->u.object.keys[0])) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsed path name was NULL"); + + const char *path_keys[] = {path_name, (const char *)0}; - if (NULL == (key_obj = yajl_tree_get(parse_tree, object_class_keys, yajl_t_string))) { + if (NULL == (target_tree = yajl_tree_get(key_obj, path_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "unable to parse object under path key"); + } + + if (NULL == (key_obj = yajl_tree_get(target_tree, object_class_keys, yajl_t_string))) { FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "couldn't parse object class"); } @@ -1852,10 +1873,11 @@ RV_parse_response(char *HTTP_response, const void *callback_data_in, void *callb herr_t RV_copy_object_URI_callback(char *HTTP_response, const void *callback_data_in, void *callback_data_out) { - yajl_val parse_tree = NULL, key_obj; - char *parsed_string; - char *buf_out = (char *)callback_data_out; - herr_t ret_value = SUCCEED; + yajl_val parse_tree = NULL, key_obj = NULL, single_obj = NULL, target_tree = NULL; + char *parsed_string; + char *buf_out = (char *)callback_data_out; + const char *path_name = NULL; + herr_t ret_value = SUCCEED; #ifdef RV_CONNECTOR_DEBUG printf("-> Retrieving object's URI from server's HTTP response\n\n"); @@ -1869,11 +1891,29 @@ RV_copy_object_URI_callback(char *HTTP_response, const void *callback_data_in, v if (NULL == (parse_tree = yajl_tree_parse(HTTP_response, NULL, 0))) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsing JSON failed"); + target_tree = parse_tree; + + /* If the response contains 'h5paths', + * it may describe multiple objects. Needs to be unwrapped first. */ + if (NULL != yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object)) { + if (NULL == (target_tree = yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "can't parse h5paths object"); + + /* Access the first object under h5paths */ + if (NULL == (path_name = target_tree->u.object.keys[0])) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsed path name was NULL"); + + const char *path_keys[] = {path_name, (const char *)0}; + + if (NULL == (target_tree = yajl_tree_get(target_tree, path_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "unable to parse object under path key"); + } + /* To handle the awkward case of soft and external links, which do not return an "ID", * first check for the link class field and short circuit if it is found to be * equal to "H5L_TYPE_SOFT" */ - if (NULL != (key_obj = yajl_tree_get(parse_tree, link_class_keys, yajl_t_string))) { + if (NULL != (key_obj = yajl_tree_get(target_tree, link_class_keys, yajl_t_string))) { char *link_type; if (NULL == (link_type = YAJL_GET_STRING(key_obj))) @@ -1887,7 +1927,7 @@ RV_copy_object_URI_callback(char *HTTP_response, const void *callback_data_in, v /* First attempt to retrieve the URI of the object by using the JSON key sequence * "link" -> "id", which is returned when making a GET Link request. */ - key_obj = yajl_tree_get(parse_tree, link_id_keys, yajl_t_string); + key_obj = yajl_tree_get(target_tree, link_id_keys, yajl_t_string); if (key_obj) { if (!YAJL_IS_STRING(key_obj)) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_BADVALUE, FAIL, "returned URI is not a string"); @@ -1909,7 +1949,7 @@ RV_copy_object_URI_callback(char *HTTP_response, const void *callback_data_in, v * for just the JSON key "id", which would generally correspond to trying to * retrieve the URI of a newly-created or opened object that isn't a file. */ - key_obj = yajl_tree_get(parse_tree, object_id_keys, yajl_t_string); + key_obj = yajl_tree_get(target_tree, object_id_keys, yajl_t_string); if (key_obj) { if (!YAJL_IS_STRING(key_obj)) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_BADVALUE, FAIL, "returned URI is not a string"); @@ -1932,7 +1972,7 @@ RV_copy_object_URI_callback(char *HTTP_response, const void *callback_data_in, v * retrieve the URI of a newly-created or opened file, or to a search for * the root group of a file. */ - if (NULL == (key_obj = yajl_tree_get(parse_tree, root_id_keys, yajl_t_string))) + if (NULL == (key_obj = yajl_tree_get(target_tree, root_id_keys, yajl_t_string))) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_CANTGET, FAIL, "retrieval of URI failed"); if (!YAJL_IS_STRING(key_obj)) @@ -2005,8 +2045,9 @@ RV_find_object_by_path(RV_object_t *parent_obj, const char *obj_path, H5I_type_t herr_t (*obj_found_callback)(char *, const void *, void *), void *callback_data_in, void *callback_data_out) { - RV_object_t *external_file = NULL; - hbool_t is_relative_path = FALSE; + RV_object_t *external_file = NULL; + hbool_t is_relative_path = FALSE; + size_t escaped_path_size = 0; H5L_info2_t link_info; char *url_encoded_link_name = NULL; char *path_dirname = NULL; @@ -2015,6 +2056,8 @@ RV_find_object_by_path(RV_object_t *parent_obj, const char *obj_path, H5I_type_t const char *ext_filename = NULL; const char *ext_obj_path = NULL; char request_endpoint[URL_MAX_LENGTH]; + char *escaped_obj_path = NULL; + char *request_body = NULL; long http_response; int url_len = 0; server_api_version version; @@ -2090,15 +2133,33 @@ RV_find_object_by_path(RV_object_t *parent_obj, const char *obj_path, H5I_type_t if (SERVER_VERSION_MATCHES_OR_EXCEEDS(version, 0, 8, 0)) { /* Set up request URL to make server do repeated traversal of symbolic links */ + if (SERVER_VERSION_SUPPORTS_LONG_NAMES(version)) { + /* Send object path in body of POST request */ + if (RV_JSON_escape_string(obj_path, escaped_obj_path, &escaped_path_size) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_BADVALUE, FAIL, "can't get size of escaped object path"); - if (NULL == (url_encoded_path_name = H5_rest_url_encode_path(obj_path))) - FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTENCODE, FAIL, "can't URL-encode object path"); + if ((escaped_obj_path = RV_malloc(escaped_path_size)) == NULL) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, "can't allocate space for escaped path"); - if ((url_len = snprintf(request_endpoint, URL_MAX_LENGTH, - "/?h5path=%s%s%s&follow_soft_links=1&follow_external_links=1", - url_encoded_path_name, is_relative_path ? "&parent_id=" : "", - is_relative_path ? parent_obj->URI : "")) < 0) - FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + if (RV_JSON_escape_string(obj_path, escaped_obj_path, &escaped_path_size) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_BADVALUE, FAIL, "can't escape object path"); + + if ((url_len = snprintf( + request_endpoint, URL_MAX_LENGTH, "/?follow_soft_links=1&follow_external_links=1%s%s", + is_relative_path ? "&parent_id=" : "", is_relative_path ? parent_obj->URI : "")) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + } + else { + /* Send object path in URL of GET request */ + if (NULL == (url_encoded_path_name = H5_rest_url_encode_path(obj_path))) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTENCODE, FAIL, "can't URL-encode object path"); + + if ((url_len = snprintf(request_endpoint, URL_MAX_LENGTH, + "/?h5path=%s%s%s&follow_soft_links=1&follow_external_links=1", + url_encoded_path_name, is_relative_path ? "&parent_id=" : "", + is_relative_path ? parent_obj->URI : "")) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + } } else { /* Server will not traverse symbolic links for us */ @@ -2195,8 +2256,38 @@ RV_find_object_by_path(RV_object_t *parent_obj, const char *obj_path, H5I_type_t if (url_len >= URL_MAX_LENGTH) FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "Request URL size exceeded maximum URL size"); - http_response = RV_curl_get(curl, &parent_obj->domain->u.file.server_info, request_endpoint, - parent_obj->domain->u.file.filepath_name, CONTENT_TYPE_JSON); + /* Make POST request that supports long paths if server supports it. + Otherwise, make GET request */ + if (SERVER_VERSION_SUPPORTS_LONG_NAMES(version)) { + const char *fmt_string = "{\"h5paths\": [\"%s\"]}"; + size_t request_body_len = 0; + int bytes_printed = 0; + + request_body_len = strlen(fmt_string) + strlen(escaped_obj_path) + 1; + + if ((request_body = RV_malloc(request_body_len)) == NULL) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, "can't allocate space for path request body"); + + if ((bytes_printed = snprintf(request_body, request_body_len, fmt_string, escaped_obj_path)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + + if (bytes_printed >= request_body_len) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, + "request body size exceeded allocated buffer size"); + + if ((http_response = RV_curl_post(curl, &parent_obj->domain->u.file.server_info, request_endpoint, + parent_obj->domain->u.file.filepath_name, request_body, + (size_t)bytes_printed, CONTENT_TYPE_JSON)) < 0) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_CANTGET, FAIL, + "internal failure while making POST request to server"); + } + else { + + if ((http_response = RV_curl_get(curl, &parent_obj->domain->u.file.server_info, request_endpoint, + parent_obj->domain->u.file.filepath_name, CONTENT_TYPE_JSON)) < 0) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_CANTGET, FAIL, + "internal failure while making GET request to server"); + } if (HTTP_SUCCESS(http_response)) ret_value = TRUE; @@ -2304,6 +2395,14 @@ RV_find_object_by_path(RV_object_t *parent_obj, const char *obj_path, H5I_type_t curl_free(url_encoded_link_name); if (path_dirname) RV_free(path_dirname); + if (escaped_obj_path) + RV_free(escaped_obj_path); + if (request_body) + RV_free(request_body); + + /* Necessary to prevent curl from potentially accessing freed buffers in subsequent calls */ + if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_POST, 0)) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTSET, FAIL, "can't unset cURL HTTP POST request: %s", curl_err_buf); if (external_file) if (RV_file_close(external_file, H5P_DEFAULT, NULL) < 0) @@ -2387,11 +2486,12 @@ RV_parse_creation_properties_callback(yajl_val parse_tree, char **GCPL_buf_out) herr_t RV_copy_object_loc_info_callback(char *HTTP_response, const void *callback_data_in, void *callback_data_out) { - yajl_val parse_tree = NULL, key_obj; - char *parsed_string; - loc_info *loc_info_out = (loc_info *)callback_data_out; - const server_info_t *server_info = (const server_info_t *)callback_data_in; - herr_t ret_value = SUCCEED; + yajl_val parse_tree = NULL, key_obj = NULL, target_tree = NULL; + char *parsed_string = NULL; + const char *path_name = NULL; + loc_info *loc_info_out = (loc_info *)callback_data_out; + const server_info_t *server_info = (const server_info_t *)callback_data_in; + herr_t ret_value = SUCCEED; char *GCPL_buf = NULL; @@ -2414,10 +2514,27 @@ RV_copy_object_loc_info_callback(char *HTTP_response, const void *callback_data_ if (NULL == (parse_tree = yajl_tree_parse(HTTP_response, NULL, 0))) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsing JSON failed"); + target_tree = parse_tree; + + /* If the response contains 'h5paths', + * it may describe multiple objects. Needs to be unwrapped first. */ + if (NULL != yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object)) { + if (NULL == (target_tree = yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "can't parse h5paths object"); + + /* Access the first object under h5paths */ + if (NULL == (path_name = target_tree->u.object.keys[0])) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsed path name was NULL"); + + const char *path_keys[] = {path_name, (const char *)0}; + + if (NULL == (target_tree = yajl_tree_get(target_tree, path_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "unable to parse object under path key"); + } /* Not all objects have a creationProperties field, so fail this gracefully */ H5E_BEGIN_TRY { - RV_parse_creation_properties_callback(parse_tree, &GCPL_buf); + RV_parse_creation_properties_callback(target_tree, &GCPL_buf); } H5E_END_TRY @@ -2426,7 +2543,7 @@ RV_copy_object_loc_info_callback(char *HTTP_response, const void *callback_data_ } /* Retrieve domain path */ - if (NULL == (key_obj = yajl_tree_get(parse_tree, domain_keys, yajl_t_string))) + if (NULL == (key_obj = yajl_tree_get(target_tree, domain_keys, yajl_t_string))) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_BADVALUE, FAIL, "failed to parse domain"); if (!YAJL_IS_STRING(key_obj)) @@ -2436,7 +2553,7 @@ RV_copy_object_loc_info_callback(char *HTTP_response, const void *callback_data_ FUNC_GOTO_ERROR(H5E_OBJECT, H5E_BADVALUE, FAIL, "domain was NULL"); /* Retrieve domain id */ - if (NULL == (key_obj = yajl_tree_get(parse_tree, root_id_keys, yajl_t_string))) + if (NULL == (key_obj = yajl_tree_get(target_tree, root_id_keys, yajl_t_string))) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_BADVALUE, FAIL, "failed to parse domain id"); if (!YAJL_IS_STRING(key_obj)) @@ -2733,12 +2850,13 @@ RV_copy_attribute_name_by_index(char *HTTP_response, const void *callback_data_i hid_t RV_parse_dataspace(char *space) { - yajl_val parse_tree = NULL, key_obj = NULL; - hsize_t *space_dims = NULL; - hsize_t *space_maxdims = NULL; - hid_t dataspace = FAIL; - char *dataspace_type = NULL; - hid_t ret_value = FAIL; + yajl_val parse_tree = NULL, key_obj = NULL, target_tree = NULL; + hsize_t *space_dims = NULL; + hsize_t *space_maxdims = NULL; + hid_t dataspace = FAIL; + char *dataspace_type = NULL; + const char *path_name = NULL; + hid_t ret_value = FAIL; #ifdef RV_CONNECTOR_DEBUG printf("-> Parsing dataspace from HTTP response\n\n"); @@ -2750,8 +2868,28 @@ RV_parse_dataspace(char *space) if (NULL == (parse_tree = yajl_tree_parse(space, NULL, 0))) FUNC_GOTO_ERROR(H5E_DATASPACE, H5E_PARSEERROR, FAIL, "JSON parse tree creation failed"); + target_tree = parse_tree; + + /* If the response contains 'h5paths', + * it may describe multiple objects. Needs to be unwrapped first. */ + if (NULL != yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object)) { + if (NULL == (key_obj = yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "can't parse h5paths object"); + + /* Access the first object under h5paths */ + if (NULL == (path_name = key_obj->u.object.keys[0])) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsed path name was NULL"); + + const char *path_keys[] = {path_name, (const char *)0}; + + if (NULL == (key_obj = yajl_tree_get(key_obj, path_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "unable to parse object under path key"); + + target_tree = key_obj; + } + /* Retrieve the Dataspace type */ - if (NULL == (key_obj = yajl_tree_get(parse_tree, dataspace_class_keys, yajl_t_string))) + if (NULL == (key_obj = yajl_tree_get(target_tree, dataspace_class_keys, yajl_t_string))) FUNC_GOTO_ERROR(H5E_DATASPACE, H5E_PARSEERROR, FAIL, "can't retrieve dataspace class"); if (NULL == (dataspace_type = YAJL_GET_STRING(key_obj))) @@ -2783,13 +2921,13 @@ RV_parse_dataspace(char *space) printf("-> SIMPLE dataspace\n\n"); #endif - if (NULL == (dims_obj = yajl_tree_get(parse_tree, dataspace_dims_keys, yajl_t_array))) + if (NULL == (dims_obj = yajl_tree_get(target_tree, dataspace_dims_keys, yajl_t_array))) FUNC_GOTO_ERROR(H5E_DATASPACE, H5E_PARSEERROR, FAIL, "can't retrieve dataspace dims"); /* Check to see whether the maximum dimension size is specified as part of the * dataspace's JSON representation */ - if (NULL == (maxdims_obj = yajl_tree_get(parse_tree, dataspace_max_dims_keys, yajl_t_array))) + if (NULL == (maxdims_obj = yajl_tree_get(target_tree, dataspace_max_dims_keys, yajl_t_array))) maxdims_specified = FALSE; if (!YAJL_GET_ARRAY(dims_obj)->len) @@ -3835,6 +3973,9 @@ RV_JSON_escape_string(const char *in, char *out, size_t *out_size) char *out_ptr = NULL; char escape_characters[NUM_JSON_ESCAPE_CHARS] = {'\b', '\f', '\n', '\r', '\t', '\"', '\\'}; + if (!in) + FUNC_GOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "cannot JSON escape NULL string"); + if (out == NULL) { /* Determine necessary buffer size */ *out_size = in_size + 1; diff --git a/src/rest_vol.h b/src/rest_vol.h index 2407b30f..d7ac9832 100644 --- a/src/rest_vol.h +++ b/src/rest_vol.h @@ -410,6 +410,12 @@ extern const char *link_creation_time_keys[]; */ extern const char *link_collection_keys2[]; +/* JSON keys to retrieve objects accessed through path(s) */ +extern const char *h5paths_keys[]; + +/* JSON keys to retrieve the path to a domain in HSDS */ +extern const char *domain_keys[]; + /* A global struct containing the buffer which cURL will write its * responses out to after making a call to the server. The buffer * in this struct is allocated upon connector initialization and is @@ -803,6 +809,8 @@ herr_t RV_JSON_escape_string(const char *in, char *out, size_t *out_size); #define SERVER_VERSION_SUPPORTS_FIXED_LENGTH_UTF8(version) \ (SERVER_VERSION_MATCHES_OR_EXCEEDS(version, 0, 8, 5)) +#define SERVER_VERSION_SUPPORTS_LONG_NAMES(version) (SERVER_VERSION_MATCHES_OR_EXCEEDS(version, 0, 8, 6)) + #ifdef __cplusplus } #endif diff --git a/src/rest_vol_dataset.c b/src/rest_vol_dataset.c index 4dc13371..dfaa66aa 100644 --- a/src/rest_vol_dataset.c +++ b/src/rest_vol_dataset.c @@ -212,7 +212,7 @@ RV_dataset_create(void *obj, const H5VL_loc_params_t *loc_params, const char *na "dataset create URL size exceeded maximum URL size"); #ifdef RV_CONNECTOR_DEBUG - printf("-> Dataset creation request URL: %s\n\n", request_url); + printf("-> Dataset creation request endpoint: %s\n\n", request_endpoint); #endif http_response = RV_curl_post(curl, &new_dataset->domain->u.file.server_info, request_endpoint, @@ -722,10 +722,6 @@ RV_dataset_read(size_t count, void *dset[], hid_t mem_type_id[], hid_t _mem_spac #ifdef RV_CONNECTOR_DEBUG printf("-> Reading dataset\n\n"); - - printf(" /***************************************\\\n"); - printf("-> | Making GET/POST request to the server |\n"); - printf(" \\***************************************/\n\n"); #endif if (CURLM_OK != curl_multi_setopt(curl_multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS, NUM_MAX_HOST_CONNS)) @@ -1194,10 +1190,6 @@ RV_dataset_write(size_t count, void *dset[], hid_t mem_type_id[], hid_t _mem_spa #ifdef RV_CONNECTOR_DEBUG printf("-> Writing dataset\n\n"); - - printf(" /**********************************\\\n"); - printf("-> | Making PUT request to the server |\n"); - printf(" \\**********************************/\n\n"); #endif if (CURLM_OK != curl_multi_setopt(curl_multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS, NUM_MAX_HOST_CONNS)) @@ -1695,12 +1687,13 @@ static herr_t RV_parse_dataset_creation_properties_callback(char *HTTP_response, const void *callback_data_in, void *callback_data_out) { - yajl_val parse_tree = NULL, creation_properties_obj, key_obj; + yajl_val parse_tree = NULL, creation_properties_obj = NULL, key_obj = NULL, target_tree = NULL; hid_t *DCPL = (hid_t *)callback_data_out; hid_t fill_type = H5I_INVALID_HID; char *encoded_fill_value = NULL; char *decoded_fill_value = NULL; unsigned int *ud_parameters = NULL; + const char *path_name = NULL; herr_t ret_value = SUCCEED; #ifdef RV_CONNECTOR_DEBUG @@ -1715,9 +1708,27 @@ RV_parse_dataset_creation_properties_callback(char *HTTP_response, const void *c if (NULL == (parse_tree = yajl_tree_parse(HTTP_response, NULL, 0))) FUNC_GOTO_ERROR(H5E_DATASET, H5E_PARSEERROR, FAIL, "parsing JSON failed"); + target_tree = parse_tree; + + /* If the response contains 'h5paths', + * it may describe multiple objects. Needs to be unwrapped first. */ + if (NULL != yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object)) { + if (NULL == (target_tree = yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "can't parse h5paths object"); + + /* Access the first object under h5paths */ + if (NULL == (path_name = target_tree->u.object.keys[0])) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsed path name was NULL"); + + const char *path_keys[] = {path_name, (const char *)0}; + + if (NULL == (target_tree = yajl_tree_get(target_tree, path_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "unable to parse object under path key"); + } + /* Retrieve the creationProperties object */ if (NULL == - (creation_properties_obj = yajl_tree_get(parse_tree, creation_properties_keys, yajl_t_object))) + (creation_properties_obj = yajl_tree_get(target_tree, creation_properties_keys, yajl_t_object))) FUNC_GOTO_ERROR(H5E_DATASET, H5E_CANTGET, FAIL, "retrieval of creationProperties object failed"); /******************************************************************************************** diff --git a/src/rest_vol_datatype.c b/src/rest_vol_datatype.c index bfeab77d..571f546f 100644 --- a/src/rest_vol_datatype.c +++ b/src/rest_vol_datatype.c @@ -1426,18 +1426,19 @@ RV_convert_datatype_to_JSON(hid_t type_id, char **type_body, size_t *type_body_l static hid_t RV_convert_JSON_to_datatype(const char *type) { - yajl_val parse_tree = NULL, key_obj = NULL; - hsize_t *array_dims = NULL; - size_t i; - hid_t datatype = FAIL; - hid_t *compound_member_type_array = NULL; - hid_t enum_base_type = FAIL; - char **compound_member_names = NULL; - char *datatype_class = NULL; - char *array_base_type_substring = NULL; - char *tmp_cmpd_type_buffer = NULL; - char *tmp_enum_base_type_buffer = NULL; - hid_t ret_value = FAIL; + yajl_val parse_tree = NULL, key_obj = NULL, target_tree = NULL; + hsize_t *array_dims = NULL; + size_t i; + hid_t datatype = FAIL; + hid_t *compound_member_type_array = NULL; + hid_t enum_base_type = FAIL; + char **compound_member_names = NULL; + char *datatype_class = NULL; + char *array_base_type_substring = NULL; + char *tmp_cmpd_type_buffer = NULL; + char *tmp_enum_base_type_buffer = NULL; + const char *path_name = NULL; + hid_t ret_value = FAIL; #ifdef RV_CONNECTOR_DEBUG printf("-> Converting JSON buffer %s to hid_t\n", type); @@ -1447,6 +1448,24 @@ RV_convert_JSON_to_datatype(const char *type) if (NULL == (parse_tree = yajl_tree_parse(type, NULL, 0))) FUNC_GOTO_ERROR(H5E_DATATYPE, H5E_PARSEERROR, FAIL, "JSON parse tree creation failed"); + target_tree = parse_tree; + + /* If the response contains 'h5paths', + * it may describe multiple objects. Needs to be unwrapped first. */ + if (NULL != yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object)) { + if (NULL == (target_tree = yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "can't parse h5paths object"); + + /* Access the first object under h5paths */ + if (NULL == (path_name = target_tree->u.object.keys[0])) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsed path name was NULL"); + + const char *path_keys[] = {path_name, (const char *)0}; + + if (NULL == (target_tree = yajl_tree_get(target_tree, path_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "unable to parse object under path key"); + } + if (NULL == (key_obj = yajl_tree_get(parse_tree, type_class_keys, yajl_t_string))) FUNC_GOTO_ERROR(H5E_DATATYPE, H5E_PARSEERROR, FAIL, "can't parse datatype from JSON representation"); diff --git a/src/rest_vol_link.c b/src/rest_vol_link.c index 63182d3b..88e03f38 100644 --- a/src/rest_vol_link.c +++ b/src/rest_vol_link.c @@ -74,10 +74,12 @@ RV_link_create(H5VL_link_create_args_t *args, void *obj, const H5VL_loc_params_t RV_object_t *new_link_loc_obj = (RV_object_t *)obj; upload_info uinfo; size_t create_request_nalloc = 0; + size_t escaped_link_size = 0; void *hard_link_target_obj; char *create_request_body = NULL; char request_endpoint[URL_MAX_LENGTH]; char *url_encoded_link_name = NULL; + char *escaped_link_name = NULL; int create_request_body_len = 0; int url_len = 0; long http_response; @@ -132,6 +134,21 @@ RV_link_create(H5VL_link_create_args_t *args, void *obj, const H5VL_loc_params_t if (!(new_link_loc_obj->domain->u.file.intent & H5F_ACC_RDWR)) FUNC_GOTO_ERROR(H5E_FILE, H5E_BADVALUE, FAIL, "no write intent on file"); + /* If link name will be sent in request body, JSON escape it */ + if (SERVER_VERSION_SUPPORTS_LONG_NAMES(new_link_loc_obj->domain->u.file.server_info.version) && + loc_params->loc_data.loc_by_name.name) { + if (RV_JSON_escape_string(loc_params->loc_data.loc_by_name.name, escaped_link_name, + &escaped_link_size) < 0) + FUNC_GOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "can't get size of JSON escaped link name"); + + if ((escaped_link_name = RV_malloc(escaped_link_size)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, "can't allocate space for escaped link name"); + + if (RV_JSON_escape_string(loc_params->loc_data.loc_by_name.name, escaped_link_name, + &escaped_link_size) < 0) + FUNC_GOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "can't JSON escape link name"); + } + switch (args->op_type) { /* H5Lcreate_hard */ case H5VL_LINK_CREATE_HARD: { @@ -193,21 +210,46 @@ RV_link_create(H5VL_link_create_args_t *args, void *obj, const H5VL_loc_params_t #endif { - const char *const fmt_string = "{\"id\": \"%s\"}"; + const char *const fmt_string_no_title = "{\"id\": \"%s\"}"; + const char *const fmt_string_title = "{\"links\": {\"%s\": {\"id\": \"%s\"}}}"; /* Form the request body to create the Link */ - create_request_nalloc = (strlen(fmt_string) - 2) + strlen(target_URI) + 1; - if (NULL == (create_request_body = (char *)RV_malloc(create_request_nalloc))) - FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, - "can't allocate space for link create request body"); + if (SERVER_VERSION_SUPPORTS_LONG_NAMES( + new_link_loc_obj->domain->u.file.server_info.version) && + loc_params->loc_data.loc_by_name.name) { + /* Include escaped link name in body */ + create_request_nalloc = + (strlen(fmt_string_title) - 4) + strlen(target_URI) + strlen(escaped_link_name) + 1; + + if (NULL == (create_request_body = (char *)RV_malloc(create_request_nalloc))) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, + "can't allocate space for link create request body"); - if ((create_request_body_len = - snprintf(create_request_body, create_request_nalloc, fmt_string, target_URI)) < 0) - FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + if ((create_request_body_len = + snprintf(create_request_body, create_request_nalloc, fmt_string_title, + escaped_link_name, target_URI)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); - if ((size_t)create_request_body_len >= create_request_nalloc) - FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, - "link create request body size exceeded allocated buffer size"); + if ((size_t)create_request_body_len >= create_request_nalloc) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, + "link create request body size buffer size"); + } + else { + /* Body only contains target id */ + create_request_nalloc = (strlen(fmt_string_no_title) - 2) + strlen(target_URI) + 1; + + if (NULL == (create_request_body = (char *)RV_malloc(create_request_nalloc))) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, + "can't allocate space for link create request body"); + + if ((create_request_body_len = snprintf(create_request_body, create_request_nalloc, + fmt_string_no_title, target_URI)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + + if ((size_t)create_request_body_len >= create_request_nalloc) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, + "link create request body size exceeded allocated buffer size"); + } } #ifdef RV_CONNECTOR_DEBUG @@ -227,21 +269,46 @@ RV_link_create(H5VL_link_create_args_t *args, void *obj, const H5VL_loc_params_t #endif { - const char *const fmt_string = "{\"h5path\": \"%s\"}"; + const char *const fmt_string_no_title = "{\"h5path\": \"%s\"}"; + const char *const fmt_string_title = "{\"links\": {\"%s\": {\"h5path\": \"%s\"}}}"; /* Form the request body to create the Link */ - create_request_nalloc = (strlen(fmt_string) - 2) + strlen(link_target) + 1; - if (NULL == (create_request_body = (char *)RV_malloc(create_request_nalloc))) - FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, - "can't allocate space for link create request body"); + if (SERVER_VERSION_SUPPORTS_LONG_NAMES( + new_link_loc_obj->domain->u.file.server_info.version) && + loc_params->loc_data.loc_by_name.name) { + /* Body contains link title */ + create_request_nalloc = + (strlen(fmt_string_title) - 4) + strlen(link_target) + strlen(escaped_link_name) + 1; + + if (NULL == (create_request_body = (char *)RV_malloc(create_request_nalloc))) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, + "can't allocate space for link create request body"); - if ((create_request_body_len = - snprintf(create_request_body, create_request_nalloc, fmt_string, link_target)) < 0) - FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + if ((create_request_body_len = + snprintf(create_request_body, create_request_nalloc, fmt_string_title, + escaped_link_name, link_target)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); - if ((size_t)create_request_body_len >= create_request_nalloc) - FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, - "link create request body size exceeded allocated buffer size"); + if ((size_t)create_request_body_len >= create_request_nalloc) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, + "link create request body size exceeded allocated buffer size"); + } + else { + /* Body only contains h5path */ + create_request_nalloc = (strlen(fmt_string_no_title) - 2) + strlen(link_target) + 1; + + if (NULL == (create_request_body = (char *)RV_malloc(create_request_nalloc))) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, + "can't allocate space for link create request body"); + + if ((create_request_body_len = snprintf(create_request_body, create_request_nalloc, + fmt_string_no_title, link_target)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + + if ((size_t)create_request_body_len >= create_request_nalloc) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, + "link create request body size exceeded allocated buffer size"); + } } #ifdef RV_CONNECTOR_DEBUG @@ -271,22 +338,47 @@ RV_link_create(H5VL_link_create_args_t *args, void *obj, const H5VL_loc_params_t #endif { - const char *const fmt_string = "{\"h5domain\": \"%s\", \"h5path\": \"%s\"}"; + const char *const fmt_string_no_title = "{\"h5domain\": \"%s\", \"h5path\": \"%s\"}"; + const char *const fmt_string_title = + "{\"links\": {\"%s\": {\"h5domain\": \"%s\", \"h5path\": \"%s\"}}}"; /* Form the request body to create the Link */ - create_request_nalloc = - (strlen(fmt_string) - 4) + strlen(file_path) + strlen(link_target) + 1; - if (NULL == (create_request_body = (char *)RV_malloc(create_request_nalloc))) - FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, - "can't allocate space for link create request body"); + if (SERVER_VERSION_SUPPORTS_LONG_NAMES( + new_link_loc_obj->domain->u.file.server_info.version) && + loc_params->loc_data.loc_by_name.name) { + /* Body contains link name */ + create_request_nalloc = (strlen(fmt_string_title) - 6) + strlen(file_path) + + strlen(link_target) + strlen(escaped_link_name) + 1; + + if (NULL == (create_request_body = (char *)RV_malloc(create_request_nalloc))) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, + "can't allocate space for link create request body"); - if ((create_request_body_len = snprintf(create_request_body, create_request_nalloc, - fmt_string, file_path, link_target)) < 0) - FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + if ((create_request_body_len = + snprintf(create_request_body, create_request_nalloc, fmt_string_title, + escaped_link_name, file_path, link_target)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); - if ((size_t)create_request_body_len >= create_request_nalloc) - FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, - "link create request body size exceeded allocated buffer size"); + if ((size_t)create_request_body_len >= create_request_nalloc) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, + "link create request body size exceeded allocated buffer size"); + } + else { + /* Body does not contain link name */ + create_request_nalloc = + (strlen(fmt_string_no_title) - 4) + strlen(file_path) + strlen(link_target) + 1; + if (NULL == (create_request_body = (char *)RV_malloc(create_request_nalloc))) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, + "can't allocate space for link create request body"); + + if ((create_request_body_len = snprintf(create_request_body, create_request_nalloc, + fmt_string_no_title, file_path, link_target)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + + if ((size_t)create_request_body_len >= create_request_nalloc) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, + "link create request body size exceeded allocated buffer size"); + } } #ifdef RV_CONNECTOR_DEBUG @@ -300,17 +392,26 @@ RV_link_create(H5VL_link_create_args_t *args, void *obj, const H5VL_loc_params_t FUNC_GOTO_ERROR(H5E_LINK, H5E_BADVALUE, FAIL, "Invalid link create type"); } /* end switch */ - /* URL-encode the name of the link to ensure that the resulting URL for the link - * creation operation doesn't contain any illegal characters - */ - if (NULL == (url_encoded_link_name = - curl_easy_escape(curl, H5_rest_basename(loc_params->loc_data.loc_by_name.name), 0))) - FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTENCODE, FAIL, "can't URL-encode link name"); - - /* Redirect cURL from the base URL to "/groups//links/" to create the link */ - if ((url_len = snprintf(request_endpoint, URL_MAX_LENGTH, "/groups/%s/links/%s", new_link_loc_obj->URI, - url_encoded_link_name)) < 0) - FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + if (SERVER_VERSION_SUPPORTS_LONG_NAMES(new_link_loc_obj->domain->u.file.server_info.version) && + loc_params->loc_data.loc_by_name.name) { + /* Redirect cURL from the base URL to "/groups//links" to create the link */ + if ((url_len = + snprintf(request_endpoint, URL_MAX_LENGTH, "/groups/%s/links", new_link_loc_obj->URI)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + } + else { + /* URL-encode the name of the link to ensure that the resulting URL for the link + * creation operation doesn't contain any illegal characters + */ + if (NULL == (url_encoded_link_name = + curl_easy_escape(curl, H5_rest_basename(loc_params->loc_data.loc_by_name.name), 0))) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTENCODE, FAIL, "can't URL-encode link name"); + + /* Redirect cURL from the base URL to "/groups//links/" to create the link */ + if ((url_len = snprintf(request_endpoint, URL_MAX_LENGTH, "/groups/%s/links/%s", + new_link_loc_obj->URI, url_encoded_link_name)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + } if (url_len >= URL_MAX_LENGTH) FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "link create URL size exceeded maximum URL size"); @@ -323,7 +424,6 @@ RV_link_create(H5VL_link_create_args_t *args, void *obj, const H5VL_loc_params_t uinfo.buffer_size = (size_t)create_request_body_len; uinfo.bytes_sent = 0; - // TODO - Check this uses right filename for external links http_response = RV_curl_put(curl, &new_link_loc_obj->domain->u.file.server_info, request_endpoint, new_link_loc_obj->domain->u.file.filepath_name, &uinfo, CONTENT_TYPE_JSON); @@ -343,6 +443,8 @@ RV_link_create(H5VL_link_create_args_t *args, void *obj, const H5VL_loc_params_t RV_free(create_request_body); if (url_encoded_link_name) curl_free(url_encoded_link_name); + if (escaped_link_name) + RV_free(escaped_link_name); PRINT_ERROR_STACK; @@ -709,12 +811,16 @@ RV_link_specific(void *obj, const H5VL_loc_params_t *loc_params, H5VL_link_speci { RV_object_t *loc_obj = (RV_object_t *)obj; hbool_t empty_dirname; + size_t escaped_link_size = 0; + int request_body_len = 0; hid_t link_iter_group_id = H5I_INVALID_HID; void *link_iter_group_object = NULL; char *link_path_dirname = NULL; char temp_URI[URI_MAX_LENGTH]; char request_endpoint[URL_MAX_LENGTH]; char *url_encoded_link_name = NULL; + char *escaped_link_name = NULL; + char *request_body = NULL; int url_len = 0; long http_response; herr_t ret_value = SUCCEED; @@ -825,24 +931,89 @@ RV_link_specific(void *obj, const H5VL_loc_params_t *loc_params, H5VL_link_speci FUNC_GOTO_ERROR(H5E_LINK, H5E_PATH, FAIL, "can't locate parent group for link"); } /* end if */ - /* URL-encode the link name so that the resulting URL for the link GET - * operation doesn't contain any illegal characters - */ - if (NULL == (url_encoded_link_name = curl_easy_escape( - curl, H5_rest_basename(loc_params->loc_data.loc_by_name.name), 0))) - FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTENCODE, FAIL, "can't URL-encode link name"); + /* Setup cURL to make the request */ + if (SERVER_VERSION_SUPPORTS_LONG_NAMES(loc_obj->domain->u.file.server_info.version)) { + /* Send link name in body of POST request */ + const char *fmt_string = "{\"titles\": [\"%s\"]}"; + int bytes_printed; - if ((url_len = snprintf(request_endpoint, URL_MAX_LENGTH, "/groups/%s/links/%s", - empty_dirname ? loc_obj->URI : temp_URI, url_encoded_link_name)) < 0) - FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + /* JSON escape link name */ + if (RV_JSON_escape_string(H5_rest_basename(loc_params->loc_data.loc_by_name.name), + escaped_link_name, &escaped_link_size) < 0) + FUNC_GOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "can't get size of JSON escaped link name"); + + if ((escaped_link_name = RV_malloc(escaped_link_size)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, + "can't allocate space for escaped link name"); + + if (RV_JSON_escape_string(H5_rest_basename(loc_params->loc_data.loc_by_name.name), + escaped_link_name, &escaped_link_size) < 0) + FUNC_GOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "can't JSON escape link name"); + + request_body_len = (int)(strlen(fmt_string) - 2 + strlen(escaped_link_name) + 1); + + if ((request_body = RV_malloc((size_t)request_body_len)) == NULL) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTALLOC, FAIL, + "can't allocate space for link query body"); + + if ((bytes_printed = + snprintf(request_body, (size_t)request_body_len, fmt_string, escaped_link_name)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + + if (bytes_printed >= request_body_len) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, + "request body size exceeded allocated buffer size"); + + if ((url_len = snprintf(request_endpoint, URL_MAX_LENGTH, "/groups/%s/links", + empty_dirname ? loc_obj->URI : temp_URI)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + +#ifdef RV_CONNECTOR_DEBUG + printf("-> Checking for existence of link using endpoint: %s\n\n", request_endpoint); +#endif + + if ((http_response = RV_curl_post(curl, &loc_obj->domain->u.file.server_info, + request_endpoint, loc_obj->domain->u.file.filepath_name, + request_body, (size_t)bytes_printed, CONTENT_TYPE_JSON)) < + 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTGET, FAIL, + "internal failure while making POST request to server"); + } + else { + /* URL-encode the link name so that the resulting URL for the link GET + * operation doesn't contain any illegal characters + */ + if (NULL == (url_encoded_link_name = curl_easy_escape( + curl, H5_rest_basename(loc_params->loc_data.loc_by_name.name), 0))) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTENCODE, FAIL, "can't URL-encode link name"); + + if ((url_len = snprintf(request_endpoint, URL_MAX_LENGTH, "/groups/%s/links/%s", + empty_dirname ? loc_obj->URI : temp_URI, url_encoded_link_name)) < 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "snprintf error"); + +#ifdef RV_CONNECTOR_DEBUG + printf("-> Checking for existence of link using endpoint: %s\n\n", request_endpoint); +#endif + if ((http_response = RV_curl_get(curl, &loc_obj->domain->u.file.server_info, request_endpoint, + loc_obj->domain->u.file.filepath_name, CONTENT_TYPE_JSON)) < + 0) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTGET, FAIL, + "internal failure while making GET request to server"); + } if (url_len >= URL_MAX_LENGTH) FUNC_GOTO_ERROR(H5E_LINK, H5E_SYSERRSTR, FAIL, "H5Lexists request URL size exceeded maximum URL size"); - http_response = RV_curl_get(curl, &loc_obj->domain->u.file.server_info, request_endpoint, - loc_obj->domain->u.file.filepath_name, CONTENT_TYPE_JSON); - *ret = HTTP_SUCCESS(http_response); + if (HTTP_CLIENT_ERROR(http_response) && http_response != 404 && http_response != 410) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTGET, FAIL, "malformed client request: response code %zu\n", + http_response); + + if (HTTP_SERVER_ERROR(http_response)) + FUNC_GOTO_ERROR(H5E_LINK, H5E_CANTGET, FAIL, "internal server failure: response code %zu\n", + http_response); + + *ret = HTTP_SUCCESS(http_response); break; } /* H5VL_LINK_EXISTS */ @@ -978,6 +1149,11 @@ RV_link_specific(void *obj, const H5VL_loc_params_t *loc_params, H5VL_link_speci if (url_encoded_link_name) curl_free(url_encoded_link_name); + if (escaped_link_name) + RV_free(escaped_link_name); + if (request_body) + RV_free(request_body); + PRINT_ERROR_STACK; return ret_value; diff --git a/src/rest_vol_object.c b/src/rest_vol_object.c index c7eb56c7..5c4ea994 100644 --- a/src/rest_vol_object.c +++ b/src/rest_vol_object.c @@ -40,7 +40,6 @@ static void RV_free_object_table(object_table_entry *object_table, size_t num_en /* JSON keys to retrieve relevant information for H5Oget_info */ const char *attribute_count_keys[] = {"attributeCount", (const char *)0}; -const char *hrefs_keys[] = {"hrefs", (const char *)0}; /*------------------------------------------------------------------------- * Function: RV_object_open @@ -1043,9 +1042,10 @@ static herr_t RV_get_object_info_callback(char *HTTP_response, const void *callback_data_in, void *callback_data_out) { H5O_info2_t *obj_info = (H5O_info2_t *)callback_data_out; - yajl_val parse_tree = NULL, key_obj; + yajl_val parse_tree = NULL, key_obj = NULL, target_tree = NULL; size_t i; - char *object_id, *domain_path = NULL; + char *object_id = NULL, *domain_path = NULL; + const char *path_name = NULL; herr_t ret_value = SUCCEED; #ifdef RV_CONNECTOR_DEBUG @@ -1062,47 +1062,38 @@ RV_get_object_info_callback(char *HTTP_response, const void *callback_data_in, v if (NULL == (parse_tree = yajl_tree_parse(HTTP_response, NULL, 0))) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsing JSON failed"); + target_tree = parse_tree; + + /* If the response contains 'h5paths', + * it may describe multiple objects. Needs to be unwrapped first. */ + if (NULL != yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object)) { + if (NULL == (target_tree = yajl_tree_get(parse_tree, h5paths_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "can't parse h5paths object"); + + /* Access the first object under h5paths */ + if (NULL == (path_name = target_tree->u.object.keys[0])) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "parsed path name was NULL"); + + const char *path_keys[] = {path_name, (const char *)0}; + + if (NULL == (target_tree = yajl_tree_get(target_tree, path_keys, yajl_t_object))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "unable to parse object under path key"); + } + /* * Fill out the fileno and addr fields with somewhat faked data, as these fields are used * in other places to verify that two objects are different. The domain path is hashed * and converted to an unsigned long for the fileno field and the object's UUID string * is hashed to an haddr_t for the addr field. */ + if (NULL == (key_obj = yajl_tree_get(target_tree, domain_keys, yajl_t_string))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "can't get domain from response"); - if (NULL == (key_obj = yajl_tree_get(parse_tree, hrefs_keys, yajl_t_array))) - FUNC_GOTO_ERROR(H5E_OBJECT, H5E_CANTGET, FAIL, "retrieval of object HREFs failed"); - - /* Find the "home" href that corresponds to the object's domain path */ - for (i = 0; i < YAJL_GET_ARRAY(key_obj)->len; i++) { - yajl_val href_obj = YAJL_GET_ARRAY(key_obj)->values[i]; - size_t j; - - if (!YAJL_IS_OBJECT(href_obj)) - FUNC_GOTO_ERROR(H5E_OBJECT, H5E_BADVALUE, FAIL, "HREFs array value is not an object"); - - for (j = 0; j < YAJL_GET_OBJECT(href_obj)->len; j++) { - char *key_val; - - if (NULL == (key_val = YAJL_GET_STRING(YAJL_GET_OBJECT(href_obj)->values[j]))) - FUNC_GOTO_ERROR(H5E_OBJECT, H5E_BADVALUE, FAIL, "HREF object key value was NULL"); - - /* If this object's "rel" key does not have the value "home", skip this object */ - if (!strcmp(YAJL_GET_OBJECT(href_obj)->keys[j], "rel") && strcmp(key_val, "home")) { - domain_path = NULL; - break; - } /* end if */ - - if (!strcmp(YAJL_GET_OBJECT(href_obj)->keys[j], "href")) - domain_path = key_val; - } /* end for */ - - if (domain_path) - break; - } /* end for */ + if (!YAJL_IS_STRING(key_obj)) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "retrieved domain was not a valid string"); - if (!domain_path) - FUNC_GOTO_ERROR(H5E_OBJECT, H5E_CANTGET, FAIL, - "unable to determine a value for object info file number field"); + if (NULL == (domain_path = YAJL_GET_STRING(key_obj))) + FUNC_GOTO_ERROR(H5E_OBJECT, H5E_PARSEERROR, FAIL, "retrieved domain was NULL"); obj_info->fileno = (unsigned long)rv_hash_string(domain_path); @@ -1110,7 +1101,7 @@ RV_get_object_info_callback(char *HTTP_response, const void *callback_data_in, v printf("-> Object's file number: %lu\n", (unsigned long)obj_info->fileno); #endif - if (NULL == (key_obj = yajl_tree_get(parse_tree, object_id_keys, yajl_t_string))) + if (NULL == (key_obj = yajl_tree_get(target_tree, object_id_keys, yajl_t_string))) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_CANTGET, FAIL, "retrieval of object ID failed"); if (NULL == (object_id = YAJL_GET_STRING(key_obj))) @@ -1124,7 +1115,7 @@ RV_get_object_info_callback(char *HTTP_response, const void *callback_data_in, v #endif /* Retrieve the object's attribute count */ - if (NULL == (key_obj = yajl_tree_get(parse_tree, attribute_count_keys, yajl_t_number))) + if (NULL == (key_obj = yajl_tree_get(target_tree, attribute_count_keys, yajl_t_number))) FUNC_GOTO_ERROR(H5E_OBJECT, H5E_CANTGET, FAIL, "retrieval of object attribute count failed"); if (!YAJL_IS_INTEGER(key_obj)) @@ -1216,6 +1207,7 @@ RV_object_iter_callback(char *HTTP_response, const void *callback_data_in, void size_t object_table_num_entries = 0; herr_t ret_value = SUCCEED; char URL[URL_MAX_LENGTH]; + const char *path_name = NULL; #ifdef RV_CONNECTOR_DEBUG printf("-> Iterating recursively through objects according to server's HTTP response\n\n");