Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
9 changes: 9 additions & 0 deletions spec/std/time/location_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,15 @@ class Time::Location
end
end
end

{% if flag?(:win32) %}
it "loads time zone information from registry" do
location = Location.load_local
location.zones.size.should eq 2
zone_names = {location.zones[0].name, location.zones[1].name}
Crystal::System::Time::WINDOWS_ZONE_NAMES.key_for?(zone_names).should_not be_nil
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 "./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.
Registry.each_name(LibC::HKEY_CLASSES_ROOT) do |name|
# skip anything that is not a file extension
next if name.size < 2 || !(name[0] === '.')

Registry.open?(LibC::HKEY_CLASSES_ROOT, name) do |sub_handle|
content_type = Registry.get_string(sub_handle, CONTENT_TYPE)
if content_type
::MIME.register String.from_utf16(name), content_type
end
end
end
end
end
100 changes: 100 additions & 0 deletions src/crystal/system/win32/registry.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
require "c/winreg"
require "c/regapix"

module Crystal::System::Registry
Comment thread
straight-shoota marked this conversation as resolved.
Outdated
# 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?
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 = Registry.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
else
raise RuntimeError.from_os_error("RegEnumValueW", status)
end
end
end

def self.get_string(handle : LibC::HKEY, name : Slice(UInt16))
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 "RegQueryValueExW retry buffer"
end
end
end

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 "RegQueryValueExW"
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 "./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
Registry.open?(LibC::HKEY_LOCAL_MACHINE, REGISTRY_TIME_ZONES) do |key_handle|
Registry.each_name(key_handle) do |name|
Registry.open?(key_handle, name) do |sub_handle|
# TODO: Implement reading MUI
std = Registry.get_string(sub_handle, Std)
dlt = Registry.get_string(sub_handle, Dlt)

if std == stdname || dlt == dstname
return String.from_utf16(name)
end
end
end
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
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