Skip to content

Commit 35f42fc

Browse files
fix: fixed baseIRI parser issue
1 parent b72366d commit 35f42fc

2 files changed

Lines changed: 74 additions & 85 deletions

File tree

app/components/NewFolderDialog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useState, useEffect, useRef } from "react";
44
import Modal from "./shared/Modal";
55
import Button from "./shared/Button";
6+
import Input from "./shared/Input";
67
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
78
import { createContainerAt, getSolidDataset, UrlString } from "@inrupt/solid-client";
89
import toast from "react-hot-toast";
@@ -129,14 +130,13 @@ export default function NewFolderDialog({
129130
}
130131
>
131132
<div className="py-2">
132-
<input
133+
<Input
133134
ref={inputRef}
134135
type="text"
135136
value={folderName}
136-
onChange={(e) => setFolderName(e.target.value)}
137+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setFolderName(e.target.value)}
137138
onKeyDown={handleKeyDown}
138139
placeholder="Untitled folder"
139-
className="w-full h-10 rounded-md border border-gray-300 bg-white px-3 text-sm text-black placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-[#7B42F6] focus:border-[#7B42F6] transition-colors disabled:bg-gray-50 disabled:text-gray-500"
140140
disabled={isCreating}
141141
/>
142142
</div>

app/lib/hooks/useSolidStorages.ts

Lines changed: 71 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,63 @@ interface UseSolidStoragesResult {
2424
error: Error | null;
2525
}
2626

27+
/**
28+
* Resolves and normalizes a storage URL, handling relative URLs, undefined prefixes, etc.
29+
* @param {string} storageUrl - The storage URL to resolve
30+
* @param {string} baseUrl - The base URL to resolve relative URLs against
31+
* @returns {string | null} - The resolved absolute URL, or null if invalid
32+
*/
33+
function resolveStorageUrl(storageUrl: string, baseUrl: string): string | null {
34+
// Handle the case where n3 parser didn't resolve the prefix correctly
35+
// "pre:" prefix resolves to "</.>" which should be the root "/"
36+
if (storageUrl === 'undefined/' || storageUrl.includes('undefined')) {
37+
const baseUrlObj = new URL(baseUrl);
38+
return `${baseUrlObj.protocol}//${baseUrlObj.host}/`;
39+
}
40+
41+
// Handle relative URLs that end with "/." or are just "/"
42+
if (storageUrl.endsWith('/.') || storageUrl.endsWith('/./') ||
43+
storageUrl === './' || storageUrl === '/' ||
44+
(storageUrl.startsWith('/') && !storageUrl.startsWith('http'))) {
45+
const baseUrlObj = new URL(baseUrl);
46+
if (storageUrl.endsWith('/.') || storageUrl === './' || storageUrl === '/') {
47+
return `${baseUrlObj.protocol}//${baseUrlObj.host}/`;
48+
} else {
49+
// Handle paths like "/path" -> "https://domain.com/path"
50+
try {
51+
return new URL(storageUrl, baseUrl).href;
52+
} catch (e) {
53+
// If URL construction fails, try manual resolution
54+
if (storageUrl.startsWith('/')) {
55+
return `${baseUrlObj.protocol}//${baseUrlObj.host}${storageUrl}`;
56+
}
57+
}
58+
}
59+
}
60+
61+
// Also check if it's a relative URL without protocol
62+
if (!storageUrl.startsWith('http://') && !storageUrl.startsWith('https://')) {
63+
try {
64+
const baseUrlObj = new URL(baseUrl);
65+
if (storageUrl.startsWith('/')) {
66+
return `${baseUrlObj.protocol}//${baseUrlObj.host}${storageUrl}`;
67+
} else {
68+
return new URL(storageUrl, baseUrl).href;
69+
}
70+
} catch (e) {
71+
// Silent error handling
72+
return null;
73+
}
74+
}
75+
76+
// Final validation - ensure it's a valid absolute URL
77+
if (storageUrl && storageUrl.startsWith('http')) {
78+
return storageUrl;
79+
}
80+
81+
return null;
82+
}
83+
2784
/**
2885
* Discovers storage by traversing up the folder hierarchy from the WebID
2986
* Based on: https://github.com/SolidLabResearch/Bashlib/blob/80de25cbb4b3ed057f95e25bc057f1be9b00cef3/src/utils/util.ts#L73-L104
@@ -238,16 +295,19 @@ export function useSolidStorages(): UseSolidStoragesResult {
238295
// Parse the RDF content
239296
const store = new Store();
240297

298+
// Extract base URL for resolving relative URIs like <#me>
299+
const baseUrl = webId.split('#')[0];
300+
241301
if (contentType.includes('text/turtle') || contentType.includes('application/turtle') ||
242302
contentType.includes('text/n3') || contentType.includes('application/n3')) {
243-
const parser = new Parser();
303+
const parser = new Parser({ baseIRI: baseUrl });
244304
const quads = parser.parse(content);
245305
store.addQuads(quads);
246306
} else if (contentType.includes('application/ld+json')) {
247307
// For JSON-LD, we'd need a different parser, but for now let's try to extract from Turtle
248308
// Most Solid servers return Turtle even if JSON-LD is requested
249309
try {
250-
const parser = new Parser();
310+
const parser = new Parser({ baseIRI: baseUrl });
251311
const quads = parser.parse(content);
252312
store.addQuads(quads);
253313
} catch (e) {
@@ -256,7 +316,6 @@ export function useSolidStorages(): UseSolidStoragesResult {
256316
}
257317

258318
// Find the main subject - try different variants
259-
const baseUrl = webId.split('#')[0];
260319
const subjectVariants = [
261320
new NamedNode(webId),
262321
new NamedNode(baseUrl + '#me'),
@@ -313,26 +372,9 @@ export function useSolidStorages(): UseSolidStoragesResult {
313372
const pimStorageQuads = store.getQuads(mainSubject, new NamedNode(PIM_STORAGE), null, null);
314373
pimStorageQuads.forEach(quad => {
315374
if (quad.object instanceof NamedNode) {
316-
let storageUrl = quad.object.value;
317-
const originalValue = storageUrl;
318-
319-
// Handle the case where n3 parser didn't resolve the prefix correctly
320-
// "pre:" prefix resolves to "</.>" which should be the root "/"
321-
if (storageUrl === 'undefined/' || storageUrl.includes('undefined')) {
322-
const baseUrlObj = new URL(baseUrl);
323-
storageUrl = `${baseUrlObj.protocol}//${baseUrlObj.host}/`;
324-
} else if (!storageUrl.startsWith('http://') && !storageUrl.startsWith('https://')) {
325-
// Resolve relative URLs
326-
const baseUrlObj = new URL(baseUrl);
327-
if (storageUrl === './' || storageUrl === '/' || storageUrl.endsWith('/.')) {
328-
storageUrl = `${baseUrlObj.protocol}//${baseUrlObj.host}/`;
329-
} else {
330-
storageUrl = new URL(storageUrl, baseUrl).href;
331-
}
332-
}
333-
334-
if (storageUrl && storageUrl.startsWith('http') && !storageUrls.includes(storageUrl)) {
335-
storageUrls.push(storageUrl);
375+
const resolvedUrl = resolveStorageUrl(quad.object.value, baseUrl);
376+
if (resolvedUrl && !storageUrls.includes(resolvedUrl)) {
377+
storageUrls.push(resolvedUrl);
336378
}
337379
}
338380
});
@@ -352,68 +394,15 @@ export function useSolidStorages(): UseSolidStoragesResult {
352394
const allPimStorageQuads = store.getQuads(null, new NamedNode(PIM_STORAGE), null, null);
353395
allPimStorageQuads.forEach(quad => {
354396
if (quad.object instanceof NamedNode) {
355-
let storageUrl = quad.object.value;
356-
const originalValue = storageUrl;
357-
358-
// Handle the case where n3 parser didn't resolve the prefix correctly
359-
// "pre:" prefix resolves to "</.>" which should be the root "/"
360-
// If we see "undefined/" it means the prefix wasn't resolved
361-
if (storageUrl === 'undefined/' || storageUrl.includes('undefined')) {
362-
// The prefix "pre:" resolves to "</.>" which is the root
363-
const baseUrlObj = new URL(baseUrl);
364-
storageUrl = `${baseUrlObj.protocol}//${baseUrlObj.host}/`;
365-
}
366-
// Handle relative URLs that end with "/." or are just "/"
367-
else if (storageUrl.endsWith('/.') || storageUrl.endsWith('/./') ||
368-
storageUrl === './' || storageUrl === '/' ||
369-
(storageUrl.startsWith('/') && !storageUrl.startsWith('http'))) {
370-
// Resolve relative URL to absolute
371-
const baseUrlObj = new URL(baseUrl);
372-
if (storageUrl.endsWith('/.') || storageUrl === './' || storageUrl === '/') {
373-
storageUrl = `${baseUrlObj.protocol}//${baseUrlObj.host}/`;
374-
} else {
375-
// Handle paths like "/path" -> "https://domain.com/path"
376-
try {
377-
storageUrl = new URL(storageUrl, baseUrl).href;
378-
} catch (e) {
379-
// If URL construction fails, try manual resolution
380-
if (storageUrl.startsWith('/')) {
381-
storageUrl = `${baseUrlObj.protocol}//${baseUrlObj.host}${storageUrl}`;
382-
}
383-
}
384-
}
385-
}
386-
// Also check if it's a relative URL without protocol
387-
else if (!storageUrl.startsWith('http://') && !storageUrl.startsWith('https://')) {
388-
try {
389-
const baseUrlObj = new URL(baseUrl);
390-
if (storageUrl.startsWith('/')) {
391-
storageUrl = `${baseUrlObj.protocol}//${baseUrlObj.host}${storageUrl}`;
392-
} else {
393-
storageUrl = new URL(storageUrl, baseUrl).href;
394-
}
395-
} catch (e) {
396-
// Silent error handling
397-
}
398-
}
399-
400-
// Final validation - ensure it's a valid absolute URL
401-
if (storageUrl && storageUrl.startsWith('http')) {
402-
if (!storageUrls.includes(storageUrl)) {
403-
storageUrls.push(storageUrl);
404-
}
397+
const resolvedUrl = resolveStorageUrl(quad.object.value, baseUrl);
398+
if (resolvedUrl && !storageUrls.includes(resolvedUrl)) {
399+
storageUrls.push(resolvedUrl);
405400
}
406401
} else if (quad.object instanceof Literal) {
407402
// Sometimes storage might be a literal, try to resolve it
408-
const storageValue = quad.object.value;
409-
if (storageValue === './' || storageValue === '/' || storageValue.startsWith('/')) {
410-
const baseUrlObj = new URL(baseUrl);
411-
const resolvedUrl = storageValue === './' || storageValue === '/'
412-
? `${baseUrlObj.protocol}//${baseUrlObj.host}/`
413-
: new URL(storageValue, baseUrl).href;
414-
if (!storageUrls.includes(resolvedUrl)) {
415-
storageUrls.push(resolvedUrl);
416-
}
403+
const resolvedUrl = resolveStorageUrl(quad.object.value, baseUrl);
404+
if (resolvedUrl && !storageUrls.includes(resolvedUrl)) {
405+
storageUrls.push(resolvedUrl);
417406
}
418407
}
419408
});

0 commit comments

Comments
 (0)