Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions spec/std/mime_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,12 @@ describe MIME do
MIME.from_extension?(".foo").should eq "foo/bar"
end
end

{% if flag?(:win32) %}
it "loads MIME data from registry" do
MIME.register(".wma", "non-initialized")
MIME.init
MIME.from_extension?(".wma").should eq "audio/x-ms-wma"
end
{% end %}
end
25 changes: 25 additions & 0 deletions spec/std/time/location_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,31 @@ class Time::Location
end
end
end

{% if flag?(:win32) %}
it "loads time zone information from registry" do
LibC.GetTimeZoneInformation(out old_info)
begin
info = LibC::TIME_ZONE_INFORMATION.new(
bias: -60,
standardBias: 0,
daylightBias: -60,
standardDate: LibC::SYSTEMTIME.new(wYear: 0, wMonth: 10, wDayOfWeek: 0, wDay: 5, wHour: 3, wMinute: 0, wSecond: 0, wMilliseconds: 0),
daylightDate: LibC::SYSTEMTIME.new(wYear: 0, wMonth: 3, wDayOfWeek: 0, wDay: 5, wHour: 2, wMinute: 0, wSecond: 0, wMilliseconds: 0),
standardName: StaticArray(UInt16, 32).new(0),
daylightName: StaticArray(UInt16, 32).new(0),
)
info.standardName.to_slice.copy_from "Central Europe Standard Time".to_utf16
info.daylightName.to_slice.copy_from "Central Europe Summer Time".to_utf16
LibC.SetTimeZoneInformation(pointerof(info))

location = Location.load_local
location.zones.should eq [Time::Location::Zone.new("CET", 3600, false), Time::Location::Zone.new("CEST", 7200, true)]
ensure
LibC.SetTimeZoneInformation(pointerof(old_info))
end
end
{% end %}
end

describe ".fixed" do
Expand Down
18 changes: 15 additions & 3 deletions src/crystal/system/win32/mime.cr
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
require "./windows_registry"

module Crystal::System::MIME
CONTENT_TYPE = "Content Type".to_utf16

# Load MIME types from operating system source.
def self.load
# TODO: MIME types in Windows are provided by the registry. This needs to be
# implemented when registry access it is available.
# Until then, there will no system-provided MIME types in Windows.
WindowsRegistry.each_name(LibC::HKEY_CLASSES_ROOT) do |name|
# skip anything that is not a file extension
next if name.size < 2 || !(name[0] === '.')

WindowsRegistry.open?(LibC::HKEY_CLASSES_ROOT, name) do |sub_handle|
content_type = WindowsRegistry.get_string(sub_handle, CONTENT_TYPE)
if content_type
::MIME.register String.from_utf16(name), content_type
end
end
end
end
end
24 changes: 20 additions & 4 deletions src/crystal/system/win32/time.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require "c/winbase"
require "c/timezoneapi"
require "c/windows"
require "./zone_names"
require "./windows_registry"

module Crystal::System::Time
# Win32 epoch is 1601-01-01 00:00:00 UTC
Expand Down Expand Up @@ -155,13 +156,13 @@ module Crystal::System::Time

# Normalizes the names of the standard and dst zones.
private def self.normalize_zone_names(info : LibC::TIME_ZONE_INFORMATION) : Tuple(String, String)
stdname = String.from_utf16(info.standardName.to_slice)
stdname, _ = String.from_utf16(info.standardName.to_slice.to_unsafe)

if normalized_names = WINDOWS_ZONE_NAMES[stdname]?
return normalized_names
end

dstname = String.from_utf16(info.daylightName.to_slice)
dstname, _ = String.from_utf16(info.daylightName.to_slice.to_unsafe)

if english_name = translate_zone_name(stdname, dstname)
if normalized_names = WINDOWS_ZONE_NAMES[english_name]?
Expand All @@ -174,10 +175,25 @@ module Crystal::System::Time
return stdname, dstname
end

REGISTRY_TIME_ZONES = %q(SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones).to_utf16
Std = "Std".to_utf16
Dlt = "Dlt".to_utf16

# Searches the registry for an English name of a time zone named *stdname* or *dstname*
# and returns the English name.
private def self.translate_zone_name(stdname, dstname)
# TODO: Needs implementation once there is access to the registry.
nil
WindowsRegistry.open?(LibC::HKEY_LOCAL_MACHINE, REGISTRY_TIME_ZONES) do |key_handle|
WindowsRegistry.each_name(key_handle) do |name|
WindowsRegistry.open?(key_handle, name) do |sub_handle|
# TODO: Implement reading MUI
std = WindowsRegistry.get_string(sub_handle, Std)
dlt = WindowsRegistry.get_string(sub_handle, Dlt)

if std == stdname || dlt == dstname
return String.from_utf16(name)
end
end
end
end
end
end
104 changes: 104 additions & 0 deletions src/crystal/system/win32/windows_registry.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
require "c/winreg"
require "c/regapix"

module Crystal::System::WindowsRegistry
# Opens a subkey at path *name* and returns the new key handle or `nil` if it
# does not exist.
#
# Users need to ensure the opened key will be closed after usage (see `#close`).
# *sam* specifies desired access rights to the key to be opened.
def self.open?(handle : LibC::HKEY, name : Slice(UInt16), sam = LibC::REGSAM::READ)
status = LibC.RegOpenKeyExW(handle, name, 0, sam, out sub_handle)
status = WinError.new(status)

case status
when .error_success?
sub_handle
when .error_file_not_found?
# key does not exist
nil
else
raise RuntimeError.from_os_error("RegOpenKeyExW", status)
end
end

def self.open?(handle : LibC::HKEY, name : Slice(UInt16), sam = LibC::REGSAM::READ)
key_handle = open?(handle, name, sam)

return unless key_handle

begin
yield key_handle
ensure
close key_handle
end
end

# Closes the handle.
def self.close(handle : LibC::HKEY) : Nil
status = LibC.RegCloseKey(handle)
status = WinError.new(status)

unless status.error_success? || status.error_invalid_handle?
raise RuntimeError.from_os_error("RegCloseKey", status)
end
end

# Iterates all value names in this key and yields them to the block.
def self.each_name(handle : LibC::HKEY, &block : Slice(UInt16) -> Nil) : Nil
status = LibC.RegQueryInfoKeyW(handle, nil, nil, nil, out sub_key_count, out max_sub_key_length, nil, nil, nil, nil, nil, nil)
status = WinError.new(status)

return unless status.error_success?

buffer = Slice(UInt16).new(max_sub_key_length + 1)

sub_key_count.times do |i|
length = buffer.size.to_u32
status = LibC.RegEnumKeyExW(handle, i, buffer, pointerof(length), nil, nil, nil, nil)
status = WinError.new(status)

case status
when .error_success?
yield buffer[0, length]
when .error_no_more_items?
break
Comment thread
beta-ziliani marked this conversation as resolved.
else
raise RuntimeError.from_os_error("RegEnumValueW", status)
end
end
end

# Reads a raw value into a buffer and creates a string from it.
def self.get_string(handle : LibC::HKEY, name : Slice(UInt16))
Comment thread
beta-ziliani marked this conversation as resolved.
Crystal::System.retry_wstr_buffer do |buffer, small_buf|
raw = get_raw(handle, name, Bytes.new(buffer.to_unsafe.as(UInt8*), buffer.bytesize)) || return
_, length = raw

if 0 <= length <= buffer.size
return String.from_utf16(buffer[0, length // 2 - 1])
elsif small_buf && length > 0
next length
else
raise RuntimeError.new("RegQueryValueExW retry buffer")
end
end
end

# Reads a raw value into a buffer.
def self.get_raw(handle : LibC::HKEY, name : Slice(UInt16), buffer : Slice(UInt8))
length = buffer.size.to_u32
status = LibC.RegQueryValueExW(handle, name, nil, out valtype, buffer, pointerof(length))
status = WinError.new(status)
case status
when .error_success?
{valtype, length}
when .error_file_not_found?
nil
when .error_more_data?
{valtype, length}
else
raise RuntimeError.from_os_error("RegQueryValueExW", status)
end
end
end
3 changes: 3 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/regapix.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
lib LibC
type HKEY = Void*
end
1 change: 1 addition & 0 deletions src/lib_c/x86_64-windows-msvc/c/timezoneapi.cr
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ lib LibC
TIME_ZONE_ID_DAYLIGHT = 2_u32

fun GetTimeZoneInformation(tz_info : TIME_ZONE_INFORMATION*) : DWORD
fun SetTimeZoneInformation(tz_info : TIME_ZONE_INFORMATION*) : BOOL
end
48 changes: 48 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/winnt.cr
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,52 @@ lib LibC

DUPLICATE_CLOSE_SOURCE = 0x00000001
DUPLICATE_SAME_ACCESS = 0x00000002

enum REGSAM
# Required to query the values of a registry key.
QUERY_VALUE = 0x0001

# Required to create, delete, or set a registry value.
SET_VALUE = 0x0002

# Required to create a subkey of a registry key.
CREATE_SUB_KEY = 0x0004

# Required to enumerate the subkeys of a registry key.
ENUMERATE_SUB_KEYS = 0x0008

# Required to request change notifications for a registry key or for subkeys of a registry key.
NOTIFY = 0x0010

# Reserved for system use.
CREATE_LINK = 0x0020

# Indicates that an application on 64-bit Windows should operate on the 32-bit registry view. This flag is ignored by 32-bit Windows.
# This flag must be combined using the OR operator with the other flags in this table that either query or access registry values.
# Windows 2000: This flag is not supported.
WOW64_32KEY = 0x0200

# Indicates that an application on 64-bit Windows should operate on the 64-bit registry view. This flag is ignored by 32-bit Windows.
# This flag must be combined using the OR operator with the other flags in this table that either query or access registry values.
# Windows 2000: This flag is not supported.
WOW64_64KEY = 0x0100

WOW64_RES = 0x0300

# Combines the `STANDARD_RIGHTS_READ`, `QUERY_VALUE`, `ENUMERATE_SUB_KEYS`, and `NOTIFY` values.
# (STANDARD_RIGHTS_READ | QUERY_VALUE | ENUMERATE_SUB_KEYS | NOTIFY) & ~SYNCHRONIZE
READ = 0x20019

# Combines the `STANDARD_RIGHTS_REQUIRED`, `QUERY_VALUE`, `SET_VALUE`, `CREATE_SUB_KEY`, `ENUMERATE_SUB_KEYS`, `NOTIFY`, and `CREATE_LINK` access rights.
# (STANDARD_RIGHTS_ALL | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_LINK) & ~SYNCHRONIZE
ALL_ACCESS = 0xf003f

# Equivalent to `READ`.
# KEY_READ & ~SYNCHRONIZE
EXECUTE = 0x20019

# Combines the STANDARD_RIGHTS_WRITE, `KEY_SET_VALUE`, and `KEY_CREATE_SUB_KEY` access rights.
# (STANDARD_RIGHTS_WRITE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY) & ~SYNCHRONIZE
WRITE = 0x20006
end
end
42 changes: 42 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/winreg.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
lib LibC
alias LSTATUS = DWORD

enum RegistryRoutineFlags
NONE = 0
SZ = 1
EXPAND_SZ = 2
BINARY = 3
DWORD = 4
DWORD_LITTLE_ENDIAN = DWORD
DWORD_BIG_ENDIAN = 5
LINK = 6
MULTI_SZ = 7
RESOURCE_LIST = 8
FULL_RESOURCE_DESCRIPTOR = 9
RESOURCE_REQUIREMENTS_LIST = 10
QWORD = 11
QWORD_LITTLE_ENDIAN = QWORD
end

HKEY_CLASSES_ROOT = Pointer(Void).new(0x80000000).as(HKEY)
HKEY_CURRENT_USER = Pointer(Void).new(0x80000001).as(HKEY)
HKEY_LOCAL_MACHINE = Pointer(Void).new(0x80000002).as(HKEY)
HKEY_USERS = Pointer(Void).new(0x80000003).as(HKEY)
HKEY_PERFORMANCE_DATA = Pointer(Void).new(0x80000004).as(HKEY)
HKEY_CURRENT_CONFIG = Pointer(Void).new(0x80000005).as(HKEY)
HKEY_DYN_DATA = Pointer(Void).new(0x8000006).as(HKEY)

fun RegOpenKeyExW(hKey : HKEY, lpSubKey : LPWSTR, ulOptions : DWORD, samDesired : REGSAM, phkResult : HKEY*) : LSTATUS
fun RegCloseKey(hKey : HKEY) : LSTATUS
fun RegEnumKeyExW(hKey : HKEY, dwIndex : DWORD,
lpName : LPWSTR, lpcchName : DWORD*,
lpReserved : DWORD*,
lpClass : LPWSTR, lpcchClass : DWORD*,
lpftLastWriteTime : FILETIME*) : LSTATUS
fun RegQueryInfoKeyW(hKey : HKEY, lpClass : LPSTR, lpcchClass : DWORD*, lpReserved : DWORD*,
lpcSubKeys : DWORD*, lpcbMaxSubKeyLen : DWORD*,
lpcbMaxClassLen : DWORD*,
lpcValues : DWORD*, lpcbMaxValueNameLen : DWORD*, lpcbMaxValueLen : DWORD*,
lpcbSecurityDescriptor : DWORD*, lpftLastWriteTime : FILETIME*) : DWORD
fun RegQueryValueExW(hKey : HKEY, lpValueName : LPWSTR, lpReserved : DWORD*, lpType : RegistryRoutineFlags*, lpData : BYTE*, lpcbData : DWORD*) : LSTATUS
end