You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Write our to_ascii function (drop ICU dependency) #89
That's correct @miguelteixeiraa. ToASCII refers to domain to ascii algorithm in the URL spec. Currently, we are using ICU in non-windows environments, and our own implementation in Windows. We want to get rid of ICU while keeping the test results happy.
Thinking about pick this one up. Does this issue have any urgency? Is anyone already working on this? Also please let me know if there is another one that would be more interesting to pick up now.
I have a nasty PR that demonstrates what it might look like... #68
Don't copy my code, it is not good. It was done quickly. One mistake I did was to try to stay in UTF-8. It is a bad idea. Move to UTF-32 right away and process in UTF-32 mode throughout, getting back to UTF-8 only at the end.
But I proved that you could almost get it done in a few hours. In truth, I probably did less than 20% of the required work, but it was enough to pass 90% of our tests. Getting it to be correct takes many hours.
UTF-32 is pretty simple. Each character is a 32-bit integer (but only just about the first million values are allowed, so 32-bit is too much, but that's fine). The largest possible value is 0x10FFFF. But, actually, anything over 0xE01EF is disallowed for us.
Important: we want to be able to update the future Unicode releases so that the table should not be hard-coded... we should have a script that goes from the table to our implementation.
Map. For each code point in the domain_name string, look up the status value in Section 5, IDNA Mapping Table, and take the following actions:
disallowed: Leave the code point unchanged in the string, and record that there was an error.
ignored: Remove the code point from the string. This is equivalent to mapping the code point to an empty string.
mapped: Replace the code point in the string by the value for the mapping in Section 5, IDNA Mapping Table.
valid: Leave the code point unchanged in the string.
Attempt to convert the rest of the label to Unicode according to Punycode [[RFC3492 (https://www.unicode.org/reports/tr46/#RFC3492)]. If that conversion fails, record that there was an error, and continue with the next label. Otherwise replace the original label in the string by the results of the conversion.
Verify that the label meets the validity criteria in Section 4.1, Validity Criteria for Nontransitional Processing. If any of the validity criteria are not satisfied, record that there was an error.
If the label does not start with “xn--”:
Verify that the label meets the validity criteria in Section 4.1, Validity Criteria for the input Processing choice (Transitional or Nontransitional). If any of the validity criteria are not satisfied, record that there was an error.
What you want to do is write some kind of script that reads the table and outputs code. That's more or less or ICU does it, I think.
Take 0x2e. It is the '.' in ascii. It remains a dot. Fine. But look later...
3002 ; mapped ; 002E # 1.1 IDEOGRAPHIC FULL STOP
So character 0x3002 must be mapped to 0x2e.
Some characters are ignored, we just skip them.
Now we must somehow go from the table to efficient C++ code. This has been done before, and I have not looked at how people do it. So here is my naive take.
At the moment, 5986 different characters are mapped to 2945 distinct characters. This can be implemented using a key-value map, using 32 bits for the key and 32 bits (or less) for the value. That's about 50 kB right there. Maybe less if you get clever. A flat data structure with a binary search might work to locate the values and the mapping.
Next we have the disallowed and the ignored values. This could be implemented using a flat list where you have 2 bits per unicode code points (1114111), so that's 2 MB. Each code point can be allowed, disallowed, ignored or mapped. But we probably don't want to use 2MB. So just represent the data as an list of ranges. Each range can be defined by its starting position (a value no larger than 1114111) and two bits representing whether it is allowed, disallowed, ignored or mapped. So each range can be represented using 32 bits. The table has something like 9000 lines, but most of it is descriptive, there are far fewer than 9000 ranges in there... So, let us say 24 kB to store that. Implementation-wise, a simple binary search through the ranges will do.
So mapping, unallowed and so forth... we are talking about less than 100kB.
Normalization is tiny. So I am hopeful we can do all of that in less than 150kB
I recommend breaking down the implementation into the steps I have outlined. At each stage, write some tests. Don't be a cowboy like I did... it is too complicated to do in one sitting.
For some context, currently we rely on ICU which requires something like 40 MB. I think that implementing just what we need would be far smaller. Less than 200 kB.
I don't know when I'm going to finish this, but if anyone already has an idea in mind and thinks they could implement it quickly, please don't get hung up on me.
I was thinking about storing the idna table in an interval map like this one:
template <typename T, typename U>
classIntervalMap {
std::map<std::pair<T, T>, U> storage;
public:IntervalMap() {}
voidinsert(std::pair<T, T> interval, U value)
{
storage[interval] = value;
}
U find(T value)
{
auto it = storage.lower_bound(std::make_pair(value, value));
if (it == storage.end() || value < it->first.first) {
if (it == storage.begin())
returnU();
--it;
}
if (value >= it->first.first && value <= it->first.second)
return it->second;
returnU();
}
};
Next step would be to write some tests for to_ascii. It is not complete because it does not do mapping, it does not scan for forbidden characters and it does not check for bidi conditions. But it is otherwise a good sketch.
Activity
miguelteixeiraa commentedon Jan 25, 2023
Is the
to_asciifunction that lives in src/unicode.cpp the work related to this issue already? Or this issue is to replace that function?anonrig commentedon Jan 25, 2023
That's correct @miguelteixeiraa. ToASCII refers to
domain to ascii algorithmin the URL spec. Currently, we are using ICU in non-windows environments, and our own implementation in Windows. We want to get rid of ICU while keeping the test results happy.miguelteixeiraa commentedon Jan 26, 2023
Thinking about pick this one up. Does this issue have any urgency? Is anyone already working on this? Also please let me know if there is another one that would be more interesting to pick up now.
lemire commentedon Jan 27, 2023
@miguelteixeiraa It would be a great issue.
I have a nasty PR that demonstrates what it might look like...
#68
Don't copy my code, it is not good. It was done quickly. One mistake I did was to try to stay in UTF-8. It is a bad idea. Move to UTF-32 right away and process in UTF-32 mode throughout, getting back to UTF-8 only at the end.
But I proved that you could almost get it done in a few hours. In truth, I probably did less than 20% of the required work, but it was enough to pass 90% of our tests. Getting it to be correct takes many hours.
UTF-32 is pretty simple. Each character is a 32-bit integer (but only just about the first million values are allowed, so 32-bit is too much, but that's fine). The largest possible value is 0x10FFFF. But, actually, anything over 0xE01EF is disallowed for us.
Here are some starting points.
Reference to the standard: https://www.unicode.org/reports/tr46/#ToUnicode
Step 3 is a fun part. It depends on the Unicode version. For Unicode 15, the table is https://www.unicode.org/Public/idna/15.0.0/IdnaMappingTable.txt
Important: we want to be able to update the future Unicode releases so that the table should not be hard-coded... we should have a script that goes from the table to our implementation.
Verify that the label meets the validity criteria in Section 4.1, Validity Criteria for the input Processing choice (Transitional or Nontransitional). If any of the validity criteria are not satisfied, record that there was an error.
What you want to do is write some kind of script that reads the table and outputs code. That's more or less or ICU does it, I think.
Take 0x2e. It is the '.' in ascii. It remains a dot. Fine. But look later...
So character 0x3002 must be mapped to 0x2e.
Some characters are ignored, we just skip them.
Now we must somehow go from the table to efficient C++ code. This has been done before, and I have not looked at how people do it. So here is my naive take.
At the moment, 5986 different characters are mapped to 2945 distinct characters. This can be implemented using a key-value map, using 32 bits for the key and 32 bits (or less) for the value. That's about 50 kB right there. Maybe less if you get clever. A flat data structure with a binary search might work to locate the values and the mapping.
Next we have the disallowed and the ignored values. This could be implemented using a flat list where you have 2 bits per unicode code points (1114111), so that's 2 MB. Each code point can be allowed, disallowed, ignored or mapped. But we probably don't want to use 2MB. So just represent the data as an list of ranges. Each range can be defined by its starting position (a value no larger than 1114111) and two bits representing whether it is allowed, disallowed, ignored or mapped. So each range can be represented using 32 bits. The table has something like 9000 lines, but most of it is descriptive, there are far fewer than 9000 ranges in there... So, let us say 24 kB to store that. Implementation-wise, a simple binary search through the ranges will do.
So mapping, unallowed and so forth... we are talking about less than 100kB.
Normalization is tiny. So I am hopeful we can do all of that in less than 150kB
I recommend breaking down the implementation into the steps I have outlined. At each stage, write some tests. Don't be a cowboy like I did... it is too complicated to do in one sitting.
lemire commentedon Jan 27, 2023
I could probably even do the mapping and scripting over the big table. I have a good idea of how to do it.
The normalization only looks mysterious, but if you scan through the perl code, you see that there is relatively little involved.
lemire commentedon Jan 27, 2023
For some context, currently we rely on ICU which requires something like 40 MB. I think that implementing just what we need would be far smaller. Less than 200 kB.
miguelteixeiraa commentedon Jan 27, 2023
Seems definitely hard. I like it. Thanks for sharing these ideas with me! I'll work on this.
miguelteixeiraa commentedon Jan 30, 2023
This task is hard 😵
I don't know when I'm going to finish this, but if anyone already has an idea in mind and thinks they could implement it quickly, please don't get hung up on me.
I was thinking about storing the idna table in an interval map like this one:
then the table would be created like this:
and access would be like this:
but maybe this implementation is too costly.. what do you think?
the construction of this interval map could be done from a script, like this very ugly one here:
miguelteixeiraa commentedon Jan 30, 2023
I could also ignore the "range/interval" thing, use unordered_map to store the whole table and get an O(1)
lemire commentedon Jan 30, 2023
Do not try to do it all, and especially not all at once. It is indeed quite a large project... there is a reason nobody does ever does it!
I have a good idea how to implement this table and I can do it. You leave it up to me, I can probably do it this week.
lemire commentedon Jan 30, 2023
@miguelteixeiraa @anonrig I think we could start a secondary project, maybe https://github.com/ada-url/idna/, and work on it there. Because, indeed, it will be non-trivial and it will take some time (weeks).
lemire commentedon Jan 30, 2023
@miguelteixeiraa @anonrig And it could be cool to have it as a separate component.
miguelteixeiraa commentedon Jan 31, 2023
Sounds like a good idea! : )
lemire commentedon Feb 1, 2023
Note that it is an evolving target : whatwg/url#744
lemire commentedon Feb 4, 2023
@miguelteixeiraa Can you have a look at https://github.com/ada-url/idna ?
Next step would be to write some tests for to_ascii. It is not complete because it does not do mapping, it does not scan for forbidden characters and it does not check for bidi conditions. But it is otherwise a good sketch.