Hi,
I've seen this question: Is there a way to get a Confluence page's tiny link programmatically? and I have one in a similar vein.
What is the algorithm used to create the tiny links?
I'm assuming it's some kind of base 64/62 encoder of some sort? If anyone knows where in the source to look for the code, a path would be greatly appreciated.
Steve
Check out the source of com.atlassian.confluence.pages.TinyUrl...
Base64.encodeBase64(longToByteArray(id));
id is the pageId.
I recently ported this to python:
def page_id_to_tiny(page_id):
return base64.b64encode((page_id).to_bytes(4, byteorder='little')).decode().replace('/','-').replace('+','_').rstrip('A=')
def tiny_to_page_id(tiny):
return int.from_bytes(base64.b64decode(tiny.ljust(8,'A').replace('_','+').replace('-','/').encode()),byteorder='little')
Updated as per feedback below from @Sebastian Schmachtel and @Fabian Sturm that noticed that Atlassian do not actually implement url safe encoding from rfc4648 correctly.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
This would have been a perfect answer but I think Atlassian screwed up and + is replaced by _ and / by - instead of the other way round as it is defined for the url safe encoding rfc4648.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I used this functions and started to see weird effects. So I returned here et voila a hint just in time :-)
My (working) code looks now like this
def page_id_to_tiny(page_ids: List[int]) -> List[str]:
"""converts a list of pageids (int) to a list of last part of shortlink (/x/$PART)"""
shortlinks: List[str] = []
for page_id in page_ids:
encoded = base64.b64encode(page_id.to_bytes(4, byteorder='little')).decode()
encoded_fixed = encoded.replace("/", "-").replace("+", "_").rstrip('A=')
shortlinks.append(encoded_fixed)
return shortlinks
def tiny_to_page_id(tiny_links: List[str]) -> List[int]:
"""converts a list of last part of shortlink (/x/$PART) to a list of pageids (int)"""
page_ids: List[int] = []
for tiny_link in tiny_links:
tiny_link = tiny_link.replace("-", "/").replace("_", "+")
page_ids.append(int.from_bytes(base64.b64decode(tiny_link.ljust(8, 'A').encode()), byteorder='little'))
return page_ids
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Here a solution in JS. Please check if the browsers are compatible with btoa and atob (https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa).
// The long to byte array conversion is taken from this source:
// https://stackoverflow.com/questions/8482309/converting-javascript-integer-to-byte-array-and-back
function longToByteArray(long) {
var byteArray = [0, 0, 0, 0];
for ( var index = 0; index < byteArray.length; index ++ ) {
var byte = long & 0xff;
byteArray [ index ] = byte;
long = (long - byte) / 256 ;
}
return byteArray;
}
function pageIdToShortUrl(pageId) {
return btoa(longToByteArray(pageId)
.map(String.fromCharCode)
.map(function (s) { return s[0] })
.join(""))
.replace(/=/g,"");
}
function byteArrayToLong(byteArray) {
var value = 0;
for ( var i = byteArray.length - 1; i >= 0; i--) {
value = (value * 256) + byteArray[i];
}
return value;
};
function shortUrlToPageId(shortUrl) {
return byteArrayToLong(atob(shortUrl)
.split("")
.map(function (b) { return b.charCodeAt()}));
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I used the following functions in PHP based on the Steve's code:
function id2tinylink($id) { $bytes = pack("L", (int)$id); // convert the long to bytes $base64string=base64_encode($bytes); $base64string_escaped=strtr($base64string,'+/','_-'); return rtrim($base64string_escaped, "A="); } function tinylink2id($tinylink){ $base64string_encoded = str_pad($tinylink, 8, "A", STR_PAD_RIGHT); $base64string = strtr($base64string_encoded, '_-', '+/'); $bytes = base64_decode($base64string); return unpack("L",$bytes)[1]; }
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I used your examples to write a PostgreSQL equivalent. Probably not as efficient, but could be useful.
-- Encode to short URL
SELECT TRIM(
TRAILING 'A=' FROM
translate(encode(('\x' || regexp_replace(
lpad(to_hex(content.contentid), 16, '0'),
'(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)',
'\8\7\6\5\4\3\2\1'))::bytea, 'base64'), '-_', '/+')) "short_url"
FROM (SELECT 200999755 "contentid") "content"
An example from the Confluence table directly:
SELECT contentid, TRIM(
TRAILING 'A=' FROM
translate(encode(('\x' || regexp_replace(
lpad(to_hex(content.contentid), 16, '0'),
'(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)',
'\8\7\6\5\4\3\2\1'))::bytea, 'base64'), '-_', '/+')) "short_url",
title
FROM content
LIMIT 10
To decode a short URL to it's content ID:
-- Decode to content ID
SELECT ('x' || regexp_replace(
rpad(encode(decode(translate(short.url || repeat('A', 8 - length(short.url)), '_-', '+/'), 'base64'), 'hex'), 16, '0'),
'(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)',
'\8\7\6\5\4\3\2\1'))::bit(64)::bigint "content_id"
FROM (SELECT 'SwP7Cw' "url") "short"
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Here is a rough bash implementation. I don't bother chopping a trailing A because this official implementation also didn't
confluence-tiny-code() {
local id="${1?}"
local tiny
printf -v tiny "\\\x%02x" $(( id & 0xff )) $(( (id >> 8 ) & 0xff )) $(( (id >> 16 ) & 0xff )) $(( (id >> 24 ) & 0xff ))
tiny=$(printf "$tiny" | base64)
tiny="${tiny//\+/_}"
tiny="${tiny//\//-}"
echo "${tiny%%=*}"
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
If anyone would like to emulate this functionality in PHP for whatever reason:
function tinylinkid($id) { $bytes = pack("L", $id); // convert the long to bytes $bytes = rtrim($bytes); // trim any null characters $bytestring = ""; $len = strlen($bytes); for($i = 0; $i < $len; $i++) { $bytestring = $bytestring . $bytes[$i]; // build the byte string } $base64string = base64_encode($bytestring); // base64 encode the byte string $base64string = rtrim($base64string, "="); // trim any base64 padding characters return $base64string; }
Thanks for David for the pointer.
Steve
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.