1/*
2 * magic.c:  wrappers around libmagic
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27/*** Includes. ***/
28
29#include <apr_lib.h>
30#include <apr_file_info.h>
31
32#include "svn_io.h"
33#include "svn_types.h"
34#include "svn_pools.h"
35#include "svn_error.h"
36#include "svn_config.h"
37#include "svn_hash.h"
38
39#include "svn_private_config.h"
40
41#include "private/svn_magic.h"
42
43#ifdef SVN_HAVE_LIBMAGIC
44#include <magic.h>
45#endif
46
47struct svn_magic__cookie_t {
48#ifdef SVN_HAVE_LIBMAGIC
49  magic_t magic;
50#else
51  char dummy;
52#endif
53};
54
55#ifdef SVN_HAVE_LIBMAGIC
56/* Close the magic database. */
57static apr_status_t
58close_magic_cookie(void *baton)
59{
60  svn_magic__cookie_t *mc = (svn_magic__cookie_t*)baton;
61  magic_close(mc->magic);
62  return APR_SUCCESS;
63}
64#endif
65
66svn_error_t *
67svn_magic__init(svn_magic__cookie_t **magic_cookie,
68                apr_hash_t *config,
69                apr_pool_t *result_pool)
70{
71  svn_magic__cookie_t *mc = NULL;
72
73#ifdef SVN_HAVE_LIBMAGIC
74  if (config)
75    {
76      svn_boolean_t enable;
77      svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
78
79      SVN_ERR(svn_config_get_bool(cfg, &enable,
80                                  SVN_CONFIG_SECTION_MISCELLANY,
81                                  SVN_CONFIG_OPTION_ENABLE_MAGIC_FILE,
82                                  TRUE));
83      if (!enable)
84        {
85          *magic_cookie = NULL;
86          return SVN_NO_ERROR;
87        }
88    }
89
90  mc = apr_palloc(result_pool, sizeof(*mc));
91
92  /* Initialise libmagic. */
93#ifndef MAGIC_MIME_TYPE
94  /* Some old versions of libmagic don't support MAGIC_MIME_TYPE.
95   * We can use MAGIC_MIME instead. It returns more than we need
96   * but we can work around that (see below). */
97  mc->magic = magic_open(MAGIC_MIME | MAGIC_ERROR);
98#else
99  mc->magic = magic_open(MAGIC_MIME_TYPE | MAGIC_ERROR);
100#endif
101  if (mc->magic)
102    {
103      /* This loads the default magic database.
104       * Point the MAGIC environment variable at your favourite .mgc
105       * file to load a non-default database. */
106      if (magic_load(mc->magic, NULL) == -1)
107        {
108          magic_close(mc->magic);
109          mc = NULL;
110        }
111      else
112        apr_pool_cleanup_register(result_pool, mc, close_magic_cookie,
113                                  apr_pool_cleanup_null);
114    }
115#endif
116
117  *magic_cookie = mc;
118
119  return SVN_NO_ERROR;
120}
121
122svn_error_t *
123svn_magic__detect_binary_mimetype(const char **mimetype,
124                                  const char *local_abspath,
125                                  svn_magic__cookie_t *magic_cookie,
126                                  apr_pool_t *result_pool,
127                                  apr_pool_t *scratch_pool)
128{
129  const char *magic_mimetype = NULL;
130#ifdef SVN_HAVE_LIBMAGIC
131  apr_finfo_t finfo;
132
133  /* Do not ask libmagic for the mime-types of empty files.
134   * This prevents mime-types like "application/x-empty" from making
135   * Subversion treat empty files as binary. */
136  SVN_ERR(svn_io_stat(&finfo, local_abspath, APR_FINFO_SIZE, scratch_pool));
137  if (finfo.size > 0)
138    {
139      magic_mimetype = magic_file(magic_cookie->magic, local_abspath);
140      if (magic_mimetype)
141        {
142          /* Only return binary mime-types. */
143          if (strncmp(magic_mimetype, "text/", 5) == 0)
144            magic_mimetype = NULL;
145          else
146            {
147              svn_error_t *err;
148#ifndef MAGIC_MIME_TYPE
149              char *p;
150
151              /* Strip off trailing stuff like " charset=ascii". */
152              p = strchr(magic_mimetype, ' ');
153              if (p)
154                *p = '\0';
155#endif
156              /* Make sure we got a valid mime type. */
157              err = svn_mime_type_validate(magic_mimetype, scratch_pool);
158              if (err)
159                {
160                  if (err->apr_err == SVN_ERR_BAD_MIME_TYPE)
161                    {
162                      svn_error_clear(err);
163                      magic_mimetype = NULL;
164                    }
165                  else
166                    return svn_error_trace(err);
167                }
168              else
169                {
170                  /* The string is allocated from memory managed by libmagic
171                   * so we must copy it to the result pool. */
172                  magic_mimetype = apr_pstrdup(result_pool, magic_mimetype);
173                }
174            }
175        }
176    }
177#endif
178
179  *mimetype = magic_mimetype;
180  return SVN_NO_ERROR;
181}
182