1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6'''python %prog [options] platform chromium_os_flag template
7
8platform specifies which platform source is being generated for
9  and can be one of (win, mac, linux)
10chromium_os_flag should be 1 if this is a Chromium OS build
11template is the path to a .json policy template file.'''
12
13from __future__ import with_statement
14from functools import partial
15import json
16from optparse import OptionParser
17import re
18import sys
19import textwrap
20import types
21
22
23CHROME_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome'
24CHROMIUM_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Chromium'
25
26
27class PolicyDetails:
28  """Parses a policy template and caches all its details."""
29
30  # Maps policy types to a tuple with 3 other types:
31  # - the equivalent base::Value::Type or 'TYPE_EXTERNAL' if the policy
32  #   references external data
33  # - the equivalent Protobuf field type
34  # - the name of one of the protobufs for shared policy types
35  # TODO(joaodasilva): refactor the 'dict' type into a more generic 'json' type
36  # that can also be used to represent lists of other JSON objects.
37  TYPE_MAP = {
38    'dict':         ('TYPE_DICTIONARY',   'string',       'String'),
39    'external':     ('TYPE_EXTERNAL',     'string',       'String'),
40    'int':          ('TYPE_INTEGER',      'int64',        'Integer'),
41    'int-enum':     ('TYPE_INTEGER',      'int64',        'Integer'),
42    'list':         ('TYPE_LIST',         'StringList',   'StringList'),
43    'main':         ('TYPE_BOOLEAN',      'bool',         'Boolean'),
44    'string':       ('TYPE_STRING',       'string',       'String'),
45    'string-enum':  ('TYPE_STRING',       'string',       'String'),
46  }
47
48  class EnumItem:
49    def __init__(self, item):
50      self.caption = PolicyDetails._RemovePlaceholders(item['caption'])
51      self.value = item['value']
52
53  def __init__(self, policy, os, is_chromium_os):
54    self.id = policy['id']
55    self.name = policy['name']
56    self.is_deprecated = policy.get('deprecated', False)
57    self.is_device_only = policy.get('device_only', False)
58    self.schema = policy.get('schema', {})
59
60    expected_platform = 'chrome_os' if is_chromium_os else os.lower()
61    self.platforms = []
62    for platform, version in [ p.split(':') for p in policy['supported_on'] ]:
63      if not version.endswith('-'):
64        continue
65
66      if platform.startswith('chrome.'):
67        platform_sub = platform[7:]
68        if platform_sub == '*':
69          self.platforms.extend(['win', 'mac', 'linux'])
70        else:
71          self.platforms.append(platform_sub)
72      else:
73        self.platforms.append(platform)
74
75    self.platforms.sort()
76    self.is_supported = expected_platform in self.platforms
77
78    if not PolicyDetails.TYPE_MAP.has_key(policy['type']):
79      raise NotImplementedError('Unknown policy type for %s: %s' %
80                                (policy['name'], policy['type']))
81    self.policy_type, self.protobuf_type, self.policy_protobuf_type = \
82        PolicyDetails.TYPE_MAP[policy['type']]
83    self.schema = policy['schema']
84
85    self.desc = '\n'.join(
86        map(str.strip,
87            PolicyDetails._RemovePlaceholders(policy['desc']).splitlines()))
88    self.caption = PolicyDetails._RemovePlaceholders(policy['caption'])
89    self.max_size = policy.get('max_size', 0)
90
91    items = policy.get('items')
92    if items is None:
93      self.items = None
94    else:
95      self.items = [ PolicyDetails.EnumItem(entry) for entry in items ]
96
97  PH_PATTERN = re.compile('<ph[^>]*>([^<]*|[^<]*<ex>([^<]*)</ex>[^<]*)</ph>')
98
99  # Simplistic grit placeholder stripper.
100  @staticmethod
101  def _RemovePlaceholders(text):
102    result = ''
103    pos = 0
104    for m in PolicyDetails.PH_PATTERN.finditer(text):
105      result += text[pos:m.start(0)]
106      result += m.group(2) or m.group(1)
107      pos = m.end(0)
108    result += text[pos:]
109    return result
110
111
112def main():
113  parser = OptionParser(usage=__doc__)
114  parser.add_option('--pch', '--policy-constants-header', dest='header_path',
115                    help='generate header file of policy constants',
116                    metavar='FILE')
117  parser.add_option('--pcc', '--policy-constants-source', dest='source_path',
118                    help='generate source file of policy constants',
119                    metavar='FILE')
120  parser.add_option('--cpp', '--cloud-policy-protobuf',
121                    dest='cloud_policy_proto_path',
122                    help='generate cloud policy protobuf file',
123                    metavar='FILE')
124  parser.add_option('--csp', '--chrome-settings-protobuf',
125                    dest='chrome_settings_proto_path',
126                    help='generate chrome settings protobuf file',
127                    metavar='FILE')
128  parser.add_option('--cpd', '--cloud-policy-decoder',
129                    dest='cloud_policy_decoder_path',
130                    help='generate C++ code decoding the cloud policy protobuf',
131                    metavar='FILE')
132
133  (opts, args) = parser.parse_args()
134
135  if len(args) != 3:
136    print 'exactly platform, chromium_os flag and input file must be specified.'
137    parser.print_help()
138    return 2
139
140  os = args[0]
141  is_chromium_os = args[1] == '1'
142  template_file_name = args[2]
143
144  template_file_contents = _LoadJSONFile(template_file_name)
145  policy_details = [ PolicyDetails(policy, os, is_chromium_os)
146                     for policy in _Flatten(template_file_contents) ]
147  sorted_policy_details = sorted(policy_details, key=lambda policy: policy.name)
148
149  def GenerateFile(path, writer, sorted=False):
150    if path:
151      with open(path, 'w') as f:
152        _OutputGeneratedWarningHeader(f, template_file_name)
153        writer(sorted and sorted_policy_details or policy_details, os, f)
154
155  GenerateFile(opts.header_path, _WritePolicyConstantHeader, sorted=True)
156  GenerateFile(opts.source_path, _WritePolicyConstantSource, sorted=True)
157  GenerateFile(opts.cloud_policy_proto_path, _WriteCloudPolicyProtobuf)
158  GenerateFile(opts.chrome_settings_proto_path, _WriteChromeSettingsProtobuf)
159  GenerateFile(opts.cloud_policy_decoder_path, _WriteCloudPolicyDecoder)
160
161  return 0
162
163
164#------------------ shared helpers ---------------------------------#
165
166def _OutputGeneratedWarningHeader(f, template_file_path):
167  f.write('//\n'
168          '// DO NOT MODIFY THIS FILE DIRECTLY!\n'
169          '// IT IS GENERATED BY generate_policy_source.py\n'
170          '// FROM ' + template_file_path + '\n'
171          '//\n\n')
172
173
174COMMENT_WRAPPER = textwrap.TextWrapper()
175COMMENT_WRAPPER.width = 80
176COMMENT_WRAPPER.initial_indent = '// '
177COMMENT_WRAPPER.subsequent_indent = '// '
178COMMENT_WRAPPER.replace_whitespace = False
179
180
181# Writes a comment, each line prefixed by // and wrapped to 80 spaces.
182def _OutputComment(f, comment):
183  for line in comment.splitlines():
184    if len(line) == 0:
185      f.write('//')
186    else:
187      f.write(COMMENT_WRAPPER.fill(line))
188    f.write('\n')
189
190
191# Returns an iterator over all the policies in |template_file_contents|.
192def _Flatten(template_file_contents):
193  for policy in template_file_contents['policy_definitions']:
194    if policy['type'] == 'group':
195      for sub_policy in policy['policies']:
196        yield sub_policy
197    else:
198      yield policy
199
200
201def _LoadJSONFile(json_file):
202  with open(json_file, 'r') as f:
203    text = f.read()
204  return eval(text)
205
206
207#------------------ policy constants header ------------------------#
208
209def _WritePolicyConstantHeader(policies, os, f):
210  f.write('#ifndef CHROME_COMMON_POLICY_CONSTANTS_H_\n'
211          '#define CHROME_COMMON_POLICY_CONSTANTS_H_\n'
212          '\n'
213          '#include <string>\n'
214          '\n'
215          '#include "base/basictypes.h"\n'
216          '#include "base/values.h"\n'
217          '#include "components/policy/core/common/policy_details.h"\n'
218          '\n'
219          'namespace policy {\n'
220          '\n'
221          'namespace internal {\n'
222          'struct SchemaData;\n'
223          '}\n\n')
224
225  if os == 'win':
226    f.write('// The windows registry path where Chrome policy '
227            'configuration resides.\n'
228            'extern const wchar_t kRegistryChromePolicyKey[];\n')
229
230  f.write('// Returns the PolicyDetails for |policy| if |policy| is a known\n'
231          '// Chrome policy, otherwise returns NULL.\n'
232          'const PolicyDetails* GetChromePolicyDetails('
233              'const std::string& policy);\n'
234          '\n'
235          '// Returns the schema data of the Chrome policy schema.\n'
236          'const internal::SchemaData* GetChromeSchemaData();\n'
237          '\n')
238  f.write('// Key names for the policy settings.\n'
239          'namespace key {\n\n')
240  for policy in policies:
241    # TODO(joaodasilva): Include only supported policies in
242    # configuration_policy_handler.cc and configuration_policy_handler_list.cc
243    # so that these names can be conditional on 'policy.is_supported'.
244    # http://crbug.com/223616
245    f.write('extern const char k' + policy.name + '[];\n')
246  f.write('\n}  // namespace key\n\n'
247          '}  // namespace policy\n\n'
248          '#endif  // CHROME_COMMON_POLICY_CONSTANTS_H_\n')
249
250
251#------------------ policy constants source ------------------------#
252
253# A mapping of the simple schema types to base::Value::Types.
254SIMPLE_SCHEMA_NAME_MAP = {
255  'boolean': 'TYPE_BOOLEAN',
256  'integer': 'TYPE_INTEGER',
257  'null'   : 'TYPE_NULL',
258  'number' : 'TYPE_DOUBLE',
259  'string' : 'TYPE_STRING',
260}
261
262class SchemaNodesGenerator:
263  """Builds the internal structs to represent a JSON schema."""
264
265  def __init__(self, shared_strings):
266    """Creates a new generator.
267
268    |shared_strings| is a map of strings to a C expression that evaluates to
269    that string at runtime. This mapping can be used to reuse existing string
270    constants."""
271    self.shared_strings = shared_strings
272    self.schema_nodes = []
273    self.property_nodes = []
274    self.properties_nodes = []
275    self.restriction_nodes = []
276    self.int_enums = []
277    self.string_enums = []
278    self.simple_types = {
279      'boolean': None,
280      'integer': None,
281      'null': None,
282      'number': None,
283      'string': None,
284    }
285    self.stringlist_type = None
286    self.ranges = {}
287    self.id_map = {}
288
289  def GetString(self, s):
290    if s in self.shared_strings:
291      return self.shared_strings[s]
292    # Generate JSON escaped string, which is slightly different from desired
293    # C/C++ escaped string. Known differences includes unicode escaping format.
294    return json.dumps(s)
295
296  def AppendSchema(self, type, extra, comment=''):
297    index = len(self.schema_nodes)
298    self.schema_nodes.append((type, extra, comment))
299    return index
300
301  def AppendRestriction(self, first, second):
302    r = (str(first), str(second))
303    if not r in self.ranges:
304      self.ranges[r] = len(self.restriction_nodes)
305      self.restriction_nodes.append(r)
306    return self.ranges[r]
307
308  def GetSimpleType(self, name):
309    if self.simple_types[name] == None:
310      self.simple_types[name] = self.AppendSchema(
311          SIMPLE_SCHEMA_NAME_MAP[name],
312          -1,
313          'simple type: ' + name)
314    return self.simple_types[name]
315
316  def GetStringList(self):
317    if self.stringlist_type == None:
318      self.stringlist_type = self.AppendSchema(
319          'TYPE_LIST',
320          self.GetSimpleType('string'),
321          'simple type: stringlist')
322    return self.stringlist_type
323
324  def SchemaHaveRestriction(self, schema):
325    return any(keyword in schema for keyword in
326        ['minimum', 'maximum', 'enum', 'pattern'])
327
328  def IsConsecutiveInterval(self, seq):
329    sortedSeq = sorted(seq)
330    return all(sortedSeq[i] + 1 == sortedSeq[i + 1]
331               for i in xrange(len(sortedSeq) - 1))
332
333  def GetEnumIntegerType(self, schema, name):
334    assert all(type(x) == int for x in schema['enum'])
335    possible_values = schema['enum']
336    if self.IsConsecutiveInterval(possible_values):
337      index = self.AppendRestriction(max(possible_values), min(possible_values))
338      return self.AppendSchema('TYPE_INTEGER', index,
339          'integer with enumeration restriction (use range instead): %s' % name)
340    offset_begin = len(self.int_enums)
341    self.int_enums += possible_values
342    offset_end = len(self.int_enums)
343    return self.AppendSchema('TYPE_INTEGER',
344        self.AppendRestriction(offset_begin, offset_end),
345        'integer with enumeration restriction: %s' % name)
346
347  def GetEnumStringType(self, schema, name):
348    assert all(type(x) == str for x in schema['enum'])
349    offset_begin = len(self.string_enums)
350    self.string_enums += schema['enum']
351    offset_end = len(self.string_enums)
352    return self.AppendSchema('TYPE_STRING',
353        self.AppendRestriction(offset_begin, offset_end),
354        'string with enumeration restriction: %s' % name)
355
356  def GetEnumType(self, schema, name):
357    if len(schema['enum']) == 0:
358      raise RuntimeError('Empty enumeration in %s' % name)
359    elif schema['type'] == 'integer':
360      return self.GetEnumIntegerType(schema, name)
361    elif schema['type'] == 'string':
362      return self.GetEnumStringType(schema, name)
363    else:
364      raise RuntimeError('Unknown enumeration type in %s' % name)
365
366  def GetPatternType(self, schema, name):
367    if schema['type'] != 'string':
368      raise RuntimeError('Unknown pattern type in %s' % name)
369    pattern = schema['pattern']
370    # Try to compile the pattern to validate it, note that the syntax used
371    # here might be slightly different from re2.
372    # TODO(binjin): Add a python wrapper of re2 and use it here.
373    re.compile(pattern)
374    index = len(self.string_enums);
375    self.string_enums.append(pattern);
376    return self.AppendSchema('TYPE_STRING',
377        self.AppendRestriction(index, index),
378        'string with pattern restriction: %s' % name);
379
380  def GetRangedType(self, schema, name):
381    if schema['type'] != 'integer':
382      raise RuntimeError('Unknown ranged type in %s' % name)
383    min_value_set, max_value_set = False, False
384    if 'minimum' in schema:
385      min_value = int(schema['minimum'])
386      min_value_set = True
387    if 'maximum' in schema:
388      max_value = int(schema['minimum'])
389      max_value_set = True
390    if min_value_set and max_value_set and min_value > max_value:
391      raise RuntimeError('Invalid ranged type in %s' % name)
392    index = self.AppendRestriction(
393        str(max_value) if max_value_set else 'INT_MAX',
394        str(min_value) if min_value_set else 'INT_MIN')
395    return self.AppendSchema('TYPE_INTEGER',
396        index,
397        'integer with ranged restriction: %s' % name)
398
399  def Generate(self, schema, name):
400    """Generates the structs for the given schema.
401
402    |schema|: a valid JSON schema in a dictionary.
403    |name|: the name of the current node, for the generated comments."""
404    if schema.has_key('$ref'):
405      if schema.has_key('id'):
406        raise RuntimeError("Schemas with a $ref can't have an id")
407      if not isinstance(schema['$ref'], types.StringTypes):
408        raise RuntimeError("$ref attribute must be a string")
409      return schema['$ref']
410    if schema['type'] in self.simple_types:
411      if not self.SchemaHaveRestriction(schema):
412        # Simple types use shared nodes.
413        return self.GetSimpleType(schema['type'])
414      elif 'enum' in schema:
415        return self.GetEnumType(schema, name)
416      elif 'pattern' in schema:
417        return self.GetPatternType(schema, name)
418      else:
419        return self.GetRangedType(schema, name)
420
421    if schema['type'] == 'array':
422      # Special case for lists of strings, which is a common policy type.
423      # The 'type' may be missing if the schema has a '$ref' attribute.
424      if schema['items'].get('type', '') == 'string':
425        return self.GetStringList()
426      return self.AppendSchema('TYPE_LIST',
427          self.GenerateAndCollectID(schema['items'], 'items of ' + name))
428    elif schema['type'] == 'object':
429      # Reserve an index first, so that dictionaries come before their
430      # properties. This makes sure that the root node is the first in the
431      # SchemaNodes array.
432      index = self.AppendSchema('TYPE_DICTIONARY', -1)
433
434      if 'additionalProperties' in schema:
435        additionalProperties = self.GenerateAndCollectID(
436            schema['additionalProperties'],
437            'additionalProperties of ' + name)
438      else:
439        additionalProperties = -1
440
441      # Properties must be sorted by name, for the binary search lookup.
442      # Note that |properties| must be evaluated immediately, so that all the
443      # recursive calls to Generate() append the necessary child nodes; if
444      # |properties| were a generator then this wouldn't work.
445      sorted_properties = sorted(schema.get('properties', {}).items())
446      properties = [
447          (self.GetString(key), self.GenerateAndCollectID(subschema, key))
448          for key, subschema in sorted_properties ]
449
450      pattern_properties = []
451      for pattern, subschema in schema.get('patternProperties', {}).items():
452        pattern_properties.append((self.GetString(pattern),
453            self.GenerateAndCollectID(subschema, pattern)));
454
455      begin = len(self.property_nodes)
456      self.property_nodes += properties
457      end = len(self.property_nodes)
458      self.property_nodes += pattern_properties
459      pattern_end = len(self.property_nodes)
460
461      if index == 0:
462        self.root_properties_begin = begin
463        self.root_properties_end = end
464
465      extra = len(self.properties_nodes)
466      self.properties_nodes.append((begin, end, pattern_end,
467          additionalProperties, name))
468
469      # Set the right data at |index| now.
470      self.schema_nodes[index] = ('TYPE_DICTIONARY', extra, name)
471      return index
472    else:
473      assert False
474
475  def GenerateAndCollectID(self, schema, name):
476    """A wrapper of Generate(), will take the return value, check and add 'id'
477    attribute to self.id_map. The wrapper needs to be used for every call to
478    Generate().
479    """
480    index = self.Generate(schema, name)
481    if not schema.has_key('id'):
482      return index
483    id_str = schema['id']
484    if self.id_map.has_key(id_str):
485      raise RuntimeError('Duplicated id: ' + id_str)
486    self.id_map[id_str] = index
487    return index
488
489  def Write(self, f):
490    """Writes the generated structs to the given file.
491
492    |f| an open file to write to."""
493    f.write('const internal::SchemaNode kSchemas[] = {\n'
494            '//  Type                          Extra\n')
495    for type, extra, comment in self.schema_nodes:
496      type += ','
497      f.write('  { base::Value::%-18s %3d },  // %s\n' % (type, extra, comment))
498    f.write('};\n\n')
499
500    if self.property_nodes:
501      f.write('const internal::PropertyNode kPropertyNodes[] = {\n'
502              '//  Property                                          #Schema\n')
503      for key, schema in self.property_nodes:
504        key += ','
505        f.write('  { %-50s %6d },\n' % (key, schema))
506      f.write('};\n\n')
507
508    if self.properties_nodes:
509      f.write('const internal::PropertiesNode kProperties[] = {\n'
510              '//  Begin    End  PatternEnd Additional Properties\n')
511      for node in self.properties_nodes:
512        f.write('  { %5d, %5d, %10d, %5d },  // %s\n' % node)
513      f.write('};\n\n')
514
515    if self.restriction_nodes:
516      f.write('const internal::RestrictionNode kRestrictionNodes[] = {\n')
517      f.write('//   FIRST, SECOND\n')
518      for first, second in self.restriction_nodes:
519        f.write('  {{ %-8s %4s}},\n' % (first + ',', second))
520      f.write('};\n\n')
521
522    if self.int_enums:
523      f.write('const int kIntegerEnumerations[] = {\n')
524      for possible_values in self.int_enums:
525        f.write('  %d,\n' % possible_values)
526      f.write('};\n\n')
527
528    if self.string_enums:
529      f.write('const char* kStringEnumerations[] = {\n')
530      for possible_values in self.string_enums:
531        f.write('  %s,\n' % self.GetString(possible_values))
532      f.write('};\n\n')
533
534    f.write('const internal::SchemaData kChromeSchemaData = {\n'
535            '  kSchemas,\n')
536    f.write('  kPropertyNodes,\n' if self.property_nodes else '  NULL,\n')
537    f.write('  kProperties,\n' if self.properties_nodes else '  NULL,\n')
538    f.write('  kRestrictionNodes,\n' if self.restriction_nodes else '  NULL,\n')
539    f.write('  kIntegerEnumerations,\n' if self.int_enums else '  NULL,\n')
540    f.write('  kStringEnumerations,\n' if self.string_enums else '  NULL,\n')
541    f.write('};\n\n')
542
543  def GetByID(self, id_str):
544    if not isinstance(id_str, types.StringTypes):
545      return id_str
546    if not self.id_map.has_key(id_str):
547      raise RuntimeError('Invalid $ref: ' + id_str)
548    return self.id_map[id_str]
549
550  def ResolveID(self, index, params):
551    return params[:index] + (self.GetByID(params[index]),) + params[index+1:]
552
553  def ResolveReferences(self):
554    """Resolve reference mapping, required to be called after Generate()
555
556    After calling Generate(), the type of indices used in schema structures
557    might be either int or string. An int type suggests that it's a resolved
558    index, but for string type it's unresolved. Resolving a reference is as
559    simple as looking up for corresponding ID in self.id_map, and replace the
560    old index with the mapped index.
561    """
562    self.schema_nodes = map(partial(self.ResolveID, 1), self.schema_nodes)
563    self.property_nodes = map(partial(self.ResolveID, 1), self.property_nodes)
564    self.properties_nodes = map(partial(self.ResolveID, 3),
565        self.properties_nodes)
566
567def _WritePolicyConstantSource(policies, os, f):
568  f.write('#include "policy/policy_constants.h"\n'
569          '\n'
570          '#include <algorithm>\n'
571          '#include <climits>\n'
572          '\n'
573          '#include "base/logging.h"\n'
574          '#include "components/policy/core/common/schema_internal.h"\n'
575          '\n'
576          'namespace policy {\n'
577          '\n'
578          'namespace {\n'
579          '\n')
580
581  # Generate the Chrome schema.
582  chrome_schema = {
583    'type': 'object',
584    'properties': {},
585  }
586  shared_strings = {}
587  for policy in policies:
588    shared_strings[policy.name] = "key::k%s" % policy.name
589    if policy.is_supported:
590      chrome_schema['properties'][policy.name] = policy.schema
591
592  # Note: this list must be kept in sync with the known property list of the
593  # Chrome schema, so that binary seaching in the PropertyNode array gets the
594  # right index on this array as well. See the implementation of
595  # GetChromePolicyDetails() below.
596  f.write('const PolicyDetails kChromePolicyDetails[] = {\n'
597          '//  is_deprecated  is_device_policy  id    max_external_data_size\n')
598  for policy in policies:
599    if policy.is_supported:
600      f.write('  { %-14s %-16s %3s, %24s },\n' % (
601                  'true,' if policy.is_deprecated else 'false,',
602                  'true,' if policy.is_device_only else 'false,',
603                  policy.id,
604                  policy.max_size))
605  f.write('};\n\n')
606
607  schema_generator = SchemaNodesGenerator(shared_strings)
608  schema_generator.GenerateAndCollectID(chrome_schema, 'root node')
609  schema_generator.ResolveReferences()
610  schema_generator.Write(f)
611
612  f.write('bool CompareKeys(const internal::PropertyNode& node,\n'
613          '                 const std::string& key) {\n'
614          '  return node.key < key;\n'
615          '}\n\n')
616
617  f.write('}  // namespace\n\n')
618
619  if os == 'win':
620    f.write('#if defined(GOOGLE_CHROME_BUILD)\n'
621            'const wchar_t kRegistryChromePolicyKey[] = '
622            'L"' + CHROME_POLICY_KEY + '";\n'
623            '#else\n'
624            'const wchar_t kRegistryChromePolicyKey[] = '
625            'L"' + CHROMIUM_POLICY_KEY + '";\n'
626            '#endif\n\n')
627
628  f.write('const internal::SchemaData* GetChromeSchemaData() {\n'
629          '  return &kChromeSchemaData;\n'
630          '}\n\n')
631
632  f.write('const PolicyDetails* GetChromePolicyDetails('
633              'const std::string& policy) {\n'
634          '  // First index in kPropertyNodes of the Chrome policies.\n'
635          '  static const int begin_index = %s;\n'
636          '  // One-past-the-end of the Chrome policies in kPropertyNodes.\n'
637          '  static const int end_index = %s;\n' %
638          (schema_generator.root_properties_begin,
639           schema_generator.root_properties_end))
640  f.write('  const internal::PropertyNode* begin =\n'
641          '      kPropertyNodes + begin_index;\n'
642          '  const internal::PropertyNode* end = kPropertyNodes + end_index;\n'
643          '  const internal::PropertyNode* it =\n'
644          '      std::lower_bound(begin, end, policy, CompareKeys);\n'
645          '  if (it == end || it->key != policy)\n'
646          '    return NULL;\n'
647          '  // This relies on kPropertyNodes from begin_index to end_index\n'
648          '  // having exactly the same policies (and in the same order) as\n'
649          '  // kChromePolicyDetails, so that binary searching on the first\n'
650          '  // gets the same results as a binary search on the second would.\n'
651          '  // However, kPropertyNodes has the policy names and\n'
652          '  // kChromePolicyDetails doesn\'t, so we obtain the index into\n'
653          '  // the second array by searching the first to avoid duplicating\n'
654          '  // the policy name pointers.\n'
655          '  // Offsetting |it| from |begin| here obtains the index we\'re\n'
656          '  // looking for.\n'
657          '  size_t index = it - begin;\n'
658          '  CHECK_LT(index, arraysize(kChromePolicyDetails));\n'
659          '  return kChromePolicyDetails + index;\n'
660          '}\n\n')
661
662  f.write('namespace key {\n\n')
663  for policy in policies:
664    # TODO(joaodasilva): Include only supported policies in
665    # configuration_policy_handler.cc and configuration_policy_handler_list.cc
666    # so that these names can be conditional on 'policy.is_supported'.
667    # http://crbug.com/223616
668    f.write('const char k{name}[] = "{name}";\n'.format(name=policy.name))
669  f.write('\n}  // namespace key\n\n'
670          '}  // namespace policy\n')
671
672
673#------------------ policy protobufs --------------------------------#
674
675CHROME_SETTINGS_PROTO_HEAD = '''
676syntax = "proto2";
677
678option optimize_for = LITE_RUNTIME;
679
680package enterprise_management;
681
682// For StringList and PolicyOptions.
683import "cloud_policy.proto";
684
685'''
686
687
688CLOUD_POLICY_PROTO_HEAD = '''
689syntax = "proto2";
690
691option optimize_for = LITE_RUNTIME;
692
693package enterprise_management;
694
695message StringList {
696  repeated string entries = 1;
697}
698
699message PolicyOptions {
700  enum PolicyMode {
701    // The given settings are applied regardless of user choice.
702    MANDATORY = 0;
703    // The user may choose to override the given settings.
704    RECOMMENDED = 1;
705    // No policy value is present and the policy should be ignored.
706    UNSET = 2;
707  }
708  optional PolicyMode mode = 1 [default = MANDATORY];
709}
710
711message BooleanPolicyProto {
712  optional PolicyOptions policy_options = 1;
713  optional bool value = 2;
714}
715
716message IntegerPolicyProto {
717  optional PolicyOptions policy_options = 1;
718  optional int64 value = 2;
719}
720
721message StringPolicyProto {
722  optional PolicyOptions policy_options = 1;
723  optional string value = 2;
724}
725
726message StringListPolicyProto {
727  optional PolicyOptions policy_options = 1;
728  optional StringList value = 2;
729}
730
731'''
732
733
734# Field IDs [1..RESERVED_IDS] will not be used in the wrapping protobuf.
735RESERVED_IDS = 2
736
737
738def _WritePolicyProto(f, policy, fields):
739  _OutputComment(f, policy.caption + '\n\n' + policy.desc)
740  if policy.items is not None:
741    _OutputComment(f, '\nValid values:')
742    for item in policy.items:
743      _OutputComment(f, '  %s: %s' % (str(item.value), item.caption))
744  if policy.policy_type == 'TYPE_DICTIONARY':
745    _OutputComment(f, '\nValue schema:\n%s' %
746                   json.dumps(policy.schema, sort_keys=True, indent=4,
747                              separators=(',', ': ')))
748  _OutputComment(f, '\nSupported on: %s' % ', '.join(policy.platforms))
749  f.write('message %sProto {\n' % policy.name)
750  f.write('  optional PolicyOptions policy_options = 1;\n')
751  f.write('  optional %s %s = 2;\n' % (policy.protobuf_type, policy.name))
752  f.write('}\n\n')
753  fields += [ '  optional %sProto %s = %s;\n' %
754              (policy.name, policy.name, policy.id + RESERVED_IDS) ]
755
756
757def _WriteChromeSettingsProtobuf(policies, os, f):
758  f.write(CHROME_SETTINGS_PROTO_HEAD)
759
760  fields = []
761  f.write('// PBs for individual settings.\n\n')
762  for policy in policies:
763    # Note: this protobuf also gets the unsupported policies, since it's an
764    # exaustive list of all the supported user policies on any platform.
765    if not policy.is_device_only:
766      _WritePolicyProto(f, policy, fields)
767
768  f.write('// --------------------------------------------------\n'
769          '// Big wrapper PB containing the above groups.\n\n'
770          'message ChromeSettingsProto {\n')
771  f.write(''.join(fields))
772  f.write('}\n\n')
773
774
775def _WriteCloudPolicyProtobuf(policies, os, f):
776  f.write(CLOUD_POLICY_PROTO_HEAD)
777  f.write('message CloudPolicySettings {\n')
778  for policy in policies:
779    if policy.is_supported and not policy.is_device_only:
780      f.write('  optional %sPolicyProto %s = %s;\n' %
781              (policy.policy_protobuf_type, policy.name,
782               policy.id + RESERVED_IDS))
783  f.write('}\n\n')
784
785
786#------------------ protobuf decoder -------------------------------#
787
788CPP_HEAD = '''
789#include <limits>
790#include <string>
791
792#include "base/basictypes.h"
793#include "base/callback.h"
794#include "base/json/json_reader.h"
795#include "base/logging.h"
796#include "base/memory/scoped_ptr.h"
797#include "base/memory/weak_ptr.h"
798#include "base/values.h"
799#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
800#include "components/policy/core/common/external_data_fetcher.h"
801#include "components/policy/core/common/policy_map.h"
802#include "policy/policy_constants.h"
803#include "policy/proto/cloud_policy.pb.h"
804
805using google::protobuf::RepeatedPtrField;
806
807namespace policy {
808
809namespace em = enterprise_management;
810
811base::Value* DecodeIntegerValue(google::protobuf::int64 value) {
812  if (value < std::numeric_limits<int>::min() ||
813      value > std::numeric_limits<int>::max()) {
814    LOG(WARNING) << "Integer value " << value
815                 << " out of numeric limits, ignoring.";
816    return NULL;
817  }
818
819  return base::Value::CreateIntegerValue(static_cast<int>(value));
820}
821
822base::ListValue* DecodeStringList(const em::StringList& string_list) {
823  base::ListValue* list_value = new base::ListValue;
824  RepeatedPtrField<std::string>::const_iterator entry;
825  for (entry = string_list.entries().begin();
826       entry != string_list.entries().end(); ++entry) {
827    list_value->Append(base::Value::CreateStringValue(*entry));
828  }
829  return list_value;
830}
831
832base::Value* DecodeJson(const std::string& json) {
833  scoped_ptr<base::Value> root(
834      base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS));
835
836  if (!root)
837    LOG(WARNING) << "Invalid JSON string, ignoring: " << json;
838
839  // Accept any Value type that parsed as JSON, and leave it to the handler to
840  // convert and check the concrete type.
841  return root.release();
842}
843
844void DecodePolicy(const em::CloudPolicySettings& policy,
845                  base::WeakPtr<CloudExternalDataManager> external_data_manager,
846                  PolicyMap* map) {
847'''
848
849
850CPP_FOOT = '''}
851
852}  // namespace policy
853'''
854
855
856def _CreateValue(type, arg):
857  if type == 'TYPE_BOOLEAN':
858    return 'base::Value::CreateBooleanValue(%s)' % arg
859  elif type == 'TYPE_INTEGER':
860    return 'DecodeIntegerValue(%s)' % arg
861  elif type == 'TYPE_STRING':
862    return 'base::Value::CreateStringValue(%s)' % arg
863  elif type == 'TYPE_LIST':
864    return 'DecodeStringList(%s)' % arg
865  elif type == 'TYPE_DICTIONARY' or type == 'TYPE_EXTERNAL':
866    return 'DecodeJson(%s)' % arg
867  else:
868    raise NotImplementedError('Unknown type %s' % type)
869
870
871def _CreateExternalDataFetcher(type, name):
872  if type == 'TYPE_EXTERNAL':
873    return 'new ExternalDataFetcher(external_data_manager, key::k%s)' % name
874  return 'NULL'
875
876
877def _WritePolicyCode(f, policy):
878  membername = policy.name.lower()
879  proto_type = '%sPolicyProto' % policy.policy_protobuf_type
880  f.write('  if (policy.has_%s()) {\n' % membername)
881  f.write('    const em::%s& policy_proto = policy.%s();\n' %
882          (proto_type, membername))
883  f.write('    if (policy_proto.has_value()) {\n')
884  f.write('      PolicyLevel level = POLICY_LEVEL_MANDATORY;\n'
885          '      bool do_set = true;\n'
886          '      if (policy_proto.has_policy_options()) {\n'
887          '        do_set = false;\n'
888          '        switch(policy_proto.policy_options().mode()) {\n'
889          '          case em::PolicyOptions::MANDATORY:\n'
890          '            do_set = true;\n'
891          '            level = POLICY_LEVEL_MANDATORY;\n'
892          '            break;\n'
893          '          case em::PolicyOptions::RECOMMENDED:\n'
894          '            do_set = true;\n'
895          '            level = POLICY_LEVEL_RECOMMENDED;\n'
896          '            break;\n'
897          '          case em::PolicyOptions::UNSET:\n'
898          '            break;\n'
899          '        }\n'
900          '      }\n'
901          '      if (do_set) {\n')
902  f.write('        base::Value* value = %s;\n' %
903          (_CreateValue(policy.policy_type, 'policy_proto.value()')))
904  # TODO(bartfab): |value| == NULL indicates that the policy value could not be
905  # parsed successfully. Surface such errors in the UI.
906  f.write('        if (value) {\n')
907  f.write('          ExternalDataFetcher* external_data_fetcher = %s;\n' %
908          _CreateExternalDataFetcher(policy.policy_type, policy.name))
909  f.write('          map->Set(key::k%s, level, POLICY_SCOPE_USER,\n' %
910          policy.name)
911  f.write('                   value, external_data_fetcher);\n'
912          '        }\n'
913          '      }\n'
914          '    }\n'
915          '  }\n')
916
917
918def _WriteCloudPolicyDecoder(policies, os, f):
919  f.write(CPP_HEAD)
920  for policy in policies:
921    if policy.is_supported and not policy.is_device_only:
922      _WritePolicyCode(f, policy)
923  f.write(CPP_FOOT)
924
925
926if __name__ == '__main__':
927  sys.exit(main())
928