Merge remote branch 'gvdb/master'
[platform/upstream/glib.git] / gio / win32 / gwinhttpfile.c
index 2e24116..5f2d6be 100644 (file)
@@ -1,5 +1,5 @@
 /* GIO - GLib Input, Output and Streaming Library
- * 
+ *
  * Copyright (C) 2006-2007 Red Hat, Inc.
  * Copyright (C) 2008 Novell, Inc.
  *
 
 #include "config.h"
 
+#include <stdio.h>
 #include <string.h>
+#include <wchar.h>
 
 #include "gfile.h"
 #include "gfileattribute.h"
+#include "gfileinfo.h"
 #include "gwinhttpfile.h"
 #include "gwinhttpfileinputstream.h"
 #include "gwinhttpfileoutputstream.h"
@@ -58,6 +61,8 @@ g_winhttp_file_finalize (GObject *object)
   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);
 }
 
@@ -74,13 +79,13 @@ g_winhttp_file_init (GWinHttpFile *winhttp)
 {
 }
 
-/**
+/*
  * _g_winhttp_file_new:
  * @vfs: GWinHttpVfs to use
  * @uri: URI of the GWinHttpFile to create.
- * 
+ *
  * Returns: new winhttp #GFile.
- **/
+ */
 GFile *
 _g_winhttp_file_new (GWinHttpVfs *vfs,
                      const char  *uri)
@@ -94,7 +99,7 @@ _g_winhttp_file_new (GWinHttpVfs *vfs,
     return NULL;
 
   file = g_object_new (G_TYPE_WINHTTP_FILE, NULL);
-  file->vfs = vfs;
+  file->vfs = g_object_ref (vfs);
 
   memset (&file->url, 0, sizeof (file->url));
   file->url.dwStructSize = sizeof (file->url);
@@ -105,7 +110,7 @@ _g_winhttp_file_new (GWinHttpVfs *vfs,
   file->url.dwUrlPathLength = 1;
   file->url.dwExtraInfoLength = 1;
 
-  if (!G_WINHTTP_VFS_GET_CLASS (vfs)->pWinHttpCrackUrl (wuri, 0, 0, &file->url))
+  if (!G_WINHTTP_VFS_GET_CLASS (vfs)->funcs->pWinHttpCrackUrl (wuri, 0, 0, &file->url))
     {
       g_free (wuri);
       return NULL;
@@ -118,7 +123,7 @@ _g_winhttp_file_new (GWinHttpVfs *vfs,
   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)->pWinHttpCrackUrl (wuri, 0, 0, &file->url))
+  if (!G_WINHTTP_VFS_GET_CLASS (vfs)->funcs->pWinHttpCrackUrl (wuri, 0, 0, &file->url))
     {
       g_free (file->url.lpszScheme);
       g_free (file->url.lpszHostName);
@@ -129,7 +134,7 @@ _g_winhttp_file_new (GWinHttpVfs *vfs,
       g_free (wuri);
       return NULL;
     }
-  
+
   g_free (wuri);
   return G_FILE (file);
 }
@@ -191,13 +196,13 @@ g_winhttp_file_get_uri (GFile *file)
   char *retval;
 
   len = 0;
-  if (!G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->pWinHttpCreateUrl (&winhttp_file->url, ICU_ESCAPE, NULL, &len) &&
+  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)->pWinHttpCreateUrl (&winhttp_file->url, ICU_ESCAPE, wuri, &len))
+  if (!G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpCreateUrl (&winhttp_file->url, ICU_ESCAPE, wuri, &len))
     {
       g_free (wuri);
       return NULL;
@@ -206,6 +211,17 @@ g_winhttp_file_get_uri (GFile *file)
   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;
 }
 
@@ -230,7 +246,7 @@ g_winhttp_file_get_parent (GFile *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)
     {
@@ -245,7 +261,7 @@ g_winhttp_file_get_parent (GFile *file)
 
   parent = _g_winhttp_file_new (winhttp_file->vfs, uri);
   g_free (uri);
-  
+
   return parent;
 }
 
@@ -282,12 +298,12 @@ g_winhttp_file_equal (GFile *file1,
 
   g_free (uri1);
   g_free (uri2);
-  
+
   return retval;
 }
 
 static const char *
-match_prefix (const char *path, 
+match_prefix (const char *path,
               const char *prefix)
 {
   int prefix_len;
@@ -295,10 +311,10 @@ match_prefix (const char *path,
   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;
 }
 
@@ -334,7 +350,7 @@ g_winhttp_file_get_relative_path (GFile *parent,
   char *retval;
 
   remainder = match_prefix (descendant_uri, parent_uri);
-  
+
   if (remainder != NULL && *remainder == '/')
     retval = g_strdup (remainder + 1);
   else
@@ -359,9 +375,19 @@ g_winhttp_file_resolve_relative_path (GFile      *file,
 
   if (*wnew_path != '/')
     {
-      wchar_t *tmp = g_new (wchar_t, wcslen (winhttp_file->url.lpszUrlPath) + 1 + wcslen (wnew_path) + 1);
-      wcscpy (tmp, winhttp_file->url.lpszUrlPath);
-      wcscat (tmp, L"/");
+      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);
@@ -371,15 +397,15 @@ g_winhttp_file_resolve_relative_path (GFile      *file,
   child = g_object_new (G_TYPE_WINHTTP_FILE, NULL);
   child->vfs = winhttp_file->vfs;
   child->url = winhttp_file->url;
-  child->url.lpszScheme = g_memdup (winhttp_file->url.lpszScheme, winhttp_file->url.dwSchemeLength*2);
-  child->url.lpszHostName = g_memdup (winhttp_file->url.lpszHostName, winhttp_file->url.dwHostNameLength*2);
-  child->url.lpszUserName = g_memdup (winhttp_file->url.lpszUserName, winhttp_file->url.dwUserNameLength*2);
-  child->url.lpszPassword = g_memdup (winhttp_file->url.lpszPassword, winhttp_file->url.dwPasswordLength*2);
+  child->url.lpszScheme = g_memdup (winhttp_file->url.lpszScheme, (winhttp_file->url.dwSchemeLength+1)*2);
+  child->url.lpszHostName = g_memdup (winhttp_file->url.lpszHostName, (winhttp_file->url.dwHostNameLength+1)*2);
+  child->url.lpszUserName = g_memdup (winhttp_file->url.lpszUserName, (winhttp_file->url.dwUserNameLength+1)*2);
+  child->url.lpszPassword = g_memdup (winhttp_file->url.lpszPassword, (winhttp_file->url.dwPasswordLength+1)*2);
   child->url.lpszUrlPath = wnew_path;
-  child->url.dwUrlPathLength = 2*(wcslen (wnew_path)+1);
+  child->url.dwUrlPathLength = wcslen (wnew_path);
   child->url.lpszExtraInfo = NULL;
   child->url.dwExtraInfoLength = 0;
-  
+
   return (GFile *) child;
 }
 
@@ -394,15 +420,14 @@ g_winhttp_file_get_child_for_display_name (GFile        *file,
   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,
+      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;
 }
 
@@ -412,13 +437,186 @@ g_winhttp_file_set_display_name (GFile         *file,
                                  GCancellable  *cancellable,
                                  GError       **error)
 {
-  g_set_error_literal (error, G_IO_ERROR,
-                       G_IO_ERROR_NOT_SUPPORTED,
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                        _("Operation not supported"));
 
   return NULL;
 }
 
+static time_t
+mktime_utc (SYSTEMTIME *t)
+{
+  time_t retval;
+
+  static const gint days_before[] =
+  {
+    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+  };
+
+  if (t->wMonth < 1 || t->wMonth > 12)
+    return (time_t) -1;
+
+  retval = (t->wYear - 1970) * 365;
+  retval += (t->wYear - 1968) / 4;
+  retval += days_before[t->wMonth-1] + t->wDay - 1;
+
+  if (t->wYear % 4 == 0 && t->wMonth < 3)
+    retval -= 1;
+
+  retval = ((((retval * 24) + t->wHour) * 60) + t->wMinute) * 60 + t->wSecond;
+
+  return retval;
+}
+
+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);
+
+  content_length = NULL;
+  if (_g_winhttp_query_header (winhttp_file->vfs,
+                               request,
+                               "HEAD request",
+                               WINHTTP_QUERY_CONTENT_LENGTH,
+                               &content_length,
+                               NULL))
+    {
+      gint64 cl;
+      int n;
+
+      if (swscanf (content_length, L"%I64d%n", &cl, &n) == 1 &&
+          n == wcslen (content_length))
+        g_file_info_set_size (info, cl);
+
+      g_free (content_length);
+    }
+
+  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)
+    {
+      GTimeVal tv;
+
+      tv.tv_sec = mktime_utc (&last_modified);
+      tv.tv_usec = last_modified.wMilliseconds * 1000;
+
+      g_file_info_set_modification_time (info, &tv);
+    }
+
+  g_file_attribute_matcher_unref (matcher);
+
+  return info;
+}
+
 static GFileInputStream *
 g_winhttp_file_read (GFile         *file,
                      GCancellable  *cancellable,
@@ -432,7 +630,7 @@ g_winhttp_file_read (GFile         *file,
       NULL,
     };
 
-  connection = G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->pWinHttpConnect
+  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,
@@ -440,17 +638,12 @@ g_winhttp_file_read (GFile         *file,
 
   if (connection == NULL)
     {
-      char *emsg = _g_winhttp_error_message (GetLastError ());
-
-      g_set_error (error, G_IO_ERROR,
-                   G_IO_ERROR_FAILED,
-                   "%s", emsg);
-      g_free (emsg);
+      _g_winhttp_set_error (error, GetLastError (), "HTTP connection");
 
       return NULL;
     }
 
-  request = G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->pWinHttpOpenRequest
+  request = G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->funcs->pWinHttpOpenRequest
     (connection,
      L"GET",
      winhttp_file->url.lpszUrlPath,
@@ -461,12 +654,7 @@ g_winhttp_file_read (GFile         *file,
 
   if (request == NULL)
     {
-      char *emsg = _g_winhttp_error_message (GetLastError ());
-
-      g_set_error (error, G_IO_ERROR,
-                   G_IO_ERROR_FAILED,
-                   "%s", emsg);
-      g_free (emsg);
+      _g_winhttp_set_error (error, GetLastError (), "GET request");
 
       return NULL;
     }
@@ -481,9 +669,9 @@ g_winhttp_file_create (GFile             *file,
                        GError           **error)
 {
   GWinHttpFile *winhttp_file = G_WINHTTP_FILE (file);
-  HINTERNET connection, request;
+  HINTERNET connection;
 
-  connection = G_WINHTTP_VFS_GET_CLASS (winhttp_file->vfs)->pWinHttpConnect
+  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,
@@ -491,12 +679,7 @@ g_winhttp_file_create (GFile             *file,
 
   if (connection == NULL)
     {
-      char *emsg = _g_winhttp_error_message (GetLastError ());
-
-      g_set_error (error, G_IO_ERROR,
-                   G_IO_ERROR_FAILED,
-                   "%s", emsg);
-      g_free (emsg);
+      _g_winhttp_set_error (error, GetLastError (), "HTTP connection");
 
       return NULL;
     }
@@ -550,7 +733,8 @@ g_winhttp_file_copy (GFile                  *source,
                      GError                **error)
 {
   /* Fall back to default copy?? */
-  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Copy not supported");
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                       "Copy not supported");
 
   return FALSE;
 }
@@ -590,6 +774,7 @@ g_winhttp_file_file_iface_init (GFileIface *iface)
   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