/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 Red Hat, Inc. * Copyright (C) 2008 Novell, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see . * * Author: Alexander Larsson * Author: Tor Lillqvist */ #include "config.h" #include #include #include #include "gio/gfile.h" #include "gio/gfileattribute.h" #include "gio/gfileinfo.h" #include "gio/gfileinfo-priv.h" #include "gwinhttpfile.h" #include "gwinhttpfileinputstream.h" #include "gwinhttpfileoutputstream.h" #include "gio/gioerror.h" #include "glibintl.h" static void g_winhttp_file_file_iface_init (GFileIface *iface); #define g_winhttp_file_get_type _g_winhttp_file_get_type G_DEFINE_TYPE_WITH_CODE (GWinHttpFile, g_winhttp_file, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_FILE, g_winhttp_file_file_iface_init)) static void g_winhttp_file_finalize (GObject *object) { GWinHttpFile *file; file = G_WINHTTP_FILE (object); g_free (file->url.lpszScheme); g_free (file->url.lpszHostName); g_free (file->url.lpszUserName); g_free (file->url.lpszPassword); g_free (file->url.lpszUrlPath); g_free (file->url.lpszExtraInfo); g_object_unref (file->vfs); G_OBJECT_CLASS (g_winhttp_file_parent_class)->finalize (object); } static void g_winhttp_file_class_init (GWinHttpFileClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = g_winhttp_file_finalize; } static void g_winhttp_file_init (GWinHttpFile *winhttp) { } /* * _g_winhttp_file_new: * @vfs: GWinHttpVfs to use * @uri: URI of the GWinHttpFile to create. * * Returns: (nullable): new winhttp #GFile, or %NULL if there was an error constructing it. */ GFile * _g_winhttp_file_new (GWinHttpVfs *vfs, const char *uri) { wchar_t *wuri; GWinHttpFile *file; wuri = g_utf8_to_utf16 (uri, -1, NULL, NULL, NULL); if (wuri == NULL) return NULL; file = g_object_new (G_TYPE_WINHTTP_FILE, NULL); file->vfs = g_object_ref (vfs); memset (&file->url, 0, sizeof (file->url)); file->url.dwStructSize = sizeof (file->url); file->url.dwSchemeLength = 1; file->url.dwHostNameLength = 1; file->url.dwUserNameLength = 1; file->url.dwPasswordLength = 1; file->url.dwUrlPathLength = 1; file->url.dwExtraInfoLength = 1; if (!G_WINHTTP_VFS_GET_CLASS (vfs)->funcs->pWinHttpCrackUrl (wuri, 0, 0, &file->url)) { g_free (wuri); return NULL; } file->url.lpszScheme = g_new (wchar_t, ++file->url.dwSchemeLength); file->url.lpszHostName = g_new (wchar_t, ++file->url.dwHostNameLength); file->url.lpszUserName = g_new (wchar_t, ++file->url.dwUserNameLength); file->url.lpszPassword = g_new (wchar_t, ++file->url.dwPasswordLength); file->url.lpszUrlPath = g_new (wchar_t, ++file->url.dwUrlPathLength); file->url.lpszExtraInfo = g_new (wchar_t, ++file->url.dwExtraInfoLength); if (!G_WINHTTP_VFS_GET_CLASS (vfs)->funcs->pWinHttpCrackUrl (wuri, 0, 0, &file->url)) { g_free (file->url.lpszScheme); g_free (file->url.lpszHostName); g_free (file->url.lpszUserName); g_free (file->url.lpszPassword); g_free (file->url.lpszUrlPath); g_free (file->url.lpszExtraInfo); g_free (wuri); return NULL; } g_free (wuri); return G_FILE (file); } static gboolean g_winhttp_file_is_native (GFile *file) { return FALSE; } static gboolean g_winhttp_file_has_uri_scheme (GFile *file, const char *uri_scheme) { return (g_ascii_strcasecmp (uri_scheme, "http") == 0 || g_ascii_strcasecmp (uri_scheme, "https") == 0); } static char * g_winhttp_file_get_uri_scheme (GFile *file) { GWinHttpFile *winhttp_file = G_WINHTTP_FILE (file); return g_utf16_to_utf8 (winhttp_file->url.lpszScheme, -1, NULL, NULL, NULL); } static char * g_winhttp_file_get_basename (GFile *file) { GWinHttpFile *winhttp_file = G_WINHTTP_FILE (file); char *basename; char *last_slash; char *retval; basename = g_utf16_to_utf8 (winhttp_file->url.lpszUrlPath, -1, NULL, NULL, NULL); last_slash = strrchr (basename, '/'); /* If no slash, or only "/" fallback to full path part of URI */ if (last_slash == NULL || last_slash[1] == '\0') return basename; retval = g_strdup (last_slash + 1); g_free (basename); return retval; } static char * g_winhttp_file_get_display_name (GFile *file) { char *basename; /* FIXME: This could be improved by using a new g_utf16_make_valid() function * to recover what we can from the URI, and then suffixing it with * “ (invalid encoding)” as per g_filename_display_basename(). */ basename = g_winhttp_file_get_basename (file); if (!basename) return g_strdup (_(" (invalid encoding)")); return g_steal_pointer (&basename); } static char * g_winhttp_file_get_path (GFile *file) { return NULL; } static char * g_winhttp_file_get_uri (GFile *file) { GWinHttpFile *winhttp_file = G_WINHTTP_FILE (file); DWORD len; wchar_t *wuri; char *retval; len = 0; if (!G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpCreateUrl (&winhttp_file->url, ICU_ESCAPE, NULL, &len) && GetLastError () != ERROR_INSUFFICIENT_BUFFER) return NULL; wuri = g_new (wchar_t, ++len); if (!G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpCreateUrl (&winhttp_file->url, ICU_ESCAPE, wuri, &len)) { g_free (wuri); return NULL; } retval = g_utf16_to_utf8 (wuri, -1, NULL, NULL, NULL); g_free (wuri); if (g_str_has_prefix (retval, "http://:@")) { memmove (retval + 7, retval + 9, strlen (retval) - 9); retval[strlen (retval) - 2] = '\0'; } else if (g_str_has_prefix (retval, "https://:@")) { memmove (retval + 8, retval + 10, strlen (retval) - 10); retval[strlen (retval) - 2] = '\0'; } return retval; } static char * g_winhttp_file_get_parse_name (GFile *file) { /* FIXME: More hair surely needed */ return g_winhttp_file_get_uri (file); } static GFile * g_winhttp_file_get_parent (GFile *file) { GWinHttpFile *winhttp_file; char *uri; char *last_slash; GFile *parent; winhttp_file = G_WINHTTP_FILE (file); uri = g_winhttp_file_get_uri (file); if (uri == NULL) return NULL; last_slash = strrchr (uri, '/'); if (last_slash == NULL || *(last_slash+1) == 0) { g_free (uri); return NULL; } while (last_slash > uri && *last_slash == '/') last_slash--; last_slash[1] = '\0'; parent = _g_winhttp_file_new (winhttp_file->vfs, uri); g_free (uri); return parent; } static GFile * g_winhttp_file_dup (GFile *file) { GWinHttpFile *winhttp_file = G_WINHTTP_FILE (file); char *uri = g_winhttp_file_get_uri (file); GFile *retval = _g_winhttp_file_new (winhttp_file->vfs, uri); g_free (uri); return retval; } static guint g_winhttp_file_hash (GFile *file) { char *uri = g_winhttp_file_get_uri (file); guint retval = g_str_hash (uri); g_free (uri); return retval; } static gboolean g_winhttp_file_equal (GFile *file1, GFile *file2) { char *uri1 = g_winhttp_file_get_uri (file1); char *uri2 = g_winhttp_file_get_uri (file2); gboolean retval = g_str_equal (uri1, uri2); g_free (uri1); g_free (uri2); return retval; } static const char * match_prefix (const char *path, const char *prefix) { int prefix_len; prefix_len = strlen (prefix); if (strncmp (path, prefix, prefix_len) != 0) return NULL; if (prefix_len > 0 && prefix[prefix_len-1] == '/') prefix_len--; return path + prefix_len; } static gboolean g_winhttp_file_prefix_matches (GFile *parent, GFile *descendant) { char *parent_uri = g_winhttp_file_get_uri (parent); char *descendant_uri = g_winhttp_file_get_uri (descendant); const char *remainder; gboolean retval; remainder = match_prefix (descendant_uri, parent_uri); if (remainder != NULL && *remainder == '/') retval = TRUE; else retval = FALSE; g_free (parent_uri); g_free (descendant_uri); return retval; } static char * g_winhttp_file_get_relative_path (GFile *parent, GFile *descendant) { char *parent_uri = g_winhttp_file_get_uri (parent); char *descendant_uri = g_winhttp_file_get_uri (descendant); const char *remainder; char *retval; remainder = match_prefix (descendant_uri, parent_uri); if (remainder != NULL && *remainder == '/') retval = g_strdup (remainder + 1); else retval = NULL; g_free (parent_uri); g_free (descendant_uri); return retval; } static GFile * g_winhttp_file_resolve_relative_path (GFile *file, const char *relative_path) { GWinHttpFile *winhttp_file = G_WINHTTP_FILE (file); GWinHttpFile *child; wchar_t *wnew_path = g_utf8_to_utf16 (relative_path, -1, NULL, NULL, NULL); if (wnew_path == NULL) return NULL; if (*wnew_path != '/') { wchar_t *tmp = NULL; int trailing_slash = winhttp_file->url.lpszUrlPath[winhttp_file->url.dwUrlPathLength-1] == L'/'? 1 : 0; if (trailing_slash) { tmp = g_new (wchar_t, wcslen (winhttp_file->url.lpszUrlPath) + wcslen (wnew_path) + 1); wcscpy (tmp, winhttp_file->url.lpszUrlPath); } else { tmp = g_new (wchar_t, wcslen (winhttp_file->url.lpszUrlPath) + 1 + wcslen (wnew_path) + 1); wcscpy (tmp, winhttp_file->url.lpszUrlPath); wcscat (tmp, L"/"); } wcscat (tmp, wnew_path); g_free (wnew_path); wnew_path = tmp; } child = g_object_new (G_TYPE_WINHTTP_FILE, NULL); child->vfs = winhttp_file->vfs; child->url = winhttp_file->url; child->url.lpszScheme = g_memdup2 (winhttp_file->url.lpszScheme, ((gsize) winhttp_file->url.dwSchemeLength + 1) * 2); child->url.lpszHostName = g_memdup2 (winhttp_file->url.lpszHostName, ((gsize) winhttp_file->url.dwHostNameLength + 1) * 2); child->url.lpszUserName = g_memdup2 (winhttp_file->url.lpszUserName, ((gsize) winhttp_file->url.dwUserNameLength + 1) * 2); child->url.lpszPassword = g_memdup2 (winhttp_file->url.lpszPassword, ((gsize) winhttp_file->url.dwPasswordLength + 1) * 2); child->url.lpszUrlPath = wnew_path; child->url.dwUrlPathLength = wcslen (wnew_path); child->url.lpszExtraInfo = NULL; child->url.dwExtraInfoLength = 0; return (GFile *) child; } static GFile * g_winhttp_file_get_child_for_display_name (GFile *file, const char *display_name, GError **error) { GFile *new_file; char *basename; basename = g_locale_from_utf8 (display_name, -1, NULL, NULL, NULL); if (basename == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, _("Invalid filename %s"), display_name); return NULL; } new_file = g_file_get_child (file, basename); g_free (basename); return new_file; } static GFile * g_winhttp_file_set_display_name (GFile *file, const char *display_name, GCancellable *cancellable, GError **error) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported")); return NULL; } static GFileInfo * g_winhttp_file_query_info (GFile *file, const char *attributes, GFileQueryInfoFlags flags, GCancellable *cancellable, GError **error) { GWinHttpFile *winhttp_file = G_WINHTTP_FILE (file); HINTERNET connection, request; const wchar_t *accept_types[] = { L"*/*", NULL, }; GFileInfo *info; GFileAttributeMatcher *matcher; char *basename; wchar_t *content_length; wchar_t *content_type; SYSTEMTIME last_modified; DWORD last_modified_len; connection = G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpConnect (G_WINHTTP_VFS (winhttp_file->vfs)->session, winhttp_file->url.lpszHostName, winhttp_file->url.nPort, 0); if (connection == NULL) { _g_winhttp_set_error (error, GetLastError (), "HTTP connection"); return NULL; } request = G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpOpenRequest (connection, L"HEAD", winhttp_file->url.lpszUrlPath, NULL, WINHTTP_NO_REFERER, accept_types, winhttp_file->url.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0); if (request == NULL) { _g_winhttp_set_error (error, GetLastError (), "HEAD request"); return NULL; } if (!G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpSendRequest (request, NULL, 0, NULL, 0, 0, 0)) { _g_winhttp_set_error (error, GetLastError (), "HEAD request"); return NULL; } if (!_g_winhttp_response (winhttp_file->vfs, request, error, "HEAD request")) return NULL; matcher = g_file_attribute_matcher_new (attributes); info = g_file_info_new (); g_file_info_set_attribute_mask (info, matcher); basename = g_winhttp_file_get_basename (file); g_file_info_set_name (info, basename); g_free (basename); if (_g_file_attribute_matcher_matches_id (matcher, G_FILE_ATTRIBUTE_ID_STANDARD_DISPLAY_NAME)) { char *display_name = g_winhttp_file_get_display_name (file); g_file_info_set_display_name (info, display_name); g_free (display_name); } if (_g_file_attribute_matcher_matches_id (matcher, G_FILE_ATTRIBUTE_ID_STANDARD_TYPE)) g_file_info_set_file_type (info, G_FILE_TYPE_REGULAR); content_length = NULL; if (_g_winhttp_query_header (winhttp_file->vfs, request, "HEAD request", WINHTTP_QUERY_CONTENT_LENGTH, &content_length, NULL)) { gint64 cl; size_t n; const char *gint64_format = "%"G_GINT64_FORMAT"%n"; wchar_t *gint64_format_w = g_utf8_to_utf16 (gint64_format, -1, NULL, NULL, NULL); if (swscanf (content_length, gint64_format_w, &cl, &n) == 1 && n == wcslen (content_length)) g_file_info_set_size (info, cl); g_free (content_length); g_free (gint64_format_w); } if (matcher == NULL) return info; content_type = NULL; if (_g_winhttp_query_header (winhttp_file->vfs, request, "HEAD request", WINHTTP_QUERY_CONTENT_TYPE, &content_type, NULL)) { char *ct = g_utf16_to_utf8 (content_type, -1, NULL, NULL, NULL); if (ct != NULL) { char *p = strchr (ct, ';'); if (p != NULL) { char *tmp = g_strndup (ct, p - ct); g_file_info_set_content_type (info, tmp); g_free (tmp); } else g_file_info_set_content_type (info, ct); } g_free (ct); } last_modified_len = sizeof (last_modified); if (G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpQueryHeaders (request, WINHTTP_QUERY_LAST_MODIFIED | WINHTTP_QUERY_FLAG_SYSTEMTIME, NULL, &last_modified, &last_modified_len, NULL) && last_modified_len == sizeof (last_modified) && /* Don't bother comparing to the exact Y2038 moment */ last_modified.wYear >= 1970 && last_modified.wYear < 2038) { GDateTime *dt = NULL, *dt2 = NULL; dt = g_date_time_new_from_unix_utc (last_modified.wMilliseconds / 1000); dt2 = g_date_time_add_seconds (dt, (last_modified.wMilliseconds % 1000) / 1000); g_file_info_set_modification_date_time (info, dt2); g_date_time_unref (dt2); g_date_time_unref (dt); } g_file_attribute_matcher_unref (matcher); return info; } static GFileInputStream * g_winhttp_file_read (GFile *file, GCancellable *cancellable, GError **error) { GWinHttpFile *winhttp_file = G_WINHTTP_FILE (file); HINTERNET connection, request; const wchar_t *accept_types[] = { L"*/*", NULL, }; connection = G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpConnect (G_WINHTTP_VFS (winhttp_file->vfs)->session, winhttp_file->url.lpszHostName, winhttp_file->url.nPort, 0); if (connection == NULL) { _g_winhttp_set_error (error, GetLastError (), "HTTP connection"); return NULL; } request = G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpOpenRequest (connection, L"GET", winhttp_file->url.lpszUrlPath, NULL, WINHTTP_NO_REFERER, accept_types, winhttp_file->url.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0); if (request == NULL) { _g_winhttp_set_error (error, GetLastError (), "GET request"); return NULL; } return _g_winhttp_file_input_stream_new (winhttp_file, connection, request); } static GFileOutputStream * g_winhttp_file_create (GFile *file, GFileCreateFlags flags, GCancellable *cancellable, GError **error) { GWinHttpFile *winhttp_file = G_WINHTTP_FILE (file); HINTERNET connection; connection = G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpConnect (G_WINHTTP_VFS (winhttp_file->vfs)->session, winhttp_file->url.lpszHostName, winhttp_file->url.nPort, 0); if (connection == NULL) { _g_winhttp_set_error (error, GetLastError (), "HTTP connection"); return NULL; } return _g_winhttp_file_output_stream_new (winhttp_file, connection); } #if 0 static GFileOutputStream * g_winhttp_file_replace (GFile *file, const char *etag, gboolean make_backup, GFileCreateFlags flags, GCancellable *cancellable, GError **error) { /* FIXME: Implement */ return NULL; } static gboolean g_winhttp_file_delete (GFile *file, GCancellable *cancellable, GError **error) { /* FIXME: Implement */ return FALSE; } static gboolean g_winhttp_file_make_directory (GFile *file, GCancellable *cancellable, GError **error) { /* FIXME: Implement */ return FALSE; } static gboolean g_winhttp_file_copy (GFile *source, GFile *destination, GFileCopyFlags flags, GCancellable *cancellable, GFileProgressCallback progress_callback, gpointer progress_callback_data, GError **error) { /* Fall back to default copy?? */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Copy not supported"); return FALSE; } static gboolean g_winhttp_file_move (GFile *source, GFile *destination, GFileCopyFlags flags, GCancellable *cancellable, GFileProgressCallback progress_callback, gpointer progress_callback_data, GError **error) { /* FIXME: Implement */ return FALSE; } #endif static void g_winhttp_file_file_iface_init (GFileIface *iface) { iface->dup = g_winhttp_file_dup; iface->hash = g_winhttp_file_hash; iface->equal = g_winhttp_file_equal; iface->is_native = g_winhttp_file_is_native; iface->has_uri_scheme = g_winhttp_file_has_uri_scheme; iface->get_uri_scheme = g_winhttp_file_get_uri_scheme; iface->get_basename = g_winhttp_file_get_basename; iface->get_path = g_winhttp_file_get_path; iface->get_uri = g_winhttp_file_get_uri; iface->get_parse_name = g_winhttp_file_get_parse_name; iface->get_parent = g_winhttp_file_get_parent; iface->prefix_matches = g_winhttp_file_prefix_matches; iface->get_relative_path = g_winhttp_file_get_relative_path; iface->resolve_relative_path = g_winhttp_file_resolve_relative_path; iface->get_child_for_display_name = g_winhttp_file_get_child_for_display_name; iface->set_display_name = g_winhttp_file_set_display_name; iface->query_info = g_winhttp_file_query_info; iface->read_fn = g_winhttp_file_read; iface->create = g_winhttp_file_create; #if 0 iface->replace = g_winhttp_file_replace; iface->delete_file = g_winhttp_file_delete; iface->make_directory = g_winhttp_file_make_directory; iface->copy = g_winhttp_file_copy; iface->move = g_winhttp_file_move; #endif }