// Copyright 2014 The Crashpad Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "util/mac/launchd.h"

#import <Foundation/Foundation.h>

#include "base/apple/bridging.h"
#include "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/mac/scoped_launch_data.h"
#include "base/strings/sys_string_conversions.h"
#include "util/misc/implicit_cast.h"

namespace crashpad {

launch_data_t CFPropertyToLaunchData(CFPropertyListRef property_cf) {
  @autoreleasepool {
    // This function mixes Foundation and Core Foundation access to property
    // list elements according to which is more convenient and correct for any
    // specific task.

    launch_data_t data_launch = nullptr;
    CFTypeID type_id_cf = CFGetTypeID(property_cf);

    if (type_id_cf == CFDictionaryGetTypeID()) {
      NSDictionary* dictionary_ns = base::apple::CFToNSPtrCast(
          base::apple::CFCastStrict<CFDictionaryRef>(property_cf));
      base::mac::ScopedLaunchData dictionary_launch(
          LaunchDataAlloc(LAUNCH_DATA_DICTIONARY));

      for (NSString* key in dictionary_ns) {
        if (![key isKindOfClass:[NSString class]]) {
          return nullptr;
        }

        CFPropertyListRef value_cf =
            (__bridge CFPropertyListRef)dictionary_ns[key];
        launch_data_t value_launch = CFPropertyToLaunchData(value_cf);
        if (!value_launch) {
          return nullptr;
        }

        LaunchDataDictInsert(
            dictionary_launch.get(), value_launch, [key UTF8String]);
      }

      data_launch = dictionary_launch.release();

    } else if (type_id_cf == CFArrayGetTypeID()) {
      NSArray* array_ns = base::apple::CFToNSPtrCast(
          base::apple::CFCastStrict<CFArrayRef>(property_cf));
      base::mac::ScopedLaunchData array_launch(
          LaunchDataAlloc(LAUNCH_DATA_ARRAY));
      size_t index = 0;

      for (id element_ns in array_ns) {
        CFPropertyListRef element_cf = (__bridge CFPropertyListRef)element_ns;
        launch_data_t element_launch = CFPropertyToLaunchData(element_cf);
        if (!element_launch) {
          return nullptr;
        }

        LaunchDataArraySetIndex(array_launch.get(), element_launch, index++);
      }

      data_launch = array_launch.release();

    } else if (type_id_cf == CFNumberGetTypeID()) {
      CFNumberRef number_cf =
          base::apple::CFCastStrict<CFNumberRef>(property_cf);
      NSNumber* number_ns = base::apple::CFToNSPtrCast(number_cf);
      switch (CFNumberGetType(number_cf)) {
        case kCFNumberSInt8Type:
        case kCFNumberSInt16Type:
        case kCFNumberSInt32Type:
        case kCFNumberSInt64Type:
        case kCFNumberCharType:
        case kCFNumberShortType:
        case kCFNumberIntType:
        case kCFNumberLongType:
        case kCFNumberLongLongType:
        case kCFNumberCFIndexType:
        case kCFNumberNSIntegerType: {
          data_launch = LaunchDataNewInteger([number_ns longLongValue]);
          break;
        }

        case kCFNumberFloat32Type:
        case kCFNumberFloat64Type:
        case kCFNumberFloatType:
        case kCFNumberDoubleType: {
          data_launch = LaunchDataNewReal([number_ns doubleValue]);
          break;
        }

        default: { return nullptr; }
      }

    } else if (type_id_cf == CFBooleanGetTypeID()) {
      CFBooleanRef boolean_cf =
          base::apple::CFCastStrict<CFBooleanRef>(property_cf);
      data_launch = LaunchDataNewBool(CFBooleanGetValue(boolean_cf));

    } else if (type_id_cf == CFStringGetTypeID()) {
      NSString* string_ns = base::apple::CFToNSPtrCast(
          base::apple::CFCastStrict<CFStringRef>(property_cf));

      // -fileSystemRepresentation might be more correct than -UTF8String,
      // because these strings can hold paths. The analogous function in
      // launchctl, CF2launch_data() (10.9.4
      // launchd-842.92.1/support/launchctl.c), uses UTF-8 instead of filesystem
      // encoding, so do the same here. Note that there’s another occurrence of
      // -UTF8String above, used for dictionary keys.
      data_launch = LaunchDataNewString([string_ns UTF8String]);

    } else if (type_id_cf == CFDataGetTypeID()) {
      NSData* data_ns = base::apple::CFToNSPtrCast(
          base::apple::CFCastStrict<CFDataRef>(property_cf));
      data_launch = LaunchDataNewOpaque([data_ns bytes], [data_ns length]);
    } else {
      base::apple::ScopedCFTypeRef<CFStringRef> type_name_cf(
          CFCopyTypeIDDescription(type_id_cf));
      DLOG(ERROR) << "unable to convert CFTypeID " << type_id_cf << " ("
                  << base::SysCFStringRefToUTF8(type_name_cf.get()) << ")";
    }

    return data_launch;
  }
}

}  // namespace crashpad
