Skip to content

Commit

Permalink
Sanitize File Paths (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
InsertCreativityHere authored Jan 30, 2024
1 parent 95da533 commit 821d01d
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 51 deletions.
3 changes: 2 additions & 1 deletion server/src/configuration_set.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) ZeroC, Inc.

use crate::slice_config;
use crate::utils::sanitize_path;
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use slice_config::SliceConfig;
Expand Down Expand Up @@ -100,7 +101,7 @@ fn parse_paths(value: &serde_json::Value) -> Vec<String> {
dirs_array
.iter()
.filter_map(|v| v.as_str())
.map(String::from)
.map(sanitize_path)
.collect::<Vec<_>>()
})
.unwrap_or_default()
Expand Down
2 changes: 1 addition & 1 deletion server/src/diagnostic_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ fn try_into_lsp_diagnostic_related_information(
note: &Note,
) -> Option<tower_lsp::lsp_types::DiagnosticRelatedInformation> {
let span = note.span.as_ref()?;
let file_path = Url::from_file_path(span.file.clone()).ok()?;
let file_path = convert_slice_url_to_uri(&span.file)?;
let start_position = Position::new((span.start.row - 1) as u32, (span.start.col - 1) as u32);
let end_position = Position::new((span.end.row - 1) as u32, (span.end.col - 1) as u32);

Expand Down
12 changes: 3 additions & 9 deletions server/src/hover.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
// Copyright (c) ZeroC, Inc.

use crate::{configuration_set::CompilationData, utils::url_to_file_path};
use slicec::{
grammar::{Element, Enum, Primitive, Symbol, TypeRef, TypeRefDefinition, Types},
slice_file::Location,
slice_file::{Location, SliceFile},
visitor::Visitor,
};
use tower_lsp::lsp_types::{Hover, HoverContents, MarkedString, Position, Url};
use tower_lsp::lsp_types::{Hover, HoverContents, MarkedString, Position};

pub fn try_into_hover_result(
data: &CompilationData,
uri: Url,
file: &SliceFile,
position: Position,
) -> tower_lsp::jsonrpc::Result<Hover> {
let file = url_to_file_path(&uri)
.and_then(|p| data.files.get(&p))
.expect("Could not convert URI to Slice formatted URL for hover request");

// Convert position to row and column 1 based
let col = (position.character + 1) as usize;
let row = (position.line + 1) as usize;
Expand Down
12 changes: 3 additions & 9 deletions server/src/jump_definition.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
// Copyright (c) ZeroC, Inc.

use crate::{configuration_set::CompilationData, utils::url_to_file_path};
use slicec::{
grammar::{
Class, Commentable, CustomType, Entity, Enum, Enumerator, Exception, Field, Identifier,
Interface, Message, MessageComponent, NamedSymbol, Operation, Struct, Symbol, TypeAlias,
TypeRef, TypeRefDefinition, Types,
},
slice_file::{Location, Span},
slice_file::{Location, SliceFile, Span},
visitor::Visitor,
};
use tower_lsp::lsp_types::{Position, Url};

pub fn get_definition_span(data: &CompilationData, uri: Url, position: Position) -> Option<Span> {
let file_path = url_to_file_path(&uri)?;

// Attempt to retrieve the file from the data
let file = data.files.get(&file_path)?;
use tower_lsp::lsp_types::Position;

pub fn get_definition_span(file: &SliceFile, position: Position) -> Option<Span> {
// Convert position to row and column 1 based
let col = (position.character + 1) as usize;
let row = (position.line + 1) as usize;
Expand Down
39 changes: 18 additions & 21 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use jump_definition::get_definition_span;
use notifications::{ShowNotification, ShowNotificationParams};
use std::{collections::HashMap, path::Path};
use tower_lsp::{jsonrpc::Error, lsp_types::*, Client, LanguageServer, LspService, Server};
use utils::{convert_slice_url_to_uri, url_to_file_path, FindFile};
use utils::{convert_slice_url_to_uri, url_to_sanitized_file_path, FindFile};

use crate::session::Session;

Expand Down Expand Up @@ -180,19 +180,18 @@ impl LanguageServer for Backend {
let position = params.text_document_position_params.position;

// Convert the URI to a file path and back to a URL to ensure that the URI is formatted correctly for Windows.
let file_name = url_to_file_path(&uri).ok_or_else(Error::internal_error)?;
let url = uri
.to_file_path()
.and_then(Url::from_file_path)
.map_err(|_| Error::internal_error())?;
let file_path = url_to_sanitized_file_path(&uri).ok_or_else(Error::internal_error)?;

// Find the configuration set that contains the file
let configuration_sets = self.session.configuration_sets.lock().await;
let compilation_data = configuration_sets.iter().find_file(&file_name);
let compilation_data = configuration_sets.iter().find_file(&file_path);

// Get the definition span and convert it to a GotoDefinitionResponse
compilation_data
.and_then(|data| get_definition_span(data, url, position))
.and_then(|data| {
let file = data.files.get(&file_path).expect("mismatch in file name occurred during goto request");
get_definition_span(file, position)
})
.and_then(|location| {
let start = Position {
line: (location.start.row - 1) as u32,
Expand All @@ -202,7 +201,7 @@ impl LanguageServer for Backend {
line: (location.end.row - 1) as u32,
character: (location.end.col - 1) as u32,
};
Url::from_file_path(location.file).ok().map(|uri| {
convert_slice_url_to_uri(&location.file).map(|uri| {
GotoDefinitionResponse::Scalar(Location {
uri,
range: Range::new(start, end),
Expand All @@ -217,31 +216,29 @@ impl LanguageServer for Backend {
let position = params.text_document_position_params.position;

// Convert the URI to a file path and back to a URL to ensure that the URI is formatted correctly for Windows.
let file_name = url_to_file_path(&uri).ok_or_else(Error::internal_error)?;
let url = uri
.to_file_path()
.and_then(Url::from_file_path)
.map_err(|_| Error::internal_error())?;
let file_path = url_to_sanitized_file_path(&uri).ok_or_else(Error::internal_error)?;

// Find the configuration set that contains the file and get the hover info
let configuration_sets = self.session.configuration_sets.lock().await;

Ok(configuration_sets
.iter()
.find_file(&file_name)
.and_then(|compilation_data| {
try_into_hover_result(compilation_data, url, position).ok()
.find_file(&file_path)
.and_then(|data| {
let file = data.files.get(&file_path).expect("mismatch in file name occurred during hover request");
try_into_hover_result(file, position).ok()
}))
}

async fn did_open(&self, params: DidOpenTextDocumentParams) {
if let Some(file_name) = url_to_file_path(&params.text_document.uri) {
self.handle_file_change(&file_name).await;
if let Some(file_path) = url_to_sanitized_file_path(&params.text_document.uri) {
self.handle_file_change(&file_path).await;
}
}

async fn did_save(&self, params: DidSaveTextDocumentParams) {
if let Some(file_name) = url_to_file_path(&params.text_document.uri) {
self.handle_file_change(&file_name).await;
if let Some(file_path) = url_to_sanitized_file_path(&params.text_document.uri) {
self.handle_file_change(&file_path).await;
}
}
}
Expand Down
15 changes: 9 additions & 6 deletions server/src/session.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// Copyright (c) ZeroC, Inc.

use crate::configuration_set::ConfigurationSet;
use crate::{
configuration_set::ConfigurationSet,
utils::{sanitize_path, url_to_sanitized_file_path},
};
use std::path::PathBuf;
use tokio::sync::{Mutex, RwLock};
use tower_lsp::lsp_types::{DidChangeConfigurationParams, Url};
use tower_lsp::lsp_types::DidChangeConfigurationParams;

pub struct Session {
/// This vector contains all of the configuration sets for the language server. Each element is a tuple containing
Expand Down Expand Up @@ -37,17 +40,17 @@ impl Session {
let built_in_slice_path = initialization_options
.as_ref()
.and_then(|opts| opts.get("builtInSlicePath"))
.and_then(|v| v.as_str().map(str::to_owned))
.and_then(|value| value.as_str())
.map(sanitize_path)
.expect("builtInSlicePath not found in initialization options");

// Use the root_uri if it exists temporarily as we cannot access configuration until
// after initialization. Additionally, LSP may provide the windows path with escaping or a lowercase
// drive letter. To fix this, we convert the path to a URL and then back to a path.
let root_path = params
.root_uri
.and_then(|uri| uri.to_file_path().ok())
.and_then(|path| Url::from_file_path(path).ok())
.and_then(|uri| uri.to_file_path().ok())
.and_then(|uri| url_to_sanitized_file_path(&uri))
.map(PathBuf::from)
.expect("`root_uri` was not sent by the client, or was malformed");

// Load any user configuration from the 'slice.configurations' option.
Expand Down
38 changes: 34 additions & 4 deletions server/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,42 @@ where
}
}

// This helper function converts a Url from tower_lsp into a string that can be used to
// retrieve a file from the compilation state from slicec.
pub fn url_to_sanitized_file_path(url: &Url) -> Option<String> {
let path = url.to_file_path().ok()?;
let path_string = path.to_str()?;
Some(sanitize_path(path_string))
}

pub fn convert_slice_url_to_uri(url: &str) -> Option<Url> {
Url::from_file_path(url).ok()
}

// This helper function converts a Url from tower_lsp into a string that can be used to
// retrieve a file from the compilation state from slicec.
pub fn url_to_file_path(url: &Url) -> Option<String> {
Some(url.to_file_path().ok()?.to_str()?.to_owned())
#[cfg(target_os = "windows")]
pub fn sanitize_path(s: &str) -> String {
use std::path::{Component, Prefix};

// Replace any forward-slashes with back-slashes.
let mut sanitized_path = s.replace('/', "\\");

// Check if the path begins with a disk prefix (windows), and if so, make sure it's capitalized.
if let Some(Component::Prefix(prefix)) = Path::new(&sanitized_path).components().next() {
if matches!(prefix.kind(), Prefix::Disk(_) | Prefix::VerbatimDisk(_)) {
// disk prefixes are always of the form 'C:' or '\\?\C:'
let colon_index = sanitized_path.find(':').expect("no colon in disk prefix");
let disk_prefix = sanitized_path.split_at_mut(colon_index).0;

// Windows disk prefixes only use ascii characters.
assert!(disk_prefix.is_ascii());
disk_prefix.make_ascii_uppercase()
}
}

sanitized_path
}

#[cfg(not(target_os = "windows"))]
pub fn sanitize_path(s: &str) -> String {
s.to_owned()
}

0 comments on commit 821d01d

Please sign in to comment.